@heretyc/subagent-mcp 2.8.3 → 2.8.6

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.
@@ -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\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 (claude);\r\n gpt-5.5 (codex). Valid efforts: haiku -> \"none\" only; sonnet -> low|medium|high|xhigh|max;\r\n opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> low|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";
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 -> low|medium|high|xhigh|max;\n opus/opus-4-8 -> those plus ultracode; gpt-5.5 -> low|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";
@@ -6,13 +6,13 @@
6
6
  // stalled - ALIVE but NO parsed visible provider stream item for the
7
7
  // heartbeat window. NOT a failure and NOT terminal; does NOT
8
8
  // count against provider concurrency caps.
9
- // finished - process exited 0.
9
+ // finished - current turn completed, or driver exited 0.
10
10
  // errored - process exited non-zero.
11
11
  // stopped - process was killed.
12
12
  //
13
13
  // Liveness is driven by a heartbeat: launch time is the initial heartbeat and
14
14
  // every subsequent PARSED visible provider stream item refreshes it (raw
15
- // raw stdout/stderr bytes do not). A live agent is
15
+ // stdout/stderr bytes do not). A live agent is
16
16
  // `processing` until the heartbeat is older than the window, at which point it
17
17
  // becomes `stalled`. Resumed visible activity returns it to `processing`.
18
18
  // 10-minute visible-stream heartbeat window. A live agent with no parsed
@@ -44,7 +44,8 @@ export function computeStatusTransition(input) {
44
44
  // opts in (poll_agent does; list_agents omits it to stay token-efficient).
45
45
  export function buildLivenessFields(status, exitCode, lastActivity, now, includeHint = true) {
46
46
  const idle_seconds = Math.floor((now - lastActivity) / 1000);
47
- const alive = exitCode === null && (status === "processing" || status === "stalled");
47
+ const alive = exitCode === null &&
48
+ (status === "processing" || status === "stalled" || status === "finished");
48
49
  const fields = { alive, idle_seconds };
49
50
  if (status === "stalled" && includeHint) {
50
51
  fields.hint =
@@ -3,18 +3,45 @@
3
3
  // "Visible" = provider stream events, summaries, and assistant messages a human
4
4
  // would see in the CLI. Provider-internal reasoning blocks (Claude `thinking` /
5
5
  // `redacted_thinking` content blocks, Codex `reasoning` items/events) are not
6
- // parsed into visible items. Supports Codex `--json` JSONL and Claude
7
- // stream-json / buffered-json where feasible. NEVER throws; unparseable lines
8
- // are skipped.
6
+ // parsed into visible items. Supports Codex app-server JSON-RPC notifications,
7
+ // older Codex JSONL, and Claude SDK/stream-json events where feasible. NEVER
8
+ // throws; unparseable lines are skipped.
9
9
  function pushText(out, type, text) {
10
10
  if (typeof text === "string" && text.trim().length > 0) {
11
11
  out.push({ type, text: text.trim() });
12
12
  }
13
13
  }
14
- // Codex `exec --json` is newline-delimited JSON. Visible: agent messages and
15
- // completed items that carry text. Chain-of-thought (anything whose type or
16
- // item_type is `reasoning`) is dropped.
14
+ // Codex app-server uses newline-delimited JSON-RPC. Visible: assistant deltas,
15
+ // agent messages, and completed items that carry text. Chain-of-thought
16
+ // (anything whose type or item_type is `reasoning`) is dropped.
17
17
  function collectCodex(e, out) {
18
+ if (typeof e.method === "string") {
19
+ const params = e.params && typeof e.params === "object" ? e.params : {};
20
+ if (e.method === "item/agentMessage/delta") {
21
+ pushText(out, "agent_message", params.delta);
22
+ return;
23
+ }
24
+ if (e.method === "item/started" || e.method === "item/completed") {
25
+ const item = params.item && typeof params.item === "object" ? params.item : {};
26
+ const itemType = typeof item.type === "string" ? item.type : "item";
27
+ if (itemType.includes("reasoning"))
28
+ return;
29
+ pushText(out, itemType, item.text ?? item.command ?? item.summary);
30
+ return;
31
+ }
32
+ if (e.method === "turn/completed") {
33
+ const turn = params.turn && typeof params.turn === "object" ? params.turn : {};
34
+ const items = Array.isArray(turn.items) ? turn.items : [];
35
+ for (const item of items) {
36
+ if (!item || typeof item !== "object")
37
+ continue;
38
+ const obj = item;
39
+ if (obj.type === "agentMessage")
40
+ pushText(out, "agent_message", obj.text);
41
+ }
42
+ return;
43
+ }
44
+ }
18
45
  const type = typeof e.type === "string" ? e.type : "";
19
46
  if (type.includes("reasoning"))
20
47
  return;
@@ -173,6 +200,24 @@ export function flushStream(provider, pending) {
173
200
  collectLine(provider, trimmed, items);
174
201
  return { items, pending: "", lines: [trimmed] };
175
202
  }
203
+ export function isTurnCompletedLine(provider, line) {
204
+ const trimmed = line.trim();
205
+ if (!trimmed)
206
+ return false;
207
+ try {
208
+ const evt = JSON.parse(trimmed);
209
+ if (provider === "codex") {
210
+ return evt.method === "turn/completed" || evt.type === "turn.completed";
211
+ }
212
+ if (provider === "claude") {
213
+ return evt.type === "result";
214
+ }
215
+ }
216
+ catch {
217
+ return false;
218
+ }
219
+ return false;
220
+ }
176
221
  // Append new items to a rolling buffer, retaining only the last `n`.
177
222
  export function retainLastN(buffer, items, n) {
178
223
  if (items.length === 0)
package/package.json CHANGED
@@ -1,51 +1,53 @@
1
- {
2
- "name": "@heretyc/subagent-mcp",
3
- "version": "2.8.3",
4
- "description": "MCP server that launches and manages local Claude Code and Codex CLI sub-agents as child processes (no direct Anthropic/OpenAI API).",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "subagent-mcp": "dist/index.js"
9
- },
10
- "files": [
11
- "dist",
12
- "directives",
13
- "scripts/postinstall.mjs",
14
- "LICENSE",
15
- "NOTICE",
16
- "README.md"
17
- ],
18
- "scripts": {
19
- "build": "node scripts/gen-ruleset-scaffold.mjs && tsc && node scripts/copy-provider.mjs",
20
- "start": "node dist/index.js",
21
- "postinstall": "node scripts/postinstall.mjs",
22
- "prepare": "npm run build",
23
- "prepublishOnly": "npm test",
24
- "test": "node test/effort.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/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/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/cli-args.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"
25
- },
26
- "author": "Lexi Blackburn",
27
- "license": "Apache-2.0",
28
- "repository": {
29
- "type": "git",
30
- "url": "git+https://github.com/Heretyc/subagent-mcp.git"
31
- },
32
- "homepage": "https://github.com/Heretyc/subagent-mcp#readme",
33
- "bugs": {
34
- "url": "https://github.com/Heretyc/subagent-mcp/issues"
35
- },
36
- "dependencies": {
37
- "@modelcontextprotocol/sdk": "^1.0.0",
38
- "zod": "^3.0.0"
39
- },
40
- "devDependencies": {
41
- "@types/node": "^20.0.0",
42
- "typescript": "^5.0.0"
43
- },
44
- "engines": {
45
- "node": ">=18"
46
- },
47
- "publishConfig": {
48
- "registry": "https://npm.pkg.github.com",
49
- "access": "public"
50
- }
51
- }
1
+ {
2
+ "name": "@heretyc/subagent-mcp",
3
+ "version": "2.8.6",
4
+ "description": "MCP server that launches and manages always-interactive Claude Code and Codex sub-agent sessions (no direct Anthropic/OpenAI API).",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "subagent-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "directives",
13
+ "scripts/postinstall.mjs",
14
+ "LICENSE",
15
+ "NOTICE",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "check:versions": "node scripts/check_version_sync.mjs",
20
+ "build": "npm run check:versions && node scripts/gen-ruleset-scaffold.mjs && tsc && node scripts/copy-provider.mjs",
21
+ "start": "node dist/index.js",
22
+ "postinstall": "node scripts/postinstall.mjs",
23
+ "prepare": "npm run build",
24
+ "prepublishOnly": "npm test",
25
+ "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/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/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/cli-args.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"
26
+ },
27
+ "author": "Lexi Blackburn",
28
+ "license": "Apache-2.0",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/Heretyc/subagent-mcp.git"
32
+ },
33
+ "homepage": "https://github.com/Heretyc/subagent-mcp#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/Heretyc/subagent-mcp/issues"
36
+ },
37
+ "dependencies": {
38
+ "@anthropic-ai/claude-agent-sdk": "^0.3.177",
39
+ "@modelcontextprotocol/sdk": "^1.0.0",
40
+ "zod": "^4.4.3"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.0.0",
44
+ "typescript": "^5.0.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "publishConfig": {
50
+ "registry": "https://npm.pkg.github.com",
51
+ "access": "public"
52
+ }
53
+ }
@@ -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);