@sakanano-moe/agenthelm 6.5.3 → 6.7.0

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.
Files changed (112) hide show
  1. package/README.md +68 -0
  2. package/README.zh-CN.md +28 -0
  3. package/bin/agenthelm.js +1 -1
  4. package/package.json +2 -2
  5. package/runtime/index.json +80 -0
  6. package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +26 -0
  7. package/runtime/plugins/agent-pack/.mcp.json +12 -0
  8. package/runtime/plugins/agent-pack/hooks/hooks.json +16 -0
  9. package/runtime/plugins/agent-pack/mcp.json +14 -0
  10. package/runtime/plugins/agent-pack/plugin.json +20 -0
  11. package/runtime/plugins/agent-pack/references/capability-config.md +148 -0
  12. package/runtime/plugins/agent-pack/runtime/agent_pack_claude.py +448 -0
  13. package/runtime/{agent_pack_config.py → plugins/agent-pack/runtime/agent_pack_config.py} +178 -22
  14. package/runtime/{agenthelm.py → plugins/agent-pack/runtime/agenthelm.py} +42 -1
  15. package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +388 -0
  16. package/runtime/{mcp_server.py → plugins/agent-pack/runtime/mcp_server.py} +30 -8
  17. package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +46 -0
  18. package/runtime/plugins/agents-init/.codex-plugin/plugin.json +22 -0
  19. package/runtime/plugins/agents-init/content.json +31 -0
  20. package/runtime/plugins/agents-init/plugin.json +19 -0
  21. package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +376 -0
  22. package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +119 -0
  23. package/runtime/plugins/agents-init/skills/agents-init/index.html +357 -0
  24. package/runtime/plugins/agents-init/skills/agents-init/launcher.sh +93 -0
  25. package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +171 -0
  26. package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +1190 -0
  27. package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +678 -0
  28. package/runtime/plugins/agents-init/skills/agents-init/skill.json +122 -0
  29. package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +26 -0
  30. package/runtime/plugins/opinion-manager/catalog/README.md +15 -0
  31. package/runtime/plugins/opinion-manager/catalog/rules/.gitkeep +0 -0
  32. package/runtime/plugins/opinion-manager/catalog/rules/om.align-requirements/versions/1.0.0.json +11 -0
  33. package/runtime/plugins/opinion-manager/catalog/rules/om.assumptions-authority/versions/1.0.0.json +11 -0
  34. package/runtime/plugins/opinion-manager/catalog/rules/om.batch-questions/versions/1.0.0.json +11 -0
  35. package/runtime/plugins/opinion-manager/catalog/rules/om.bilingual-terms/versions/1.0.0.json +11 -0
  36. package/runtime/plugins/opinion-manager/catalog/rules/om.calculations/versions/1.0.0.json +11 -0
  37. package/runtime/plugins/opinion-manager/catalog/rules/om.chinese-response/versions/1.0.0.json +11 -0
  38. package/runtime/plugins/opinion-manager/catalog/rules/om.clean-deliverable/versions/1.0.0.json +11 -0
  39. package/runtime/plugins/opinion-manager/catalog/rules/om.complete-options/versions/1.0.0.json +11 -0
  40. package/runtime/plugins/opinion-manager/catalog/rules/om.conclusion-first/versions/1.0.0.json +11 -0
  41. package/runtime/plugins/opinion-manager/catalog/rules/om.concrete-titles/versions/1.0.0.json +11 -0
  42. package/runtime/plugins/opinion-manager/catalog/rules/om.consistent-items/versions/1.0.0.json +11 -0
  43. package/runtime/plugins/opinion-manager/catalog/rules/om.device-readable/versions/1.0.0.json +11 -0
  44. package/runtime/plugins/opinion-manager/catalog/rules/om.editable-diagrams/versions/1.0.0.json +11 -0
  45. package/runtime/plugins/opinion-manager/catalog/rules/om.evidence-inference/versions/1.0.0.json +11 -0
  46. package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-chinese/versions/1.0.0.json +11 -0
  47. package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-project-language/versions/1.0.0.json +11 -0
  48. package/runtime/plugins/opinion-manager/catalog/rules/om.markdown-structure/versions/1.0.0.json +11 -0
  49. package/runtime/plugins/opinion-manager/catalog/rules/om.portable-files/versions/1.0.0.json +11 -0
  50. package/runtime/plugins/opinion-manager/catalog/rules/om.report-context/versions/1.0.0.json +11 -0
  51. package/runtime/plugins/opinion-manager/catalog/rules/om.report-formats/versions/1.0.0.json +11 -0
  52. package/runtime/plugins/opinion-manager/catalog/rules/om.report-review/versions/1.0.0.json +11 -0
  53. package/runtime/plugins/opinion-manager/catalog/rules/om.response-brief/versions/1.0.0.json +11 -0
  54. package/runtime/plugins/opinion-manager/catalog/rules/om.response-dense/versions/1.0.0.json +11 -0
  55. package/runtime/plugins/opinion-manager/catalog/rules/om.rules-tools/versions/1.0.0.json +11 -0
  56. package/runtime/plugins/opinion-manager/catalog/rules/om.semantic-wrap/versions/1.0.0.json +11 -0
  57. package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-proportional/versions/1.0.0.json +11 -0
  58. package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-three-level/versions/1.0.0.json +11 -0
  59. package/runtime/plugins/opinion-manager/catalog/rules/om.tables-compact/versions/1.0.0.json +11 -0
  60. package/runtime/plugins/opinion-manager/catalog/rules/om.tables-list-first/versions/1.0.0.json +11 -0
  61. package/runtime/plugins/opinion-manager/catalog/rules/om.test-evidence/versions/1.0.0.json +11 -0
  62. package/runtime/plugins/opinion-manager/catalog/rules/om.versioned-files/versions/1.0.0.json +11 -0
  63. package/runtime/plugins/opinion-manager/catalog/templates/.gitkeep +0 -0
  64. package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/compact-tables/versions/1.0.0.json +21 -0
  65. package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/list-first/versions/1.0.0.json +21 -0
  66. package/runtime/plugins/opinion-manager/catalog/templates/development-standard/chinese-maintenance/versions/1.0.0.json +15 -0
  67. package/runtime/plugins/opinion-manager/catalog/templates/development-standard/project-language/versions/1.0.0.json +15 -0
  68. package/runtime/plugins/opinion-manager/catalog/templates/response-style/brief-chinese/versions/1.0.0.json +19 -0
  69. package/runtime/plugins/opinion-manager/catalog/templates/response-style/dense-chinese/versions/1.0.0.json +19 -0
  70. package/runtime/plugins/opinion-manager/catalog/templates/scenario/concise-project/versions/1.0.0.json +39 -0
  71. package/runtime/plugins/opinion-manager/catalog/templates/scenario/reviewed-project/versions/1.0.0.json +39 -0
  72. package/runtime/plugins/opinion-manager/catalog/templates/work-method/proportional/versions/1.0.0.json +20 -0
  73. package/runtime/plugins/opinion-manager/catalog/templates/work-method/three-level/versions/1.0.0.json +20 -0
  74. package/runtime/plugins/opinion-manager/content.json +63 -0
  75. package/runtime/plugins/opinion-manager/plugin.json +18 -0
  76. package/runtime/plugins/opinion-manager/references/lock.schema.json +14 -0
  77. package/runtime/plugins/opinion-manager/references/onboarding.md +57 -0
  78. package/runtime/plugins/opinion-manager/references/profile.schema.json +56 -0
  79. package/runtime/plugins/opinion-manager/references/rule.schema.json +16 -0
  80. package/runtime/plugins/opinion-manager/references/template.schema.json +71 -0
  81. package/runtime/plugins/opinion-manager/references/versioned-template.schema.json +19 -0
  82. package/runtime/plugins/opinion-manager/references/versioning.md +263 -0
  83. package/runtime/plugins/opinion-manager/runtime/constants.py +7 -0
  84. package/runtime/plugins/opinion-manager/runtime/context.py +180 -0
  85. package/runtime/plugins/opinion-manager/runtime/lifecycle.py +395 -0
  86. package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +419 -0
  87. package/runtime/plugins/opinion-manager/runtime/versioned.py +705 -0
  88. package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +62 -0
  89. package/runtime/plugins/opinion-manager/skills/opinion-manager/agents/openai.yaml +4 -0
  90. package/runtime/plugins/opinion-manager/templates/.gitkeep +1 -0
  91. package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +24 -0
  92. package/runtime/plugins/project-orchestrator/content.json +37 -0
  93. package/runtime/plugins/project-orchestrator/plugin.json +19 -0
  94. package/runtime/plugins/project-orchestrator/references/asset-synonyms.example.json +8 -0
  95. package/runtime/plugins/project-orchestrator/references/case-workspaces.md +159 -0
  96. package/runtime/plugins/project-orchestrator/references/case.schema.json +31 -0
  97. package/runtime/plugins/project-orchestrator/runtime/assets.py +1288 -0
  98. package/runtime/plugins/project-orchestrator/runtime/case.py +557 -0
  99. package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +350 -0
  100. package/runtime/plugins/project-orchestrator/runtime/memory.py +363 -0
  101. package/runtime/plugins/project-orchestrator/runtime/project_base.py +107 -0
  102. package/runtime/plugins/project-orchestrator/runtime/project_cli.py +599 -0
  103. package/runtime/plugins/project-orchestrator/runtime/project_demo.py +504 -0
  104. package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +470 -0
  105. package/runtime/plugins/project-orchestrator/runtime/project_menu.py +394 -0
  106. package/runtime/plugins/project-orchestrator/runtime/project_real.py +522 -0
  107. package/runtime/plugins/project-orchestrator/runtime/project_write.py +329 -0
  108. package/runtime/plugins/project-orchestrator/runtime/wf.py +439 -0
  109. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +230 -0
  110. package/runtime/plugins/project-orchestrator/skills/project-orchestrator/agents/openai.yaml +4 -0
  111. package/runtime/plugins/project-orchestrator/templates/CASE.md +39 -0
  112. /package/runtime/{mcp_env_launcher.py → plugins/agent-pack/runtime/mcp_env_launcher.py} +0 -0
@@ -0,0 +1,388 @@
1
+ """agenthelm 用户级运行时:把四个插件的确定性工具装进用户数据目录,日常只需要 Python 3.11+。
2
+
3
+ 布局(``$XDG_DATA_HOME`` 缺省为 ``~/.local/share``)::
4
+
5
+ agenthelm/runtimes/<发布版本>/ index.json、plugins/<插件>/、bin/agenthelm、runtime.json
6
+ agenthelm/current -> runtimes/<发布版本>
7
+
8
+ 项目根启动器先找 ``current/bin/agenthelm``,所以装好后打开菜单不联网、不经过 npx。
9
+ 同一版本装好后不再改写;新版本装在旁边并切换 ``current``,旧版本保留,可用 ``use`` 切回。
10
+ 来源三选一:本命令所在的发布内容(npm 包、发布包或仓库检出)、``--from-release``
11
+ (联网下载发布包并核对摘要)、``--source``(本地目录或 .tar.gz,离线用)。
12
+ 写操作默认只预览,``--apply`` 才执行。只在 macOS 与 Linux 上验证过。
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import hashlib
18
+ import json
19
+ import os
20
+ import shutil
21
+ import sys
22
+ import tempfile
23
+ from datetime import UTC, datetime
24
+ from pathlib import Path
25
+ from typing import Any
26
+
27
+ PLUGINS = ("agent-pack", "agents-init", "opinion-manager", "project-orchestrator")
28
+ SKIP_PARTS = {"tests", "__pycache__", ".DS_Store", "node_modules"}
29
+ MANIFEST = "runtime.json"
30
+ SHIM = """#!/bin/sh
31
+ # agenthelm 运行时 {version}(agenthelm runtime install 生成)。只需要 Python 3.11+,不需要 AI 客户端。
32
+ self=$0
33
+ while [ -L "$self" ]; do # a command link (--link-bin) points here; follow it to the runtime
34
+ link=$(readlink "$self")
35
+ case $link in /*) self=$link ;; *) self=$(dirname -- "$self")/$link ;; esac
36
+ done
37
+ here=$(CDPATH= cd -- "$(dirname -- "$self")/.." && pwd)
38
+ for candidate in "${{AGENTHELM_PYTHON:-}}" python3 python; do
39
+ [ -n "$candidate" ] || continue
40
+ command -v "$candidate" >/dev/null 2>&1 || continue
41
+ if "$candidate" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)' >/dev/null 2>&1; then
42
+ exec "$candidate" "$here/plugins/agent-pack/runtime/agenthelm.py" "$@"
43
+ fi
44
+ done
45
+ echo "agenthelm 需要 Python 3.11 或更高版本:安装后重试,或设置 AGENTHELM_PYTHON。" >&2
46
+ exit 3
47
+ """
48
+
49
+
50
+ class RuntimeInstallError(Exception):
51
+ def __init__(self, message: str, next_steps: list[str] | None = None) -> None:
52
+ super().__init__(message)
53
+ self.next_steps = next_steps or []
54
+
55
+
56
+ # ---------------------------------------------------------------- locations
57
+
58
+ def data_root(env: Any) -> Path:
59
+ base = env.get("XDG_DATA_HOME")
60
+ return (Path(base) if base else env.home / ".local" / "share") / "agenthelm"
61
+
62
+
63
+ def runtimes_dir(env: Any) -> Path:
64
+ return data_root(env) / "runtimes"
65
+
66
+
67
+ def current_link(env: Any) -> Path:
68
+ return data_root(env) / "current"
69
+
70
+
71
+ def bundled_root() -> Path | None:
72
+ """The release content this file ships in: npm package, release tarball or checkout."""
73
+ root = Path(__file__).resolve().parents[3]
74
+ return root if _is_release_root(root) else None
75
+
76
+
77
+ def _is_release_root(root: Path) -> bool:
78
+ return (root / "index.json").is_file() and all((root / "plugins" / name / "plugin.json").is_file()
79
+ for name in PLUGINS)
80
+
81
+
82
+ def running_from(env: Any) -> str:
83
+ here = Path(__file__).resolve()
84
+ try:
85
+ if data_root(env).resolve() in here.parents:
86
+ return "runtime"
87
+ except OSError:
88
+ pass
89
+ text = here.as_posix()
90
+ if "/plugins/cache/" in text or "/.cursor/plugins/" in text:
91
+ return "client-plugin"
92
+ if "/node_modules/" in text or "/_npx/" in text:
93
+ return "npm"
94
+ return "checkout"
95
+
96
+
97
+ # ---------------------------------------------------------------- source description
98
+
99
+ def _shipped_files(root: Path) -> list[Path]:
100
+ files = []
101
+ for name in PLUGINS:
102
+ base = root / "plugins" / name
103
+ for path in sorted(base.rglob("*")):
104
+ relative = path.relative_to(root)
105
+ if path.is_file() and not path.is_symlink() and not (set(relative.parts) & SKIP_PARTS) \
106
+ and path.suffix != ".pyc":
107
+ files.append(relative)
108
+ return files
109
+
110
+
111
+ def describe(root: Path) -> dict:
112
+ if not _is_release_root(root):
113
+ raise RuntimeInstallError(f"{root} 不是 agenthelm 发布内容(缺少 index.json 或 plugins/<插件>)",
114
+ ["--source 指向仓库检出、解压后的发布包目录或 .tar.gz 发布包"])
115
+ index = json.loads((root / "index.json").read_text(encoding="utf-8"))
116
+ version = str(index.get("plugin", {}).get("version") or "")
117
+ if not version:
118
+ raise RuntimeInstallError(f"{root}/index.json 没有发布版本号")
119
+ digest = hashlib.sha256()
120
+ files = _shipped_files(root)
121
+ for relative in files:
122
+ digest.update(relative.as_posix().encode("utf-8") + b"\0" + (root / relative).read_bytes() + b"\0")
123
+ plugins = {name: json.loads((root / "plugins" / name / "plugin.json").read_text(encoding="utf-8")).get("version")
124
+ for name in PLUGINS}
125
+ return {"version": version, "plugins": plugins, "digest": digest.hexdigest(), "files": len(files)}
126
+
127
+
128
+ def _read_manifest(directory: Path) -> dict | None:
129
+ try:
130
+ data = json.loads((directory / MANIFEST).read_text(encoding="utf-8"))
131
+ except (OSError, ValueError):
132
+ return None
133
+ return data if isinstance(data, dict) else None
134
+
135
+
136
+ def installed(env: Any) -> list[dict]:
137
+ base = runtimes_dir(env)
138
+ found = []
139
+ if base.is_dir():
140
+ for directory in sorted(base.iterdir()):
141
+ manifest = _read_manifest(directory) if directory.is_dir() and not directory.name.startswith(".") else None
142
+ if manifest:
143
+ found.append({**manifest, "path": str(directory)})
144
+ return found
145
+
146
+
147
+ def current(env: Any) -> dict | None:
148
+ link = current_link(env)
149
+ if not link.is_symlink():
150
+ return None
151
+ manifest = _read_manifest(link)
152
+ return {**manifest, "path": str(link.resolve())} if manifest else None
153
+
154
+
155
+ def release_version(latest: dict | None) -> str | None:
156
+ tag = ((latest or {}).get("release") or {}).get("tag")
157
+ return tag[1:] if isinstance(tag, str) and tag.startswith("v") else tag
158
+
159
+
160
+ def report(env: Any, latest: dict | None = None) -> dict:
161
+ active = current(env)
162
+ newest = release_version(latest)
163
+ return {"current": active, "installed": installed(env), "data_root": str(data_root(env)),
164
+ "running_from": running_from(env), "latest_release": newest,
165
+ "update_available": bool(active and newest and _newer(newest, active["version"]))}
166
+
167
+
168
+ def _newer(candidate: str, existing: str) -> bool:
169
+ def key(value: str) -> tuple:
170
+ parts = value.split("-", 1)[0].split(".")
171
+ return tuple(int(part) if part.isdigit() else 0 for part in parts)
172
+ return key(candidate) > key(existing)
173
+
174
+
175
+ # ---------------------------------------------------------------- plan and apply
176
+
177
+ def plan(env: Any, root: Path, origin: str, *, link_bin: Path | None = None) -> dict:
178
+ info = describe(root)
179
+ target = runtimes_dir(env) / info["version"]
180
+ existing = _read_manifest(target)
181
+ active = current(env)
182
+ if existing and existing.get("digest") != info["digest"]:
183
+ raise RuntimeInstallError(f"运行时 {info['version']} 已安装,但内容与来源不同(来源:{origin})",
184
+ [f"已装:{target};同一版本不会被改写",
185
+ "确认要换掉时先手动移走该目录(例如移到 ~/.agent-pack/recovery/),再重新安装"])
186
+ writes = []
187
+ if not existing:
188
+ writes.append({"path": str(target), "scope": "用户数据目录(新版本,旧版本保留)"})
189
+ switch = not active or active.get("version") != info["version"]
190
+ if switch:
191
+ writes.append({"path": str(current_link(env)), "scope": f"切换当前版本 → {info['version']}"})
192
+ link = None
193
+ if link_bin is not None:
194
+ link = link_bin.expanduser() / "agenthelm"
195
+ expected = current_link(env) / "bin" / "agenthelm"
196
+ if link.is_symlink() and Path(os.readlink(link)) == expected:
197
+ link = None
198
+ elif link.exists() or link.is_symlink():
199
+ raise RuntimeInstallError(f"{link} 已存在且不是本运行时的链接,不会覆盖", ["换一个 --link-bin 目录,或手动处理该文件"])
200
+ else:
201
+ writes.append({"path": str(link), "scope": "命令链接(指向当前运行时)"})
202
+ return {"version": info["version"], "plugins": info["plugins"], "digest": info["digest"], "source": origin,
203
+ "target": str(target), "install": not existing, "switch": switch,
204
+ "previous": active.get("version") if active else None, "link": str(link) if link else None,
205
+ "writes": writes, "action": "install" if not existing else ("switch" if switch else "none")}
206
+
207
+
208
+ def apply(env: Any, planned: dict, root: Path) -> dict:
209
+ base = runtimes_dir(env)
210
+ base.mkdir(parents=True, exist_ok=True)
211
+ target = Path(planned["target"])
212
+ if planned["install"]:
213
+ staging = Path(tempfile.mkdtemp(prefix=f".{planned['version']}-", dir=base))
214
+ try:
215
+ for relative in _shipped_files(root):
216
+ destination = staging / relative
217
+ destination.parent.mkdir(parents=True, exist_ok=True)
218
+ shutil.copy2(root / relative, destination)
219
+ shutil.copy2(root / "index.json", staging / "index.json")
220
+ if describe(staging)["digest"] != planned["digest"]:
221
+ raise RuntimeInstallError("复制后的内容与来源摘要不一致,已放弃安装")
222
+ shim = staging / "bin" / "agenthelm"
223
+ shim.parent.mkdir()
224
+ shim.write_text(SHIM.format(version=planned["version"]), encoding="utf-8")
225
+ shim.chmod(0o755)
226
+ manifest = {"schema_version": 1, "version": planned["version"], "plugins": planned["plugins"],
227
+ "digest": planned["digest"], "source": planned["source"],
228
+ "installed_at": datetime.now(UTC).replace(microsecond=0).isoformat()}
229
+ (staging / MANIFEST).write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
230
+ os.rename(staging, target)
231
+ except BaseException:
232
+ shutil.rmtree(staging, ignore_errors=True)
233
+ raise
234
+ if planned["switch"]:
235
+ _switch(env, target)
236
+ if planned["link"]:
237
+ link = Path(planned["link"])
238
+ link.parent.mkdir(parents=True, exist_ok=True)
239
+ link.symlink_to(current_link(env) / "bin" / "agenthelm")
240
+ active = current(env)
241
+ shim = current_link(env) / "bin" / "agenthelm"
242
+ verified = bool(active and active["version"] == planned["version"] and os.access(shim, os.X_OK))
243
+ return {"ok": verified, "version": planned["version"], "current": active,
244
+ "verify": (f"重新读取:当前运行时 {planned['version']},命令 {shim}" if verified
245
+ else "回读不一致:请运行 agenthelm runtime status 查看"),
246
+ "rollback": f"agenthelm runtime use {planned['previous']} --apply" if planned["previous"]
247
+ and planned["previous"] != planned["version"] else None}
248
+
249
+
250
+ def _switch(env: Any, target: Path) -> None:
251
+ link = current_link(env)
252
+ temporary = link.with_name(f".current.{os.getpid()}")
253
+ if temporary.is_symlink() or temporary.exists():
254
+ temporary.unlink()
255
+ temporary.symlink_to(Path("runtimes") / target.name)
256
+ os.replace(temporary, link)
257
+
258
+
259
+ def use(env: Any, version: str, *, apply_change: bool) -> dict:
260
+ target = runtimes_dir(env) / version
261
+ if not _read_manifest(target):
262
+ versions = "、".join(item["version"] for item in installed(env)) or "(无)"
263
+ raise RuntimeInstallError(f"没有安装运行时 {version}", [f"已安装:{versions}"])
264
+ active = current(env)
265
+ result = {"version": version, "previous": active.get("version") if active else None, "applied": False}
266
+ if apply_change:
267
+ _switch(env, target)
268
+ result["applied"] = True
269
+ result["current"] = current(env)
270
+ return result
271
+
272
+
273
+ # ---------------------------------------------------------------- sources
274
+
275
+ def resolve_source(host: Any, env: Any, args: argparse.Namespace, workdir: Path, *, prefer_release: bool) -> tuple[Path, str]:
276
+ if args.source is not None:
277
+ source = args.source.expanduser().resolve()
278
+ if source.is_file():
279
+ extracted = workdir / "source"
280
+ extracted.mkdir()
281
+ host._safe_extract(source, extracted)
282
+ roots = [path for path in extracted.iterdir() if path.is_dir()]
283
+ if len(roots) != 1:
284
+ raise RuntimeInstallError("发布包结构不符合预期")
285
+ return roots[0], f"本地发布包 {source.name}"
286
+ return source, f"本地目录 {source}"
287
+ bundled = bundled_root()
288
+ if bundled is not None and not prefer_release and not args.from_release:
289
+ return bundled, f"随附内容({running_from(env)})"
290
+ try:
291
+ latest, _ = host.load_latest(env, args.latest, max_age=0)
292
+ return host.release_source(latest, workdir), "GitHub Release(已核对摘要)"
293
+ except (host.CheckError, OSError, ValueError) as exc:
294
+ raise RuntimeInstallError(f"无法下载发布包:{exc}",
295
+ ["离线时用 --source 指向发布包(.tar.gz)或仓库检出",
296
+ "下载地址见 https://github.com/lc4t/agenthelm/releases"]) from exc
297
+
298
+
299
+ # ---------------------------------------------------------------- command line
300
+
301
+ def parser() -> argparse.ArgumentParser:
302
+ result = argparse.ArgumentParser(prog="agenthelm runtime", description=__doc__.split("\n\n")[0])
303
+ sub = result.add_subparsers(dest="action", required=True)
304
+ for name, text in (("status", "当前运行时、已装版本与可用更新"),
305
+ ("install", "安装运行时(默认用本命令随附的内容;默认预览)"),
306
+ ("update", "联网安装最新发布版本并切换(默认预览)"),
307
+ ("use", "切换到已安装的某个版本(默认预览)")):
308
+ command = sub.add_parser(name, help=text)
309
+ command.add_argument("--output", choices=("text", "json"), default="text")
310
+ command.add_argument("--home", type=Path, help=argparse.SUPPRESS)
311
+ command.add_argument("--latest", help="latest.json 的 URL 或本地路径")
312
+ if name == "use":
313
+ command.add_argument("version")
314
+ if name in ("install", "update"):
315
+ command.add_argument("--source", type=Path, help="本地目录或 .tar.gz 发布包(离线安装)")
316
+ command.add_argument("--link-bin", type=Path, help="在这个目录建 agenthelm 命令链接(例如 ~/.local/bin)")
317
+ if name == "install":
318
+ command.add_argument("--from-release", action="store_true", help="联网下载发布包,而不用随附内容")
319
+ if name != "status":
320
+ command.add_argument("--apply", action="store_true")
321
+ if name == "status":
322
+ command.add_argument("--offline", action="store_true", help="只用缓存的版本信息")
323
+ return result
324
+
325
+
326
+ def _print(payload: dict, output: str, lines: list[str]) -> None:
327
+ if output == "json":
328
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
329
+ else:
330
+ print("\n".join(lines))
331
+
332
+
333
+ def main(argv: list[str], host: Any) -> int:
334
+ args = parser().parse_args(argv)
335
+ env = host.current_env(args.home)
336
+ if sys.platform == "win32":
337
+ print("agenthelm runtime 目前只支持 macOS 与 Linux。", file=sys.stderr)
338
+ return 4
339
+ try:
340
+ if args.action == "status":
341
+ try:
342
+ latest, _ = host.load_latest(env, args.latest, offline=args.offline)
343
+ except (host.CheckError, OSError, ValueError):
344
+ latest = None
345
+ data = report(env, latest)
346
+ active = data["current"]
347
+ lines = [f"当前运行时:{active['version']}({active['path']})" if active else "当前运行时:未安装",
348
+ f"已安装:{'、'.join(item['version'] for item in data['installed']) or '(无)'}",
349
+ f"本次命令来自:{data['running_from']}"]
350
+ if data["update_available"]:
351
+ lines.append(f"有新版本 {data['latest_release']}:agenthelm runtime update 预览")
352
+ if not active:
353
+ lines.append("安装:agenthelm runtime install(预览)→ 加 --apply 执行")
354
+ _print({"ok": True, **data}, args.output, lines)
355
+ return 0
356
+ if args.action == "use":
357
+ result = use(env, args.version, apply_change=args.apply)
358
+ lines = [f"{'已切换' if result['applied'] else '将切换'}:{result['previous']} → {result['version']}"]
359
+ if not args.apply:
360
+ lines.append("以上为预览;加 --apply 执行。")
361
+ _print({"ok": True, **result}, args.output, lines)
362
+ return 0
363
+ with tempfile.TemporaryDirectory(prefix="agenthelm-runtime-") as temporary:
364
+ root, origin = resolve_source(host, env, args, Path(temporary), prefer_release=args.action == "update")
365
+ planned = plan(env, root, origin, link_bin=args.link_bin)
366
+ lines = [f"运行时 {planned['version']}(来源:{origin})"]
367
+ lines += [f" 将写入:{item['path']}({item['scope']})" for item in planned["writes"]] or [" 已是当前版本,无需改动。"]
368
+ if not args.apply or planned["action"] == "none" and not planned["link"]:
369
+ if args.apply is False and planned["writes"]:
370
+ lines.append("以上为预览;加 --apply 执行。")
371
+ _print({"ok": True, "applied": False, "plan": planned}, args.output, lines)
372
+ return 0
373
+ result = apply(env, planned, root)
374
+ lines.append((" " + result["verify"]))
375
+ if result["rollback"]:
376
+ lines.append(f" 回到旧版本:{result['rollback']}")
377
+ if not os.environ.get("PATH", "").count(str(current_link(env) / "bin")) and not planned["link"]:
378
+ lines.append(f" 直接用命令:把 {current_link(env) / 'bin'} 加入 PATH,或重新运行并加 --link-bin ~/.local/bin")
379
+ _print({"ok": result["ok"], "applied": True, "plan": planned, **result}, args.output, lines)
380
+ return 0 if result["ok"] else 6
381
+ except RuntimeInstallError as exc:
382
+ if args.output == "json":
383
+ print(json.dumps({"ok": False, "error": str(exc), "next": exc.next_steps}, ensure_ascii=False, indent=2))
384
+ else:
385
+ print(f"出错:{exc}", file=sys.stderr)
386
+ for step in exc.next_steps:
387
+ print(f" · {step}", file=sys.stderr)
388
+ return 4 if "无法下载" in str(exc) else 2
@@ -43,7 +43,7 @@ TOOLS = [
43
43
  {
44
44
  "name": "agent_pack_bootstrap",
45
45
  "title": "Install Agent Plugin",
46
- "description": "Dry-run or install a portable plugin, including agent-pack itself, into Codex, Cursor, or Claude Code without overwriting an existing package.",
46
+ "description": "Dry-run or install a portable plugin, including agent-pack itself, into Codex, Cursor, or Claude Code without overwriting an existing package. For Claude Code, skills / mcps limit the install to those components, and local credential values on MCP entries are kept unless resetOverrides.",
47
47
  "inputSchema": {
48
48
  "type": "object",
49
49
  "properties": {
@@ -53,6 +53,9 @@ TOOLS = [
53
53
  "apply": {"type": "boolean", "default": False},
54
54
  "replace": {"type": "boolean", "default": False},
55
55
  "recoveryRoot": {"type": "string"},
56
+ "skills": {"type": "array", "items": {"type": "string"}, "default": []},
57
+ "mcps": {"type": "array", "items": {"type": "string"}, "default": []},
58
+ "resetOverrides": {"type": "boolean", "default": False},
56
59
  },
57
60
  "required": ["plugin", "client"],
58
61
  "additionalProperties": False,
@@ -62,12 +65,14 @@ TOOLS = [
62
65
  {
63
66
  "name": "agent_pack_reconcile",
64
67
  "title": "Reconcile Project and Client Capabilities",
65
- "description": "Classify capability drift, safely replace the managed project package, and archive explicitly selected legacy Skills or plugins with rollback. apply defaults to false.",
68
+ "description": "Classify capability drift, safely replace the managed project package, and archive explicitly selected legacy Skills or plugins with rollback. Claude Code gets its own package, MCP entries and Skill links; local credential values are kept unless resetOverrides. apply defaults to false.",
66
69
  "inputSchema": {
67
70
  "type": "object",
68
71
  "properties": {
69
72
  "project": {"type": "string"},
70
- "client": {"type": "string", "enum": ["codex", "cursor"]},
73
+ "client": {"type": "string", "enum": ["codex", "cursor", "claude"]},
74
+ "replace": {"type": "boolean", "default": False},
75
+ "resetOverrides": {"type": "boolean", "default": False},
71
76
  "home": {"type": "string"},
72
77
  "retireSkills": {"type": "array", "items": {"type": "string"}, "default": []},
73
78
  "retirePlugins": {"type": "array", "items": {"type": "string"}, "default": []},
@@ -97,10 +102,15 @@ TOOLS = [
97
102
  {
98
103
  "name": "agent_pack_doctor",
99
104
  "title": "Validate Project Capabilities",
100
- "description": "Validate an initialized project's Agent Plugin, Skill, MCP, duplicate-name, portability, and secret boundaries.",
105
+ "description": "Validate an initialized project's Agent Plugin, Skill, MCP, duplicate-name, portability, and secret boundaries. With clients, also report each client's missing, stale and different components and kept local overrides.",
101
106
  "inputSchema": {
102
107
  "type": "object",
103
- "properties": {"project": {"type": "string"}},
108
+ "properties": {
109
+ "project": {"type": "string"},
110
+ "clients": {"type": "array", "items": {"type": "string", "enum": ["codex", "cursor", "claude"]},
111
+ "default": []},
112
+ "home": {"type": "string"},
113
+ },
104
114
  "required": ["project"],
105
115
  "additionalProperties": False,
106
116
  },
@@ -127,14 +137,17 @@ TOOLS = [
127
137
  {
128
138
  "name": "agent_pack_sync",
129
139
  "title": "Sync Project Capabilities to an Agent Client",
130
- "description": "Dry-run or install a project's portable capability package into Codex or Cursor. apply defaults to false and existing targets are never overwritten.",
140
+ "description": "Dry-run or install a project's portable capability package into Codex, Cursor or Claude Code. apply defaults to false; replaced content goes to the recovery directory. Claude Code keeps local credential values unless resetOverrides, and only replaces entries it did not install with replace.",
131
141
  "inputSchema": {
132
142
  "type": "object",
133
143
  "properties": {
134
144
  "project": {"type": "string"},
135
- "client": {"type": "string", "enum": ["codex", "cursor"]},
145
+ "client": {"type": "string", "enum": ["codex", "cursor", "claude"]},
136
146
  "home": {"type": "string"},
137
147
  "apply": {"type": "boolean", "default": False},
148
+ "replace": {"type": "boolean", "default": False},
149
+ "resetOverrides": {"type": "boolean", "default": False},
150
+ "recoveryRoot": {"type": "string"},
138
151
  },
139
152
  "required": ["project", "client"],
140
153
  "additionalProperties": False,
@@ -174,6 +187,9 @@ def _call(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
174
187
  apply=bool(arguments.get("apply", False)),
175
188
  replace=bool(arguments.get("replace", False)),
176
189
  recovery_root=recovery,
190
+ skills=arguments.get("skills", []),
191
+ mcps=arguments.get("mcps", []),
192
+ reset_overrides=bool(arguments.get("resetOverrides", False)),
177
193
  )
178
194
  if name == "agent_pack_reconcile":
179
195
  recovery_value = arguments.get("recoveryRoot")
@@ -187,6 +203,8 @@ def _call(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
187
203
  recovery_root=recovery,
188
204
  apply=bool(arguments.get("apply", False)),
189
205
  retire_only=bool(arguments.get("retireOnly", False)),
206
+ replace=bool(arguments.get("replace", False)),
207
+ reset_overrides=bool(arguments.get("resetOverrides", False)),
190
208
  )
191
209
  if name == "agent_pack_inventory":
192
210
  inventory = inventory_source(arguments["source"], home)
@@ -194,7 +212,7 @@ def _call(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
194
212
  payload["ok"] = not payload["issues"]
195
213
  return payload
196
214
  if name == "agent_pack_doctor":
197
- return doctor(Path(arguments["project"]))
215
+ return doctor(Path(arguments["project"]), arguments.get("clients", []), home)
198
216
  if name == "agent_pack_transfer":
199
217
  source = inventory_source(arguments["source"], home)
200
218
  return transfer_to_project(
@@ -205,11 +223,15 @@ def _call(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
205
223
  apply=bool(arguments.get("apply", False)),
206
224
  )
207
225
  if name == "agent_pack_sync":
226
+ recovery_value = arguments.get("recoveryRoot")
208
227
  return sync_project(
209
228
  Path(arguments["project"]),
210
229
  arguments["client"],
211
230
  home=home,
212
231
  apply=bool(arguments.get("apply", False)),
232
+ replace=bool(arguments.get("replace", False)),
233
+ reset_overrides=bool(arguments.get("resetOverrides", False)),
234
+ recovery_root=Path(recovery_value) if isinstance(recovery_value, str) else None,
213
235
  )
214
236
  raise AgentPackError(f"unknown tool: {name}")
215
237
 
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: agent-pack
3
+ description: Deterministically inventory, verify, install, sync, migrate and reconcile Agent Plugins, Skills and MCP servers across projects, plugin directories and clients such as Codex, Cursor and Claude. Use when discovering, importing, exporting, syncing or verifying Agent capabilities, or diagnosing extension drift on a machine. The project lifecycle (Task/Case/routing/verification/Git) is not part of this Skill; that is project-orchestrator. 中文:确定性地盘点、校验、安装、同步、迁移与对账 Agent Plugin、Skill 与 MCP server——在项目、插件目录与 Codex / Cursor / Claude 等客户端之间。当需要发现、导入、导出、同步或验证 Agent 能力,或诊断某台机器的扩展安装漂移时使用。项目生命周期(Task/Case/路由/验证/Git)不在本 Skill 内——那是 project-orchestrator。
4
+ ---
5
+
6
+ # Agent Pack(Agent 扩展能力管理)
7
+
8
+ ## 定位
9
+
10
+ `agent-pack` 是**确定性工具**,不做生命周期决策。它只在项目、插件目录与已支持客户端之间 inventory / doctor / bootstrap / sync / transfer / reconcile。所有写操作默认 dry-run;重名、无效 manifest、外部 symlink、含疑似密钥的 MCP 条目一律失败关闭。
11
+
12
+ - 项目编排(选 Task/Case、路由、验证、Git 交付)归 **project-orchestrator**,不在本 Skill 内。
13
+ - 跨机整机恢复的**单一用户入口**是 `python3 tools/agent-bootstrap`(`doctor` / `install <client>` / `list`),它读取 `00-system/capabilities.json` 的 `agent_extensions` 契约并在内部调用本工具。日常先用它。
14
+ - 只有需要单组件级操作(transfer 某个 skill、reconcile 退役某个旧包、向另一个项目导出)时才直接用本工具。
15
+
16
+ ## 入口
17
+
18
+ 优先使用随附的 `agent-pack` MCP Tool(`agent_pack_inventory` / `agent_pack_doctor` / `agent_pack_bootstrap` / `agent_pack_sync` / `agent_pack_transfer` / `agent_pack_reconcile`);不可用时运行标准库 CLI `runtime/agent_pack_config.py`。执行能力管理前读取 [references/capability-config.md](../../references/capability-config.md)。
19
+
20
+ `runtime/agenthelm.py` 另有两个面向人的入口:`project` 转交 project-orchestrator 的项目入口(不需要 AI Agent);`runtime install|update|use|status` 管理用户级运行时(`~/.local/share/agenthelm/`,项目根目录的启动器优先使用它),写操作默认预览、`--apply` 才执行,新版本装在旁边、旧版本保留。
21
+
22
+ ## 执行顺序
23
+
24
+ 1. 盘点来源与目标(`inventory --source project:/path | plugin:/path | client:codex | client:cursor | client:claude`);
25
+ 2. 把每个组件分类为 Agent Plugin、Skill 或 MCP server;
26
+ 3. 拒绝重名、无效 manifest、外部 symlink 以及包含密钥的 MCP 配置;
27
+ 4. 通过 dry-run 预览导入、导出与客户端同步;
28
+ 5. 计划写入其他项目或用户级客户端时申请授权;
29
+ 6. 只应用明确选择的组件;
30
+ 7. 对目标运行 `doctor`。
31
+
32
+ 目标客户端尚未安装某包时,从可信本地插件根目录使用 `bootstrap`。`bootstrap` 支持 `codex` / `cursor` / `claude`;Claude 端写入 `~/.claude/skills` 符号链接与 `~/.claude.json` user-scope MCP,可用 `--skill` / `--mcp` 只装选中的组件,本机认证值默认保留(`--reset-overrides` 才丢弃),装完需重启 Claude Code。把项目能力同步到 Claude 用 `sync --client claude`:它为 Claude 单独生成包,只增删改本包装过的条目,不要用 `bootstrap` 指向 Codex 的包。核对客户端是否与项目一致用 `doctor --client <客户端>`。处理已有客户端时优先使用 `reconcile`:审阅组件状态,明确列出要退役的旧 Skill/plugin,再执行应用。只退役、不安装项目包时加 `--retire-only`。用户验收迁移前,已退役或替换的内容必须保留在报告的恢复目录(`~/.agent-pack/recovery/`)中。
33
+
34
+ ## 打包与协议
35
+
36
+ 优先使用 Agent Plugins 1.0 作为可移植包格式:根目录 `plugin.json`、直接子目录 `skills/<name>/SKILL.md` 和根目录 `mcp.json`。客户端专属投影(`.codex-plugin/`、`.mcp.json`)只作为 adapter 保留,且其 `name` / `version` 由 `plugin.json` 生成,禁止手改。以 MCP 2026-07-28 为主要 wire revision;已安装客户端仍要求旧版本时保留兼容投影。
37
+
38
+ ## 版本完整性
39
+
40
+ `plugin.json#version` 是唯一权威。`doctor` 对每个被盘点插件比对仓库 `plugin.json` 版本与其 `.codex-plugin/plugin.json` 适配器版本,不一致即报 issue(fail,不是 warning)。`~/.codex/plugins/cache/personal/` 是 Codex 自身的版本化缓存,滞后于 marketplace 源,须由 Codex 下次激活或重启刷新——这不是 `agent-pack` 能写的。
41
+
42
+ ## 边界
43
+
44
+ - 不初始化项目、不创建骨架(那是 `agents-init`)。
45
+ - 不选择 Task/Case、不写项目进度、不做 Git 提交(那是 project-orchestrator)。
46
+ - 不把原生客户端 MCP 配置中的凭据复制进可移植包;含密钥条目保持 `blocked` 并留在客户端本地。
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "agents-init",
3
+ "version": "6.6.1",
4
+ "description": "Initialize or migrate an Agent project contract; ships with project-orchestrator, agent-pack and opinion-manager.",
5
+ "author": {
6
+ "name": "lc4t"
7
+ },
8
+ "skills": "./skills/",
9
+ "interface": {
10
+ "displayName": "Agents Init",
11
+ "shortDescription": "Initialize or migrate the project skeleton and Project Profile",
12
+ "longDescription": "Use agents-init to create or migrate a portable project contract, including a root launcher that opens the project menu without any AI agent. project-orchestrator manages the project lifecycle, agent-pack manages Plugins/Skills/MCP, and opinion-manager manages behavior rules between the user and the Agent; all three ship alongside it as separate plugins. 中文:用 agents-init 创建或迁移可移植项目契约(含不需要 AI Agent 就能打开项目菜单的根目录启动器)。project-orchestrator 管理项目生命周期,agent-pack 管理 Plugin/Skill/MCP,opinion-manager 管理用户与 Agent 之间的行为规则;三者作为独立 Plugin 随同分发。",
13
+ "developerName": "lc4t",
14
+ "category": "Developer Tools",
15
+ "capabilities": [
16
+ "project-initialization",
17
+ "project-migration",
18
+ "opinion-configuration"
19
+ ],
20
+ "defaultPrompt": "Use $agents-init to initialize or migrate this project, then use $opinion-manager to configure the Agent behavior rules for this environment."
21
+ }
22
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "schema_version": 1,
3
+ "packs": [
4
+ {
5
+ "id": "agents-init/project-contract",
6
+ "version": "1.1.0",
7
+ "kind": "template",
8
+ "title": "项目契约模板",
9
+ "description": "初始化时渲染为项目入口文件的中文模板与根目录启动器;更新后由用户逐文件选择是否重新渲染。",
10
+ "sources": [
11
+ "skills/agents-init/AGENT.template.md",
12
+ "skills/agents-init/launcher.sh"
13
+ ],
14
+ "materializes": [
15
+ "AGENTS.md",
16
+ "AGENT.RULES.md",
17
+ "CLAUDE.md",
18
+ "AGENT.md",
19
+ "docs/refs/README.md"
20
+ ],
21
+ "project_lock": "agenthelm",
22
+ "files": {
23
+ "skills/agents-init/AGENT.template.md": "cb35f9a20b845686f24823c03bc71c5b93683dc750b61bd4eae733da2085b056",
24
+ "skills/agents-init/launcher.sh": "cf27d6cca786905a192446ea4eefe5635942914c3358c78d12117af912107cad"
25
+ },
26
+ "materializes_from_profile": [
27
+ "entry.launcher"
28
+ ]
29
+ }
30
+ ]
31
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "agents-init",
4
+ "version": "6.6.1",
5
+ "description": "Initialize or migrate an Agent project contract (AGENTS.md, Project Profile, root launcher); ships with project-orchestrator, agent-pack and opinion-manager.",
6
+ "author": {
7
+ "name": "lc4t"
8
+ },
9
+ "repository": "https://github.com/lc4t/agenthelm",
10
+ "license": "MIT",
11
+ "keywords": [
12
+ "agents-init",
13
+ "project-orchestrator",
14
+ "agent-pack",
15
+ "opinion-manager",
16
+ "agent-skills",
17
+ "agent-plugins"
18
+ ]
19
+ }