@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.
- package/README.md +68 -0
- package/README.zh-CN.md +28 -0
- package/bin/agenthelm.js +1 -1
- package/package.json +2 -2
- package/runtime/index.json +80 -0
- package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +26 -0
- package/runtime/plugins/agent-pack/.mcp.json +12 -0
- package/runtime/plugins/agent-pack/hooks/hooks.json +16 -0
- package/runtime/plugins/agent-pack/mcp.json +14 -0
- package/runtime/plugins/agent-pack/plugin.json +20 -0
- package/runtime/plugins/agent-pack/references/capability-config.md +148 -0
- package/runtime/plugins/agent-pack/runtime/agent_pack_claude.py +448 -0
- package/runtime/{agent_pack_config.py → plugins/agent-pack/runtime/agent_pack_config.py} +178 -22
- package/runtime/{agenthelm.py → plugins/agent-pack/runtime/agenthelm.py} +42 -1
- package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +388 -0
- package/runtime/{mcp_server.py → plugins/agent-pack/runtime/mcp_server.py} +30 -8
- package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +46 -0
- package/runtime/plugins/agents-init/.codex-plugin/plugin.json +22 -0
- package/runtime/plugins/agents-init/content.json +31 -0
- package/runtime/plugins/agents-init/plugin.json +19 -0
- package/runtime/plugins/agents-init/skills/agents-init/AGENT.template.md +376 -0
- package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +119 -0
- package/runtime/plugins/agents-init/skills/agents-init/index.html +357 -0
- package/runtime/plugins/agents-init/skills/agents-init/launcher.sh +93 -0
- package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +171 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +1190 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_init_project.py +678 -0
- package/runtime/plugins/agents-init/skills/agents-init/skill.json +122 -0
- package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +26 -0
- package/runtime/plugins/opinion-manager/catalog/README.md +15 -0
- package/runtime/plugins/opinion-manager/catalog/rules/.gitkeep +0 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.align-requirements/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.assumptions-authority/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.batch-questions/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.bilingual-terms/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.calculations/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.chinese-response/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.clean-deliverable/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.complete-options/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.conclusion-first/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.concrete-titles/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.consistent-items/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.device-readable/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.editable-diagrams/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.evidence-inference/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-chinese/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.maintenance-project-language/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.markdown-structure/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.portable-files/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.report-context/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.report-formats/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.report-review/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.response-brief/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.response-dense/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.rules-tools/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.semantic-wrap/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-proportional/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.solutions-three-level/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.tables-compact/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.tables-list-first/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.test-evidence/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/rules/om.versioned-files/versions/1.0.0.json +11 -0
- package/runtime/plugins/opinion-manager/catalog/templates/.gitkeep +0 -0
- package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/compact-tables/versions/1.0.0.json +21 -0
- package/runtime/plugins/opinion-manager/catalog/templates/delivery-standard/list-first/versions/1.0.0.json +21 -0
- package/runtime/plugins/opinion-manager/catalog/templates/development-standard/chinese-maintenance/versions/1.0.0.json +15 -0
- package/runtime/plugins/opinion-manager/catalog/templates/development-standard/project-language/versions/1.0.0.json +15 -0
- package/runtime/plugins/opinion-manager/catalog/templates/response-style/brief-chinese/versions/1.0.0.json +19 -0
- package/runtime/plugins/opinion-manager/catalog/templates/response-style/dense-chinese/versions/1.0.0.json +19 -0
- package/runtime/plugins/opinion-manager/catalog/templates/scenario/concise-project/versions/1.0.0.json +39 -0
- package/runtime/plugins/opinion-manager/catalog/templates/scenario/reviewed-project/versions/1.0.0.json +39 -0
- package/runtime/plugins/opinion-manager/catalog/templates/work-method/proportional/versions/1.0.0.json +20 -0
- package/runtime/plugins/opinion-manager/catalog/templates/work-method/three-level/versions/1.0.0.json +20 -0
- package/runtime/plugins/opinion-manager/content.json +63 -0
- package/runtime/plugins/opinion-manager/plugin.json +18 -0
- package/runtime/plugins/opinion-manager/references/lock.schema.json +14 -0
- package/runtime/plugins/opinion-manager/references/onboarding.md +57 -0
- package/runtime/plugins/opinion-manager/references/profile.schema.json +56 -0
- package/runtime/plugins/opinion-manager/references/rule.schema.json +16 -0
- package/runtime/plugins/opinion-manager/references/template.schema.json +71 -0
- package/runtime/plugins/opinion-manager/references/versioned-template.schema.json +19 -0
- package/runtime/plugins/opinion-manager/references/versioning.md +263 -0
- package/runtime/plugins/opinion-manager/runtime/constants.py +7 -0
- package/runtime/plugins/opinion-manager/runtime/context.py +180 -0
- package/runtime/plugins/opinion-manager/runtime/lifecycle.py +395 -0
- package/runtime/plugins/opinion-manager/runtime/opinion_manager.py +419 -0
- package/runtime/plugins/opinion-manager/runtime/versioned.py +705 -0
- package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +62 -0
- package/runtime/plugins/opinion-manager/skills/opinion-manager/agents/openai.yaml +4 -0
- package/runtime/plugins/opinion-manager/templates/.gitkeep +1 -0
- package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +24 -0
- package/runtime/plugins/project-orchestrator/content.json +37 -0
- package/runtime/plugins/project-orchestrator/plugin.json +19 -0
- package/runtime/plugins/project-orchestrator/references/asset-synonyms.example.json +8 -0
- package/runtime/plugins/project-orchestrator/references/case-workspaces.md +159 -0
- package/runtime/plugins/project-orchestrator/references/case.schema.json +31 -0
- package/runtime/plugins/project-orchestrator/runtime/assets.py +1288 -0
- package/runtime/plugins/project-orchestrator/runtime/case.py +557 -0
- package/runtime/plugins/project-orchestrator/runtime/case_workspace.py +350 -0
- package/runtime/plugins/project-orchestrator/runtime/memory.py +363 -0
- package/runtime/plugins/project-orchestrator/runtime/project_base.py +107 -0
- package/runtime/plugins/project-orchestrator/runtime/project_cli.py +599 -0
- package/runtime/plugins/project-orchestrator/runtime/project_demo.py +504 -0
- package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +470 -0
- package/runtime/plugins/project-orchestrator/runtime/project_menu.py +394 -0
- package/runtime/plugins/project-orchestrator/runtime/project_real.py +522 -0
- package/runtime/plugins/project-orchestrator/runtime/project_write.py +329 -0
- package/runtime/plugins/project-orchestrator/runtime/wf.py +439 -0
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +230 -0
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/agents/openai.yaml +4 -0
- package/runtime/plugins/project-orchestrator/templates/CASE.md +39 -0
- /package/runtime/{mcp_env_launcher.py → plugins/agent-pack/runtime/mcp_env_launcher.py} +0 -0
|
@@ -1019,8 +1019,15 @@ def install_plugin(
|
|
|
1019
1019
|
apply: bool = False,
|
|
1020
1020
|
replace: bool = False,
|
|
1021
1021
|
recovery_root: Path | None = None,
|
|
1022
|
+
skills: Iterable[str] = (),
|
|
1023
|
+
mcps: Iterable[str] = (),
|
|
1024
|
+
reset_overrides: bool = False,
|
|
1022
1025
|
) -> dict[str, Any]:
|
|
1023
|
-
"""Bootstrap a portable plugin into a supported client without overwriting.
|
|
1026
|
+
"""Bootstrap a portable plugin into a supported client without overwriting.
|
|
1027
|
+
|
|
1028
|
+
For Claude Code, ``skills`` / ``mcps`` limit the install to those components
|
|
1029
|
+
and local credential values on MCP entries are kept unless ``reset_overrides``.
|
|
1030
|
+
"""
|
|
1024
1031
|
plugin = plugin.expanduser().resolve()
|
|
1025
1032
|
manifest, issues = validate_plugin_manifest(plugin)
|
|
1026
1033
|
if issues:
|
|
@@ -1030,8 +1037,11 @@ def install_plugin(
|
|
|
1030
1037
|
home = (home or Path.home()).expanduser().resolve()
|
|
1031
1038
|
if client == "claude":
|
|
1032
1039
|
return _install_plugin_claude(
|
|
1033
|
-
plugin, name, home=home, apply=apply, replace=replace, recovery_root=recovery_root
|
|
1040
|
+
plugin, name, home=home, apply=apply, replace=replace, recovery_root=recovery_root,
|
|
1041
|
+
select_skills=set(skills), select_mcps=set(mcps), reset_overrides=reset_overrides,
|
|
1034
1042
|
)
|
|
1043
|
+
if skills or mcps:
|
|
1044
|
+
raise AgentPackError("--skill / --mcp selection is supported for the claude client only")
|
|
1035
1045
|
destination = _client_package_destination(client, home, name)
|
|
1036
1046
|
action = "install-plugin"
|
|
1037
1047
|
if destination.exists() or destination.is_symlink():
|
|
@@ -1103,6 +1113,9 @@ def _install_plugin_claude(
|
|
|
1103
1113
|
apply: bool,
|
|
1104
1114
|
replace: bool,
|
|
1105
1115
|
recovery_root: Path | None,
|
|
1116
|
+
select_skills: set[str] | None = None,
|
|
1117
|
+
select_mcps: set[str] | None = None,
|
|
1118
|
+
reset_overrides: bool = False,
|
|
1106
1119
|
) -> dict[str, Any]:
|
|
1107
1120
|
"""Install a plugin into Claude Code as live links plus user-scope MCP entries.
|
|
1108
1121
|
|
|
@@ -1112,24 +1125,42 @@ def _install_plugin_claude(
|
|
|
1112
1125
|
record ``~/.claude/agent-pack/plugins/<name>`` (symlink to the source root)
|
|
1113
1126
|
that inventory uses to report the installed version.
|
|
1114
1127
|
"""
|
|
1128
|
+
import agent_pack_claude # noqa: PLC0415 - shipped next to this file
|
|
1129
|
+
|
|
1130
|
+
select_skills, select_mcps = select_skills or set(), select_mcps or set()
|
|
1131
|
+
selective = bool(select_skills or select_mcps)
|
|
1115
1132
|
record = _claude_plugin_records(home) / name
|
|
1116
1133
|
links: list[tuple[str, str, Path, Path]] = [("plugin", name, record, plugin)]
|
|
1134
|
+
available_skills: set[str] = set()
|
|
1117
1135
|
skills_root = plugin / "skills"
|
|
1118
1136
|
if skills_root.is_dir() and not skills_root.is_symlink():
|
|
1119
1137
|
for child in sorted(skills_root.iterdir(), key=lambda item: item.name):
|
|
1120
1138
|
if child.is_dir() and not child.name.startswith(".") and (child / "SKILL.md").is_file():
|
|
1121
|
-
|
|
1139
|
+
available_skills.add(child.name)
|
|
1140
|
+
if not selective or child.name in select_skills:
|
|
1141
|
+
links.append(("skill", child.name, home / ".claude" / "skills" / child.name, child))
|
|
1142
|
+
unknown_skills = select_skills - available_skills
|
|
1143
|
+
if unknown_skills:
|
|
1144
|
+
raise AgentPackError("plugin has no such Skill: " + ", ".join(sorted(unknown_skills)))
|
|
1122
1145
|
|
|
1123
1146
|
config_path = _claude_user_config(home)
|
|
1124
1147
|
servers: dict[str, dict[str, Any]] = {}
|
|
1125
1148
|
mcp_path = plugin / "mcp.json"
|
|
1126
1149
|
if mcp_path.is_file():
|
|
1127
1150
|
data_root = home / ".claude" / "agent-pack" / "data" / name
|
|
1128
|
-
|
|
1151
|
+
declared = _read_json(mcp_path).get("mcpServers", {})
|
|
1152
|
+
unknown_mcps = select_mcps - set(declared)
|
|
1153
|
+
if unknown_mcps:
|
|
1154
|
+
raise AgentPackError("plugin has no such MCP server: " + ", ".join(sorted(unknown_mcps)))
|
|
1155
|
+
for server, raw in sorted(declared.items()):
|
|
1156
|
+
if selective and server not in select_mcps:
|
|
1157
|
+
continue
|
|
1129
1158
|
parsed = _portable_mcp(server, raw)
|
|
1130
1159
|
if not parsed.portable:
|
|
1131
1160
|
raise AgentPackError(f"MCP {server} is not portable: " + "; ".join(parsed.issues))
|
|
1132
1161
|
servers[server] = _claude_mcp_config(parsed.config, record, data_root)
|
|
1162
|
+
elif select_mcps:
|
|
1163
|
+
raise AgentPackError("plugin has no mcp.json: " + ", ".join(sorted(select_mcps)))
|
|
1133
1164
|
|
|
1134
1165
|
config: dict[str, Any] = {}
|
|
1135
1166
|
if servers and config_path.exists():
|
|
@@ -1151,16 +1182,17 @@ def _install_plugin_claude(
|
|
|
1151
1182
|
else:
|
|
1152
1183
|
action = "link"
|
|
1153
1184
|
operations.append({"action": f"{action}-{kind}", "client": "claude", "name": item_name, "to": str(path)})
|
|
1185
|
+
entries: dict[str, dict[str, Any]] = {}
|
|
1154
1186
|
for server, rendered in servers.items():
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
action = "keep"
|
|
1160
|
-
else:
|
|
1161
|
-
action = "replace"
|
|
1187
|
+
state, entries[server], kept = agent_pack_claude.classify_mcp(
|
|
1188
|
+
rendered, existing_servers.get(server), reset=reset_overrides)
|
|
1189
|
+
action = {"differ": "replace"}.get(state, state)
|
|
1190
|
+
if action == "replace":
|
|
1162
1191
|
conflicts.append(f"mcp {server}: {config_path}")
|
|
1163
|
-
|
|
1192
|
+
operation = {"action": f"{action}-mcp", "client": "claude", "name": server, "to": str(config_path)}
|
|
1193
|
+
if kept:
|
|
1194
|
+
operation["kept_overrides"] = kept
|
|
1195
|
+
operations.append(operation)
|
|
1164
1196
|
if conflicts and not replace:
|
|
1165
1197
|
raise AgentPackError("Claude client already has different content: " + "; ".join(conflicts))
|
|
1166
1198
|
result: dict[str, Any] = {"ok": True, "applied": apply, "operations": operations}
|
|
@@ -1192,8 +1224,8 @@ def _install_plugin_claude(
|
|
|
1192
1224
|
os.symlink(target, path, target_is_directory=True)
|
|
1193
1225
|
created.append(path)
|
|
1194
1226
|
changed = {
|
|
1195
|
-
server:
|
|
1196
|
-
if existing_servers.get(server) !=
|
|
1227
|
+
server: entry for server, entry in entries.items()
|
|
1228
|
+
if existing_servers.get(server) != entry
|
|
1197
1229
|
}
|
|
1198
1230
|
if changed:
|
|
1199
1231
|
replaced = {server: existing_servers[server] for server in changed if server in existing_servers}
|
|
@@ -1287,12 +1319,23 @@ def reconcile_project(
|
|
|
1287
1319
|
recovery_root: Path | None = None,
|
|
1288
1320
|
apply: bool = False,
|
|
1289
1321
|
retire_only: bool = False,
|
|
1322
|
+
replace: bool = False,
|
|
1323
|
+
reset_overrides: bool = False,
|
|
1290
1324
|
) -> dict[str, Any]:
|
|
1291
1325
|
"""Reconcile one project package and explicitly selected legacy components.
|
|
1292
1326
|
|
|
1293
1327
|
``retire_only`` archives the selected legacy components without installing
|
|
1294
|
-
or replacing the project package itself.
|
|
1328
|
+
or replacing the project package itself. Claude Code is handled by
|
|
1329
|
+
``agent_pack_claude`` (dedicated package, MCP entries, Skill links);
|
|
1330
|
+
``replace`` and ``reset_overrides`` only apply there.
|
|
1295
1331
|
"""
|
|
1332
|
+
if client == "claude":
|
|
1333
|
+
import agent_pack_claude # noqa: PLC0415 - shipped next to this file
|
|
1334
|
+
|
|
1335
|
+
return agent_pack_claude.sync_project(
|
|
1336
|
+
project, home=home, apply=apply, replace=replace, reset_overrides=reset_overrides,
|
|
1337
|
+
recovery_root=recovery_root, retire_skills=retire_skills, retire_plugins=retire_plugins,
|
|
1338
|
+
retire_only=retire_only)
|
|
1296
1339
|
layout = load_project_layout(project)
|
|
1297
1340
|
authority = inventory_project(layout.root)
|
|
1298
1341
|
package_name = _package_name(layout.root)
|
|
@@ -1402,8 +1445,12 @@ def sync_project(
|
|
|
1402
1445
|
*,
|
|
1403
1446
|
home: Path | None = None,
|
|
1404
1447
|
apply: bool = False,
|
|
1448
|
+
replace: bool = False,
|
|
1449
|
+
reset_overrides: bool = False,
|
|
1450
|
+
recovery_root: Path | None = None,
|
|
1405
1451
|
) -> dict[str, Any]:
|
|
1406
|
-
return reconcile_project(project, client, home=home, apply=apply
|
|
1452
|
+
return reconcile_project(project, client, home=home, apply=apply, replace=replace,
|
|
1453
|
+
reset_overrides=reset_overrides, recovery_root=recovery_root)
|
|
1407
1454
|
|
|
1408
1455
|
|
|
1409
1456
|
def _update_personal_marketplace(home: Path, package_name: str) -> None:
|
|
@@ -1425,7 +1472,81 @@ def _update_personal_marketplace(home: Path, package_name: str) -> None:
|
|
|
1425
1472
|
_write_json_atomic(marketplace, payload)
|
|
1426
1473
|
|
|
1427
1474
|
|
|
1428
|
-
def
|
|
1475
|
+
def _skill_fingerprints(package: Path) -> dict[str, str]:
|
|
1476
|
+
root = package / "skills"
|
|
1477
|
+
if not root.is_dir() or root.is_symlink():
|
|
1478
|
+
return {}
|
|
1479
|
+
found = {}
|
|
1480
|
+
for child in sorted(root.iterdir()):
|
|
1481
|
+
if child.is_dir() and not child.is_symlink() and (child / "SKILL.md").is_file():
|
|
1482
|
+
try:
|
|
1483
|
+
found[child.name] = _tree_fingerprint(child)
|
|
1484
|
+
except AgentPackError:
|
|
1485
|
+
found[child.name] = "unreadable"
|
|
1486
|
+
return found
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
def _package_mcps(package: Path) -> dict[str, Any]:
|
|
1490
|
+
path = package / "mcp.json"
|
|
1491
|
+
if not path.is_file() or path.is_symlink():
|
|
1492
|
+
return {}
|
|
1493
|
+
try:
|
|
1494
|
+
servers = _read_json(path).get("mcpServers", {})
|
|
1495
|
+
except AgentPackError:
|
|
1496
|
+
return {}
|
|
1497
|
+
return servers if isinstance(servers, dict) else {}
|
|
1498
|
+
|
|
1499
|
+
|
|
1500
|
+
def client_drift(project: Path, client: str, home: Path | None = None) -> dict[str, Any]:
|
|
1501
|
+
"""Compare what a client has installed for this project with the project's authority.
|
|
1502
|
+
|
|
1503
|
+
Codex and Cursor load the project package directory, so the installed package
|
|
1504
|
+
is compared with a freshly built one (Skills, MCP servers, the rest of the
|
|
1505
|
+
files). Claude Code is compared entry by entry by ``agent_pack_claude``.
|
|
1506
|
+
Same-name MCP servers configured by hand in the client are listed as native.
|
|
1507
|
+
"""
|
|
1508
|
+
if client == "claude":
|
|
1509
|
+
import agent_pack_claude # noqa: PLC0415 - shipped next to this file
|
|
1510
|
+
|
|
1511
|
+
return agent_pack_claude.drift(project, home)
|
|
1512
|
+
home = (home or Path.home()).expanduser().resolve()
|
|
1513
|
+
layout = load_project_layout(project)
|
|
1514
|
+
authority = inventory_project(layout.root)
|
|
1515
|
+
name = _package_name(layout.root)
|
|
1516
|
+
destination = _client_package_destination(client, home, name)
|
|
1517
|
+
report: dict[str, Any] = {"client": client, "package": name, "missing": [], "different": [], "overrides": [],
|
|
1518
|
+
"stale": [], "native": [], "excluded": [], "dangling": [], "coupled": None}
|
|
1519
|
+
report["excluded"] = sorted(item.name for item in authority.mcps
|
|
1520
|
+
if item.portable and not _mcp_allowed(layout, item.name, client))
|
|
1521
|
+
with tempfile.TemporaryDirectory(prefix="agent-pack-drift-") as temporary:
|
|
1522
|
+
expected = _build_package(authority, name, Path(temporary), client)
|
|
1523
|
+
if destination.is_symlink() and not destination.exists():
|
|
1524
|
+
report["dangling"].append(f"package {name}: {destination}")
|
|
1525
|
+
elif not destination.is_dir():
|
|
1526
|
+
report["missing"].append(f"package {name}")
|
|
1527
|
+
report["missing"] += [f"skill {skill}" for skill in _skill_fingerprints(expected)]
|
|
1528
|
+
report["missing"] += [f"mcp {server}" for server in _package_mcps(expected)]
|
|
1529
|
+
elif _tree_fingerprint(expected) != _tree_fingerprint(destination):
|
|
1530
|
+
want, have = _skill_fingerprints(expected), _skill_fingerprints(destination)
|
|
1531
|
+
report["missing"] += [f"skill {skill}" for skill in sorted(set(want) - set(have))]
|
|
1532
|
+
report["stale"] += [f"skill {skill}" for skill in sorted(set(have) - set(want))]
|
|
1533
|
+
report["different"] += [f"skill {skill}" for skill in sorted(set(want) & set(have))
|
|
1534
|
+
if want[skill] != have[skill]]
|
|
1535
|
+
want_mcps, have_mcps = _package_mcps(expected), _package_mcps(destination)
|
|
1536
|
+
report["missing"] += [f"mcp {server}" for server in sorted(set(want_mcps) - set(have_mcps))]
|
|
1537
|
+
report["stale"] += [f"mcp {server}" for server in sorted(set(have_mcps) - set(want_mcps))]
|
|
1538
|
+
report["different"] += [f"mcp {server}" for server in sorted(set(want_mcps) & set(have_mcps))
|
|
1539
|
+
if want_mcps[server] != have_mcps[server]]
|
|
1540
|
+
if not (report["missing"] or report["stale"] or report["different"]):
|
|
1541
|
+
report["different"].append(f"package {name}: other files differ")
|
|
1542
|
+
native = _codex_mcps(home) if client == "codex" else _json_client_mcps(home / ".cursor" / "mcp.json")
|
|
1543
|
+
report["native"] = sorted(native)
|
|
1544
|
+
report["in_sync"] = not (report["missing"] or report["different"] or report["stale"] or report["dangling"])
|
|
1545
|
+
return report
|
|
1546
|
+
|
|
1547
|
+
|
|
1548
|
+
def doctor(project: Path, clients: Iterable[str] = (), home: Path | None = None) -> dict[str, Any]:
|
|
1549
|
+
"""Validate the project; with ``clients``, also report each client's drift from it."""
|
|
1429
1550
|
layout = load_project_layout(project)
|
|
1430
1551
|
inventory = inventory_project(layout.root)
|
|
1431
1552
|
issues = list(inventory.issues)
|
|
@@ -1465,7 +1586,17 @@ def doctor(project: Path) -> dict[str, Any]:
|
|
|
1465
1586
|
issues.extend(f"credential env file missing key: {name}" for name in sorted(missing))
|
|
1466
1587
|
except AgentPackError as exc:
|
|
1467
1588
|
issues.append(str(exc))
|
|
1468
|
-
|
|
1589
|
+
reports: dict[str, Any] = {}
|
|
1590
|
+
for client in dict.fromkeys(clients):
|
|
1591
|
+
report = client_drift(layout.root, client, home)
|
|
1592
|
+
reports[client] = report
|
|
1593
|
+
for kind in ("missing", "different", "stale", "dangling"):
|
|
1594
|
+
issues.extend(f"{client}: {kind} {item}" for item in report[kind])
|
|
1595
|
+
if report["coupled"]:
|
|
1596
|
+
issues.append(f"{client}: install record points into another package directory: {report['coupled']}")
|
|
1597
|
+
warnings.extend(f"{client}: kept local override {item['name']} ({', '.join(item['keys'])})"
|
|
1598
|
+
for item in report["overrides"])
|
|
1599
|
+
result: dict[str, Any] = {
|
|
1469
1600
|
"ok": not issues,
|
|
1470
1601
|
"source": inventory.source,
|
|
1471
1602
|
"counts": {
|
|
@@ -1476,6 +1607,9 @@ def doctor(project: Path) -> dict[str, Any]:
|
|
|
1476
1607
|
"issues": sorted(set(issues)),
|
|
1477
1608
|
"warnings": sorted(set(warnings)),
|
|
1478
1609
|
}
|
|
1610
|
+
if reports:
|
|
1611
|
+
result["clients"] = reports
|
|
1612
|
+
return result
|
|
1479
1613
|
|
|
1480
1614
|
|
|
1481
1615
|
def _print(value: dict[str, Any], output: str) -> None:
|
|
@@ -1508,6 +1642,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1508
1642
|
|
|
1509
1643
|
doctor_parser = sub.add_parser("doctor")
|
|
1510
1644
|
doctor_parser.add_argument("--project", type=Path, default=Path.cwd())
|
|
1645
|
+
doctor_parser.add_argument("--client", action="append", default=[], choices=("codex", "cursor", "claude"),
|
|
1646
|
+
help="also compare this client's installed capabilities with the project (repeatable)")
|
|
1647
|
+
doctor_parser.add_argument("--home", type=Path)
|
|
1511
1648
|
|
|
1512
1649
|
transfer_parser = sub.add_parser("transfer")
|
|
1513
1650
|
transfer_parser.add_argument("--from", dest="source", required=True)
|
|
@@ -1519,9 +1656,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1519
1656
|
|
|
1520
1657
|
sync_parser = sub.add_parser("sync")
|
|
1521
1658
|
sync_parser.add_argument("--project", type=Path, default=Path.cwd())
|
|
1522
|
-
sync_parser.add_argument("--client", choices=("codex", "cursor"), required=True)
|
|
1659
|
+
sync_parser.add_argument("--client", choices=("codex", "cursor", "claude"), required=True)
|
|
1523
1660
|
sync_parser.add_argument("--home", type=Path)
|
|
1524
1661
|
sync_parser.add_argument("--apply", action="store_true")
|
|
1662
|
+
sync_parser.add_argument("--replace", action="store_true",
|
|
1663
|
+
help="claude: also replace Skills / MCP entries this package did not install")
|
|
1664
|
+
sync_parser.add_argument("--reset-overrides", action="store_true",
|
|
1665
|
+
help="claude: drop local credential values (headers / env) instead of keeping them")
|
|
1666
|
+
sync_parser.add_argument("--recovery-root", type=Path)
|
|
1525
1667
|
|
|
1526
1668
|
bootstrap_parser = sub.add_parser("bootstrap")
|
|
1527
1669
|
bootstrap_parser.add_argument("--plugin", type=Path, required=True)
|
|
@@ -1530,10 +1672,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1530
1672
|
bootstrap_parser.add_argument("--apply", action="store_true")
|
|
1531
1673
|
bootstrap_parser.add_argument("--replace", action="store_true")
|
|
1532
1674
|
bootstrap_parser.add_argument("--recovery-root", type=Path)
|
|
1675
|
+
bootstrap_parser.add_argument("--skill", action="append", default=[], help="claude: install only this Skill")
|
|
1676
|
+
bootstrap_parser.add_argument("--mcp", action="append", default=[], help="claude: install only this MCP server")
|
|
1677
|
+
bootstrap_parser.add_argument("--reset-overrides", action="store_true",
|
|
1678
|
+
help="claude: drop local credential values (headers / env) instead of keeping them")
|
|
1533
1679
|
|
|
1534
1680
|
reconcile_parser = sub.add_parser("reconcile")
|
|
1535
1681
|
reconcile_parser.add_argument("--project", type=Path, default=Path.cwd())
|
|
1536
|
-
reconcile_parser.add_argument("--client", choices=("codex", "cursor"), required=True)
|
|
1682
|
+
reconcile_parser.add_argument("--client", choices=("codex", "cursor", "claude"), required=True)
|
|
1683
|
+
reconcile_parser.add_argument("--replace", action="store_true",
|
|
1684
|
+
help="claude: also replace Skills / MCP entries this package did not install")
|
|
1685
|
+
reconcile_parser.add_argument("--reset-overrides", action="store_true",
|
|
1686
|
+
help="claude: drop local credential values (headers / env) instead of keeping them")
|
|
1537
1687
|
reconcile_parser.add_argument("--home", type=Path)
|
|
1538
1688
|
reconcile_parser.add_argument("--retire-skill", action="append", default=[])
|
|
1539
1689
|
reconcile_parser.add_argument("--retire-plugin", action="append", default=[])
|
|
@@ -1554,7 +1704,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1554
1704
|
result = inventory_source(args.source, args.home).to_dict()
|
|
1555
1705
|
result["ok"] = not result["issues"]
|
|
1556
1706
|
elif args.command == "doctor":
|
|
1557
|
-
result = doctor(args.project)
|
|
1707
|
+
result = doctor(args.project, args.client, args.home)
|
|
1558
1708
|
elif args.command == "transfer":
|
|
1559
1709
|
if not args.skill and not args.mcp:
|
|
1560
1710
|
raise AgentPackError("select at least one --skill or --mcp")
|
|
@@ -1567,7 +1717,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1567
1717
|
)
|
|
1568
1718
|
elif args.command == "sync":
|
|
1569
1719
|
result = sync_project(
|
|
1570
|
-
args.project, args.client, home=args.home, apply=args.apply
|
|
1720
|
+
args.project, args.client, home=args.home, apply=args.apply, replace=args.replace,
|
|
1721
|
+
reset_overrides=args.reset_overrides, recovery_root=args.recovery_root,
|
|
1571
1722
|
)
|
|
1572
1723
|
elif args.command == "bootstrap":
|
|
1573
1724
|
result = install_plugin(
|
|
@@ -1577,6 +1728,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1577
1728
|
apply=args.apply,
|
|
1578
1729
|
replace=args.replace,
|
|
1579
1730
|
recovery_root=args.recovery_root,
|
|
1731
|
+
skills=args.skill,
|
|
1732
|
+
mcps=args.mcp,
|
|
1733
|
+
reset_overrides=args.reset_overrides,
|
|
1580
1734
|
)
|
|
1581
1735
|
else:
|
|
1582
1736
|
result = reconcile_project(
|
|
@@ -1588,6 +1742,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1588
1742
|
recovery_root=args.recovery_root,
|
|
1589
1743
|
apply=args.apply,
|
|
1590
1744
|
retire_only=args.retire_only,
|
|
1745
|
+
replace=args.replace,
|
|
1746
|
+
reset_overrides=args.reset_overrides,
|
|
1591
1747
|
)
|
|
1592
1748
|
_print(result, args.output)
|
|
1593
1749
|
return 0 if result.get("ok", True) else 1
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""agenthelm:在 Claude Code、Codex、Cursor 间安装、检查与更新 agenthelm 插件。
|
|
3
3
|
|
|
4
|
-
``
|
|
4
|
+
``project`` 打开不需要 AI Agent 的项目自助入口(由 project-orchestrator 实现);``runtime``
|
|
5
|
+
管理用户级运行时。``check`` 只读:比较各客户端已安装的插件版本、项目里由模板渲染的文件,
|
|
5
6
|
以及 Opinion 来源与云端 ``latest.json``。``install``、``update``、``migrate``
|
|
6
7
|
默认只预览,``--apply`` 才执行。通过 ``npx @sakanano-moe/agenthelm <命令>`` 或
|
|
7
8
|
``python3 <agent-pack>/runtime/agenthelm.py <命令>`` 运行。``--hook`` 供会话开始时静默调用:
|
|
@@ -1003,6 +1004,10 @@ def parser() -> argparse.ArgumentParser:
|
|
|
1003
1004
|
doctor_parser = sub.add_parser("doctor", help="报告重复安装、版本不一致、旧方式残留与检查开关")
|
|
1004
1005
|
common(doctor_parser)
|
|
1005
1006
|
|
|
1007
|
+
sub.add_parser("runtime", add_help=False,
|
|
1008
|
+
help="用户级运行时:install / update / use / status(日常只需要 Python,不经过 npx)")
|
|
1009
|
+
sub.add_parser("project", add_help=False,
|
|
1010
|
+
help="项目自助入口:找资料、看进展、项目指南、检查(不需要 AI Agent;详见 project --help)")
|
|
1006
1011
|
locate_parser = sub.add_parser("locate", help="输出插件的已安装位置(最新优先),供下游脚本查找")
|
|
1007
1012
|
locate_parser.add_argument("plugin")
|
|
1008
1013
|
locate_parser.add_argument("--version", help="只要这个版本")
|
|
@@ -1034,7 +1039,43 @@ def _print_steps(results: list[dict], apply: bool) -> None:
|
|
|
1034
1039
|
print("以上为预览;确认后追加 --apply 执行。")
|
|
1035
1040
|
|
|
1036
1041
|
|
|
1042
|
+
def run_project(argv: list[str]) -> int:
|
|
1043
|
+
"""``agenthelm project``: hand over to project-orchestrator's project entry, which owns it."""
|
|
1044
|
+
installed = installed_plugins(current_env(), {"project-orchestrator"})
|
|
1045
|
+
copies = [PLUGIN_ROOT.parent / "project-orchestrator", *(Path(item["path"]) for item in installed)]
|
|
1046
|
+
# Older copies predate the project entry; only those that ship it count.
|
|
1047
|
+
usable = [path for path in copies if (path / "runtime" / "project_cli.py").is_file()]
|
|
1048
|
+
if not usable:
|
|
1049
|
+
stale = any((path / "plugin.json").is_file() for path in copies)
|
|
1050
|
+
found = "(已安装的 project-orchestrator 版本过旧)" if stale else ""
|
|
1051
|
+
print(f"agenthelm project 需要 project-orchestrator 的项目入口,但没有找到{found}。\n"
|
|
1052
|
+
f" · 更新插件:{CLI} update,或从发布包安装运行时(见 INSTALL.md)", file=sys.stderr)
|
|
1053
|
+
return 3
|
|
1054
|
+
# Prefer the copy shipped next to this agent-pack (same release), then the newest installed one.
|
|
1055
|
+
sibling = PLUGIN_ROOT.parent / "project-orchestrator"
|
|
1056
|
+
newest = sibling if sibling in usable else \
|
|
1057
|
+
max(usable, key=lambda path: version_key(_manifest_version(path) or "") or (0,))
|
|
1058
|
+
sys.path.insert(0, str(newest / "runtime"))
|
|
1059
|
+
import project_cli # noqa: PLC0415 located at run time
|
|
1060
|
+
|
|
1061
|
+
project_cli.HOST = sys.modules[__name__]
|
|
1062
|
+
return project_cli.main(argv)
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def run_runtime(argv: list[str]) -> int:
|
|
1066
|
+
"""``agenthelm runtime``: the user-level runtime that project launchers use without any AI client."""
|
|
1067
|
+
sys.path.insert(0, str(RUNTIME_DIR))
|
|
1068
|
+
import agenthelm_runtime # noqa: PLC0415 - shipped next to this file
|
|
1069
|
+
|
|
1070
|
+
return agenthelm_runtime.main(argv, sys.modules[__name__])
|
|
1071
|
+
|
|
1072
|
+
|
|
1037
1073
|
def main(argv: list[str] | None = None) -> int:
|
|
1074
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
1075
|
+
if argv[:1] == ["project"]:
|
|
1076
|
+
return run_project(argv[1:])
|
|
1077
|
+
if argv[:1] == ["runtime"]:
|
|
1078
|
+
return run_runtime(argv[1:])
|
|
1038
1079
|
args = parser().parse_args(argv)
|
|
1039
1080
|
env = current_env(args.home)
|
|
1040
1081
|
home = env.home
|