mirror of
https://github.com/lllin000/PaperForge.git
synced 2026-07-22 06:50:53 +00:00
feat: skill auto-deploy in update + Copy Context as pure JS + shared skill_deploy service
- New services/skill_deploy.py: single source of truth for agent skill deployment - AGENT_CONFIGS with 9 platforms, all vault-local - deploy_skills() with install/update mode (overwrite flag) - Used by both setup wizard and update worker - update.py: _deploy_all_skills() after pip/git/zip update - setup_wizard.py: delegates skill deploy to shared service - config.py: add agent_platform default - main.js: Copy Context + Copy Collection Context now pure JS - Uses in-memory _cachedItems / _currentPaperEntry - No subprocess spawn, no timeout, no JSON parse errors - testable.js: remove needsKey/needsFilter from context actions - commands.test.mjs: update assertions for removed flags
This commit is contained in:
parent
be54b81499
commit
f885adae48
7 changed files with 376 additions and 73 deletions
|
|
@ -55,6 +55,7 @@ DEFAULT_CONFIG: dict[str, str] = {
|
|||
"base_dir": "Bases",
|
||||
"skill_dir": ".opencode/skills",
|
||||
"command_dir": ".opencode/command",
|
||||
"agent_platform": "opencode",
|
||||
}
|
||||
|
||||
# Environment variable name map — maps config key to PAPERFORGE_* env var
|
||||
|
|
|
|||
|
|
@ -1475,6 +1475,43 @@ class PaperForgeStatusView extends ItemView {
|
|||
modal.open();
|
||||
return;
|
||||
}
|
||||
// Pure JS: Copy Context (single paper) — uses in-memory entry, no subprocess
|
||||
if (a.id === 'paperforge-copy-context') {
|
||||
let entry = this._currentPaperEntry;
|
||||
if (!entry) {
|
||||
const file = this.app.workspace.getActiveFile();
|
||||
if (file) {
|
||||
const cache = this.app.metadataCache.getFileCache(file);
|
||||
const key = cache?.frontmatter?.zotero_key;
|
||||
if (key) entry = this._findEntry(key);
|
||||
}
|
||||
}
|
||||
if (entry) {
|
||||
navigator.clipboard.writeText(JSON.stringify(entry, null, 2)).then(() => {
|
||||
this._showMessage('[OK] ' + (entry.zotero_key || '') + ' copied to clipboard', 'ok');
|
||||
new Notice('[OK] Paper context copied to clipboard', 4000);
|
||||
}).catch(e => {
|
||||
this._showMessage('[!!] Clipboard failed: ' + e.message, 'error');
|
||||
});
|
||||
} else {
|
||||
this._showMessage('[!!] No paper entry found', 'error');
|
||||
new Notice('[!!] Open a paper note or select one in the dashboard first', 5000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Pure JS: Copy Collection Context — uses cached index, no subprocess
|
||||
if (a.id === 'paperforge-copy-collection-context') {
|
||||
const items = this._cachedItems || [];
|
||||
navigator.clipboard.writeText(JSON.stringify(items, null, 2)).then(() => {
|
||||
this._showMessage('[OK] ' + items.length + ' entries copied to clipboard', 'ok');
|
||||
new Notice('[OK] ' + items.length + ' papers copied to clipboard', 4000);
|
||||
}).catch(e => {
|
||||
this._showMessage('[!!] Clipboard failed: ' + e.message, 'error');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Guard: disabled actions show coming-soon notice
|
||||
if (a.disabled) {
|
||||
new Notice(`[i] ${a.disabledMsg || 'This action is not yet available.'}`, 6000);
|
||||
|
|
|
|||
|
|
@ -175,7 +175,6 @@ const ACTIONS = [
|
|||
desc: "Copy this paper\u2019s canonical index entry JSON to clipboard for AI use",
|
||||
icon: "\u2139",
|
||||
cmd: "context",
|
||||
needsKey: true,
|
||||
okMsg: "Context copied",
|
||||
},
|
||||
{
|
||||
|
|
@ -184,7 +183,6 @@ const ACTIONS = [
|
|||
desc: "Copy canonical index entries for all visible papers to clipboard",
|
||||
icon: "\u2261",
|
||||
cmd: "context",
|
||||
needsFilter: true,
|
||||
okMsg: "Collection context copied",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ describe('ACTIONS', () => {
|
|||
it('repair action is enabled (no disabled flag)', () => {
|
||||
expect(ACTIONS.find(a => a.id === 'paperforge-repair')?.disabled).toBeUndefined();
|
||||
});
|
||||
it('copy-context action has needsKey', () => {
|
||||
expect(ACTIONS.find(a => a.id === 'paperforge-copy-context')?.needsKey).toBe(true);
|
||||
it('copy-context action has no needsKey (pure JS)', () => {
|
||||
expect(ACTIONS.find(a => a.id === 'paperforge-copy-context')?.needsKey).toBeUndefined();
|
||||
});
|
||||
it('copy-collection-context action has needsFilter', () => {
|
||||
expect(ACTIONS.find(a => a.id === 'paperforge-copy-collection-context')?.needsFilter).toBe(true);
|
||||
it('copy-collection-context action has no needsFilter (pure JS)', () => {
|
||||
expect(ACTIONS.find(a => a.id === 'paperforge-copy-collection-context')?.needsFilter).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
261
paperforge/services/skill_deploy.py
Normal file
261
paperforge/services/skill_deploy.py
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
"""Skill deployment service — single source of truth for agent skill installation and updates.
|
||||
|
||||
Used by both setup wizard (install) and update worker (update).
|
||||
All deployments are vault-local only.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
# ── Agent Platform Configurations ──
|
||||
# Canonical source of truth. setup_wizard.py imports from here.
|
||||
|
||||
AGENT_CONFIGS = {
|
||||
"opencode": {
|
||||
"name": "OpenCode",
|
||||
"skill_dir": ".opencode/skills",
|
||||
"command_dir": ".opencode/command",
|
||||
"format": "flat_command",
|
||||
"prefix": "/",
|
||||
"config_file": None,
|
||||
},
|
||||
"claude": {
|
||||
"name": "Claude Code",
|
||||
"skill_dir": ".claude/skills",
|
||||
"format": "skill_directory",
|
||||
"prefix": "/",
|
||||
"config_file": ".claude/skills.json",
|
||||
},
|
||||
"codex": {
|
||||
"name": "Codex",
|
||||
"skill_dir": ".codex/skills",
|
||||
"format": "skill_directory",
|
||||
"prefix": "$",
|
||||
"config_file": None,
|
||||
},
|
||||
"cursor": {
|
||||
"name": "Cursor",
|
||||
"skill_dir": ".cursor/skills",
|
||||
"format": "skill_directory",
|
||||
"prefix": "/",
|
||||
"config_file": ".cursor/settings.json",
|
||||
},
|
||||
"windsurf": {
|
||||
"name": "Windsurf",
|
||||
"skill_dir": ".windsurf/skills",
|
||||
"format": "skill_directory",
|
||||
"prefix": "/",
|
||||
"config_file": None,
|
||||
},
|
||||
"github_copilot": {
|
||||
"name": "GitHub Copilot",
|
||||
"skill_dir": ".github/skills",
|
||||
"format": "skill_directory",
|
||||
"prefix": "/",
|
||||
"config_file": ".github/copilot-instructions.md",
|
||||
},
|
||||
"cline": {
|
||||
"name": "Cline",
|
||||
"skill_dir": ".clinerules",
|
||||
"format": "rules_file",
|
||||
"prefix": "/",
|
||||
"config_file": ".clinerules",
|
||||
},
|
||||
"augment": {
|
||||
"name": "Augment",
|
||||
"skill_dir": ".augment/skills",
|
||||
"format": "skill_directory",
|
||||
"prefix": "/",
|
||||
"config_file": None,
|
||||
},
|
||||
"trae": {
|
||||
"name": "Trae",
|
||||
"skill_dir": ".trae/skills",
|
||||
"format": "skill_directory",
|
||||
"prefix": "/",
|
||||
"config_file": None,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _resolve_source_root() -> Path:
|
||||
"""Resolve the paperforge package root (where skills/ lives)."""
|
||||
import paperforge
|
||||
return Path(paperforge.__file__).parent
|
||||
|
||||
|
||||
def _substitute_vars(text: str, system_dir: str, resources_dir: str, literature_dir: str, base_dir: str, skill_dir: str, prefix: str = "/") -> str:
|
||||
for old, new in [
|
||||
("<system_dir>", system_dir),
|
||||
("<resources_dir>", resources_dir),
|
||||
("<literature_dir>", literature_dir),
|
||||
("<base_dir>", base_dir),
|
||||
("<skill_dir>", skill_dir),
|
||||
("<prefix>", prefix),
|
||||
]:
|
||||
text = text.replace(old, new)
|
||||
return text
|
||||
|
||||
|
||||
# ── Deploy helpers ──
|
||||
|
||||
def _deploy_skill_directory(vault: Path, skill_dir: str, source_root: Path, system_dir: str, resources_dir: str, literature_dir: str, base_dir: str, prefix: str = "/", overwrite: bool = False) -> list[str]:
|
||||
"""Deploy pf-* skills as independent SKILL.md directories (Claude Code, Codex, Cursor, etc.)."""
|
||||
imported = []
|
||||
src_scripts = source_root / "skills" / "literature-qa" / "scripts"
|
||||
src_charts = source_root / "skills" / "literature-qa" / "chart-reading"
|
||||
src_prompt = source_root / "skills" / "literature-qa" / "prompt_deep_subagent.md"
|
||||
|
||||
for skill_file in sorted(src_scripts.glob("pf-*.md")):
|
||||
skill_name = skill_file.stem
|
||||
skill_dst = vault / skill_dir / skill_name
|
||||
skill_dst.mkdir(parents=True, exist_ok=True)
|
||||
text = skill_file.read_text(encoding="utf-8")
|
||||
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, base_dir, skill_dir, prefix)
|
||||
dst_file = skill_dst / "SKILL.md"
|
||||
if overwrite or not dst_file.exists():
|
||||
dst_file.write_text(text, encoding="utf-8")
|
||||
imported.append(skill_name)
|
||||
|
||||
# pf-deep extras: scripts, chart-reading, subagent prompt
|
||||
pf_deep_dst = vault / skill_dir / "pf-deep"
|
||||
pf_deep_dst.mkdir(parents=True, exist_ok=True)
|
||||
ld_dst = pf_deep_dst / "scripts" / "ld_deep.py"
|
||||
ld_src = src_scripts / "ld_deep.py"
|
||||
if ld_src.exists() and (overwrite or not ld_dst.exists()):
|
||||
ld_dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(ld_src, ld_dst)
|
||||
prompt_dst = pf_deep_dst / "prompt_deep_subagent.md"
|
||||
if src_prompt.exists() and (overwrite or not prompt_dst.exists()):
|
||||
shutil.copy2(src_prompt, prompt_dst)
|
||||
if src_charts.exists() and src_charts.is_dir():
|
||||
chart_dst = pf_deep_dst / "chart-reading"
|
||||
if overwrite and chart_dst.exists():
|
||||
shutil.rmtree(chart_dst)
|
||||
chart_dst.mkdir(parents=True, exist_ok=True)
|
||||
for f in src_charts.glob("*.md"):
|
||||
if overwrite or not (chart_dst / f.name).exists():
|
||||
shutil.copy2(f, chart_dst / f.name)
|
||||
|
||||
return imported
|
||||
|
||||
|
||||
def _deploy_flat_command(vault: Path, command_dir: str, source_root: Path, system_dir: str, resources_dir: str, literature_dir: str, base_dir: str, skill_dir: str, overwrite: bool = False) -> list[str]:
|
||||
"""Deploy skills in flat .md command format (OpenCode)."""
|
||||
imported = []
|
||||
command_src = source_root / "skills" / "literature-qa" / "scripts"
|
||||
command_dst = vault / command_dir
|
||||
if not (command_src.exists() and command_src.is_dir()):
|
||||
return imported
|
||||
|
||||
command_dst.mkdir(parents=True, exist_ok=True)
|
||||
for f in command_src.glob("pf-*.md"):
|
||||
text = f.read_text(encoding="utf-8")
|
||||
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, base_dir, skill_dir)
|
||||
dst_file = command_dst / f.name
|
||||
if overwrite or not dst_file.exists():
|
||||
dst_file.write_text(text, encoding="utf-8")
|
||||
imported.append(f.stem)
|
||||
|
||||
return imported
|
||||
|
||||
|
||||
def _deploy_rules_file(vault: Path, skill_dir: str, source_root: Path, system_dir: str, resources_dir: str, literature_dir: str, base_dir: str, overwrite: bool = False) -> list[str]:
|
||||
"""Deploy skills as .clinerules directory (Cline)."""
|
||||
imported = []
|
||||
src_scripts = source_root / "skills" / "literature-qa" / "scripts"
|
||||
src_charts = source_root / "skills" / "literature-qa" / "chart-reading"
|
||||
src_prompt = source_root / "skills" / "literature-qa" / "prompt_deep_subagent.md"
|
||||
|
||||
pf_deep_dst = vault / skill_dir / "pf-deep"
|
||||
pf_deep_dst.mkdir(parents=True, exist_ok=True)
|
||||
ld_src = src_scripts / "ld_deep.py"
|
||||
ld_dst = pf_deep_dst / "scripts" / "ld_deep.py"
|
||||
if ld_src.exists() and (overwrite or not ld_dst.exists()):
|
||||
ld_dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(ld_src, ld_dst)
|
||||
prompt_dst = pf_deep_dst / "prompt_deep_subagent.md"
|
||||
if src_prompt.exists() and (overwrite or not prompt_dst.exists()):
|
||||
shutil.copy2(src_prompt, prompt_dst)
|
||||
if src_charts.exists() and src_charts.is_dir():
|
||||
chart_dst = pf_deep_dst / "chart-reading"
|
||||
if overwrite and chart_dst.exists():
|
||||
shutil.rmtree(chart_dst)
|
||||
chart_dst.mkdir(parents=True, exist_ok=True)
|
||||
for f in src_charts.glob("*.md"):
|
||||
if overwrite or not (chart_dst / f.name).exists():
|
||||
shutil.copy2(f, chart_dst / f.name)
|
||||
|
||||
imported.append("clinerules")
|
||||
return imported
|
||||
|
||||
|
||||
# ── Main entry point ──
|
||||
|
||||
def deploy_skills(
|
||||
vault: Path,
|
||||
agent_key: str = "opencode",
|
||||
system_dir: str = "System",
|
||||
resources_dir: str = "Resources",
|
||||
literature_dir: str = "Literature",
|
||||
base_dir: str = "Bases",
|
||||
overwrite: bool = False,
|
||||
) -> dict:
|
||||
"""Deploy skills, commands, and AGENTS.md for a given agent platform.
|
||||
|
||||
Args:
|
||||
vault: Obsidian vault root
|
||||
agent_key: Agent platform key (opencode, cursor, claude, etc.)
|
||||
overwrite: True for update (overwrite existing), False for install (skip)
|
||||
|
||||
Returns:
|
||||
dict with 'skills', 'commands', 'agents_md', 'errors' keys
|
||||
"""
|
||||
agent_config = AGENT_CONFIGS.get(agent_key)
|
||||
if not agent_config:
|
||||
return {"skills": [], "commands": [], "agents_md": False, "errors": [f"Unknown agent: {agent_key}"]}
|
||||
|
||||
source_root = _resolve_source_root()
|
||||
if not (source_root / "skills" / "literature-qa").exists():
|
||||
return {"skills": [], "commands": [], "agents_md": False, "errors": ["Skills source not found in package"]}
|
||||
|
||||
skill_dir = agent_config.get("skill_dir", ".opencode/skills")
|
||||
fmt = agent_config.get("format", "skill_directory")
|
||||
prefix = agent_config.get("prefix", "/")
|
||||
imported_skills: list[str] = []
|
||||
errors: list[str] = []
|
||||
|
||||
# Deploy skills by format
|
||||
try:
|
||||
if fmt == "flat_command":
|
||||
imported_skills = _deploy_flat_command(vault, agent_config["command_dir"], source_root, system_dir, resources_dir, literature_dir, base_dir, skill_dir, overwrite)
|
||||
imported_skills += _deploy_skill_directory(vault, skill_dir, source_root, system_dir, resources_dir, literature_dir, base_dir, prefix, overwrite)
|
||||
elif fmt == "rules_file":
|
||||
imported_skills = _deploy_rules_file(vault, agent_config["skill_dir"], source_root, system_dir, resources_dir, literature_dir, base_dir, overwrite)
|
||||
else:
|
||||
imported_skills = _deploy_skill_directory(vault, skill_dir, source_root, system_dir, resources_dir, literature_dir, base_dir, prefix, overwrite)
|
||||
except Exception as e:
|
||||
errors.append(f"Skill deploy failed: {e}")
|
||||
|
||||
# Deploy AGENTS.md
|
||||
agents_ok = False
|
||||
agents_src = source_root.parent / "AGENTS.md"
|
||||
if agents_src.exists():
|
||||
try:
|
||||
agents_dst = vault / "AGENTS.md"
|
||||
text = agents_src.read_text(encoding="utf-8")
|
||||
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, base_dir, skill_dir, prefix)
|
||||
if overwrite or not agents_dst.exists():
|
||||
agents_dst.write_text(text, encoding="utf-8")
|
||||
agents_ok = True
|
||||
except Exception as e:
|
||||
errors.append(f"AGENTS.md deploy failed: {e}")
|
||||
|
||||
return {
|
||||
"skills": imported_skills,
|
||||
"commands": imported_skills,
|
||||
"agents_md": agents_ok,
|
||||
"errors": errors,
|
||||
}
|
||||
|
|
@ -846,60 +846,31 @@ def headless_setup(
|
|||
_copy_file_incremental(mod_src, pf_path / "worker/scripts" / mod)
|
||||
print(f" [OK] worker scripts")
|
||||
|
||||
# Deploy skills based on agent format
|
||||
fmt = agent_config.get("format", "skill_directory")
|
||||
prefix = agent_config.get("prefix", "/")
|
||||
imported_skills = []
|
||||
# Deploy skills via shared service (single source of truth)
|
||||
from paperforge.services.skill_deploy import deploy_skills as _deploy_skills_service
|
||||
|
||||
if fmt == "flat_command":
|
||||
imported_skills = _deploy_flat_command(
|
||||
vault,
|
||||
agent_config["command_dir"],
|
||||
repo_root,
|
||||
system_dir,
|
||||
resources_dir,
|
||||
literature_dir,
|
||||
base_dir,
|
||||
skill_dir,
|
||||
)
|
||||
# OpenCode also needs the skill directory (ld_deep.py, prompt, chart-reading)
|
||||
imported_skills += _deploy_skill_directory(
|
||||
vault,
|
||||
skill_dir,
|
||||
repo_root,
|
||||
system_dir,
|
||||
resources_dir,
|
||||
literature_dir,
|
||||
base_dir,
|
||||
prefix,
|
||||
)
|
||||
elif fmt == "rules_file":
|
||||
imported_skills = _deploy_rules_file(
|
||||
vault,
|
||||
agent_config["skill_dir"],
|
||||
repo_root,
|
||||
system_dir,
|
||||
resources_dir,
|
||||
literature_dir,
|
||||
base_dir,
|
||||
skill_dir,
|
||||
)
|
||||
else:
|
||||
# skill_directory (default)
|
||||
imported_skills = _deploy_skill_directory(
|
||||
vault,
|
||||
skill_dir,
|
||||
repo_root,
|
||||
system_dir,
|
||||
resources_dir,
|
||||
literature_dir,
|
||||
base_dir,
|
||||
prefix,
|
||||
)
|
||||
skill_result = _deploy_skills_service(
|
||||
vault=vault,
|
||||
agent_key=agent_key,
|
||||
system_dir=system_dir,
|
||||
resources_dir=resources_dir,
|
||||
literature_dir=literature_dir,
|
||||
base_dir=base_dir,
|
||||
overwrite=False,
|
||||
)
|
||||
imported_skills = skill_result["skills"]
|
||||
for err in skill_result.get("errors", []):
|
||||
print(f" [WARN] {err}")
|
||||
|
||||
if imported_skills:
|
||||
print(f" [OK] {len(imported_skills)} skill(s): {', '.join(imported_skills)}")
|
||||
|
||||
# AGENTS.md
|
||||
if skill_result["agents_md"]:
|
||||
print(f" [OK] AGENTS.md")
|
||||
else:
|
||||
print(f" [WARN] AGENTS.md source not found; skipping")
|
||||
|
||||
# Create agent config file if defined (e.g., Claude skills.json)
|
||||
config_file = agent_config.get("config_file")
|
||||
if config_file:
|
||||
|
|
@ -937,24 +908,6 @@ def headless_setup(
|
|||
else:
|
||||
print(f" [WARN] Plugin source not found: {plugin_src}")
|
||||
|
||||
# AGENTS.md
|
||||
agents_src = repo_root / "AGENTS.md"
|
||||
agents_dst = vault / "AGENTS.md"
|
||||
if agents_src.exists():
|
||||
text = agents_src.read_text(encoding="utf-8")
|
||||
for old, new in [
|
||||
("<system_dir>", system_dir),
|
||||
("<resources_dir>", resources_dir),
|
||||
("<literature_dir>", literature_dir),
|
||||
("<base_dir>", base_dir),
|
||||
("<skill_dir>", skill_dir),
|
||||
]:
|
||||
text = text.replace(old, new)
|
||||
_write_text_incremental(agents_dst, text)
|
||||
print(f" [OK] AGENTS.md")
|
||||
else:
|
||||
print(f" [WARN] AGENTS.md source not found; skipping")
|
||||
|
||||
# =========================================================================
|
||||
# Phase 5: Create config files
|
||||
# =========================================================================
|
||||
|
|
|
|||
|
|
@ -34,6 +34,31 @@ def _sync_obsidian_plugin(vault: Path) -> None:
|
|||
_pf_utils.install_obsidian_plugin(vault)
|
||||
|
||||
|
||||
def _deploy_skills(vault: Path) -> None:
|
||||
"""Copy the literature-qa skill from the package to the vault's agent dir."""
|
||||
import paperforge
|
||||
|
||||
src = Path(paperforge.__file__).parent / "skills" / "literature-qa"
|
||||
if not src.exists():
|
||||
logger.debug("Skills source not found: %s", src)
|
||||
return
|
||||
|
||||
raw = read_paperforge_json(vault)
|
||||
agent = raw.get("agent_platform", "opencode")
|
||||
agent_dirs = {
|
||||
"opencode": ".opencode/skills/literature-qa",
|
||||
"claude": ".claude/skills/literature-qa",
|
||||
"cursor": ".cursor/skills/literature-qa",
|
||||
"copilot": ".github/skills/literature-qa",
|
||||
"windsurf": ".windsurf/skills/literature-qa",
|
||||
"codex": ".codex/skills/literature-qa",
|
||||
}
|
||||
target_rel = agent_dirs.get(agent, agent_dirs["opencode"])
|
||||
dst = vault / target_rel
|
||||
shutil.copytree(src, dst, dirs_exist_ok=True)
|
||||
logger.info("Skills deployed: %s -> %s", src, dst)
|
||||
|
||||
|
||||
def protected_paths(vault: Path) -> set[str]:
|
||||
cfg = load_vault_config(vault)
|
||||
pf = f"{cfg['system_dir']}/PaperForge"
|
||||
|
|
@ -237,6 +262,32 @@ def update_via_zip(vault: Path) -> bool:
|
|||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
|
||||
|
||||
def _deploy_all_skills(vault: Path) -> None:
|
||||
"""Deploy latest skills and AGENTS.md to vault after update."""
|
||||
try:
|
||||
from paperforge.services.skill_deploy import deploy_skills
|
||||
from paperforge.config import load_vault_config
|
||||
|
||||
config = load_vault_config(vault)
|
||||
result = deploy_skills(
|
||||
vault=vault,
|
||||
agent_key=config.get("agent_platform", "opencode"),
|
||||
system_dir=config.get("system_dir", "System"),
|
||||
resources_dir=config.get("resources_dir", "Resources"),
|
||||
literature_dir=config.get("literature_dir", "Literature"),
|
||||
base_dir=config.get("base_dir", "Bases"),
|
||||
overwrite=True,
|
||||
)
|
||||
if result["skills"]:
|
||||
logger.info("已部署 %d 个 skill: %s", len(result["skills"]), ", ".join(result["skills"]))
|
||||
if result["agents_md"]:
|
||||
logger.info("已更新 AGENTS.md")
|
||||
for err in result.get("errors", []):
|
||||
logger.warning("Skill 部署警告: %s", err)
|
||||
except Exception as e:
|
||||
logger.warning("Skill 部署失败(非致命): %s", e)
|
||||
|
||||
|
||||
def run_update(vault: Path) -> int:
|
||||
"""运行更新检查与安装"""
|
||||
try:
|
||||
|
|
@ -262,6 +313,7 @@ def run_update(vault: Path) -> int:
|
|||
needs = remote != local
|
||||
if not needs:
|
||||
_sync_obsidian_plugin(vault)
|
||||
_deploy_all_skills(vault)
|
||||
logger.info("当前已是最新版本")
|
||||
return 0
|
||||
logger.info("发现新版本: %s -> %s", local, remote)
|
||||
|
|
@ -298,6 +350,7 @@ def run_update(vault: Path) -> int:
|
|||
|
||||
if success:
|
||||
_sync_obsidian_plugin(vault)
|
||||
_deploy_all_skills(vault)
|
||||
logger.info("更新完成!请重启 Obsidian")
|
||||
return 0 if success else 1
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue