@heretyc/subagent-mcp 2.6.0 → 2.6.2

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";
package/dist/setup.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // - Every config file is backed up before its first edit.
17
17
  // - Failures never abort the run: they are collected and reported at the end
18
18
  // with a copy-paste repair prompt the user can hand to Claude/Codex.
19
- import { existsSync, readFileSync, writeFileSync, copyFileSync, } from "node:fs";
19
+ import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, } from "node:fs";
20
20
  import { homedir } from "node:os";
21
21
  import { join, dirname, resolve } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
@@ -104,17 +104,14 @@ export function reconcileClaudeJson(cj, serverPath) {
104
104
  const cur = servers["subagent-mcp"];
105
105
  if (cur) {
106
106
  const args = cur.args;
107
- const exact = cur.command === "node" &&
108
- Array.isArray(args) &&
109
- args.length === 1 &&
110
- args[0] === serverPath;
107
+ const exact = cur.command === "subagent-mcp" && Array.isArray(args) && args.length === 0;
111
108
  if (exact)
112
109
  return { changed: false, status: "ok" };
113
110
  }
114
111
  servers["subagent-mcp"] = {
115
112
  type: "stdio",
116
- command: "node",
117
- args: [serverPath],
113
+ command: "subagent-mcp",
114
+ args: [],
118
115
  env: {},
119
116
  };
120
117
  return { changed: true, status: cur ? "repaired" : "added" };
@@ -144,7 +141,7 @@ export function reconcileCodexToml(toml, serverPath) {
144
141
  status: "added",
145
142
  };
146
143
  }
147
- if (m[0].includes(`args = ["${serverPath}"]`)) {
144
+ if (m[0].includes(`command = "node"`) && m[0].includes(`args = ["${serverPath}"]`)) {
148
145
  return { toml, changed: false, status: "ok" };
149
146
  }
150
147
  return {
@@ -242,26 +239,60 @@ function backup(file) {
242
239
  }
243
240
  }
244
241
  function runCmd(cmd, cmdArgs) {
242
+ return runCmdCapture(cmd, cmdArgs).ok;
243
+ }
244
+ function quoteWinShellArg(arg) {
245
+ if (!/[ \t"]/.test(arg))
246
+ return arg;
247
+ return `"${arg.replace(/"/g, '\\"')}"`;
248
+ }
249
+ function quoteWinShellExe(exe) {
250
+ return `"${exe.replace(/"/g, '\\"')}"`;
251
+ }
252
+ function runCmdCapture(cmd, cmdArgs) {
245
253
  console.log(` $ ${cmd} ${cmdArgs.join(" ")}`);
246
254
  if (DRY_RUN) {
247
255
  console.log(" (dry-run: skipped)");
248
- return true;
256
+ return { ok: true, stdout: "" };
249
257
  }
250
258
  try {
251
- if (process.platform === "win32") {
252
- // npm-installed CLIs are .cmd shims execFileSync can't spawn directly.
253
- const line = [cmd, ...cmdArgs.map((a) => (/\s/.test(a) ? `"${a}"` : a))].join(" ");
254
- execSync(line, { stdio: "pipe" });
255
- }
256
- else {
257
- execFileSync(cmd, cmdArgs, { stdio: "pipe" });
258
- }
259
- return true;
259
+ const exe = findOnPath(cmd) ?? cmd;
260
+ const isWinCmdShim = process.platform === "win32" && /\.(?:cmd|bat)$/i.test(exe);
261
+ const stdout = isWinCmdShim
262
+ ? execSync([quoteWinShellExe(exe), ...cmdArgs.map(quoteWinShellArg)].join(" "), { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
263
+ : execFileSync(exe, cmdArgs, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
264
+ return { ok: true, stdout };
260
265
  }
261
266
  catch {
262
- return false;
267
+ return { ok: false, stdout: "" };
263
268
  }
264
269
  }
270
+ export function claudeAddArgs() {
271
+ return ["mcp", "add", "subagent-mcp", "subagent-mcp", "-s", "user"];
272
+ }
273
+ export function claudeRemoveArgs() {
274
+ return ["mcp", "remove", "subagent-mcp", "-s", "user"];
275
+ }
276
+ export function codexAddArgs(serverPath) {
277
+ return ["mcp", "add", "subagent-mcp", "--", "node", serverPath];
278
+ }
279
+ export function codexRemoveArgs() {
280
+ return ["mcp", "remove", "subagent-mcp"];
281
+ }
282
+ function claudeRegisteredViaCli() {
283
+ const get = runCmdCapture("claude", ["mcp", "get", "subagent-mcp"]);
284
+ if (get.ok && get.stdout.includes("subagent-mcp"))
285
+ return true;
286
+ const list = runCmdCapture("claude", ["mcp", "list"]);
287
+ return list.ok && list.stdout.includes("subagent-mcp");
288
+ }
289
+ function codexRegisteredViaCli() {
290
+ const get = runCmdCapture("codex", ["mcp", "get", "subagent-mcp"]);
291
+ if (get.ok && get.stdout.includes("subagent-mcp"))
292
+ return true;
293
+ const list = runCmdCapture("codex", ["mcp", "list"]);
294
+ return list.ok && list.stdout.includes("subagent-mcp");
295
+ }
265
296
  const issues = [];
266
297
  function repairPromptFor(vendor, problem) {
267
298
  const p = serverPaths();
@@ -269,7 +300,7 @@ function repairPromptFor(vendor, problem) {
269
300
  return (`subagent-mcp setup hit a problem on my machine: ${problem}. ` +
270
301
  `The install root is "${fwd(INSTALL_ROOT)}". Please repair my Claude Code wiring: ` +
271
302
  `(1) register a user-scope MCP server named "subagent-mcp" running ` +
272
- `[node "${p.server}"] (use 'claude mcp add --scope user' or edit the mcpServers ` +
303
+ `the global bin shim "subagent-mcp" (use 'claude mcp add subagent-mcp subagent-mcp -s user' or edit the mcpServers ` +
273
304
  `key in ~/.claude.json), and (2) ensure ~/.claude/settings.json has a ` +
274
305
  `hooks.UserPromptSubmit entry {type:"command", command:"node", args:["${p.claudeHook}"]}. ` +
275
306
  `Back up any file before editing it.`);
@@ -308,20 +339,27 @@ function wireClaude() {
308
339
  const probe = JSON.parse(JSON.stringify(cj));
309
340
  const { status } = reconcileClaudeJson(probe, p.server);
310
341
  if (status === "ok") {
311
- describe("ok", "MCP server (user scope)");
342
+ let registered = claudeRegisteredViaCli();
343
+ if (!registered) {
344
+ runCmd("claude", claudeAddArgs());
345
+ registered = claudeRegisteredViaCli();
346
+ }
347
+ if (registered)
348
+ describe("ok", "MCP server (user scope)");
349
+ else
350
+ fail("claude", "MCP server file shape is correct, but 'claude mcp add' failed to register it with the CLI");
312
351
  }
313
352
  else {
314
353
  if (status === "repaired") {
315
354
  console.log(" MCP server registration points at a stale path — re-registering.");
316
- runCmd("claude", ["mcp", "remove", "-s", "user", "subagent-mcp"]);
355
+ runCmd("claude", claudeRemoveArgs());
317
356
  }
318
- const cliOk = runCmd("claude", [
319
- "mcp", "add", "--scope", "user", "subagent-mcp", "--", "node", p.server,
320
- ]);
357
+ const cliOk = runCmd("claude", claudeAddArgs());
358
+ const cliVerified = cliOk && claudeRegisteredViaCli();
321
359
  // Read back; if the CLI failed or didn't take, write the entry directly.
322
360
  const after = readJson(cjFile, {});
323
361
  const verify = reconcileClaudeJson(after, p.server);
324
- if (verify.status !== "ok" && !DRY_RUN) {
362
+ if (!cliVerified && verify.status !== "ok" && !DRY_RUN) {
325
363
  if (!cliOk)
326
364
  console.log(" 'claude mcp add' failed — writing ~/.claude.json directly.");
327
365
  backup(cjFile);
@@ -359,7 +397,14 @@ function wireCodex() {
359
397
  const cfg = join(codexDir, "config.toml");
360
398
  const toml = existsSync(cfg) ? readFileSync(cfg, "utf8") : "";
361
399
  const r = reconcileCodexToml(toml, p.server);
362
- if (r.changed && !DRY_RUN) {
400
+ if (r.status === "repaired") {
401
+ console.log(" MCP server registration points at a stale path — re-registering.");
402
+ runCmd("codex", codexRemoveArgs());
403
+ }
404
+ const cliOk = r.status === "ok" && codexRegisteredViaCli() ? true : runCmd("codex", codexAddArgs(p.server));
405
+ if (!cliOk && r.changed && !DRY_RUN) {
406
+ console.log(" 'codex mcp add' failed — writing ~/.codex/config.toml directly.");
407
+ mkdirSync(codexDir, { recursive: true });
363
408
  backup(cfg);
364
409
  writeFileSync(cfg, r.toml);
365
410
  }
@@ -377,6 +422,7 @@ function wireCodex() {
377
422
  const hookCmd = `node "${p.codexHook}"`;
378
423
  const { changed, statuses } = reconcileCodexHooks(h, hookCmd);
379
424
  if (changed && !DRY_RUN) {
425
+ mkdirSync(codexDir, { recursive: true });
380
426
  backup(hfile);
381
427
  writeFileSync(hfile, JSON.stringify(h, null, 2));
382
428
  }
@@ -392,7 +438,7 @@ function wireCodex() {
392
438
  fail("codex", `could not write hooks.json: ${e.message}`);
393
439
  }
394
440
  }
395
- export function verifyWiring(root = INSTALL_ROOT) {
441
+ export function verifyWiring(root = INSTALL_ROOT, repair = false) {
396
442
  const p = serverPaths(root);
397
443
  const results = [];
398
444
  const home = homedir();
@@ -403,7 +449,29 @@ export function verifyWiring(root = INSTALL_ROOT) {
403
449
  detail: missing.length === 0 ? `all present under ${fwd(root)}` : `missing: ${missing.join(", ")}`,
404
450
  });
405
451
  const hasClaude = findOnPath("claude") !== null;
452
+ const hasClaudeConfig = existsSync(join(home, ".claude.json"));
406
453
  if (hasClaude) {
454
+ const sj = readJson(join(home, ".claude", "settings.json"), {});
455
+ let registered = claudeRegisteredViaCli();
456
+ let repaired = false;
457
+ if (!registered && repair) {
458
+ runCmd("claude", claudeAddArgs());
459
+ repaired = true;
460
+ registered = claudeRegisteredViaCli();
461
+ }
462
+ const hk = reconcileClaudeSettings(JSON.parse(JSON.stringify(sj)), p.claudeHook);
463
+ results.push({
464
+ label: "claude: MCP server (user scope)",
465
+ ok: registered,
466
+ detail: registered ? (repaired ? "repaired" : "registered") : "not registered; CLI repair failed",
467
+ });
468
+ results.push({
469
+ label: "claude: UserPromptSubmit hook",
470
+ ok: hk.status === "ok",
471
+ detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
472
+ });
473
+ }
474
+ else if (hasClaudeConfig) {
407
475
  const cj = readJson(join(home, ".claude.json"), {});
408
476
  const sj = readJson(join(home, ".claude", "settings.json"), {});
409
477
  const srv = reconcileClaudeJson(JSON.parse(JSON.stringify(cj)), p.server);
@@ -411,34 +479,48 @@ export function verifyWiring(root = INSTALL_ROOT) {
411
479
  results.push({
412
480
  label: "claude: MCP server (user scope)",
413
481
  ok: srv.status === "ok",
414
- detail: srv.status === "ok" ? "registered" : `${srv.status === "repaired" ? "stale path" : "not registered"} run: subagent-mcp setup`,
482
+ detail: srv.status === "ok" ? "registered (file fallback)" : "not registered; claude CLI not on PATH",
415
483
  });
416
484
  results.push({
417
485
  label: "claude: UserPromptSubmit hook",
418
486
  ok: hk.status === "ok",
419
- detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} run: subagent-mcp setup`,
487
+ detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
420
488
  });
421
489
  }
422
- const hasCodex = findOnPath("codex") !== null || existsSync(join(home, ".codex"));
490
+ const hasCodexCli = findOnPath("codex") !== null;
491
+ const hasCodex = hasCodexCli || existsSync(join(home, ".codex"));
423
492
  if (hasCodex) {
424
493
  const cfg = join(home, ".codex", "config.toml");
425
494
  const toml = existsSync(cfg) ? readFileSync(cfg, "utf8") : "";
426
495
  const tomlR = reconcileCodexToml(toml, p.server);
427
496
  const hj = readJson(join(home, ".codex", "hooks.json"), { hooks: {} });
428
497
  const hkR = reconcileCodexHooks(hj, `node "${p.codexHook}"`);
498
+ let registered = false;
499
+ let repaired = false;
500
+ if (hasCodexCli) {
501
+ registered = codexRegisteredViaCli();
502
+ if (!registered && repair) {
503
+ runCmd("codex", codexAddArgs(p.server));
504
+ repaired = true;
505
+ registered = codexRegisteredViaCli();
506
+ }
507
+ }
508
+ else {
509
+ registered = tomlR.status === "ok";
510
+ }
429
511
  results.push({
430
512
  label: "codex: config.toml MCP server block",
431
- ok: tomlR.status === "ok",
432
- detail: tomlR.status === "ok" ? "registered" : `${tomlR.status === "repaired" ? "stale path" : "not registered"} run: subagent-mcp setup`,
513
+ ok: registered,
514
+ detail: registered ? (repaired ? "repaired" : "registered") : "not registered; CLI repair failed",
433
515
  });
434
516
  const allOk = Object.values(hkR.statuses).every((s) => s === "ok");
435
517
  results.push({
436
518
  label: "codex: SessionStart + UserPromptSubmit hooks",
437
519
  ok: allOk,
438
- detail: allOk ? "wired (trust via /hooks in Codex)" : "incomplete run: subagent-mcp setup",
520
+ detail: allOk ? "wired (trust via /hooks in Codex)" : "incomplete - run: subagent-mcp setup",
439
521
  });
440
522
  }
441
- if (!hasClaude && !hasCodex) {
523
+ if (!hasClaude && !hasClaudeConfig && !hasCodex) {
442
524
  results.push({
443
525
  label: "vendors",
444
526
  ok: false,
@@ -483,7 +565,7 @@ export async function runSetup() {
483
565
  // Read-back verification: report what is ACTUALLY on disk now.
484
566
  if (!DRY_RUN) {
485
567
  console.log("\n--- Verification (read-back) ---");
486
- for (const r of verifyWiring()) {
568
+ for (const r of verifyWiring(INSTALL_ROOT, false)) {
487
569
  console.log(` ${r.ok ? "PASS" : "FAIL"} ${r.label} — ${r.detail}`);
488
570
  }
489
571
  }
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "@heretyc/subagent-mcp",
3
- "version": "2.6.0",
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-directives.test.mjs && node test/setup-repair.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.6.2",
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-directives.test.mjs && node test/setup-repair.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,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 hook)");
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 hook)");
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);