@node9/proxy 1.0.0 → 1.0.1

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,28 @@ 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)`);
79
- }
80
- return lines.join("\n");
81
- }
82
110
  async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal) {
83
111
  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`;
112
+ const formattedArgs = formatArgs(args);
113
+ const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 Action Approval`;
94
114
  let message = "";
95
- 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}
115
+ if (locked) message += `\u26A0\uFE0F LOCKED BY ADMIN POLICY
102
116
  `;
103
- message += `Agent: ${agent || "AI Agent"}
117
+ message += `Tool: ${toolName}
104
118
  `;
105
- if (explainableLabel) {
106
- message += `Reason: ${explainableLabel}
119
+ message += `Agent: ${agent || "AI Agent"}
107
120
  `;
108
- }
109
- message += `
110
- Arguments:
111
- ${formatArgs(args)}`;
112
- if (!locked) {
113
- message += `
121
+ message += `Rule: ${explainableLabel || "Security Policy"}
114
122
 
115
- Enter = Allow | Click "Block" to deny`;
116
- }
117
- const safeMessage = message.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/`/g, "'");
118
- const safeTitle = title.replace(/"/g, '\\"');
123
+ `;
124
+ message += `${formattedArgs}`;
125
+ process.stderr.write(import_chalk.default.yellow(`
126
+ \u{1F6E1}\uFE0F Node9: Intercepted "${toolName}" \u2014 awaiting user...
127
+ `));
119
128
  return new Promise((resolve) => {
120
129
  let childProcess = null;
121
130
  const onAbort = () => {
122
- if (childProcess) {
131
+ if (childProcess && childProcess.pid) {
123
132
  try {
124
133
  process.kill(childProcess.pid, "SIGKILL");
125
134
  } catch {
@@ -131,83 +140,51 @@ Enter = Allow | Click "Block" to deny`;
131
140
  if (signal.aborted) return resolve("deny");
132
141
  signal.addEventListener("abort", onAbort);
133
142
  }
134
- const cleanup = () => {
135
- if (signal) signal.removeEventListener("abort", onAbort);
136
- };
137
143
  try {
138
144
  if (process.platform === "darwin") {
139
145
  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
- });
146
+ const script = `on run argv
147
+ tell application "System Events"
148
+ activate
149
+ display dialog (item 1 of argv) with title (item 2 of argv) ${buttons}
150
+ end tell
151
+ end run`;
152
+ childProcess = (0, import_child_process.spawn)("osascript", ["-e", script, "--", message, title]);
157
153
  } 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",
154
+ const argsList = [
155
+ locked ? "--info" : "--question",
156
+ "--modal",
157
+ "--width=450",
170
158
  "--title",
171
159
  title,
172
160
  "--text",
173
- safeMessage,
161
+ message,
174
162
  "--ok-label",
175
- "Allow",
176
- "--cancel-label",
177
- "Block",
178
- "--extra-button",
179
- "Always Allow",
163
+ locked ? "Waiting..." : "Allow",
180
164
  "--timeout",
181
165
  "300"
182
166
  ];
167
+ if (!locked) {
168
+ argsList.push("--cancel-label", "Block");
169
+ argsList.push("--extra-button", "Always Allow");
170
+ }
183
171
  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
172
  } 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 }`;
173
+ const b64Msg = Buffer.from(message).toString("base64");
174
+ const b64Title = Buffer.from(title).toString("base64");
175
+ 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
176
  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
177
  }
178
+ let output = "";
179
+ childProcess?.stdout?.on("data", (d) => output += d.toString());
180
+ childProcess?.on("close", (code) => {
181
+ if (signal) signal.removeEventListener("abort", onAbort);
182
+ if (locked) return resolve("deny");
183
+ if (output.includes("Always Allow")) return resolve("always_allow");
184
+ if (code === 0) return resolve("allow");
185
+ resolve("deny");
186
+ });
209
187
  } catch {
210
- cleanup();
211
188
  resolve("deny");
212
189
  }
213
190
  });
@@ -735,8 +712,8 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
735
712
  const isNetworkError = error.message.includes("fetch") || error.name === "AbortError" || error.message.includes("ECONNREFUSED");
736
713
  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
714
  console.error(
738
- import_chalk.default.yellow(`
739
- \u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) + import_chalk.default.dim(`
715
+ import_chalk2.default.yellow(`
716
+ \u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) + import_chalk2.default.dim(`
740
717
  Falling back to local rules...
741
718
  `)
742
719
  );
@@ -744,13 +721,13 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
744
721
  }
745
722
  if (cloudEnforced && cloudRequestId) {
746
723
  console.error(
747
- import_chalk.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
724
+ import_chalk2.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
748
725
  );
749
- console.error(import_chalk.default.cyan(" Dashboard \u2192 ") + import_chalk.default.bold("Mission Control > Activity Feed\n"));
726
+ console.error(import_chalk2.default.cyan(" Dashboard \u2192 ") + import_chalk2.default.bold("Mission Control > Activity Feed\n"));
750
727
  } else if (!cloudEnforced) {
751
728
  const cloudOffReason = !creds?.apiKey ? "no API key \u2014 run `node9 login` to connect" : "privacy mode (cloud disabled)";
752
729
  console.error(
753
- import_chalk.default.dim(`
730
+ import_chalk2.default.dim(`
754
731
  \u{1F6E1}\uFE0F Node9: intercepted "${toolName}" \u2014 cloud off (${cloudOffReason})
755
732
  `)
756
733
  );
@@ -815,9 +792,9 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
815
792
  try {
816
793
  if (!approvers.native && !cloudEnforced) {
817
794
  console.error(
818
- import_chalk.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for browser approval.")
795
+ import_chalk2.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for browser approval.")
819
796
  );
820
- console.error(import_chalk.default.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
797
+ console.error(import_chalk2.default.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
821
798
  `));
822
799
  }
823
800
  const daemonDecision = await askDaemon(toolName, args, meta, signal);
@@ -840,11 +817,11 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
840
817
  racePromises.push(
841
818
  (async () => {
842
819
  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)}`);
820
+ console.log(import_chalk2.default.bgRed.white.bold(` \u{1F6D1} NODE9 INTERCEPTOR `));
821
+ console.log(`${import_chalk2.default.bold("Action:")} ${import_chalk2.default.red(toolName)}`);
822
+ console.log(`${import_chalk2.default.bold("Flagged By:")} ${import_chalk2.default.yellow(explainableLabel)}`);
846
823
  if (isRemoteLocked) {
847
- console.log(import_chalk.default.yellow(`\u26A1 LOCKED BY ADMIN POLICY: Waiting for Slack Approval...
824
+ console.log(import_chalk2.default.yellow(`\u26A1 LOCKED BY ADMIN POLICY: Waiting for Slack Approval...
848
825
  `));
849
826
  await new Promise((_, reject) => {
850
827
  signal.addEventListener("abort", () => reject(new Error("Aborted by SaaS")));
@@ -1091,11 +1068,11 @@ async function pollNode9SaaS(requestId, creds, signal) {
1091
1068
  if (!statusRes.ok) continue;
1092
1069
  const { status, reason } = await statusRes.json();
1093
1070
  if (status === "APPROVED") {
1094
- console.error(import_chalk.default.green("\u2705 Approved via Cloud.\n"));
1071
+ console.error(import_chalk2.default.green("\u2705 Approved via Cloud.\n"));
1095
1072
  return { approved: true, reason };
1096
1073
  }
1097
1074
  if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
1098
- console.error(import_chalk.default.red("\u274C Denied via Cloud.\n"));
1075
+ console.error(import_chalk2.default.red("\u274C Denied via Cloud.\n"));
1099
1076
  return { approved: false, reason };
1100
1077
  }
1101
1078
  } catch {
@@ -1123,11 +1100,11 @@ async function resolveNode9SaaS(requestId, creds, approved) {
1123
1100
  var import_fs2 = __toESM(require("fs"));
1124
1101
  var import_path2 = __toESM(require("path"));
1125
1102
  var import_os2 = __toESM(require("os"));
1126
- var import_chalk2 = __toESM(require("chalk"));
1103
+ var import_chalk3 = __toESM(require("chalk"));
1127
1104
  var import_prompts2 = require("@inquirer/prompts");
1128
1105
  function printDaemonTip() {
1129
1106
  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")
1107
+ 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
1108
  );
1132
1109
  }
1133
1110
  function fullPathCommand(subcommand) {
@@ -1168,7 +1145,7 @@ async function setupClaude() {
1168
1145
  matcher: ".*",
1169
1146
  hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 60 }]
1170
1147
  });
1171
- console.log(import_chalk2.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
1148
+ console.log(import_chalk3.default.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
1172
1149
  anythingChanged = true;
1173
1150
  }
1174
1151
  const hasPostHook = settings.hooks.PostToolUse?.some(
@@ -1180,7 +1157,7 @@ async function setupClaude() {
1180
1157
  matcher: ".*",
1181
1158
  hooks: [{ type: "command", command: fullPathCommand("log"), timeout: 600 }]
1182
1159
  });
1183
- console.log(import_chalk2.default.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
1160
+ console.log(import_chalk3.default.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
1184
1161
  anythingChanged = true;
1185
1162
  }
1186
1163
  if (anythingChanged) {
@@ -1194,10 +1171,10 @@ async function setupClaude() {
1194
1171
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1195
1172
  }
1196
1173
  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}`));
1174
+ console.log(import_chalk3.default.bold("The following existing entries will be modified:\n"));
1175
+ console.log(import_chalk3.default.white(` ${mcpPath}`));
1199
1176
  for (const { name, originalCmd } of serversToWrap) {
1200
- console.log(import_chalk2.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1177
+ console.log(import_chalk3.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1201
1178
  }
1202
1179
  console.log("");
1203
1180
  const proceed = await (0, import_prompts2.confirm)({ message: "Wrap these MCP servers?", default: true });
@@ -1207,22 +1184,22 @@ async function setupClaude() {
1207
1184
  }
1208
1185
  claudeConfig.mcpServers = servers;
1209
1186
  writeJson(mcpPath, claudeConfig);
1210
- console.log(import_chalk2.default.green(`
1187
+ console.log(import_chalk3.default.green(`
1211
1188
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1212
1189
  anythingChanged = true;
1213
1190
  } else {
1214
- console.log(import_chalk2.default.yellow(" Skipped MCP server wrapping."));
1191
+ console.log(import_chalk3.default.yellow(" Skipped MCP server wrapping."));
1215
1192
  }
1216
1193
  console.log("");
1217
1194
  }
1218
1195
  if (!anythingChanged && serversToWrap.length === 0) {
1219
- console.log(import_chalk2.default.blue("\u2139\uFE0F Node9 is already fully configured for Claude Code."));
1196
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Node9 is already fully configured for Claude Code."));
1220
1197
  printDaemonTip();
1221
1198
  return;
1222
1199
  }
1223
1200
  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."));
1201
+ console.log(import_chalk3.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
1202
+ console.log(import_chalk3.default.gray(" Restart Claude Code for changes to take effect."));
1226
1203
  printDaemonTip();
1227
1204
  }
1228
1205
  }
@@ -1250,7 +1227,7 @@ async function setupGemini() {
1250
1227
  }
1251
1228
  ]
1252
1229
  });
1253
- console.log(import_chalk2.default.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
1230
+ console.log(import_chalk3.default.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
1254
1231
  anythingChanged = true;
1255
1232
  }
1256
1233
  const hasAfterHook = Array.isArray(settings.hooks.AfterTool) && settings.hooks.AfterTool.some(
@@ -1263,7 +1240,7 @@ async function setupGemini() {
1263
1240
  matcher: ".*",
1264
1241
  hooks: [{ name: "node9-log", type: "command", command: fullPathCommand("log") }]
1265
1242
  });
1266
- console.log(import_chalk2.default.green(" \u2705 AfterTool hook added \u2192 node9 log"));
1243
+ console.log(import_chalk3.default.green(" \u2705 AfterTool hook added \u2192 node9 log"));
1267
1244
  anythingChanged = true;
1268
1245
  }
1269
1246
  if (anythingChanged) {
@@ -1277,10 +1254,10 @@ async function setupGemini() {
1277
1254
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1278
1255
  }
1279
1256
  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)`));
1257
+ console.log(import_chalk3.default.bold("The following existing entries will be modified:\n"));
1258
+ console.log(import_chalk3.default.white(` ${settingsPath} (mcpServers)`));
1282
1259
  for (const { name, originalCmd } of serversToWrap) {
1283
- console.log(import_chalk2.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1260
+ console.log(import_chalk3.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1284
1261
  }
1285
1262
  console.log("");
1286
1263
  const proceed = await (0, import_prompts2.confirm)({ message: "Wrap these MCP servers?", default: true });
@@ -1290,22 +1267,22 @@ async function setupGemini() {
1290
1267
  }
1291
1268
  settings.mcpServers = servers;
1292
1269
  writeJson(settingsPath, settings);
1293
- console.log(import_chalk2.default.green(`
1270
+ console.log(import_chalk3.default.green(`
1294
1271
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1295
1272
  anythingChanged = true;
1296
1273
  } else {
1297
- console.log(import_chalk2.default.yellow(" Skipped MCP server wrapping."));
1274
+ console.log(import_chalk3.default.yellow(" Skipped MCP server wrapping."));
1298
1275
  }
1299
1276
  console.log("");
1300
1277
  }
1301
1278
  if (!anythingChanged && serversToWrap.length === 0) {
1302
- console.log(import_chalk2.default.blue("\u2139\uFE0F Node9 is already fully configured for Gemini CLI."));
1279
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Node9 is already fully configured for Gemini CLI."));
1303
1280
  printDaemonTip();
1304
1281
  return;
1305
1282
  }
1306
1283
  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."));
1284
+ console.log(import_chalk3.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Gemini CLI!"));
1285
+ console.log(import_chalk3.default.gray(" Restart Gemini CLI for changes to take effect."));
1309
1286
  printDaemonTip();
1310
1287
  }
1311
1288
  }
@@ -1324,7 +1301,7 @@ async function setupCursor() {
1324
1301
  if (!hasPreHook) {
1325
1302
  if (!hooksFile.hooks.preToolUse) hooksFile.hooks.preToolUse = [];
1326
1303
  hooksFile.hooks.preToolUse.push({ command: fullPathCommand("check") });
1327
- console.log(import_chalk2.default.green(" \u2705 preToolUse hook added \u2192 node9 check"));
1304
+ console.log(import_chalk3.default.green(" \u2705 preToolUse hook added \u2192 node9 check"));
1328
1305
  anythingChanged = true;
1329
1306
  }
1330
1307
  const hasPostHook = hooksFile.hooks.postToolUse?.some(
@@ -1333,7 +1310,7 @@ async function setupCursor() {
1333
1310
  if (!hasPostHook) {
1334
1311
  if (!hooksFile.hooks.postToolUse) hooksFile.hooks.postToolUse = [];
1335
1312
  hooksFile.hooks.postToolUse.push({ command: fullPathCommand("log") });
1336
- console.log(import_chalk2.default.green(" \u2705 postToolUse hook added \u2192 node9 log"));
1313
+ console.log(import_chalk3.default.green(" \u2705 postToolUse hook added \u2192 node9 log"));
1337
1314
  anythingChanged = true;
1338
1315
  }
1339
1316
  if (anythingChanged) {
@@ -1347,10 +1324,10 @@ async function setupCursor() {
1347
1324
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1348
1325
  }
1349
1326
  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}`));
1327
+ console.log(import_chalk3.default.bold("The following existing entries will be modified:\n"));
1328
+ console.log(import_chalk3.default.white(` ${mcpPath}`));
1352
1329
  for (const { name, originalCmd } of serversToWrap) {
1353
- console.log(import_chalk2.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1330
+ console.log(import_chalk3.default.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1354
1331
  }
1355
1332
  console.log("");
1356
1333
  const proceed = await (0, import_prompts2.confirm)({ message: "Wrap these MCP servers?", default: true });
@@ -1360,22 +1337,22 @@ async function setupCursor() {
1360
1337
  }
1361
1338
  mcpConfig.mcpServers = servers;
1362
1339
  writeJson(mcpPath, mcpConfig);
1363
- console.log(import_chalk2.default.green(`
1340
+ console.log(import_chalk3.default.green(`
1364
1341
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1365
1342
  anythingChanged = true;
1366
1343
  } else {
1367
- console.log(import_chalk2.default.yellow(" Skipped MCP server wrapping."));
1344
+ console.log(import_chalk3.default.yellow(" Skipped MCP server wrapping."));
1368
1345
  }
1369
1346
  console.log("");
1370
1347
  }
1371
1348
  if (!anythingChanged && serversToWrap.length === 0) {
1372
- console.log(import_chalk2.default.blue("\u2139\uFE0F Node9 is already fully configured for Cursor."));
1349
+ console.log(import_chalk3.default.blue("\u2139\uFE0F Node9 is already fully configured for Cursor."));
1373
1350
  printDaemonTip();
1374
1351
  return;
1375
1352
  }
1376
1353
  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."));
1354
+ console.log(import_chalk3.default.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Cursor!"));
1355
+ console.log(import_chalk3.default.gray(" Restart Cursor for changes to take effect."));
1379
1356
  printDaemonTip();
1380
1357
  }
1381
1358
  }
@@ -2354,7 +2331,7 @@ var import_path3 = __toESM(require("path"));
2354
2331
  var import_os3 = __toESM(require("os"));
2355
2332
  var import_child_process2 = require("child_process");
2356
2333
  var import_crypto = require("crypto");
2357
- var import_chalk3 = __toESM(require("chalk"));
2334
+ var import_chalk4 = __toESM(require("chalk"));
2358
2335
  var DAEMON_PORT2 = 7391;
2359
2336
  var DAEMON_HOST2 = "127.0.0.1";
2360
2337
  var homeDir = import_os3.default.homedir();
@@ -2800,7 +2777,7 @@ data: ${JSON.stringify(readPersistentDecisions())}
2800
2777
  return;
2801
2778
  }
2802
2779
  }
2803
- console.error(import_chalk3.default.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
2780
+ console.error(import_chalk4.default.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
2804
2781
  process.exit(1);
2805
2782
  });
2806
2783
  server.listen(DAEMON_PORT2, DAEMON_HOST2, () => {
@@ -2809,17 +2786,17 @@ data: ${JSON.stringify(readPersistentDecisions())}
2809
2786
  JSON.stringify({ pid: process.pid, port: DAEMON_PORT2, internalToken, autoStarted }),
2810
2787
  { mode: 384 }
2811
2788
  );
2812
- console.log(import_chalk3.default.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE: http://127.0.0.1:${DAEMON_PORT2}`));
2789
+ console.log(import_chalk4.default.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE: http://127.0.0.1:${DAEMON_PORT2}`));
2813
2790
  });
2814
2791
  }
2815
2792
  function stopDaemon() {
2816
- if (!import_fs3.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk3.default.yellow("Not running."));
2793
+ if (!import_fs3.default.existsSync(DAEMON_PID_FILE)) return console.log(import_chalk4.default.yellow("Not running."));
2817
2794
  try {
2818
2795
  const { pid } = JSON.parse(import_fs3.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
2819
2796
  process.kill(pid, "SIGTERM");
2820
- console.log(import_chalk3.default.green("\u2705 Stopped."));
2797
+ console.log(import_chalk4.default.green("\u2705 Stopped."));
2821
2798
  } catch {
2822
- console.log(import_chalk3.default.gray("Cleaned up stale PID file."));
2799
+ console.log(import_chalk4.default.gray("Cleaned up stale PID file."));
2823
2800
  } finally {
2824
2801
  try {
2825
2802
  import_fs3.default.unlinkSync(DAEMON_PID_FILE);
@@ -2829,13 +2806,13 @@ function stopDaemon() {
2829
2806
  }
2830
2807
  function daemonStatus() {
2831
2808
  if (!import_fs3.default.existsSync(DAEMON_PID_FILE))
2832
- return console.log(import_chalk3.default.yellow("Node9 daemon: not running"));
2809
+ return console.log(import_chalk4.default.yellow("Node9 daemon: not running"));
2833
2810
  try {
2834
2811
  const { pid } = JSON.parse(import_fs3.default.readFileSync(DAEMON_PID_FILE, "utf-8"));
2835
2812
  process.kill(pid, 0);
2836
- console.log(import_chalk3.default.green("Node9 daemon: running"));
2813
+ console.log(import_chalk4.default.green("Node9 daemon: running"));
2837
2814
  } catch {
2838
- console.log(import_chalk3.default.yellow("Node9 daemon: not running (stale PID)"));
2815
+ console.log(import_chalk4.default.yellow("Node9 daemon: not running (stale PID)"));
2839
2816
  }
2840
2817
  }
2841
2818
 
@@ -2843,7 +2820,7 @@ function daemonStatus() {
2843
2820
  var import_child_process4 = require("child_process");
2844
2821
  var import_execa = require("execa");
2845
2822
  var import_execa2 = require("execa");
2846
- var import_chalk4 = __toESM(require("chalk"));
2823
+ var import_chalk5 = __toESM(require("chalk"));
2847
2824
  var import_readline = __toESM(require("readline"));
2848
2825
  var import_fs5 = __toESM(require("fs"));
2849
2826
  var import_path5 = __toESM(require("path"));
@@ -2982,7 +2959,7 @@ async function runProxy(targetCommand) {
2982
2959
  if (stdout) executable = stdout.trim();
2983
2960
  } catch {
2984
2961
  }
2985
- console.log(import_chalk4.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
2962
+ console.log(import_chalk5.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
2986
2963
  const child = (0, import_child_process4.spawn)(executable, args, {
2987
2964
  stdio: ["pipe", "pipe", "inherit"],
2988
2965
  // We control STDIN and STDOUT
@@ -3083,28 +3060,28 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
3083
3060
  import_fs5.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
3084
3061
  }
3085
3062
  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`));
3063
+ console.log(import_chalk5.default.green(`\u2705 Profile "${profileName}" saved`));
3064
+ console.log(import_chalk5.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
3088
3065
  } 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.`));
3066
+ console.log(import_chalk5.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
3067
+ console.log(import_chalk5.default.gray(` All decisions stay on this machine.`));
3091
3068
  } 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.`));
3069
+ console.log(import_chalk5.default.green(`\u2705 Logged in \u2014 agent mode`));
3070
+ console.log(import_chalk5.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
3094
3071
  }
3095
3072
  });
3096
3073
  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
3074
  if (target === "gemini") return await setupGemini();
3098
3075
  if (target === "claude") return await setupClaude();
3099
3076
  if (target === "cursor") return await setupCursor();
3100
- console.error(import_chalk4.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
3077
+ console.error(import_chalk5.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
3101
3078
  process.exit(1);
3102
3079
  });
3103
3080
  program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").action((options) => {
3104
3081
  const configPath = import_path5.default.join(import_os5.default.homedir(), ".node9", "config.json");
3105
3082
  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.`));
3083
+ console.log(import_chalk5.default.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
3084
+ console.log(import_chalk5.default.gray(` Run with --force to overwrite.`));
3108
3085
  return;
3109
3086
  }
3110
3087
  const defaultConfig = {
@@ -3167,8 +3144,8 @@ program.command("init").description("Create ~/.node9/config.json with default po
3167
3144
  if (!import_fs5.default.existsSync(import_path5.default.dirname(configPath)))
3168
3145
  import_fs5.default.mkdirSync(import_path5.default.dirname(configPath), { recursive: true });
3169
3146
  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.`));
3147
+ console.log(import_chalk5.default.green(`\u2705 Global config created: ${configPath}`));
3148
+ console.log(import_chalk5.default.gray(` Edit this file to add custom tool inspection or security rules.`));
3172
3149
  });
3173
3150
  program.command("status").description("Show current Node9 mode, policy source, and persistent decisions").action(() => {
3174
3151
  const creds = getCredentials();
@@ -3177,43 +3154,43 @@ program.command("status").description("Show current Node9 mode, policy source, a
3177
3154
  const settings = mergedConfig.settings;
3178
3155
  console.log("");
3179
3156
  if (creds && settings.approvers.cloud) {
3180
- console.log(import_chalk4.default.green(" \u25CF Agent mode") + import_chalk4.default.gray(" \u2014 cloud team policy enforced"));
3157
+ console.log(import_chalk5.default.green(" \u25CF Agent mode") + import_chalk5.default.gray(" \u2014 cloud team policy enforced"));
3181
3158
  } else if (creds && !settings.approvers.cloud) {
3182
3159
  console.log(
3183
- import_chalk4.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk4.default.gray(" \u2014 all decisions stay on this machine")
3160
+ import_chalk5.default.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + import_chalk5.default.gray(" \u2014 all decisions stay on this machine")
3184
3161
  );
3185
3162
  } else {
3186
3163
  console.log(
3187
- import_chalk4.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk4.default.gray(" \u2014 no API key (Local rules only)")
3164
+ import_chalk5.default.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + import_chalk5.default.gray(" \u2014 no API key (Local rules only)")
3188
3165
  );
3189
3166
  }
3190
3167
  console.log("");
3191
3168
  if (daemonRunning) {
3192
3169
  console.log(
3193
- import_chalk4.default.green(" \u25CF Daemon running") + import_chalk4.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT2}/`)
3170
+ import_chalk5.default.green(" \u25CF Daemon running") + import_chalk5.default.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT2}/`)
3194
3171
  );
3195
3172
  } else {
3196
- console.log(import_chalk4.default.gray(" \u25CB Daemon stopped"));
3173
+ console.log(import_chalk5.default.gray(" \u25CB Daemon stopped"));
3197
3174
  }
3198
3175
  if (settings.enableUndo) {
3199
3176
  console.log(
3200
- import_chalk4.default.magenta(" \u25CF Undo Engine") + import_chalk4.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
3177
+ import_chalk5.default.magenta(" \u25CF Undo Engine") + import_chalk5.default.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
3201
3178
  );
3202
3179
  }
3203
3180
  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");
3181
+ const modeLabel = settings.mode === "audit" ? import_chalk5.default.blue("audit") : settings.mode === "strict" ? import_chalk5.default.red("strict") : import_chalk5.default.white("standard");
3205
3182
  console.log(` Mode: ${modeLabel}`);
3206
3183
  const projectConfig = import_path5.default.join(process.cwd(), "node9.config.json");
3207
3184
  const globalConfig = import_path5.default.join(import_os5.default.homedir(), ".node9", "config.json");
3208
3185
  console.log(
3209
- ` Local: ${import_fs5.default.existsSync(projectConfig) ? import_chalk4.default.green("Active (node9.config.json)") : import_chalk4.default.gray("Not present")}`
3186
+ ` Local: ${import_fs5.default.existsSync(projectConfig) ? import_chalk5.default.green("Active (node9.config.json)") : import_chalk5.default.gray("Not present")}`
3210
3187
  );
3211
3188
  console.log(
3212
- ` Global: ${import_fs5.default.existsSync(globalConfig) ? import_chalk4.default.green("Active (~/.node9/config.json)") : import_chalk4.default.gray("Not present")}`
3189
+ ` Global: ${import_fs5.default.existsSync(globalConfig) ? import_chalk5.default.green("Active (~/.node9/config.json)") : import_chalk5.default.gray("Not present")}`
3213
3190
  );
3214
3191
  if (mergedConfig.policy.sandboxPaths.length > 0) {
3215
3192
  console.log(
3216
- ` Sandbox: ${import_chalk4.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
3193
+ ` Sandbox: ${import_chalk5.default.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
3217
3194
  );
3218
3195
  }
3219
3196
  const pauseState = checkPause();
@@ -3221,7 +3198,7 @@ program.command("status").description("Show current Node9 mode, policy source, a
3221
3198
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
3222
3199
  console.log("");
3223
3200
  console.log(
3224
- import_chalk4.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk4.default.gray(" \u2014 all tool calls allowed")
3201
+ import_chalk5.default.yellow(` \u23F8 PAUSED until ${expiresAt}`) + import_chalk5.default.gray(" \u2014 all tool calls allowed")
3225
3202
  );
3226
3203
  }
3227
3204
  console.log("");
@@ -3232,13 +3209,13 @@ program.command("daemon").description("Run the local approval server").argument(
3232
3209
  if (cmd === "stop") return stopDaemon();
3233
3210
  if (cmd === "status") return daemonStatus();
3234
3211
  if (cmd !== "start" && action !== void 0) {
3235
- console.error(import_chalk4.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`));
3212
+ console.error(import_chalk5.default.red(`Unknown daemon action: "${action}". Use: start | stop | status`));
3236
3213
  process.exit(1);
3237
3214
  }
3238
3215
  if (options.openui) {
3239
3216
  if (isDaemonRunning()) {
3240
3217
  openBrowserLocal();
3241
- console.log(import_chalk4.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
3218
+ console.log(import_chalk5.default.green(`\u{1F310} Opened browser: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
3242
3219
  process.exit(0);
3243
3220
  }
3244
3221
  const child = (0, import_child_process4.spawn)("node9", ["daemon"], { detached: true, stdio: "ignore" });
@@ -3248,14 +3225,14 @@ program.command("daemon").description("Run the local approval server").argument(
3248
3225
  if (isDaemonRunning()) break;
3249
3226
  }
3250
3227
  openBrowserLocal();
3251
- console.log(import_chalk4.default.green(`
3228
+ console.log(import_chalk5.default.green(`
3252
3229
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
3253
3230
  process.exit(0);
3254
3231
  }
3255
3232
  if (options.background) {
3256
3233
  const child = (0, import_child_process4.spawn)("node9", ["daemon"], { detached: true, stdio: "ignore" });
3257
3234
  child.unref();
3258
- console.log(import_chalk4.default.green(`
3235
+ console.log(import_chalk5.default.green(`
3259
3236
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
3260
3237
  process.exit(0);
3261
3238
  }
@@ -3307,10 +3284,10 @@ RAW: ${raw}
3307
3284
  const sendBlock = (msg, result2) => {
3308
3285
  const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || "Local Security Policy";
3309
3286
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
3310
- console.error(import_chalk4.default.red(`
3287
+ console.error(import_chalk5.default.red(`
3311
3288
  \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}`));
3289
+ console.error(import_chalk5.default.gray(` Triggered by: ${blockedByContext}`));
3290
+ if (result2?.changeHint) console.error(import_chalk5.default.cyan(` To change: ${result2.changeHint}`));
3314
3291
  console.error("");
3315
3292
  let aiFeedbackMessage = "";
3316
3293
  if (isHumanDecision) {
@@ -3372,7 +3349,7 @@ RAW: ${raw}
3372
3349
  process.exit(0);
3373
3350
  }
3374
3351
  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..."));
3352
+ console.error(import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3376
3353
  const daemonReady = await autoStartDaemonAndWait();
3377
3354
  if (daemonReady) {
3378
3355
  const retry = await authorizeHeadless(toolName, toolInput, false, meta);
@@ -3480,7 +3457,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
3480
3457
  const ms = parseDuration(options.duration);
3481
3458
  if (ms === null) {
3482
3459
  console.error(
3483
- import_chalk4.default.red(`
3460
+ import_chalk5.default.red(`
3484
3461
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
3485
3462
  `)
3486
3463
  );
@@ -3488,20 +3465,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
3488
3465
  }
3489
3466
  pauseNode9(ms, options.duration);
3490
3467
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
3491
- console.log(import_chalk4.default.yellow(`
3468
+ console.log(import_chalk5.default.yellow(`
3492
3469
  \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.
3470
+ console.log(import_chalk5.default.gray(` All tool calls will be allowed without review.`));
3471
+ console.log(import_chalk5.default.gray(` Run "node9 resume" to re-enable early.
3495
3472
  `));
3496
3473
  });
3497
3474
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
3498
3475
  const { paused } = checkPause();
3499
3476
  if (!paused) {
3500
- console.log(import_chalk4.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
3477
+ console.log(import_chalk5.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
3501
3478
  return;
3502
3479
  }
3503
3480
  resumeNode9();
3504
- console.log(import_chalk4.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
3481
+ console.log(import_chalk5.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
3505
3482
  });
3506
3483
  var HOOK_BASED_AGENTS = {
3507
3484
  claude: "claude",
@@ -3514,21 +3491,21 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3514
3491
  if (HOOK_BASED_AGENTS[firstArg] !== void 0) {
3515
3492
  const target = HOOK_BASED_AGENTS[firstArg];
3516
3493
  console.error(
3517
- import_chalk4.default.yellow(`
3494
+ import_chalk5.default.yellow(`
3518
3495
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
3519
3496
  );
3520
- console.error(import_chalk4.default.white(`
3497
+ console.error(import_chalk5.default.white(`
3521
3498
  "${target}" uses its own hook system. Use:`));
3522
3499
  console.error(
3523
- import_chalk4.default.green(` node9 addto ${target} `) + import_chalk4.default.gray("# one-time setup")
3500
+ import_chalk5.default.green(` node9 addto ${target} `) + import_chalk5.default.gray("# one-time setup")
3524
3501
  );
3525
- console.error(import_chalk4.default.green(` ${target} `) + import_chalk4.default.gray("# run normally"));
3502
+ console.error(import_chalk5.default.green(` ${target} `) + import_chalk5.default.gray("# run normally"));
3526
3503
  process.exit(1);
3527
3504
  }
3528
3505
  const fullCommand = commandArgs.join(" ");
3529
3506
  let result = await authorizeHeadless("shell", { command: fullCommand });
3530
3507
  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..."));
3508
+ console.error(import_chalk5.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3532
3509
  const daemonReady = await autoStartDaemonAndWait();
3533
3510
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
3534
3511
  }
@@ -3537,12 +3514,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3537
3514
  }
3538
3515
  if (!result.approved) {
3539
3516
  console.error(
3540
- import_chalk4.default.red(`
3517
+ import_chalk5.default.red(`
3541
3518
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
3542
3519
  );
3543
3520
  process.exit(1);
3544
3521
  }
3545
- console.error(import_chalk4.default.green("\n\u2705 Approved \u2014 running command...\n"));
3522
+ console.error(import_chalk5.default.green("\n\u2705 Approved \u2014 running command...\n"));
3546
3523
  await runProxy(fullCommand);
3547
3524
  } else {
3548
3525
  program.help();
@@ -3551,20 +3528,20 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3551
3528
  program.command("undo").description("Revert the project to the state before the last AI action").action(async () => {
3552
3529
  const hash = getLatestSnapshotHash();
3553
3530
  if (!hash) {
3554
- console.log(import_chalk4.default.yellow("\n\u2139\uFE0F No Undo snapshot found for this machine.\n"));
3531
+ console.log(import_chalk5.default.yellow("\n\u2139\uFE0F No Undo snapshot found for this machine.\n"));
3555
3532
  return;
3556
3533
  }
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))}`));
3534
+ console.log(import_chalk5.default.magenta.bold("\n\u23EA NODE9 UNDO ENGINE"));
3535
+ console.log(import_chalk5.default.white(`Target Snapshot: ${import_chalk5.default.gray(hash.slice(0, 7))}`));
3559
3536
  const proceed = await (0, import_prompts3.confirm)({
3560
3537
  message: "Revert all files to the state before the last AI action?",
3561
3538
  default: false
3562
3539
  });
3563
3540
  if (proceed) {
3564
3541
  if (applyUndo(hash)) {
3565
- console.log(import_chalk4.default.green("\u2705 Project reverted successfully.\n"));
3542
+ console.log(import_chalk5.default.green("\u2705 Project reverted successfully.\n"));
3566
3543
  } else {
3567
- console.error(import_chalk4.default.red("\u274C Undo failed. Ensure you are in a Git repository.\n"));
3544
+ console.error(import_chalk5.default.red("\u274C Undo failed. Ensure you are in a Git repository.\n"));
3568
3545
  }
3569
3546
  }
3570
3547
  });