@heretyc/subagent-mcp 2.12.1 → 2.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/routing.js CHANGED
@@ -12,7 +12,7 @@
12
12
  import { readFileSync } from "node:fs";
13
13
  import { fileURLToPath } from "node:url";
14
14
  /** Launch model enum accepted by buildCommand. */
15
- export const LAUNCH_MODELS = ["haiku", "sonnet", "opus", "opus-4-8", "gpt-5.5"];
15
+ export const LAUNCH_MODELS = ["haiku", "sonnet", "opus", "opus-4-8", "fable", "gpt-5.5"];
16
16
  /** Launch effort enum accepted by buildCommand/resolveEffort. */
17
17
  export const LAUNCH_EFFORTS = ["medium", "high", "xhigh", "max", "ultracode"];
18
18
  /** Sentinel reported (and passed-through harmlessly) for haiku, whose effort is ignored. */
@@ -28,12 +28,14 @@ const FULL_TO_SHORT = {
28
28
  "claude-opus-4-8": new Set(["opus", "opus-4-8"]),
29
29
  "claude-sonnet-4-6": "sonnet",
30
30
  "claude-haiku-4-5": "haiku",
31
+ "claude-fable-5": "fable",
31
32
  "gpt-5.5": "gpt-5.5",
32
33
  // Short ids may already appear in a hand-authored table; map them through.
33
34
  haiku: "haiku",
34
35
  sonnet: "sonnet",
35
36
  opus: "opus",
36
37
  "opus-4-8": "opus-4-8",
38
+ fable: "fable",
37
39
  };
38
40
  export const DEFAULT_BRANCH = "cost_efficiency";
39
41
  /** Resolve dist/routing-table.json relative to this module at runtime. */
@@ -76,6 +78,7 @@ export function mapModelToProvider(model) {
76
78
  model === "sonnet" ||
77
79
  model === "opus" ||
78
80
  model === "opus-4-8" ||
81
+ model === "fable" ||
79
82
  model.startsWith("claude-")) {
80
83
  return "claude";
81
84
  }
@@ -252,8 +255,8 @@ export function validatePresence(p) {
252
255
  }
253
256
  // provider+model must satisfy the existing match rule.
254
257
  if (provider && model) {
255
- if (provider === "claude" && !["haiku", "sonnet", "opus", "opus-4-8"].includes(model)) {
256
- return `Error: Claude provider only supports haiku, sonnet, opus, or opus-4-8. Got: ${model}`;
258
+ if (provider === "claude" && !["haiku", "sonnet", "opus", "opus-4-8", "fable"].includes(model)) {
259
+ return `Error: Claude provider only supports haiku, sonnet, opus, opus-4-8, or fable. Got: ${model}`;
257
260
  }
258
261
  if (provider === "codex" && model !== "gpt-5.5") {
259
262
  return `Error: Codex provider only supports gpt-5.5. Got: ${model}`;
@@ -1,2 +1,2 @@
1
1
  // GENERATED by scripts/gen-ruleset-scaffold.mjs from src/advanced-ruleset.py — DO NOT EDIT.
2
- export const RULESET_SCAFFOLD = "#!/usr/bin/env python3\n\"\"\"advanced-ruleset.py — final-authority model-routing override hook for subagent-mcp.\n\n(a) PERFORMANCE WARNING: this script runs synchronously inside EVERY launch_agent\n call. Slow rules slow every agent launch. Keep rules lean and low-latency —\n no network calls, no heavy imports at module top. This is YOUR responsibility;\n you have been warned.\n\n(b) OUTPUT CONTRACT (routing mode): print to stdout ONE JSON array — the modified\n candidate list (reorder / filter / replace allowed). Template:\n [\n {\"provider\": \"claude\", \"model\": \"sonnet\", \"effort\": \"high\", \"rank\": 1},\n {\"provider\": \"codex\", \"model\": \"gpt-5.5\", \"effort\": \"xhigh\", \"rank\": 2}\n ]\n Valid providers: claude, codex. Valid models: haiku, sonnet, opus, opus-4-8 (claude);\n gpt-5.5 (codex). Valid efforts: haiku -> \"none\" only; sonnet -> medium|high|xhigh|max;\n opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> medium|high|xhigh.\n \"rank\" on output is ignored. An EMPTY array vetoes the launch. Anything else\n invalid fails the launch hard — the server validates strictly.\n\n(c) INPUT CONTRACT (routing mode, invoked as: <python> advanced-ruleset.py route):\n stdin receives one JSON object:\n { \"candidates\": [ {\"provider\",\"model\",\"effort\",\"rank\"} ... ], # rank 1..N best->worst\n \"context\": { \"task_category\": str, \"cwd\": str,\n \"selection_mode\": \"auto\"|\"provider\"|\"provider_model\"|\"explicit\",\n \"provider\": str|None, \"model\": str|None, \"effort\": str|None } }\n OS environment variables are visible natively (os.environ).\n\nENV-CHECK MODE (no arguments): prints {\"ready\": true|false, \"load-rules\": true|false}.\nRuns once per MCP server process. load-rules false => ruleset silently disabled\nfor the rest of the process. Set LOAD_RULES = True below to activate.\n\"\"\"\nimport json\nimport sys\n\nLOAD_RULES = False\n\n# --- Requirements stub (scaffold itself is stdlib-only) ----------------------\n# List third-party distributions your rules import, e.g.:\n# REQUIREMENTS = [\"requests\", \"pyyaml\"]\n# Install with: <python> -m pip install <name> ...\nREQUIREMENTS = []\n\ndef missing_requirements():\n \"\"\"pip-check helper: returns the REQUIREMENTS entries not importable here.\"\"\"\n import importlib.util\n return [r for r in REQUIREMENTS\n if importlib.util.find_spec(r.replace(\"-\", \"_\")) is None]\n\ndef env_check():\n missing = missing_requirements()\n json.dump({\"ready\": not missing, \"load-rules\": bool(LOAD_RULES)}, sys.stdout)\n\ndef apply_rules(candidates, context):\n \"\"\"YOUR RULES HERE. Default: passthrough (returns the list unchanged).\"\"\"\n return candidates\n\ndef route():\n payload = json.load(sys.stdin)\n out = apply_rules(payload.get(\"candidates\", []), payload.get(\"context\", {}))\n json.dump(out, sys.stdout)\n\nif __name__ == \"__main__\":\n if len(sys.argv) > 1 and sys.argv[1] == \"route\":\n route()\n else:\n env_check()\n";
2
+ export const RULESET_SCAFFOLD = "#!/usr/bin/env python3\r\n\"\"\"advanced-ruleset.py — final-authority model-routing override hook for subagent-mcp.\r\n\r\n(a) PERFORMANCE WARNING: this script runs synchronously inside EVERY launch_agent\r\n call. Slow rules slow every agent launch. Keep rules lean and low-latency —\r\n no network calls, no heavy imports at module top. This is YOUR responsibility;\r\n you have been warned.\r\n\r\n(b) OUTPUT CONTRACT (routing mode): print to stdout ONE JSON array — the modified\r\n candidate list (reorder / filter / replace allowed). Template:\r\n [\r\n {\"provider\": \"claude\", \"model\": \"sonnet\", \"effort\": \"high\", \"rank\": 1},\r\n {\"provider\": \"codex\", \"model\": \"gpt-5.5\", \"effort\": \"xhigh\", \"rank\": 2}\r\n ]\r\n Valid providers: claude, codex. Valid models: haiku, sonnet, opus, opus-4-8, fable (claude);\r\n gpt-5.5 (codex). Valid efforts: haiku -> \"none\" only; sonnet -> medium|high|xhigh|max;\r\n fable -> medium|high|xhigh|max; opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> medium|high|xhigh.\r\n \"rank\" on output is ignored. An EMPTY array vetoes the launch. Anything else\r\n invalid fails the launch hard — the server validates strictly.\r\n\r\n(c) INPUT CONTRACT (routing mode, invoked as: <python> advanced-ruleset.py route):\r\n stdin receives one JSON object:\r\n { \"candidates\": [ {\"provider\",\"model\",\"effort\",\"rank\"} ... ], # rank 1..N best->worst\r\n \"context\": { \"task_category\": str, \"cwd\": str,\r\n \"selection_mode\": \"auto\"|\"provider\"|\"provider_model\"|\"explicit\",\r\n \"provider\": str|None, \"model\": str|None, \"effort\": str|None } }\r\n OS environment variables are visible natively (os.environ).\r\n\r\nENV-CHECK MODE (no arguments): prints {\"ready\": true|false, \"load-rules\": true|false}.\r\nRuns once per MCP server process. load-rules false => ruleset silently disabled\r\nfor the rest of the process. Set LOAD_RULES = True below to activate.\r\n\"\"\"\r\nimport json\r\nimport sys\r\n\r\nLOAD_RULES = False\r\n\r\n# --- Requirements stub (scaffold itself is stdlib-only) ----------------------\r\n# List third-party distributions your rules import, e.g.:\r\n# REQUIREMENTS = [\"requests\", \"pyyaml\"]\r\n# Install with: <python> -m pip install <name> ...\r\nREQUIREMENTS = []\r\n\r\ndef missing_requirements():\r\n \"\"\"pip-check helper: returns the REQUIREMENTS entries not importable here.\"\"\"\r\n import importlib.util\r\n return [r for r in REQUIREMENTS\r\n if importlib.util.find_spec(r.replace(\"-\", \"_\")) is None]\r\n\r\ndef env_check():\r\n missing = missing_requirements()\r\n json.dump({\"ready\": not missing, \"load-rules\": bool(LOAD_RULES)}, sys.stdout)\r\n\r\ndef apply_rules(candidates, context):\r\n \"\"\"YOUR RULES HERE. Default: passthrough (returns the list unchanged).\"\"\"\r\n return candidates\r\n\r\ndef route():\r\n payload = json.load(sys.stdin)\r\n out = apply_rules(payload.get(\"candidates\", []), payload.get(\"context\", {}))\r\n json.dump(out, sys.stdout)\r\n\r\nif __name__ == \"__main__\":\r\n if len(sys.argv) > 1 and sys.argv[1] == \"route\":\r\n route()\r\n else:\r\n env_check()\r\n";
package/dist/ruleset.js CHANGED
@@ -169,7 +169,7 @@ const CODEX_EFFORTS = LAUNCH_EFFORTS.filter((e) => e !== "ultracode" && e !== "m
169
169
  function effortAllowed(model, effort) {
170
170
  if (model === "haiku")
171
171
  return effort === HAIKU_EFFORT;
172
- if (model === "sonnet")
172
+ if (model === "sonnet" || model === "fable")
173
173
  return SONNET_EFFORTS.includes(effort);
174
174
  if (model === "opus" || model === "opus-4-8") {
175
175
  return LAUNCH_EFFORTS.includes(effort);
@@ -185,7 +185,7 @@ function effortAllowed(model, effort) {
185
185
  *
186
186
  * Per element: string provider/model/effort; provider ∈ {claude, codex};
187
187
  * model ∈ launch enum; provider↔model legality (claude↔{haiku,sonnet,opus,
188
- * opus-4-8}, codex↔gpt-5.5); per-model effort legality incl. haiku→"none".
188
+ * opus-4-8,fable}, codex↔gpt-5.5); per-model effort legality incl. haiku→"none".
189
189
  * Extra keys (incl. rank) are ignored on output; duplicates are allowed (the
190
190
  * attempt loop just tries them in order). An EMPTY array is VALID — it is the
191
191
  * limit case of the allowed filter operation and means "veto the launch"
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "@heretyc/subagent-mcp",
3
- "version": "2.12.1",
4
- "description": "MCP server that launches and manages always-interactive Claude Code and Codex sub-agent sessions (no direct Anthropic/OpenAI API).",
5
- "keywords": [
6
- "mcp",
7
- "model-context-protocol",
8
- "mcp-server",
9
- "claude",
10
- "claude-code",
11
- "codex",
12
- "orchestration",
13
- "subagent",
14
- "sub-agent",
15
- "multi-agent",
16
- "ai-agents",
17
- "anthropic",
18
- "openai"
19
- ],
20
- "type": "module",
21
- "main": "dist/index.js",
22
- "bin": {
23
- "subagent-mcp": "dist/index.js"
24
- },
25
- "files": [
26
- "dist",
27
- "directives",
28
- "scripts/postinstall.mjs",
29
- "LICENSE",
30
- "NOTICE",
31
- "README.md"
32
- ],
33
- "scripts": {
34
- "check:versions": "node scripts/check_version_sync.mjs",
35
- "build": "npm run check:versions && node scripts/gen-ruleset-scaffold.mjs && tsc && node scripts/copy-provider.mjs",
36
- "verify:npmjs-release": "node scripts/verify_npmjs_release.mjs",
37
- "start": "node dist/index.js",
38
- "postinstall": "node scripts/postinstall.mjs",
39
- "prepare": "npm run build",
40
- "prepublishOnly": "npm test",
41
- "test": "npm run check:versions && node test/effort.test.mjs && node test/drivers.test.mjs && node test/platform.test.mjs && node test/wait.test.mjs && node test/status.test.mjs && node test/output.test.mjs && node test/stream.test.mjs && node test/routing.test.mjs && node test/deadlock.test.mjs && node test/handler-validation.test.mjs && node test/index-handler.test.mjs && node test/ruleset.test.mjs && node test/ruleset-exec.test.mjs && node test/ruleset-handler.test.mjs && node test/failover.test.mjs && node test/global-concurrency-cap.test.mjs && node test/zombie.test.mjs && node test/orchestration-marker.test.mjs && node test/orchestration-hook-core.test.mjs && node test/orchestration-adapters.test.mjs && node test/orchestration-pretool.test.mjs && node test/orchestration-directives.test.mjs && node test/no-five-call.test.mjs && node test/no-per-provider-cap.test.mjs && node test/rag-pointers.test.mjs && node test/check-worktree-subagent.test.mjs && node test/launch-agent-upsert.test.mjs && node test/claude-session-limit.test.mjs && node test/init-migration.test.mjs && node test/init-global.test.mjs && node test/mirror-fragments.test.mjs && node test/performance-tier-effort.test.mjs && node test/setup-repair.test.mjs && node test/setup-quoting.test.mjs && node test/setup-wire.test.mjs && node test/setup-cli-integration.test.mjs && node test/init.test.mjs && node test/model-selection-mode.test.mjs && node test/cli-args.test.mjs && node test/index-guard.test.mjs && node test/drivers-guard.test.mjs && node test/audit-next13-lb3.test.mjs && node test/zombie-guard.test.mjs && node test/concurrency-guard.test.mjs && node test/hook-core-guard.test.mjs && node scripts/validate_provider.mjs && node scripts/validate_seed_sites.mjs && node scripts/validate_routing_audit.mjs && node test/seed-sites.test.mjs && node test/mcp-compliance.test.mjs"
42
- },
43
- "author": "Lexi Blackburn",
44
- "license": "Apache-2.0",
45
- "repository": {
46
- "type": "git",
47
- "url": "git+https://github.com/Heretyc/subagent-mcp.git"
48
- },
49
- "homepage": "https://github.com/Heretyc/subagent-mcp#readme",
50
- "bugs": {
51
- "url": "https://github.com/Heretyc/subagent-mcp/issues"
52
- },
53
- "dependencies": {
54
- "@anthropic-ai/claude-agent-sdk": "^0.3.177",
55
- "@modelcontextprotocol/sdk": "^1.0.0",
56
- "zod": "^4.4.3"
57
- },
58
- "devDependencies": {
59
- "@types/node": "^20.0.0",
60
- "typescript": "^5.0.0"
61
- },
62
- "engines": {
63
- "node": ">=18"
64
- },
65
- "publishConfig": {
66
- "registry": "https://registry.npmjs.org",
67
- "access": "public"
68
- }
69
- }
1
+ {
2
+ "name": "@heretyc/subagent-mcp",
3
+ "version": "2.12.3",
4
+ "description": "MCP server that launches and manages always-interactive Claude Code and Codex sub-agent sessions (no direct Anthropic/OpenAI API).",
5
+ "keywords": [
6
+ "mcp",
7
+ "model-context-protocol",
8
+ "mcp-server",
9
+ "claude",
10
+ "claude-code",
11
+ "codex",
12
+ "orchestration",
13
+ "subagent",
14
+ "sub-agent",
15
+ "multi-agent",
16
+ "ai-agents",
17
+ "anthropic",
18
+ "openai"
19
+ ],
20
+ "type": "module",
21
+ "main": "dist/index.js",
22
+ "bin": {
23
+ "subagent-mcp": "dist/index.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "directives",
28
+ "scripts/postinstall.mjs",
29
+ "LICENSE",
30
+ "NOTICE",
31
+ "README.md"
32
+ ],
33
+ "scripts": {
34
+ "check:versions": "node scripts/check_version_sync.mjs",
35
+ "build": "npm run check:versions && node scripts/gen-ruleset-scaffold.mjs && tsc && node scripts/copy-provider.mjs",
36
+ "verify:npmjs-release": "node scripts/verify_npmjs_release.mjs",
37
+ "start": "node dist/index.js",
38
+ "postinstall": "node scripts/postinstall.mjs",
39
+ "prepare": "npm run build",
40
+ "prepublishOnly": "npm test",
41
+ "test": "npm run check:versions && node test/effort.test.mjs && node test/drivers.test.mjs && node test/platform.test.mjs && node test/wait.test.mjs && node test/status.test.mjs && node test/output.test.mjs && node test/stream.test.mjs && node test/routing.test.mjs && node test/deadlock.test.mjs && node test/handler-validation.test.mjs && node test/index-handler.test.mjs && node test/ruleset.test.mjs && node test/ruleset-exec.test.mjs && node test/ruleset-handler.test.mjs && node test/failover.test.mjs && node test/global-concurrency-cap.test.mjs && node test/update-check.test.mjs && node test/zombie.test.mjs && node test/orchestration-marker.test.mjs && node test/orchestration-hook-core.test.mjs && node test/orchestration-adapters.test.mjs && node test/orchestration-pretool.test.mjs && node test/orchestration-directives.test.mjs && node test/no-five-call.test.mjs && node test/no-per-provider-cap.test.mjs && node test/rag-pointers.test.mjs && node test/check-worktree-subagent.test.mjs && node test/launch-agent-upsert.test.mjs && node test/claude-session-limit.test.mjs && node test/init-migration.test.mjs && node test/init-global.test.mjs && node test/mirror-fragments.test.mjs && node test/performance-tier-effort.test.mjs && node test/setup-repair.test.mjs && node test/setup-quoting.test.mjs && node test/setup-wire.test.mjs && node test/setup-cli-integration.test.mjs && node test/init.test.mjs && node test/model-selection-mode.test.mjs && node test/cli-args.test.mjs && node test/index-guard.test.mjs && node test/drivers-guard.test.mjs && node test/audit-next13-lb3.test.mjs && node test/zombie-guard.test.mjs && node test/concurrency-guard.test.mjs && node test/hook-core-guard.test.mjs && node scripts/validate_provider.mjs && node scripts/validate_seed_sites.mjs && node scripts/validate_routing_audit.mjs && node test/seed-sites.test.mjs && node test/mcp-compliance.test.mjs"
42
+ },
43
+ "author": "Lexi Blackburn",
44
+ "license": "Apache-2.0",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/Heretyc/subagent-mcp.git"
48
+ },
49
+ "homepage": "https://github.com/Heretyc/subagent-mcp#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/Heretyc/subagent-mcp/issues"
52
+ },
53
+ "dependencies": {
54
+ "@anthropic-ai/claude-agent-sdk": "^0.3.177",
55
+ "@modelcontextprotocol/sdk": "^1.0.0",
56
+ "zod": "^4.4.3"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^20.0.0",
60
+ "typescript": "^5.0.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=18"
64
+ },
65
+ "publishConfig": {
66
+ "registry": "https://registry.npmjs.org",
67
+ "access": "public"
68
+ }
69
+ }
@@ -1,102 +1,102 @@
1
- #!/usr/bin/env node
2
- // Postinstall banner for subagent-mcp.
3
- //
4
- // `npm install -g @heretyc/subagent-mcp` ships only the MCP server + hook
5
- // assets; it does NOT wire them into Claude Code / Codex. Without feedback the
6
- // user has no idea an addon landed, let alone that a second step is required.
7
- // This prints a clear "what installed / what to run next / how to verify"
8
- // banner so the install is self-explanatory.
9
- //
10
- // Rules:
11
- // - NEVER fail the install. Any error is swallowed; always exit 0.
12
- // - Only speak for a real end-user install. In the dev checkout (src/ present)
13
- // stay silent so `npm install` during development isn't noisy.
14
- // - Print only — do not mutate vendor config. Wiring is the explicit,
15
- // reversible `subagent-mcp setup` step.
16
-
17
- import { existsSync, readFileSync } from "node:fs";
18
- import { homedir } from "node:os";
19
- import { join, dirname, resolve } from "node:path";
20
- import { fileURLToPath } from "node:url";
21
- import { execSync } from "node:child_process";
22
-
23
- try {
24
- // Install root: scripts/postinstall.mjs -> scripts/ -> <root>
25
- const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
26
-
27
- // Dev checkout? (src/ only exists in the repo, never in the shipped tarball.)
28
- // Stay silent there — the maintainer doesn't need the end-user banner.
29
- if (existsSync(join(ROOT, "src"))) process.exit(0);
30
-
31
- let version = "";
32
- try {
33
- version = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version || "";
34
- } catch { /* version is cosmetic */ }
35
-
36
- // Best-effort vendor detection so the banner can say what WILL be wired.
37
- // Pure read-only; failures just fall back to "not detected".
38
- function has(cmd) {
39
- try {
40
- execSync(process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`, {
41
- stdio: "ignore",
42
- });
43
- return true;
44
- } catch {
45
- return false;
46
- }
47
- }
48
- const hasClaude = has("claude");
49
- const hasCodex = has("codex") || existsSync(join(homedir(), ".codex"));
50
-
51
- const L = [];
52
- const line = (s = "") => L.push(s);
53
- const bar = "============================================================";
54
-
55
- line();
56
- line(bar);
57
- line(` subagent-mcp installed${version ? ` (v${version})` : ""}`);
58
- line(bar);
59
- line();
60
- line(" This is an MCP ADDON for Claude Code CLI and Codex CLI.");
61
- line(" It is NOT active yet — one command wires it in.");
62
- line();
63
- line(" FINISH SETUP (auto-detects vendors, wires all present):");
64
- line();
65
- line(" subagent-mcp setup");
66
- line();
67
- line(" That registers the MCP server AND installs the per-turn");
68
- line(" orchestration-mode hooks for every vendor it finds.");
69
- line();
70
-
71
- // Detected vendors — concrete, so the user knows what setup will touch.
72
- if (hasClaude || hasCodex) {
73
- line(" Detected on this machine:");
74
- if (hasClaude) line(" - Claude Code CLI (will get MCP server + UserPromptSubmit/PreToolUse hooks)");
75
- if (hasCodex) line(" - Codex CLI (will get MCP server + SessionStart/UserPromptSubmit hooks)");
76
- } else {
77
- line(" No Claude Code or Codex CLI detected yet. Install one,");
78
- line(" then run: subagent-mcp setup");
79
- }
80
- line();
81
-
82
- line(" AFTER setup — confirm it took effect:");
83
- if (hasClaude || !hasCodex) {
84
- line(" - Claude Code: restart the session, run /mcp");
85
- line(" -> 'subagent-mcp' shows Connected.");
86
- }
87
- if (hasCodex || !hasClaude) {
88
- line(" - Codex CLI: restart the session, run /hooks");
89
- line(" -> TRUST the new subagent-mcp hook.");
90
- }
91
- line();
92
- line(" Preview without changes: subagent-mcp setup --dry-run");
93
- line(" Health check any time: subagent-mcp doctor");
94
- line(" Docs: https://github.com/Heretyc/subagent-mcp#readme");
95
- line(bar);
96
- line();
97
-
98
- process.stdout.write(L.join("\n") + "\n");
99
- } catch {
100
- // Never let a banner failure break the install.
101
- }
102
- process.exit(0);
1
+ #!/usr/bin/env node
2
+ // Postinstall banner for subagent-mcp.
3
+ //
4
+ // `npm install -g @heretyc/subagent-mcp` ships only the MCP server + hook
5
+ // assets; it does NOT wire them into Claude Code / Codex. Without feedback the
6
+ // user has no idea an addon landed, let alone that a second step is required.
7
+ // This prints a clear "what installed / what to run next / how to verify"
8
+ // banner so the install is self-explanatory.
9
+ //
10
+ // Rules:
11
+ // - NEVER fail the install. Any error is swallowed; always exit 0.
12
+ // - Only speak for a real end-user install. In the dev checkout (src/ present)
13
+ // stay silent so `npm install` during development isn't noisy.
14
+ // - Print only — do not mutate vendor config. Wiring is the explicit,
15
+ // reversible `subagent-mcp setup` step.
16
+
17
+ import { existsSync, readFileSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join, dirname, resolve } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+ import { execSync } from "node:child_process";
22
+
23
+ try {
24
+ // Install root: scripts/postinstall.mjs -> scripts/ -> <root>
25
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
26
+
27
+ // Dev checkout? (src/ only exists in the repo, never in the shipped tarball.)
28
+ // Stay silent there — the maintainer doesn't need the end-user banner.
29
+ if (existsSync(join(ROOT, "src"))) process.exit(0);
30
+
31
+ let version = "";
32
+ try {
33
+ version = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf8")).version || "";
34
+ } catch { /* version is cosmetic */ }
35
+
36
+ // Best-effort vendor detection so the banner can say what WILL be wired.
37
+ // Pure read-only; failures just fall back to "not detected".
38
+ function has(cmd) {
39
+ try {
40
+ execSync(process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`, {
41
+ stdio: "ignore",
42
+ });
43
+ return true;
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+ const hasClaude = has("claude");
49
+ const hasCodex = has("codex") || existsSync(join(homedir(), ".codex"));
50
+
51
+ const L = [];
52
+ const line = (s = "") => L.push(s);
53
+ const bar = "============================================================";
54
+
55
+ line();
56
+ line(bar);
57
+ line(` subagent-mcp installed${version ? ` (v${version})` : ""}`);
58
+ line(bar);
59
+ line();
60
+ line(" This is an MCP ADDON for Claude Code CLI and Codex CLI.");
61
+ line(" It is NOT active yet — one command wires it in.");
62
+ line();
63
+ line(" FINISH SETUP (auto-detects vendors, wires all present):");
64
+ line();
65
+ line(" subagent-mcp setup");
66
+ line();
67
+ line(" That registers the MCP server AND installs the per-turn");
68
+ line(" orchestration-mode hooks for every vendor it finds.");
69
+ line();
70
+
71
+ // Detected vendors — concrete, so the user knows what setup will touch.
72
+ if (hasClaude || hasCodex) {
73
+ line(" Detected on this machine:");
74
+ if (hasClaude) line(" - Claude Code CLI (will get MCP server + UserPromptSubmit/PreToolUse hooks)");
75
+ if (hasCodex) line(" - Codex CLI (will get MCP server + SessionStart/UserPromptSubmit hooks)");
76
+ } else {
77
+ line(" No Claude Code or Codex CLI detected yet. Install one,");
78
+ line(" then run: subagent-mcp setup");
79
+ }
80
+ line();
81
+
82
+ line(" AFTER setup — confirm it took effect:");
83
+ if (hasClaude || !hasCodex) {
84
+ line(" - Claude Code: restart the session, run /mcp");
85
+ line(" -> 'subagent-mcp' shows Connected.");
86
+ }
87
+ if (hasCodex || !hasClaude) {
88
+ line(" - Codex CLI: restart the session, run /hooks");
89
+ line(" -> TRUST the new subagent-mcp hook.");
90
+ }
91
+ line();
92
+ line(" Preview without changes: subagent-mcp setup --dry-run");
93
+ line(" Health check any time: subagent-mcp doctor");
94
+ line(" Docs: https://github.com/Heretyc/subagent-mcp#readme");
95
+ line(bar);
96
+ line();
97
+
98
+ process.stdout.write(L.join("\n") + "\n");
99
+ } catch {
100
+ // Never let a banner failure break the install.
101
+ }
102
+ process.exit(0);