@sakanano-moe/agenthelm 6.6.0 → 6.8.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/package.json +2 -2
- package/runtime/index.json +15 -15
- package/runtime/plugins/agent-pack/.codex-plugin/plugin.json +11 -4
- package/runtime/plugins/agent-pack/plugin.json +1 -1
- package/runtime/plugins/agent-pack/references/capability-config.md +29 -5
- package/runtime/plugins/agent-pack/runtime/agent_pack_claude.py +448 -0
- package/runtime/plugins/agent-pack/runtime/agent_pack_config.py +178 -22
- package/runtime/plugins/agent-pack/runtime/agenthelm.py +237 -6
- package/runtime/plugins/agent-pack/runtime/agenthelm_runtime.py +19 -0
- package/runtime/plugins/agent-pack/runtime/mcp_server.py +30 -8
- package/runtime/plugins/agent-pack/skills/agent-pack/SKILL.md +2 -2
- package/runtime/plugins/agents-init/.codex-plugin/plugin.json +5 -5
- package/runtime/plugins/agents-init/content.json +15 -0
- package/runtime/plugins/agents-init/plugin.json +2 -2
- package/runtime/plugins/agents-init/skills/agents-init/SKILL.md +14 -1
- package/runtime/plugins/agents-init/skills/agents-init/project.schema.json +18 -3
- package/runtime/plugins/agents-init/skills/agents-init/scripts/init_project.py +43 -4
- package/runtime/plugins/agents-init/skills/agents-init/scripts/scaffold.py +933 -0
- package/runtime/plugins/agents-init/skills/agents-init/scripts/test_scaffold.py +551 -0
- package/runtime/plugins/agents-init/skills/agents-init/skeleton.json +64 -0
- package/runtime/plugins/agents-init/skills/agents-init/skill.json +3 -3
- package/runtime/plugins/opinion-manager/.codex-plugin/plugin.json +5 -5
- package/runtime/plugins/opinion-manager/plugin.json +1 -1
- package/runtime/plugins/opinion-manager/skills/opinion-manager/SKILL.md +1 -1
- package/runtime/plugins/opinion-manager/skills/opinion-manager/agents/openai.yaml +2 -2
- package/runtime/plugins/project-orchestrator/.codex-plugin/plugin.json +10 -4
- package/runtime/plugins/project-orchestrator/plugin.json +1 -1
- package/runtime/plugins/project-orchestrator/runtime/project_cli.py +69 -9
- package/runtime/plugins/project-orchestrator/runtime/project_maintain.py +380 -27
- package/runtime/plugins/project-orchestrator/runtime/project_menu.py +18 -0
- package/runtime/plugins/project-orchestrator/runtime/project_real.py +7 -1
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/SKILL.md +1 -1
- package/runtime/plugins/project-orchestrator/skills/project-orchestrator/agents/openai.yaml +2 -2
|
@@ -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
|