@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.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/core.ts
7
- import chalk from "chalk";
7
+ import chalk2 from "chalk";
8
8
  import { confirm } from "@inquirer/prompts";
9
9
  import fs from "fs";
10
10
  import path from "path";
@@ -14,19 +14,69 @@ import { parse } from "sh-syntax";
14
14
 
15
15
  // src/ui/native.ts
16
16
  import { spawn } from "child_process";
17
+ import chalk from "chalk";
17
18
  var isTestEnv = () => {
18
19
  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";
19
20
  };
21
+ function smartTruncate(str, maxLen = 500) {
22
+ if (str.length <= maxLen) return str;
23
+ const edge = Math.floor(maxLen / 2) - 3;
24
+ return `${str.slice(0, edge)} ... ${str.slice(-edge)}`;
25
+ }
26
+ function formatArgs(args) {
27
+ if (args === null || args === void 0) return "(none)";
28
+ let parsed = args;
29
+ if (typeof args === "string") {
30
+ const trimmed = args.trim();
31
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
32
+ try {
33
+ parsed = JSON.parse(trimmed);
34
+ } catch {
35
+ parsed = args;
36
+ }
37
+ } else {
38
+ return smartTruncate(args, 600);
39
+ }
40
+ }
41
+ if (typeof parsed === "object" && !Array.isArray(parsed)) {
42
+ const obj = parsed;
43
+ const codeKeys = [
44
+ "command",
45
+ "cmd",
46
+ "shell_command",
47
+ "bash_command",
48
+ "script",
49
+ "code",
50
+ "input",
51
+ "sql",
52
+ "query",
53
+ "arguments",
54
+ "args",
55
+ "param",
56
+ "params",
57
+ "text"
58
+ ];
59
+ const foundKey = Object.keys(obj).find((k) => codeKeys.includes(k.toLowerCase()));
60
+ if (foundKey) {
61
+ const val = obj[foundKey];
62
+ const str = typeof val === "string" ? val : JSON.stringify(val);
63
+ return `[${foundKey.toUpperCase()}]:
64
+ ${smartTruncate(str, 500)}`;
65
+ }
66
+ return Object.entries(obj).slice(0, 5).map(
67
+ ([k, v]) => ` ${k}: ${smartTruncate(typeof v === "string" ? v : JSON.stringify(v), 300)}`
68
+ ).join("\n");
69
+ }
70
+ return smartTruncate(JSON.stringify(parsed), 200);
71
+ }
20
72
  function sendDesktopNotification(title, body) {
21
73
  if (isTestEnv()) return;
22
74
  try {
23
- const safeTitle = title.replace(/"/g, '\\"');
24
- const safeBody = body.replace(/"/g, '\\"');
25
75
  if (process.platform === "darwin") {
26
- const script = `display notification "${safeBody}" with title "${safeTitle}"`;
76
+ const script = `display notification "${body.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"`;
27
77
  spawn("osascript", ["-e", script], { detached: true, stdio: "ignore" }).unref();
28
78
  } else if (process.platform === "linux") {
29
- spawn("notify-send", [safeTitle, safeBody, "--icon=dialog-warning"], {
79
+ spawn("notify-send", [title, body, "--icon=dialog-warning"], {
30
80
  detached: true,
31
81
  stdio: "ignore"
32
82
  }).unref();
@@ -34,69 +84,28 @@ function sendDesktopNotification(title, body) {
34
84
  } catch {
35
85
  }
36
86
  }
37
- function formatArgs(args) {
38
- if (args === null || args === void 0) return "(none)";
39
- if (typeof args !== "object" || Array.isArray(args)) {
40
- const str = typeof args === "string" ? args : JSON.stringify(args);
41
- return str.length > 200 ? str.slice(0, 200) + "\u2026" : str;
42
- }
43
- const entries = Object.entries(args).filter(
44
- ([, v]) => v !== null && v !== void 0 && v !== ""
45
- );
46
- if (entries.length === 0) return "(none)";
47
- const MAX_FIELDS = 5;
48
- const MAX_VALUE_LEN = 120;
49
- const lines = entries.slice(0, MAX_FIELDS).map(([key, val]) => {
50
- const str = typeof val === "string" ? val : JSON.stringify(val);
51
- const truncated = str.length > MAX_VALUE_LEN ? str.slice(0, MAX_VALUE_LEN) + "\u2026" : str;
52
- return ` ${key}: ${truncated}`;
53
- });
54
- if (entries.length > MAX_FIELDS) {
55
- lines.push(` \u2026 and ${entries.length - MAX_FIELDS} more field(s)`);
56
- }
57
- return lines.join("\n");
58
- }
59
87
  async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal) {
60
88
  if (isTestEnv()) return "deny";
61
- if (process.env.NODE9_DEBUG === "1" || process.env.VITEST) {
62
- console.log(`[DEBUG Native] askNativePopup called for: ${toolName}`);
63
- console.log(`[DEBUG Native] isTestEnv check:`, {
64
- VITEST: process.env.VITEST,
65
- NODE_ENV: process.env.NODE_ENV,
66
- CI: process.env.CI,
67
- isTest: isTestEnv()
68
- });
69
- }
70
- const title = locked ? `\u26A1 Node9 \u2014 Locked by Admin Policy` : `\u{1F6E1}\uFE0F Node9 \u2014 Action Requires Approval`;
89
+ const formattedArgs = formatArgs(args);
90
+ const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 Action Approval`;
71
91
  let message = "";
72
- if (locked) {
73
- message += `\u26A1 Awaiting remote approval via Slack. Local override is disabled.
74
- `;
75
- 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
76
- `;
77
- }
78
- message += `Tool: ${toolName}
92
+ if (locked) message += `\u26A0\uFE0F LOCKED BY ADMIN POLICY
79
93
  `;
80
- message += `Agent: ${agent || "AI Agent"}
94
+ message += `Tool: ${toolName}
81
95
  `;
82
- if (explainableLabel) {
83
- message += `Reason: ${explainableLabel}
96
+ message += `Agent: ${agent || "AI Agent"}
84
97
  `;
85
- }
86
- message += `
87
- Arguments:
88
- ${formatArgs(args)}`;
89
- if (!locked) {
90
- message += `
98
+ message += `Rule: ${explainableLabel || "Security Policy"}
91
99
 
92
- Enter = Allow | Click "Block" to deny`;
93
- }
94
- const safeMessage = message.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/`/g, "'");
95
- const safeTitle = title.replace(/"/g, '\\"');
100
+ `;
101
+ message += `${formattedArgs}`;
102
+ process.stderr.write(chalk.yellow(`
103
+ \u{1F6E1}\uFE0F Node9: Intercepted "${toolName}" \u2014 awaiting user...
104
+ `));
96
105
  return new Promise((resolve) => {
97
106
  let childProcess = null;
98
107
  const onAbort = () => {
99
- if (childProcess) {
108
+ if (childProcess && childProcess.pid) {
100
109
  try {
101
110
  process.kill(childProcess.pid, "SIGKILL");
102
111
  } catch {
@@ -108,83 +117,51 @@ Enter = Allow | Click "Block" to deny`;
108
117
  if (signal.aborted) return resolve("deny");
109
118
  signal.addEventListener("abort", onAbort);
110
119
  }
111
- const cleanup = () => {
112
- if (signal) signal.removeEventListener("abort", onAbort);
113
- };
114
120
  try {
115
121
  if (process.platform === "darwin") {
116
122
  const buttons = locked ? `buttons {"Waiting\u2026"} default button "Waiting\u2026"` : `buttons {"Block", "Always Allow", "Allow"} default button "Allow" cancel button "Block"`;
117
- const script = `
118
- tell application "System Events"
119
- activate
120
- display dialog "${safeMessage}" with title "${safeTitle}" ${buttons}
121
- end tell`;
122
- childProcess = spawn("osascript", ["-e", script]);
123
- let output = "";
124
- childProcess.stdout?.on("data", (d) => output += d.toString());
125
- childProcess.on("close", (code) => {
126
- cleanup();
127
- if (locked) return resolve("deny");
128
- if (code === 0) {
129
- if (output.includes("Always Allow")) return resolve("always_allow");
130
- if (output.includes("Allow")) return resolve("allow");
131
- }
132
- resolve("deny");
133
- });
123
+ const script = `on run argv
124
+ tell application "System Events"
125
+ activate
126
+ display dialog (item 1 of argv) with title (item 2 of argv) ${buttons}
127
+ end tell
128
+ end run`;
129
+ childProcess = spawn("osascript", ["-e", script, "--", message, title]);
134
130
  } else if (process.platform === "linux") {
135
- const argsList = locked ? [
136
- "--info",
137
- "--title",
138
- title,
139
- "--text",
140
- safeMessage,
141
- "--ok-label",
142
- "Waiting for Slack\u2026",
143
- "--timeout",
144
- "300"
145
- ] : [
146
- "--question",
131
+ const argsList = [
132
+ locked ? "--info" : "--question",
133
+ "--modal",
134
+ "--width=450",
147
135
  "--title",
148
136
  title,
149
137
  "--text",
150
- safeMessage,
138
+ message,
151
139
  "--ok-label",
152
- "Allow",
153
- "--cancel-label",
154
- "Block",
155
- "--extra-button",
156
- "Always Allow",
140
+ locked ? "Waiting..." : "Allow",
157
141
  "--timeout",
158
142
  "300"
159
143
  ];
144
+ if (!locked) {
145
+ argsList.push("--cancel-label", "Block");
146
+ argsList.push("--extra-button", "Always Allow");
147
+ }
160
148
  childProcess = spawn("zenity", argsList);
161
- let output = "";
162
- childProcess.stdout?.on("data", (d) => output += d.toString());
163
- childProcess.on("close", (code) => {
164
- cleanup();
165
- if (locked) return resolve("deny");
166
- if (output.trim() === "Always Allow") return resolve("always_allow");
167
- if (code === 0) return resolve("allow");
168
- resolve("deny");
169
- });
170
149
  } else if (process.platform === "win32") {
171
- const buttonType = locked ? "OK" : "YesNo";
172
- const ps = `
173
- Add-Type -AssemblyName PresentationFramework;
174
- $res = [System.Windows.MessageBox]::Show("${safeMessage}", "${safeTitle}", "${buttonType}", "Warning", "Button2", "DefaultDesktopOnly");
175
- if ($res -eq "Yes") { exit 0 } else { exit 1 }`;
150
+ const b64Msg = Buffer.from(message).toString("base64");
151
+ const b64Title = Buffer.from(title).toString("base64");
152
+ 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 }`;
176
153
  childProcess = spawn("powershell", ["-Command", ps]);
177
- childProcess.on("close", (code) => {
178
- cleanup();
179
- if (locked) return resolve("deny");
180
- resolve(code === 0 ? "allow" : "deny");
181
- });
182
- } else {
183
- cleanup();
184
- resolve("deny");
185
154
  }
155
+ let output = "";
156
+ childProcess?.stdout?.on("data", (d) => output += d.toString());
157
+ childProcess?.on("close", (code) => {
158
+ if (signal) signal.removeEventListener("abort", onAbort);
159
+ if (locked) return resolve("deny");
160
+ if (output.includes("Always Allow")) return resolve("always_allow");
161
+ if (code === 0) return resolve("allow");
162
+ resolve("deny");
163
+ });
186
164
  } catch {
187
- cleanup();
188
165
  resolve("deny");
189
166
  }
190
167
  });
@@ -712,8 +689,8 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
712
689
  const isNetworkError = error.message.includes("fetch") || error.name === "AbortError" || error.message.includes("ECONNREFUSED");
713
690
  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;
714
691
  console.error(
715
- chalk.yellow(`
716
- \u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) + chalk.dim(`
692
+ chalk2.yellow(`
693
+ \u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) + chalk2.dim(`
717
694
  Falling back to local rules...
718
695
  `)
719
696
  );
@@ -721,13 +698,13 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
721
698
  }
722
699
  if (cloudEnforced && cloudRequestId) {
723
700
  console.error(
724
- chalk.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
701
+ chalk2.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
725
702
  );
726
- console.error(chalk.cyan(" Dashboard \u2192 ") + chalk.bold("Mission Control > Activity Feed\n"));
703
+ console.error(chalk2.cyan(" Dashboard \u2192 ") + chalk2.bold("Mission Control > Activity Feed\n"));
727
704
  } else if (!cloudEnforced) {
728
705
  const cloudOffReason = !creds?.apiKey ? "no API key \u2014 run `node9 login` to connect" : "privacy mode (cloud disabled)";
729
706
  console.error(
730
- chalk.dim(`
707
+ chalk2.dim(`
731
708
  \u{1F6E1}\uFE0F Node9: intercepted "${toolName}" \u2014 cloud off (${cloudOffReason})
732
709
  `)
733
710
  );
@@ -792,9 +769,9 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
792
769
  try {
793
770
  if (!approvers.native && !cloudEnforced) {
794
771
  console.error(
795
- chalk.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for browser approval.")
772
+ chalk2.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for browser approval.")
796
773
  );
797
- console.error(chalk.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
774
+ console.error(chalk2.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
798
775
  `));
799
776
  }
800
777
  const daemonDecision = await askDaemon(toolName, args, meta, signal);
@@ -817,11 +794,11 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
817
794
  racePromises.push(
818
795
  (async () => {
819
796
  try {
820
- console.log(chalk.bgRed.white.bold(` \u{1F6D1} NODE9 INTERCEPTOR `));
821
- console.log(`${chalk.bold("Action:")} ${chalk.red(toolName)}`);
822
- console.log(`${chalk.bold("Flagged By:")} ${chalk.yellow(explainableLabel)}`);
797
+ console.log(chalk2.bgRed.white.bold(` \u{1F6D1} NODE9 INTERCEPTOR `));
798
+ console.log(`${chalk2.bold("Action:")} ${chalk2.red(toolName)}`);
799
+ console.log(`${chalk2.bold("Flagged By:")} ${chalk2.yellow(explainableLabel)}`);
823
800
  if (isRemoteLocked) {
824
- console.log(chalk.yellow(`\u26A1 LOCKED BY ADMIN POLICY: Waiting for Slack Approval...
801
+ console.log(chalk2.yellow(`\u26A1 LOCKED BY ADMIN POLICY: Waiting for Slack Approval...
825
802
  `));
826
803
  await new Promise((_, reject) => {
827
804
  signal.addEventListener("abort", () => reject(new Error("Aborted by SaaS")));
@@ -1068,11 +1045,11 @@ async function pollNode9SaaS(requestId, creds, signal) {
1068
1045
  if (!statusRes.ok) continue;
1069
1046
  const { status, reason } = await statusRes.json();
1070
1047
  if (status === "APPROVED") {
1071
- console.error(chalk.green("\u2705 Approved via Cloud.\n"));
1048
+ console.error(chalk2.green("\u2705 Approved via Cloud.\n"));
1072
1049
  return { approved: true, reason };
1073
1050
  }
1074
1051
  if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
1075
- console.error(chalk.red("\u274C Denied via Cloud.\n"));
1052
+ console.error(chalk2.red("\u274C Denied via Cloud.\n"));
1076
1053
  return { approved: false, reason };
1077
1054
  }
1078
1055
  } catch {
@@ -1100,11 +1077,11 @@ async function resolveNode9SaaS(requestId, creds, approved) {
1100
1077
  import fs2 from "fs";
1101
1078
  import path2 from "path";
1102
1079
  import os2 from "os";
1103
- import chalk2 from "chalk";
1080
+ import chalk3 from "chalk";
1104
1081
  import { confirm as confirm2 } from "@inquirer/prompts";
1105
1082
  function printDaemonTip() {
1106
1083
  console.log(
1107
- chalk2.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + chalk2.white("\n To view your history or manage persistent rules, run:") + chalk2.green("\n node9 daemon --openui")
1084
+ chalk3.cyan("\n \u{1F4A1} Node9 will protect you automatically using Native OS popups.") + chalk3.white("\n To view your history or manage persistent rules, run:") + chalk3.green("\n node9 daemon --openui")
1108
1085
  );
1109
1086
  }
1110
1087
  function fullPathCommand(subcommand) {
@@ -1145,7 +1122,7 @@ async function setupClaude() {
1145
1122
  matcher: ".*",
1146
1123
  hooks: [{ type: "command", command: fullPathCommand("check"), timeout: 60 }]
1147
1124
  });
1148
- console.log(chalk2.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
1125
+ console.log(chalk3.green(" \u2705 PreToolUse hook added \u2192 node9 check"));
1149
1126
  anythingChanged = true;
1150
1127
  }
1151
1128
  const hasPostHook = settings.hooks.PostToolUse?.some(
@@ -1157,7 +1134,7 @@ async function setupClaude() {
1157
1134
  matcher: ".*",
1158
1135
  hooks: [{ type: "command", command: fullPathCommand("log"), timeout: 600 }]
1159
1136
  });
1160
- console.log(chalk2.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
1137
+ console.log(chalk3.green(" \u2705 PostToolUse hook added \u2192 node9 log"));
1161
1138
  anythingChanged = true;
1162
1139
  }
1163
1140
  if (anythingChanged) {
@@ -1171,10 +1148,10 @@ async function setupClaude() {
1171
1148
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1172
1149
  }
1173
1150
  if (serversToWrap.length > 0) {
1174
- console.log(chalk2.bold("The following existing entries will be modified:\n"));
1175
- console.log(chalk2.white(` ${mcpPath}`));
1151
+ console.log(chalk3.bold("The following existing entries will be modified:\n"));
1152
+ console.log(chalk3.white(` ${mcpPath}`));
1176
1153
  for (const { name, originalCmd } of serversToWrap) {
1177
- console.log(chalk2.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1154
+ console.log(chalk3.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1178
1155
  }
1179
1156
  console.log("");
1180
1157
  const proceed = await confirm2({ message: "Wrap these MCP servers?", default: true });
@@ -1184,22 +1161,22 @@ async function setupClaude() {
1184
1161
  }
1185
1162
  claudeConfig.mcpServers = servers;
1186
1163
  writeJson(mcpPath, claudeConfig);
1187
- console.log(chalk2.green(`
1164
+ console.log(chalk3.green(`
1188
1165
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1189
1166
  anythingChanged = true;
1190
1167
  } else {
1191
- console.log(chalk2.yellow(" Skipped MCP server wrapping."));
1168
+ console.log(chalk3.yellow(" Skipped MCP server wrapping."));
1192
1169
  }
1193
1170
  console.log("");
1194
1171
  }
1195
1172
  if (!anythingChanged && serversToWrap.length === 0) {
1196
- console.log(chalk2.blue("\u2139\uFE0F Node9 is already fully configured for Claude Code."));
1173
+ console.log(chalk3.blue("\u2139\uFE0F Node9 is already fully configured for Claude Code."));
1197
1174
  printDaemonTip();
1198
1175
  return;
1199
1176
  }
1200
1177
  if (anythingChanged) {
1201
- console.log(chalk2.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
1202
- console.log(chalk2.gray(" Restart Claude Code for changes to take effect."));
1178
+ console.log(chalk3.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Claude Code!"));
1179
+ console.log(chalk3.gray(" Restart Claude Code for changes to take effect."));
1203
1180
  printDaemonTip();
1204
1181
  }
1205
1182
  }
@@ -1227,7 +1204,7 @@ async function setupGemini() {
1227
1204
  }
1228
1205
  ]
1229
1206
  });
1230
- console.log(chalk2.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
1207
+ console.log(chalk3.green(" \u2705 BeforeTool hook added \u2192 node9 check"));
1231
1208
  anythingChanged = true;
1232
1209
  }
1233
1210
  const hasAfterHook = Array.isArray(settings.hooks.AfterTool) && settings.hooks.AfterTool.some(
@@ -1240,7 +1217,7 @@ async function setupGemini() {
1240
1217
  matcher: ".*",
1241
1218
  hooks: [{ name: "node9-log", type: "command", command: fullPathCommand("log") }]
1242
1219
  });
1243
- console.log(chalk2.green(" \u2705 AfterTool hook added \u2192 node9 log"));
1220
+ console.log(chalk3.green(" \u2705 AfterTool hook added \u2192 node9 log"));
1244
1221
  anythingChanged = true;
1245
1222
  }
1246
1223
  if (anythingChanged) {
@@ -1254,10 +1231,10 @@ async function setupGemini() {
1254
1231
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1255
1232
  }
1256
1233
  if (serversToWrap.length > 0) {
1257
- console.log(chalk2.bold("The following existing entries will be modified:\n"));
1258
- console.log(chalk2.white(` ${settingsPath} (mcpServers)`));
1234
+ console.log(chalk3.bold("The following existing entries will be modified:\n"));
1235
+ console.log(chalk3.white(` ${settingsPath} (mcpServers)`));
1259
1236
  for (const { name, originalCmd } of serversToWrap) {
1260
- console.log(chalk2.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1237
+ console.log(chalk3.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1261
1238
  }
1262
1239
  console.log("");
1263
1240
  const proceed = await confirm2({ message: "Wrap these MCP servers?", default: true });
@@ -1267,22 +1244,22 @@ async function setupGemini() {
1267
1244
  }
1268
1245
  settings.mcpServers = servers;
1269
1246
  writeJson(settingsPath, settings);
1270
- console.log(chalk2.green(`
1247
+ console.log(chalk3.green(`
1271
1248
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1272
1249
  anythingChanged = true;
1273
1250
  } else {
1274
- console.log(chalk2.yellow(" Skipped MCP server wrapping."));
1251
+ console.log(chalk3.yellow(" Skipped MCP server wrapping."));
1275
1252
  }
1276
1253
  console.log("");
1277
1254
  }
1278
1255
  if (!anythingChanged && serversToWrap.length === 0) {
1279
- console.log(chalk2.blue("\u2139\uFE0F Node9 is already fully configured for Gemini CLI."));
1256
+ console.log(chalk3.blue("\u2139\uFE0F Node9 is already fully configured for Gemini CLI."));
1280
1257
  printDaemonTip();
1281
1258
  return;
1282
1259
  }
1283
1260
  if (anythingChanged) {
1284
- console.log(chalk2.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Gemini CLI!"));
1285
- console.log(chalk2.gray(" Restart Gemini CLI for changes to take effect."));
1261
+ console.log(chalk3.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Gemini CLI!"));
1262
+ console.log(chalk3.gray(" Restart Gemini CLI for changes to take effect."));
1286
1263
  printDaemonTip();
1287
1264
  }
1288
1265
  }
@@ -1301,7 +1278,7 @@ async function setupCursor() {
1301
1278
  if (!hasPreHook) {
1302
1279
  if (!hooksFile.hooks.preToolUse) hooksFile.hooks.preToolUse = [];
1303
1280
  hooksFile.hooks.preToolUse.push({ command: fullPathCommand("check") });
1304
- console.log(chalk2.green(" \u2705 preToolUse hook added \u2192 node9 check"));
1281
+ console.log(chalk3.green(" \u2705 preToolUse hook added \u2192 node9 check"));
1305
1282
  anythingChanged = true;
1306
1283
  }
1307
1284
  const hasPostHook = hooksFile.hooks.postToolUse?.some(
@@ -1310,7 +1287,7 @@ async function setupCursor() {
1310
1287
  if (!hasPostHook) {
1311
1288
  if (!hooksFile.hooks.postToolUse) hooksFile.hooks.postToolUse = [];
1312
1289
  hooksFile.hooks.postToolUse.push({ command: fullPathCommand("log") });
1313
- console.log(chalk2.green(" \u2705 postToolUse hook added \u2192 node9 log"));
1290
+ console.log(chalk3.green(" \u2705 postToolUse hook added \u2192 node9 log"));
1314
1291
  anythingChanged = true;
1315
1292
  }
1316
1293
  if (anythingChanged) {
@@ -1324,10 +1301,10 @@ async function setupCursor() {
1324
1301
  serversToWrap.push({ name, originalCmd: parts.join(" "), parts });
1325
1302
  }
1326
1303
  if (serversToWrap.length > 0) {
1327
- console.log(chalk2.bold("The following existing entries will be modified:\n"));
1328
- console.log(chalk2.white(` ${mcpPath}`));
1304
+ console.log(chalk3.bold("The following existing entries will be modified:\n"));
1305
+ console.log(chalk3.white(` ${mcpPath}`));
1329
1306
  for (const { name, originalCmd } of serversToWrap) {
1330
- console.log(chalk2.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1307
+ console.log(chalk3.gray(` \u2022 ${name}: "${originalCmd}" \u2192 node9 ${originalCmd}`));
1331
1308
  }
1332
1309
  console.log("");
1333
1310
  const proceed = await confirm2({ message: "Wrap these MCP servers?", default: true });
@@ -1337,22 +1314,22 @@ async function setupCursor() {
1337
1314
  }
1338
1315
  mcpConfig.mcpServers = servers;
1339
1316
  writeJson(mcpPath, mcpConfig);
1340
- console.log(chalk2.green(`
1317
+ console.log(chalk3.green(`
1341
1318
  \u2705 ${serversToWrap.length} MCP server(s) wrapped`));
1342
1319
  anythingChanged = true;
1343
1320
  } else {
1344
- console.log(chalk2.yellow(" Skipped MCP server wrapping."));
1321
+ console.log(chalk3.yellow(" Skipped MCP server wrapping."));
1345
1322
  }
1346
1323
  console.log("");
1347
1324
  }
1348
1325
  if (!anythingChanged && serversToWrap.length === 0) {
1349
- console.log(chalk2.blue("\u2139\uFE0F Node9 is already fully configured for Cursor."));
1326
+ console.log(chalk3.blue("\u2139\uFE0F Node9 is already fully configured for Cursor."));
1350
1327
  printDaemonTip();
1351
1328
  return;
1352
1329
  }
1353
1330
  if (anythingChanged) {
1354
- console.log(chalk2.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Cursor!"));
1355
- console.log(chalk2.gray(" Restart Cursor for changes to take effect."));
1331
+ console.log(chalk3.green.bold("\u{1F6E1}\uFE0F Node9 is now protecting Cursor!"));
1332
+ console.log(chalk3.gray(" Restart Cursor for changes to take effect."));
1356
1333
  printDaemonTip();
1357
1334
  }
1358
1335
  }
@@ -2331,7 +2308,7 @@ import path3 from "path";
2331
2308
  import os3 from "os";
2332
2309
  import { spawn as spawn2 } from "child_process";
2333
2310
  import { randomUUID } from "crypto";
2334
- import chalk3 from "chalk";
2311
+ import chalk4 from "chalk";
2335
2312
  var DAEMON_PORT2 = 7391;
2336
2313
  var DAEMON_HOST2 = "127.0.0.1";
2337
2314
  var homeDir = os3.homedir();
@@ -2777,7 +2754,7 @@ data: ${JSON.stringify(readPersistentDecisions())}
2777
2754
  return;
2778
2755
  }
2779
2756
  }
2780
- console.error(chalk3.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
2757
+ console.error(chalk4.red("\n\u{1F6D1} Node9 Daemon Error:"), e.message);
2781
2758
  process.exit(1);
2782
2759
  });
2783
2760
  server.listen(DAEMON_PORT2, DAEMON_HOST2, () => {
@@ -2786,17 +2763,17 @@ data: ${JSON.stringify(readPersistentDecisions())}
2786
2763
  JSON.stringify({ pid: process.pid, port: DAEMON_PORT2, internalToken, autoStarted }),
2787
2764
  { mode: 384 }
2788
2765
  );
2789
- console.log(chalk3.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE: http://127.0.0.1:${DAEMON_PORT2}`));
2766
+ console.log(chalk4.green(`\u{1F6E1}\uFE0F Node9 Guard LIVE: http://127.0.0.1:${DAEMON_PORT2}`));
2790
2767
  });
2791
2768
  }
2792
2769
  function stopDaemon() {
2793
- if (!fs3.existsSync(DAEMON_PID_FILE)) return console.log(chalk3.yellow("Not running."));
2770
+ if (!fs3.existsSync(DAEMON_PID_FILE)) return console.log(chalk4.yellow("Not running."));
2794
2771
  try {
2795
2772
  const { pid } = JSON.parse(fs3.readFileSync(DAEMON_PID_FILE, "utf-8"));
2796
2773
  process.kill(pid, "SIGTERM");
2797
- console.log(chalk3.green("\u2705 Stopped."));
2774
+ console.log(chalk4.green("\u2705 Stopped."));
2798
2775
  } catch {
2799
- console.log(chalk3.gray("Cleaned up stale PID file."));
2776
+ console.log(chalk4.gray("Cleaned up stale PID file."));
2800
2777
  } finally {
2801
2778
  try {
2802
2779
  fs3.unlinkSync(DAEMON_PID_FILE);
@@ -2806,13 +2783,13 @@ function stopDaemon() {
2806
2783
  }
2807
2784
  function daemonStatus() {
2808
2785
  if (!fs3.existsSync(DAEMON_PID_FILE))
2809
- return console.log(chalk3.yellow("Node9 daemon: not running"));
2786
+ return console.log(chalk4.yellow("Node9 daemon: not running"));
2810
2787
  try {
2811
2788
  const { pid } = JSON.parse(fs3.readFileSync(DAEMON_PID_FILE, "utf-8"));
2812
2789
  process.kill(pid, 0);
2813
- console.log(chalk3.green("Node9 daemon: running"));
2790
+ console.log(chalk4.green("Node9 daemon: running"));
2814
2791
  } catch {
2815
- console.log(chalk3.yellow("Node9 daemon: not running (stale PID)"));
2792
+ console.log(chalk4.yellow("Node9 daemon: not running (stale PID)"));
2816
2793
  }
2817
2794
  }
2818
2795
 
@@ -2820,7 +2797,7 @@ function daemonStatus() {
2820
2797
  import { spawn as spawn3, execSync } from "child_process";
2821
2798
  import { parseCommandString } from "execa";
2822
2799
  import { execa } from "execa";
2823
- import chalk4 from "chalk";
2800
+ import chalk5 from "chalk";
2824
2801
  import readline from "readline";
2825
2802
  import fs5 from "fs";
2826
2803
  import path5 from "path";
@@ -2959,7 +2936,7 @@ async function runProxy(targetCommand) {
2959
2936
  if (stdout) executable = stdout.trim();
2960
2937
  } catch {
2961
2938
  }
2962
- console.log(chalk4.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
2939
+ console.log(chalk5.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
2963
2940
  const child = spawn3(executable, args, {
2964
2941
  stdio: ["pipe", "pipe", "inherit"],
2965
2942
  // We control STDIN and STDOUT
@@ -3060,28 +3037,28 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
3060
3037
  fs5.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
3061
3038
  }
3062
3039
  if (options.profile && profileName !== "default") {
3063
- console.log(chalk4.green(`\u2705 Profile "${profileName}" saved`));
3064
- console.log(chalk4.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
3040
+ console.log(chalk5.green(`\u2705 Profile "${profileName}" saved`));
3041
+ console.log(chalk5.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
3065
3042
  } else if (options.local) {
3066
- console.log(chalk4.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
3067
- console.log(chalk4.gray(` All decisions stay on this machine.`));
3043
+ console.log(chalk5.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
3044
+ console.log(chalk5.gray(` All decisions stay on this machine.`));
3068
3045
  } else {
3069
- console.log(chalk4.green(`\u2705 Logged in \u2014 agent mode`));
3070
- console.log(chalk4.gray(` Team policy enforced for all calls via Node9 cloud.`));
3046
+ console.log(chalk5.green(`\u2705 Logged in \u2014 agent mode`));
3047
+ console.log(chalk5.gray(` Team policy enforced for all calls via Node9 cloud.`));
3071
3048
  }
3072
3049
  });
3073
3050
  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) => {
3074
3051
  if (target === "gemini") return await setupGemini();
3075
3052
  if (target === "claude") return await setupClaude();
3076
3053
  if (target === "cursor") return await setupCursor();
3077
- console.error(chalk4.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
3054
+ console.error(chalk5.red(`Unknown target: "${target}". Supported: claude, gemini, cursor`));
3078
3055
  process.exit(1);
3079
3056
  });
3080
3057
  program.command("init").description("Create ~/.node9/config.json with default policy (safe to run multiple times)").option("--force", "Overwrite existing config").action((options) => {
3081
3058
  const configPath = path5.join(os5.homedir(), ".node9", "config.json");
3082
3059
  if (fs5.existsSync(configPath) && !options.force) {
3083
- console.log(chalk4.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
3084
- console.log(chalk4.gray(` Run with --force to overwrite.`));
3060
+ console.log(chalk5.yellow(`\u2139\uFE0F Global config already exists: ${configPath}`));
3061
+ console.log(chalk5.gray(` Run with --force to overwrite.`));
3085
3062
  return;
3086
3063
  }
3087
3064
  const defaultConfig = {
@@ -3144,8 +3121,8 @@ program.command("init").description("Create ~/.node9/config.json with default po
3144
3121
  if (!fs5.existsSync(path5.dirname(configPath)))
3145
3122
  fs5.mkdirSync(path5.dirname(configPath), { recursive: true });
3146
3123
  fs5.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
3147
- console.log(chalk4.green(`\u2705 Global config created: ${configPath}`));
3148
- console.log(chalk4.gray(` Edit this file to add custom tool inspection or security rules.`));
3124
+ console.log(chalk5.green(`\u2705 Global config created: ${configPath}`));
3125
+ console.log(chalk5.gray(` Edit this file to add custom tool inspection or security rules.`));
3149
3126
  });
3150
3127
  program.command("status").description("Show current Node9 mode, policy source, and persistent decisions").action(() => {
3151
3128
  const creds = getCredentials();
@@ -3154,43 +3131,43 @@ program.command("status").description("Show current Node9 mode, policy source, a
3154
3131
  const settings = mergedConfig.settings;
3155
3132
  console.log("");
3156
3133
  if (creds && settings.approvers.cloud) {
3157
- console.log(chalk4.green(" \u25CF Agent mode") + chalk4.gray(" \u2014 cloud team policy enforced"));
3134
+ console.log(chalk5.green(" \u25CF Agent mode") + chalk5.gray(" \u2014 cloud team policy enforced"));
3158
3135
  } else if (creds && !settings.approvers.cloud) {
3159
3136
  console.log(
3160
- chalk4.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + chalk4.gray(" \u2014 all decisions stay on this machine")
3137
+ chalk5.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + chalk5.gray(" \u2014 all decisions stay on this machine")
3161
3138
  );
3162
3139
  } else {
3163
3140
  console.log(
3164
- chalk4.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + chalk4.gray(" \u2014 no API key (Local rules only)")
3141
+ chalk5.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + chalk5.gray(" \u2014 no API key (Local rules only)")
3165
3142
  );
3166
3143
  }
3167
3144
  console.log("");
3168
3145
  if (daemonRunning) {
3169
3146
  console.log(
3170
- chalk4.green(" \u25CF Daemon running") + chalk4.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT2}/`)
3147
+ chalk5.green(" \u25CF Daemon running") + chalk5.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT2}/`)
3171
3148
  );
3172
3149
  } else {
3173
- console.log(chalk4.gray(" \u25CB Daemon stopped"));
3150
+ console.log(chalk5.gray(" \u25CB Daemon stopped"));
3174
3151
  }
3175
3152
  if (settings.enableUndo) {
3176
3153
  console.log(
3177
- chalk4.magenta(" \u25CF Undo Engine") + chalk4.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
3154
+ chalk5.magenta(" \u25CF Undo Engine") + chalk5.gray(` \u2192 Auto-snapshotting Git repos on AI change`)
3178
3155
  );
3179
3156
  }
3180
3157
  console.log("");
3181
- const modeLabel = settings.mode === "audit" ? chalk4.blue("audit") : settings.mode === "strict" ? chalk4.red("strict") : chalk4.white("standard");
3158
+ const modeLabel = settings.mode === "audit" ? chalk5.blue("audit") : settings.mode === "strict" ? chalk5.red("strict") : chalk5.white("standard");
3182
3159
  console.log(` Mode: ${modeLabel}`);
3183
3160
  const projectConfig = path5.join(process.cwd(), "node9.config.json");
3184
3161
  const globalConfig = path5.join(os5.homedir(), ".node9", "config.json");
3185
3162
  console.log(
3186
- ` Local: ${fs5.existsSync(projectConfig) ? chalk4.green("Active (node9.config.json)") : chalk4.gray("Not present")}`
3163
+ ` Local: ${fs5.existsSync(projectConfig) ? chalk5.green("Active (node9.config.json)") : chalk5.gray("Not present")}`
3187
3164
  );
3188
3165
  console.log(
3189
- ` Global: ${fs5.existsSync(globalConfig) ? chalk4.green("Active (~/.node9/config.json)") : chalk4.gray("Not present")}`
3166
+ ` Global: ${fs5.existsSync(globalConfig) ? chalk5.green("Active (~/.node9/config.json)") : chalk5.gray("Not present")}`
3190
3167
  );
3191
3168
  if (mergedConfig.policy.sandboxPaths.length > 0) {
3192
3169
  console.log(
3193
- ` Sandbox: ${chalk4.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
3170
+ ` Sandbox: ${chalk5.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
3194
3171
  );
3195
3172
  }
3196
3173
  const pauseState = checkPause();
@@ -3198,7 +3175,7 @@ program.command("status").description("Show current Node9 mode, policy source, a
3198
3175
  const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
3199
3176
  console.log("");
3200
3177
  console.log(
3201
- chalk4.yellow(` \u23F8 PAUSED until ${expiresAt}`) + chalk4.gray(" \u2014 all tool calls allowed")
3178
+ chalk5.yellow(` \u23F8 PAUSED until ${expiresAt}`) + chalk5.gray(" \u2014 all tool calls allowed")
3202
3179
  );
3203
3180
  }
3204
3181
  console.log("");
@@ -3209,13 +3186,13 @@ program.command("daemon").description("Run the local approval server").argument(
3209
3186
  if (cmd === "stop") return stopDaemon();
3210
3187
  if (cmd === "status") return daemonStatus();
3211
3188
  if (cmd !== "start" && action !== void 0) {
3212
- console.error(chalk4.red(`Unknown daemon action: "${action}". Use: start | stop | status`));
3189
+ console.error(chalk5.red(`Unknown daemon action: "${action}". Use: start | stop | status`));
3213
3190
  process.exit(1);
3214
3191
  }
3215
3192
  if (options.openui) {
3216
3193
  if (isDaemonRunning()) {
3217
3194
  openBrowserLocal();
3218
- console.log(chalk4.green(`\u{1F310} Opened browser: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
3195
+ console.log(chalk5.green(`\u{1F310} Opened browser: http://${DAEMON_HOST2}:${DAEMON_PORT2}/`));
3219
3196
  process.exit(0);
3220
3197
  }
3221
3198
  const child = spawn3("node9", ["daemon"], { detached: true, stdio: "ignore" });
@@ -3225,14 +3202,14 @@ program.command("daemon").description("Run the local approval server").argument(
3225
3202
  if (isDaemonRunning()) break;
3226
3203
  }
3227
3204
  openBrowserLocal();
3228
- console.log(chalk4.green(`
3205
+ console.log(chalk5.green(`
3229
3206
  \u{1F6E1}\uFE0F Node9 daemon started + browser opened`));
3230
3207
  process.exit(0);
3231
3208
  }
3232
3209
  if (options.background) {
3233
3210
  const child = spawn3("node9", ["daemon"], { detached: true, stdio: "ignore" });
3234
3211
  child.unref();
3235
- console.log(chalk4.green(`
3212
+ console.log(chalk5.green(`
3236
3213
  \u{1F6E1}\uFE0F Node9 daemon started in background (PID ${child.pid})`));
3237
3214
  process.exit(0);
3238
3215
  }
@@ -3284,10 +3261,10 @@ RAW: ${raw}
3284
3261
  const sendBlock = (msg, result2) => {
3285
3262
  const blockedByContext = result2?.blockedByLabel || result2?.blockedBy || "Local Security Policy";
3286
3263
  const isHumanDecision = blockedByContext.toLowerCase().includes("user") || blockedByContext.toLowerCase().includes("daemon") || blockedByContext.toLowerCase().includes("decision");
3287
- console.error(chalk4.red(`
3264
+ console.error(chalk5.red(`
3288
3265
  \u{1F6D1} Node9 blocked "${toolName}"`));
3289
- console.error(chalk4.gray(` Triggered by: ${blockedByContext}`));
3290
- if (result2?.changeHint) console.error(chalk4.cyan(` To change: ${result2.changeHint}`));
3266
+ console.error(chalk5.gray(` Triggered by: ${blockedByContext}`));
3267
+ if (result2?.changeHint) console.error(chalk5.cyan(` To change: ${result2.changeHint}`));
3291
3268
  console.error("");
3292
3269
  let aiFeedbackMessage = "";
3293
3270
  if (isHumanDecision) {
@@ -3349,7 +3326,7 @@ RAW: ${raw}
3349
3326
  process.exit(0);
3350
3327
  }
3351
3328
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && !process.stdout.isTTY && config.settings.autoStartDaemon) {
3352
- console.error(chalk4.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3329
+ console.error(chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3353
3330
  const daemonReady = await autoStartDaemonAndWait();
3354
3331
  if (daemonReady) {
3355
3332
  const retry = await authorizeHeadless(toolName, toolInput, false, meta);
@@ -3457,7 +3434,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
3457
3434
  const ms = parseDuration(options.duration);
3458
3435
  if (ms === null) {
3459
3436
  console.error(
3460
- chalk4.red(`
3437
+ chalk5.red(`
3461
3438
  \u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
3462
3439
  `)
3463
3440
  );
@@ -3465,20 +3442,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
3465
3442
  }
3466
3443
  pauseNode9(ms, options.duration);
3467
3444
  const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
3468
- console.log(chalk4.yellow(`
3445
+ console.log(chalk5.yellow(`
3469
3446
  \u23F8 Node9 paused until ${expiresAt}`));
3470
- console.log(chalk4.gray(` All tool calls will be allowed without review.`));
3471
- console.log(chalk4.gray(` Run "node9 resume" to re-enable early.
3447
+ console.log(chalk5.gray(` All tool calls will be allowed without review.`));
3448
+ console.log(chalk5.gray(` Run "node9 resume" to re-enable early.
3472
3449
  `));
3473
3450
  });
3474
3451
  program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
3475
3452
  const { paused } = checkPause();
3476
3453
  if (!paused) {
3477
- console.log(chalk4.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
3454
+ console.log(chalk5.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
3478
3455
  return;
3479
3456
  }
3480
3457
  resumeNode9();
3481
- console.log(chalk4.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
3458
+ console.log(chalk5.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
3482
3459
  });
3483
3460
  var HOOK_BASED_AGENTS = {
3484
3461
  claude: "claude",
@@ -3491,21 +3468,21 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3491
3468
  if (HOOK_BASED_AGENTS[firstArg] !== void 0) {
3492
3469
  const target = HOOK_BASED_AGENTS[firstArg];
3493
3470
  console.error(
3494
- chalk4.yellow(`
3471
+ chalk5.yellow(`
3495
3472
  \u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
3496
3473
  );
3497
- console.error(chalk4.white(`
3474
+ console.error(chalk5.white(`
3498
3475
  "${target}" uses its own hook system. Use:`));
3499
3476
  console.error(
3500
- chalk4.green(` node9 addto ${target} `) + chalk4.gray("# one-time setup")
3477
+ chalk5.green(` node9 addto ${target} `) + chalk5.gray("# one-time setup")
3501
3478
  );
3502
- console.error(chalk4.green(` ${target} `) + chalk4.gray("# run normally"));
3479
+ console.error(chalk5.green(` ${target} `) + chalk5.gray("# run normally"));
3503
3480
  process.exit(1);
3504
3481
  }
3505
3482
  const fullCommand = commandArgs.join(" ");
3506
3483
  let result = await authorizeHeadless("shell", { command: fullCommand });
3507
3484
  if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
3508
- console.error(chalk4.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3485
+ console.error(chalk5.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
3509
3486
  const daemonReady = await autoStartDaemonAndWait();
3510
3487
  if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
3511
3488
  }
@@ -3514,12 +3491,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3514
3491
  }
3515
3492
  if (!result.approved) {
3516
3493
  console.error(
3517
- chalk4.red(`
3494
+ chalk5.red(`
3518
3495
  \u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
3519
3496
  );
3520
3497
  process.exit(1);
3521
3498
  }
3522
- console.error(chalk4.green("\n\u2705 Approved \u2014 running command...\n"));
3499
+ console.error(chalk5.green("\n\u2705 Approved \u2014 running command...\n"));
3523
3500
  await runProxy(fullCommand);
3524
3501
  } else {
3525
3502
  program.help();
@@ -3528,20 +3505,20 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
3528
3505
  program.command("undo").description("Revert the project to the state before the last AI action").action(async () => {
3529
3506
  const hash = getLatestSnapshotHash();
3530
3507
  if (!hash) {
3531
- console.log(chalk4.yellow("\n\u2139\uFE0F No Undo snapshot found for this machine.\n"));
3508
+ console.log(chalk5.yellow("\n\u2139\uFE0F No Undo snapshot found for this machine.\n"));
3532
3509
  return;
3533
3510
  }
3534
- console.log(chalk4.magenta.bold("\n\u23EA NODE9 UNDO ENGINE"));
3535
- console.log(chalk4.white(`Target Snapshot: ${chalk4.gray(hash.slice(0, 7))}`));
3511
+ console.log(chalk5.magenta.bold("\n\u23EA NODE9 UNDO ENGINE"));
3512
+ console.log(chalk5.white(`Target Snapshot: ${chalk5.gray(hash.slice(0, 7))}`));
3536
3513
  const proceed = await confirm3({
3537
3514
  message: "Revert all files to the state before the last AI action?",
3538
3515
  default: false
3539
3516
  });
3540
3517
  if (proceed) {
3541
3518
  if (applyUndo(hash)) {
3542
- console.log(chalk4.green("\u2705 Project reverted successfully.\n"));
3519
+ console.log(chalk5.green("\u2705 Project reverted successfully.\n"));
3543
3520
  } else {
3544
- console.error(chalk4.red("\u274C Undo failed. Ensure you are in a Git repository.\n"));
3521
+ console.error(chalk5.red("\u274C Undo failed. Ensure you are in a Git repository.\n"));
3545
3522
  }
3546
3523
  }
3547
3524
  });