@node9/proxy 1.35.1 → 1.36.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.
Files changed (3) hide show
  1. package/dist/cli.js +175 -214
  2. package/dist/cli.mjs +175 -214
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8154,7 +8154,7 @@ function binaryInPath(binary) {
8154
8154
  return false;
8155
8155
  }
8156
8156
  function detectAgents(homeDir2 = import_os12.default.homedir()) {
8157
- const exists = (p) => {
8157
+ const exists2 = (p) => {
8158
8158
  try {
8159
8159
  return import_fs13.default.existsSync(p);
8160
8160
  } catch (err2) {
@@ -8168,7 +8168,7 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
8168
8168
  };
8169
8169
  const desktopPath = claudeDesktopConfigPath(homeDir2);
8170
8170
  return {
8171
- claude: exists(import_path15.default.join(homeDir2, ".claude")) || exists(import_path15.default.join(homeDir2, ".claude.json")),
8171
+ claude: exists2(import_path15.default.join(homeDir2, ".claude")) || exists2(import_path15.default.join(homeDir2, ".claude.json")),
8172
8172
  // Antigravity (agy) shares the ~/.gemini root, so a bare
8173
8173
  // `exists(~/.gemini)` would report the (EOL'd) Gemini CLI as
8174
8174
  // installed on every agy machine — and `node9 init` would then
@@ -8178,35 +8178,35 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
8178
8178
  // creates ~/.gemini/settings.json on first run; agy does not touch
8179
8179
  // it (it uses antigravity-cli/settings.json) — that file is the
8180
8180
  // legacy-CLI discriminator.
8181
- gemini: exists(import_path15.default.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini"),
8181
+ gemini: exists2(import_path15.default.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini"),
8182
8182
  // agy creates ~/.gemini/antigravity-cli/ on first launch; the IDE
8183
8183
  // creates antigravity-ide/. PATH fallback covers installed-but-
8184
8184
  // never-launched (same class as opencode #186).
8185
- antigravity: exists(import_path15.default.join(homeDir2, ".gemini", "antigravity-cli")) || exists(import_path15.default.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy"),
8185
+ antigravity: exists2(import_path15.default.join(homeDir2, ".gemini", "antigravity-cli")) || exists2(import_path15.default.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy"),
8186
8186
  // GitHub Copilot CLI creates ~/.copilot on first launch; PATH
8187
8187
  // fallback covers installed-but-never-launched (same as opencode #186).
8188
- copilot: exists(import_path15.default.join(homeDir2, ".copilot")) || binaryInPath("copilot"),
8189
- cursor: exists(import_path15.default.join(homeDir2, ".cursor")),
8190
- codex: exists(import_path15.default.join(homeDir2, ".codex")),
8191
- windsurf: exists(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
8192
- vscode: exists(import_path15.default.join(homeDir2, ".vscode")),
8193
- claudeDesktop: desktopPath !== null && exists(import_path15.default.dirname(desktopPath)),
8188
+ copilot: exists2(import_path15.default.join(homeDir2, ".copilot")) || binaryInPath("copilot"),
8189
+ cursor: exists2(import_path15.default.join(homeDir2, ".cursor")),
8190
+ codex: exists2(import_path15.default.join(homeDir2, ".codex")),
8191
+ windsurf: exists2(import_path15.default.join(homeDir2, ".codeium", "windsurf")),
8192
+ vscode: exists2(import_path15.default.join(homeDir2, ".vscode")),
8193
+ claudeDesktop: desktopPath !== null && exists2(import_path15.default.dirname(desktopPath)),
8194
8194
  // Opencode creates ~/.config/opencode lazily on first launch — fall back
8195
8195
  // to a PATH lookup so installed-but-never-launched CLIs are still wired.
8196
- opencode: exists(import_path15.default.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode"),
8196
+ opencode: exists2(import_path15.default.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode"),
8197
8197
  // Pi (https://pi.dev): config dir is ~/.pi/agent (CONFIG_DIR_NAME=".pi"
8198
8198
  // + agentDir, verified against opensources/pi-main/packages/coding-agent/
8199
8199
  // src/config.ts:449,475). The Bun-compiled binary path may create this
8200
8200
  // dir lazily on first launch — same class of bug as opencode's #186
8201
8201
  // (design R6) — so fall back to PATH lookup for installed-but-never-
8202
8202
  // launched pi.
8203
- pi: exists(import_path15.default.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
8203
+ pi: exists2(import_path15.default.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
8204
8204
  // Hermes Agent (https://github.com/NousResearch/hermes-agent): home dir
8205
8205
  // is $HERMES_HOME (default ~/.hermes) per hermes_constants.py:30. config.yaml
8206
8206
  // appears after `hermes setup` has run; the directory alone exists from
8207
8207
  // install time. PATH fallback covers the rare case where the user blew
8208
8208
  // away ~/.hermes but kept the binary.
8209
- hermes: exists(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
8209
+ hermes: exists2(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
8210
8210
  };
8211
8211
  }
8212
8212
  async function setupCursor() {
@@ -19968,6 +19968,7 @@ var import_fs38 = __toESM(require("fs"));
19968
19968
  var import_path39 = __toESM(require("path"));
19969
19969
  var import_os34 = __toESM(require("os"));
19970
19970
  var yaml2 = __toESM(require("yaml"));
19971
+ var import_smol_toml2 = require("smol-toml");
19971
19972
  init_setup();
19972
19973
  function readJson2(filePath) {
19973
19974
  if (!import_fs38.default.existsSync(filePath)) return null;
@@ -19983,118 +19984,198 @@ function matchersHaveNode9Hook(matchers) {
19983
19984
  function flatHaveNode9Hook(entries) {
19984
19985
  return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
19985
19986
  }
19986
- function jsonWire(filePath, read) {
19987
- const parsed = readJson2(filePath);
19988
- if (parsed === null) return "absent";
19989
- if (parsed === "invalid") return "invalid";
19990
- return read(parsed) ? "wired" : "unwired";
19991
- }
19992
- function hermesWire(home) {
19993
- const configPath = hermesConfigPath(home);
19994
- if (!import_fs38.default.existsSync(configPath)) return "absent";
19987
+ function readHookRoot(filePath, format) {
19988
+ if (!import_fs38.default.existsSync(filePath)) return "absent";
19995
19989
  let raw;
19996
19990
  try {
19997
- raw = import_fs38.default.readFileSync(configPath, "utf-8");
19991
+ raw = import_fs38.default.readFileSync(filePath, "utf-8");
19998
19992
  } catch {
19999
19993
  return "absent";
20000
19994
  }
20001
19995
  try {
20002
- const cfg = yaml2.parse(raw);
20003
- const pre = (cfg?.hooks?.pre_tool_call ?? []).some(
20004
- (e) => typeof e?.command === "string" && isNode9Hook(e.command)
20005
- );
20006
- return pre ? "wired" : "unwired";
19996
+ const parsed = format === "yaml" ? yaml2.parse(raw) : JSON.parse(raw);
19997
+ return parsed?.hooks ?? {};
20007
19998
  } catch {
20008
19999
  return "invalid";
20009
20000
  }
20010
20001
  }
20002
+ function eventWired(root, ev, format) {
20003
+ const arr = root[ev.key];
20004
+ if (format === "matcher") return matchersHaveNode9Hook(arr);
20005
+ return flatHaveNode9Hook(arr);
20006
+ }
20007
+ function detectMcp(servers) {
20008
+ const entries = Object.entries(servers ?? {});
20009
+ const present = entries.some(([, s]) => s?.command === "node9");
20010
+ const wrapped = entries.filter(([, s]) => s?.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
20011
+ return { wrapped, present };
20012
+ }
20013
+ function readMcp(filePath, format) {
20014
+ if (!import_fs38.default.existsSync(filePath)) return { wrapped: [], present: false };
20015
+ try {
20016
+ if (format === "toml") {
20017
+ const parsed2 = (0, import_smol_toml2.parse)(import_fs38.default.readFileSync(filePath, "utf-8"));
20018
+ return detectMcp(parsed2?.mcp_servers);
20019
+ }
20020
+ const parsed = readJson2(filePath);
20021
+ if (parsed === null || parsed === "invalid") return { wrapped: [], present: false };
20022
+ return detectMcp(parsed.mcpServers);
20023
+ } catch {
20024
+ return { wrapped: [], present: false };
20025
+ }
20026
+ }
20027
+ var exists = (p) => {
20028
+ try {
20029
+ return import_fs38.default.existsSync(p);
20030
+ } catch {
20031
+ return false;
20032
+ }
20033
+ };
20034
+ var ck = (key) => ({ key, kind: "check" });
20035
+ var lg = (key) => ({ key, kind: "log" });
20036
+ var DEFAULT_LABEL_PAD = 11;
20037
+ var hookLabelOf = (ev, pad) => `${ev.key.padEnd(pad)} (node9 ${ev.kind})`;
20011
20038
  var AGENT_SPECS = [
20012
20039
  {
20013
20040
  id: "claude",
20014
20041
  label: "Claude Code",
20015
- hookLabel: "PreToolUse hook",
20016
20042
  setupCommand: "node9 setup claude",
20017
- settingsPath: (h) => import_path39.default.join(h, ".claude", "settings.json"),
20018
- wireState: (h) => jsonWire(
20019
- import_path39.default.join(h, ".claude", "settings.json"),
20020
- (p) => matchersHaveNode9Hook(p.hooks?.PreToolUse)
20021
- )
20043
+ hookFile: (h) => import_path39.default.join(h, ".claude", "settings.json"),
20044
+ hookFormat: "matcher",
20045
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20046
+ mcpFile: (h) => import_path39.default.join(h, ".claude.json"),
20047
+ present: (h) => exists(import_path39.default.join(h, ".claude", "settings.json")) || exists(import_path39.default.join(h, ".claude.json"))
20022
20048
  },
20023
20049
  {
20024
20050
  id: "gemini",
20025
20051
  label: "Gemini CLI",
20026
- hookLabel: "BeforeTool hook",
20027
20052
  setupCommand: "node9 setup gemini",
20028
- settingsPath: (h) => import_path39.default.join(h, ".gemini", "settings.json"),
20029
- wireState: (h) => jsonWire(
20030
- import_path39.default.join(h, ".gemini", "settings.json"),
20031
- (p) => matchersHaveNode9Hook(p.hooks?.BeforeTool)
20032
- )
20053
+ hookFile: (h) => import_path39.default.join(h, ".gemini", "settings.json"),
20054
+ hookFormat: "matcher",
20055
+ hookEvents: [ck("BeforeTool"), lg("AfterTool")],
20056
+ mcpFile: (h) => import_path39.default.join(h, ".gemini", "settings.json"),
20057
+ present: (h) => exists(import_path39.default.join(h, ".gemini", "settings.json"))
20033
20058
  },
20034
20059
  {
20035
20060
  id: "codex",
20036
20061
  label: "Codex",
20037
- hookLabel: "PreToolUse hook",
20038
20062
  setupCommand: "node9 setup codex",
20039
- settingsPath: (h) => import_path39.default.join(h, ".codex", "hooks.json"),
20040
- wireState: (h) => jsonWire(
20041
- import_path39.default.join(h, ".codex", "hooks.json"),
20042
- (p) => matchersHaveNode9Hook(p.hooks?.PreToolUse)
20043
- )
20063
+ hookFile: (h) => import_path39.default.join(h, ".codex", "hooks.json"),
20064
+ hookFormat: "matcher",
20065
+ hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
20066
+ mcpFile: (h) => import_path39.default.join(h, ".codex", "config.toml"),
20067
+ mcpFormat: "toml",
20068
+ present: (h) => exists(import_path39.default.join(h, ".codex"))
20044
20069
  },
20045
20070
  {
20046
20071
  id: "antigravity",
20047
20072
  label: "Antigravity",
20048
- hookLabel: "PreToolUse hook",
20049
20073
  setupCommand: "node9 setup antigravity",
20050
- settingsPath: (h) => import_path39.default.join(h, ".gemini", "config", "hooks.json"),
20051
- wireState: (h) => jsonWire(
20052
- import_path39.default.join(h, ".gemini", "config", "hooks.json"),
20053
- (p) => matchersHaveNode9Hook(p.hooks?.PreToolUse)
20054
- )
20074
+ hookFile: (h) => import_path39.default.join(h, ".gemini", "config", "hooks.json"),
20075
+ hookFormat: "matcher",
20076
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20077
+ mcpFile: (h) => import_path39.default.join(h, ".gemini", "config", "mcp_config.json"),
20078
+ present: (h) => exists(import_path39.default.join(h, ".gemini", "config", "hooks.json")) || exists(import_path39.default.join(h, ".gemini", "antigravity-cli")) || exists(import_path39.default.join(h, ".gemini", "antigravity-ide"))
20055
20079
  },
20056
20080
  {
20057
20081
  id: "copilot",
20058
20082
  label: "GitHub Copilot",
20059
- hookLabel: "PreToolUse hook",
20060
20083
  setupCommand: "node9 setup copilot",
20061
- settingsPath: (h) => import_path39.default.join(h, ".copilot", "hooks", "node9.json"),
20062
- wireState: (h) => jsonWire(
20063
- import_path39.default.join(h, ".copilot", "hooks", "node9.json"),
20064
- (p) => flatHaveNode9Hook(p.hooks?.PreToolUse)
20065
- )
20084
+ hookFile: (h) => import_path39.default.join(h, ".copilot", "hooks", "node9.json"),
20085
+ hookFormat: "flat",
20086
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
20087
+ mcpFile: (h) => import_path39.default.join(h, ".copilot", "mcp-config.json"),
20088
+ present: (h) => exists(import_path39.default.join(h, ".copilot"))
20066
20089
  },
20067
20090
  {
20068
20091
  id: "cursor",
20069
20092
  label: "Cursor",
20070
- hookLabel: "preToolUse hook",
20071
20093
  setupCommand: "node9 setup cursor",
20072
- settingsPath: (h) => import_path39.default.join(h, ".cursor", "hooks.json"),
20073
- wireState: (h) => jsonWire(
20074
- import_path39.default.join(h, ".cursor", "hooks.json"),
20075
- (p) => flatHaveNode9Hook(p.hooks?.preToolUse)
20076
- )
20094
+ // MCP-only no hook file (see note above).
20095
+ hookFormat: "flat",
20096
+ hookEvents: [],
20097
+ mcpFile: (h) => import_path39.default.join(h, ".cursor", "mcp.json"),
20098
+ present: (h) => exists(import_path39.default.join(h, ".cursor", "mcp.json"))
20077
20099
  },
20078
20100
  {
20079
20101
  id: "hermes",
20080
20102
  label: "Hermes Agent",
20081
- hookLabel: "pre_tool_call hook",
20082
20103
  setupCommand: "node9 setup hermes",
20083
- settingsPath: (h) => hermesConfigPath(h),
20084
- wireState: (h) => hermesWire(h)
20104
+ hookFile: (h) => hermesConfigPath(h),
20105
+ hookFormat: "yaml",
20106
+ hookEvents: [ck("pre_tool_call"), lg("post_tool_call")],
20107
+ labelPad: 14,
20108
+ // 'post_tool_call' is wider than the default
20109
+ present: (h) => exists(hermesConfigPath(h))
20110
+ },
20111
+ {
20112
+ // Plugin-shim agents — protected by a node9-authored plugin/extension file
20113
+ // (no hooks, no MCP). hookFormat is unused for these (shimFile drives it).
20114
+ id: "opencode",
20115
+ label: "OpenCode",
20116
+ setupCommand: "node9 setup opencode",
20117
+ hookFormat: "flat",
20118
+ hookEvents: [],
20119
+ shimFile: (h) => import_path39.default.join(h, ".config", "opencode", "plugins", "node9.js"),
20120
+ present: (h) => exists(import_path39.default.join(h, ".config", "opencode")) || exists(import_path39.default.join(h, ".config", "opencode", "plugins", "node9.js"))
20121
+ },
20122
+ {
20123
+ id: "pi",
20124
+ label: "Pi",
20125
+ setupCommand: "node9 setup pi",
20126
+ hookFormat: "flat",
20127
+ hookEvents: [],
20128
+ shimFile: (h) => import_path39.default.join(h, ".pi", "agent", "extensions", "node9.js"),
20129
+ present: (h) => exists(import_path39.default.join(h, ".pi", "agent")) || exists(import_path39.default.join(h, ".pi", "agent", "extensions", "node9.js"))
20085
20130
  }
20086
20131
  ];
20087
20132
  function getAgentWiring(home = import_os34.default.homedir()) {
20088
20133
  const detected = detectAgents(home);
20089
- return AGENT_SPECS.map((spec) => ({
20090
- id: spec.id,
20091
- label: spec.label,
20092
- hookLabel: spec.hookLabel,
20093
- setupCommand: spec.setupCommand,
20094
- settingsPath: spec.settingsPath(home),
20095
- installed: detected[spec.id],
20096
- wireState: spec.wireState(home)
20097
- }));
20134
+ return AGENT_SPECS.map((spec) => {
20135
+ const present = spec.present(home);
20136
+ const pad = spec.labelPad ?? DEFAULT_LABEL_PAD;
20137
+ let hooks;
20138
+ let wireState;
20139
+ let hookLabel;
20140
+ let settingsPath;
20141
+ if (spec.shimFile) {
20142
+ const shimWired = exists(spec.shimFile(home));
20143
+ hooks = [{ label: "node9 plugin (node9 check)", wired: shimWired }];
20144
+ wireState = shimWired ? "wired" : present ? "unwired" : "absent";
20145
+ hookLabel = "node9 plugin";
20146
+ settingsPath = spec.shimFile(home);
20147
+ } else {
20148
+ const root = spec.hookFile ? readHookRoot(spec.hookFile(home), spec.hookFormat) : "absent";
20149
+ const primary = spec.hookEvents[0];
20150
+ const rootPresent = root !== "absent" && root !== "invalid";
20151
+ hooks = spec.hookEvents.map((ev) => ({
20152
+ label: hookLabelOf(ev, pad),
20153
+ wired: rootPresent && eventWired(root, ev, spec.hookFormat)
20154
+ }));
20155
+ if (root === "absent") wireState = "absent";
20156
+ else if (root === "invalid") wireState = "invalid";
20157
+ else wireState = primary && eventWired(root, primary, spec.hookFormat) ? "wired" : "unwired";
20158
+ hookLabel = primary ? `${primary.key} hook` : "MCP proxy";
20159
+ settingsPath = spec.hookFile ? spec.hookFile(home) : spec.mcpFile ? spec.mcpFile(home) : "";
20160
+ }
20161
+ const mcp = spec.mcpFile ? readMcp(spec.mcpFile(home), spec.mcpFormat ?? "json") : null;
20162
+ const anyHookWired = hooks.some((h) => h.wired);
20163
+ return {
20164
+ id: spec.id,
20165
+ label: spec.label,
20166
+ setupCommand: spec.setupCommand,
20167
+ installed: detected[spec.id],
20168
+ present,
20169
+ hooks,
20170
+ wireState,
20171
+ hookLabel,
20172
+ settingsPath,
20173
+ configFormat: spec.hookFormat === "yaml" ? "YAML" : "JSON",
20174
+ mcpServers: mcp ? mcp.wrapped : null,
20175
+ mcpProtected: mcp ? mcp.present : false,
20176
+ isProtected: anyHookWired || (mcp?.present ?? false)
20177
+ };
20178
+ });
20098
20179
  }
20099
20180
 
20100
20181
  // src/cli/commands/doctor.ts
@@ -20184,12 +20265,13 @@ function registerDoctorCommand(program2, version2) {
20184
20265
  section("Agent Hooks");
20185
20266
  const notConfigured = [];
20186
20267
  for (const a of getAgentWiring(homeDir2)) {
20187
- if (a.wireState === "wired") {
20188
- pass(`${a.label} \u2014 ${a.hookLabel} active`);
20189
- } else if (a.wireState === "unwired") {
20190
- fail(`${a.label} \u2014 settings found but node9 hook missing`, `Run: ${a.setupCommand}`);
20268
+ const anyHookWired = a.hooks.some((h) => h.wired);
20269
+ if (a.isProtected) {
20270
+ pass(`${a.label} \u2014 ${anyHookWired ? `${a.hookLabel} active` : "MCP proxy active"}`);
20191
20271
  } else if (a.wireState === "invalid") {
20192
20272
  fail(`${a.label} \u2014 settings file is invalid JSON`, a.settingsPath);
20273
+ } else if (a.wireState === "unwired") {
20274
+ fail(`${a.label} \u2014 settings found but node9 hook missing`, `Run: ${a.setupCommand}`);
20193
20275
  } else {
20194
20276
  notConfigured.push(a.label);
20195
20277
  }
@@ -21493,50 +21575,8 @@ var import_chalk15 = __toESM(require("chalk"));
21493
21575
  var import_fs42 = __toESM(require("fs"));
21494
21576
  var import_path43 = __toESM(require("path"));
21495
21577
  var import_os38 = __toESM(require("os"));
21496
- var yaml3 = __toESM(require("yaml"));
21497
21578
  init_core();
21498
21579
  init_daemon();
21499
- init_setup();
21500
- function readHermesHooks(configPath) {
21501
- if (!import_fs42.default.existsSync(configPath)) return null;
21502
- let raw;
21503
- try {
21504
- raw = import_fs42.default.readFileSync(configPath, "utf-8");
21505
- } catch {
21506
- return null;
21507
- }
21508
- try {
21509
- const cfg = yaml3.parse(raw);
21510
- const has = (event) => (cfg?.hooks?.[event] ?? []).some(
21511
- (e) => typeof e?.command === "string" && isNode9Hook(e.command)
21512
- );
21513
- return { pre: has("pre_tool_call"), post: has("post_tool_call") };
21514
- } catch {
21515
- console.error(
21516
- import_chalk15.default.yellow(
21517
- ` \u26A0\uFE0F Hermes config.yaml at ${configPath} is not valid YAML \u2014 showing as unwired.`
21518
- )
21519
- );
21520
- return { pre: false, post: false };
21521
- }
21522
- }
21523
- function readJson3(filePath) {
21524
- try {
21525
- if (import_fs42.default.existsSync(filePath)) return JSON.parse(import_fs42.default.readFileSync(filePath, "utf-8"));
21526
- } catch {
21527
- }
21528
- return null;
21529
- }
21530
- function matchersHaveNode9Hook2(matchers) {
21531
- return (matchers ?? []).some((m) => (m.hooks ?? []).some((h) => isNode9Hook(h.command)));
21532
- }
21533
- function flatHaveNode9Hook2(entries) {
21534
- return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
21535
- }
21536
- function wrappedMcpServers(servers) {
21537
- if (!servers) return [];
21538
- return Object.entries(servers).filter(([, s]) => s.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
21539
- }
21540
21580
  function printAgentSection(label, hookPairs, wrapped) {
21541
21581
  console.log(import_chalk15.default.bold(` ${label}`));
21542
21582
  for (const { name, present } of hookPairs) {
@@ -21603,102 +21643,23 @@ function registerStatusCommand(program2) {
21603
21643
  ` Sandbox: ${import_chalk15.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
21604
21644
  );
21605
21645
  }
21606
- const homeDir2 = import_os38.default.homedir();
21607
- const claudeSettings = readJson3(
21608
- import_path43.default.join(homeDir2, ".claude", "settings.json")
21609
- );
21610
- const claudeConfig = readJson3(import_path43.default.join(homeDir2, ".claude.json"));
21611
- const geminiSettings = readJson3(
21612
- import_path43.default.join(homeDir2, ".gemini", "settings.json")
21613
- );
21614
- const cursorConfig = readJson3(import_path43.default.join(homeDir2, ".cursor", "mcp.json"));
21615
- const antigravityHooks = readJson3(
21616
- import_path43.default.join(homeDir2, ".gemini", "config", "hooks.json")
21617
- );
21618
- const antigravityMcp = readJson3(
21619
- import_path43.default.join(homeDir2, ".gemini", "config", "mcp_config.json")
21620
- );
21621
- const antigravityPresent = antigravityHooks !== null || import_fs42.default.existsSync(import_path43.default.join(homeDir2, ".gemini", "antigravity-cli")) || import_fs42.default.existsSync(import_path43.default.join(homeDir2, ".gemini", "antigravity-ide"));
21622
- const copilotHooks = readJson3(
21623
- import_path43.default.join(homeDir2, ".copilot", "hooks", "node9.json")
21624
- );
21625
- const copilotMcp = readJson3(
21626
- import_path43.default.join(homeDir2, ".copilot", "mcp-config.json")
21627
- );
21628
- const copilotPresent = import_fs42.default.existsSync(import_path43.default.join(homeDir2, ".copilot"));
21629
- const hermesHooks = readHermesHooks(hermesConfigPath(homeDir2));
21630
- const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig || antigravityPresent || copilotPresent || hermesHooks;
21631
- if (agentFound) {
21646
+ const wiring = getAgentWiring(import_os38.default.homedir()).filter((a) => a.present);
21647
+ if (wiring.length > 0) {
21632
21648
  console.log("");
21633
21649
  console.log(import_chalk15.default.bold(" Agent Wiring:"));
21634
21650
  console.log("");
21635
- if (claudeSettings || claudeConfig) {
21636
- const preHook = matchersHaveNode9Hook2(claudeSettings?.hooks?.PreToolUse);
21637
- const postHook = matchersHaveNode9Hook2(claudeSettings?.hooks?.PostToolUse);
21638
- printAgentSection(
21639
- "Claude Code",
21640
- [
21641
- { name: "PreToolUse (node9 check)", present: preHook },
21642
- { name: "PostToolUse (node9 log)", present: postHook }
21643
- ],
21644
- wrappedMcpServers(claudeConfig?.mcpServers)
21645
- );
21646
- console.log("");
21647
- }
21648
- if (geminiSettings) {
21649
- const beforeHook = matchersHaveNode9Hook2(geminiSettings.hooks?.BeforeTool);
21650
- const afterHook = matchersHaveNode9Hook2(geminiSettings.hooks?.AfterTool);
21651
- printAgentSection(
21652
- "Gemini CLI",
21653
- [
21654
- { name: "BeforeTool (node9 check)", present: beforeHook },
21655
- { name: "AfterTool (node9 log)", present: afterHook }
21656
- ],
21657
- wrappedMcpServers(geminiSettings.mcpServers)
21658
- );
21659
- console.log("");
21660
- }
21661
- if (antigravityPresent) {
21662
- const preHook = matchersHaveNode9Hook2(antigravityHooks?.hooks?.PreToolUse);
21663
- const postHook = matchersHaveNode9Hook2(antigravityHooks?.hooks?.PostToolUse);
21664
- printAgentSection(
21665
- "Antigravity",
21666
- [
21667
- { name: "PreToolUse (node9 check)", present: preHook },
21668
- { name: "PostToolUse (node9 log)", present: postHook }
21669
- ],
21670
- wrappedMcpServers(antigravityMcp?.mcpServers)
21671
- );
21672
- console.log("");
21673
- }
21674
- if (copilotPresent) {
21675
- const preHook = flatHaveNode9Hook2(copilotHooks?.hooks?.PreToolUse);
21676
- const postHook = flatHaveNode9Hook2(copilotHooks?.hooks?.PostToolUse);
21677
- const promptHook = flatHaveNode9Hook2(copilotHooks?.hooks?.UserPromptSubmit);
21678
- printAgentSection(
21679
- "GitHub Copilot",
21680
- [
21681
- { name: "PreToolUse (node9 check)", present: preHook },
21682
- { name: "PostToolUse (node9 log)", present: postHook },
21683
- { name: "UserPromptSubmit (node9 check)", present: promptHook }
21684
- ],
21685
- wrappedMcpServers(copilotMcp?.mcpServers)
21686
- );
21687
- console.log("");
21688
- }
21689
- if (cursorConfig) {
21690
- printAgentSection("Cursor", [], wrappedMcpServers(cursorConfig.mcpServers));
21691
- console.log("");
21692
- }
21693
- if (hermesHooks) {
21651
+ for (const a of wiring) {
21652
+ if (a.wireState === "invalid") {
21653
+ console.error(
21654
+ import_chalk15.default.yellow(
21655
+ ` \u26A0\uFE0F ${a.label} config at ${a.settingsPath} is not valid ${a.configFormat} \u2014 showing as unwired.`
21656
+ )
21657
+ );
21658
+ }
21694
21659
  printAgentSection(
21695
- "Hermes Agent",
21696
- [
21697
- { name: "pre_tool_call (node9 check)", present: hermesHooks.pre },
21698
- { name: "post_tool_call (node9 log)", present: hermesHooks.post }
21699
- ],
21700
- null
21701
- // Hermes has no MCP surface
21660
+ a.label,
21661
+ a.hooks.map((h) => ({ name: h.label, present: h.wired })),
21662
+ a.mcpServers
21702
21663
  );
21703
21664
  console.log("");
21704
21665
  }
package/dist/cli.mjs CHANGED
@@ -8136,7 +8136,7 @@ function binaryInPath(binary) {
8136
8136
  return false;
8137
8137
  }
8138
8138
  function detectAgents(homeDir2 = os12.homedir()) {
8139
- const exists = (p) => {
8139
+ const exists2 = (p) => {
8140
8140
  try {
8141
8141
  return fs13.existsSync(p);
8142
8142
  } catch (err2) {
@@ -8150,7 +8150,7 @@ function detectAgents(homeDir2 = os12.homedir()) {
8150
8150
  };
8151
8151
  const desktopPath = claudeDesktopConfigPath(homeDir2);
8152
8152
  return {
8153
- claude: exists(path15.join(homeDir2, ".claude")) || exists(path15.join(homeDir2, ".claude.json")),
8153
+ claude: exists2(path15.join(homeDir2, ".claude")) || exists2(path15.join(homeDir2, ".claude.json")),
8154
8154
  // Antigravity (agy) shares the ~/.gemini root, so a bare
8155
8155
  // `exists(~/.gemini)` would report the (EOL'd) Gemini CLI as
8156
8156
  // installed on every agy machine — and `node9 init` would then
@@ -8160,35 +8160,35 @@ function detectAgents(homeDir2 = os12.homedir()) {
8160
8160
  // creates ~/.gemini/settings.json on first run; agy does not touch
8161
8161
  // it (it uses antigravity-cli/settings.json) — that file is the
8162
8162
  // legacy-CLI discriminator.
8163
- gemini: exists(path15.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini"),
8163
+ gemini: exists2(path15.join(homeDir2, ".gemini", "settings.json")) || binaryInPath("gemini"),
8164
8164
  // agy creates ~/.gemini/antigravity-cli/ on first launch; the IDE
8165
8165
  // creates antigravity-ide/. PATH fallback covers installed-but-
8166
8166
  // never-launched (same class as opencode #186).
8167
- antigravity: exists(path15.join(homeDir2, ".gemini", "antigravity-cli")) || exists(path15.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy"),
8167
+ antigravity: exists2(path15.join(homeDir2, ".gemini", "antigravity-cli")) || exists2(path15.join(homeDir2, ".gemini", "antigravity-ide")) || binaryInPath("agy"),
8168
8168
  // GitHub Copilot CLI creates ~/.copilot on first launch; PATH
8169
8169
  // fallback covers installed-but-never-launched (same as opencode #186).
8170
- copilot: exists(path15.join(homeDir2, ".copilot")) || binaryInPath("copilot"),
8171
- cursor: exists(path15.join(homeDir2, ".cursor")),
8172
- codex: exists(path15.join(homeDir2, ".codex")),
8173
- windsurf: exists(path15.join(homeDir2, ".codeium", "windsurf")),
8174
- vscode: exists(path15.join(homeDir2, ".vscode")),
8175
- claudeDesktop: desktopPath !== null && exists(path15.dirname(desktopPath)),
8170
+ copilot: exists2(path15.join(homeDir2, ".copilot")) || binaryInPath("copilot"),
8171
+ cursor: exists2(path15.join(homeDir2, ".cursor")),
8172
+ codex: exists2(path15.join(homeDir2, ".codex")),
8173
+ windsurf: exists2(path15.join(homeDir2, ".codeium", "windsurf")),
8174
+ vscode: exists2(path15.join(homeDir2, ".vscode")),
8175
+ claudeDesktop: desktopPath !== null && exists2(path15.dirname(desktopPath)),
8176
8176
  // Opencode creates ~/.config/opencode lazily on first launch — fall back
8177
8177
  // to a PATH lookup so installed-but-never-launched CLIs are still wired.
8178
- opencode: exists(path15.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode"),
8178
+ opencode: exists2(path15.join(homeDir2, ".config", "opencode")) || binaryInPath("opencode"),
8179
8179
  // Pi (https://pi.dev): config dir is ~/.pi/agent (CONFIG_DIR_NAME=".pi"
8180
8180
  // + agentDir, verified against opensources/pi-main/packages/coding-agent/
8181
8181
  // src/config.ts:449,475). The Bun-compiled binary path may create this
8182
8182
  // dir lazily on first launch — same class of bug as opencode's #186
8183
8183
  // (design R6) — so fall back to PATH lookup for installed-but-never-
8184
8184
  // launched pi.
8185
- pi: exists(path15.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
8185
+ pi: exists2(path15.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
8186
8186
  // Hermes Agent (https://github.com/NousResearch/hermes-agent): home dir
8187
8187
  // is $HERMES_HOME (default ~/.hermes) per hermes_constants.py:30. config.yaml
8188
8188
  // appears after `hermes setup` has run; the directory alone exists from
8189
8189
  // install time. PATH fallback covers the rare case where the user blew
8190
8190
  // away ~/.hermes but kept the binary.
8191
- hermes: exists(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
8191
+ hermes: exists2(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
8192
8192
  };
8193
8193
  }
8194
8194
  async function setupCursor() {
@@ -19941,6 +19941,7 @@ import fs38 from "fs";
19941
19941
  import path39 from "path";
19942
19942
  import os34 from "os";
19943
19943
  import * as yaml2 from "yaml";
19944
+ import { parse as parseToml2 } from "smol-toml";
19944
19945
  function readJson2(filePath) {
19945
19946
  if (!fs38.existsSync(filePath)) return null;
19946
19947
  try {
@@ -19955,118 +19956,198 @@ function matchersHaveNode9Hook(matchers) {
19955
19956
  function flatHaveNode9Hook(entries) {
19956
19957
  return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
19957
19958
  }
19958
- function jsonWire(filePath, read) {
19959
- const parsed = readJson2(filePath);
19960
- if (parsed === null) return "absent";
19961
- if (parsed === "invalid") return "invalid";
19962
- return read(parsed) ? "wired" : "unwired";
19963
- }
19964
- function hermesWire(home) {
19965
- const configPath = hermesConfigPath(home);
19966
- if (!fs38.existsSync(configPath)) return "absent";
19959
+ function readHookRoot(filePath, format) {
19960
+ if (!fs38.existsSync(filePath)) return "absent";
19967
19961
  let raw;
19968
19962
  try {
19969
- raw = fs38.readFileSync(configPath, "utf-8");
19963
+ raw = fs38.readFileSync(filePath, "utf-8");
19970
19964
  } catch {
19971
19965
  return "absent";
19972
19966
  }
19973
19967
  try {
19974
- const cfg = yaml2.parse(raw);
19975
- const pre = (cfg?.hooks?.pre_tool_call ?? []).some(
19976
- (e) => typeof e?.command === "string" && isNode9Hook(e.command)
19977
- );
19978
- return pre ? "wired" : "unwired";
19968
+ const parsed = format === "yaml" ? yaml2.parse(raw) : JSON.parse(raw);
19969
+ return parsed?.hooks ?? {};
19979
19970
  } catch {
19980
19971
  return "invalid";
19981
19972
  }
19982
19973
  }
19974
+ function eventWired(root, ev, format) {
19975
+ const arr = root[ev.key];
19976
+ if (format === "matcher") return matchersHaveNode9Hook(arr);
19977
+ return flatHaveNode9Hook(arr);
19978
+ }
19979
+ function detectMcp(servers) {
19980
+ const entries = Object.entries(servers ?? {});
19981
+ const present = entries.some(([, s]) => s?.command === "node9");
19982
+ const wrapped = entries.filter(([, s]) => s?.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
19983
+ return { wrapped, present };
19984
+ }
19985
+ function readMcp(filePath, format) {
19986
+ if (!fs38.existsSync(filePath)) return { wrapped: [], present: false };
19987
+ try {
19988
+ if (format === "toml") {
19989
+ const parsed2 = parseToml2(fs38.readFileSync(filePath, "utf-8"));
19990
+ return detectMcp(parsed2?.mcp_servers);
19991
+ }
19992
+ const parsed = readJson2(filePath);
19993
+ if (parsed === null || parsed === "invalid") return { wrapped: [], present: false };
19994
+ return detectMcp(parsed.mcpServers);
19995
+ } catch {
19996
+ return { wrapped: [], present: false };
19997
+ }
19998
+ }
19999
+ var exists = (p) => {
20000
+ try {
20001
+ return fs38.existsSync(p);
20002
+ } catch {
20003
+ return false;
20004
+ }
20005
+ };
20006
+ var ck = (key) => ({ key, kind: "check" });
20007
+ var lg = (key) => ({ key, kind: "log" });
20008
+ var DEFAULT_LABEL_PAD = 11;
20009
+ var hookLabelOf = (ev, pad) => `${ev.key.padEnd(pad)} (node9 ${ev.kind})`;
19983
20010
  var AGENT_SPECS = [
19984
20011
  {
19985
20012
  id: "claude",
19986
20013
  label: "Claude Code",
19987
- hookLabel: "PreToolUse hook",
19988
20014
  setupCommand: "node9 setup claude",
19989
- settingsPath: (h) => path39.join(h, ".claude", "settings.json"),
19990
- wireState: (h) => jsonWire(
19991
- path39.join(h, ".claude", "settings.json"),
19992
- (p) => matchersHaveNode9Hook(p.hooks?.PreToolUse)
19993
- )
20015
+ hookFile: (h) => path39.join(h, ".claude", "settings.json"),
20016
+ hookFormat: "matcher",
20017
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20018
+ mcpFile: (h) => path39.join(h, ".claude.json"),
20019
+ present: (h) => exists(path39.join(h, ".claude", "settings.json")) || exists(path39.join(h, ".claude.json"))
19994
20020
  },
19995
20021
  {
19996
20022
  id: "gemini",
19997
20023
  label: "Gemini CLI",
19998
- hookLabel: "BeforeTool hook",
19999
20024
  setupCommand: "node9 setup gemini",
20000
- settingsPath: (h) => path39.join(h, ".gemini", "settings.json"),
20001
- wireState: (h) => jsonWire(
20002
- path39.join(h, ".gemini", "settings.json"),
20003
- (p) => matchersHaveNode9Hook(p.hooks?.BeforeTool)
20004
- )
20025
+ hookFile: (h) => path39.join(h, ".gemini", "settings.json"),
20026
+ hookFormat: "matcher",
20027
+ hookEvents: [ck("BeforeTool"), lg("AfterTool")],
20028
+ mcpFile: (h) => path39.join(h, ".gemini", "settings.json"),
20029
+ present: (h) => exists(path39.join(h, ".gemini", "settings.json"))
20005
20030
  },
20006
20031
  {
20007
20032
  id: "codex",
20008
20033
  label: "Codex",
20009
- hookLabel: "PreToolUse hook",
20010
20034
  setupCommand: "node9 setup codex",
20011
- settingsPath: (h) => path39.join(h, ".codex", "hooks.json"),
20012
- wireState: (h) => jsonWire(
20013
- path39.join(h, ".codex", "hooks.json"),
20014
- (p) => matchersHaveNode9Hook(p.hooks?.PreToolUse)
20015
- )
20035
+ hookFile: (h) => path39.join(h, ".codex", "hooks.json"),
20036
+ hookFormat: "matcher",
20037
+ hookEvents: [ck("PreToolUse"), ck("UserPromptSubmit")],
20038
+ mcpFile: (h) => path39.join(h, ".codex", "config.toml"),
20039
+ mcpFormat: "toml",
20040
+ present: (h) => exists(path39.join(h, ".codex"))
20016
20041
  },
20017
20042
  {
20018
20043
  id: "antigravity",
20019
20044
  label: "Antigravity",
20020
- hookLabel: "PreToolUse hook",
20021
20045
  setupCommand: "node9 setup antigravity",
20022
- settingsPath: (h) => path39.join(h, ".gemini", "config", "hooks.json"),
20023
- wireState: (h) => jsonWire(
20024
- path39.join(h, ".gemini", "config", "hooks.json"),
20025
- (p) => matchersHaveNode9Hook(p.hooks?.PreToolUse)
20026
- )
20046
+ hookFile: (h) => path39.join(h, ".gemini", "config", "hooks.json"),
20047
+ hookFormat: "matcher",
20048
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse")],
20049
+ mcpFile: (h) => path39.join(h, ".gemini", "config", "mcp_config.json"),
20050
+ present: (h) => exists(path39.join(h, ".gemini", "config", "hooks.json")) || exists(path39.join(h, ".gemini", "antigravity-cli")) || exists(path39.join(h, ".gemini", "antigravity-ide"))
20027
20051
  },
20028
20052
  {
20029
20053
  id: "copilot",
20030
20054
  label: "GitHub Copilot",
20031
- hookLabel: "PreToolUse hook",
20032
20055
  setupCommand: "node9 setup copilot",
20033
- settingsPath: (h) => path39.join(h, ".copilot", "hooks", "node9.json"),
20034
- wireState: (h) => jsonWire(
20035
- path39.join(h, ".copilot", "hooks", "node9.json"),
20036
- (p) => flatHaveNode9Hook(p.hooks?.PreToolUse)
20037
- )
20056
+ hookFile: (h) => path39.join(h, ".copilot", "hooks", "node9.json"),
20057
+ hookFormat: "flat",
20058
+ hookEvents: [ck("PreToolUse"), lg("PostToolUse"), ck("UserPromptSubmit")],
20059
+ mcpFile: (h) => path39.join(h, ".copilot", "mcp-config.json"),
20060
+ present: (h) => exists(path39.join(h, ".copilot"))
20038
20061
  },
20039
20062
  {
20040
20063
  id: "cursor",
20041
20064
  label: "Cursor",
20042
- hookLabel: "preToolUse hook",
20043
20065
  setupCommand: "node9 setup cursor",
20044
- settingsPath: (h) => path39.join(h, ".cursor", "hooks.json"),
20045
- wireState: (h) => jsonWire(
20046
- path39.join(h, ".cursor", "hooks.json"),
20047
- (p) => flatHaveNode9Hook(p.hooks?.preToolUse)
20048
- )
20066
+ // MCP-only no hook file (see note above).
20067
+ hookFormat: "flat",
20068
+ hookEvents: [],
20069
+ mcpFile: (h) => path39.join(h, ".cursor", "mcp.json"),
20070
+ present: (h) => exists(path39.join(h, ".cursor", "mcp.json"))
20049
20071
  },
20050
20072
  {
20051
20073
  id: "hermes",
20052
20074
  label: "Hermes Agent",
20053
- hookLabel: "pre_tool_call hook",
20054
20075
  setupCommand: "node9 setup hermes",
20055
- settingsPath: (h) => hermesConfigPath(h),
20056
- wireState: (h) => hermesWire(h)
20076
+ hookFile: (h) => hermesConfigPath(h),
20077
+ hookFormat: "yaml",
20078
+ hookEvents: [ck("pre_tool_call"), lg("post_tool_call")],
20079
+ labelPad: 14,
20080
+ // 'post_tool_call' is wider than the default
20081
+ present: (h) => exists(hermesConfigPath(h))
20082
+ },
20083
+ {
20084
+ // Plugin-shim agents — protected by a node9-authored plugin/extension file
20085
+ // (no hooks, no MCP). hookFormat is unused for these (shimFile drives it).
20086
+ id: "opencode",
20087
+ label: "OpenCode",
20088
+ setupCommand: "node9 setup opencode",
20089
+ hookFormat: "flat",
20090
+ hookEvents: [],
20091
+ shimFile: (h) => path39.join(h, ".config", "opencode", "plugins", "node9.js"),
20092
+ present: (h) => exists(path39.join(h, ".config", "opencode")) || exists(path39.join(h, ".config", "opencode", "plugins", "node9.js"))
20093
+ },
20094
+ {
20095
+ id: "pi",
20096
+ label: "Pi",
20097
+ setupCommand: "node9 setup pi",
20098
+ hookFormat: "flat",
20099
+ hookEvents: [],
20100
+ shimFile: (h) => path39.join(h, ".pi", "agent", "extensions", "node9.js"),
20101
+ present: (h) => exists(path39.join(h, ".pi", "agent")) || exists(path39.join(h, ".pi", "agent", "extensions", "node9.js"))
20057
20102
  }
20058
20103
  ];
20059
20104
  function getAgentWiring(home = os34.homedir()) {
20060
20105
  const detected = detectAgents(home);
20061
- return AGENT_SPECS.map((spec) => ({
20062
- id: spec.id,
20063
- label: spec.label,
20064
- hookLabel: spec.hookLabel,
20065
- setupCommand: spec.setupCommand,
20066
- settingsPath: spec.settingsPath(home),
20067
- installed: detected[spec.id],
20068
- wireState: spec.wireState(home)
20069
- }));
20106
+ return AGENT_SPECS.map((spec) => {
20107
+ const present = spec.present(home);
20108
+ const pad = spec.labelPad ?? DEFAULT_LABEL_PAD;
20109
+ let hooks;
20110
+ let wireState;
20111
+ let hookLabel;
20112
+ let settingsPath;
20113
+ if (spec.shimFile) {
20114
+ const shimWired = exists(spec.shimFile(home));
20115
+ hooks = [{ label: "node9 plugin (node9 check)", wired: shimWired }];
20116
+ wireState = shimWired ? "wired" : present ? "unwired" : "absent";
20117
+ hookLabel = "node9 plugin";
20118
+ settingsPath = spec.shimFile(home);
20119
+ } else {
20120
+ const root = spec.hookFile ? readHookRoot(spec.hookFile(home), spec.hookFormat) : "absent";
20121
+ const primary = spec.hookEvents[0];
20122
+ const rootPresent = root !== "absent" && root !== "invalid";
20123
+ hooks = spec.hookEvents.map((ev) => ({
20124
+ label: hookLabelOf(ev, pad),
20125
+ wired: rootPresent && eventWired(root, ev, spec.hookFormat)
20126
+ }));
20127
+ if (root === "absent") wireState = "absent";
20128
+ else if (root === "invalid") wireState = "invalid";
20129
+ else wireState = primary && eventWired(root, primary, spec.hookFormat) ? "wired" : "unwired";
20130
+ hookLabel = primary ? `${primary.key} hook` : "MCP proxy";
20131
+ settingsPath = spec.hookFile ? spec.hookFile(home) : spec.mcpFile ? spec.mcpFile(home) : "";
20132
+ }
20133
+ const mcp = spec.mcpFile ? readMcp(spec.mcpFile(home), spec.mcpFormat ?? "json") : null;
20134
+ const anyHookWired = hooks.some((h) => h.wired);
20135
+ return {
20136
+ id: spec.id,
20137
+ label: spec.label,
20138
+ setupCommand: spec.setupCommand,
20139
+ installed: detected[spec.id],
20140
+ present,
20141
+ hooks,
20142
+ wireState,
20143
+ hookLabel,
20144
+ settingsPath,
20145
+ configFormat: spec.hookFormat === "yaml" ? "YAML" : "JSON",
20146
+ mcpServers: mcp ? mcp.wrapped : null,
20147
+ mcpProtected: mcp ? mcp.present : false,
20148
+ isProtected: anyHookWired || (mcp?.present ?? false)
20149
+ };
20150
+ });
20070
20151
  }
20071
20152
 
20072
20153
  // src/cli/commands/doctor.ts
@@ -20156,12 +20237,13 @@ function registerDoctorCommand(program2, version2) {
20156
20237
  section("Agent Hooks");
20157
20238
  const notConfigured = [];
20158
20239
  for (const a of getAgentWiring(homeDir2)) {
20159
- if (a.wireState === "wired") {
20160
- pass(`${a.label} \u2014 ${a.hookLabel} active`);
20161
- } else if (a.wireState === "unwired") {
20162
- fail(`${a.label} \u2014 settings found but node9 hook missing`, `Run: ${a.setupCommand}`);
20240
+ const anyHookWired = a.hooks.some((h) => h.wired);
20241
+ if (a.isProtected) {
20242
+ pass(`${a.label} \u2014 ${anyHookWired ? `${a.hookLabel} active` : "MCP proxy active"}`);
20163
20243
  } else if (a.wireState === "invalid") {
20164
20244
  fail(`${a.label} \u2014 settings file is invalid JSON`, a.settingsPath);
20245
+ } else if (a.wireState === "unwired") {
20246
+ fail(`${a.label} \u2014 settings found but node9 hook missing`, `Run: ${a.setupCommand}`);
20165
20247
  } else {
20166
20248
  notConfigured.push(a.label);
20167
20249
  }
@@ -21463,52 +21545,10 @@ function registerDaemonCommand(program2) {
21463
21545
  // src/cli/commands/status.ts
21464
21546
  init_core();
21465
21547
  init_daemon();
21466
- init_setup();
21467
21548
  import chalk15 from "chalk";
21468
21549
  import fs42 from "fs";
21469
21550
  import path43 from "path";
21470
21551
  import os38 from "os";
21471
- import * as yaml3 from "yaml";
21472
- function readHermesHooks(configPath) {
21473
- if (!fs42.existsSync(configPath)) return null;
21474
- let raw;
21475
- try {
21476
- raw = fs42.readFileSync(configPath, "utf-8");
21477
- } catch {
21478
- return null;
21479
- }
21480
- try {
21481
- const cfg = yaml3.parse(raw);
21482
- const has = (event) => (cfg?.hooks?.[event] ?? []).some(
21483
- (e) => typeof e?.command === "string" && isNode9Hook(e.command)
21484
- );
21485
- return { pre: has("pre_tool_call"), post: has("post_tool_call") };
21486
- } catch {
21487
- console.error(
21488
- chalk15.yellow(
21489
- ` \u26A0\uFE0F Hermes config.yaml at ${configPath} is not valid YAML \u2014 showing as unwired.`
21490
- )
21491
- );
21492
- return { pre: false, post: false };
21493
- }
21494
- }
21495
- function readJson3(filePath) {
21496
- try {
21497
- if (fs42.existsSync(filePath)) return JSON.parse(fs42.readFileSync(filePath, "utf-8"));
21498
- } catch {
21499
- }
21500
- return null;
21501
- }
21502
- function matchersHaveNode9Hook2(matchers) {
21503
- return (matchers ?? []).some((m) => (m.hooks ?? []).some((h) => isNode9Hook(h.command)));
21504
- }
21505
- function flatHaveNode9Hook2(entries) {
21506
- return (Array.isArray(entries) ? entries : []).some((h) => isNode9Hook(h.command));
21507
- }
21508
- function wrappedMcpServers(servers) {
21509
- if (!servers) return [];
21510
- return Object.entries(servers).filter(([, s]) => s.command === "node9" && Array.isArray(s.args) && s.args.length > 0).map(([name, s]) => `${name} \u2192 ${s.args.join(" ")}`);
21511
- }
21512
21552
  function printAgentSection(label, hookPairs, wrapped) {
21513
21553
  console.log(chalk15.bold(` ${label}`));
21514
21554
  for (const { name, present } of hookPairs) {
@@ -21575,102 +21615,23 @@ function registerStatusCommand(program2) {
21575
21615
  ` Sandbox: ${chalk15.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
21576
21616
  );
21577
21617
  }
21578
- const homeDir2 = os38.homedir();
21579
- const claudeSettings = readJson3(
21580
- path43.join(homeDir2, ".claude", "settings.json")
21581
- );
21582
- const claudeConfig = readJson3(path43.join(homeDir2, ".claude.json"));
21583
- const geminiSettings = readJson3(
21584
- path43.join(homeDir2, ".gemini", "settings.json")
21585
- );
21586
- const cursorConfig = readJson3(path43.join(homeDir2, ".cursor", "mcp.json"));
21587
- const antigravityHooks = readJson3(
21588
- path43.join(homeDir2, ".gemini", "config", "hooks.json")
21589
- );
21590
- const antigravityMcp = readJson3(
21591
- path43.join(homeDir2, ".gemini", "config", "mcp_config.json")
21592
- );
21593
- const antigravityPresent = antigravityHooks !== null || fs42.existsSync(path43.join(homeDir2, ".gemini", "antigravity-cli")) || fs42.existsSync(path43.join(homeDir2, ".gemini", "antigravity-ide"));
21594
- const copilotHooks = readJson3(
21595
- path43.join(homeDir2, ".copilot", "hooks", "node9.json")
21596
- );
21597
- const copilotMcp = readJson3(
21598
- path43.join(homeDir2, ".copilot", "mcp-config.json")
21599
- );
21600
- const copilotPresent = fs42.existsSync(path43.join(homeDir2, ".copilot"));
21601
- const hermesHooks = readHermesHooks(hermesConfigPath(homeDir2));
21602
- const agentFound = claudeSettings || claudeConfig || geminiSettings || cursorConfig || antigravityPresent || copilotPresent || hermesHooks;
21603
- if (agentFound) {
21618
+ const wiring = getAgentWiring(os38.homedir()).filter((a) => a.present);
21619
+ if (wiring.length > 0) {
21604
21620
  console.log("");
21605
21621
  console.log(chalk15.bold(" Agent Wiring:"));
21606
21622
  console.log("");
21607
- if (claudeSettings || claudeConfig) {
21608
- const preHook = matchersHaveNode9Hook2(claudeSettings?.hooks?.PreToolUse);
21609
- const postHook = matchersHaveNode9Hook2(claudeSettings?.hooks?.PostToolUse);
21610
- printAgentSection(
21611
- "Claude Code",
21612
- [
21613
- { name: "PreToolUse (node9 check)", present: preHook },
21614
- { name: "PostToolUse (node9 log)", present: postHook }
21615
- ],
21616
- wrappedMcpServers(claudeConfig?.mcpServers)
21617
- );
21618
- console.log("");
21619
- }
21620
- if (geminiSettings) {
21621
- const beforeHook = matchersHaveNode9Hook2(geminiSettings.hooks?.BeforeTool);
21622
- const afterHook = matchersHaveNode9Hook2(geminiSettings.hooks?.AfterTool);
21623
- printAgentSection(
21624
- "Gemini CLI",
21625
- [
21626
- { name: "BeforeTool (node9 check)", present: beforeHook },
21627
- { name: "AfterTool (node9 log)", present: afterHook }
21628
- ],
21629
- wrappedMcpServers(geminiSettings.mcpServers)
21630
- );
21631
- console.log("");
21632
- }
21633
- if (antigravityPresent) {
21634
- const preHook = matchersHaveNode9Hook2(antigravityHooks?.hooks?.PreToolUse);
21635
- const postHook = matchersHaveNode9Hook2(antigravityHooks?.hooks?.PostToolUse);
21636
- printAgentSection(
21637
- "Antigravity",
21638
- [
21639
- { name: "PreToolUse (node9 check)", present: preHook },
21640
- { name: "PostToolUse (node9 log)", present: postHook }
21641
- ],
21642
- wrappedMcpServers(antigravityMcp?.mcpServers)
21643
- );
21644
- console.log("");
21645
- }
21646
- if (copilotPresent) {
21647
- const preHook = flatHaveNode9Hook2(copilotHooks?.hooks?.PreToolUse);
21648
- const postHook = flatHaveNode9Hook2(copilotHooks?.hooks?.PostToolUse);
21649
- const promptHook = flatHaveNode9Hook2(copilotHooks?.hooks?.UserPromptSubmit);
21650
- printAgentSection(
21651
- "GitHub Copilot",
21652
- [
21653
- { name: "PreToolUse (node9 check)", present: preHook },
21654
- { name: "PostToolUse (node9 log)", present: postHook },
21655
- { name: "UserPromptSubmit (node9 check)", present: promptHook }
21656
- ],
21657
- wrappedMcpServers(copilotMcp?.mcpServers)
21658
- );
21659
- console.log("");
21660
- }
21661
- if (cursorConfig) {
21662
- printAgentSection("Cursor", [], wrappedMcpServers(cursorConfig.mcpServers));
21663
- console.log("");
21664
- }
21665
- if (hermesHooks) {
21623
+ for (const a of wiring) {
21624
+ if (a.wireState === "invalid") {
21625
+ console.error(
21626
+ chalk15.yellow(
21627
+ ` \u26A0\uFE0F ${a.label} config at ${a.settingsPath} is not valid ${a.configFormat} \u2014 showing as unwired.`
21628
+ )
21629
+ );
21630
+ }
21666
21631
  printAgentSection(
21667
- "Hermes Agent",
21668
- [
21669
- { name: "pre_tool_call (node9 check)", present: hermesHooks.pre },
21670
- { name: "post_tool_call (node9 log)", present: hermesHooks.post }
21671
- ],
21672
- null
21673
- // Hermes has no MCP surface
21632
+ a.label,
21633
+ a.hooks.map((h) => ({ name: h.label, present: h.wired })),
21634
+ a.mcpServers
21674
21635
  );
21675
21636
  console.log("");
21676
21637
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.35.1",
3
+ "version": "1.36.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",