@node9/proxy 1.0.0 → 1.0.2

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 CHANGED
@@ -27,7 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_commander = require("commander");
28
28
 
29
29
  // src/core.ts
30
- var import_chalk = __toESM(require("chalk"));
30
+ var import_chalk2 = __toESM(require("chalk"));
31
31
  var import_prompts = require("@inquirer/prompts");
32
32
  var import_fs = __toESM(require("fs"));
33
33
  var import_path = __toESM(require("path"));
@@ -37,19 +37,69 @@ var import_sh_syntax = require("sh-syntax");
37
37
 
38
38
  // src/ui/native.ts
39
39
  var import_child_process = require("child_process");
40
+ var import_chalk = __toESM(require("chalk"));
40
41
  var isTestEnv = () => {
41
42
  return process.env.NODE_ENV === "test" || process.env.VITEST === "true" || !!process.env.VITEST || process.env.CI === "true" || !!process.env.CI || process.env.NODE9_TESTING === "1";
42
43
  };
44
+ function smartTruncate(str, maxLen = 500) {
45
+ if (str.length <= maxLen) return str;
46
+ const edge = Math.floor(maxLen / 2) - 3;
47
+ return `${str.slice(0, edge)} ... ${str.slice(-edge)}`;
48
+ }
49
+ function formatArgs(args) {
50
+ if (args === null || args === void 0) return "(none)";
51
+ let parsed = args;
52
+ if (typeof args === "string") {
53
+ const trimmed = args.trim();
54
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
55
+ try {
56
+ parsed = JSON.parse(trimmed);
57
+ } catch {
58
+ parsed = args;
59
+ }
60
+ } else {
61
+ return smartTruncate(args, 600);
62
+ }
63
+ }
64
+ if (typeof parsed === "object" && !Array.isArray(parsed)) {
65
+ const obj = parsed;
66
+ const codeKeys = [
67
+ "command",
68
+ "cmd",
69
+ "shell_command",
70
+ "bash_command",
71
+ "script",
72
+ "code",
73
+ "input",
74
+ "sql",
75
+ "query",
76
+ "arguments",
77
+ "args",
78
+ "param",
79
+ "params",
80
+ "text"
81
+ ];
82
+ const foundKey = Object.keys(obj).find((k) => codeKeys.includes(k.toLowerCase()));
83
+ if (foundKey) {
84
+ const val = obj[foundKey];
85
+ const str = typeof val === "string" ? val : JSON.stringify(val);
86
+ return `[${foundKey.toUpperCase()}]:
87
+ ${smartTruncate(str, 500)}`;
88
+ }
89
+ return Object.entries(obj).slice(0, 5).map(
90
+ ([k, v]) => ` ${k}: ${smartTruncate(typeof v === "string" ? v : JSON.stringify(v), 300)}`
91
+ ).join("\n");
92
+ }
93
+ return smartTruncate(JSON.stringify(parsed), 200);
94
+ }
43
95
  function sendDesktopNotification(title, body) {
44
96
  if (isTestEnv()) return;
45
97
  try {
46
- const safeTitle = title.replace(/"/g, '\\"');
47
- const safeBody = body.replace(/"/g, '\\"');
48
98
  if (process.platform === "darwin") {
49
- const script = `display notification "${safeBody}" with title "${safeTitle}"`;
99
+ const script = `display notification "${body.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"`;
50
100
  (0, import_child_process.spawn)("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
51
101
  } else if (process.platform === "linux") {
52
- (0, import_child_process.spawn)("notify-send", [safeTitle, safeBody, "--icon=dialog-warning"], {
102
+ (0, import_child_process.spawn)("notify-send", [title, body, "--icon=dialog-warning"], {
53
103
  detached: true,
54
104
  stdio: "ignore"
55
105
  }).unref();
@@ -57,69 +107,54 @@ function sendDesktopNotification(title, body) {
57
107
  } catch {
58
108
  }
59
109
  }
60
- function formatArgs(args) {
61
- if (args === null || args === void 0) return "(none)";
62
- if (typeof args !== "object" || Array.isArray(args)) {
63
- const str = typeof args === "string" ? args : JSON.stringify(args);
64
- return str.length > 200 ? str.slice(0, 200) + "\u2026" : str;
65
- }
66
- const entries = Object.entries(args).filter(
67
- ([, v]) => v !== null && v !== void 0 && v !== ""
68
- );
69
- if (entries.length === 0) return "(none)";
70
- const MAX_FIELDS = 5;
71
- const MAX_VALUE_LEN = 120;
72
- const lines = entries.slice(0, MAX_FIELDS).map(([key, val]) => {
73
- const str = typeof val === "string" ? val : JSON.stringify(val);
74
- const truncated = str.length > MAX_VALUE_LEN ? str.slice(0, MAX_VALUE_LEN) + "\u2026" : str;
75
- return ` ${key}: ${truncated}`;
76
- });
77
- if (entries.length > MAX_FIELDS) {
78
- lines.push(` \u2026 and ${entries.length - MAX_FIELDS} more field(s)`);
110
+ function escapePango(text) {
111
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
112
+ }
113
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
114
+ const lines = [];
115
+ if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
116
+ lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
117
+ lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
118
+ lines.push("");
119
+ lines.push(formattedArgs);
120
+ if (!locked) {
121
+ lines.push("");
122
+ lines.push('\u21B5 Enter = Allow \u21B5 | \u238B Esc = Block \u238B | "Always Allow" = never ask again');
79
123
  }
80
124
  return lines.join("\n");
81
125
  }
82
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal) {
83
- if (isTestEnv()) return "deny";
84
- if (process.env.NODE9_DEBUG === "1" || process.env.VITEST) {
85
- console.log(`[DEBUG Native] askNativePopup called for: ${toolName}`);
86
- console.log(`[DEBUG Native] isTestEnv check:`, {
87
- VITEST: process.env.VITEST,
88
- NODE_ENV: process.env.NODE_ENV,
89
- CI: process.env.CI,
90
- isTest: isTestEnv()
91
- });
92
- }
93
- const title = locked ? `\u26A1 Node9 \u2014 Locked by Admin Policy` : `\u{1F6E1}\uFE0F Node9 \u2014 Action Requires Approval`;
94
- let message = "";
126
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
127
+ const lines = [];
95
128
  if (locked) {
96
- message += `\u26A1 Awaiting remote approval via Slack. Local override is disabled.
97
- `;
98
- message += `\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
99
- `;
100
- }
101
- message += `Tool: ${toolName}
102
- `;
103
- message += `Agent: ${agent || "AI Agent"}
104
- `;
105
- if (explainableLabel) {
106
- message += `Reason: ${explainableLabel}
107
- `;
129
+ lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
130
+ lines.push("");
108
131
  }
109
- message += `
110
- Arguments:
111
- ${formatArgs(args)}`;
132
+ lines.push(
133
+ `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
134
+ );
135
+ lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
136
+ lines.push("");
137
+ lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
112
138
  if (!locked) {
113
- message += `
114
-
115
- Enter = Allow | Click "Block" to deny`;
139
+ lines.push("");
140
+ lines.push(
141
+ '<small>\u21B5 Enter = <b>Allow \u21B5</b> | \u238B Esc = <b>Block \u238B</b> | "Always Allow" = never ask again</small>'
142
+ );
116
143
  }
117
- const safeMessage = message.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/`/g, "'");
118
- const safeTitle = title.replace(/"/g, '\\"');
144
+ return lines.join("\n");
145
+ }
146
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal) {
147
+ if (isTestEnv()) return "deny";
148
+ const formattedArgs = formatArgs(args);
149
+ const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 Action Approval`;
150
+ const message = buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked);
151
+ process.stderr.write(import_chalk.default.yellow(`
152
+ \u{1F6E1}\uFE0F Node9: Intercepted "${toolName}" \u2014 awaiting user...
153
+ `));
119
154
  return new Promise((resolve) => {
120
155
  let childProcess = null;
121
156
  const onAbort = () => {
122
- if (childProcess) {
157
+ if (childProcess && childProcess.pid) {
123
158
  try {
124
159
  process.kill(childProcess.pid, "SIGKILL");
125
160
  } catch {
@@ -131,83 +166,58 @@ Enter = Allow | Click "Block" to deny`;
131
166
  if (signal.aborted) return resolve("deny");
132
167
  signal.addEventListener("abort", onAbort);
133
168
  }
134
- const cleanup = () => {
135
- if (signal) signal.removeEventListener("abort", onAbort);
136
- };
137
169
  try {
138
170
  if (process.platform === "darwin") {
139
- const buttons = locked ? `buttons {"Waiting\u2026"} default button "Waiting\u2026"` : `buttons {"Block", "Always Allow", "Allow"} default button "Allow" cancel button "Block"`;
140
- const script = `
141
- tell application "System Events"
142
- activate
143
- display dialog "${safeMessage}" with title "${safeTitle}" ${buttons}
144
- end tell`;
145
- childProcess = (0, import_child_process.spawn)("osascript", ["-e", script]);
146
- let output = "";
147
- childProcess.stdout?.on("data", (d) => output += d.toString());
148
- childProcess.on("close", (code) => {
149
- cleanup();
150
- if (locked) return resolve("deny");
151
- if (code === 0) {
152
- if (output.includes("Always Allow")) return resolve("always_allow");
153
- if (output.includes("Allow")) return resolve("allow");
154
- }
155
- resolve("deny");
156
- });
171
+ const buttons = locked ? `buttons {"Waiting\u2026"} default button "Waiting\u2026"` : `buttons {"Block \u238B", "Always Allow", "Allow \u21B5"} default button "Allow \u21B5" cancel button "Block \u238B"`;
172
+ const script = `on run argv
173
+ tell application "System Events"
174
+ activate
175
+ display dialog (item 1 of argv) with title (item 2 of argv) ${buttons}
176
+ end tell
177
+ end run`;
178
+ childProcess = (0, import_child_process.spawn)("osascript", ["-e", script, "--", message, title]);
157
179
  } else if (process.platform === "linux") {
158
- const argsList = locked ? [
159
- "--info",
160
- "--title",
161
- title,
162
- "--text",
163
- safeMessage,
164
- "--ok-label",
165
- "Waiting for Slack\u2026",
166
- "--timeout",
167
- "300"
168
- ] : [
169
- "--question",
180
+ const pangoMessage = buildPangoMessage(
181
+ toolName,
182
+ formattedArgs,
183
+ agent,
184
+ explainableLabel,
185
+ locked
186
+ );
187
+ const argsList = [
188
+ locked ? "--info" : "--question",
189
+ "--modal",
190
+ "--width=480",
170
191
  "--title",
171
192
  title,
172
193
  "--text",
173
- safeMessage,
194
+ pangoMessage,
174
195
  "--ok-label",
175
- "Allow",
176
- "--cancel-label",
177
- "Block",
178
- "--extra-button",
179
- "Always Allow",
196
+ locked ? "Waiting..." : "Allow \u21B5",
180
197
  "--timeout",
181
198
  "300"
182
199
  ];
200
+ if (!locked) {
201
+ argsList.push("--cancel-label", "Block \u238B");
202
+ argsList.push("--extra-button", "Always Allow");
203
+ }
183
204
  childProcess = (0, import_child_process.spawn)("zenity", argsList);
184
- let output = "";
185
- childProcess.stdout?.on("data", (d) => output += d.toString());
186
- childProcess.on("close", (code) => {
187
- cleanup();
188
- if (locked) return resolve("deny");
189
- if (output.trim() === "Always Allow") return resolve("always_allow");
190
- if (code === 0) return resolve("allow");
191
- resolve("deny");
192
- });
193
205
  } else if (process.platform === "win32") {
194
- const buttonType = locked ? "OK" : "YesNo";
195
- const ps = `
196
- Add-Type -AssemblyName PresentationFramework;
197
- $res = [System.Windows.MessageBox]::Show("${safeMessage}", "${safeTitle}", "${buttonType}", "Warning", "Button2", "DefaultDesktopOnly");
198
- if ($res -eq "Yes") { exit 0 } else { exit 1 }`;
206
+ const b64Msg = Buffer.from(message).toString("base64");
207
+ const b64Title = Buffer.from(title).toString("base64");
208
+ const ps = `Add-Type -AssemblyName PresentationFramework; $msg = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("${b64Msg}")); $title = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("${b64Title}")); $res = [System.Windows.MessageBox]::Show($msg, $title, "${locked ? "OK" : "YesNo"}", "Warning", "Button2", "DefaultDesktopOnly"); if ($res -eq "Yes") { exit 0 } else { exit 1 }`;
199
209
  childProcess = (0, import_child_process.spawn)("powershell", ["-Command", ps]);
200
- childProcess.on("close", (code) => {
201
- cleanup();
202
- if (locked) return resolve("deny");
203
- resolve(code === 0 ? "allow" : "deny");
204
- });
205
- } else {
206
- cleanup();
207
- resolve("deny");
208
210
  }
211
+ let output = "";
212
+ childProcess?.stdout?.on("data", (d) => output += d.toString());
213
+ childProcess?.on("close", (code) => {
214
+ if (signal) signal.removeEventListener("abort", onAbort);
215
+ if (locked) return resolve("deny");
216
+ if (output.includes("Always Allow")) return resolve("always_allow");
217
+ if (code === 0) return resolve("allow");
218
+ resolve("deny");
219
+ });
209
220
  } catch {
210
- cleanup();
211
221
  resolve("deny");
212
222
  }
213
223
  });
@@ -216,6 +226,8 @@ Enter = Allow | Click "Block" to deny`;
216
226
  // src/core.ts
217
227
  var PAUSED_FILE = import_path.default.join(import_os.default.homedir(), ".node9", "PAUSED");
218
228
  var TRUST_FILE = import_path.default.join(import_os.default.homedir(), ".node9", "trust.json");
229
+ var LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
230
+ var HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
219
231
  function checkPause() {
220
232
  try {
221
233
  if (!import_fs.default.existsSync(PAUSED_FILE)) return { paused: false };
@@ -282,36 +294,39 @@ function writeTrustSession(toolName, durationMs) {
282
294
  }
283
295
  }
284
296
  }
285
- function appendAuditModeEntry(toolName, args) {
297
+ function appendToLog(logPath, entry) {
286
298
  try {
287
- const entry = JSON.stringify({
288
- ts: (/* @__PURE__ */ new Date()).toISOString(),
289
- tool: toolName,
290
- args,
291
- decision: "would-have-blocked",
292
- source: "audit-mode"
293
- });
294
- const logPath = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
295
299
  const dir = import_path.default.dirname(logPath);
296
300
  if (!import_fs.default.existsSync(dir)) import_fs.default.mkdirSync(dir, { recursive: true });
297
- import_fs.default.appendFileSync(logPath, entry + "\n");
301
+ import_fs.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
298
302
  } catch {
299
303
  }
300
304
  }
301
- var DANGEROUS_WORDS = [
302
- "delete",
303
- "drop",
304
- "remove",
305
- "terminate",
306
- "refund",
307
- "write",
308
- "update",
309
- "destroy",
310
- "rm",
311
- "rmdir",
312
- "purge",
313
- "format"
314
- ];
305
+ function appendHookDebug(toolName, args, meta) {
306
+ const safeArgs = args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {};
307
+ appendToLog(HOOK_DEBUG_LOG, {
308
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
309
+ tool: toolName,
310
+ args: safeArgs,
311
+ agent: meta?.agent,
312
+ mcpServer: meta?.mcpServer,
313
+ hostname: import_os.default.hostname(),
314
+ cwd: process.cwd()
315
+ });
316
+ }
317
+ function appendLocalAudit(toolName, args, decision, checkedBy, meta) {
318
+ const safeArgs = args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {};
319
+ appendToLog(LOCAL_AUDIT_LOG, {
320
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
321
+ tool: toolName,
322
+ args: safeArgs,
323
+ decision,
324
+ checkedBy,
325
+ agent: meta?.agent,
326
+ mcpServer: meta?.mcpServer,
327
+ hostname: import_os.default.hostname()
328
+ });
329
+ }
315
330
  function tokenize(toolName) {
316
331
  return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
317
332
  }
@@ -418,16 +433,28 @@ function redactSecrets(text) {
418
433
  );
419
434
  return redacted;
420
435
  }
436
+ var DANGEROUS_WORDS = [
437
+ "drop",
438
+ "truncate",
439
+ "purge",
440
+ "format",
441
+ "destroy",
442
+ "terminate",
443
+ "revoke",
444
+ "docker",
445
+ "psql"
446
+ ];
421
447
  var DEFAULT_CONFIG = {
422
448
  settings: {
423
449
  mode: "standard",
424
450
  autoStartDaemon: true,
425
- enableUndo: false,
451
+ enableUndo: true,
452
+ // 🔥 ALWAYS TRUE BY DEFAULT for the safety net
426
453
  enableHookLogDebug: false,
427
454
  approvers: { native: true, browser: true, cloud: true, terminal: true }
428
455
  },
429
456
  policy: {
430
- sandboxPaths: [],
457
+ sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
431
458
  dangerousWords: DANGEROUS_WORDS,
432
459
  ignoredTools: [
433
460
  "list_*",
@@ -435,12 +462,44 @@ var DEFAULT_CONFIG = {
435
462
  "read_*",
436
463
  "describe_*",
437
464
  "read",
465
+ "glob",
438
466
  "grep",
439
467
  "ls",
440
- "askuserquestion"
468
+ "notebookread",
469
+ "notebookedit",
470
+ "webfetch",
471
+ "websearch",
472
+ "exitplanmode",
473
+ "askuserquestion",
474
+ "agent",
475
+ "task*",
476
+ "toolsearch",
477
+ "mcp__ide__*",
478
+ "getDiagnostics"
441
479
  ],
442
- toolInspection: { bash: "command", shell: "command" },
443
- rules: [{ action: "rm", allowPaths: ["**/node_modules/**", "dist/**", ".DS_Store"] }]
480
+ toolInspection: {
481
+ bash: "command",
482
+ shell: "command",
483
+ run_shell_command: "command",
484
+ "terminal.execute": "command",
485
+ "postgres:query": "sql"
486
+ },
487
+ rules: [
488
+ {
489
+ action: "rm",
490
+ allowPaths: [
491
+ "**/node_modules/**",
492
+ "dist/**",
493
+ "build/**",
494
+ ".next/**",
495
+ "coverage/**",
496
+ ".cache/**",
497
+ "tmp/**",
498
+ "temp/**",
499
+ ".DS_Store"
500
+ ]
501
+ }
502
+ ]
444
503
  },
445
504
  environments: {}
446
505
  };
@@ -505,20 +564,15 @@ async function evaluatePolicy(toolName, args, agent) {
505
564
  }
506
565
  const isManual = agent === "Terminal";
507
566
  if (isManual) {
508
- const NUCLEAR_COMMANDS = [
509
- "drop",
510
- "destroy",
511
- "purge",
512
- "rmdir",
513
- "format",
514
- "truncate",
515
- "alter",
516
- "grant",
517
- "revoke",
518
- "docker"
519
- ];
520
- const hasNuclear = allTokens.some((t) => NUCLEAR_COMMANDS.includes(t.toLowerCase()));
521
- if (!hasNuclear) return { decision: "allow" };
567
+ const SYSTEM_DISASTER_COMMANDS = ["mkfs", "shred", "dd", "drop", "truncate", "purge"];
568
+ const hasSystemDisaster = allTokens.some(
569
+ (t) => SYSTEM_DISASTER_COMMANDS.includes(t.toLowerCase())
570
+ );
571
+ const isRootWipe = allTokens.includes("rm") && (allTokens.includes("/") || allTokens.includes("/*"));
572
+ if (hasSystemDisaster || isRootWipe) {
573
+ return { decision: "review", blockedByLabel: "Manual Nuclear Protection" };
574
+ }
575
+ return { decision: "allow" };
522
576
  }
523
577
  if (pathTokens.length > 0 && config.policy.sandboxPaths.length > 0) {
524
578
  const allInSandbox = pathTokens.every((p) => matchesPattern(p, config.policy.sandboxPaths));
@@ -532,27 +586,39 @@ async function evaluatePolicy(toolName, args, agent) {
532
586
  if (pathTokens.length > 0) {
533
587
  const anyBlocked = pathTokens.some((p) => matchesPattern(p, rule.blockPaths || []));
534
588
  if (anyBlocked)
535
- return { decision: "review", blockedByLabel: "Project/Global Config (Rule Block)" };
589
+ return {
590
+ decision: "review",
591
+ blockedByLabel: `Project/Global Config \u2014 rule "${rule.action}" (path blocked)`
592
+ };
536
593
  const allAllowed = pathTokens.every((p) => matchesPattern(p, rule.allowPaths || []));
537
594
  if (allAllowed) return { decision: "allow" };
538
595
  }
539
- return { decision: "review", blockedByLabel: "Project/Global Config (Rule Default Block)" };
596
+ return {
597
+ decision: "review",
598
+ blockedByLabel: `Project/Global Config \u2014 rule "${rule.action}" (default block)`
599
+ };
540
600
  }
541
601
  }
602
+ let matchedDangerousWord;
542
603
  const isDangerous = allTokens.some(
543
604
  (token) => config.policy.dangerousWords.some((word) => {
544
605
  const w = word.toLowerCase();
545
- if (token === w) return true;
546
- try {
547
- return new RegExp(`\\b${w}\\b`, "i").test(token);
548
- } catch {
549
- return false;
550
- }
606
+ const hit = token === w || (() => {
607
+ try {
608
+ return new RegExp(`\\b${w}\\b`, "i").test(token);
609
+ } catch {
610
+ return false;
611
+ }
612
+ })();
613
+ if (hit && !matchedDangerousWord) matchedDangerousWord = word;
614
+ return hit;
551
615
  })
552
616
  );
553
617
  if (isDangerous) {
554
- const label = isManual ? "Manual Nuclear Protection" : "Project/Global Config (Dangerous Word)";
555
- return { decision: "review", blockedByLabel: label };
618
+ return {
619
+ decision: "review",
620
+ blockedByLabel: `Project/Global Config \u2014 dangerous word: "${matchedDangerousWord}"`
621
+ };
556
622
  }
557
623
  if (config.settings.mode === "strict") {
558
624
  const envConfig = getActiveEnvironment(config);
@@ -667,13 +733,16 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
667
733
  approvers.browser = false;
668
734
  approvers.terminal = false;
669
735
  }
736
+ if (config.settings.enableHookLogDebug && !isTestEnv2) {
737
+ appendHookDebug(toolName, args, meta);
738
+ }
670
739
  const isManual = meta?.agent === "Terminal";
671
740
  let explainableLabel = "Local Config";
672
741
  if (config.settings.mode === "audit") {
673
742
  if (!isIgnoredTool(toolName)) {
674
743
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
675
744
  if (policyResult.decision === "review") {
676
- appendAuditModeEntry(toolName, args);
745
+ appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
677
746
  sendDesktopNotification(
678
747
  "Node9 Audit Mode",
679
748
  `Would have blocked "${toolName}" (${policyResult.blockedByLabel || "Local Config"}) \u2014 running in audit mode`
@@ -685,20 +754,24 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
685
754
  if (!isIgnoredTool(toolName)) {
686
755
  if (getActiveTrustSession(toolName)) {
687
756
  if (creds?.apiKey) auditLocalAllow(toolName, args, "trust", creds, meta);
757
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta);
688
758
  return { approved: true, checkedBy: "trust" };
689
759
  }
690
760
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
691
761
  if (policyResult.decision === "allow") {
692
762
  if (creds?.apiKey) auditLocalAllow(toolName, args, "local-policy", creds, meta);
763
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta);
693
764
  return { approved: true, checkedBy: "local-policy" };
694
765
  }
695
766
  explainableLabel = policyResult.blockedByLabel || "Local Config";
696
767
  const persistent = getPersistentDecision(toolName);
697
768
  if (persistent === "allow") {
698
769
  if (creds?.apiKey) auditLocalAllow(toolName, args, "persistent", creds, meta);
770
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta);
699
771
  return { approved: true, checkedBy: "persistent" };
700
772
  }
701
773
  if (persistent === "deny") {
774
+ if (!isManual) appendLocalAudit(toolName, args, "deny", "persistent-deny", meta);
702
775
  return {
703
776
  approved: false,
704
777
  reason: `This tool ("${toolName}") is explicitly listed in your 'Always Deny' list.`,
@@ -708,6 +781,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
708
781
  }
709
782
  } else {
710
783
  if (creds?.apiKey) auditLocalAllow(toolName, args, "ignoredTools", creds, meta);
784
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta);
711
785
  return { approved: true };
712
786
  }
713
787
  let cloudRequestId = null;
@@ -735,8 +809,8 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
735
809
  const isNetworkError = error.message.includes("fetch") || error.name === "AbortError" || error.message.includes("ECONNREFUSED");
736
810
  const reason = isAuthError ? "Invalid or missing API key. Run `node9 login` to generate a key (must start with n9_live_)." : isNetworkError ? "Could not reach the Node9 cloud. Check your network or API URL." : error.message;
737
811
  console.error(
738
- import_chalk.default.yellow(`
739
- \u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) + import_chalk.default.dim(`
812
+ import_chalk2.default.yellow(`
813
+ \u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) + import_chalk2.default.dim(`
740
814
  Falling back to local rules...
741
815
  `)
742
816
  );
@@ -744,13 +818,13 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
744
818
  }
745
819
  if (cloudEnforced && cloudRequestId) {
746
820
  console.error(
747
- import_chalk.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
821
+ import_chalk2.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
748
822
  );
749
- console.error(import_chalk.default.cyan(" Dashboard \u2192 ") + import_chalk.default.bold("Mission Control > Activity Feed\n"));
823
+ console.error(import_chalk2.default.cyan(" Dashboard \u2192 ") + import_chalk2.default.bold("Mission Control > Activity Feed\n"));
750
824
  } else if (!cloudEnforced) {
751
825
  const cloudOffReason = !creds?.apiKey ? "no API key \u2014 run `node9 login` to connect" : "privacy mode (cloud disabled)";
752
826
  console.error(
753
- import_chalk.default.dim(`
827
+ import_chalk2.default.dim(`
754
828
  \u{1F6E1}\uFE0F Node9: intercepted "${toolName}" \u2014 cloud off (${cloudOffReason})
755
829
  `)
756
830
  );
@@ -815,9 +889,9 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
815
889
  try {
816
890
  if (!approvers.native && !cloudEnforced) {
817
891
  console.error(
818
- import_chalk.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for browser approval.")
892
+ import_chalk2.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for browser approval.")
819
893
  );
820
- console.error(import_chalk.default.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
894
+ console.error(import_chalk2.default.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
821
895
  `));
822
896
  }
823
897
  const daemonDecision = await askDaemon(toolName, args, meta, signal);
@@ -840,11 +914,11 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
840
914
  racePromises.push(
841
915
  (async () => {
842
916
  try {
843
- console.log(import_chalk.default.bgRed.white.bold(` \u{1F6D1} NODE9 INTERCEPTOR `));
844
- console.log(`${import_chalk.default.bold("Action:")} ${import_chalk.default.red(toolName)}`);
845
- console.log(`${import_chalk.default.bold("Flagged By:")} ${import_chalk.default.yellow(explainableLabel)}`);
917
+ console.log(import_chalk2.default.bgRed.white.bold(` \u{1F6D1} NODE9 INTERCEPTOR `));
918
+ console.log(`${import_chalk2.default.bold("Action:")} ${import_chalk2.default.red(toolName)}`);
919
+ console.log(`${import_chalk2.default.bold("Flagged By:")} ${import_chalk2.default.yellow(explainableLabel)}`);
846
920
  if (isRemoteLocked) {
847
- console.log(import_chalk.default.yellow(`\u26A1 LOCKED BY ADMIN POLICY: Waiting for Slack Approval...
921
+ console.log(import_chalk2.default.yellow(`\u26A1 LOCKED BY ADMIN POLICY: Waiting for Slack Approval...
848
922
  `));
849
923
  await new Promise((_, reject) => {
850
924
  signal.addEventListener("abort", () => reject(new Error("Aborted by SaaS")));
@@ -932,6 +1006,15 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
932
1006
  if (cloudRequestId && creds && finalResult.checkedBy !== "cloud") {
933
1007
  await resolveNode9SaaS(cloudRequestId, creds, finalResult.approved);
934
1008
  }
1009
+ if (!isManual) {
1010
+ appendLocalAudit(
1011
+ toolName,
1012
+ args,
1013
+ finalResult.approved ? "allow" : "deny",
1014
+ finalResult.checkedBy || finalResult.blockedBy || "unknown",
1015
+ meta
1016
+ );
1017
+ }
935
1018
  return finalResult;
936
1019
  }
937
1020
  function getConfig() {
@@ -962,8 +1045,8 @@ function getConfig() {
962
1045
  mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
963
1046
  if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
964
1047
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
965
- if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
966
1048
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
1049
+ if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
967
1050
  if (p.toolInspection)
968
1051
  mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
969
1052
  if (p.rules) mergedPolicy.rules.push(...p.rules);
@@ -1091,11 +1174,11 @@ async function pollNode9SaaS(requestId, creds, signal) {
1091
1174
  if (!statusRes.ok) continue;
1092
1175
  const { status, reason } = await statusRes.json();
1093
1176
  if (status === "APPROVED") {
1094
- console.error(import_chalk.default.green("\u2705 Approved via Cloud.\n"));
1177
+ console.error(import_chalk2.default.green("\u2705 Approved via Cloud.\n"));
1095
1178
  return { approved: true, reason };
1096
1179
  }
1097
1180
  if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
1098
- console.error(import_chalk.default.red("\u274C Denied via Cloud.\n"));
1181
+ console.error(import_chalk2.default.red("\u274C Denied via Cloud.\n"));
1099
1182
  return { approved: false, reason };
1100
1183
  }
1101
1184
  } catch {
@@ -1123,11 +1206,11 @@ async function resolveNode9SaaS(requestId, creds, approved) {
1123
1206
  var import_fs2 = __toESM(require("fs"));
1124
1207
  var import_path2 = __toESM(require("path"));
1125
1208
  var import_os2 = __toESM(require("os"));
1126
- var import_chalk2 = __toESM(require("chalk"));
1209
+ var import_chalk3 = __toESM(require("chalk"));
1127
1210
  var import_prompts2 = require("@inquirer/prompts");
1128
1211
  function printDaemonTip() {
1129
1212
  console.log(
1130
- import_chalk2.default.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + import_chalk2.default.white("\n To view your history or manage persistent rules, run:") + import_chalk2.default.green("\n node9 daemon --openui")
1213
+ import_chalk3.default.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + import_chalk3.default.white("\n To view your history or manage persistent rules, run:") + import_chalk3.default.green("\n node9 daemon --openui")
1131
1214
  );
1132
1215
  }
1133
1216
  function fullPathCommand(subcommand) {
@@ -1168,7 +1251,7 @@ async function setupClaude() {
1168
1251
  matcher: ".*",
1169
1252
  hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 60 }]
1170
1253
  });
1171
- console.log(import_chalk2.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
1254
+ console.log(import_chalk3.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
1172
1255
  anythingChanged = true;
1173
1256
  }
1174
1257
  const hasPostHook = settings.hooks.PostToolUse?.some(
@@ -1180,7 +1263,7 @@ async function setupClaude() {
1180
1263
  matcher: ".*",
1181
1264
  hooks: [{ type: "command", command: fullPathCommand("log"), timeout: 600 }]
1182
1265
  });
1183
- console.log(import_chalk2.default.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
1266
+ console.log(import_chalk3.default.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
1184
1267
  anythingChanged = true;
1185
1268
  }
1186
1269
  if (anythingChanged) {
@@ -1194,10 +1277,10 @@ async function setupClaude() {
1194
1277
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1195
1278
  }
1196
1279
  if (serversToWrap.length > 0) {
1197
- console.log(import_chalk2.default.bold("The following existing entries will be modified:\n"));
1198
- console.log(import_chalk2.default.white(` ${mcpPath}`));
1280
+ console.log(import_chalk3.default.bold("The following existing entries will be modified:\n"));
1281
+ console.log(import_chalk3.default.white(` ${mcpPath}`));
1199
1282
  for (const { name, originalCmd } of serversToWrap) {
1200
- console.log(import_chalk2.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1283
+ console.log(import_chalk3.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1201
1284
  }
1202
1285
  console.log("");
1203
1286
  const proceed = await (0, import_prompts2.confirm)({ message: "Wrap these MCP servers?", default: true });
@@ -1207,22 +1290,22 @@ async function setupClaude() {
1207
1290
  }
1208
1291
  claudeConfig.mcpServers = servers;
1209
1292
  writeJson(mcpPath, claudeConfig);
1210
- console.log(import_chalk2.default.green(`
1293
+ console.log(import_chalk3.default.green(`
1211
1294
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1212
1295
  anythingChanged = true;
1213
1296
  } else {
1214
- console.log(import_chalk2.default.yellow(" Skipped MCP server wrapping."));
1297
+ console.log(import_chalk3.default.yellow(" Skipped MCP server wrapping."));
1215
1298
  }
1216
1299
  console.log("");
1217
1300
  }
1218
1301
  if (!anythingChanged && serversToWrap.length === 0) {
1219
- console.log(import_chalk2.default.blue("\u2139\uFE0F Node9 is already fully configured for Claude Code."));
1302
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Node9 is already fully configured for Claude Code."));
1220
1303
  printDaemonTip();
1221
1304
  return;
1222
1305
  }
1223
1306
  if (anythingChanged) {
1224
- console.log(import_chalk2.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
1225
- console.log(import_chalk2.default.gray(" Restart Claude Code for changes to take effect."));
1307
+ console.log(import_chalk3.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
1308
+ console.log(import_chalk3.default.gray(" Restart Claude Code for changes to take effect."));
1226
1309
  printDaemonTip();
1227
1310
  }
1228
1311
  }
@@ -1250,7 +1333,7 @@ async function setupGemini() {
1250
1333
  }
1251
1334
  ]
1252
1335
  });
1253
- console.log(import_chalk2.default.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
1336
+ console.log(import_chalk3.default.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
1254
1337
  anythingChanged = true;
1255
1338
  }
1256
1339
  const hasAfterHook = Array.isArray(settings.hooks.AfterTool) && settings.hooks.AfterTool.some(
@@ -1263,7 +1346,7 @@ async function setupGemini() {
1263
1346
  matcher: ".*",
1264
1347
  hooks: [{ name: "node9-log", type: "command", command: fullPathCommand("log") }]
1265
1348
  });
1266
- console.log(import_chalk2.default.green(" \u2705 AfterTool hook added \u2192 node9 log"));
1349
+ console.log(import_chalk3.default.green(" \u2705 AfterTool hook added \u2192 node9 log"));
1267
1350
  anythingChanged = true;
1268
1351
  }
1269
1352
  if (anythingChanged) {
@@ -1277,10 +1360,10 @@ async function setupGemini() {
1277
1360
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1278
1361
  }
1279
1362
  if (serversToWrap.length > 0) {
1280
- console.log(import_chalk2.default.bold("The following existing entries will be modified:\n"));
1281
- console.log(import_chalk2.default.white(` ${settingsPath} (mcpServers)`));
1363
+ console.log(import_chalk3.default.bold("The following existing entries will be modified:\n"));
1364
+ console.log(import_chalk3.default.white(` ${settingsPath} (mcpServers)`));
1282
1365
  for (const { name, originalCmd } of serversToWrap) {
1283
- console.log(import_chalk2.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1366
+ console.log(import_chalk3.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1284
1367
  }
1285
1368
  console.log("");
1286
1369
  const proceed = await (0, import_prompts2.confirm)({ message: "Wrap these MCP servers?", default: true });
@@ -1290,22 +1373,22 @@ async function setupGemini() {
1290
1373
  }
1291
1374
  settings.mcpServers = servers;
1292
1375
  writeJson(settingsPath, settings);
1293
- console.log(import_chalk2.default.green(`
1376
+ console.log(import_chalk3.default.green(`
1294
1377
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1295
1378
  anythingChanged = true;
1296
1379
  } else {
1297
- console.log(import_chalk2.default.yellow(" Skipped MCP server wrapping."));
1380
+ console.log(import_chalk3.default.yellow(" Skipped MCP server wrapping."));
1298
1381
  }
1299
1382
  console.log("");
1300
1383
  }
1301
1384
  if (!anythingChanged && serversToWrap.length === 0) {
1302
- console.log(import_chalk2.default.blue("\u2139\uFE0F Node9 is already fully configured for Gemini CLI."));
1385
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Node9 is already fully configured for Gemini CLI."));
1303
1386
  printDaemonTip();
1304
1387
  return;
1305
1388
  }
1306
1389
  if (anythingChanged) {
1307
- console.log(import_chalk2.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Gemini CLI!"));
1308
- console.log(import_chalk2.default.gray(" Restart Gemini CLI for changes to take effect."));
1390
+ console.log(import_chalk3.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Gemini CLI!"));
1391
+ console.log(import_chalk3.default.gray(" Restart Gemini CLI for changes to take effect."));
1309
1392
  printDaemonTip();
1310
1393
  }
1311
1394
  }
@@ -1324,7 +1407,7 @@ async function setupCursor() {
1324
1407
  if (!hasPreHook) {
1325
1408
  if (!hooksFile.hooks.preToolUse) hooksFile.hooks.preToolUse = [];
1326
1409
  hooksFile.hooks.preToolUse.push({ command: fullPathCommand("check") });
1327
- console.log(import_chalk2.default.green(" \u2705 preToolUse hook added \u2192 node9 check"));
1410
+ console.log(import_chalk3.default.green(" \u2705 preToolUse hook added \u2192 node9 check"));
1328
1411
  anythingChanged = true;
1329
1412
  }
1330
1413
  const hasPostHook = hooksFile.hooks.postToolUse?.some(
@@ -1333,7 +1416,7 @@ async function setupCursor() {
1333
1416
  if (!hasPostHook) {
1334
1417
  if (!hooksFile.hooks.postToolUse) hooksFile.hooks.postToolUse = [];
1335
1418
  hooksFile.hooks.postToolUse.push({ command: fullPathCommand("log") });
1336
- console.log(import_chalk2.default.green(" \u2705 postToolUse hook added \u2192 node9 log"));
1419
+ console.log(import_chalk3.default.green(" \u2705 postToolUse hook added \u2192 node9 log"));
1337
1420
  anythingChanged = true;
1338
1421
  }
1339
1422
  if (anythingChanged) {
@@ -1347,10 +1430,10 @@ async function setupCursor() {
1347
1430
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1348
1431
  }
1349
1432
  if (serversToWrap.length > 0) {
1350
- console.log(import_chalk2.default.bold("The following existing entries will be modified:\n"));
1351
- console.log(import_chalk2.default.white(` ${mcpPath}`));
1433
+ console.log(import_chalk3.default.bold("The following existing entries will be modified:\n"));
1434
+ console.log(import_chalk3.default.white(` ${mcpPath}`));
1352
1435
  for (const { name, originalCmd } of serversToWrap) {
1353
- console.log(import_chalk2.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1436
+ console.log(import_chalk3.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1354
1437
  }
1355
1438
  console.log("");
1356
1439
  const proceed = await (0, import_prompts2.confirm)({ message: "Wrap these MCP servers?", default: true });
@@ -1360,22 +1443,22 @@ async function setupCursor() {
1360
1443
  }
1361
1444
  mcpConfig.mcpServers = servers;
1362
1445
  writeJson(mcpPath, mcpConfig);
1363
- console.log(import_chalk2.default.green(`
1446
+ console.log(import_chalk3.default.green(`
1364
1447
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1365
1448
  anythingChanged = true;
1366
1449
  } else {
1367
- console.log(import_chalk2.default.yellow(" Skipped MCP server wrapping."));
1450
+ console.log(import_chalk3.default.yellow(" Skipped MCP server wrapping."));
1368
1451
  }
1369
1452
  console.log("");
1370
1453
  }
1371
1454
  if (!anythingChanged && serversToWrap.length === 0) {
1372
- console.log(import_chalk2.default.blue("\u2139\uFE0F Node9 is already fully configured for Cursor."));
1455
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Node9 is already fully configured for Cursor."));
1373
1456
  printDaemonTip();
1374
1457
  return;
1375
1458
  }
1376
1459
  if (anythingChanged) {
1377
- console.log(import_chalk2.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Cursor!"));
1378
- console.log(import_chalk2.default.gray(" Restart Cursor for changes to take effect."));
1460
+ console.log(import_chalk3.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Cursor!"));
1461
+ console.log(import_chalk3.default.gray(" Restart Cursor for changes to take effect."));
1379
1462
  printDaemonTip();
1380
1463
  }
1381
1464
  }
@@ -2354,7 +2437,7 @@ var import_path3 = __toESM(require("path"));
2354
2437
  var import_os3 = __toESM(require("os"));
2355
2438
  var import_child_process2 = require("child_process");
2356
2439
  var import_crypto = require("crypto");
2357
- var import_chalk3 = __toESM(require("chalk"));
2440
+ var import_chalk4 = __toESM(require("chalk"));
2358
2441
  var DAEMON_PORT2 = 7391;
2359
2442
  var DAEMON_HOST2 = "127.0.0.1";
2360
2443
  var homeDir = import_os3.default.homedir();
@@ -2800,7 +2883,7 @@ data: ${JSON.stringify(readPersistentDecisions())}
2800
2883
  return;
2801
2884
  }
2802
2885
  }
2803
- console.error(import_chalk3.default.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
2886
+ console.error(import_chalk4.default.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
2804
2887
  process.exit(1);
2805
2888
  });
2806
2889
  server.listen(DAEMON_PORT2, DAEMON_HOST2, () => {
@@ -2809,17 +2892,17 @@ data: ${JSON.stringify(readPersistentDecisions())}
2809
2892
  JSON.stringify({ pid: process.pid, port: DAEMON_PORT2, internalToken, autoStarted }),
2810
2893
  { mode: 384 }
2811
2894
  );
2812
- console.log(import_chalk3.default.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE: http://127.0.0.1:${DAEMON_PORT2}`));
2895
+ console.log(import_chalk4.default.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE: http://127.0.0.1:${DAEMON_PORT2}`));
2813
2896
  });
2814
2897
  }
2815
2898
  function stopDaemon() {
2816
- if (!import_fs3.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
2899
+ if (!import_fs3.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk4.default.yellow("Not running."));
2817
2900
  try {
2818
2901
  const { pid } = JSON.parse(import_fs3.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
2819
2902
  process.kill(pid, "SIGTERM");
2820
- console.log(import_chalk3.default.green("\u2705 Stopped."));
2903
+ console.log(import_chalk4.default.green("\u2705 Stopped."));
2821
2904
  } catch {
2822
- console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
2905
+ console.log(import_chalk4.default.gray("Cleaned up stale PID file."));
2823
2906
  } finally {
2824
2907
  try {
2825
2908
  import_fs3.default.unlinkSync(DAEMON_PID_FILE);
@@ -2829,13 +2912,13 @@ function stopDaemon() {
2829
2912
  }
2830
2913
  function daemonStatus() {
2831
2914
  if (!import_fs3.default.existsSync(DAEMON_PID_FILE))
2832
- return console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
2915
+ return console.log(import_chalk4.default.yellow("Node9 daemon: not running"));
2833
2916
  try {
2834
2917
  const { pid } = JSON.parse(import_fs3.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
2835
2918
  process.kill(pid, 0);
2836
- console.log(import_chalk3.default.green("Node9 daemon: running"));
2919
+ console.log(import_chalk4.default.green("Node9 daemon: running"));
2837
2920
  } catch {
2838
- console.log(import_chalk3.default.yellow("Node9 daemon: not running (stale PID)"));
2921
+ console.log(import_chalk4.default.yellow("Node9 daemon: not running (stale PID)"));
2839
2922
  }
2840
2923
  }
2841
2924
 
@@ -2843,7 +2926,7 @@ function daemonStatus() {
2843
2926
  var import_child_process4 = require("child_process");
2844
2927
  var import_execa = require("execa");
2845
2928
  var import_execa2 = require("execa");
2846
- var import_chalk4 = __toESM(require("chalk"));
2929
+ var import_chalk5 = __toESM(require("chalk"));
2847
2930
  var import_readline = __toESM(require("readline"));
2848
2931
  var import_fs5 = __toESM(require("fs"));
2849
2932
  var import_path5 = __toESM(require("path"));
@@ -2982,7 +3065,7 @@ async function runProxy(targetCommand) {
2982
3065
  if (stdout) executable = stdout.trim();
2983
3066
  } catch {
2984
3067
  }
2985
- console.log(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
3068
+ console.log(import_chalk5.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
2986
3069
  const child = (0, import_child_process4.spawn)(executable, args, {
2987
3070
  stdio: ["pipe", "pipe", "inherit"],
2988
3071
  // We control STDIN and STDOUT
@@ -3083,92 +3166,47 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
3083
3166
  import_fs5.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
3084
3167
  }
3085
3168
  if (options.profile && profileName !== "default") {
3086
- console.log(import_chalk4.default.green(`\u2705 Profile "${profileName}" saved`));
3087
- console.log(import_chalk4.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
3169
+ console.log(import_chalk5.default.green(`\u2705 Profile "${profileName}" saved`));
3170
+ console.log(import_chalk5.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
3088
3171
  } else if (options.local) {
3089
- console.log(import_chalk4.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
3090
- console.log(import_chalk4.default.gray(` All decisions stay on this machine.`));
3172
+ console.log(import_chalk5.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
3173
+ console.log(import_chalk5.default.gray(` All decisions stay on this machine.`));
3091
3174
  } else {
3092
- console.log(import_chalk4.default.green(`\u2705 Logged in \u2014 agent mode`));
3093
- console.log(import_chalk4.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
3175
+ console.log(import_chalk5.default.green(`\u2705 Logged in \u2014 agent mode`));
3176
+ console.log(import_chalk5.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
3094
3177
  }
3095
3178
  });
3096
3179
  program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to protect: claude | gemini | cursor").action(async (target) => {
3097
3180
  if (target === "gemini") return await setupGemini();
3098
3181
  if (target === "claude") return await setupClaude();
3099
3182
  if (target === "cursor") return await setupCursor();
3100
- console.error(import_chalk4.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
3183
+ console.error(import_chalk5.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
3101
3184
  process.exit(1);
3102
3185
  });
3103
- program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").action((options) => {
3186
+ program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").option("-m, --mode <mode>", "Set initial security mode (standard, strict, audit)", "standard").action((options) => {
3104
3187
  const configPath = import_path5.default.join(import_os5.default.homedir(), ".node9", "config.json");
3105
3188
  if (import_fs5.default.existsSync(configPath) && !options.force) {
3106
- console.log(import_chalk4.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
3107
- console.log(import_chalk4.default.gray(` Run with --force to overwrite.`));
3189
+ console.log(import_chalk5.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
3190
+ console.log(import_chalk5.default.gray(` Run with --force to overwrite.`));
3108
3191
  return;
3109
3192
  }
3110
- const defaultConfig = {
3111
- version: "1.0",
3193
+ const requestedMode = options.mode.toLowerCase();
3194
+ const safeMode = ["standard", "strict", "audit"].includes(requestedMode) ? requestedMode : DEFAULT_CONFIG.settings.mode;
3195
+ const configToSave = {
3196
+ ...DEFAULT_CONFIG,
3112
3197
  settings: {
3113
- mode: "standard",
3114
- autoStartDaemon: true,
3115
- enableUndo: true,
3116
- enableHookLogDebug: false,
3117
- approvers: { native: true, browser: true, cloud: true, terminal: true }
3118
- },
3119
- policy: {
3120
- sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
3121
- dangerousWords: DANGEROUS_WORDS,
3122
- ignoredTools: [
3123
- "list_*",
3124
- "get_*",
3125
- "read_*",
3126
- "describe_*",
3127
- "read",
3128
- "write",
3129
- "edit",
3130
- "glob",
3131
- "grep",
3132
- "ls",
3133
- "notebookread",
3134
- "notebookedit",
3135
- "webfetch",
3136
- "websearch",
3137
- "exitplanmode",
3138
- "askuserquestion",
3139
- "agent",
3140
- "task*"
3141
- ],
3142
- toolInspection: {
3143
- bash: "command",
3144
- shell: "command",
3145
- run_shell_command: "command",
3146
- "terminal.execute": "command",
3147
- "postgres:query": "sql"
3148
- },
3149
- rules: [
3150
- {
3151
- action: "rm",
3152
- allowPaths: [
3153
- "**/node_modules/**",
3154
- "dist/**",
3155
- "build/**",
3156
- ".next/**",
3157
- "coverage/**",
3158
- ".cache/**",
3159
- "tmp/**",
3160
- "temp/**",
3161
- ".DS_Store"
3162
- ]
3163
- }
3164
- ]
3198
+ ...DEFAULT_CONFIG.settings,
3199
+ mode: safeMode
3165
3200
  }
3166
3201
  };
3167
- if (!import_fs5.default.existsSync(import_path5.default.dirname(configPath)))
3168
- import_fs5.default.mkdirSync(import_path5.default.dirname(configPath), { recursive: true });
3169
- import_fs5.default.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
3170
- console.log(import_chalk4.default.green(`\u2705 Global config created: ${configPath}`));
3171
- console.log(import_chalk4.default.gray(` Edit this file to add custom tool inspection or security rules.`));
3202
+ const dir = import_path5.default.dirname(configPath);
3203
+ if (!import_fs5.default.existsSync(dir)) import_fs5.default.mkdirSync(dir, { recursive: true });
3204
+ import_fs5.default.writeFileSync(configPath, JSON.stringify(configToSave, null, 2));
3205
+ console.log(import_chalk5.default.green(`\u2705 Global config created: ${configPath}`));
3206
+ console.log(import_chalk5.default.cyan(` Mode set to: ${safeMode}`));
3207
+ console.log(
3208
+ import_chalk5.default.gray(` Undo Engine is ENABLED by default. Use 'node9 undo' to revert AI changes.`)
3209
+ );
3172
3210
  });
3173
3211
  program.command("status").description("Show current Node9 mode, policy source, and persistent decisions").action(() => {
3174
3212
  const creds = getCredentials();
@@ -3177,43 +3215,43 @@ program.command("status").description("Show current Node9 mode, policy source, a
3177
3215
  const settings = mergedConfig.settings;
3178
3216
  console.log("");
3179
3217
  if (creds && settings.approvers.cloud) {
3180
- console.log(import_chalk4.default.green(" \u25CF Agent mode") + import_chalk4.default.gray(" \u2014 cloud team policy enforced"));
3218
+ console.log(import_chalk5.default.green(" \u25CF Agent mode") + import_chalk5.default.gray(" \u2014 cloud team policy enforced"));
3181
3219
  } else if (creds && !settings.approvers.cloud) {
3182
3220
  console.log(
3183
- import_chalk4.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk4.default.gray(" \u2014 all decisions stay on this machine")
3221
+ import_chalk5.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk5.default.gray(" \u2014 all decisions stay on this machine")
3184
3222
  );
3185
3223
  } else {
3186
3224
  console.log(
3187
- import_chalk4.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk4.default.gray(" \u2014 no API key (Local rules only)")
3225
+ import_chalk5.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk5.default.gray(" \u2014 no API key (Local rules only)")
3188
3226
  );
3189
3227
  }
3190
3228
  console.log("");
3191
3229
  if (daemonRunning) {
3192
3230
  console.log(
3193
- import_chalk4.default.green(" \u25CF Daemon running") + import_chalk4.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT2}/`)
3231
+ import_chalk5.default.green(" \u25CF Daemon running") + import_chalk5.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT2}/`)
3194
3232
  );
3195
3233
  } else {
3196
- console.log(import_chalk4.default.gray(" \u25CB Daemon stopped"));
3234
+ console.log(import_chalk5.default.gray(" \u25CB Daemon stopped"));
3197
3235
  }
3198
3236
  if (settings.enableUndo) {
3199
3237
  console.log(
3200
- import_chalk4.default.magenta(" \u25CF Undo Engine") + import_chalk4.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
3238
+ import_chalk5.default.magenta(" \u25CF Undo Engine") + import_chalk5.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
3201
3239
  );
3202
3240
  }
3203
3241
  console.log("");
3204
- const modeLabel = settings.mode === "audit" ? import_chalk4.default.blue("audit") : settings.mode === "strict" ? import_chalk4.default.red("strict") : import_chalk4.default.white("standard");
3242
+ const modeLabel = settings.mode === "audit" ? import_chalk5.default.blue("audit") : settings.mode === "strict" ? import_chalk5.default.red("strict") : import_chalk5.default.white("standard");
3205
3243
  console.log(` Mode: ${modeLabel}`);
3206
3244
  const projectConfig = import_path5.default.join(process.cwd(), "node9.config.json");
3207
3245
  const globalConfig = import_path5.default.join(import_os5.default.homedir(), ".node9", "config.json");
3208
3246
  console.log(
3209
- ` Local: ${import_fs5.default.existsSync(projectConfig) ? import_chalk4.default.green("Active (node9.config.json)") : import_chalk4.default.gray("Not present")}`
3247
+ ` Local: ${import_fs5.default.existsSync(projectConfig) ? import_chalk5.default.green("Active (node9.config.json)") : import_chalk5.default.gray("Not present")}`
3210
3248
  );
3211
3249
  console.log(
3212
- ` Global: ${import_fs5.default.existsSync(globalConfig) ? import_chalk4.default.green("Active (~/.node9/config.json)") : import_chalk4.default.gray("Not present")}`
3250
+ ` Global: ${import_fs5.default.existsSync(globalConfig) ? import_chalk5.default.green("Active (~/.node9/config.json)") : import_chalk5.default.gray("Not present")}`
3213
3251
  );
3214
3252
  if (mergedConfig.policy.sandboxPaths.length > 0) {
3215
3253
  console.log(
3216
- ` Sandbox: ${import_chalk4.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
3254
+ ` Sandbox: ${import_chalk5.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
3217
3255
  );
3218
3256
  }
3219
3257
  const pauseState = checkPause();
@@ -3221,7 +3259,7 @@ program.command("status").description("Show current Node9 mode, policy source, a
3221
3259
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
3222
3260
  console.log("");
3223
3261
  console.log(
3224
- import_chalk4.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk4.default.gray(" \u2014 all tool calls allowed")
3262
+ import_chalk5.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk5.default.gray(" \u2014 all tool calls allowed")
3225
3263
  );
3226
3264
  }
3227
3265
  console.log("");
@@ -3232,13 +3270,13 @@ program.command("daemon").description("Run the local approval server").argument(
3232
3270
  if (cmd === "stop") return stopDaemon();
3233
3271
  if (cmd === "status") return daemonStatus();
3234
3272
  if (cmd !== "start" && action !== void 0) {
3235
- console.error(import_chalk4.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`));
3273
+ console.error(import_chalk5.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`));
3236
3274
  process.exit(1);
3237
3275
  }
3238
3276
  if (options.openui) {
3239
3277
  if (isDaemonRunning()) {
3240
3278
  openBrowserLocal();
3241
- console.log(import_chalk4.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
3279
+ console.log(import_chalk5.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
3242
3280
  process.exit(0);
3243
3281
  }
3244
3282
  const child = (0, import_child_process4.spawn)("node9", ["daemon"], { detached: true, stdio: "ignore" });
@@ -3248,14 +3286,14 @@ program.command("daemon").description("Run the local approval server").argument(
3248
3286
  if (isDaemonRunning()) break;
3249
3287
  }
3250
3288
  openBrowserLocal();
3251
- console.log(import_chalk4.default.green(`
3289
+ console.log(import_chalk5.default.green(`
3252
3290
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
3253
3291
  process.exit(0);
3254
3292
  }
3255
3293
  if (options.background) {
3256
3294
  const child = (0, import_child_process4.spawn)("node9", ["daemon"], { detached: true, stdio: "ignore" });
3257
3295
  child.unref();
3258
- console.log(import_chalk4.default.green(`
3296
+ console.log(import_chalk5.default.green(`
3259
3297
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
3260
3298
  process.exit(0);
3261
3299
  }
@@ -3307,31 +3345,32 @@ RAW: ${raw}
3307
3345
  const sendBlock = (msg, result2) => {
3308
3346
  const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || "Local Security Policy";
3309
3347
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
3310
- console.error(import_chalk4.default.red(`
3348
+ console.error(import_chalk5.default.red(`
3311
3349
  \u{1F6D1} Node9 blocked "${toolName}"`));
3312
- console.error(import_chalk4.default.gray(` Triggered by: ${blockedByContext}`));
3313
- if (result2?.changeHint) console.error(import_chalk4.default.cyan(` To change: ${result2.changeHint}`));
3350
+ console.error(import_chalk5.default.gray(` Triggered by: ${blockedByContext}`));
3351
+ if (result2?.changeHint) console.error(import_chalk5.default.cyan(` To change: ${result2.changeHint}`));
3314
3352
  console.error("");
3315
3353
  let aiFeedbackMessage = "";
3316
3354
  if (isHumanDecision) {
3317
3355
  aiFeedbackMessage = `NODE9 SECURITY INTERVENTION: The human user specifically REJECTED this action.
3318
- REASON: ${msg || "No specific reason provided by user."}
3356
+ REASON: ${msg || "No specific reason provided by user."}
3319
3357
 
3320
- INSTRUCTIONS FOR AI AGENT:
3321
- - Do NOT retry this exact command immediately.
3322
- - Explain to the user that you understand they blocked the action.
3323
- - Ask the user if there is an alternative approach they would prefer, or if they intended to block this action entirely.
3324
- - If you believe this action is critical, explain your reasoning to the user and ask them to run 'node9 pause 15m' to allow you to proceed.`;
3358
+ INSTRUCTIONS FOR AI AGENT:
3359
+ - Do NOT retry this exact command immediately.
3360
+ - Explain to the user that you understand they blocked the action.
3361
+ - Ask the user if there is an alternative approach they would prefer, or if they intended to block this action entirely.
3362
+ - If you believe this action is critical, explain your reasoning to the user and ask them to run 'node9 pause 15m' to allow you to proceed.`;
3325
3363
  } else {
3326
3364
  aiFeedbackMessage = `NODE9 SECURITY INTERVENTION: Action blocked by automated policy [${blockedByContext}].
3327
- REASON: ${msg}
3365
+ REASON: ${msg}
3328
3366
 
3329
- INSTRUCTIONS FOR AI AGENT:
3330
- - This command violates the current security configuration.
3331
- - Do NOT attempt to bypass this rule with bash syntax tricks; it will be blocked again.
3332
- - Pivot to a non-destructive or read-only alternative.
3333
- - Inform the user which security rule was triggered.`;
3367
+ INSTRUCTIONS FOR AI AGENT:
3368
+ - This command violates the current security configuration.
3369
+ - Do NOT attempt to bypass this rule with bash syntax tricks; it will be blocked again.
3370
+ - Pivot to a non-destructive or read-only alternative.
3371
+ - Inform the user which security rule was triggered.`;
3334
3372
  }
3373
+ console.error(import_chalk5.default.dim(` (Detailed instructions sent to AI agent)`));
3335
3374
  process.stdout.write(
3336
3375
  JSON.stringify({
3337
3376
  decision: "block",
@@ -3372,7 +3411,7 @@ RAW: ${raw}
3372
3411
  process.exit(0);
3373
3412
  }
3374
3413
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
3375
- console.error(import_chalk4.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3414
+ console.error(import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3376
3415
  const daemonReady = await autoStartDaemonAndWait();
3377
3416
  if (daemonReady) {
3378
3417
  const retry = await authorizeHeadless(toolName, toolInput, false, meta);
@@ -3480,7 +3519,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
3480
3519
  const ms = parseDuration(options.duration);
3481
3520
  if (ms === null) {
3482
3521
  console.error(
3483
- import_chalk4.default.red(`
3522
+ import_chalk5.default.red(`
3484
3523
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
3485
3524
  `)
3486
3525
  );
@@ -3488,20 +3527,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
3488
3527
  }
3489
3528
  pauseNode9(ms, options.duration);
3490
3529
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
3491
- console.log(import_chalk4.default.yellow(`
3530
+ console.log(import_chalk5.default.yellow(`
3492
3531
  \u23F8 Node9 paused until ${expiresAt}`));
3493
- console.log(import_chalk4.default.gray(` All tool calls will be allowed without review.`));
3494
- console.log(import_chalk4.default.gray(` Run "node9 resume" to re-enable early.
3532
+ console.log(import_chalk5.default.gray(` All tool calls will be allowed without review.`));
3533
+ console.log(import_chalk5.default.gray(` Run "node9 resume" to re-enable early.
3495
3534
  `));
3496
3535
  });
3497
3536
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
3498
3537
  const { paused } = checkPause();
3499
3538
  if (!paused) {
3500
- console.log(import_chalk4.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
3539
+ console.log(import_chalk5.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
3501
3540
  return;
3502
3541
  }
3503
3542
  resumeNode9();
3504
- console.log(import_chalk4.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
3543
+ console.log(import_chalk5.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
3505
3544
  });
3506
3545
  var HOOK_BASED_AGENTS = {
3507
3546
  claude: "claude",
@@ -3514,21 +3553,23 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3514
3553
  if (HOOK_BASED_AGENTS[firstArg] !== void 0) {
3515
3554
  const target = HOOK_BASED_AGENTS[firstArg];
3516
3555
  console.error(
3517
- import_chalk4.default.yellow(`
3556
+ import_chalk5.default.yellow(`
3518
3557
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
3519
3558
  );
3520
- console.error(import_chalk4.default.white(`
3559
+ console.error(import_chalk5.default.white(`
3521
3560
  "${target}" uses its own hook system. Use:`));
3522
3561
  console.error(
3523
- import_chalk4.default.green(` node9 addto ${target} `) + import_chalk4.default.gray("# one-time setup")
3562
+ import_chalk5.default.green(` node9 addto ${target} `) + import_chalk5.default.gray("# one-time setup")
3524
3563
  );
3525
- console.error(import_chalk4.default.green(` ${target} `) + import_chalk4.default.gray("# run normally"));
3564
+ console.error(import_chalk5.default.green(` ${target} `) + import_chalk5.default.gray("# run normally"));
3526
3565
  process.exit(1);
3527
3566
  }
3528
3567
  const fullCommand = commandArgs.join(" ");
3529
- let result = await authorizeHeadless("shell", { command: fullCommand });
3568
+ let result = await authorizeHeadless("shell", { command: fullCommand }, true, {
3569
+ agent: "Terminal"
3570
+ });
3530
3571
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
3531
- console.error(import_chalk4.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3572
+ console.error(import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3532
3573
  const daemonReady = await autoStartDaemonAndWait();
3533
3574
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
3534
3575
  }
@@ -3537,12 +3578,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3537
3578
  }
3538
3579
  if (!result.approved) {
3539
3580
  console.error(
3540
- import_chalk4.default.red(`
3581
+ import_chalk5.default.red(`
3541
3582
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
3542
3583
  );
3543
3584
  process.exit(1);
3544
3585
  }
3545
- console.error(import_chalk4.default.green("\n\u2705 Approved \u2014 running command...\n"));
3586
+ console.error(import_chalk5.default.green("\n\u2705 Approved \u2014 running command...\n"));
3546
3587
  await runProxy(fullCommand);
3547
3588
  } else {
3548
3589
  program.help();
@@ -3551,20 +3592,20 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3551
3592
  program.command("undo").description("Revert the project to the state before the last AI action").action(async () => {
3552
3593
  const hash = getLatestSnapshotHash();
3553
3594
  if (!hash) {
3554
- console.log(import_chalk4.default.yellow("\n\u2139\uFE0F No Undo snapshot found for this machine.\n"));
3595
+ console.log(import_chalk5.default.yellow("\n\u2139\uFE0F No Undo snapshot found for this machine.\n"));
3555
3596
  return;
3556
3597
  }
3557
- console.log(import_chalk4.default.magenta.bold("\n\u23EA NODE9 UNDO ENGINE"));
3558
- console.log(import_chalk4.default.white(`Target Snapshot: ${import_chalk4.default.gray(hash.slice(0, 7))}`));
3598
+ console.log(import_chalk5.default.magenta.bold("\n\u23EA NODE9 UNDO ENGINE"));
3599
+ console.log(import_chalk5.default.white(`Target Snapshot: ${import_chalk5.default.gray(hash.slice(0, 7))}`));
3559
3600
  const proceed = await (0, import_prompts3.confirm)({
3560
3601
  message: "Revert all files to the state before the last AI action?",
3561
3602
  default: false
3562
3603
  });
3563
3604
  if (proceed) {
3564
3605
  if (applyUndo(hash)) {
3565
- console.log(import_chalk4.default.green("\u2705 Project reverted successfully.\n"));
3606
+ console.log(import_chalk5.default.green("\u2705 Project reverted successfully.\n"));
3566
3607
  } else {
3567
- console.error(import_chalk4.default.red("\u274C Undo failed. Ensure you are in a Git repository.\n"));
3608
+ console.error(import_chalk5.default.red("\u274C Undo failed. Ensure you are in a Git repository.\n"));
3568
3609
  }
3569
3610
  }
3570
3611
  });