@sakanano-moe/agenthelm 6.5.2 → 6.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/runtime/agenthelm.py +69 -27
package/package.json
CHANGED
package/runtime/agenthelm.py
CHANGED
|
@@ -255,7 +255,9 @@ def plugin_findings(latest: dict, installed: list[dict]) -> list[dict]:
|
|
|
255
255
|
for item in installed:
|
|
256
256
|
target = latest.get("plugins", {}).get(item["plugin"], {}).get("version")
|
|
257
257
|
current = item.get("version") or ""
|
|
258
|
-
if
|
|
258
|
+
if item["client"] == "legacy":
|
|
259
|
+
status = "legacy" # a pre-marketplace copy: it gets migrated, not updated
|
|
260
|
+
elif target is None:
|
|
259
261
|
status = "unknown"
|
|
260
262
|
elif is_newer(target, current):
|
|
261
263
|
status = "behind"
|
|
@@ -421,7 +423,11 @@ def render_text(report: dict) -> str:
|
|
|
421
423
|
if not report["plugins"]:
|
|
422
424
|
lines.append("未发现已安装的 agenthelm 插件。")
|
|
423
425
|
for item in report["plugins"]:
|
|
424
|
-
label = {"behind": "可更新", "current": "最新", "ahead": "高于云端", "unknown": "版本未知"
|
|
426
|
+
label = {"behind": "可更新", "current": "最新", "ahead": "高于云端", "unknown": "版本未知",
|
|
427
|
+
"legacy": "旧方式安装,待迁移"}[item["status"]]
|
|
428
|
+
if item["status"] == "legacy":
|
|
429
|
+
lines.append(f" [旧方式] {item['plugin']} {item.get('version')} {label}")
|
|
430
|
+
continue
|
|
425
431
|
line = f" [{item['client']}] {item['plugin']} {item.get('version')} → {item.get('latest')} {label}"
|
|
426
432
|
if item["status"] == "behind":
|
|
427
433
|
kinds = {"code": "代码", "content": "仅模板/样例", "both": "代码与模板"}
|
|
@@ -436,8 +442,6 @@ def render_text(report: dict) -> str:
|
|
|
436
442
|
hint = UPDATE_HINTS.get(item["client"])
|
|
437
443
|
if hint:
|
|
438
444
|
line += f"\n 更新:{hint.format(plugin=item['plugin'])}"
|
|
439
|
-
elif item["client"] == "legacy":
|
|
440
|
-
line += f"\n 旧方式安装:{UPDATE_HINTS['legacy']}"
|
|
441
445
|
lines.append(line)
|
|
442
446
|
for item in report["content"]:
|
|
443
447
|
if item.get("pack") is None:
|
|
@@ -458,6 +462,8 @@ def render_text(report: dict) -> str:
|
|
|
458
462
|
if not report["plugins"]:
|
|
459
463
|
lines.append(f"运行 `{CLI} install` 预览安装。")
|
|
460
464
|
else:
|
|
465
|
+
if any(item["status"] == "legacy" for item in report["plugins"]):
|
|
466
|
+
lines.append(f"旧方式安装可用 `{UPDATE_HINTS['legacy']}` 预览迁移。")
|
|
461
467
|
lines.append(one_line(report) or "全部为最新。")
|
|
462
468
|
return "\n".join(lines) + "\n"
|
|
463
469
|
|
|
@@ -512,9 +518,9 @@ def github_slug(latest: dict) -> str:
|
|
|
512
518
|
|
|
513
519
|
def detect_clients(env: Env) -> list[str]:
|
|
514
520
|
found = []
|
|
515
|
-
if
|
|
521
|
+
if find_cli(env, "claude") or env.claude_dir.is_dir():
|
|
516
522
|
found.append("claude")
|
|
517
|
-
if
|
|
523
|
+
if find_cli(env, "codex") or env.codex_dir.is_dir():
|
|
518
524
|
found.append("codex")
|
|
519
525
|
if (env.home / ".cursor").is_dir():
|
|
520
526
|
found.append("cursor")
|
|
@@ -608,8 +614,34 @@ class Step:
|
|
|
608
614
|
return data
|
|
609
615
|
|
|
610
616
|
|
|
611
|
-
def
|
|
612
|
-
|
|
617
|
+
def known_cli_locations(env: Env, name: str) -> list[Path]:
|
|
618
|
+
"""Where a client's command line lives when it is not on PATH (GUI apps, per-user installs)."""
|
|
619
|
+
home = env.home
|
|
620
|
+
if name == "codex":
|
|
621
|
+
bundle = "ChatGPT.app/Contents/Resources/codex-cli/bin/codex"
|
|
622
|
+
return [Path("/Applications") / bundle, home / "Applications" / bundle,
|
|
623
|
+
home / ".local/bin/codex", Path("/opt/homebrew/bin/codex"), Path("/usr/local/bin/codex")]
|
|
624
|
+
return [home / ".local/bin" / name, Path("/opt/homebrew/bin") / name, Path("/usr/local/bin") / name]
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def find_cli(env: Env, name: str) -> str | None:
|
|
628
|
+
"""A client's command line: ``AGENTHELM_<NAME>`` override, then PATH, then the usual app locations."""
|
|
629
|
+
override = env.get(f"AGENTHELM_{name.upper()}")
|
|
630
|
+
if override and os.access(override, os.X_OK):
|
|
631
|
+
return override
|
|
632
|
+
on_path = shutil.which(name, path=env.get("PATH") or "")
|
|
633
|
+
if on_path:
|
|
634
|
+
return on_path
|
|
635
|
+
for candidate in known_cli_locations(env, name):
|
|
636
|
+
if candidate.is_file() and os.access(candidate, os.X_OK):
|
|
637
|
+
return str(candidate)
|
|
638
|
+
return None
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _cli(env: Env, name: str, *args: str) -> tuple[list[str], str | None]:
|
|
642
|
+
"""The command (using the resolved executable) and why it cannot run, if it cannot."""
|
|
643
|
+
found = find_cli(env, name)
|
|
644
|
+
return [found or name, *args], (None if found else f"未找到 {name} 命令(已查 PATH 与常见安装位置;可用 AGENTHELM_{name.upper()} 指定)")
|
|
613
645
|
|
|
614
646
|
|
|
615
647
|
def install_steps(env: Env, latest: dict, clients: list[str], plugins: list[str], source: Path | None,
|
|
@@ -618,17 +650,17 @@ def install_steps(env: Env, latest: dict, clients: list[str], plugins: list[str]
|
|
|
618
650
|
steps: list[Step] = []
|
|
619
651
|
for client in clients:
|
|
620
652
|
if client == "claude":
|
|
621
|
-
|
|
622
|
-
|
|
653
|
+
command, blocked = _cli(env, "claude", "plugin", "marketplace", "add", slug)
|
|
654
|
+
steps.append(Step("install", "claude", f"Claude Code 添加 agenthelm 市场({slug})", command, blocked=blocked))
|
|
623
655
|
for plugin in plugins:
|
|
624
|
-
|
|
625
|
-
|
|
656
|
+
command, blocked = _cli(env, "claude", "plugin", "install", f"{plugin}@{MARKETPLACE}")
|
|
657
|
+
steps.append(Step("install", "claude", f"Claude Code 安装 {plugin}", command, blocked=blocked))
|
|
626
658
|
elif client == "codex":
|
|
627
|
-
|
|
628
|
-
|
|
659
|
+
command, blocked = _cli(env, "codex", "plugin", "marketplace", "add", slug)
|
|
660
|
+
steps.append(Step("install", "codex", f"Codex 添加 agenthelm 市场({slug})", command, blocked=blocked))
|
|
629
661
|
for plugin in plugins:
|
|
630
|
-
|
|
631
|
-
|
|
662
|
+
command, blocked = _cli(env, "codex", "plugin", "add", f"{plugin}@{MARKETPLACE}")
|
|
663
|
+
steps.append(Step("install", "codex", f"Codex 安装 {plugin}", command, blocked=blocked))
|
|
632
664
|
elif client == "cursor":
|
|
633
665
|
steps.append(cursor_step(latest, plugins, source, home, "安装"))
|
|
634
666
|
return steps
|
|
@@ -655,17 +687,16 @@ def update_steps(env: Env, report: dict, project: Path | None, source: Path | No
|
|
|
655
687
|
if only in (None, "plugins"):
|
|
656
688
|
claude = sorted({item["plugin"] for item in behind if item["client"] == "claude"})
|
|
657
689
|
if claude:
|
|
658
|
-
|
|
659
|
-
|
|
690
|
+
command, blocked = _cli(env, "claude", "plugin", "marketplace", "update", MARKETPLACE)
|
|
691
|
+
steps.append(Step("update", "claude", "刷新 Claude Code 的 agenthelm 市场", command, blocked=blocked))
|
|
660
692
|
for item in [entry for entry in behind if entry["client"] == "claude"]:
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
command += ["--scope", item["scope"]]
|
|
693
|
+
extra = ["--scope", item["scope"]] if item.get("scope") and item["scope"] != "user" else []
|
|
694
|
+
command, blocked = _cli(env, "claude", "plugin", "update", f"{item['plugin']}@{MARKETPLACE}", *extra)
|
|
664
695
|
steps.append(Step("update", "claude", f"Claude Code 更新 {item['plugin']} → {item['latest']}",
|
|
665
|
-
command, blocked=
|
|
696
|
+
command, blocked=blocked))
|
|
666
697
|
if any(item["client"] == "codex" for item in behind):
|
|
667
|
-
|
|
668
|
-
|
|
698
|
+
command, blocked = _cli(env, "codex", "plugin", "marketplace", "upgrade", MARKETPLACE)
|
|
699
|
+
steps.append(Step("update", "codex", "Codex 升级 agenthelm 市场与已装插件", command, blocked=blocked))
|
|
669
700
|
cursor = sorted({item["plugin"] for item in behind if item["client"] == "cursor"})
|
|
670
701
|
if cursor:
|
|
671
702
|
steps.append(cursor_step(latest, cursor, source, home, "更新"))
|
|
@@ -799,6 +830,10 @@ def legacy_artifacts(env: Env, names: set[str]) -> list[dict]:
|
|
|
799
830
|
"path": str(env.home / ".agents/plugins/marketplace.json")})
|
|
800
831
|
for name in _codex_personal_sections(env, names):
|
|
801
832
|
artifacts.append({"kind": "codex-config-section", "name": name, "path": str(env.codex_dir / "config.toml")})
|
|
833
|
+
for name in sorted(names):
|
|
834
|
+
cache = env.codex_dir / "plugins" / "cache" / "personal" / name
|
|
835
|
+
if cache.is_dir() and not cache.is_symlink():
|
|
836
|
+
artifacts.append({"kind": "codex-personal-cache", "name": name, "path": str(cache)})
|
|
802
837
|
try:
|
|
803
838
|
servers = read_json(env.home / ".claude.json").get("mcpServers", {})
|
|
804
839
|
except (OSError, ValueError, AttributeError):
|
|
@@ -827,10 +862,17 @@ def retire_legacy(env: Env, artifacts: list[dict], recovery: Path) -> list[dict]
|
|
|
827
862
|
done: list[dict] = []
|
|
828
863
|
backed_up: set[str] = set()
|
|
829
864
|
|
|
865
|
+
def inside_recovery(path: Path) -> Path:
|
|
866
|
+
"""The path below the recovery folder: home-relative, or absolute for locations outside home."""
|
|
867
|
+
try:
|
|
868
|
+
return path.relative_to(env.home)
|
|
869
|
+
except ValueError:
|
|
870
|
+
return Path("external") / str(path).lstrip("/")
|
|
871
|
+
|
|
830
872
|
def backup(path: Path) -> None:
|
|
831
873
|
if str(path) in backed_up:
|
|
832
874
|
return
|
|
833
|
-
target = recovery / "files" / path
|
|
875
|
+
target = recovery / "files" / inside_recovery(path)
|
|
834
876
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
835
877
|
shutil.copy2(path, target)
|
|
836
878
|
backed_up.add(str(path))
|
|
@@ -840,8 +882,8 @@ def retire_legacy(env: Env, artifacts: list[dict], recovery: Path) -> list[dict]
|
|
|
840
882
|
if item.get("manual"):
|
|
841
883
|
done.append({**item, "status": "manual"})
|
|
842
884
|
continue
|
|
843
|
-
if item["kind"] in {"plugin-copy", "plugin-record", "claude-skill-link"}:
|
|
844
|
-
target = recovery / "moved" / path
|
|
885
|
+
if item["kind"] in {"plugin-copy", "plugin-record", "claude-skill-link", "codex-personal-cache"}:
|
|
886
|
+
target = recovery / "moved" / inside_recovery(path)
|
|
845
887
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
846
888
|
os.replace(path, target)
|
|
847
889
|
done.append({**item, "status": "moved", "to": str(target)})
|