@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 +200 -223
- package/dist/cli.mjs +200 -223
- package/dist/index.js +109 -132
- package/dist/index.mjs +109 -132
- package/package.json +30 -1
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
|
|
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 "${
|
|
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", [
|
|
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
|
-
|
|
85
|
-
|
|
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 += `
|
|
117
|
+
message += `Tool: ${toolName}
|
|
104
118
|
`;
|
|
105
|
-
|
|
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
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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 =
|
|
159
|
-
"--info",
|
|
160
|
-
"--
|
|
161
|
-
|
|
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
|
-
|
|
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
|
|
195
|
-
const
|
|
196
|
-
|
|
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
|
-
|
|
739
|
-
\u26A0\uFE0F Node9: Cloud API Handshake failed \u2014 ${reason}`) +
|
|
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
|
-
|
|
724
|
+
import_chalk2.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for Organization approval.")
|
|
748
725
|
);
|
|
749
|
-
console.error(
|
|
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
|
-
|
|
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
|
-
|
|
795
|
+
import_chalk2.default.yellow("\n\u{1F6E1}\uFE0F Node9: Action suspended \u2014 waiting for browser approval.")
|
|
819
796
|
);
|
|
820
|
-
console.error(
|
|
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(
|
|
844
|
-
console.log(`${
|
|
845
|
-
console.log(`${
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1103
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
1127
1104
|
var import_prompts2 = require("@inquirer/prompts");
|
|
1128
1105
|
function printDaemonTip() {
|
|
1129
1106
|
console.log(
|
|
1130
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
1198
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1225
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1281
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1308
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
1351
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1378
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2797
|
+
console.log(import_chalk4.default.green("\u2705 Stopped."));
|
|
2821
2798
|
} catch {
|
|
2822
|
-
console.log(
|
|
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(
|
|
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(
|
|
2813
|
+
console.log(import_chalk4.default.green("Node9 daemon: running"));
|
|
2837
2814
|
} catch {
|
|
2838
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
3087
|
-
console.log(
|
|
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(
|
|
3090
|
-
console.log(
|
|
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(
|
|
3093
|
-
console.log(
|
|
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(
|
|
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(
|
|
3107
|
-
console.log(
|
|
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(
|
|
3171
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
3173
|
+
console.log(import_chalk5.default.gray(" \u25CB Daemon stopped"));
|
|
3197
3174
|
}
|
|
3198
3175
|
if (settings.enableUndo) {
|
|
3199
3176
|
console.log(
|
|
3200
|
-
|
|
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" ?
|
|
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) ?
|
|
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) ?
|
|
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: ${
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3287
|
+
console.error(import_chalk5.default.red(`
|
|
3311
3288
|
\u{1F6D1} Node9 blocked "${toolName}"`));
|
|
3312
|
-
console.error(
|
|
3313
|
-
if (result2?.changeHint) console.error(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
3468
|
+
console.log(import_chalk5.default.yellow(`
|
|
3492
3469
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
3493
|
-
console.log(
|
|
3494
|
-
console.log(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
3494
|
+
import_chalk5.default.yellow(`
|
|
3518
3495
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
3519
3496
|
);
|
|
3520
|
-
console.error(
|
|
3497
|
+
console.error(import_chalk5.default.white(`
|
|
3521
3498
|
"${target}" uses its own hook system. Use:`));
|
|
3522
3499
|
console.error(
|
|
3523
|
-
|
|
3500
|
+
import_chalk5.default.green(` node9 addto ${target} `) + import_chalk5.default.gray("# one-time setup")
|
|
3524
3501
|
);
|
|
3525
|
-
console.error(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
3558
|
-
console.log(
|
|
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(
|
|
3542
|
+
console.log(import_chalk5.default.green("\u2705 Project reverted successfully.\n"));
|
|
3566
3543
|
} else {
|
|
3567
|
-
console.error(
|
|
3544
|
+
console.error(import_chalk5.default.red("\u274C Undo failed. Ensure you are in a Git repository.\n"));
|
|
3568
3545
|
}
|
|
3569
3546
|
}
|
|
3570
3547
|
});
|