@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sakanano-moe/agenthelm",
3
- "version": "6.5.2",
3
+ "version": "6.5.3",
4
4
  "description": "agenthelm 命令行:在 Claude Code、Codex、Cursor 间安装、检查与更新 agenthelm 项目管理插件。",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -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 target is None:
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": "版本未知"}[item["status"]]
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 shutil.which("claude") or env.claude_dir.is_dir():
521
+ if find_cli(env, "claude") or env.claude_dir.is_dir():
516
522
  found.append("claude")
517
- if shutil.which("codex") or env.codex_dir.is_dir():
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 _need(binary: str) -> str | None:
612
- return None if shutil.which(binary) else f"未找到 {binary} 命令"
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
- steps.append(Step("install", "claude", f"Claude Code 添加 agenthelm 市场({slug})",
622
- ["claude", "plugin", "marketplace", "add", slug], blocked=_need("claude")))
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
- steps.append(Step("install", "claude", f"Claude Code 安装 {plugin}",
625
- ["claude", "plugin", "install", f"{plugin}@{MARKETPLACE}"], blocked=_need("claude")))
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
- steps.append(Step("install", "codex", f"Codex 添加 agenthelm 市场({slug})",
628
- ["codex", "plugin", "marketplace", "add", slug], blocked=_need("codex")))
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
- steps.append(Step("install", "codex", f"Codex 安装 {plugin}",
631
- ["codex", "plugin", "add", f"{plugin}@{MARKETPLACE}"], blocked=_need("codex")))
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
- steps.append(Step("update", "claude", "刷新 Claude Code 的 agenthelm 市场",
659
- ["claude", "plugin", "marketplace", "update", MARKETPLACE], blocked=_need("claude")))
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
- command = ["claude", "plugin", "update", f"{item['plugin']}@{MARKETPLACE}"]
662
- if item.get("scope") and item["scope"] != "user":
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=_need("claude")))
696
+ command, blocked=blocked))
666
697
  if any(item["client"] == "codex" for item in behind):
667
- steps.append(Step("update", "codex", "Codex 升级 agenthelm 市场与已装插件",
668
- ["codex", "plugin", "marketplace", "upgrade", MARKETPLACE], blocked=_need("codex")))
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.relative_to(env.home)
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.relative_to(env.home)
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)})