@heretyc/subagent-mcp 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/doctor.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  // `subagent-mcp doctor` — read-only health check for the installed addon.
3
3
  //
4
- // Diagnoses without touching any file: install completeness, vendor presence,
5
- // and whether each vendor's wiring (MCP server + hooks) points at THIS install.
6
- // The fixer is always `subagent-mcp setup` (idempotent, self-repairing); doctor
7
- // just tells you whether you need it and what exactly is wrong.
4
+ // Diagnoses install completeness, vendor presence, and whether each vendor's
5
+ // wiring (MCP server + hooks) points at THIS install. Doctor self-repairs
6
+ // missing MCP registrations via vendor CLIs; use `subagent-mcp setup` for
7
+ // config-file and hook repairs.
8
8
  //
9
9
  // Exit code: 0 = everything healthy, 1 = at least one check failed.
10
10
  import { verifyWiring } from "./setup.js";
11
11
  export async function runDoctor() {
12
- console.log("subagent-mcp doctor (read-only changes nothing)\n");
12
+ console.log("subagent-mcp doctor (checks wiring; repairs missing MCP registrations via vendor CLIs)\n");
13
13
  const major = Number(process.versions.node.split(".")[0]);
14
14
  console.log(` ${major >= 18 ? "PASS" : "FAIL"} node version — ${process.versions.node}` +
15
15
  (major >= 18 ? "" : " (Node >= 18 required)"));
16
16
  let failed = major < 18 ? 1 : 0;
17
- for (const r of verifyWiring()) {
17
+ for (const r of verifyWiring(undefined, true)) {
18
18
  console.log(` ${r.ok ? "PASS" : "FAIL"} ${r.label} — ${r.detail}`);
19
19
  if (!r.ok)
20
20
  failed++;
package/dist/setup.js CHANGED
@@ -16,13 +16,14 @@
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";
23
23
  import { execFileSync, execSync } from "node:child_process";
24
24
  const cliArgs = process.argv.slice(3); // argv[2]='setup', flags start at [3]
25
25
  const DRY_RUN = cliArgs.includes("--dry-run");
26
+ export const SERVER_NAME = "subagent-mcp";
26
27
  // Install root: dist/setup.js -> dist/ -> <install-root>
27
28
  const INSTALL_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
28
29
  export function fwd(p) {
@@ -104,17 +105,14 @@ export function reconcileClaudeJson(cj, serverPath) {
104
105
  const cur = servers["subagent-mcp"];
105
106
  if (cur) {
106
107
  const args = cur.args;
107
- const exact = cur.command === "node" &&
108
- Array.isArray(args) &&
109
- args.length === 1 &&
110
- args[0] === serverPath;
108
+ const exact = cur.command === "subagent-mcp" && Array.isArray(args) && args.length === 0;
111
109
  if (exact)
112
110
  return { changed: false, status: "ok" };
113
111
  }
114
112
  servers["subagent-mcp"] = {
115
113
  type: "stdio",
116
- command: "node",
117
- args: [serverPath],
114
+ command: "subagent-mcp",
115
+ args: [],
118
116
  env: {},
119
117
  };
120
118
  return { changed: true, status: cur ? "repaired" : "added" };
@@ -144,7 +142,7 @@ export function reconcileCodexToml(toml, serverPath) {
144
142
  status: "added",
145
143
  };
146
144
  }
147
- if (m[0].includes(`args = ["${serverPath}"]`)) {
145
+ if (m[0].includes(`command = "node"`) && m[0].includes(`args = ["${serverPath}"]`)) {
148
146
  return { toml, changed: false, status: "ok" };
149
147
  }
150
148
  return {
@@ -242,26 +240,113 @@ function backup(file) {
242
240
  }
243
241
  }
244
242
  function runCmd(cmd, cmdArgs) {
243
+ return runCmdCapture(cmd, cmdArgs).ok;
244
+ }
245
+ /**
246
+ * Parse an npm cmd-shim (.cmd) for its dp0-relative node script.
247
+ * Matches both modern `"%dp0%\node_modules\...\cli.js"` and legacy
248
+ * `"%~dp0\..."` forms. Returns the absolute JS path or null.
249
+ */
250
+ export function resolveCmdShimNodeScript(cmdPath) {
251
+ try {
252
+ const text = readFileSync(cmdPath, "utf8");
253
+ const m = text.match(/"%(?:~dp0|dp0%)\\([^"]+\.(?:js|cjs|mjs))"/i);
254
+ if (!m)
255
+ return null;
256
+ const js = join(dirname(cmdPath), m[1]);
257
+ return existsSync(js) ? js : null;
258
+ }
259
+ catch {
260
+ return null;
261
+ }
262
+ }
263
+ // Conservative safe-charset: quote on ANYTHING else, including the cmd.exe
264
+ // metachars & | < > ^ ( ) % ! plus space, tab, and ".
265
+ const WIN_SAFE_ARG = /^[A-Za-z0-9_.,:=@+\/\\-]+$/;
266
+ export function quoteWinShellArg(arg) {
267
+ if (arg !== "" && WIN_SAFE_ARG.test(arg))
268
+ return arg;
269
+ // "" doubling: quote-state-safe at the cmd parse stage, a literal " at the
270
+ // final MSVCRT argv stage (\" would close the quote and expose metachars).
271
+ return `"${arg.replace(/"/g, '""')}"`;
272
+ }
273
+ export function quoteWinShellExe(exe) {
274
+ return `"${exe.replace(/"/g, '""')}"`;
275
+ }
276
+ function runCmdCapture(cmd, cmdArgs) {
245
277
  console.log(` $ ${cmd} ${cmdArgs.join(" ")}`);
246
278
  if (DRY_RUN) {
247
279
  console.log(" (dry-run: skipped)");
248
- return true;
280
+ return { ok: true, stdout: "" };
249
281
  }
250
282
  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" });
283
+ const exe = findOnPath(cmd) ?? cmd;
284
+ const isWinCmdShim = process.platform === "win32" && /\.(?:cmd|bat)$/i.test(exe);
285
+ let stdout;
286
+ if (!isWinCmdShim) {
287
+ stdout = execFileSync(exe, cmdArgs, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
255
288
  }
256
289
  else {
257
- execFileSync(cmd, cmdArgs, { stdio: "pipe" });
290
+ // Primary: bypass cmd.exe by invoking node on the shim's JS entry —
291
+ // execFileSync without a shell does correct argv quoting, so cmd.exe
292
+ // metachar/percent expansion never applies.
293
+ const js = resolveCmdShimNodeScript(exe);
294
+ if (js) {
295
+ stdout = execFileSync(process.execPath, [js, ...cmdArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
296
+ }
297
+ else {
298
+ // Fallback for non-npm shims: hardened cmd.exe quoting. '%' is the one
299
+ // channel cmd cannot neutralize on a /c line — warn, don't reject.
300
+ if ([exe, ...cmdArgs].some((a) => a.includes("%"))) {
301
+ console.log(" note: an argument contains '%' — cmd.exe may expand it as an env var; if this command fails, check for name collisions.");
302
+ }
303
+ stdout = execSync([quoteWinShellExe(exe), ...cmdArgs.map(quoteWinShellArg)].join(" "), {
304
+ encoding: "utf8",
305
+ stdio: ["ignore", "pipe", "pipe"],
306
+ });
307
+ }
258
308
  }
259
- return true;
309
+ return { ok: true, stdout };
260
310
  }
261
311
  catch {
262
- return false;
312
+ return { ok: false, stdout: "" };
263
313
  }
264
314
  }
315
+ export function claudeAddArgs() {
316
+ return ["mcp", "add", "subagent-mcp", "subagent-mcp", "-s", "user"];
317
+ }
318
+ export function claudeRemoveArgs() {
319
+ return ["mcp", "remove", "subagent-mcp", "-s", "user"];
320
+ }
321
+ export function codexAddArgs(serverPath) {
322
+ return ["mcp", "add", "subagent-mcp", "--", "node", serverPath];
323
+ }
324
+ export function codexRemoveArgs() {
325
+ return ["mcp", "remove", "subagent-mcp"];
326
+ }
327
+ /**
328
+ * True iff CLI output mentions `name` as a standalone token: the chars
329
+ * immediately around it must not be server-name chars [A-Za-z0-9._-].
330
+ * Robust to `claude mcp list` / `codex mcp list` / `mcp get` formats
331
+ * ("name: cmd - ✓ Connected", table rows, "Name: x" detail views), and
332
+ * rejects sibling names like `subagent-mcp-dev` / `my-subagent-mcp`.
333
+ */
334
+ export function outputListsServer(stdout, name = SERVER_NAME) {
335
+ const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
336
+ return new RegExp(`(^|[^A-Za-z0-9._-])${esc}($|[^A-Za-z0-9._-])`, "m").test(stdout);
337
+ }
338
+ const defaultExecDeps = {
339
+ run: runCmd,
340
+ capture: runCmdCapture,
341
+ dryRun: DRY_RUN,
342
+ };
343
+ function registeredViaCli(cli, deps = defaultExecDeps) {
344
+ const get = deps.capture(cli, ["mcp", "get", SERVER_NAME]);
345
+ if (get.ok && outputListsServer(get.stdout))
346
+ return true;
347
+ const list = deps.capture(cli, ["mcp", "list"]);
348
+ return list.ok && outputListsServer(list.stdout);
349
+ }
265
350
  const issues = [];
266
351
  function repairPromptFor(vendor, problem) {
267
352
  const p = serverPaths();
@@ -269,7 +354,7 @@ function repairPromptFor(vendor, problem) {
269
354
  return (`subagent-mcp setup hit a problem on my machine: ${problem}. ` +
270
355
  `The install root is "${fwd(INSTALL_ROOT)}". Please repair my Claude Code wiring: ` +
271
356
  `(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 ` +
357
+ `the global bin shim "subagent-mcp" (use 'claude mcp add subagent-mcp subagent-mcp -s user' or edit the mcpServers ` +
273
358
  `key in ~/.claude.json), and (2) ensure ~/.claude/settings.json has a ` +
274
359
  `hooks.UserPromptSubmit entry {type:"command", command:"node", args:["${p.claudeHook}"]}. ` +
275
360
  `Back up any file before editing it.`);
@@ -297,38 +382,91 @@ function describe(status, what) {
297
382
  else
298
383
  console.log(` ${what}: pointed at a stale path — repaired.`);
299
384
  }
385
+ export function vendorWireSpecs(p = serverPaths(), home = homedir()) {
386
+ const claudeFile = join(home, ".claude.json");
387
+ const codexDir = join(home, ".codex");
388
+ const codexFile = join(codexDir, "config.toml");
389
+ return {
390
+ claude: {
391
+ vendor: "claude",
392
+ cli: "claude",
393
+ configFile: claudeFile,
394
+ addArgs: claudeAddArgs(),
395
+ removeArgs: claudeRemoveArgs(),
396
+ read: () => readJson(claudeFile, {}),
397
+ reconcile: (cfg) => {
398
+ const r = reconcileClaudeJson(cfg, p.server);
399
+ return { status: r.status, changed: r.changed, out: cfg };
400
+ },
401
+ serialize: (out) => JSON.stringify(out, null, 2),
402
+ cliFailMsg: "MCP server file shape is correct, but 'claude mcp add' failed to register it with the CLI",
403
+ },
404
+ codex: {
405
+ vendor: "codex",
406
+ cli: "codex",
407
+ configFile: codexFile,
408
+ addArgs: codexAddArgs(p.server),
409
+ removeArgs: codexRemoveArgs(),
410
+ read: () => (existsSync(codexFile) ? readFileSync(codexFile, "utf8") : ""),
411
+ reconcile: (cfg) => {
412
+ const r = reconcileCodexToml(cfg, p.server);
413
+ return { status: r.status, changed: r.changed, out: r.toml };
414
+ },
415
+ serialize: (out) => out,
416
+ ensureDir: () => mkdirSync(codexDir, { recursive: true }),
417
+ cliFailMsg: "MCP server file shape is correct, but 'codex mcp add' failed to register it with the CLI",
418
+ },
419
+ };
420
+ }
421
+ /**
422
+ * Single wiring driver for both vendors. Policy: CLI-first -> read-back ->
423
+ * reconcile -> unconditional canonical write on divergence -> fail only when
424
+ * neither the CLI registration nor the file fallback took.
425
+ */
426
+ export function wireMcpServer(spec, deps = defaultExecDeps) {
427
+ const initial = spec.reconcile(spec.read());
428
+ if (initial.status === "repaired") {
429
+ console.log(" MCP server registration points at a stale path — re-registering.");
430
+ deps.run(spec.cli, spec.removeArgs);
431
+ }
432
+ let registered = initial.status === "ok" && registeredViaCli(spec.cli, deps);
433
+ if (!registered) {
434
+ deps.run(spec.cli, spec.addArgs);
435
+ registered = registeredViaCli(spec.cli, deps);
436
+ }
437
+ if (deps.dryRun) {
438
+ // Never verify/fail/write in dry-run: capture() returns empty stdout, so
439
+ // any verification below would fail spuriously.
440
+ if (initial.changed)
441
+ console.log(" (dry-run: not written)");
442
+ return { status: initial.status, registered: true, wroteFile: false, failure: null };
443
+ }
444
+ const after = spec.reconcile(spec.read()); // read-back: what is ACTUALLY on disk now
445
+ let wroteFile = false;
446
+ if (after.status !== "ok") {
447
+ spec.ensureDir?.();
448
+ backup(spec.configFile);
449
+ writeFileSync(spec.configFile, spec.serialize(after.out));
450
+ wroteFile = true;
451
+ console.log(registered
452
+ ? ` NOTE: ${spec.cli} CLI registration diverged from the canonical config — rewrote ${spec.configFile} to the canonical form.`
453
+ : ` '${spec.cli} mcp add' failed — writing ${spec.configFile} directly.`);
454
+ }
455
+ const failure = registered || wroteFile ? null : spec.cliFailMsg;
456
+ return { status: initial.status, registered, wroteFile, failure };
457
+ }
300
458
  function wireClaude() {
301
459
  console.log("\n--- Claude Code CLI ---");
302
460
  const p = serverPaths();
303
- const cjFile = join(homedir(), ".claude.json");
304
- // 1) MCP server (user scope). Reconcile against ~/.claude.json; prefer the
305
- // official CLI for writes, fall back to a direct (schema-identical) edit.
461
+ const specs = vendorWireSpecs(p);
462
+ // 1) MCP server (user scope). CLI-first, read-back verified, with a direct
463
+ // (schema-identical) ~/.claude.json write whenever the file diverges.
306
464
  try {
307
- const cj = readJson(cjFile, {});
308
- const probe = JSON.parse(JSON.stringify(cj));
309
- const { status } = reconcileClaudeJson(probe, p.server);
310
- if (status === "ok") {
311
- describe("ok", "MCP server (user scope)");
312
- }
313
- else {
314
- if (status === "repaired") {
315
- console.log(" MCP server registration points at a stale path — re-registering.");
316
- runCmd("claude", ["mcp", "remove", "-s", "user", "subagent-mcp"]);
317
- }
318
- const cliOk = runCmd("claude", [
319
- "mcp", "add", "--scope", "user", "subagent-mcp", "--", "node", p.server,
320
- ]);
321
- // Read back; if the CLI failed or didn't take, write the entry directly.
322
- const after = readJson(cjFile, {});
323
- const verify = reconcileClaudeJson(after, p.server);
324
- if (verify.status !== "ok" && !DRY_RUN) {
325
- if (!cliOk)
326
- console.log(" 'claude mcp add' failed — writing ~/.claude.json directly.");
327
- backup(cjFile);
328
- writeFileSync(cjFile, JSON.stringify(after, null, 2));
329
- }
330
- describe(status, "MCP server (user scope)");
331
- }
465
+ const r = wireMcpServer(specs.claude);
466
+ if (r.failure)
467
+ fail("claude", r.failure);
468
+ else
469
+ describe(r.status, "MCP server (user scope)");
332
470
  }
333
471
  catch (e) {
334
472
  fail("claude", `could not register the MCP server: ${e.message}`);
@@ -354,18 +492,15 @@ function wireCodex() {
354
492
  console.log("\n--- Codex CLI ---");
355
493
  const p = serverPaths();
356
494
  const codexDir = join(homedir(), ".codex");
495
+ const specs = vendorWireSpecs(p);
357
496
  // 1) config.toml — MCP server block (created if the file is missing).
358
497
  try {
359
- const cfg = join(codexDir, "config.toml");
360
- const toml = existsSync(cfg) ? readFileSync(cfg, "utf8") : "";
361
- const r = reconcileCodexToml(toml, p.server);
362
- if (r.changed && !DRY_RUN) {
363
- backup(cfg);
364
- writeFileSync(cfg, r.toml);
365
- }
366
- describe(r.status, toml === "" ? "config.toml (created) MCP server block" : "config.toml MCP server block");
367
- if (r.changed && DRY_RUN)
368
- console.log(" (dry-run: not written)");
498
+ const existed = existsSync(specs.codex.configFile);
499
+ const r = wireMcpServer(specs.codex);
500
+ if (r.failure)
501
+ fail("codex", r.failure);
502
+ else
503
+ describe(r.status, existed ? "config.toml MCP server block" : "config.toml (created) MCP server block");
369
504
  }
370
505
  catch (e) {
371
506
  fail("codex", `could not write config.toml: ${e.message}`);
@@ -377,6 +512,7 @@ function wireCodex() {
377
512
  const hookCmd = `node "${p.codexHook}"`;
378
513
  const { changed, statuses } = reconcileCodexHooks(h, hookCmd);
379
514
  if (changed && !DRY_RUN) {
515
+ mkdirSync(codexDir, { recursive: true });
380
516
  backup(hfile);
381
517
  writeFileSync(hfile, JSON.stringify(h, null, 2));
382
518
  }
@@ -392,7 +528,26 @@ function wireCodex() {
392
528
  fail("codex", `could not write hooks.json: ${e.message}`);
393
529
  }
394
530
  }
395
- export function verifyWiring(root = INSTALL_ROOT) {
531
+ /** Detail string for a CLI registration check; "CLI repair failed" only when a
532
+ * repair was actually attempted. */
533
+ export function registrationDetail(registered, attemptedRepair) {
534
+ if (registered)
535
+ return attemptedRepair ? "repaired" : "registered";
536
+ return attemptedRepair
537
+ ? "not registered; CLI repair failed"
538
+ : "not registered — run: subagent-mcp doctor";
539
+ }
540
+ function checkCliRegistration(cli, addArgs, repair, deps = defaultExecDeps) {
541
+ let registered = registeredViaCli(cli, deps);
542
+ let attemptedRepair = false;
543
+ if (!registered && repair) {
544
+ deps.run(cli, addArgs);
545
+ attemptedRepair = true;
546
+ registered = registeredViaCli(cli, deps);
547
+ }
548
+ return { registered, attemptedRepair };
549
+ }
550
+ export function verifyWiring(root = INSTALL_ROOT, repair = false) {
396
551
  const p = serverPaths(root);
397
552
  const results = [];
398
553
  const home = homedir();
@@ -403,42 +558,70 @@ export function verifyWiring(root = INSTALL_ROOT) {
403
558
  detail: missing.length === 0 ? `all present under ${fwd(root)}` : `missing: ${missing.join(", ")}`,
404
559
  });
405
560
  const hasClaude = findOnPath("claude") !== null;
561
+ const hasClaudeConfig = existsSync(join(home, ".claude.json"));
406
562
  if (hasClaude) {
563
+ const sj = readJson(join(home, ".claude", "settings.json"), {});
564
+ const { registered, attemptedRepair } = checkCliRegistration("claude", claudeAddArgs(), repair);
565
+ const hk = reconcileClaudeSettings(sj, p.claudeHook);
566
+ results.push({
567
+ label: "claude: MCP server (user scope)",
568
+ ok: registered,
569
+ detail: registrationDetail(registered, attemptedRepair),
570
+ });
571
+ results.push({
572
+ label: "claude: UserPromptSubmit hook",
573
+ ok: hk.status === "ok",
574
+ detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
575
+ });
576
+ }
577
+ else if (hasClaudeConfig) {
407
578
  const cj = readJson(join(home, ".claude.json"), {});
408
579
  const sj = readJson(join(home, ".claude", "settings.json"), {});
409
- const srv = reconcileClaudeJson(JSON.parse(JSON.stringify(cj)), p.server);
410
- const hk = reconcileClaudeSettings(JSON.parse(JSON.stringify(sj)), p.claudeHook);
580
+ const srv = reconcileClaudeJson(cj, p.server);
581
+ const hk = reconcileClaudeSettings(sj, p.claudeHook);
411
582
  results.push({
412
583
  label: "claude: MCP server (user scope)",
413
584
  ok: srv.status === "ok",
414
- detail: srv.status === "ok" ? "registered" : `${srv.status === "repaired" ? "stale path" : "not registered"} — run: subagent-mcp setup`,
585
+ detail: srv.status === "ok" ? "registered (file fallback)" : "config stale — run: subagent-mcp setup",
415
586
  });
416
587
  results.push({
417
588
  label: "claude: UserPromptSubmit hook",
418
589
  ok: hk.status === "ok",
419
- detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} run: subagent-mcp setup`,
590
+ detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
420
591
  });
421
592
  }
422
- const hasCodex = findOnPath("codex") !== null || existsSync(join(home, ".codex"));
593
+ const hasCodexCli = findOnPath("codex") !== null;
594
+ const hasCodex = hasCodexCli || existsSync(join(home, ".codex"));
423
595
  if (hasCodex) {
424
596
  const cfg = join(home, ".codex", "config.toml");
425
597
  const toml = existsSync(cfg) ? readFileSync(cfg, "utf8") : "";
426
598
  const tomlR = reconcileCodexToml(toml, p.server);
427
599
  const hj = readJson(join(home, ".codex", "hooks.json"), { hooks: {} });
428
600
  const hkR = reconcileCodexHooks(hj, `node "${p.codexHook}"`);
601
+ let registered = false;
602
+ let detail;
603
+ if (hasCodexCli) {
604
+ const r = checkCliRegistration("codex", codexAddArgs(p.server), repair);
605
+ registered = r.registered;
606
+ detail = registrationDetail(r.registered, r.attemptedRepair);
607
+ }
608
+ else {
609
+ registered = tomlR.status === "ok";
610
+ detail = registered ? "registered" : "config stale — run: subagent-mcp setup";
611
+ }
429
612
  results.push({
430
613
  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`,
614
+ ok: registered,
615
+ detail,
433
616
  });
434
617
  const allOk = Object.values(hkR.statuses).every((s) => s === "ok");
435
618
  results.push({
436
619
  label: "codex: SessionStart + UserPromptSubmit hooks",
437
620
  ok: allOk,
438
- detail: allOk ? "wired (trust via /hooks in Codex)" : "incomplete run: subagent-mcp setup",
621
+ detail: allOk ? "wired (trust via /hooks in Codex)" : "incomplete - run: subagent-mcp setup",
439
622
  });
440
623
  }
441
- if (!hasClaude && !hasCodex) {
624
+ if (!hasClaude && !hasClaudeConfig && !hasCodex) {
442
625
  results.push({
443
626
  label: "vendors",
444
627
  ok: false,
@@ -483,7 +666,7 @@ export async function runSetup() {
483
666
  // Read-back verification: report what is ACTUALLY on disk now.
484
667
  if (!DRY_RUN) {
485
668
  console.log("\n--- Verification (read-back) ---");
486
- for (const r of verifyWiring()) {
669
+ for (const r of verifyWiring(INSTALL_ROOT, false)) {
487
670
  console.log(` ${r.ok ? "PASS" : "FAIL"} ${r.label} — ${r.detail}`);
488
671
  }
489
672
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heretyc/subagent-mcp",
3
- "version": "2.6.1",
3
+ "version": "2.7.0",
4
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
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,7 +21,7 @@
21
21
  "postinstall": "node scripts/postinstall.mjs",
22
22
  "prepare": "npm run build",
23
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"
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 test/setup-quoting.test.mjs && node test/setup-wire.test.mjs && node test/setup-cli-integration.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
25
  },
26
26
  "author": "Lexi Blackburn",
27
27
  "license": "Apache-2.0",