@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 +6 -6
- package/dist/setup.js +249 -66
- package/package.json +2 -2
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
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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 (
|
|
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 === "
|
|
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: "
|
|
117
|
-
args: [
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
|
304
|
-
// 1) MCP server (user scope).
|
|
305
|
-
//
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
describe(
|
|
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
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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
|
-
|
|
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(
|
|
410
|
-
const hk = reconcileClaudeSettings(
|
|
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
|
|
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"}
|
|
590
|
+
detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
|
|
420
591
|
});
|
|
421
592
|
}
|
|
422
|
-
const
|
|
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:
|
|
432
|
-
detail
|
|
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
|
|
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.
|
|
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",
|