@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.
- package/dist/cli.js +175 -214
- package/dist/cli.mjs +175 -214
- 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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
8189
|
-
cursor:
|
|
8190
|
-
codex:
|
|
8191
|
-
windsurf:
|
|
8192
|
-
vscode:
|
|
8193
|
-
claudeDesktop: desktopPath !== null &&
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
19987
|
-
|
|
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(
|
|
19991
|
+
raw = import_fs38.default.readFileSync(filePath, "utf-8");
|
|
19998
19992
|
} catch {
|
|
19999
19993
|
return "absent";
|
|
20000
19994
|
}
|
|
20001
19995
|
try {
|
|
20002
|
-
const
|
|
20003
|
-
|
|
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
|
-
|
|
20018
|
-
|
|
20019
|
-
|
|
20020
|
-
|
|
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
|
-
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
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
|
-
|
|
20040
|
-
|
|
20041
|
-
|
|
20042
|
-
|
|
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
|
-
|
|
20051
|
-
|
|
20052
|
-
|
|
20053
|
-
|
|
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
|
-
|
|
20062
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
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
|
-
|
|
20073
|
-
|
|
20074
|
-
|
|
20075
|
-
|
|
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
|
-
|
|
20084
|
-
|
|
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
|
-
|
|
20091
|
-
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
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
|
-
|
|
20188
|
-
|
|
20189
|
-
|
|
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
|
|
21607
|
-
|
|
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
|
-
|
|
21636
|
-
|
|
21637
|
-
|
|
21638
|
-
|
|
21639
|
-
|
|
21640
|
-
|
|
21641
|
-
|
|
21642
|
-
|
|
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
|
-
|
|
21696
|
-
|
|
21697
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
8171
|
-
cursor:
|
|
8172
|
-
codex:
|
|
8173
|
-
windsurf:
|
|
8174
|
-
vscode:
|
|
8175
|
-
claudeDesktop: desktopPath !== null &&
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
19959
|
-
|
|
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(
|
|
19963
|
+
raw = fs38.readFileSync(filePath, "utf-8");
|
|
19970
19964
|
} catch {
|
|
19971
19965
|
return "absent";
|
|
19972
19966
|
}
|
|
19973
19967
|
try {
|
|
19974
|
-
const
|
|
19975
|
-
|
|
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
|
-
|
|
19990
|
-
|
|
19991
|
-
|
|
19992
|
-
|
|
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
|
-
|
|
20001
|
-
|
|
20002
|
-
|
|
20003
|
-
|
|
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
|
-
|
|
20012
|
-
|
|
20013
|
-
|
|
20014
|
-
|
|
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
|
-
|
|
20023
|
-
|
|
20024
|
-
|
|
20025
|
-
|
|
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
|
-
|
|
20034
|
-
|
|
20035
|
-
|
|
20036
|
-
|
|
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
|
-
|
|
20045
|
-
|
|
20046
|
-
|
|
20047
|
-
|
|
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
|
-
|
|
20056
|
-
|
|
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
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
|
|
20068
|
-
|
|
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
|
-
|
|
20160
|
-
|
|
20161
|
-
|
|
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
|
|
21579
|
-
|
|
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
|
-
|
|
21608
|
-
|
|
21609
|
-
|
|
21610
|
-
|
|
21611
|
-
|
|
21612
|
-
|
|
21613
|
-
|
|
21614
|
-
|
|
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
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
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.
|
|
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",
|