@node9/proxy 1.27.0 → 1.28.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/README.md +3 -3
- package/dist/cli.js +265 -16
- package/dist/cli.mjs +265 -16
- package/dist/dashboard.mjs +9 -1
- package/dist/index.js +10 -1
- package/dist/index.mjs +10 -1
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
Node9 sits between your AI agent and the tools it can use — **discover** what it's already been doing, **protect** against risky actions in real time, and **review** what happened over any time window.
|
|
12
12
|
|
|
13
|
-
Works with **Claude Code · Codex CLI · Gemini CLI · Cursor · Windsurf · any MCP server**.
|
|
13
|
+
Works with **Claude Code · Codex CLI · Gemini CLI · Cursor · Windsurf · VSCode · Claude Desktop · Opencode · Pi · Hermes Agent · any MCP server**.
|
|
14
14
|
|
|
15
15
|
## What Node9 does
|
|
16
16
|
|
|
@@ -66,7 +66,7 @@ npm install -g node9-ai
|
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
node9 init # auto-wires
|
|
69
|
+
node9 init # auto-wires all detected agents + MCP servers
|
|
70
70
|
node9 doctor # verify everything is wired correctly
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -195,7 +195,7 @@ def run_command(cmd: str) -> str:
|
|
|
195
195
|
## Under the hood
|
|
196
196
|
|
|
197
197
|
- **Scan** reads raw agent history from `~/.claude/projects/`, `~/.gemini/tmp/`, `~/.codex/sessions/` — no API calls, fully offline
|
|
198
|
-
- **Runtime**
|
|
198
|
+
- **Runtime** intercepts tool calls via pre-execution hooks (Claude Code, Codex, Gemini CLI, Opencode, Pi) or via the MCP gateway (Cursor, Windsurf, VSCode, Claude Desktop). All decisions land in `~/.node9/audit.log` atomically.
|
|
199
199
|
- **MCP gateway** is a stdio proxy; intercepts `tools/list` + `tools/call` JSON-RPC, forwards the rest
|
|
200
200
|
- **Policy engine** uses [mvdan-sh](https://github.com/mvdan/sh) for bash AST analysis — defeats obfuscation via backslash escaping, variable substitution, eval of remote download
|
|
201
201
|
- **Shadow repo** for auto-undo lives at `~/.node9/snapshots/<hash16>/` — never touches your `.git`
|
package/dist/cli.js
CHANGED
|
@@ -119,9 +119,11 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
119
119
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
120
120
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
121
121
|
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
122
|
+
const agentToolNameField = meta?.agentToolName ? { agentToolName: meta.agentToolName } : {};
|
|
122
123
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
123
124
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
124
125
|
tool: toolName,
|
|
126
|
+
...agentToolNameField,
|
|
125
127
|
...argsField,
|
|
126
128
|
decision,
|
|
127
129
|
checkedBy,
|
|
@@ -3945,7 +3947,14 @@ var init_config = __esm({
|
|
|
3945
3947
|
"edit_file",
|
|
3946
3948
|
"create_file",
|
|
3947
3949
|
"edit",
|
|
3948
|
-
"replace"
|
|
3950
|
+
"replace",
|
|
3951
|
+
// Claude / canonicalised Hermes — shouldSnapshot lowercases the
|
|
3952
|
+
// incoming name before set-membership, so we list the lowercase
|
|
3953
|
+
// forms of `Bash`/`Write`/`Edit`/`MultiEdit`. Without these,
|
|
3954
|
+
// post-canonicalisation Hermes `patch` / `write_file` (which now
|
|
3955
|
+
// arrive as `Edit` / `Write`) silently skipped snapshotting.
|
|
3956
|
+
"write",
|
|
3957
|
+
"multiedit"
|
|
3949
3958
|
],
|
|
3950
3959
|
onlyPaths: [],
|
|
3951
3960
|
ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
|
|
@@ -7340,7 +7349,13 @@ function detectAgents(homeDir2 = import_os12.default.homedir()) {
|
|
|
7340
7349
|
// dir lazily on first launch — same class of bug as opencode's #186
|
|
7341
7350
|
// (design R6) — so fall back to PATH lookup for installed-but-never-
|
|
7342
7351
|
// launched pi.
|
|
7343
|
-
pi: exists(import_path15.default.join(homeDir2, ".pi", "agent")) || binaryInPath("pi")
|
|
7352
|
+
pi: exists(import_path15.default.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
|
|
7353
|
+
// Hermes Agent (https://github.com/NousResearch/hermes-agent): home dir
|
|
7354
|
+
// is $HERMES_HOME (default ~/.hermes) per hermes_constants.py:30. config.yaml
|
|
7355
|
+
// appears after `hermes setup` has run; the directory alone exists from
|
|
7356
|
+
// install time. PATH fallback covers the rare case where the user blew
|
|
7357
|
+
// away ~/.hermes but kept the binary.
|
|
7358
|
+
hermes: exists(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
|
|
7344
7359
|
};
|
|
7345
7360
|
}
|
|
7346
7361
|
async function setupCursor() {
|
|
@@ -8147,6 +8162,169 @@ function teardownPi() {
|
|
|
8147
8162
|
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Could not remove ${extensionPath}: ${String(err2)}`));
|
|
8148
8163
|
}
|
|
8149
8164
|
}
|
|
8165
|
+
function hermesHomeDir(homeDir2 = import_os12.default.homedir()) {
|
|
8166
|
+
const env = process.env.HERMES_HOME?.trim();
|
|
8167
|
+
if (env && import_path15.default.isAbsolute(env)) return env;
|
|
8168
|
+
return import_path15.default.join(homeDir2, ".hermes");
|
|
8169
|
+
}
|
|
8170
|
+
function hermesConfigPath(homeDir2 = import_os12.default.homedir()) {
|
|
8171
|
+
return import_path15.default.join(hermesHomeDir(homeDir2), HERMES_CONFIG_FILENAME);
|
|
8172
|
+
}
|
|
8173
|
+
function hermesAllowlistPath(homeDir2 = import_os12.default.homedir()) {
|
|
8174
|
+
return import_path15.default.join(hermesHomeDir(homeDir2), HERMES_ALLOWLIST_FILENAME);
|
|
8175
|
+
}
|
|
8176
|
+
function setupHermes() {
|
|
8177
|
+
const homeDir2 = import_os12.default.homedir();
|
|
8178
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
8179
|
+
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
8180
|
+
if (!import_fs13.default.existsSync(configPath)) {
|
|
8181
|
+
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
|
|
8182
|
+
console.log(import_chalk.default.gray(" Run `hermes setup` first, then re-run node9 setup hermes."));
|
|
8183
|
+
return;
|
|
8184
|
+
}
|
|
8185
|
+
let anythingChanged = false;
|
|
8186
|
+
const raw = import_fs13.default.readFileSync(configPath, "utf-8");
|
|
8187
|
+
const doc = yaml.parseDocument(raw);
|
|
8188
|
+
if (doc.errors.length > 0) {
|
|
8189
|
+
console.log(import_chalk.default.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
|
|
8190
|
+
for (const err2 of doc.errors.slice(0, 3)) {
|
|
8191
|
+
console.log(import_chalk.default.gray(` \u2022 ${err2.message}`));
|
|
8192
|
+
}
|
|
8193
|
+
console.log(
|
|
8194
|
+
import_chalk.default.gray(" Fix the file (or run `hermes config edit`), then re-run node9 setup hermes.")
|
|
8195
|
+
);
|
|
8196
|
+
return;
|
|
8197
|
+
}
|
|
8198
|
+
const current = doc.toJS() ?? {};
|
|
8199
|
+
for (const { event, subcmd } of HERMES_HOOK_PLAN) {
|
|
8200
|
+
const command = fullPathCommand(subcmd);
|
|
8201
|
+
const existing = current.hooks?.[event] ?? [];
|
|
8202
|
+
const node9Idx = existing.findIndex(
|
|
8203
|
+
(e) => typeof e?.command === "string" && isNode9Hook(e.command)
|
|
8204
|
+
);
|
|
8205
|
+
if (node9Idx === -1) {
|
|
8206
|
+
const newEntries = [...existing, { command, timeout: 10 }];
|
|
8207
|
+
doc.setIn(["hooks", event], newEntries);
|
|
8208
|
+
console.log(import_chalk.default.green(` \u2705 Hermes ${event} hook added \u2192 node9 ${subcmd}`));
|
|
8209
|
+
anythingChanged = true;
|
|
8210
|
+
} else if (existing[node9Idx].command !== command || isStaleHookCommand(existing[node9Idx].command ?? "")) {
|
|
8211
|
+
const newEntries = [...existing];
|
|
8212
|
+
newEntries[node9Idx] = { ...newEntries[node9Idx], command };
|
|
8213
|
+
doc.setIn(["hooks", event], newEntries);
|
|
8214
|
+
console.log(import_chalk.default.yellow(` \u{1F527} Hermes ${event} hook repaired (stale path \u2192 current binary)`));
|
|
8215
|
+
anythingChanged = true;
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
if (current.hooks_auto_accept !== true) {
|
|
8219
|
+
doc.set("hooks_auto_accept", true);
|
|
8220
|
+
console.log(import_chalk.default.green(" \u2705 hooks_auto_accept set to true"));
|
|
8221
|
+
anythingChanged = true;
|
|
8222
|
+
}
|
|
8223
|
+
if (anythingChanged) {
|
|
8224
|
+
import_fs13.default.writeFileSync(configPath, doc.toString());
|
|
8225
|
+
}
|
|
8226
|
+
let allowlist = {};
|
|
8227
|
+
if (import_fs13.default.existsSync(allowlistPath)) {
|
|
8228
|
+
try {
|
|
8229
|
+
allowlist = JSON.parse(import_fs13.default.readFileSync(allowlistPath, "utf-8"));
|
|
8230
|
+
} catch {
|
|
8231
|
+
allowlist = {};
|
|
8232
|
+
}
|
|
8233
|
+
}
|
|
8234
|
+
if (!Array.isArray(allowlist.approvals)) allowlist.approvals = [];
|
|
8235
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
8236
|
+
let allowlistChanged = false;
|
|
8237
|
+
for (const { event, subcmd } of HERMES_HOOK_PLAN) {
|
|
8238
|
+
const command = fullPathCommand(subcmd);
|
|
8239
|
+
const existingIdx = allowlist.approvals.findIndex(
|
|
8240
|
+
(e) => e?.event === event && typeof e?.command === "string" && isNode9Hook(e.command)
|
|
8241
|
+
);
|
|
8242
|
+
if (existingIdx === -1) {
|
|
8243
|
+
allowlist.approvals.push({ event, command, approved_at: nowIso });
|
|
8244
|
+
allowlistChanged = true;
|
|
8245
|
+
} else if (allowlist.approvals[existingIdx].command !== command) {
|
|
8246
|
+
allowlist.approvals[existingIdx] = { event, command, approved_at: nowIso };
|
|
8247
|
+
allowlistChanged = true;
|
|
8248
|
+
}
|
|
8249
|
+
}
|
|
8250
|
+
if (allowlistChanged) {
|
|
8251
|
+
import_fs13.default.mkdirSync(import_path15.default.dirname(allowlistPath), { recursive: true });
|
|
8252
|
+
import_fs13.default.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
|
|
8253
|
+
console.log(import_chalk.default.green(" \u2705 Hermes shell-hooks allowlist populated"));
|
|
8254
|
+
anythingChanged = true;
|
|
8255
|
+
}
|
|
8256
|
+
if (anythingChanged) {
|
|
8257
|
+
console.log(import_chalk.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Hermes Agent!"));
|
|
8258
|
+
console.log(import_chalk.default.gray(" Restart Hermes for changes to take effect."));
|
|
8259
|
+
printDaemonTip();
|
|
8260
|
+
} else {
|
|
8261
|
+
console.log(import_chalk.default.blue("\u2139\uFE0F Node9 is already fully configured for Hermes Agent."));
|
|
8262
|
+
printDaemonTip();
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
function teardownHermes() {
|
|
8266
|
+
const homeDir2 = import_os12.default.homedir();
|
|
8267
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
8268
|
+
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
8269
|
+
if (!import_fs13.default.existsSync(configPath)) {
|
|
8270
|
+
console.log(import_chalk.default.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
|
|
8271
|
+
return;
|
|
8272
|
+
}
|
|
8273
|
+
const raw = import_fs13.default.readFileSync(configPath, "utf-8");
|
|
8274
|
+
const doc = yaml.parseDocument(raw);
|
|
8275
|
+
if (doc.errors.length > 0) {
|
|
8276
|
+
console.log(
|
|
8277
|
+
import_chalk.default.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
|
|
8278
|
+
);
|
|
8279
|
+
} else {
|
|
8280
|
+
teardownHermesConfigDoc(doc, configPath);
|
|
8281
|
+
}
|
|
8282
|
+
teardownHermesAllowlist(allowlistPath);
|
|
8283
|
+
}
|
|
8284
|
+
function teardownHermesConfigDoc(doc, configPath) {
|
|
8285
|
+
let anythingChanged = false;
|
|
8286
|
+
const current = doc.toJS() ?? {};
|
|
8287
|
+
for (const { event } of HERMES_HOOK_PLAN) {
|
|
8288
|
+
const existing = current.hooks?.[event] ?? [];
|
|
8289
|
+
const filtered = existing.filter(
|
|
8290
|
+
(e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
|
|
8291
|
+
);
|
|
8292
|
+
if (filtered.length === existing.length) continue;
|
|
8293
|
+
if (filtered.length === 0) {
|
|
8294
|
+
doc.deleteIn(["hooks", event]);
|
|
8295
|
+
} else {
|
|
8296
|
+
doc.setIn(["hooks", event], filtered);
|
|
8297
|
+
}
|
|
8298
|
+
anythingChanged = true;
|
|
8299
|
+
}
|
|
8300
|
+
const afterHooks = doc.toJS()?.hooks;
|
|
8301
|
+
if (afterHooks && typeof afterHooks === "object" && Object.keys(afterHooks).length === 0) {
|
|
8302
|
+
doc.delete("hooks");
|
|
8303
|
+
anythingChanged = true;
|
|
8304
|
+
}
|
|
8305
|
+
if (anythingChanged) {
|
|
8306
|
+
import_fs13.default.writeFileSync(configPath, doc.toString());
|
|
8307
|
+
console.log(import_chalk.default.green(` \u2705 Removed Node9 hooks from ${configPath}`));
|
|
8308
|
+
} else {
|
|
8309
|
+
console.log(import_chalk.default.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
|
|
8310
|
+
}
|
|
8311
|
+
}
|
|
8312
|
+
function teardownHermesAllowlist(allowlistPath) {
|
|
8313
|
+
if (!import_fs13.default.existsSync(allowlistPath)) return;
|
|
8314
|
+
try {
|
|
8315
|
+
const raw = import_fs13.default.readFileSync(allowlistPath, "utf-8");
|
|
8316
|
+
const allowlist = JSON.parse(raw);
|
|
8317
|
+
if (!Array.isArray(allowlist.approvals)) return;
|
|
8318
|
+
const before = allowlist.approvals.length;
|
|
8319
|
+
allowlist.approvals = allowlist.approvals.filter(
|
|
8320
|
+
(e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
|
|
8321
|
+
);
|
|
8322
|
+
if (allowlist.approvals.length === before) return;
|
|
8323
|
+
import_fs13.default.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
|
|
8324
|
+
console.log(import_chalk.default.green(` \u2705 Removed Node9 entries from ${allowlistPath}`));
|
|
8325
|
+
} catch {
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8150
8328
|
function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
|
|
8151
8329
|
const detected = detectAgents(homeDir2);
|
|
8152
8330
|
const claudeWired = (() => {
|
|
@@ -8258,10 +8436,29 @@ function getAgentsStatus(homeDir2 = import_os12.default.homedir()) {
|
|
|
8258
8436
|
// simple existence check on the canonical install location.
|
|
8259
8437
|
wired: import_fs13.default.existsSync(import_path15.default.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME)),
|
|
8260
8438
|
mode: detected.pi ? "hooks" : null
|
|
8439
|
+
},
|
|
8440
|
+
{
|
|
8441
|
+
name: "hermes",
|
|
8442
|
+
label: "Hermes Agent",
|
|
8443
|
+
installed: detected.hermes,
|
|
8444
|
+
// Wired = node9 hook entry exists in the parsed config.yaml.
|
|
8445
|
+
// Reading the YAML cheaply via yaml.parse — we don't need the
|
|
8446
|
+
// Document API for a boolean status check.
|
|
8447
|
+
wired: (() => {
|
|
8448
|
+
try {
|
|
8449
|
+
const raw = import_fs13.default.readFileSync(hermesConfigPath(homeDir2), "utf-8");
|
|
8450
|
+
const cfg = yaml.parse(raw);
|
|
8451
|
+
const pre = cfg?.hooks?.["pre_tool_call"] ?? [];
|
|
8452
|
+
return pre.some((e) => typeof e?.command === "string" && isNode9Hook(e.command));
|
|
8453
|
+
} catch {
|
|
8454
|
+
return false;
|
|
8455
|
+
}
|
|
8456
|
+
})(),
|
|
8457
|
+
mode: detected.hermes ? "hooks" : null
|
|
8261
8458
|
}
|
|
8262
8459
|
];
|
|
8263
8460
|
}
|
|
8264
|
-
var import_fs13, import_path15, import_os12, import_chalk, import_prompts, import_smol_toml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME;
|
|
8461
|
+
var import_fs13, import_path15, import_os12, import_chalk, import_prompts, import_smol_toml, yaml, NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME, HERMES_CONFIG_FILENAME, HERMES_ALLOWLIST_FILENAME, HERMES_HOOK_PLAN;
|
|
8265
8462
|
var init_setup = __esm({
|
|
8266
8463
|
"src/setup.ts"() {
|
|
8267
8464
|
"use strict";
|
|
@@ -8271,6 +8468,7 @@ var init_setup = __esm({
|
|
|
8271
8468
|
import_chalk = __toESM(require("chalk"));
|
|
8272
8469
|
import_prompts = require("@inquirer/prompts");
|
|
8273
8470
|
import_smol_toml = require("smol-toml");
|
|
8471
|
+
yaml = __toESM(require("yaml"));
|
|
8274
8472
|
init_mcp_pin();
|
|
8275
8473
|
init_setup_opencode_shim();
|
|
8276
8474
|
init_setup_pi_shim();
|
|
@@ -8278,6 +8476,12 @@ var init_setup = __esm({
|
|
|
8278
8476
|
CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
|
|
8279
8477
|
OPENCODE_PLUGIN_NAME = "node9.js";
|
|
8280
8478
|
PI_EXTENSION_NAME = "node9.js";
|
|
8479
|
+
HERMES_CONFIG_FILENAME = "config.yaml";
|
|
8480
|
+
HERMES_ALLOWLIST_FILENAME = "shell-hooks-allowlist.json";
|
|
8481
|
+
HERMES_HOOK_PLAN = [
|
|
8482
|
+
{ event: "pre_tool_call", subcmd: "check" },
|
|
8483
|
+
{ event: "post_tool_call", subcmd: "log" }
|
|
8484
|
+
];
|
|
8281
8485
|
}
|
|
8282
8486
|
});
|
|
8283
8487
|
|
|
@@ -16575,6 +16779,33 @@ function resolveUserSkillRoot(entry, cwd) {
|
|
|
16575
16779
|
// src/cli/commands/check.ts
|
|
16576
16780
|
init_dlp();
|
|
16577
16781
|
init_audit();
|
|
16782
|
+
|
|
16783
|
+
// src/utils/hook-payload.ts
|
|
16784
|
+
function extractToolName(payload, defaultValue = "") {
|
|
16785
|
+
return payload.tool_name ?? payload.name ?? defaultValue;
|
|
16786
|
+
}
|
|
16787
|
+
function extractToolInput(payload) {
|
|
16788
|
+
return payload.tool_input ?? payload.args ?? {};
|
|
16789
|
+
}
|
|
16790
|
+
function canonicalToolName(name) {
|
|
16791
|
+
switch (name) {
|
|
16792
|
+
// Hermes Agent
|
|
16793
|
+
case "terminal":
|
|
16794
|
+
return "Bash";
|
|
16795
|
+
case "write_file":
|
|
16796
|
+
return "Write";
|
|
16797
|
+
case "patch":
|
|
16798
|
+
return "Edit";
|
|
16799
|
+
case "read_file":
|
|
16800
|
+
return "Read";
|
|
16801
|
+
case "search_files":
|
|
16802
|
+
return "Grep";
|
|
16803
|
+
default:
|
|
16804
|
+
return name;
|
|
16805
|
+
}
|
|
16806
|
+
}
|
|
16807
|
+
|
|
16808
|
+
// src/cli/commands/check.ts
|
|
16578
16809
|
function sanitize2(value) {
|
|
16579
16810
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
16580
16811
|
}
|
|
@@ -16593,9 +16824,15 @@ function detectAiAgent(payload) {
|
|
|
16593
16824
|
if (payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0) {
|
|
16594
16825
|
return "Gemini CLI";
|
|
16595
16826
|
}
|
|
16827
|
+
if (payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call") {
|
|
16828
|
+
return "Hermes";
|
|
16829
|
+
}
|
|
16596
16830
|
if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_SESSION_ID) {
|
|
16597
16831
|
return "Claude Code";
|
|
16598
16832
|
}
|
|
16833
|
+
if (process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE) {
|
|
16834
|
+
return "Hermes";
|
|
16835
|
+
}
|
|
16599
16836
|
if (process.env.GEMINI_CLI_VERSION || process.env.GEMINI_API_KEY) {
|
|
16600
16837
|
return "Gemini CLI";
|
|
16601
16838
|
}
|
|
@@ -16742,8 +16979,8 @@ RAW: ${raw}
|
|
|
16742
16979
|
import_fs32.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
16743
16980
|
`);
|
|
16744
16981
|
}
|
|
16745
|
-
const toolName = sanitize2(payload
|
|
16746
|
-
const toolInput = payload
|
|
16982
|
+
const toolName = canonicalToolName(sanitize2(extractToolName(payload)));
|
|
16983
|
+
const toolInput = extractToolInput(payload);
|
|
16747
16984
|
const agent = detectAiAgent(payload);
|
|
16748
16985
|
const mcpMatch = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
|
|
16749
16986
|
const mcpServer = mcpMatch?.[1];
|
|
@@ -17075,8 +17312,9 @@ function registerLogCommand(program2) {
|
|
|
17075
17312
|
try {
|
|
17076
17313
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
17077
17314
|
const payload = JSON.parse(raw);
|
|
17078
|
-
const
|
|
17079
|
-
const
|
|
17315
|
+
const rawToolName = sanitize3(extractToolName(payload, "unknown"));
|
|
17316
|
+
const tool = canonicalToolName(rawToolName);
|
|
17317
|
+
const rawInput = extractToolInput(payload);
|
|
17080
17318
|
const metaTag = (() => {
|
|
17081
17319
|
const m = payload.meta;
|
|
17082
17320
|
if (m && typeof m === "object") {
|
|
@@ -17085,7 +17323,7 @@ function registerLogCommand(program2) {
|
|
|
17085
17323
|
}
|
|
17086
17324
|
return void 0;
|
|
17087
17325
|
})();
|
|
17088
|
-
const agent = metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : void 0;
|
|
17326
|
+
const agent = metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call" ? "Hermes" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE ? "Hermes" : void 0;
|
|
17089
17327
|
const entry = {
|
|
17090
17328
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17091
17329
|
tool,
|
|
@@ -17094,6 +17332,7 @@ function registerLogCommand(program2) {
|
|
|
17094
17332
|
source: "post-hook"
|
|
17095
17333
|
};
|
|
17096
17334
|
if (agent) entry.agent = agent;
|
|
17335
|
+
if (rawToolName !== tool) entry.agentToolName = rawToolName;
|
|
17097
17336
|
if (payload.session_id) entry.sessionId = payload.session_id;
|
|
17098
17337
|
const logPath = import_path34.default.join(import_os29.default.homedir(), ".node9", "audit.log");
|
|
17099
17338
|
if (!import_fs33.default.existsSync(import_path34.default.dirname(logPath)))
|
|
@@ -19219,11 +19458,13 @@ function registerInitCommand(program2) {
|
|
|
19219
19458
|
if (found.length === 0) {
|
|
19220
19459
|
console.log(
|
|
19221
19460
|
import_chalk16.default.gray(
|
|
19222
|
-
"No AI agents detected. Install Claude Code, Gemini CLI, Cursor, Windsurf, VSCode, or
|
|
19461
|
+
"No AI agents detected. Install one of the supported agents (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, VSCode, Claude Desktop, Opencode, Pi, or Hermes Agent)."
|
|
19223
19462
|
)
|
|
19224
19463
|
);
|
|
19225
19464
|
console.log(
|
|
19226
|
-
import_chalk16.default.gray(
|
|
19465
|
+
import_chalk16.default.gray(
|
|
19466
|
+
"then run: node9 agents add <claude|codex|gemini|cursor|windsurf|vscode|claudeDesktop|opencode|pi|hermes>"
|
|
19467
|
+
)
|
|
19227
19468
|
);
|
|
19228
19469
|
return;
|
|
19229
19470
|
}
|
|
@@ -19243,6 +19484,7 @@ function registerInitCommand(program2) {
|
|
|
19243
19484
|
else if (agent === "claudeDesktop") await setupClaudeDesktop();
|
|
19244
19485
|
else if (agent === "opencode") await setupOpencode();
|
|
19245
19486
|
else if (agent === "pi") await setupPi();
|
|
19487
|
+
else if (agent === "hermes") setupHermes();
|
|
19246
19488
|
console.log("");
|
|
19247
19489
|
}
|
|
19248
19490
|
if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
|
|
@@ -21004,7 +21246,8 @@ var SETUP_FN = {
|
|
|
21004
21246
|
vscode: setupVSCode,
|
|
21005
21247
|
claudeDesktop: setupClaudeDesktop,
|
|
21006
21248
|
opencode: setupOpencode,
|
|
21007
|
-
pi: setupPi
|
|
21249
|
+
pi: setupPi,
|
|
21250
|
+
hermes: setupHermes
|
|
21008
21251
|
};
|
|
21009
21252
|
var TEARDOWN_FN = {
|
|
21010
21253
|
claude: teardownClaude,
|
|
@@ -21015,7 +21258,8 @@ var TEARDOWN_FN = {
|
|
|
21015
21258
|
vscode: teardownVSCode,
|
|
21016
21259
|
claudeDesktop: teardownClaudeDesktop,
|
|
21017
21260
|
opencode: teardownOpencode,
|
|
21018
|
-
pi: teardownPi
|
|
21261
|
+
pi: teardownPi,
|
|
21262
|
+
hermes: teardownHermes
|
|
21019
21263
|
};
|
|
21020
21264
|
var AGENT_NAMES = Object.keys(SETUP_FN);
|
|
21021
21265
|
function registerAgentsCommand(program2) {
|
|
@@ -22353,10 +22597,11 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
|
|
|
22353
22597
|
if (target === "codex") return await setupCodex();
|
|
22354
22598
|
if (target === "windsurf") return await setupWindsurf();
|
|
22355
22599
|
if (target === "vscode") return await setupVSCode();
|
|
22600
|
+
if (target === "hermes") return setupHermes();
|
|
22356
22601
|
if (target === "hud") return setupHud();
|
|
22357
22602
|
console.error(
|
|
22358
22603
|
import_chalk30.default.red(
|
|
22359
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
22604
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22360
22605
|
)
|
|
22361
22606
|
);
|
|
22362
22607
|
process.exit(1);
|
|
@@ -22378,6 +22623,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
22378
22623
|
console.log(" " + import_chalk30.default.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
22379
22624
|
console.log(" " + import_chalk30.default.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
22380
22625
|
console.log(" " + import_chalk30.default.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
22626
|
+
console.log(" " + import_chalk30.default.green("hermes") + " \u2014 Hermes Agent (hook mode)");
|
|
22381
22627
|
process.stdout.write(
|
|
22382
22628
|
" " + import_chalk30.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
22383
22629
|
);
|
|
@@ -22391,10 +22637,11 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
22391
22637
|
if (t === "codex") return await setupCodex();
|
|
22392
22638
|
if (t === "windsurf") return await setupWindsurf();
|
|
22393
22639
|
if (t === "vscode") return await setupVSCode();
|
|
22640
|
+
if (t === "hermes") return setupHermes();
|
|
22394
22641
|
if (t === "hud") return setupHud();
|
|
22395
22642
|
console.error(
|
|
22396
22643
|
import_chalk30.default.red(
|
|
22397
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
22644
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22398
22645
|
)
|
|
22399
22646
|
);
|
|
22400
22647
|
process.exit(1);
|
|
@@ -22413,11 +22660,12 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
|
|
|
22413
22660
|
else if (target === "codex") fn = teardownCodex;
|
|
22414
22661
|
else if (target === "windsurf") fn = teardownWindsurf;
|
|
22415
22662
|
else if (target === "vscode") fn = teardownVSCode;
|
|
22663
|
+
else if (target === "hermes") fn = teardownHermes;
|
|
22416
22664
|
else if (target === "hud") fn = teardownHud;
|
|
22417
22665
|
else {
|
|
22418
22666
|
console.error(
|
|
22419
22667
|
import_chalk30.default.red(
|
|
22420
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
22668
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22421
22669
|
)
|
|
22422
22670
|
);
|
|
22423
22671
|
process.exit(1);
|
|
@@ -22450,7 +22698,8 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
22450
22698
|
["Cursor", teardownCursor],
|
|
22451
22699
|
["Codex", teardownCodex],
|
|
22452
22700
|
["Windsurf", teardownWindsurf],
|
|
22453
|
-
["VSCode", teardownVSCode]
|
|
22701
|
+
["VSCode", teardownVSCode],
|
|
22702
|
+
["Hermes", teardownHermes]
|
|
22454
22703
|
]) {
|
|
22455
22704
|
try {
|
|
22456
22705
|
fn();
|
package/dist/cli.mjs
CHANGED
|
@@ -100,9 +100,11 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
100
100
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
101
101
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
102
102
|
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
103
|
+
const agentToolNameField = meta?.agentToolName ? { agentToolName: meta.agentToolName } : {};
|
|
103
104
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
104
105
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
105
106
|
tool: toolName,
|
|
107
|
+
...agentToolNameField,
|
|
106
108
|
...argsField,
|
|
107
109
|
decision,
|
|
108
110
|
checkedBy,
|
|
@@ -3923,7 +3925,14 @@ var init_config = __esm({
|
|
|
3923
3925
|
"edit_file",
|
|
3924
3926
|
"create_file",
|
|
3925
3927
|
"edit",
|
|
3926
|
-
"replace"
|
|
3928
|
+
"replace",
|
|
3929
|
+
// Claude / canonicalised Hermes — shouldSnapshot lowercases the
|
|
3930
|
+
// incoming name before set-membership, so we list the lowercase
|
|
3931
|
+
// forms of `Bash`/`Write`/`Edit`/`MultiEdit`. Without these,
|
|
3932
|
+
// post-canonicalisation Hermes `patch` / `write_file` (which now
|
|
3933
|
+
// arrive as `Edit` / `Write`) silently skipped snapshotting.
|
|
3934
|
+
"write",
|
|
3935
|
+
"multiedit"
|
|
3927
3936
|
],
|
|
3928
3937
|
onlyPaths: [],
|
|
3929
3938
|
ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
|
|
@@ -6823,6 +6832,7 @@ import os12 from "os";
|
|
|
6823
6832
|
import chalk from "chalk";
|
|
6824
6833
|
import { confirm } from "@inquirer/prompts";
|
|
6825
6834
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
6835
|
+
import * as yaml from "yaml";
|
|
6826
6836
|
function hasNode9McpServer(servers) {
|
|
6827
6837
|
const entry = servers["node9"];
|
|
6828
6838
|
return !!entry && entry.command === "node9" && Array.isArray(entry.args) && entry.args[0] === "mcp-server";
|
|
@@ -7321,7 +7331,13 @@ function detectAgents(homeDir2 = os12.homedir()) {
|
|
|
7321
7331
|
// dir lazily on first launch — same class of bug as opencode's #186
|
|
7322
7332
|
// (design R6) — so fall back to PATH lookup for installed-but-never-
|
|
7323
7333
|
// launched pi.
|
|
7324
|
-
pi: exists(path15.join(homeDir2, ".pi", "agent")) || binaryInPath("pi")
|
|
7334
|
+
pi: exists(path15.join(homeDir2, ".pi", "agent")) || binaryInPath("pi"),
|
|
7335
|
+
// Hermes Agent (https://github.com/NousResearch/hermes-agent): home dir
|
|
7336
|
+
// is $HERMES_HOME (default ~/.hermes) per hermes_constants.py:30. config.yaml
|
|
7337
|
+
// appears after `hermes setup` has run; the directory alone exists from
|
|
7338
|
+
// install time. PATH fallback covers the rare case where the user blew
|
|
7339
|
+
// away ~/.hermes but kept the binary.
|
|
7340
|
+
hermes: exists(hermesHomeDir(homeDir2)) || binaryInPath("hermes")
|
|
7325
7341
|
};
|
|
7326
7342
|
}
|
|
7327
7343
|
async function setupCursor() {
|
|
@@ -8128,6 +8144,169 @@ function teardownPi() {
|
|
|
8128
8144
|
console.log(chalk.yellow(` \u26A0\uFE0F Could not remove ${extensionPath}: ${String(err2)}`));
|
|
8129
8145
|
}
|
|
8130
8146
|
}
|
|
8147
|
+
function hermesHomeDir(homeDir2 = os12.homedir()) {
|
|
8148
|
+
const env = process.env.HERMES_HOME?.trim();
|
|
8149
|
+
if (env && path15.isAbsolute(env)) return env;
|
|
8150
|
+
return path15.join(homeDir2, ".hermes");
|
|
8151
|
+
}
|
|
8152
|
+
function hermesConfigPath(homeDir2 = os12.homedir()) {
|
|
8153
|
+
return path15.join(hermesHomeDir(homeDir2), HERMES_CONFIG_FILENAME);
|
|
8154
|
+
}
|
|
8155
|
+
function hermesAllowlistPath(homeDir2 = os12.homedir()) {
|
|
8156
|
+
return path15.join(hermesHomeDir(homeDir2), HERMES_ALLOWLIST_FILENAME);
|
|
8157
|
+
}
|
|
8158
|
+
function setupHermes() {
|
|
8159
|
+
const homeDir2 = os12.homedir();
|
|
8160
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
8161
|
+
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
8162
|
+
if (!fs13.existsSync(configPath)) {
|
|
8163
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Hermes config not found at ${configPath}`));
|
|
8164
|
+
console.log(chalk.gray(" Run `hermes setup` first, then re-run node9 setup hermes."));
|
|
8165
|
+
return;
|
|
8166
|
+
}
|
|
8167
|
+
let anythingChanged = false;
|
|
8168
|
+
const raw = fs13.readFileSync(configPath, "utf-8");
|
|
8169
|
+
const doc = yaml.parseDocument(raw);
|
|
8170
|
+
if (doc.errors.length > 0) {
|
|
8171
|
+
console.log(chalk.yellow(` \u26A0\uFE0F Hermes config.yaml has YAML parse errors:`));
|
|
8172
|
+
for (const err2 of doc.errors.slice(0, 3)) {
|
|
8173
|
+
console.log(chalk.gray(` \u2022 ${err2.message}`));
|
|
8174
|
+
}
|
|
8175
|
+
console.log(
|
|
8176
|
+
chalk.gray(" Fix the file (or run `hermes config edit`), then re-run node9 setup hermes.")
|
|
8177
|
+
);
|
|
8178
|
+
return;
|
|
8179
|
+
}
|
|
8180
|
+
const current = doc.toJS() ?? {};
|
|
8181
|
+
for (const { event, subcmd } of HERMES_HOOK_PLAN) {
|
|
8182
|
+
const command = fullPathCommand(subcmd);
|
|
8183
|
+
const existing = current.hooks?.[event] ?? [];
|
|
8184
|
+
const node9Idx = existing.findIndex(
|
|
8185
|
+
(e) => typeof e?.command === "string" && isNode9Hook(e.command)
|
|
8186
|
+
);
|
|
8187
|
+
if (node9Idx === -1) {
|
|
8188
|
+
const newEntries = [...existing, { command, timeout: 10 }];
|
|
8189
|
+
doc.setIn(["hooks", event], newEntries);
|
|
8190
|
+
console.log(chalk.green(` \u2705 Hermes ${event} hook added \u2192 node9 ${subcmd}`));
|
|
8191
|
+
anythingChanged = true;
|
|
8192
|
+
} else if (existing[node9Idx].command !== command || isStaleHookCommand(existing[node9Idx].command ?? "")) {
|
|
8193
|
+
const newEntries = [...existing];
|
|
8194
|
+
newEntries[node9Idx] = { ...newEntries[node9Idx], command };
|
|
8195
|
+
doc.setIn(["hooks", event], newEntries);
|
|
8196
|
+
console.log(chalk.yellow(` \u{1F527} Hermes ${event} hook repaired (stale path \u2192 current binary)`));
|
|
8197
|
+
anythingChanged = true;
|
|
8198
|
+
}
|
|
8199
|
+
}
|
|
8200
|
+
if (current.hooks_auto_accept !== true) {
|
|
8201
|
+
doc.set("hooks_auto_accept", true);
|
|
8202
|
+
console.log(chalk.green(" \u2705 hooks_auto_accept set to true"));
|
|
8203
|
+
anythingChanged = true;
|
|
8204
|
+
}
|
|
8205
|
+
if (anythingChanged) {
|
|
8206
|
+
fs13.writeFileSync(configPath, doc.toString());
|
|
8207
|
+
}
|
|
8208
|
+
let allowlist = {};
|
|
8209
|
+
if (fs13.existsSync(allowlistPath)) {
|
|
8210
|
+
try {
|
|
8211
|
+
allowlist = JSON.parse(fs13.readFileSync(allowlistPath, "utf-8"));
|
|
8212
|
+
} catch {
|
|
8213
|
+
allowlist = {};
|
|
8214
|
+
}
|
|
8215
|
+
}
|
|
8216
|
+
if (!Array.isArray(allowlist.approvals)) allowlist.approvals = [];
|
|
8217
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
8218
|
+
let allowlistChanged = false;
|
|
8219
|
+
for (const { event, subcmd } of HERMES_HOOK_PLAN) {
|
|
8220
|
+
const command = fullPathCommand(subcmd);
|
|
8221
|
+
const existingIdx = allowlist.approvals.findIndex(
|
|
8222
|
+
(e) => e?.event === event && typeof e?.command === "string" && isNode9Hook(e.command)
|
|
8223
|
+
);
|
|
8224
|
+
if (existingIdx === -1) {
|
|
8225
|
+
allowlist.approvals.push({ event, command, approved_at: nowIso });
|
|
8226
|
+
allowlistChanged = true;
|
|
8227
|
+
} else if (allowlist.approvals[existingIdx].command !== command) {
|
|
8228
|
+
allowlist.approvals[existingIdx] = { event, command, approved_at: nowIso };
|
|
8229
|
+
allowlistChanged = true;
|
|
8230
|
+
}
|
|
8231
|
+
}
|
|
8232
|
+
if (allowlistChanged) {
|
|
8233
|
+
fs13.mkdirSync(path15.dirname(allowlistPath), { recursive: true });
|
|
8234
|
+
fs13.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
|
|
8235
|
+
console.log(chalk.green(" \u2705 Hermes shell-hooks allowlist populated"));
|
|
8236
|
+
anythingChanged = true;
|
|
8237
|
+
}
|
|
8238
|
+
if (anythingChanged) {
|
|
8239
|
+
console.log(chalk.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Hermes Agent!"));
|
|
8240
|
+
console.log(chalk.gray(" Restart Hermes for changes to take effect."));
|
|
8241
|
+
printDaemonTip();
|
|
8242
|
+
} else {
|
|
8243
|
+
console.log(chalk.blue("\u2139\uFE0F Node9 is already fully configured for Hermes Agent."));
|
|
8244
|
+
printDaemonTip();
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
function teardownHermes() {
|
|
8248
|
+
const homeDir2 = os12.homedir();
|
|
8249
|
+
const configPath = hermesConfigPath(homeDir2);
|
|
8250
|
+
const allowlistPath = hermesAllowlistPath(homeDir2);
|
|
8251
|
+
if (!fs13.existsSync(configPath)) {
|
|
8252
|
+
console.log(chalk.blue(` \u2139\uFE0F ${configPath} not found \u2014 nothing to remove`));
|
|
8253
|
+
return;
|
|
8254
|
+
}
|
|
8255
|
+
const raw = fs13.readFileSync(configPath, "utf-8");
|
|
8256
|
+
const doc = yaml.parseDocument(raw);
|
|
8257
|
+
if (doc.errors.length > 0) {
|
|
8258
|
+
console.log(
|
|
8259
|
+
chalk.yellow(` \u26A0\uFE0F Skipping ${configPath} \u2014 file has YAML parse errors, fix it manually.`)
|
|
8260
|
+
);
|
|
8261
|
+
} else {
|
|
8262
|
+
teardownHermesConfigDoc(doc, configPath);
|
|
8263
|
+
}
|
|
8264
|
+
teardownHermesAllowlist(allowlistPath);
|
|
8265
|
+
}
|
|
8266
|
+
function teardownHermesConfigDoc(doc, configPath) {
|
|
8267
|
+
let anythingChanged = false;
|
|
8268
|
+
const current = doc.toJS() ?? {};
|
|
8269
|
+
for (const { event } of HERMES_HOOK_PLAN) {
|
|
8270
|
+
const existing = current.hooks?.[event] ?? [];
|
|
8271
|
+
const filtered = existing.filter(
|
|
8272
|
+
(e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
|
|
8273
|
+
);
|
|
8274
|
+
if (filtered.length === existing.length) continue;
|
|
8275
|
+
if (filtered.length === 0) {
|
|
8276
|
+
doc.deleteIn(["hooks", event]);
|
|
8277
|
+
} else {
|
|
8278
|
+
doc.setIn(["hooks", event], filtered);
|
|
8279
|
+
}
|
|
8280
|
+
anythingChanged = true;
|
|
8281
|
+
}
|
|
8282
|
+
const afterHooks = doc.toJS()?.hooks;
|
|
8283
|
+
if (afterHooks && typeof afterHooks === "object" && Object.keys(afterHooks).length === 0) {
|
|
8284
|
+
doc.delete("hooks");
|
|
8285
|
+
anythingChanged = true;
|
|
8286
|
+
}
|
|
8287
|
+
if (anythingChanged) {
|
|
8288
|
+
fs13.writeFileSync(configPath, doc.toString());
|
|
8289
|
+
console.log(chalk.green(` \u2705 Removed Node9 hooks from ${configPath}`));
|
|
8290
|
+
} else {
|
|
8291
|
+
console.log(chalk.blue(` \u2139\uFE0F No Node9 hooks found in ${configPath}`));
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
function teardownHermesAllowlist(allowlistPath) {
|
|
8295
|
+
if (!fs13.existsSync(allowlistPath)) return;
|
|
8296
|
+
try {
|
|
8297
|
+
const raw = fs13.readFileSync(allowlistPath, "utf-8");
|
|
8298
|
+
const allowlist = JSON.parse(raw);
|
|
8299
|
+
if (!Array.isArray(allowlist.approvals)) return;
|
|
8300
|
+
const before = allowlist.approvals.length;
|
|
8301
|
+
allowlist.approvals = allowlist.approvals.filter(
|
|
8302
|
+
(e) => !(typeof e?.command === "string" && isNode9Hook(e.command))
|
|
8303
|
+
);
|
|
8304
|
+
if (allowlist.approvals.length === before) return;
|
|
8305
|
+
fs13.writeFileSync(allowlistPath, JSON.stringify(allowlist, null, 2) + "\n");
|
|
8306
|
+
console.log(chalk.green(` \u2705 Removed Node9 entries from ${allowlistPath}`));
|
|
8307
|
+
} catch {
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8131
8310
|
function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
8132
8311
|
const detected = detectAgents(homeDir2);
|
|
8133
8312
|
const claudeWired = (() => {
|
|
@@ -8239,10 +8418,29 @@ function getAgentsStatus(homeDir2 = os12.homedir()) {
|
|
|
8239
8418
|
// simple existence check on the canonical install location.
|
|
8240
8419
|
wired: fs13.existsSync(path15.join(homeDir2, ".pi", "agent", "extensions", PI_EXTENSION_NAME)),
|
|
8241
8420
|
mode: detected.pi ? "hooks" : null
|
|
8421
|
+
},
|
|
8422
|
+
{
|
|
8423
|
+
name: "hermes",
|
|
8424
|
+
label: "Hermes Agent",
|
|
8425
|
+
installed: detected.hermes,
|
|
8426
|
+
// Wired = node9 hook entry exists in the parsed config.yaml.
|
|
8427
|
+
// Reading the YAML cheaply via yaml.parse — we don't need the
|
|
8428
|
+
// Document API for a boolean status check.
|
|
8429
|
+
wired: (() => {
|
|
8430
|
+
try {
|
|
8431
|
+
const raw = fs13.readFileSync(hermesConfigPath(homeDir2), "utf-8");
|
|
8432
|
+
const cfg = yaml.parse(raw);
|
|
8433
|
+
const pre = cfg?.hooks?.["pre_tool_call"] ?? [];
|
|
8434
|
+
return pre.some((e) => typeof e?.command === "string" && isNode9Hook(e.command));
|
|
8435
|
+
} catch {
|
|
8436
|
+
return false;
|
|
8437
|
+
}
|
|
8438
|
+
})(),
|
|
8439
|
+
mode: detected.hermes ? "hooks" : null
|
|
8242
8440
|
}
|
|
8243
8441
|
];
|
|
8244
8442
|
}
|
|
8245
|
-
var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME;
|
|
8443
|
+
var NODE9_MCP_SERVER_ENTRY, CODEX_PRE_TOOL_MATCHERS, OPENCODE_PLUGIN_NAME, PI_EXTENSION_NAME, HERMES_CONFIG_FILENAME, HERMES_ALLOWLIST_FILENAME, HERMES_HOOK_PLAN;
|
|
8246
8444
|
var init_setup = __esm({
|
|
8247
8445
|
"src/setup.ts"() {
|
|
8248
8446
|
"use strict";
|
|
@@ -8253,6 +8451,12 @@ var init_setup = __esm({
|
|
|
8253
8451
|
CODEX_PRE_TOOL_MATCHERS = ["^Bash$", "^apply_patch$", "^mcp__.*"];
|
|
8254
8452
|
OPENCODE_PLUGIN_NAME = "node9.js";
|
|
8255
8453
|
PI_EXTENSION_NAME = "node9.js";
|
|
8454
|
+
HERMES_CONFIG_FILENAME = "config.yaml";
|
|
8455
|
+
HERMES_ALLOWLIST_FILENAME = "shell-hooks-allowlist.json";
|
|
8456
|
+
HERMES_HOOK_PLAN = [
|
|
8457
|
+
{ event: "pre_tool_call", subcmd: "check" },
|
|
8458
|
+
{ event: "post_tool_call", subcmd: "log" }
|
|
8459
|
+
];
|
|
8256
8460
|
}
|
|
8257
8461
|
});
|
|
8258
8462
|
|
|
@@ -16547,6 +16751,33 @@ function resolveUserSkillRoot(entry, cwd) {
|
|
|
16547
16751
|
// src/cli/commands/check.ts
|
|
16548
16752
|
init_dlp();
|
|
16549
16753
|
init_audit();
|
|
16754
|
+
|
|
16755
|
+
// src/utils/hook-payload.ts
|
|
16756
|
+
function extractToolName(payload, defaultValue = "") {
|
|
16757
|
+
return payload.tool_name ?? payload.name ?? defaultValue;
|
|
16758
|
+
}
|
|
16759
|
+
function extractToolInput(payload) {
|
|
16760
|
+
return payload.tool_input ?? payload.args ?? {};
|
|
16761
|
+
}
|
|
16762
|
+
function canonicalToolName(name) {
|
|
16763
|
+
switch (name) {
|
|
16764
|
+
// Hermes Agent
|
|
16765
|
+
case "terminal":
|
|
16766
|
+
return "Bash";
|
|
16767
|
+
case "write_file":
|
|
16768
|
+
return "Write";
|
|
16769
|
+
case "patch":
|
|
16770
|
+
return "Edit";
|
|
16771
|
+
case "read_file":
|
|
16772
|
+
return "Read";
|
|
16773
|
+
case "search_files":
|
|
16774
|
+
return "Grep";
|
|
16775
|
+
default:
|
|
16776
|
+
return name;
|
|
16777
|
+
}
|
|
16778
|
+
}
|
|
16779
|
+
|
|
16780
|
+
// src/cli/commands/check.ts
|
|
16550
16781
|
function sanitize2(value) {
|
|
16551
16782
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
16552
16783
|
}
|
|
@@ -16565,9 +16796,15 @@ function detectAiAgent(payload) {
|
|
|
16565
16796
|
if (payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0) {
|
|
16566
16797
|
return "Gemini CLI";
|
|
16567
16798
|
}
|
|
16799
|
+
if (payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call") {
|
|
16800
|
+
return "Hermes";
|
|
16801
|
+
}
|
|
16568
16802
|
if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_SESSION_ID) {
|
|
16569
16803
|
return "Claude Code";
|
|
16570
16804
|
}
|
|
16805
|
+
if (process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE) {
|
|
16806
|
+
return "Hermes";
|
|
16807
|
+
}
|
|
16571
16808
|
if (process.env.GEMINI_CLI_VERSION || process.env.GEMINI_API_KEY) {
|
|
16572
16809
|
return "Gemini CLI";
|
|
16573
16810
|
}
|
|
@@ -16714,8 +16951,8 @@ RAW: ${raw}
|
|
|
16714
16951
|
fs32.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] STDIN: ${raw}
|
|
16715
16952
|
`);
|
|
16716
16953
|
}
|
|
16717
|
-
const toolName = sanitize2(payload
|
|
16718
|
-
const toolInput = payload
|
|
16954
|
+
const toolName = canonicalToolName(sanitize2(extractToolName(payload)));
|
|
16955
|
+
const toolInput = extractToolInput(payload);
|
|
16719
16956
|
const agent = detectAiAgent(payload);
|
|
16720
16957
|
const mcpMatch = toolName.match(/^mcp__([^_](?:[^_]|_(?!_))*?)__/i);
|
|
16721
16958
|
const mcpServer = mcpMatch?.[1];
|
|
@@ -17047,8 +17284,9 @@ function registerLogCommand(program2) {
|
|
|
17047
17284
|
try {
|
|
17048
17285
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
17049
17286
|
const payload = JSON.parse(raw);
|
|
17050
|
-
const
|
|
17051
|
-
const
|
|
17287
|
+
const rawToolName = sanitize3(extractToolName(payload, "unknown"));
|
|
17288
|
+
const tool = canonicalToolName(rawToolName);
|
|
17289
|
+
const rawInput = extractToolInput(payload);
|
|
17052
17290
|
const metaTag = (() => {
|
|
17053
17291
|
const m = payload.meta;
|
|
17054
17292
|
if (m && typeof m === "object") {
|
|
@@ -17057,7 +17295,7 @@ function registerLogCommand(program2) {
|
|
|
17057
17295
|
}
|
|
17058
17296
|
return void 0;
|
|
17059
17297
|
})();
|
|
17060
|
-
const agent = metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : void 0;
|
|
17298
|
+
const agent = metaTag !== void 0 ? metaTag : payload.turn_id !== void 0 ? "Codex" : payload.hook_event_name === "pre_tool_call" || payload.hook_event_name === "post_tool_call" ? "Hermes" : payload.hook_event_name === "PreToolUse" || payload.hook_event_name === "PostToolUse" || payload.tool_use_id !== void 0 || payload.permission_mode !== void 0 ? "Claude Code" : payload.hook_event_name === "BeforeTool" || payload.hook_event_name === "AfterTool" || payload.timestamp !== void 0 ? "Gemini CLI" : process.env.HERMES_SESSION_ID || process.env.HERMES_HOME || process.env.HERMES_INTERACTIVE ? "Hermes" : void 0;
|
|
17061
17299
|
const entry = {
|
|
17062
17300
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17063
17301
|
tool,
|
|
@@ -17066,6 +17304,7 @@ function registerLogCommand(program2) {
|
|
|
17066
17304
|
source: "post-hook"
|
|
17067
17305
|
};
|
|
17068
17306
|
if (agent) entry.agent = agent;
|
|
17307
|
+
if (rawToolName !== tool) entry.agentToolName = rawToolName;
|
|
17069
17308
|
if (payload.session_id) entry.sessionId = payload.session_id;
|
|
17070
17309
|
const logPath = path34.join(os29.homedir(), ".node9", "audit.log");
|
|
17071
17310
|
if (!fs33.existsSync(path34.dirname(logPath)))
|
|
@@ -19191,11 +19430,13 @@ function registerInitCommand(program2) {
|
|
|
19191
19430
|
if (found.length === 0) {
|
|
19192
19431
|
console.log(
|
|
19193
19432
|
chalk16.gray(
|
|
19194
|
-
"No AI agents detected. Install Claude Code, Gemini CLI, Cursor, Windsurf, VSCode, or
|
|
19433
|
+
"No AI agents detected. Install one of the supported agents (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, VSCode, Claude Desktop, Opencode, Pi, or Hermes Agent)."
|
|
19195
19434
|
)
|
|
19196
19435
|
);
|
|
19197
19436
|
console.log(
|
|
19198
|
-
chalk16.gray(
|
|
19437
|
+
chalk16.gray(
|
|
19438
|
+
"then run: node9 agents add <claude|codex|gemini|cursor|windsurf|vscode|claudeDesktop|opencode|pi|hermes>"
|
|
19439
|
+
)
|
|
19199
19440
|
);
|
|
19200
19441
|
return;
|
|
19201
19442
|
}
|
|
@@ -19215,6 +19456,7 @@ function registerInitCommand(program2) {
|
|
|
19215
19456
|
else if (agent === "claudeDesktop") await setupClaudeDesktop();
|
|
19216
19457
|
else if (agent === "opencode") await setupOpencode();
|
|
19217
19458
|
else if (agent === "pi") await setupPi();
|
|
19459
|
+
else if (agent === "hermes") setupHermes();
|
|
19218
19460
|
console.log("");
|
|
19219
19461
|
}
|
|
19220
19462
|
if ((process.platform === "darwin" || process.platform === "linux") && process.stdout.isTTY) {
|
|
@@ -20976,7 +21218,8 @@ var SETUP_FN = {
|
|
|
20976
21218
|
vscode: setupVSCode,
|
|
20977
21219
|
claudeDesktop: setupClaudeDesktop,
|
|
20978
21220
|
opencode: setupOpencode,
|
|
20979
|
-
pi: setupPi
|
|
21221
|
+
pi: setupPi,
|
|
21222
|
+
hermes: setupHermes
|
|
20980
21223
|
};
|
|
20981
21224
|
var TEARDOWN_FN = {
|
|
20982
21225
|
claude: teardownClaude,
|
|
@@ -20987,7 +21230,8 @@ var TEARDOWN_FN = {
|
|
|
20987
21230
|
vscode: teardownVSCode,
|
|
20988
21231
|
claudeDesktop: teardownClaudeDesktop,
|
|
20989
21232
|
opencode: teardownOpencode,
|
|
20990
|
-
pi: teardownPi
|
|
21233
|
+
pi: teardownPi,
|
|
21234
|
+
hermes: teardownHermes
|
|
20991
21235
|
};
|
|
20992
21236
|
var AGENT_NAMES = Object.keys(SETUP_FN);
|
|
20993
21237
|
function registerAgentsCommand(program2) {
|
|
@@ -22325,10 +22569,11 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
|
|
|
22325
22569
|
if (target === "codex") return await setupCodex();
|
|
22326
22570
|
if (target === "windsurf") return await setupWindsurf();
|
|
22327
22571
|
if (target === "vscode") return await setupVSCode();
|
|
22572
|
+
if (target === "hermes") return setupHermes();
|
|
22328
22573
|
if (target === "hud") return setupHud();
|
|
22329
22574
|
console.error(
|
|
22330
22575
|
chalk30.red(
|
|
22331
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
22576
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22332
22577
|
)
|
|
22333
22578
|
);
|
|
22334
22579
|
process.exit(1);
|
|
@@ -22350,6 +22595,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
22350
22595
|
console.log(" " + chalk30.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
22351
22596
|
console.log(" " + chalk30.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
22352
22597
|
console.log(" " + chalk30.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
22598
|
+
console.log(" " + chalk30.green("hermes") + " \u2014 Hermes Agent (hook mode)");
|
|
22353
22599
|
process.stdout.write(
|
|
22354
22600
|
" " + chalk30.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
22355
22601
|
);
|
|
@@ -22363,10 +22609,11 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
22363
22609
|
if (t === "codex") return await setupCodex();
|
|
22364
22610
|
if (t === "windsurf") return await setupWindsurf();
|
|
22365
22611
|
if (t === "vscode") return await setupVSCode();
|
|
22612
|
+
if (t === "hermes") return setupHermes();
|
|
22366
22613
|
if (t === "hud") return setupHud();
|
|
22367
22614
|
console.error(
|
|
22368
22615
|
chalk30.red(
|
|
22369
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
22616
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22370
22617
|
)
|
|
22371
22618
|
);
|
|
22372
22619
|
process.exit(1);
|
|
@@ -22385,11 +22632,12 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
|
|
|
22385
22632
|
else if (target === "codex") fn = teardownCodex;
|
|
22386
22633
|
else if (target === "windsurf") fn = teardownWindsurf;
|
|
22387
22634
|
else if (target === "vscode") fn = teardownVSCode;
|
|
22635
|
+
else if (target === "hermes") fn = teardownHermes;
|
|
22388
22636
|
else if (target === "hud") fn = teardownHud;
|
|
22389
22637
|
else {
|
|
22390
22638
|
console.error(
|
|
22391
22639
|
chalk30.red(
|
|
22392
|
-
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
22640
|
+
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
22393
22641
|
)
|
|
22394
22642
|
);
|
|
22395
22643
|
process.exit(1);
|
|
@@ -22422,7 +22670,8 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
22422
22670
|
["Cursor", teardownCursor],
|
|
22423
22671
|
["Codex", teardownCodex],
|
|
22424
22672
|
["Windsurf", teardownWindsurf],
|
|
22425
|
-
["VSCode", teardownVSCode]
|
|
22673
|
+
["VSCode", teardownVSCode],
|
|
22674
|
+
["Hermes", teardownHermes]
|
|
22426
22675
|
]) {
|
|
22427
22676
|
try {
|
|
22428
22677
|
fn();
|
package/dist/dashboard.mjs
CHANGED
|
@@ -2395,7 +2395,14 @@ var init_config = __esm({
|
|
|
2395
2395
|
"edit_file",
|
|
2396
2396
|
"create_file",
|
|
2397
2397
|
"edit",
|
|
2398
|
-
"replace"
|
|
2398
|
+
"replace",
|
|
2399
|
+
// Claude / canonicalised Hermes — shouldSnapshot lowercases the
|
|
2400
|
+
// incoming name before set-membership, so we list the lowercase
|
|
2401
|
+
// forms of `Bash`/`Write`/`Edit`/`MultiEdit`. Without these,
|
|
2402
|
+
// post-canonicalisation Hermes `patch` / `write_file` (which now
|
|
2403
|
+
// arrive as `Edit` / `Write`) silently skipped snapshotting.
|
|
2404
|
+
"write",
|
|
2405
|
+
"multiedit"
|
|
2399
2406
|
],
|
|
2400
2407
|
onlyPaths: [],
|
|
2401
2408
|
ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
|
|
@@ -3394,6 +3401,7 @@ var init_setup_pi_shim = __esm({
|
|
|
3394
3401
|
import chalk2 from "chalk";
|
|
3395
3402
|
import { confirm } from "@inquirer/prompts";
|
|
3396
3403
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
3404
|
+
import * as yaml from "yaml";
|
|
3397
3405
|
var init_setup = __esm({
|
|
3398
3406
|
"src/setup.ts"() {
|
|
3399
3407
|
"use strict";
|
package/dist/index.js
CHANGED
|
@@ -119,9 +119,11 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
119
119
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
120
120
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
121
121
|
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
122
|
+
const agentToolNameField = meta?.agentToolName ? { agentToolName: meta.agentToolName } : {};
|
|
122
123
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
123
124
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
124
125
|
tool: toolName,
|
|
126
|
+
...agentToolNameField,
|
|
125
127
|
...argsField,
|
|
126
128
|
decision,
|
|
127
129
|
checkedBy,
|
|
@@ -2998,7 +3000,14 @@ var DEFAULT_CONFIG = {
|
|
|
2998
3000
|
"edit_file",
|
|
2999
3001
|
"create_file",
|
|
3000
3002
|
"edit",
|
|
3001
|
-
"replace"
|
|
3003
|
+
"replace",
|
|
3004
|
+
// Claude / canonicalised Hermes — shouldSnapshot lowercases the
|
|
3005
|
+
// incoming name before set-membership, so we list the lowercase
|
|
3006
|
+
// forms of `Bash`/`Write`/`Edit`/`MultiEdit`. Without these,
|
|
3007
|
+
// post-canonicalisation Hermes `patch` / `write_file` (which now
|
|
3008
|
+
// arrive as `Edit` / `Write`) silently skipped snapshotting.
|
|
3009
|
+
"write",
|
|
3010
|
+
"multiedit"
|
|
3002
3011
|
],
|
|
3003
3012
|
onlyPaths: [],
|
|
3004
3013
|
ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
|
package/dist/index.mjs
CHANGED
|
@@ -99,9 +99,11 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
|
|
|
99
99
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
100
100
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
101
101
|
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
102
|
+
const agentToolNameField = meta?.agentToolName ? { agentToolName: meta.agentToolName } : {};
|
|
102
103
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
103
104
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
104
105
|
tool: toolName,
|
|
106
|
+
...agentToolNameField,
|
|
105
107
|
...argsField,
|
|
106
108
|
decision,
|
|
107
109
|
checkedBy,
|
|
@@ -2968,7 +2970,14 @@ var DEFAULT_CONFIG = {
|
|
|
2968
2970
|
"edit_file",
|
|
2969
2971
|
"create_file",
|
|
2970
2972
|
"edit",
|
|
2971
|
-
"replace"
|
|
2973
|
+
"replace",
|
|
2974
|
+
// Claude / canonicalised Hermes — shouldSnapshot lowercases the
|
|
2975
|
+
// incoming name before set-membership, so we list the lowercase
|
|
2976
|
+
// forms of `Bash`/`Write`/`Edit`/`MultiEdit`. Without these,
|
|
2977
|
+
// post-canonicalisation Hermes `patch` / `write_file` (which now
|
|
2978
|
+
// arrive as `Edit` / `Write`) silently skipped snapshotting.
|
|
2979
|
+
"write",
|
|
2980
|
+
"multiedit"
|
|
2972
2981
|
],
|
|
2973
2982
|
onlyPaths: [],
|
|
2974
2983
|
ignorePaths: ["**/node_modules/**", "dist/**", "build/**", ".next/**", "**/*.log"]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "The Sudo Command for AI Agents. Execution Security for Claude Code
|
|
3
|
+
"version": "1.28.0",
|
|
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",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -31,13 +31,20 @@
|
|
|
31
31
|
"homepage": "https://github.com/node9-ai/node9-proxy#readme",
|
|
32
32
|
"keywords": [
|
|
33
33
|
"ai-security",
|
|
34
|
+
"agent-security",
|
|
35
|
+
"agentic-ai",
|
|
34
36
|
"mcp",
|
|
35
37
|
"mcp-proxy",
|
|
36
38
|
"claude-code",
|
|
39
|
+
"claude-desktop",
|
|
40
|
+
"codex",
|
|
37
41
|
"gemini-cli",
|
|
38
42
|
"cursor",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
43
|
+
"windsurf",
|
|
44
|
+
"vscode",
|
|
45
|
+
"opencode",
|
|
46
|
+
"pi-agent",
|
|
47
|
+
"hermes-agent",
|
|
41
48
|
"sudo",
|
|
42
49
|
"security-proxy",
|
|
43
50
|
"human-in-the-loop",
|
|
@@ -84,6 +91,7 @@
|
|
|
84
91
|
"safe-regex2": "^5.1.0",
|
|
85
92
|
"smol-toml": "^1.6.1",
|
|
86
93
|
"string-width": "^4.2.3",
|
|
94
|
+
"yaml": "^2.9.0",
|
|
87
95
|
"zod": "^3.25.76"
|
|
88
96
|
},
|
|
89
97
|
"bundleDependencies": [
|