@node9/proxy 1.9.0 → 1.9.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/README.md +19 -1
- package/dist/cli.js +629 -202
- package/dist/cli.mjs +627 -200
- package/dist/index.js +4 -4
- package/dist/index.mjs +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -160,8 +160,8 @@ function sanitizeConfig(raw) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
const lines = result.error.issues.map((issue) => {
|
|
163
|
-
const
|
|
164
|
-
return ` \u2022 ${
|
|
163
|
+
const path32 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
164
|
+
return ` \u2022 ${path32}: ${issue.message}`;
|
|
165
165
|
});
|
|
166
166
|
return {
|
|
167
167
|
sanitized,
|
|
@@ -828,7 +828,7 @@ var init_config = __esm({
|
|
|
828
828
|
{
|
|
829
829
|
field: "command",
|
|
830
830
|
op: "matches",
|
|
831
|
-
value: "
|
|
831
|
+
value: "\\bgit\\b.*\\bpush\\b.*(--force|--force-with-lease|-f\\b)",
|
|
832
832
|
flags: "i"
|
|
833
833
|
}
|
|
834
834
|
],
|
|
@@ -843,7 +843,7 @@ var init_config = __esm({
|
|
|
843
843
|
{
|
|
844
844
|
field: "command",
|
|
845
845
|
op: "matches",
|
|
846
|
-
value: "
|
|
846
|
+
value: "\\bgit\\b.*\\bpush\\b(?!.*(-f\\b|--force|--force-with-lease))",
|
|
847
847
|
flags: "i"
|
|
848
848
|
}
|
|
849
849
|
],
|
|
@@ -858,7 +858,7 @@ var init_config = __esm({
|
|
|
858
858
|
{
|
|
859
859
|
field: "command",
|
|
860
860
|
op: "matches",
|
|
861
|
-
value: "
|
|
861
|
+
value: "\\bgit\\b.*(reset\\s+--hard|clean\\s+-[fdxX]|\\brebase\\b|tag\\s+-d|branch\\s+-[dD])",
|
|
862
862
|
flags: "i"
|
|
863
863
|
}
|
|
864
864
|
],
|
|
@@ -870,7 +870,7 @@ var init_config = __esm({
|
|
|
870
870
|
{
|
|
871
871
|
name: "review-sudo",
|
|
872
872
|
tool: "bash",
|
|
873
|
-
conditions: [{ field: "command", op: "matches", value: "
|
|
873
|
+
conditions: [{ field: "command", op: "matches", value: "\\bsudo\\s", flags: "i" }],
|
|
874
874
|
conditionMode: "all",
|
|
875
875
|
verdict: "review",
|
|
876
876
|
reason: "Command requires elevated privileges"
|
|
@@ -1691,9 +1691,9 @@ function matchesPattern(text, patterns) {
|
|
|
1691
1691
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
1692
1692
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
1693
1693
|
}
|
|
1694
|
-
function getNestedValue(obj,
|
|
1694
|
+
function getNestedValue(obj, path32) {
|
|
1695
1695
|
if (!obj || typeof obj !== "object") return null;
|
|
1696
|
-
return
|
|
1696
|
+
return path32.split(".").reduce((prev, curr) => prev?.[curr], obj);
|
|
1697
1697
|
}
|
|
1698
1698
|
function shouldSnapshot(toolName, args, config) {
|
|
1699
1699
|
if (!config.settings.enableUndo) return false;
|
|
@@ -6782,22 +6782,22 @@ function formatBase(activity) {
|
|
|
6782
6782
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
6783
6783
|
const icon = getIcon(activity.tool);
|
|
6784
6784
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
6785
|
-
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(
|
|
6785
|
+
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(import_os22.default.homedir(), "~");
|
|
6786
6786
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
6787
|
-
return `${
|
|
6787
|
+
return `${import_chalk18.default.gray(time)} ${icon} ${import_chalk18.default.white.bold(toolName)} ${import_chalk18.default.dim(argsPreview)}`;
|
|
6788
6788
|
}
|
|
6789
6789
|
function renderResult(activity, result) {
|
|
6790
6790
|
const base = formatBase(activity);
|
|
6791
6791
|
let status;
|
|
6792
6792
|
if (result.status === "allow") {
|
|
6793
|
-
status =
|
|
6793
|
+
status = import_chalk18.default.green("\u2713 ALLOW");
|
|
6794
6794
|
} else if (result.status === "dlp") {
|
|
6795
|
-
status =
|
|
6795
|
+
status = import_chalk18.default.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
6796
6796
|
} else {
|
|
6797
|
-
status =
|
|
6797
|
+
status = import_chalk18.default.red("\u2717 BLOCK");
|
|
6798
6798
|
}
|
|
6799
6799
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
6800
|
-
const costSuffix = cost == null ? "" :
|
|
6800
|
+
const costSuffix = cost == null ? "" : import_chalk18.default.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
6801
6801
|
if (process.stdout.isTTY) {
|
|
6802
6802
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
6803
6803
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
@@ -6806,16 +6806,16 @@ function renderResult(activity, result) {
|
|
|
6806
6806
|
}
|
|
6807
6807
|
function renderPending(activity) {
|
|
6808
6808
|
if (!process.stdout.isTTY) return;
|
|
6809
|
-
process.stdout.write(`${formatBase(activity)} ${
|
|
6809
|
+
process.stdout.write(`${formatBase(activity)} ${import_chalk18.default.yellow("\u25CF \u2026")}\r`);
|
|
6810
6810
|
}
|
|
6811
6811
|
async function ensureDaemon() {
|
|
6812
6812
|
let pidPort = null;
|
|
6813
|
-
if (
|
|
6813
|
+
if (import_fs26.default.existsSync(PID_FILE)) {
|
|
6814
6814
|
try {
|
|
6815
|
-
const { port } = JSON.parse(
|
|
6815
|
+
const { port } = JSON.parse(import_fs26.default.readFileSync(PID_FILE, "utf-8"));
|
|
6816
6816
|
pidPort = port;
|
|
6817
6817
|
} catch {
|
|
6818
|
-
console.error(
|
|
6818
|
+
console.error(import_chalk18.default.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
6819
6819
|
}
|
|
6820
6820
|
}
|
|
6821
6821
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -6826,7 +6826,7 @@ async function ensureDaemon() {
|
|
|
6826
6826
|
if (res.ok) return checkPort;
|
|
6827
6827
|
} catch {
|
|
6828
6828
|
}
|
|
6829
|
-
console.log(
|
|
6829
|
+
console.log(import_chalk18.default.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
6830
6830
|
const child = (0, import_child_process14.spawn)(process.execPath, [process.argv[1], "daemon"], {
|
|
6831
6831
|
detached: true,
|
|
6832
6832
|
stdio: "ignore",
|
|
@@ -6843,7 +6843,7 @@ async function ensureDaemon() {
|
|
|
6843
6843
|
} catch {
|
|
6844
6844
|
}
|
|
6845
6845
|
}
|
|
6846
|
-
console.error(
|
|
6846
|
+
console.error(import_chalk18.default.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
6847
6847
|
process.exit(1);
|
|
6848
6848
|
}
|
|
6849
6849
|
function postDecisionHttp(id, decision, csrfToken, port, opts) {
|
|
@@ -6932,9 +6932,9 @@ function buildRecoveryCardLines(req) {
|
|
|
6932
6932
|
];
|
|
6933
6933
|
}
|
|
6934
6934
|
function readApproversFromDisk() {
|
|
6935
|
-
const configPath =
|
|
6935
|
+
const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
|
|
6936
6936
|
try {
|
|
6937
|
-
const raw = JSON.parse(
|
|
6937
|
+
const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
|
|
6938
6938
|
const settings = raw.settings ?? {};
|
|
6939
6939
|
return settings.approvers ?? {};
|
|
6940
6940
|
} catch {
|
|
@@ -6945,20 +6945,20 @@ function approverStatusLine() {
|
|
|
6945
6945
|
const a = readApproversFromDisk();
|
|
6946
6946
|
const fmt = (label, key) => {
|
|
6947
6947
|
const on = a[key] !== false;
|
|
6948
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
6948
|
+
return `[${key[0]}]${label.slice(1)} ${on ? import_chalk18.default.green("\u2713") : import_chalk18.default.dim("\u2717")}`;
|
|
6949
6949
|
};
|
|
6950
6950
|
return `${fmt("native", "native")} ${fmt("browser", "browser")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
6951
6951
|
}
|
|
6952
6952
|
function toggleApprover(channel) {
|
|
6953
|
-
const configPath =
|
|
6953
|
+
const configPath = import_path29.default.join(import_os22.default.homedir(), ".node9", "config.json");
|
|
6954
6954
|
try {
|
|
6955
|
-
const raw = JSON.parse(
|
|
6955
|
+
const raw = JSON.parse(import_fs26.default.readFileSync(configPath, "utf-8"));
|
|
6956
6956
|
const settings = raw.settings ?? {};
|
|
6957
6957
|
const approvers = settings.approvers ?? {};
|
|
6958
6958
|
approvers[channel] = approvers[channel] === false;
|
|
6959
6959
|
settings.approvers = approvers;
|
|
6960
6960
|
raw.settings = settings;
|
|
6961
|
-
|
|
6961
|
+
import_fs26.default.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
6962
6962
|
} catch (err2) {
|
|
6963
6963
|
process.stderr.write(`[node9] toggleApprover failed: ${String(err2)}
|
|
6964
6964
|
`);
|
|
@@ -6990,7 +6990,7 @@ async function startTail(options = {}) {
|
|
|
6990
6990
|
req2.end();
|
|
6991
6991
|
});
|
|
6992
6992
|
if (result.ok) {
|
|
6993
|
-
console.log(
|
|
6993
|
+
console.log(import_chalk18.default.green("\u2713 Flight Recorder buffer cleared."));
|
|
6994
6994
|
} else if (result.code === "ECONNREFUSED") {
|
|
6995
6995
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
6996
6996
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -7034,7 +7034,7 @@ async function startTail(options = {}) {
|
|
|
7034
7034
|
const channel = name === "n" ? "native" : name === "b" ? "browser" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
7035
7035
|
if (channel) {
|
|
7036
7036
|
toggleApprover(channel);
|
|
7037
|
-
console.log(
|
|
7037
|
+
console.log(import_chalk18.default.dim(` Approvers: ${approverStatusLine()}`));
|
|
7038
7038
|
}
|
|
7039
7039
|
};
|
|
7040
7040
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -7100,7 +7100,7 @@ async function startTail(options = {}) {
|
|
|
7100
7100
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
7101
7101
|
)
|
|
7102
7102
|
);
|
|
7103
|
-
const decisionStamp = action === "always-allow" ?
|
|
7103
|
+
const decisionStamp = action === "always-allow" ? import_chalk18.default.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? import_chalk18.default.cyan("\u23F1 TRUST 30m") : action === "allow" ? import_chalk18.default.green("\u2713 ALLOWED") : action === "redirect" ? import_chalk18.default.yellow("\u21A9 REDIRECT AI") : import_chalk18.default.red("\u2717 DENIED");
|
|
7104
7104
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
7105
7105
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
7106
7106
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -7128,8 +7128,8 @@ async function startTail(options = {}) {
|
|
|
7128
7128
|
}
|
|
7129
7129
|
postDecisionHttp(req2.id, httpDecision, csrfToken, port, httpOpts).catch((err2) => {
|
|
7130
7130
|
try {
|
|
7131
|
-
|
|
7132
|
-
|
|
7131
|
+
import_fs26.default.appendFileSync(
|
|
7132
|
+
import_path29.default.join(import_os22.default.homedir(), ".node9", "hook-debug.log"),
|
|
7133
7133
|
`[tail] POST /decision failed: ${String(err2)}
|
|
7134
7134
|
`
|
|
7135
7135
|
);
|
|
@@ -7151,7 +7151,7 @@ async function startTail(options = {}) {
|
|
|
7151
7151
|
);
|
|
7152
7152
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
7153
7153
|
if (externalDecision) {
|
|
7154
|
-
const source = externalDecision === "allow" ?
|
|
7154
|
+
const source = externalDecision === "allow" ? import_chalk18.default.green("\u2713 ALLOWED") : import_chalk18.default.red("\u2717 DENIED");
|
|
7155
7155
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
7156
7156
|
}
|
|
7157
7157
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -7210,16 +7210,16 @@ async function startTail(options = {}) {
|
|
|
7210
7210
|
}
|
|
7211
7211
|
} catch {
|
|
7212
7212
|
}
|
|
7213
|
-
console.log(
|
|
7214
|
-
\u{1F6F0}\uFE0F Node9 tail `) +
|
|
7213
|
+
console.log(import_chalk18.default.cyan.bold(`
|
|
7214
|
+
\u{1F6F0}\uFE0F Node9 tail `) + import_chalk18.default.dim(`\u2192 ${dashboardUrl}`));
|
|
7215
7215
|
if (canApprove) {
|
|
7216
|
-
console.log(
|
|
7217
|
-
console.log(
|
|
7216
|
+
console.log(import_chalk18.default.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
7217
|
+
console.log(import_chalk18.default.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
7218
7218
|
}
|
|
7219
7219
|
if (options.history) {
|
|
7220
|
-
console.log(
|
|
7220
|
+
console.log(import_chalk18.default.dim("Showing history + live events.\n"));
|
|
7221
7221
|
} else {
|
|
7222
|
-
console.log(
|
|
7222
|
+
console.log(import_chalk18.default.dim("Showing live events only. Use --history to include past.\n"));
|
|
7223
7223
|
}
|
|
7224
7224
|
process.on("SIGINT", () => {
|
|
7225
7225
|
exitIdleMode();
|
|
@@ -7229,13 +7229,13 @@ async function startTail(options = {}) {
|
|
|
7229
7229
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7230
7230
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7231
7231
|
}
|
|
7232
|
-
console.log(
|
|
7232
|
+
console.log(import_chalk18.default.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
7233
7233
|
process.exit(0);
|
|
7234
7234
|
});
|
|
7235
7235
|
const sseUrl = `http://127.0.0.1:${port}/events?capabilities=input`;
|
|
7236
7236
|
const req = import_http2.default.get(sseUrl, (res) => {
|
|
7237
7237
|
if (res.statusCode !== 200) {
|
|
7238
|
-
console.error(
|
|
7238
|
+
console.error(import_chalk18.default.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
7239
7239
|
process.exit(1);
|
|
7240
7240
|
}
|
|
7241
7241
|
if (canApprove) enterIdleMode();
|
|
@@ -7266,7 +7266,7 @@ async function startTail(options = {}) {
|
|
|
7266
7266
|
import_readline5.default.clearLine(process.stdout, 0);
|
|
7267
7267
|
import_readline5.default.cursorTo(process.stdout, 0);
|
|
7268
7268
|
}
|
|
7269
|
-
console.log(
|
|
7269
|
+
console.log(import_chalk18.default.red("\n\u274C Daemon disconnected."));
|
|
7270
7270
|
process.exit(1);
|
|
7271
7271
|
});
|
|
7272
7272
|
});
|
|
@@ -7358,9 +7358,9 @@ async function startTail(options = {}) {
|
|
|
7358
7358
|
const hash = data.hash ?? "";
|
|
7359
7359
|
const summary = data.argsSummary ?? data.tool;
|
|
7360
7360
|
const fileCount = data.fileCount ?? 0;
|
|
7361
|
-
const files = fileCount > 0 ?
|
|
7361
|
+
const files = fileCount > 0 ? import_chalk18.default.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
7362
7362
|
process.stdout.write(
|
|
7363
|
-
`${
|
|
7363
|
+
`${import_chalk18.default.dim(time)} ${import_chalk18.default.cyan("\u{1F4F8} snapshot")} ${import_chalk18.default.dim(hash)} ${summary}${files}
|
|
7364
7364
|
`
|
|
7365
7365
|
);
|
|
7366
7366
|
return;
|
|
@@ -7377,26 +7377,26 @@ async function startTail(options = {}) {
|
|
|
7377
7377
|
}
|
|
7378
7378
|
req.on("error", (err2) => {
|
|
7379
7379
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
7380
|
-
console.error(
|
|
7380
|
+
console.error(import_chalk18.default.red(`
|
|
7381
7381
|
\u274C ${msg}`));
|
|
7382
7382
|
process.exit(1);
|
|
7383
7383
|
});
|
|
7384
7384
|
}
|
|
7385
|
-
var import_http2,
|
|
7385
|
+
var import_http2, import_chalk18, import_fs26, import_os22, import_path29, import_readline5, import_child_process14, PID_FILE, ICONS, RESET2, BOLD2, RED, YELLOW, CYAN, GRAY, GREEN, HIDE_CURSOR, SHOW_CURSOR, ERASE_DOWN, DIVIDER;
|
|
7386
7386
|
var init_tail = __esm({
|
|
7387
7387
|
"src/tui/tail.ts"() {
|
|
7388
7388
|
"use strict";
|
|
7389
7389
|
import_http2 = __toESM(require("http"));
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7390
|
+
import_chalk18 = __toESM(require("chalk"));
|
|
7391
|
+
import_fs26 = __toESM(require("fs"));
|
|
7392
|
+
import_os22 = __toESM(require("os"));
|
|
7393
|
+
import_path29 = __toESM(require("path"));
|
|
7394
7394
|
import_readline5 = __toESM(require("readline"));
|
|
7395
7395
|
import_child_process14 = require("child_process");
|
|
7396
7396
|
init_daemon2();
|
|
7397
7397
|
init_daemon();
|
|
7398
7398
|
init_core();
|
|
7399
|
-
PID_FILE =
|
|
7399
|
+
PID_FILE = import_path29.default.join(import_os22.default.homedir(), ".node9", "daemon.pid");
|
|
7400
7400
|
ICONS = {
|
|
7401
7401
|
bash: "\u{1F4BB}",
|
|
7402
7402
|
shell: "\u{1F4BB}",
|
|
@@ -7509,9 +7509,9 @@ function formatTimeLeft(resetsAt) {
|
|
|
7509
7509
|
return ` (${m}m left)`;
|
|
7510
7510
|
}
|
|
7511
7511
|
function safeReadJson(filePath) {
|
|
7512
|
-
if (!
|
|
7512
|
+
if (!import_fs27.default.existsSync(filePath)) return null;
|
|
7513
7513
|
try {
|
|
7514
|
-
return JSON.parse(
|
|
7514
|
+
return JSON.parse(import_fs27.default.readFileSync(filePath, "utf-8"));
|
|
7515
7515
|
} catch {
|
|
7516
7516
|
return null;
|
|
7517
7517
|
}
|
|
@@ -7532,12 +7532,12 @@ function countHooksInFile(filePath) {
|
|
|
7532
7532
|
return Object.keys(cfg.hooks).length;
|
|
7533
7533
|
}
|
|
7534
7534
|
function countRulesInDir(rulesDir) {
|
|
7535
|
-
if (!
|
|
7535
|
+
if (!import_fs27.default.existsSync(rulesDir)) return 0;
|
|
7536
7536
|
let count = 0;
|
|
7537
7537
|
try {
|
|
7538
|
-
for (const entry of
|
|
7538
|
+
for (const entry of import_fs27.default.readdirSync(rulesDir, { withFileTypes: true })) {
|
|
7539
7539
|
if (entry.isDirectory()) {
|
|
7540
|
-
count += countRulesInDir(
|
|
7540
|
+
count += countRulesInDir(import_path30.default.join(rulesDir, entry.name));
|
|
7541
7541
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
7542
7542
|
count++;
|
|
7543
7543
|
}
|
|
@@ -7548,46 +7548,46 @@ function countRulesInDir(rulesDir) {
|
|
|
7548
7548
|
}
|
|
7549
7549
|
function isSamePath(a, b) {
|
|
7550
7550
|
try {
|
|
7551
|
-
return
|
|
7551
|
+
return import_path30.default.resolve(a) === import_path30.default.resolve(b);
|
|
7552
7552
|
} catch {
|
|
7553
7553
|
return false;
|
|
7554
7554
|
}
|
|
7555
7555
|
}
|
|
7556
7556
|
function countConfigs(cwd) {
|
|
7557
|
-
const homeDir2 =
|
|
7558
|
-
const claudeDir =
|
|
7557
|
+
const homeDir2 = import_os23.default.homedir();
|
|
7558
|
+
const claudeDir = import_path30.default.join(homeDir2, ".claude");
|
|
7559
7559
|
let claudeMdCount = 0;
|
|
7560
7560
|
let rulesCount = 0;
|
|
7561
7561
|
let hooksCount = 0;
|
|
7562
7562
|
const userMcpServers = /* @__PURE__ */ new Set();
|
|
7563
7563
|
const projectMcpServers = /* @__PURE__ */ new Set();
|
|
7564
|
-
if (
|
|
7565
|
-
rulesCount += countRulesInDir(
|
|
7566
|
-
const userSettings =
|
|
7564
|
+
if (import_fs27.default.existsSync(import_path30.default.join(claudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7565
|
+
rulesCount += countRulesInDir(import_path30.default.join(claudeDir, "rules"));
|
|
7566
|
+
const userSettings = import_path30.default.join(claudeDir, "settings.json");
|
|
7567
7567
|
for (const name of getMcpServerNames(userSettings)) userMcpServers.add(name);
|
|
7568
7568
|
hooksCount += countHooksInFile(userSettings);
|
|
7569
|
-
const userClaudeJson =
|
|
7569
|
+
const userClaudeJson = import_path30.default.join(homeDir2, ".claude.json");
|
|
7570
7570
|
for (const name of getMcpServerNames(userClaudeJson)) userMcpServers.add(name);
|
|
7571
7571
|
for (const name of getDisabledMcpServers(userClaudeJson, "disabledMcpServers")) {
|
|
7572
7572
|
userMcpServers.delete(name);
|
|
7573
7573
|
}
|
|
7574
7574
|
if (cwd) {
|
|
7575
|
-
if (
|
|
7576
|
-
if (
|
|
7577
|
-
const projectClaudeDir =
|
|
7575
|
+
if (import_fs27.default.existsSync(import_path30.default.join(cwd, "CLAUDE.md"))) claudeMdCount++;
|
|
7576
|
+
if (import_fs27.default.existsSync(import_path30.default.join(cwd, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7577
|
+
const projectClaudeDir = import_path30.default.join(cwd, ".claude");
|
|
7578
7578
|
const overlapsUserScope = isSamePath(projectClaudeDir, claudeDir);
|
|
7579
7579
|
if (!overlapsUserScope) {
|
|
7580
|
-
if (
|
|
7581
|
-
rulesCount += countRulesInDir(
|
|
7582
|
-
const projSettings =
|
|
7580
|
+
if (import_fs27.default.existsSync(import_path30.default.join(projectClaudeDir, "CLAUDE.md"))) claudeMdCount++;
|
|
7581
|
+
rulesCount += countRulesInDir(import_path30.default.join(projectClaudeDir, "rules"));
|
|
7582
|
+
const projSettings = import_path30.default.join(projectClaudeDir, "settings.json");
|
|
7583
7583
|
for (const name of getMcpServerNames(projSettings)) projectMcpServers.add(name);
|
|
7584
7584
|
hooksCount += countHooksInFile(projSettings);
|
|
7585
7585
|
}
|
|
7586
|
-
if (
|
|
7587
|
-
const localSettings =
|
|
7586
|
+
if (import_fs27.default.existsSync(import_path30.default.join(projectClaudeDir, "CLAUDE.local.md"))) claudeMdCount++;
|
|
7587
|
+
const localSettings = import_path30.default.join(projectClaudeDir, "settings.local.json");
|
|
7588
7588
|
for (const name of getMcpServerNames(localSettings)) projectMcpServers.add(name);
|
|
7589
7589
|
hooksCount += countHooksInFile(localSettings);
|
|
7590
|
-
const mcpJsonServers = getMcpServerNames(
|
|
7590
|
+
const mcpJsonServers = getMcpServerNames(import_path30.default.join(cwd, ".mcp.json"));
|
|
7591
7591
|
const disabledMcpJson = getDisabledMcpServers(localSettings, "disabledMcpjsonServers");
|
|
7592
7592
|
for (const name of disabledMcpJson) mcpJsonServers.delete(name);
|
|
7593
7593
|
for (const name of mcpJsonServers) projectMcpServers.add(name);
|
|
@@ -7620,12 +7620,12 @@ function readActiveShieldsHud() {
|
|
|
7620
7620
|
return shieldsCache.value;
|
|
7621
7621
|
}
|
|
7622
7622
|
try {
|
|
7623
|
-
const shieldsPath =
|
|
7624
|
-
if (!
|
|
7623
|
+
const shieldsPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "shields.json");
|
|
7624
|
+
if (!import_fs27.default.existsSync(shieldsPath)) {
|
|
7625
7625
|
shieldsCache = { value: [], ts: now };
|
|
7626
7626
|
return [];
|
|
7627
7627
|
}
|
|
7628
|
-
const parsed = JSON.parse(
|
|
7628
|
+
const parsed = JSON.parse(import_fs27.default.readFileSync(shieldsPath, "utf-8"));
|
|
7629
7629
|
if (!Array.isArray(parsed.active)) {
|
|
7630
7630
|
shieldsCache = { value: [], ts: now };
|
|
7631
7631
|
return [];
|
|
@@ -7727,17 +7727,17 @@ function renderContextLine(stdin) {
|
|
|
7727
7727
|
async function main() {
|
|
7728
7728
|
try {
|
|
7729
7729
|
const [stdin, daemonStatus2] = await Promise.all([readStdin(), queryDaemon()]);
|
|
7730
|
-
if (
|
|
7730
|
+
if (import_fs27.default.existsSync(import_path30.default.join(import_os23.default.homedir(), ".node9", "hud-debug"))) {
|
|
7731
7731
|
try {
|
|
7732
|
-
const logPath =
|
|
7732
|
+
const logPath = import_path30.default.join(import_os23.default.homedir(), ".node9", "hud-debug.log");
|
|
7733
7733
|
const MAX_LOG_SIZE = 10 * 1024 * 1024;
|
|
7734
7734
|
let size = 0;
|
|
7735
7735
|
try {
|
|
7736
|
-
size =
|
|
7736
|
+
size = import_fs27.default.statSync(logPath).size;
|
|
7737
7737
|
} catch {
|
|
7738
7738
|
}
|
|
7739
7739
|
if (size < MAX_LOG_SIZE) {
|
|
7740
|
-
|
|
7740
|
+
import_fs27.default.appendFileSync(
|
|
7741
7741
|
logPath,
|
|
7742
7742
|
JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), stdin }) + "\n"
|
|
7743
7743
|
);
|
|
@@ -7758,11 +7758,11 @@ async function main() {
|
|
|
7758
7758
|
try {
|
|
7759
7759
|
const cwd = stdin.cwd ?? process.cwd();
|
|
7760
7760
|
for (const configPath of [
|
|
7761
|
-
|
|
7762
|
-
|
|
7761
|
+
import_path30.default.join(cwd, "node9.config.json"),
|
|
7762
|
+
import_path30.default.join(import_os23.default.homedir(), ".node9", "config.json")
|
|
7763
7763
|
]) {
|
|
7764
|
-
if (!
|
|
7765
|
-
const cfg = JSON.parse(
|
|
7764
|
+
if (!import_fs27.default.existsSync(configPath)) continue;
|
|
7765
|
+
const cfg = JSON.parse(import_fs27.default.readFileSync(configPath, "utf-8"));
|
|
7766
7766
|
const hud = cfg.settings?.hud;
|
|
7767
7767
|
if (hud && "showEnvironmentCounts" in hud) return hud.showEnvironmentCounts !== false;
|
|
7768
7768
|
}
|
|
@@ -7780,13 +7780,13 @@ async function main() {
|
|
|
7780
7780
|
renderOffline();
|
|
7781
7781
|
}
|
|
7782
7782
|
}
|
|
7783
|
-
var
|
|
7783
|
+
var import_fs27, import_path30, import_os23, import_http3, RESET3, BOLD3, DIM, RED2, GREEN2, YELLOW2, BLUE, MAGENTA, CYAN2, WHITE, BAR_FILLED, BAR_EMPTY, BAR_WIDTH, shieldsCache, SHIELDS_CACHE_TTL_MS;
|
|
7784
7784
|
var init_hud = __esm({
|
|
7785
7785
|
"src/cli/hud.ts"() {
|
|
7786
7786
|
"use strict";
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7787
|
+
import_fs27 = __toESM(require("fs"));
|
|
7788
|
+
import_path30 = __toESM(require("path"));
|
|
7789
|
+
import_os23 = __toESM(require("os"));
|
|
7790
7790
|
import_http3 = __toESM(require("http"));
|
|
7791
7791
|
init_daemon();
|
|
7792
7792
|
RESET3 = "\x1B[0m";
|
|
@@ -8395,10 +8395,10 @@ function teardownHud() {
|
|
|
8395
8395
|
|
|
8396
8396
|
// src/cli.ts
|
|
8397
8397
|
init_daemon2();
|
|
8398
|
-
var
|
|
8399
|
-
var
|
|
8400
|
-
var
|
|
8401
|
-
var
|
|
8398
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
8399
|
+
var import_fs28 = __toESM(require("fs"));
|
|
8400
|
+
var import_path31 = __toESM(require("path"));
|
|
8401
|
+
var import_os24 = __toESM(require("os"));
|
|
8402
8402
|
var import_prompts2 = require("@inquirer/prompts");
|
|
8403
8403
|
|
|
8404
8404
|
// src/utils/duration.ts
|
|
@@ -10288,11 +10288,17 @@ function registerInitCommand(program2) {
|
|
|
10288
10288
|
if (sendTelemetry) fireTelemetryPing(found);
|
|
10289
10289
|
console.log("");
|
|
10290
10290
|
}
|
|
10291
|
-
|
|
10291
|
+
const agentList = found.join(", ");
|
|
10292
|
+
console.log(import_chalk11.default.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
10293
|
+
console.log("");
|
|
10294
|
+
console.log(import_chalk11.default.white(" Watch live: ") + import_chalk11.default.cyan("node9 tail"));
|
|
10295
|
+
console.log(import_chalk11.default.white(" Local UI: ") + import_chalk11.default.cyan("node9 daemon --openui"));
|
|
10292
10296
|
console.log("");
|
|
10293
|
-
console.log(import_chalk11.default.
|
|
10294
|
-
console.log(
|
|
10295
|
-
|
|
10297
|
+
console.log(import_chalk11.default.gray(" \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10298
|
+
console.log(
|
|
10299
|
+
import_chalk11.default.white(" Team dashboard + full audit trail \u2192 ") + import_chalk11.default.cyan.bold("https://node9.ai")
|
|
10300
|
+
);
|
|
10301
|
+
console.log(import_chalk11.default.gray(" \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
10296
10302
|
});
|
|
10297
10303
|
}
|
|
10298
10304
|
|
|
@@ -10650,6 +10656,90 @@ var import_child_process13 = require("child_process");
|
|
|
10650
10656
|
var import_execa3 = require("execa");
|
|
10651
10657
|
init_orchestrator();
|
|
10652
10658
|
init_provenance();
|
|
10659
|
+
|
|
10660
|
+
// src/mcp-pin.ts
|
|
10661
|
+
var import_fs24 = __toESM(require("fs"));
|
|
10662
|
+
var import_path27 = __toESM(require("path"));
|
|
10663
|
+
var import_os20 = __toESM(require("os"));
|
|
10664
|
+
var import_crypto8 = __toESM(require("crypto"));
|
|
10665
|
+
function getPinsFilePath() {
|
|
10666
|
+
return import_path27.default.join(import_os20.default.homedir(), ".node9", "mcp-pins.json");
|
|
10667
|
+
}
|
|
10668
|
+
function hashToolDefinitions(tools) {
|
|
10669
|
+
const sorted = [...tools].sort((a, b) => {
|
|
10670
|
+
const nameA = a.name ?? "";
|
|
10671
|
+
const nameB = b.name ?? "";
|
|
10672
|
+
return nameA.localeCompare(nameB);
|
|
10673
|
+
});
|
|
10674
|
+
const canonical = JSON.stringify(sorted);
|
|
10675
|
+
return import_crypto8.default.createHash("sha256").update(canonical).digest("hex");
|
|
10676
|
+
}
|
|
10677
|
+
function getServerKey(upstreamCommand) {
|
|
10678
|
+
return import_crypto8.default.createHash("sha256").update(upstreamCommand).digest("hex").slice(0, 16);
|
|
10679
|
+
}
|
|
10680
|
+
function readMcpPinsSafe() {
|
|
10681
|
+
const filePath = getPinsFilePath();
|
|
10682
|
+
try {
|
|
10683
|
+
const raw = import_fs24.default.readFileSync(filePath, "utf-8");
|
|
10684
|
+
if (!raw.trim()) {
|
|
10685
|
+
return { ok: false, reason: "corrupt", detail: "empty file" };
|
|
10686
|
+
}
|
|
10687
|
+
const parsed = JSON.parse(raw);
|
|
10688
|
+
if (!parsed.servers || typeof parsed.servers !== "object" || Array.isArray(parsed.servers)) {
|
|
10689
|
+
return { ok: false, reason: "corrupt", detail: "invalid structure: missing servers object" };
|
|
10690
|
+
}
|
|
10691
|
+
return { ok: true, pins: { servers: parsed.servers } };
|
|
10692
|
+
} catch (err2) {
|
|
10693
|
+
if (err2.code === "ENOENT") {
|
|
10694
|
+
return { ok: false, reason: "missing" };
|
|
10695
|
+
}
|
|
10696
|
+
return { ok: false, reason: "corrupt", detail: String(err2) };
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10699
|
+
function readMcpPins() {
|
|
10700
|
+
const result = readMcpPinsSafe();
|
|
10701
|
+
if (result.ok) return result.pins;
|
|
10702
|
+
if (result.reason === "missing") return { servers: {} };
|
|
10703
|
+
throw new Error(`[node9] MCP pin file is corrupt: ${result.detail}`);
|
|
10704
|
+
}
|
|
10705
|
+
function writeMcpPins(data) {
|
|
10706
|
+
const filePath = getPinsFilePath();
|
|
10707
|
+
import_fs24.default.mkdirSync(import_path27.default.dirname(filePath), { recursive: true });
|
|
10708
|
+
const tmp = `${filePath}.${import_crypto8.default.randomBytes(6).toString("hex")}.tmp`;
|
|
10709
|
+
import_fs24.default.writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
10710
|
+
import_fs24.default.renameSync(tmp, filePath);
|
|
10711
|
+
}
|
|
10712
|
+
function checkPin(serverKey, currentHash) {
|
|
10713
|
+
const result = readMcpPinsSafe();
|
|
10714
|
+
if (!result.ok) {
|
|
10715
|
+
if (result.reason === "missing") return "new";
|
|
10716
|
+
return "corrupt";
|
|
10717
|
+
}
|
|
10718
|
+
const entry = result.pins.servers[serverKey];
|
|
10719
|
+
if (!entry) return "new";
|
|
10720
|
+
return entry.toolsHash === currentHash ? "match" : "mismatch";
|
|
10721
|
+
}
|
|
10722
|
+
function updatePin(serverKey, label, toolsHash, toolNames) {
|
|
10723
|
+
const pins = readMcpPins();
|
|
10724
|
+
pins.servers[serverKey] = {
|
|
10725
|
+
label,
|
|
10726
|
+
toolsHash,
|
|
10727
|
+
toolNames,
|
|
10728
|
+
toolCount: toolNames.length,
|
|
10729
|
+
pinnedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10730
|
+
};
|
|
10731
|
+
writeMcpPins(pins);
|
|
10732
|
+
}
|
|
10733
|
+
function removePin(serverKey) {
|
|
10734
|
+
const pins = readMcpPins();
|
|
10735
|
+
delete pins.servers[serverKey];
|
|
10736
|
+
writeMcpPins(pins);
|
|
10737
|
+
}
|
|
10738
|
+
function clearAllPins() {
|
|
10739
|
+
writeMcpPins({ servers: {} });
|
|
10740
|
+
}
|
|
10741
|
+
|
|
10742
|
+
// src/mcp-gateway/index.ts
|
|
10653
10743
|
function sanitize4(value) {
|
|
10654
10744
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
10655
10745
|
}
|
|
@@ -10743,6 +10833,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10743
10833
|
let authPending = false;
|
|
10744
10834
|
let deferredExitCode = null;
|
|
10745
10835
|
let deferredStdinEnd = false;
|
|
10836
|
+
const pendingToolsListIds = /* @__PURE__ */ new Set();
|
|
10837
|
+
const serverKey = getServerKey(upstreamCommand);
|
|
10838
|
+
let pinState = "pending";
|
|
10839
|
+
const pendingToolCalls = [];
|
|
10746
10840
|
const agentIn = import_readline3.default.createInterface({ input: process.stdin, terminal: false });
|
|
10747
10841
|
agentIn.on("line", async (line) => {
|
|
10748
10842
|
let message;
|
|
@@ -10765,8 +10859,43 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10765
10859
|
child.stdin.write(line + "\n");
|
|
10766
10860
|
return;
|
|
10767
10861
|
}
|
|
10862
|
+
if (message.method === "tools/list" && message.id !== void 0 && message.id !== null) {
|
|
10863
|
+
pendingToolsListIds.add(message.id);
|
|
10864
|
+
}
|
|
10768
10865
|
if (message.method === "tools/call" || message.method === "call_tool" || message.method === "use_tool") {
|
|
10769
|
-
|
|
10866
|
+
if (pinState === "quarantined") {
|
|
10867
|
+
if (message.id === void 0 || message.id === null) return;
|
|
10868
|
+
const errorResponse = {
|
|
10869
|
+
jsonrpc: "2.0",
|
|
10870
|
+
id: message.id,
|
|
10871
|
+
error: {
|
|
10872
|
+
code: RPC_SERVER_ERROR,
|
|
10873
|
+
message: `Node9 Security: This MCP session is quarantined due to a tool definition mismatch or corrupt pin state. The human operator must review and approve changes before tool calls are allowed. Run: node9 mcp pin update ${serverKey}`,
|
|
10874
|
+
data: { reason: "pin-quarantine", serverKey, pinState }
|
|
10875
|
+
}
|
|
10876
|
+
};
|
|
10877
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
10878
|
+
return;
|
|
10879
|
+
}
|
|
10880
|
+
if (pinState === "pending") {
|
|
10881
|
+
if (pendingToolsListIds.size > 0) {
|
|
10882
|
+
pendingToolCalls.push(line);
|
|
10883
|
+
return;
|
|
10884
|
+
}
|
|
10885
|
+
if (message.id === void 0 || message.id === null) return;
|
|
10886
|
+
const errorResponse = {
|
|
10887
|
+
jsonrpc: "2.0",
|
|
10888
|
+
id: message.id,
|
|
10889
|
+
error: {
|
|
10890
|
+
code: RPC_SERVER_ERROR,
|
|
10891
|
+
message: "Node9 Security: Tool calls are blocked until MCP tool definitions have been verified. The client must issue a tools/list request before calling tools.",
|
|
10892
|
+
data: { reason: "pin-quarantine", serverKey, pinState }
|
|
10893
|
+
}
|
|
10894
|
+
};
|
|
10895
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
10896
|
+
return;
|
|
10897
|
+
}
|
|
10898
|
+
if (!deferredStdinEnd) agentIn.pause();
|
|
10770
10899
|
authPending = true;
|
|
10771
10900
|
try {
|
|
10772
10901
|
const toolName = sanitize4(
|
|
@@ -10828,9 +10957,100 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
10828
10957
|
}
|
|
10829
10958
|
child.stdin.write(line + "\n");
|
|
10830
10959
|
});
|
|
10831
|
-
|
|
10960
|
+
function drainPendingToolCalls() {
|
|
10961
|
+
if (pendingToolCalls.length === 0) {
|
|
10962
|
+
if (deferredStdinEnd && !authPending) child.stdin.end();
|
|
10963
|
+
return;
|
|
10964
|
+
}
|
|
10965
|
+
const lines = pendingToolCalls.splice(0);
|
|
10966
|
+
for (const queuedLine of lines) {
|
|
10967
|
+
agentIn.emit("line", queuedLine);
|
|
10968
|
+
}
|
|
10969
|
+
if (deferredStdinEnd && !authPending) child.stdin.end();
|
|
10970
|
+
}
|
|
10971
|
+
const upstreamOut = import_readline3.default.createInterface({ input: child.stdout, terminal: false });
|
|
10972
|
+
upstreamOut.on("line", (line) => {
|
|
10973
|
+
let parsed;
|
|
10974
|
+
try {
|
|
10975
|
+
parsed = JSON.parse(line);
|
|
10976
|
+
} catch {
|
|
10977
|
+
}
|
|
10978
|
+
if (!parsed) {
|
|
10979
|
+
process.stdout.write(line + "\n");
|
|
10980
|
+
return;
|
|
10981
|
+
}
|
|
10982
|
+
if (parsed.id !== void 0 && pendingToolsListIds.has(parsed.id)) {
|
|
10983
|
+
pendingToolsListIds.delete(parsed.id);
|
|
10984
|
+
if (parsed.result && Array.isArray(parsed.result.tools)) {
|
|
10985
|
+
const tools = parsed.result.tools;
|
|
10986
|
+
const currentHash = hashToolDefinitions(tools);
|
|
10987
|
+
const pinStatus = checkPin(serverKey, currentHash);
|
|
10988
|
+
if (pinStatus === "new") {
|
|
10989
|
+
const toolNames = tools.map((t) => t.name ?? "unknown").sort();
|
|
10990
|
+
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
10991
|
+
pinState = "validated";
|
|
10992
|
+
console.error(
|
|
10993
|
+
import_chalk15.default.green(
|
|
10994
|
+
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
10995
|
+
)
|
|
10996
|
+
);
|
|
10997
|
+
process.stdout.write(line + "\n");
|
|
10998
|
+
drainPendingToolCalls();
|
|
10999
|
+
} else if (pinStatus === "match") {
|
|
11000
|
+
pinState = "validated";
|
|
11001
|
+
process.stdout.write(line + "\n");
|
|
11002
|
+
drainPendingToolCalls();
|
|
11003
|
+
} else if (pinStatus === "corrupt") {
|
|
11004
|
+
pinState = "quarantined";
|
|
11005
|
+
console.error(
|
|
11006
|
+
import_chalk15.default.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
11007
|
+
);
|
|
11008
|
+
console.error(import_chalk15.default.red(" Tool calls are blocked until the pin file is repaired."));
|
|
11009
|
+
console.error(
|
|
11010
|
+
import_chalk15.default.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
11011
|
+
`)
|
|
11012
|
+
);
|
|
11013
|
+
const errorResponse = {
|
|
11014
|
+
jsonrpc: "2.0",
|
|
11015
|
+
id: parsed.id,
|
|
11016
|
+
error: {
|
|
11017
|
+
code: RPC_SERVER_ERROR,
|
|
11018
|
+
message: "Node9 Security: MCP pin file is corrupt or unreadable. Tool definitions cannot be verified. Session quarantined. The human operator must repair or reset the pin file. Run: node9 mcp pin reset",
|
|
11019
|
+
data: { reason: "pin-file-corrupt", serverKey }
|
|
11020
|
+
}
|
|
11021
|
+
};
|
|
11022
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
11023
|
+
drainPendingToolCalls();
|
|
11024
|
+
} else {
|
|
11025
|
+
pinState = "quarantined";
|
|
11026
|
+
console.error(
|
|
11027
|
+
import_chalk15.default.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
11028
|
+
);
|
|
11029
|
+
console.error(
|
|
11030
|
+
import_chalk15.default.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
11031
|
+
);
|
|
11032
|
+
console.error(import_chalk15.default.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
11033
|
+
console.error(import_chalk15.default.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
11034
|
+
`));
|
|
11035
|
+
const errorResponse = {
|
|
11036
|
+
jsonrpc: "2.0",
|
|
11037
|
+
id: parsed.id,
|
|
11038
|
+
error: {
|
|
11039
|
+
code: RPC_SERVER_ERROR,
|
|
11040
|
+
message: `Node9 Security: MCP server tool definitions have changed since they were last pinned. This could indicate a supply chain attack (tool poisoning / rug pull). Session quarantined \u2014 all tool calls are blocked. The human operator must review and approve the changes. Run: node9 mcp pin update ${serverKey}`,
|
|
11041
|
+
data: { reason: "tool-pin-mismatch", serverKey }
|
|
11042
|
+
}
|
|
11043
|
+
};
|
|
11044
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
11045
|
+
drainPendingToolCalls();
|
|
11046
|
+
}
|
|
11047
|
+
return;
|
|
11048
|
+
}
|
|
11049
|
+
}
|
|
11050
|
+
process.stdout.write(line + "\n");
|
|
11051
|
+
});
|
|
10832
11052
|
process.stdin.on("close", () => {
|
|
10833
|
-
if (authPending) {
|
|
11053
|
+
if (authPending || pendingToolCalls.length > 0) {
|
|
10834
11054
|
deferredStdinEnd = true;
|
|
10835
11055
|
} else {
|
|
10836
11056
|
child.stdin.end();
|
|
@@ -10859,9 +11079,9 @@ function registerMcpGatewayCommand(program2) {
|
|
|
10859
11079
|
|
|
10860
11080
|
// src/mcp-server/index.ts
|
|
10861
11081
|
var import_readline4 = __toESM(require("readline"));
|
|
10862
|
-
var
|
|
10863
|
-
var
|
|
10864
|
-
var
|
|
11082
|
+
var import_fs25 = __toESM(require("fs"));
|
|
11083
|
+
var import_os21 = __toESM(require("os"));
|
|
11084
|
+
var import_path28 = __toESM(require("path"));
|
|
10865
11085
|
init_core();
|
|
10866
11086
|
init_daemon();
|
|
10867
11087
|
init_shields();
|
|
@@ -10961,6 +11181,60 @@ var TOOLS = [
|
|
|
10961
11181
|
},
|
|
10962
11182
|
required: ["hash"]
|
|
10963
11183
|
}
|
|
11184
|
+
},
|
|
11185
|
+
{
|
|
11186
|
+
name: "node9_audit_get",
|
|
11187
|
+
description: "Read recent entries from the node9 audit log (~/.node9/audit.log). Each entry shows timestamp, tool name, decision (allow/block/review), and agent. Use this to review what AI actions have been taken recently.",
|
|
11188
|
+
inputSchema: {
|
|
11189
|
+
type: "object",
|
|
11190
|
+
properties: {
|
|
11191
|
+
limit: {
|
|
11192
|
+
type: "number",
|
|
11193
|
+
description: "Number of recent entries to return (default: 20, max: 100)."
|
|
11194
|
+
}
|
|
11195
|
+
},
|
|
11196
|
+
required: []
|
|
11197
|
+
}
|
|
11198
|
+
},
|
|
11199
|
+
{
|
|
11200
|
+
name: "node9_policy_get",
|
|
11201
|
+
description: "Show all active smart rules in detail \u2014 name, tool, verdict, conditions, and reason. Includes default rules, shield rules, and any custom project rules. Use this to understand exactly what is being blocked or reviewed.",
|
|
11202
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
11203
|
+
},
|
|
11204
|
+
{
|
|
11205
|
+
name: "node9_rule_add",
|
|
11206
|
+
description: 'Add a new protective smart rule to the global node9 config (~/.node9/config.json). Rules can block or send dangerous commands for human review based on regex conditions. IMPORTANT: only "block" and "review" verdicts are permitted \u2014 "allow" rules are never accepted because they would weaken node9 security. Rules can only be added, never removed.',
|
|
11207
|
+
inputSchema: {
|
|
11208
|
+
type: "object",
|
|
11209
|
+
properties: {
|
|
11210
|
+
name: {
|
|
11211
|
+
type: "string",
|
|
11212
|
+
description: 'Unique rule name (e.g. "block-drop-prod-db").'
|
|
11213
|
+
},
|
|
11214
|
+
tool: {
|
|
11215
|
+
type: "string",
|
|
11216
|
+
description: 'Tool to match \u2014 "bash", "*", or a specific tool name.'
|
|
11217
|
+
},
|
|
11218
|
+
field: {
|
|
11219
|
+
type: "string",
|
|
11220
|
+
description: 'Field to inspect \u2014 "command" for bash, "sql" for database tools.'
|
|
11221
|
+
},
|
|
11222
|
+
pattern: {
|
|
11223
|
+
type: "string",
|
|
11224
|
+
description: "Regex pattern to match against the field."
|
|
11225
|
+
},
|
|
11226
|
+
verdict: {
|
|
11227
|
+
type: "string",
|
|
11228
|
+
enum: ["block", "review"],
|
|
11229
|
+
description: 'Action to take when the rule matches. Only "block" or "review" are allowed.'
|
|
11230
|
+
},
|
|
11231
|
+
reason: {
|
|
11232
|
+
type: "string",
|
|
11233
|
+
description: "Human-readable explanation shown when the rule triggers."
|
|
11234
|
+
}
|
|
11235
|
+
},
|
|
11236
|
+
required: ["name", "tool", "field", "pattern", "verdict", "reason"]
|
|
11237
|
+
}
|
|
10964
11238
|
}
|
|
10965
11239
|
];
|
|
10966
11240
|
function handleStatus() {
|
|
@@ -10982,13 +11256,13 @@ function handleStatus() {
|
|
|
10982
11256
|
lines.push(`Active shields: ${activeShields.length > 0 ? activeShields.join(", ") : "none"}`);
|
|
10983
11257
|
lines.push(`Smart rules: ${config.policy.smartRules.length} loaded`);
|
|
10984
11258
|
lines.push(`DLP: ${config.policy.dlp?.enabled !== false ? "enabled" : "disabled"}`);
|
|
10985
|
-
const projectConfig =
|
|
10986
|
-
const globalConfig =
|
|
11259
|
+
const projectConfig = import_path28.default.join(process.cwd(), "node9.config.json");
|
|
11260
|
+
const globalConfig = import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json");
|
|
10987
11261
|
lines.push(
|
|
10988
|
-
`Project config (node9.config.json): ${
|
|
11262
|
+
`Project config (node9.config.json): ${import_fs25.default.existsSync(projectConfig) ? "present" : "not found"}`
|
|
10989
11263
|
);
|
|
10990
11264
|
lines.push(
|
|
10991
|
-
`Global config (~/.node9/config.json): ${
|
|
11265
|
+
`Global config (~/.node9/config.json): ${import_fs25.default.existsSync(globalConfig) ? "present" : "not found"}`
|
|
10992
11266
|
);
|
|
10993
11267
|
return lines.join("\n");
|
|
10994
11268
|
}
|
|
@@ -11062,21 +11336,21 @@ function handleShieldDisable(args) {
|
|
|
11062
11336
|
writeActiveShields(active.filter((s) => s !== name));
|
|
11063
11337
|
return `Shield "${name}" disabled.`;
|
|
11064
11338
|
}
|
|
11065
|
-
var GLOBAL_CONFIG_PATH2 =
|
|
11339
|
+
var GLOBAL_CONFIG_PATH2 = import_path28.default.join(import_os21.default.homedir(), ".node9", "config.json");
|
|
11066
11340
|
var APPROVER_CHANNELS = ["native", "browser", "cloud", "terminal"];
|
|
11067
11341
|
function readGlobalConfigRaw() {
|
|
11068
11342
|
try {
|
|
11069
|
-
if (
|
|
11070
|
-
return JSON.parse(
|
|
11343
|
+
if (import_fs25.default.existsSync(GLOBAL_CONFIG_PATH2)) {
|
|
11344
|
+
return JSON.parse(import_fs25.default.readFileSync(GLOBAL_CONFIG_PATH2, "utf-8"));
|
|
11071
11345
|
}
|
|
11072
11346
|
} catch {
|
|
11073
11347
|
}
|
|
11074
11348
|
return {};
|
|
11075
11349
|
}
|
|
11076
11350
|
function writeGlobalConfigRaw(data) {
|
|
11077
|
-
const dir =
|
|
11078
|
-
if (!
|
|
11079
|
-
|
|
11351
|
+
const dir = import_path28.default.dirname(GLOBAL_CONFIG_PATH2);
|
|
11352
|
+
if (!import_fs25.default.existsSync(dir)) import_fs25.default.mkdirSync(dir, { recursive: true });
|
|
11353
|
+
import_fs25.default.writeFileSync(GLOBAL_CONFIG_PATH2, JSON.stringify(data, null, 2) + "\n");
|
|
11080
11354
|
}
|
|
11081
11355
|
function handleApproverList() {
|
|
11082
11356
|
const config = getConfig();
|
|
@@ -11117,6 +11391,75 @@ function handleApproverSet(args) {
|
|
|
11117
11391
|
const suffix = anyEnabled ? "" : "\nWARNING: all approver channels are now disabled \u2014 node9 cannot prompt for approval.";
|
|
11118
11392
|
return `Approver channel "${channel}" ${enabled ? "enabled" : "disabled"} in ~/.node9/config.json.${suffix}`;
|
|
11119
11393
|
}
|
|
11394
|
+
function handleAuditGet(args) {
|
|
11395
|
+
const limit = Math.min(typeof args.limit === "number" ? args.limit : 20, 100);
|
|
11396
|
+
const auditPath = import_path28.default.join(import_os21.default.homedir(), ".node9", "audit.log");
|
|
11397
|
+
if (!import_fs25.default.existsSync(auditPath)) return "No audit log found.";
|
|
11398
|
+
const lines = import_fs25.default.readFileSync(auditPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
11399
|
+
const recent = lines.slice(-limit);
|
|
11400
|
+
const entries = recent.map((line) => {
|
|
11401
|
+
try {
|
|
11402
|
+
const e = JSON.parse(line);
|
|
11403
|
+
return `${e.ts} ${String(e.tool).padEnd(20)} ${String(e.decision).padEnd(8)} ${e.agent ?? ""}`;
|
|
11404
|
+
} catch {
|
|
11405
|
+
return line;
|
|
11406
|
+
}
|
|
11407
|
+
});
|
|
11408
|
+
return `Last ${entries.length} audit entries:
|
|
11409
|
+
|
|
11410
|
+
${entries.join("\n")}`;
|
|
11411
|
+
}
|
|
11412
|
+
function handlePolicyGet() {
|
|
11413
|
+
const config = getConfig();
|
|
11414
|
+
const rules = config.policy.smartRules;
|
|
11415
|
+
if (rules.length === 0) return "No smart rules active.";
|
|
11416
|
+
const lines = rules.map((r, i) => {
|
|
11417
|
+
const conditions = r.conditions.map((c) => `${c.field} ${c.op} "${c.value}"`).join(` ${r.conditionMode ?? "all"} `);
|
|
11418
|
+
return `[${i + 1}] ${r.name ?? "(unnamed)"} tool:${r.tool} verdict:${r.verdict}
|
|
11419
|
+
conditions: ${conditions}
|
|
11420
|
+
reason: ${r.reason ?? "\u2014"}`;
|
|
11421
|
+
});
|
|
11422
|
+
return `${rules.length} active smart rules:
|
|
11423
|
+
|
|
11424
|
+
${lines.join("\n\n")}`;
|
|
11425
|
+
}
|
|
11426
|
+
function handleRuleAdd(args) {
|
|
11427
|
+
const name = args.name;
|
|
11428
|
+
const tool = args.tool;
|
|
11429
|
+
const field = args.field;
|
|
11430
|
+
const pattern = args.pattern;
|
|
11431
|
+
const verdict = args.verdict;
|
|
11432
|
+
const reason = args.reason;
|
|
11433
|
+
if (!["block", "review"].includes(verdict)) {
|
|
11434
|
+
throw new Error(
|
|
11435
|
+
'verdict must be "block" or "review" \u2014 "allow" rules are not permitted as they would weaken node9 security'
|
|
11436
|
+
);
|
|
11437
|
+
}
|
|
11438
|
+
try {
|
|
11439
|
+
new RegExp(pattern);
|
|
11440
|
+
} catch {
|
|
11441
|
+
throw new Error(`Invalid regex pattern: ${pattern}`);
|
|
11442
|
+
}
|
|
11443
|
+
const raw = readGlobalConfigRaw();
|
|
11444
|
+
const policy = raw.policy ?? {};
|
|
11445
|
+
const smartRules = policy.smartRules ?? [];
|
|
11446
|
+
const existing = smartRules.find(
|
|
11447
|
+
(r) => typeof r === "object" && r !== null && r.name === name
|
|
11448
|
+
);
|
|
11449
|
+
if (existing) throw new Error(`A rule named "${name}" already exists.`);
|
|
11450
|
+
smartRules.push({
|
|
11451
|
+
name,
|
|
11452
|
+
tool,
|
|
11453
|
+
conditions: [{ field, op: "matches", value: pattern }],
|
|
11454
|
+
conditionMode: "all",
|
|
11455
|
+
verdict,
|
|
11456
|
+
reason
|
|
11457
|
+
});
|
|
11458
|
+
policy.smartRules = smartRules;
|
|
11459
|
+
raw.policy = policy;
|
|
11460
|
+
writeGlobalConfigRaw(raw);
|
|
11461
|
+
return `Rule "${name}" added to ~/.node9/config.json \u2014 verdict: ${verdict} when ${field} matches "${pattern}"`;
|
|
11462
|
+
}
|
|
11120
11463
|
function handleUndoList() {
|
|
11121
11464
|
const history = getSnapshotHistory();
|
|
11122
11465
|
if (history.length === 0) {
|
|
@@ -11200,6 +11543,12 @@ function runMcpServer() {
|
|
|
11200
11543
|
text = handleUndoList();
|
|
11201
11544
|
} else if (toolName === "node9_undo_revert") {
|
|
11202
11545
|
text = handleUndoRevert(toolArgs);
|
|
11546
|
+
} else if (toolName === "node9_audit_get") {
|
|
11547
|
+
text = handleAuditGet(toolArgs);
|
|
11548
|
+
} else if (toolName === "node9_policy_get") {
|
|
11549
|
+
text = handlePolicyGet();
|
|
11550
|
+
} else if (toolName === "node9_rule_add") {
|
|
11551
|
+
text = handleRuleAdd(toolArgs);
|
|
11203
11552
|
} else {
|
|
11204
11553
|
process.stdout.write(err(id, -32601, `Unknown tool: ${toolName}`) + "\n");
|
|
11205
11554
|
return;
|
|
@@ -11287,22 +11636,99 @@ function registerTrustCommand(program2) {
|
|
|
11287
11636
|
});
|
|
11288
11637
|
}
|
|
11289
11638
|
|
|
11639
|
+
// src/cli/commands/mcp-pin.ts
|
|
11640
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
11641
|
+
function registerMcpPinCommand(program2) {
|
|
11642
|
+
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
11643
|
+
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
11644
|
+
pinSubCmd.command("list").description("Show all pinned MCP servers and their tool definition hashes").action(() => {
|
|
11645
|
+
const result = readMcpPinsSafe();
|
|
11646
|
+
if (!result.ok) {
|
|
11647
|
+
if (result.reason === "missing") {
|
|
11648
|
+
console.log(import_chalk17.default.gray("\nNo MCP servers are pinned yet."));
|
|
11649
|
+
console.log(
|
|
11650
|
+
import_chalk17.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11651
|
+
);
|
|
11652
|
+
return;
|
|
11653
|
+
}
|
|
11654
|
+
console.error(import_chalk17.default.red(`
|
|
11655
|
+
\u274C Pin file is corrupt: ${result.detail}`));
|
|
11656
|
+
console.error(import_chalk17.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
11657
|
+
process.exit(1);
|
|
11658
|
+
}
|
|
11659
|
+
const entries = Object.entries(result.pins.servers);
|
|
11660
|
+
if (entries.length === 0) {
|
|
11661
|
+
console.log(import_chalk17.default.gray("\nNo MCP servers are pinned yet."));
|
|
11662
|
+
console.log(
|
|
11663
|
+
import_chalk17.default.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
11664
|
+
);
|
|
11665
|
+
return;
|
|
11666
|
+
}
|
|
11667
|
+
console.log(import_chalk17.default.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
11668
|
+
for (const [key, entry] of entries) {
|
|
11669
|
+
console.log(` ${import_chalk17.default.cyan(key)} ${import_chalk17.default.gray(entry.label)}`);
|
|
11670
|
+
console.log(` Tools (${entry.toolCount}): ${import_chalk17.default.white(entry.toolNames.join(", "))}`);
|
|
11671
|
+
console.log(` Hash: ${import_chalk17.default.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
11672
|
+
console.log(` Pinned: ${import_chalk17.default.gray(entry.pinnedAt)}`);
|
|
11673
|
+
console.log("");
|
|
11674
|
+
}
|
|
11675
|
+
});
|
|
11676
|
+
pinSubCmd.command("update <serverKey>").description(
|
|
11677
|
+
"Remove a pin so the next gateway connection re-pins with current tool definitions"
|
|
11678
|
+
).action((serverKey) => {
|
|
11679
|
+
let pins;
|
|
11680
|
+
try {
|
|
11681
|
+
pins = readMcpPins();
|
|
11682
|
+
} catch {
|
|
11683
|
+
console.error(import_chalk17.default.red("\n\u274C Pin file is corrupt."));
|
|
11684
|
+
console.error(import_chalk17.default.yellow(" Run: node9 mcp pin reset\n"));
|
|
11685
|
+
process.exit(1);
|
|
11686
|
+
}
|
|
11687
|
+
if (!pins.servers[serverKey]) {
|
|
11688
|
+
console.error(import_chalk17.default.red(`
|
|
11689
|
+
\u274C No pin found for server key "${serverKey}"
|
|
11690
|
+
`));
|
|
11691
|
+
console.error(`Run ${import_chalk17.default.cyan("node9 mcp pin list")} to see pinned servers.
|
|
11692
|
+
`);
|
|
11693
|
+
process.exit(1);
|
|
11694
|
+
}
|
|
11695
|
+
const label = pins.servers[serverKey].label;
|
|
11696
|
+
removePin(serverKey);
|
|
11697
|
+
console.log(import_chalk17.default.green(`
|
|
11698
|
+
\u{1F513} Pin removed for ${import_chalk17.default.cyan(serverKey)}`));
|
|
11699
|
+
console.log(import_chalk17.default.gray(` Server: ${label}`));
|
|
11700
|
+
console.log(import_chalk17.default.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
11701
|
+
});
|
|
11702
|
+
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
11703
|
+
const result = readMcpPinsSafe();
|
|
11704
|
+
if (!result.ok && result.reason === "missing") {
|
|
11705
|
+
console.log(import_chalk17.default.gray("\nNo pins to clear.\n"));
|
|
11706
|
+
return;
|
|
11707
|
+
}
|
|
11708
|
+
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
11709
|
+
clearAllPins();
|
|
11710
|
+
console.log(import_chalk17.default.green(`
|
|
11711
|
+
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
11712
|
+
console.log(import_chalk17.default.gray(" Next connection to each server will re-pin.\n"));
|
|
11713
|
+
});
|
|
11714
|
+
}
|
|
11715
|
+
|
|
11290
11716
|
// src/cli.ts
|
|
11291
11717
|
var { version } = JSON.parse(
|
|
11292
|
-
|
|
11718
|
+
import_fs28.default.readFileSync(import_path31.default.join(__dirname, "../package.json"), "utf-8")
|
|
11293
11719
|
);
|
|
11294
11720
|
var program = new import_commander.Command();
|
|
11295
11721
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
11296
11722
|
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
11297
11723
|
const DEFAULT_API_URL = "https://api.node9.ai/api/v1/intercept";
|
|
11298
|
-
const credPath =
|
|
11299
|
-
if (!
|
|
11300
|
-
|
|
11724
|
+
const credPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "credentials.json");
|
|
11725
|
+
if (!import_fs28.default.existsSync(import_path31.default.dirname(credPath)))
|
|
11726
|
+
import_fs28.default.mkdirSync(import_path31.default.dirname(credPath), { recursive: true });
|
|
11301
11727
|
const profileName = options.profile || "default";
|
|
11302
11728
|
let existingCreds = {};
|
|
11303
11729
|
try {
|
|
11304
|
-
if (
|
|
11305
|
-
const raw = JSON.parse(
|
|
11730
|
+
if (import_fs28.default.existsSync(credPath)) {
|
|
11731
|
+
const raw = JSON.parse(import_fs28.default.readFileSync(credPath, "utf-8"));
|
|
11306
11732
|
if (raw.apiKey) {
|
|
11307
11733
|
existingCreds = {
|
|
11308
11734
|
default: { apiKey: raw.apiKey, apiUrl: raw.apiUrl || DEFAULT_API_URL }
|
|
@@ -11314,13 +11740,13 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11314
11740
|
} catch {
|
|
11315
11741
|
}
|
|
11316
11742
|
existingCreds[profileName] = { apiKey, apiUrl: DEFAULT_API_URL };
|
|
11317
|
-
|
|
11743
|
+
import_fs28.default.writeFileSync(credPath, JSON.stringify(existingCreds, null, 2), { mode: 384 });
|
|
11318
11744
|
if (profileName === "default") {
|
|
11319
|
-
const configPath =
|
|
11745
|
+
const configPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "config.json");
|
|
11320
11746
|
let config = {};
|
|
11321
11747
|
try {
|
|
11322
|
-
if (
|
|
11323
|
-
config = JSON.parse(
|
|
11748
|
+
if (import_fs28.default.existsSync(configPath))
|
|
11749
|
+
config = JSON.parse(import_fs28.default.readFileSync(configPath, "utf-8"));
|
|
11324
11750
|
} catch {
|
|
11325
11751
|
}
|
|
11326
11752
|
if (!config.settings || typeof config.settings !== "object") config.settings = {};
|
|
@@ -11335,19 +11761,19 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
11335
11761
|
approvers.cloud = false;
|
|
11336
11762
|
}
|
|
11337
11763
|
s.approvers = approvers;
|
|
11338
|
-
if (!
|
|
11339
|
-
|
|
11340
|
-
|
|
11764
|
+
if (!import_fs28.default.existsSync(import_path31.default.dirname(configPath)))
|
|
11765
|
+
import_fs28.default.mkdirSync(import_path31.default.dirname(configPath), { recursive: true });
|
|
11766
|
+
import_fs28.default.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
11341
11767
|
}
|
|
11342
11768
|
if (options.profile && profileName !== "default") {
|
|
11343
|
-
console.log(
|
|
11344
|
-
console.log(
|
|
11769
|
+
console.log(import_chalk19.default.green(`\u2705 Profile "${profileName}" saved`));
|
|
11770
|
+
console.log(import_chalk19.default.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
11345
11771
|
} else if (options.local) {
|
|
11346
|
-
console.log(
|
|
11347
|
-
console.log(
|
|
11772
|
+
console.log(import_chalk19.default.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
11773
|
+
console.log(import_chalk19.default.gray(` All decisions stay on this machine.`));
|
|
11348
11774
|
} else {
|
|
11349
|
-
console.log(
|
|
11350
|
-
console.log(
|
|
11775
|
+
console.log(import_chalk19.default.green(`\u2705 Logged in \u2014 agent mode`));
|
|
11776
|
+
console.log(import_chalk19.default.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
11351
11777
|
}
|
|
11352
11778
|
});
|
|
11353
11779
|
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("<target>", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
@@ -11355,19 +11781,19 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
11355
11781
|
if (target === "claude") return await setupClaude();
|
|
11356
11782
|
if (target === "cursor") return await setupCursor();
|
|
11357
11783
|
if (target === "hud") return setupHud();
|
|
11358
|
-
console.error(
|
|
11784
|
+
console.error(import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11359
11785
|
process.exit(1);
|
|
11360
11786
|
});
|
|
11361
11787
|
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText("after", "\n Supported targets: claude gemini cursor hud").argument("[target]", "The agent to protect: claude | gemini | cursor | hud").action(async (target) => {
|
|
11362
11788
|
if (!target) {
|
|
11363
|
-
console.log(
|
|
11364
|
-
console.log(" Usage: " +
|
|
11789
|
+
console.log(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
11790
|
+
console.log(" Usage: " + import_chalk19.default.white("node9 setup <target>") + "\n");
|
|
11365
11791
|
console.log(" Targets:");
|
|
11366
|
-
console.log(" " +
|
|
11367
|
-
console.log(" " +
|
|
11368
|
-
console.log(" " +
|
|
11792
|
+
console.log(" " + import_chalk19.default.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
11793
|
+
console.log(" " + import_chalk19.default.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
11794
|
+
console.log(" " + import_chalk19.default.green("cursor") + " \u2014 Cursor (hook mode)");
|
|
11369
11795
|
process.stdout.write(
|
|
11370
|
-
" " +
|
|
11796
|
+
" " + import_chalk19.default.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
11371
11797
|
);
|
|
11372
11798
|
console.log("");
|
|
11373
11799
|
return;
|
|
@@ -11377,7 +11803,7 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
11377
11803
|
if (t === "claude") return await setupClaude();
|
|
11378
11804
|
if (t === "cursor") return await setupCursor();
|
|
11379
11805
|
if (t === "hud") return setupHud();
|
|
11380
|
-
console.error(
|
|
11806
|
+
console.error(import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`));
|
|
11381
11807
|
process.exit(1);
|
|
11382
11808
|
});
|
|
11383
11809
|
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText("after", "\n Supported targets: claude gemini cursor").argument("<target>", "The agent to remove from: claude | gemini | cursor").action((target) => {
|
|
@@ -11388,31 +11814,31 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
11388
11814
|
else if (target === "hud") fn = teardownHud;
|
|
11389
11815
|
else {
|
|
11390
11816
|
console.error(
|
|
11391
|
-
|
|
11817
|
+
import_chalk19.default.red(`Unknown target: "${target}". Supported: claude, gemini, cursor, hud`)
|
|
11392
11818
|
);
|
|
11393
11819
|
process.exit(1);
|
|
11394
11820
|
}
|
|
11395
|
-
console.log(
|
|
11821
|
+
console.log(import_chalk19.default.cyan(`
|
|
11396
11822
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
11397
11823
|
`));
|
|
11398
11824
|
try {
|
|
11399
11825
|
fn();
|
|
11400
11826
|
} catch (err2) {
|
|
11401
|
-
console.error(
|
|
11827
|
+
console.error(import_chalk19.default.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11402
11828
|
process.exit(1);
|
|
11403
11829
|
}
|
|
11404
|
-
console.log(
|
|
11830
|
+
console.log(import_chalk19.default.gray("\n Restart the agent for changes to take effect."));
|
|
11405
11831
|
});
|
|
11406
11832
|
program.command("uninstall").description("Remove all Node9 hooks and optionally delete config files").option("--purge", "Also delete ~/.node9/ directory (config, audit log, credentials)").action(async (options) => {
|
|
11407
|
-
console.log(
|
|
11408
|
-
console.log(
|
|
11833
|
+
console.log(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
11834
|
+
console.log(import_chalk19.default.bold("Stopping daemon..."));
|
|
11409
11835
|
try {
|
|
11410
11836
|
stopDaemon();
|
|
11411
|
-
console.log(
|
|
11837
|
+
console.log(import_chalk19.default.green(" \u2705 Daemon stopped"));
|
|
11412
11838
|
} catch {
|
|
11413
|
-
console.log(
|
|
11839
|
+
console.log(import_chalk19.default.blue(" \u2139\uFE0F Daemon was not running"));
|
|
11414
11840
|
}
|
|
11415
|
-
console.log(
|
|
11841
|
+
console.log(import_chalk19.default.bold("\nRemoving hooks..."));
|
|
11416
11842
|
let teardownFailed = false;
|
|
11417
11843
|
for (const [label, fn] of [
|
|
11418
11844
|
["Claude", teardownClaude],
|
|
@@ -11424,45 +11850,45 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
11424
11850
|
} catch (err2) {
|
|
11425
11851
|
teardownFailed = true;
|
|
11426
11852
|
console.error(
|
|
11427
|
-
|
|
11853
|
+
import_chalk19.default.red(
|
|
11428
11854
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
11429
11855
|
)
|
|
11430
11856
|
);
|
|
11431
11857
|
}
|
|
11432
11858
|
}
|
|
11433
11859
|
if (options.purge) {
|
|
11434
|
-
const node9Dir =
|
|
11435
|
-
if (
|
|
11860
|
+
const node9Dir = import_path31.default.join(import_os24.default.homedir(), ".node9");
|
|
11861
|
+
if (import_fs28.default.existsSync(node9Dir)) {
|
|
11436
11862
|
const confirmed = await (0, import_prompts2.confirm)({
|
|
11437
11863
|
message: `Permanently delete ${node9Dir} (config, audit log, credentials)?`,
|
|
11438
11864
|
default: false
|
|
11439
11865
|
});
|
|
11440
11866
|
if (confirmed) {
|
|
11441
|
-
|
|
11442
|
-
if (
|
|
11867
|
+
import_fs28.default.rmSync(node9Dir, { recursive: true });
|
|
11868
|
+
if (import_fs28.default.existsSync(node9Dir)) {
|
|
11443
11869
|
console.error(
|
|
11444
|
-
|
|
11870
|
+
import_chalk19.default.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
11445
11871
|
);
|
|
11446
11872
|
} else {
|
|
11447
|
-
console.log(
|
|
11873
|
+
console.log(import_chalk19.default.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
11448
11874
|
}
|
|
11449
11875
|
} else {
|
|
11450
|
-
console.log(
|
|
11876
|
+
console.log(import_chalk19.default.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
11451
11877
|
}
|
|
11452
11878
|
} else {
|
|
11453
|
-
console.log(
|
|
11879
|
+
console.log(import_chalk19.default.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
11454
11880
|
}
|
|
11455
11881
|
} else {
|
|
11456
11882
|
console.log(
|
|
11457
|
-
|
|
11883
|
+
import_chalk19.default.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
11458
11884
|
);
|
|
11459
11885
|
}
|
|
11460
11886
|
if (teardownFailed) {
|
|
11461
|
-
console.error(
|
|
11887
|
+
console.error(import_chalk19.default.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
11462
11888
|
process.exit(1);
|
|
11463
11889
|
}
|
|
11464
|
-
console.log(
|
|
11465
|
-
console.log(
|
|
11890
|
+
console.log(import_chalk19.default.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
11891
|
+
console.log(import_chalk19.default.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
11466
11892
|
});
|
|
11467
11893
|
registerDoctorCommand(program, version);
|
|
11468
11894
|
program.command("explain").description(
|
|
@@ -11475,7 +11901,7 @@ program.command("explain").description(
|
|
|
11475
11901
|
try {
|
|
11476
11902
|
args = JSON.parse(trimmed);
|
|
11477
11903
|
} catch {
|
|
11478
|
-
console.error(
|
|
11904
|
+
console.error(import_chalk19.default.red(`
|
|
11479
11905
|
\u274C Invalid JSON: ${trimmed}
|
|
11480
11906
|
`));
|
|
11481
11907
|
process.exit(1);
|
|
@@ -11486,54 +11912,54 @@ program.command("explain").description(
|
|
|
11486
11912
|
}
|
|
11487
11913
|
const result = await explainPolicy(tool, args);
|
|
11488
11914
|
console.log("");
|
|
11489
|
-
console.log(
|
|
11915
|
+
console.log(import_chalk19.default.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
11490
11916
|
console.log("");
|
|
11491
|
-
console.log(` ${
|
|
11917
|
+
console.log(` ${import_chalk19.default.bold("Tool:")} ${import_chalk19.default.white(result.tool)}`);
|
|
11492
11918
|
if (argsRaw) {
|
|
11493
11919
|
const preview = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
11494
|
-
console.log(` ${
|
|
11920
|
+
console.log(` ${import_chalk19.default.bold("Input:")} ${import_chalk19.default.gray(preview)}`);
|
|
11495
11921
|
}
|
|
11496
11922
|
console.log("");
|
|
11497
|
-
console.log(
|
|
11923
|
+
console.log(import_chalk19.default.bold("Config Sources (Waterfall):"));
|
|
11498
11924
|
for (const tier of result.waterfall) {
|
|
11499
|
-
const num =
|
|
11925
|
+
const num = import_chalk19.default.gray(` ${tier.tier}.`);
|
|
11500
11926
|
const label = tier.label.padEnd(16);
|
|
11501
11927
|
let statusStr;
|
|
11502
11928
|
if (tier.tier === 1) {
|
|
11503
|
-
statusStr =
|
|
11929
|
+
statusStr = import_chalk19.default.gray(tier.note ?? "");
|
|
11504
11930
|
} else if (tier.status === "active") {
|
|
11505
|
-
const loc = tier.path ?
|
|
11506
|
-
const note = tier.note ?
|
|
11507
|
-
statusStr =
|
|
11931
|
+
const loc = tier.path ? import_chalk19.default.gray(tier.path) : "";
|
|
11932
|
+
const note = tier.note ? import_chalk19.default.gray(`(${tier.note})`) : "";
|
|
11933
|
+
statusStr = import_chalk19.default.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
11508
11934
|
} else {
|
|
11509
|
-
statusStr =
|
|
11935
|
+
statusStr = import_chalk19.default.gray("\u25CB " + (tier.note ?? "not found"));
|
|
11510
11936
|
}
|
|
11511
|
-
console.log(`${num} ${
|
|
11937
|
+
console.log(`${num} ${import_chalk19.default.white(label)} ${statusStr}`);
|
|
11512
11938
|
}
|
|
11513
11939
|
console.log("");
|
|
11514
|
-
console.log(
|
|
11940
|
+
console.log(import_chalk19.default.bold("Policy Evaluation:"));
|
|
11515
11941
|
for (const step of result.steps) {
|
|
11516
11942
|
const isFinal = step.isFinal;
|
|
11517
11943
|
let icon;
|
|
11518
|
-
if (step.outcome === "allow") icon =
|
|
11519
|
-
else if (step.outcome === "review") icon =
|
|
11520
|
-
else if (step.outcome === "skip") icon =
|
|
11521
|
-
else icon =
|
|
11944
|
+
if (step.outcome === "allow") icon = import_chalk19.default.green(" \u2705");
|
|
11945
|
+
else if (step.outcome === "review") icon = import_chalk19.default.red(" \u{1F534}");
|
|
11946
|
+
else if (step.outcome === "skip") icon = import_chalk19.default.gray(" \u2500 ");
|
|
11947
|
+
else icon = import_chalk19.default.gray(" \u25CB ");
|
|
11522
11948
|
const name = step.name.padEnd(18);
|
|
11523
|
-
const nameStr = isFinal ?
|
|
11524
|
-
const detail = isFinal ?
|
|
11525
|
-
const arrow = isFinal ?
|
|
11949
|
+
const nameStr = isFinal ? import_chalk19.default.white.bold(name) : import_chalk19.default.white(name);
|
|
11950
|
+
const detail = isFinal ? import_chalk19.default.white(step.detail) : import_chalk19.default.gray(step.detail);
|
|
11951
|
+
const arrow = isFinal ? import_chalk19.default.yellow(" \u2190 STOP") : "";
|
|
11526
11952
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
11527
11953
|
}
|
|
11528
11954
|
console.log("");
|
|
11529
11955
|
if (result.decision === "allow") {
|
|
11530
|
-
console.log(
|
|
11956
|
+
console.log(import_chalk19.default.green.bold(" Decision: \u2705 ALLOW") + import_chalk19.default.gray(" \u2014 no approval needed"));
|
|
11531
11957
|
} else {
|
|
11532
11958
|
console.log(
|
|
11533
|
-
|
|
11959
|
+
import_chalk19.default.red.bold(" Decision: \u{1F534} REVIEW") + import_chalk19.default.gray(" \u2014 human approval required")
|
|
11534
11960
|
);
|
|
11535
11961
|
if (result.blockedByLabel) {
|
|
11536
|
-
console.log(
|
|
11962
|
+
console.log(import_chalk19.default.gray(` Reason: ${result.blockedByLabel}`));
|
|
11537
11963
|
}
|
|
11538
11964
|
}
|
|
11539
11965
|
console.log("");
|
|
@@ -11547,13 +11973,14 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
11547
11973
|
try {
|
|
11548
11974
|
await startTail2(options);
|
|
11549
11975
|
} catch (err2) {
|
|
11550
|
-
console.error(
|
|
11976
|
+
console.error(import_chalk19.default.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
11551
11977
|
process.exit(1);
|
|
11552
11978
|
}
|
|
11553
11979
|
});
|
|
11554
11980
|
registerWatchCommand(program);
|
|
11555
11981
|
registerMcpGatewayCommand(program);
|
|
11556
11982
|
registerMcpServerCommand(program);
|
|
11983
|
+
registerMcpPinCommand(program);
|
|
11557
11984
|
registerCheckCommand(program);
|
|
11558
11985
|
registerLogCommand(program);
|
|
11559
11986
|
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
@@ -11578,14 +12005,14 @@ Claude Code spawns this command every ~300ms and writes a JSON payload to stdin.
|
|
|
11578
12005
|
Run "node9 addto claude" to register it as the statusLine.`
|
|
11579
12006
|
).argument("[subcommand]", 'Optional: "debug on" / "debug off" to toggle stdin logging').argument("[state]", 'on|off \u2014 used with "debug" subcommand').action(async (subcommand, state) => {
|
|
11580
12007
|
if (subcommand === "debug") {
|
|
11581
|
-
const flagFile =
|
|
12008
|
+
const flagFile = import_path31.default.join(import_os24.default.homedir(), ".node9", "hud-debug");
|
|
11582
12009
|
if (state === "on") {
|
|
11583
|
-
|
|
11584
|
-
|
|
12010
|
+
import_fs28.default.mkdirSync(import_path31.default.dirname(flagFile), { recursive: true });
|
|
12011
|
+
import_fs28.default.writeFileSync(flagFile, "");
|
|
11585
12012
|
console.log("HUD debug logging enabled \u2192 ~/.node9/hud-debug.log");
|
|
11586
12013
|
console.log("Tail it with: tail -f ~/.node9/hud-debug.log");
|
|
11587
12014
|
} else if (state === "off") {
|
|
11588
|
-
if (
|
|
12015
|
+
if (import_fs28.default.existsSync(flagFile)) import_fs28.default.unlinkSync(flagFile);
|
|
11589
12016
|
console.log("HUD debug logging disabled.");
|
|
11590
12017
|
} else {
|
|
11591
12018
|
console.error("Usage: node9 hud debug on|off");
|
|
@@ -11600,7 +12027,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
11600
12027
|
const ms = parseDuration(options.duration);
|
|
11601
12028
|
if (ms === null) {
|
|
11602
12029
|
console.error(
|
|
11603
|
-
|
|
12030
|
+
import_chalk19.default.red(`
|
|
11604
12031
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
11605
12032
|
`)
|
|
11606
12033
|
);
|
|
@@ -11608,20 +12035,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
11608
12035
|
}
|
|
11609
12036
|
pauseNode9(ms, options.duration);
|
|
11610
12037
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
11611
|
-
console.log(
|
|
12038
|
+
console.log(import_chalk19.default.yellow(`
|
|
11612
12039
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
11613
|
-
console.log(
|
|
11614
|
-
console.log(
|
|
12040
|
+
console.log(import_chalk19.default.gray(` All tool calls will be allowed without review.`));
|
|
12041
|
+
console.log(import_chalk19.default.gray(` Run "node9 resume" to re-enable early.
|
|
11615
12042
|
`));
|
|
11616
12043
|
});
|
|
11617
12044
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
11618
12045
|
const { paused } = checkPause();
|
|
11619
12046
|
if (!paused) {
|
|
11620
|
-
console.log(
|
|
12047
|
+
console.log(import_chalk19.default.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
11621
12048
|
return;
|
|
11622
12049
|
}
|
|
11623
12050
|
resumeNode9();
|
|
11624
|
-
console.log(
|
|
12051
|
+
console.log(import_chalk19.default.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
11625
12052
|
});
|
|
11626
12053
|
var HOOK_BASED_AGENTS = {
|
|
11627
12054
|
claude: "claude",
|
|
@@ -11634,15 +12061,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
11634
12061
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
11635
12062
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
11636
12063
|
console.error(
|
|
11637
|
-
|
|
12064
|
+
import_chalk19.default.yellow(`
|
|
11638
12065
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
11639
12066
|
);
|
|
11640
|
-
console.error(
|
|
12067
|
+
console.error(import_chalk19.default.white(`
|
|
11641
12068
|
"${target}" uses its own hook system. Use:`));
|
|
11642
12069
|
console.error(
|
|
11643
|
-
|
|
12070
|
+
import_chalk19.default.green(` node9 addto ${target} `) + import_chalk19.default.gray("# one-time setup")
|
|
11644
12071
|
);
|
|
11645
|
-
console.error(
|
|
12072
|
+
console.error(import_chalk19.default.green(` ${target} `) + import_chalk19.default.gray("# run normally"));
|
|
11646
12073
|
process.exit(1);
|
|
11647
12074
|
}
|
|
11648
12075
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -11659,7 +12086,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
11659
12086
|
}
|
|
11660
12087
|
);
|
|
11661
12088
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
11662
|
-
console.error(
|
|
12089
|
+
console.error(import_chalk19.default.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
11663
12090
|
const daemonReady = await autoStartDaemonAndWait();
|
|
11664
12091
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
11665
12092
|
}
|
|
@@ -11672,12 +12099,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
11672
12099
|
}
|
|
11673
12100
|
if (!result.approved) {
|
|
11674
12101
|
console.error(
|
|
11675
|
-
|
|
12102
|
+
import_chalk19.default.red(`
|
|
11676
12103
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
11677
12104
|
);
|
|
11678
12105
|
process.exit(1);
|
|
11679
12106
|
}
|
|
11680
|
-
console.error(
|
|
12107
|
+
console.error(import_chalk19.default.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
11681
12108
|
await runProxy(fullCommand);
|
|
11682
12109
|
} else {
|
|
11683
12110
|
program.help();
|
|
@@ -11692,9 +12119,9 @@ if (process.argv[2] !== "daemon") {
|
|
|
11692
12119
|
const isCheckHook = process.argv[2] === "check";
|
|
11693
12120
|
if (isCheckHook) {
|
|
11694
12121
|
if (process.env.NODE9_DEBUG === "1" || getConfig().settings.enableHookLogDebug) {
|
|
11695
|
-
const logPath =
|
|
12122
|
+
const logPath = import_path31.default.join(import_os24.default.homedir(), ".node9", "hook-debug.log");
|
|
11696
12123
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
11697
|
-
|
|
12124
|
+
import_fs28.default.appendFileSync(logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] UNHANDLED: ${msg}
|
|
11698
12125
|
`);
|
|
11699
12126
|
}
|
|
11700
12127
|
process.exit(0);
|