@node9/proxy 1.21.0 → 1.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +365 -390
- package/dist/cli.mjs +364 -389
- package/dist/dashboard.mjs +48 -10
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -13628,12 +13628,12 @@ __export(tail_exports, {
|
|
|
13628
13628
|
startTail: () => startTail
|
|
13629
13629
|
});
|
|
13630
13630
|
import http2 from "http";
|
|
13631
|
-
import
|
|
13631
|
+
import chalk29 from "chalk";
|
|
13632
13632
|
import fs45 from "fs";
|
|
13633
13633
|
import os41 from "os";
|
|
13634
13634
|
import path47 from "path";
|
|
13635
13635
|
import readline6 from "readline";
|
|
13636
|
-
import { spawn as
|
|
13636
|
+
import { spawn as spawn8 } from "child_process";
|
|
13637
13637
|
function shortenPathSummary(s) {
|
|
13638
13638
|
if (!s || !s.startsWith("/")) return s;
|
|
13639
13639
|
const parts = s.split("/").filter(Boolean);
|
|
@@ -13708,10 +13708,10 @@ function readSessionUsage() {
|
|
|
13708
13708
|
}
|
|
13709
13709
|
}
|
|
13710
13710
|
function formatContextStat(stat) {
|
|
13711
|
-
const pctColor = stat.fillPct >= 80 ?
|
|
13711
|
+
const pctColor = stat.fillPct >= 80 ? chalk29.red : stat.fillPct >= 50 ? chalk29.yellow : chalk29.cyan;
|
|
13712
13712
|
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
13713
13713
|
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
13714
|
-
return
|
|
13714
|
+
return chalk29.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk29.dim(
|
|
13715
13715
|
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
13716
13716
|
);
|
|
13717
13717
|
}
|
|
@@ -13734,11 +13734,11 @@ function agentLabel(agent, mcpServer, sessionId) {
|
|
|
13734
13734
|
const tag = sessionTag(sessionId);
|
|
13735
13735
|
const tagSuffix = tag ? `\xB7${tag}` : "";
|
|
13736
13736
|
if (!agent || agent === "Terminal") {
|
|
13737
|
-
return mcpServer ?
|
|
13737
|
+
return mcpServer ? chalk29.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
13738
13738
|
}
|
|
13739
13739
|
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
13740
|
-
if (!short) return mcpServer ?
|
|
13741
|
-
return mcpServer ?
|
|
13740
|
+
if (!short) return mcpServer ? chalk29.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
13741
|
+
return mcpServer ? chalk29.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : chalk29.dim(`[${short}${tagSuffix}] `);
|
|
13742
13742
|
}
|
|
13743
13743
|
function formatBase(activity) {
|
|
13744
13744
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
@@ -13746,20 +13746,20 @@ function formatBase(activity) {
|
|
|
13746
13746
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
13747
13747
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os41.homedir(), "~");
|
|
13748
13748
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
13749
|
-
return `${
|
|
13749
|
+
return `${chalk29.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk29.white.bold(toolName)} ${chalk29.dim(argsPreview)}`;
|
|
13750
13750
|
}
|
|
13751
13751
|
function renderResult(activity, result) {
|
|
13752
13752
|
const base = formatBase(activity);
|
|
13753
13753
|
let status;
|
|
13754
13754
|
if (result.status === "allow") {
|
|
13755
|
-
status =
|
|
13755
|
+
status = chalk29.green("\u2713 ALLOW");
|
|
13756
13756
|
} else if (result.status === "dlp") {
|
|
13757
|
-
status =
|
|
13757
|
+
status = chalk29.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
13758
13758
|
} else {
|
|
13759
|
-
status =
|
|
13759
|
+
status = chalk29.red("\u2717 BLOCK");
|
|
13760
13760
|
}
|
|
13761
13761
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
13762
|
-
const costSuffix = cost == null ? "" :
|
|
13762
|
+
const costSuffix = cost == null ? "" : chalk29.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
13763
13763
|
if (process.stdout.isTTY) {
|
|
13764
13764
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
13765
13765
|
readline6.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -13776,7 +13776,7 @@ function renderResult(activity, result) {
|
|
|
13776
13776
|
}
|
|
13777
13777
|
function renderPending(activity) {
|
|
13778
13778
|
if (!process.stdout.isTTY) return;
|
|
13779
|
-
const line = `${formatBase(activity)} ${
|
|
13779
|
+
const line = `${formatBase(activity)} ${chalk29.yellow("\u25CF \u2026")}`;
|
|
13780
13780
|
pendingShownForId = activity.id;
|
|
13781
13781
|
pendingWrappedLines = wrappedLineCount(line);
|
|
13782
13782
|
process.stdout.write(`${line}\r`);
|
|
@@ -13788,7 +13788,7 @@ async function ensureDaemon() {
|
|
|
13788
13788
|
const { port } = JSON.parse(fs45.readFileSync(PID_FILE, "utf-8"));
|
|
13789
13789
|
pidPort = port;
|
|
13790
13790
|
} catch {
|
|
13791
|
-
console.error(
|
|
13791
|
+
console.error(chalk29.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
13792
13792
|
}
|
|
13793
13793
|
}
|
|
13794
13794
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -13799,8 +13799,8 @@ async function ensureDaemon() {
|
|
|
13799
13799
|
if (res.ok) return checkPort;
|
|
13800
13800
|
} catch {
|
|
13801
13801
|
}
|
|
13802
|
-
console.log(
|
|
13803
|
-
const child =
|
|
13802
|
+
console.log(chalk29.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
13803
|
+
const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
|
|
13804
13804
|
detached: true,
|
|
13805
13805
|
stdio: "ignore",
|
|
13806
13806
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
@@ -13816,7 +13816,7 @@ async function ensureDaemon() {
|
|
|
13816
13816
|
} catch {
|
|
13817
13817
|
}
|
|
13818
13818
|
}
|
|
13819
|
-
console.error(
|
|
13819
|
+
console.error(chalk29.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
13820
13820
|
process.exit(1);
|
|
13821
13821
|
}
|
|
13822
13822
|
function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
@@ -13885,7 +13885,7 @@ function buildCardLines(req, localCount = 0) {
|
|
|
13885
13885
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
13886
13886
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
13887
13887
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
13888
|
-
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${
|
|
13888
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk29.dim(`(${req.agent})`)}` : "";
|
|
13889
13889
|
const lines = [
|
|
13890
13890
|
``,
|
|
13891
13891
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
@@ -13954,7 +13954,7 @@ function approverStatusLine() {
|
|
|
13954
13954
|
const a = readApproversFromDisk();
|
|
13955
13955
|
const fmt = (label, key) => {
|
|
13956
13956
|
const on = a[key] !== false;
|
|
13957
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
13957
|
+
return `[${key[0]}]${label.slice(1)} ${on ? chalk29.green("\u2713") : chalk29.dim("\u2717")}`;
|
|
13958
13958
|
};
|
|
13959
13959
|
return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
13960
13960
|
}
|
|
@@ -13999,7 +13999,7 @@ async function startTail(options = {}) {
|
|
|
13999
13999
|
req2.end();
|
|
14000
14000
|
});
|
|
14001
14001
|
if (result.ok) {
|
|
14002
|
-
console.log(
|
|
14002
|
+
console.log(chalk29.green("\u2713 Flight Recorder buffer cleared."));
|
|
14003
14003
|
} else if (result.code === "ECONNREFUSED") {
|
|
14004
14004
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
14005
14005
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -14045,7 +14045,7 @@ async function startTail(options = {}) {
|
|
|
14045
14045
|
const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
14046
14046
|
if (channel) {
|
|
14047
14047
|
toggleApprover(channel);
|
|
14048
|
-
console.log(
|
|
14048
|
+
console.log(chalk29.dim(` Approvers: ${approverStatusLine()}`));
|
|
14049
14049
|
}
|
|
14050
14050
|
};
|
|
14051
14051
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -14111,7 +14111,7 @@ async function startTail(options = {}) {
|
|
|
14111
14111
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
14112
14112
|
)
|
|
14113
14113
|
);
|
|
14114
|
-
const decisionStamp = action === "always-allow" ?
|
|
14114
|
+
const decisionStamp = action === "always-allow" ? chalk29.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk29.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk29.green("\u2713 ALLOWED") : action === "redirect" ? chalk29.yellow("\u21A9 REDIRECT AI") : chalk29.red("\u2717 DENIED");
|
|
14115
14115
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
14116
14116
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
14117
14117
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -14162,7 +14162,7 @@ async function startTail(options = {}) {
|
|
|
14162
14162
|
);
|
|
14163
14163
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
14164
14164
|
if (externalDecision) {
|
|
14165
|
-
const source = externalDecision === "allow" ?
|
|
14165
|
+
const source = externalDecision === "allow" ? chalk29.green("\u2713 ALLOWED") : chalk29.red("\u2717 DENIED");
|
|
14166
14166
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
14167
14167
|
}
|
|
14168
14168
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -14210,25 +14210,25 @@ async function startTail(options = {}) {
|
|
|
14210
14210
|
if (unackedDlp > 0) {
|
|
14211
14211
|
console.log("");
|
|
14212
14212
|
console.log(
|
|
14213
|
-
|
|
14213
|
+
chalk29.bgRed.white.bold(
|
|
14214
14214
|
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
14215
14215
|
)
|
|
14216
14216
|
);
|
|
14217
14217
|
}
|
|
14218
14218
|
} catch {
|
|
14219
14219
|
}
|
|
14220
|
-
console.log(
|
|
14220
|
+
console.log(chalk29.cyan.bold(`
|
|
14221
14221
|
\u{1F6F0}\uFE0F Node9 tail`));
|
|
14222
14222
|
if (canApprove) {
|
|
14223
|
-
console.log(
|
|
14224
|
-
console.log(
|
|
14223
|
+
console.log(chalk29.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
14224
|
+
console.log(chalk29.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
14225
14225
|
}
|
|
14226
14226
|
const ctxStat = readSessionUsage();
|
|
14227
14227
|
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
14228
14228
|
if (options.history) {
|
|
14229
|
-
console.log(
|
|
14229
|
+
console.log(chalk29.dim("Showing history + live events.\n"));
|
|
14230
14230
|
} else {
|
|
14231
|
-
console.log(
|
|
14231
|
+
console.log(chalk29.dim("Showing live events only. Use --history to include past.\n"));
|
|
14232
14232
|
}
|
|
14233
14233
|
process.on("SIGINT", () => {
|
|
14234
14234
|
exitIdleMode();
|
|
@@ -14238,7 +14238,7 @@ async function startTail(options = {}) {
|
|
|
14238
14238
|
readline6.clearLine(process.stdout, 0);
|
|
14239
14239
|
readline6.cursorTo(process.stdout, 0);
|
|
14240
14240
|
}
|
|
14241
|
-
console.log(
|
|
14241
|
+
console.log(chalk29.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
14242
14242
|
process.exit(0);
|
|
14243
14243
|
});
|
|
14244
14244
|
const STALL_THRESHOLD_MS = 6e4;
|
|
@@ -14250,7 +14250,7 @@ async function startTail(options = {}) {
|
|
|
14250
14250
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
14251
14251
|
console.log("");
|
|
14252
14252
|
console.log(
|
|
14253
|
-
|
|
14253
|
+
chalk29.yellow(
|
|
14254
14254
|
"\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
|
|
14255
14255
|
)
|
|
14256
14256
|
);
|
|
@@ -14267,7 +14267,7 @@ async function startTail(options = {}) {
|
|
|
14267
14267
|
},
|
|
14268
14268
|
(res) => {
|
|
14269
14269
|
if (res.statusCode !== 200) {
|
|
14270
|
-
console.error(
|
|
14270
|
+
console.error(chalk29.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
14271
14271
|
process.exit(1);
|
|
14272
14272
|
}
|
|
14273
14273
|
if (canApprove) enterIdleMode();
|
|
@@ -14298,7 +14298,7 @@ async function startTail(options = {}) {
|
|
|
14298
14298
|
readline6.clearLine(process.stdout, 0);
|
|
14299
14299
|
readline6.cursorTo(process.stdout, 0);
|
|
14300
14300
|
}
|
|
14301
|
-
console.log(
|
|
14301
|
+
console.log(chalk29.red("\n\u274C Daemon disconnected."));
|
|
14302
14302
|
process.exit(1);
|
|
14303
14303
|
});
|
|
14304
14304
|
}
|
|
@@ -14311,7 +14311,7 @@ async function startTail(options = {}) {
|
|
|
14311
14311
|
const parsed = JSON.parse(rawData);
|
|
14312
14312
|
const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
|
|
14313
14313
|
console.log("");
|
|
14314
|
-
console.log(
|
|
14314
|
+
console.log(chalk29.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
|
|
14315
14315
|
} catch {
|
|
14316
14316
|
}
|
|
14317
14317
|
return;
|
|
@@ -14396,9 +14396,9 @@ async function startTail(options = {}) {
|
|
|
14396
14396
|
const rawSummary = data.argsSummary ?? data.tool;
|
|
14397
14397
|
const summary = shortenPathSummary(rawSummary);
|
|
14398
14398
|
const fileCount = data.fileCount ?? 0;
|
|
14399
|
-
const files = fileCount > 0 ?
|
|
14399
|
+
const files = fileCount > 0 ? chalk29.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
14400
14400
|
process.stdout.write(
|
|
14401
|
-
`${
|
|
14401
|
+
`${chalk29.dim(time)} ${chalk29.cyan("\u{1F4F8} snapshot")} ${chalk29.dim(hash)} ${summary}${files}
|
|
14402
14402
|
`
|
|
14403
14403
|
);
|
|
14404
14404
|
return;
|
|
@@ -14415,18 +14415,18 @@ async function startTail(options = {}) {
|
|
|
14415
14415
|
if (event === "execution-result") {
|
|
14416
14416
|
const exec = data;
|
|
14417
14417
|
const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
|
|
14418
|
-
const arrow = exec.isError ?
|
|
14418
|
+
const arrow = exec.isError ? chalk29.red(" \u21B3 \u2717") : chalk29.green(" \u21B3 \u2713");
|
|
14419
14419
|
const label = agentLabel(exec.agent, exec.mcpServer);
|
|
14420
14420
|
const tool = (exec.tool ?? "").slice(0, 16);
|
|
14421
|
-
const duration = typeof exec.durationMs === "number" ?
|
|
14421
|
+
const duration = typeof exec.durationMs === "number" ? chalk29.dim(` (${exec.durationMs}ms)`) : "";
|
|
14422
14422
|
console.log(
|
|
14423
|
-
`${
|
|
14423
|
+
`${chalk29.gray(time)} ${arrow} ${label}${chalk29.dim(tool)}${chalk29.dim(" completed")}${duration}`
|
|
14424
14424
|
);
|
|
14425
14425
|
}
|
|
14426
14426
|
}
|
|
14427
14427
|
req.on("error", (err2) => {
|
|
14428
14428
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
14429
|
-
console.error(
|
|
14429
|
+
console.error(chalk29.red(`
|
|
14430
14430
|
\u274C ${msg}`));
|
|
14431
14431
|
process.exit(1);
|
|
14432
14432
|
});
|
|
@@ -14862,7 +14862,7 @@ init_core();
|
|
|
14862
14862
|
init_setup();
|
|
14863
14863
|
init_daemon2();
|
|
14864
14864
|
import { Command } from "commander";
|
|
14865
|
-
import
|
|
14865
|
+
import chalk30 from "chalk";
|
|
14866
14866
|
import fs47 from "fs";
|
|
14867
14867
|
import path49 from "path";
|
|
14868
14868
|
import os43 from "os";
|
|
@@ -15656,7 +15656,7 @@ function detectAiAgent(payload) {
|
|
|
15656
15656
|
return "Terminal";
|
|
15657
15657
|
}
|
|
15658
15658
|
function registerCheckCommand(program2) {
|
|
15659
|
-
program2.command("check").description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").action(async (data) => {
|
|
15659
|
+
program2.command("check", { hidden: true }).description("Hook handler \u2014 evaluates a tool call before execution").argument("[data]", "JSON string of the tool call").action(async (data) => {
|
|
15660
15660
|
const processPayload = async (raw) => {
|
|
15661
15661
|
try {
|
|
15662
15662
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
@@ -16054,7 +16054,7 @@ function sanitize3(value) {
|
|
|
16054
16054
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
16055
16055
|
}
|
|
16056
16056
|
function registerLogCommand(program2) {
|
|
16057
|
-
program2.command("log").description("PostToolUse hook \u2014 records executed tool calls").argument("[data]", "JSON string of the tool call").action(async (data) => {
|
|
16057
|
+
program2.command("log", { hidden: true }).description("PostToolUse hook \u2014 records executed tool calls").argument("[data]", "JSON string of the tool call").action(async (data) => {
|
|
16058
16058
|
const logPayload = async (raw) => {
|
|
16059
16059
|
try {
|
|
16060
16060
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
@@ -16807,6 +16807,35 @@ function isTestEntry(entry, testTs) {
|
|
|
16807
16807
|
}
|
|
16808
16808
|
return false;
|
|
16809
16809
|
}
|
|
16810
|
+
var SUPERSEDE_WINDOW_MS = 6e4;
|
|
16811
|
+
function buildSupersededSet(entries) {
|
|
16812
|
+
const superseded = /* @__PURE__ */ new Set();
|
|
16813
|
+
for (let i = 0; i < entries.length; i++) {
|
|
16814
|
+
const e = entries[i];
|
|
16815
|
+
if (e.decision !== "deny") continue;
|
|
16816
|
+
if (e.checkedBy !== "smart-rule-block-override") continue;
|
|
16817
|
+
if (!e.argsHash || !e.sessionId) continue;
|
|
16818
|
+
const eTs = Date.parse(e.ts);
|
|
16819
|
+
if (Number.isNaN(eTs)) continue;
|
|
16820
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
16821
|
+
const next = entries[j];
|
|
16822
|
+
const nextTs = Date.parse(next.ts);
|
|
16823
|
+
if (Number.isNaN(nextTs)) continue;
|
|
16824
|
+
if (nextTs - eTs > SUPERSEDE_WINDOW_MS) break;
|
|
16825
|
+
if (next.argsHash !== e.argsHash) continue;
|
|
16826
|
+
if (next.sessionId !== e.sessionId) continue;
|
|
16827
|
+
if (next.tool !== e.tool) continue;
|
|
16828
|
+
if (next.decision === "allow" && next.checkedBy === "daemon") {
|
|
16829
|
+
superseded.add(`${e.ts}|${e.argsHash}`);
|
|
16830
|
+
break;
|
|
16831
|
+
}
|
|
16832
|
+
}
|
|
16833
|
+
}
|
|
16834
|
+
return superseded;
|
|
16835
|
+
}
|
|
16836
|
+
function supersedeKey(e) {
|
|
16837
|
+
return `${e.ts}|${e.argsHash ?? ""}`;
|
|
16838
|
+
}
|
|
16810
16839
|
function getDateRange(period, now) {
|
|
16811
16840
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
16812
16841
|
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
|
@@ -17258,6 +17287,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
17258
17287
|
}
|
|
17259
17288
|
return true;
|
|
17260
17289
|
});
|
|
17290
|
+
const superseded = buildSupersededSet(entries);
|
|
17261
17291
|
let userApproved = 0;
|
|
17262
17292
|
let userDenied = 0;
|
|
17263
17293
|
let timedOut = 0;
|
|
@@ -17275,6 +17305,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
17275
17305
|
const dailyMap = /* @__PURE__ */ new Map();
|
|
17276
17306
|
const hourMap = /* @__PURE__ */ new Map();
|
|
17277
17307
|
for (const e of entries) {
|
|
17308
|
+
if (superseded.has(supersedeKey(e))) continue;
|
|
17278
17309
|
const allow = isAllow(e.decision);
|
|
17279
17310
|
const dateKey = e.ts.slice(0, 10);
|
|
17280
17311
|
const userInteracted = e.source === "daemon";
|
|
@@ -17285,6 +17316,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
17285
17316
|
if (e.checkedBy === "timeout") timedOut++;
|
|
17286
17317
|
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
17287
17318
|
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
17319
|
+
else if (e.checkedBy === "local-decision") userDenied++;
|
|
17288
17320
|
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
17289
17321
|
}
|
|
17290
17322
|
if (e.checkedBy === "loop-detected") loopHits++;
|
|
@@ -17292,8 +17324,11 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
17292
17324
|
t.calls++;
|
|
17293
17325
|
if (!allow) t.blocked++;
|
|
17294
17326
|
toolMap.set(e.tool, t);
|
|
17295
|
-
if (!allow
|
|
17296
|
-
|
|
17327
|
+
if (!allow) {
|
|
17328
|
+
const key = e.checkedBy ?? (e.source === "daemon" ? "local-decision" : null);
|
|
17329
|
+
if (key) {
|
|
17330
|
+
blockMap.set(key, (blockMap.get(key) ?? 0) + 1);
|
|
17331
|
+
}
|
|
17297
17332
|
}
|
|
17298
17333
|
if (!allow && e.ruleName) {
|
|
17299
17334
|
ruleMap.set(e.ruleName, (ruleMap.get(e.ruleName) ?? 0) + 1);
|
|
@@ -17322,7 +17357,9 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
17322
17357
|
start,
|
|
17323
17358
|
end,
|
|
17324
17359
|
excludedTests,
|
|
17325
|
-
|
|
17360
|
+
// Subtract superseded rows so the headline event count agrees with
|
|
17361
|
+
// the bucket counters (which skip them in the loop above).
|
|
17362
|
+
total: entries.length - superseded.size,
|
|
17326
17363
|
userApproved,
|
|
17327
17364
|
userDenied,
|
|
17328
17365
|
timedOut,
|
|
@@ -18220,7 +18257,7 @@ function registerInitCommand(program2) {
|
|
|
18220
18257
|
const agentList = found.join(", ");
|
|
18221
18258
|
console.log(chalk16.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
18222
18259
|
console.log("");
|
|
18223
|
-
console.log(chalk16.white(" Watch live: ") + chalk16.cyan("node9
|
|
18260
|
+
console.log(chalk16.white(" Watch live: ") + chalk16.cyan("node9 monitor"));
|
|
18224
18261
|
console.log("");
|
|
18225
18262
|
console.log(chalk16.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"));
|
|
18226
18263
|
console.log(
|
|
@@ -18517,72 +18554,11 @@ function registerUndoCommand(program2) {
|
|
|
18517
18554
|
});
|
|
18518
18555
|
}
|
|
18519
18556
|
|
|
18520
|
-
// src/cli/commands/watch.ts
|
|
18521
|
-
init_daemon();
|
|
18522
|
-
import chalk19 from "chalk";
|
|
18523
|
-
import { spawn as spawn7, spawnSync as spawnSync4 } from "child_process";
|
|
18524
|
-
function registerWatchCommand(program2) {
|
|
18525
|
-
program2.command("watch").description("Run a command under Node9 watch mode (daemon stays alive for the session)").argument("<command>", "Command to run").argument("[args...]", "Arguments for the command").action(async (cmd, args) => {
|
|
18526
|
-
let port = DAEMON_PORT;
|
|
18527
|
-
try {
|
|
18528
|
-
const res = await fetch(`http://127.0.0.1:${DAEMON_PORT}/settings`, {
|
|
18529
|
-
signal: AbortSignal.timeout(500)
|
|
18530
|
-
});
|
|
18531
|
-
if (res.ok) {
|
|
18532
|
-
const data = await res.json();
|
|
18533
|
-
if (typeof data.port === "number") port = data.port;
|
|
18534
|
-
} else {
|
|
18535
|
-
throw new Error("not running");
|
|
18536
|
-
}
|
|
18537
|
-
} catch {
|
|
18538
|
-
console.error(chalk19.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
18539
|
-
const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
|
|
18540
|
-
detached: true,
|
|
18541
|
-
stdio: "ignore",
|
|
18542
|
-
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
|
|
18543
|
-
});
|
|
18544
|
-
child.unref();
|
|
18545
|
-
let ready = false;
|
|
18546
|
-
for (let i = 0; i < 20; i++) {
|
|
18547
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
18548
|
-
try {
|
|
18549
|
-
const r = await fetch(`http://127.0.0.1:${DAEMON_PORT}/settings`, {
|
|
18550
|
-
signal: AbortSignal.timeout(500)
|
|
18551
|
-
});
|
|
18552
|
-
if (r.ok) {
|
|
18553
|
-
ready = true;
|
|
18554
|
-
break;
|
|
18555
|
-
}
|
|
18556
|
-
} catch {
|
|
18557
|
-
}
|
|
18558
|
-
}
|
|
18559
|
-
if (!ready) {
|
|
18560
|
-
console.error(chalk19.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
18561
|
-
process.exit(1);
|
|
18562
|
-
}
|
|
18563
|
-
}
|
|
18564
|
-
console.error(
|
|
18565
|
-
chalk19.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk19.dim(` \u2192 localhost:${port}`) + chalk19.dim(
|
|
18566
|
-
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
18567
|
-
)
|
|
18568
|
-
);
|
|
18569
|
-
const result = spawnSync4(cmd, args, {
|
|
18570
|
-
stdio: "inherit",
|
|
18571
|
-
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
18572
|
-
});
|
|
18573
|
-
if (result.error) {
|
|
18574
|
-
console.error(chalk19.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
18575
|
-
process.exit(1);
|
|
18576
|
-
}
|
|
18577
|
-
process.exit(result.status ?? 0);
|
|
18578
|
-
});
|
|
18579
|
-
}
|
|
18580
|
-
|
|
18581
18557
|
// src/mcp-gateway/index.ts
|
|
18582
18558
|
init_orchestrator();
|
|
18583
18559
|
import readline4 from "readline";
|
|
18584
|
-
import
|
|
18585
|
-
import { spawn as
|
|
18560
|
+
import chalk19 from "chalk";
|
|
18561
|
+
import { spawn as spawn7 } from "child_process";
|
|
18586
18562
|
import { execa as execa2 } from "execa";
|
|
18587
18563
|
init_provenance();
|
|
18588
18564
|
|
|
@@ -18742,13 +18718,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18742
18718
|
const prov = checkProvenance(executable);
|
|
18743
18719
|
if (prov.trustLevel === "suspect") {
|
|
18744
18720
|
console.error(
|
|
18745
|
-
|
|
18721
|
+
chalk19.red(
|
|
18746
18722
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
18747
18723
|
)
|
|
18748
18724
|
);
|
|
18749
|
-
console.error(
|
|
18725
|
+
console.error(chalk19.red(" Verify this binary is trusted before proceeding."));
|
|
18750
18726
|
}
|
|
18751
|
-
console.error(
|
|
18727
|
+
console.error(chalk19.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
18752
18728
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
18753
18729
|
"NODE_OPTIONS",
|
|
18754
18730
|
"NODE_PATH",
|
|
@@ -18767,7 +18743,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18767
18743
|
const safeEnv = Object.fromEntries(
|
|
18768
18744
|
Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
|
|
18769
18745
|
);
|
|
18770
|
-
const child =
|
|
18746
|
+
const child = spawn7(executable, cmdArgs, {
|
|
18771
18747
|
stdio: ["pipe", "pipe", "inherit"],
|
|
18772
18748
|
// control stdin/stdout; inherit stderr
|
|
18773
18749
|
shell: false,
|
|
@@ -18861,10 +18837,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18861
18837
|
mcpServer
|
|
18862
18838
|
});
|
|
18863
18839
|
if (!result.approved) {
|
|
18864
|
-
console.error(
|
|
18840
|
+
console.error(chalk19.red(`
|
|
18865
18841
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
18866
|
-
console.error(
|
|
18867
|
-
console.error(
|
|
18842
|
+
console.error(chalk19.gray(` Tool: ${toolName}`));
|
|
18843
|
+
console.error(chalk19.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
18868
18844
|
`));
|
|
18869
18845
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
18870
18846
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -18976,7 +18952,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18976
18952
|
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
18977
18953
|
pinState = "validated";
|
|
18978
18954
|
console.error(
|
|
18979
|
-
|
|
18955
|
+
chalk19.green(
|
|
18980
18956
|
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
18981
18957
|
)
|
|
18982
18958
|
);
|
|
@@ -18989,11 +18965,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18989
18965
|
} else if (pinStatus === "corrupt") {
|
|
18990
18966
|
pinState = "quarantined";
|
|
18991
18967
|
console.error(
|
|
18992
|
-
|
|
18968
|
+
chalk19.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
18993
18969
|
);
|
|
18994
|
-
console.error(
|
|
18970
|
+
console.error(chalk19.red(" Tool calls are blocked until the pin file is repaired."));
|
|
18995
18971
|
console.error(
|
|
18996
|
-
|
|
18972
|
+
chalk19.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
18997
18973
|
`)
|
|
18998
18974
|
);
|
|
18999
18975
|
const errorResponse = {
|
|
@@ -19010,13 +18986,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
19010
18986
|
} else {
|
|
19011
18987
|
pinState = "quarantined";
|
|
19012
18988
|
console.error(
|
|
19013
|
-
|
|
18989
|
+
chalk19.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
19014
18990
|
);
|
|
19015
18991
|
console.error(
|
|
19016
|
-
|
|
18992
|
+
chalk19.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
19017
18993
|
);
|
|
19018
|
-
console.error(
|
|
19019
|
-
console.error(
|
|
18994
|
+
console.error(chalk19.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
18995
|
+
console.error(chalk19.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
19020
18996
|
`));
|
|
19021
18997
|
const errorResponse = {
|
|
19022
18998
|
jsonrpc: "2.0",
|
|
@@ -19059,7 +19035,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
19059
19035
|
const toolName = callId !== void 0 ? pendingCallNames.get(callId) ?? "unknown" : "unknown";
|
|
19060
19036
|
if (callId !== void 0) pendingCallNames.delete(callId);
|
|
19061
19037
|
console.error(
|
|
19062
|
-
|
|
19038
|
+
chalk19.yellow(
|
|
19063
19039
|
`\u26A1 Node9: Large MCP response from '${toolName}' (${(line.length / 1024).toFixed(0)}KB) \u2014 context window enlarged`
|
|
19064
19040
|
)
|
|
19065
19041
|
);
|
|
@@ -19113,7 +19089,7 @@ import readline5 from "readline";
|
|
|
19113
19089
|
import fs39 from "fs";
|
|
19114
19090
|
import os35 from "os";
|
|
19115
19091
|
import path41 from "path";
|
|
19116
|
-
import { spawnSync as
|
|
19092
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
19117
19093
|
init_core();
|
|
19118
19094
|
init_daemon();
|
|
19119
19095
|
init_shields();
|
|
@@ -19587,7 +19563,7 @@ function handleRuleAdd(args) {
|
|
|
19587
19563
|
return `Rule "${name}" added to ~/.node9/config.json \u2014 verdict: ${verdict} when ${field} matches "${pattern}"`;
|
|
19588
19564
|
}
|
|
19589
19565
|
function runCliCommand(subArgs) {
|
|
19590
|
-
const result =
|
|
19566
|
+
const result = spawnSync4(process.execPath, [process.argv[1], ...subArgs], {
|
|
19591
19567
|
encoding: "utf-8",
|
|
19592
19568
|
timeout: 6e4,
|
|
19593
19569
|
// Disable colors — stdout is piped (not a TTY), chalk auto-detects, but be explicit
|
|
@@ -19782,7 +19758,7 @@ function registerMcpServerCommand(program2) {
|
|
|
19782
19758
|
|
|
19783
19759
|
// src/cli/commands/trust.ts
|
|
19784
19760
|
init_trusted_hosts();
|
|
19785
|
-
import
|
|
19761
|
+
import chalk20 from "chalk";
|
|
19786
19762
|
function isValidHost(host) {
|
|
19787
19763
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
19788
19764
|
}
|
|
@@ -19792,51 +19768,51 @@ function registerTrustCommand(program2) {
|
|
|
19792
19768
|
const normalized = normalizeHost(host.trim());
|
|
19793
19769
|
if (!isValidHost(normalized)) {
|
|
19794
19770
|
console.error(
|
|
19795
|
-
|
|
19771
|
+
chalk20.red(`
|
|
19796
19772
|
\u274C Invalid host: "${host}"
|
|
19797
|
-
`) +
|
|
19773
|
+
`) + chalk20.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
19798
19774
|
);
|
|
19799
19775
|
process.exit(1);
|
|
19800
19776
|
}
|
|
19801
19777
|
addTrustedHost(normalized);
|
|
19802
|
-
console.log(
|
|
19778
|
+
console.log(chalk20.green(`
|
|
19803
19779
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
19804
19780
|
console.log(
|
|
19805
|
-
|
|
19781
|
+
chalk20.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
19806
19782
|
);
|
|
19807
19783
|
});
|
|
19808
19784
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
19809
19785
|
const normalized = normalizeHost(host.trim());
|
|
19810
19786
|
const removed = removeTrustedHost(normalized);
|
|
19811
19787
|
if (!removed) {
|
|
19812
|
-
console.error(
|
|
19788
|
+
console.error(chalk20.yellow(`
|
|
19813
19789
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
19814
19790
|
`));
|
|
19815
19791
|
process.exit(1);
|
|
19816
19792
|
}
|
|
19817
|
-
console.log(
|
|
19793
|
+
console.log(chalk20.green(`
|
|
19818
19794
|
\u2705 ${normalized} removed from trusted hosts.
|
|
19819
19795
|
`));
|
|
19820
19796
|
});
|
|
19821
19797
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
19822
19798
|
const hosts = readTrustedHosts();
|
|
19823
19799
|
if (hosts.length === 0) {
|
|
19824
|
-
console.log(
|
|
19825
|
-
console.log(` Add one: ${
|
|
19800
|
+
console.log(chalk20.gray("\n No trusted hosts configured.\n"));
|
|
19801
|
+
console.log(` Add one: ${chalk20.cyan("node9 trust add api.mycompany.com")}
|
|
19826
19802
|
`);
|
|
19827
19803
|
return;
|
|
19828
19804
|
}
|
|
19829
|
-
console.log(
|
|
19805
|
+
console.log(chalk20.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
19830
19806
|
for (const entry of hosts) {
|
|
19831
19807
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
19832
|
-
console.log(` ${
|
|
19808
|
+
console.log(` ${chalk20.cyan(entry.host.padEnd(40))} ${chalk20.gray(`added ${date}`)}`);
|
|
19833
19809
|
}
|
|
19834
19810
|
console.log("");
|
|
19835
19811
|
});
|
|
19836
19812
|
}
|
|
19837
19813
|
|
|
19838
19814
|
// src/cli/commands/mcp-pin.ts
|
|
19839
|
-
import
|
|
19815
|
+
import chalk21 from "chalk";
|
|
19840
19816
|
function registerMcpPinCommand(program2) {
|
|
19841
19817
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
19842
19818
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -19844,31 +19820,31 @@ function registerMcpPinCommand(program2) {
|
|
|
19844
19820
|
const result = readMcpPinsSafe();
|
|
19845
19821
|
if (!result.ok) {
|
|
19846
19822
|
if (result.reason === "missing") {
|
|
19847
|
-
console.log(
|
|
19823
|
+
console.log(chalk21.gray("\nNo MCP servers are pinned yet."));
|
|
19848
19824
|
console.log(
|
|
19849
|
-
|
|
19825
|
+
chalk21.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
19850
19826
|
);
|
|
19851
19827
|
return;
|
|
19852
19828
|
}
|
|
19853
|
-
console.error(
|
|
19829
|
+
console.error(chalk21.red(`
|
|
19854
19830
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
19855
|
-
console.error(
|
|
19831
|
+
console.error(chalk21.yellow(" Run: node9 mcp pin reset\n"));
|
|
19856
19832
|
process.exit(1);
|
|
19857
19833
|
}
|
|
19858
19834
|
const entries = Object.entries(result.pins.servers);
|
|
19859
19835
|
if (entries.length === 0) {
|
|
19860
|
-
console.log(
|
|
19836
|
+
console.log(chalk21.gray("\nNo MCP servers are pinned yet."));
|
|
19861
19837
|
console.log(
|
|
19862
|
-
|
|
19838
|
+
chalk21.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
19863
19839
|
);
|
|
19864
19840
|
return;
|
|
19865
19841
|
}
|
|
19866
|
-
console.log(
|
|
19842
|
+
console.log(chalk21.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
19867
19843
|
for (const [key, entry] of entries) {
|
|
19868
|
-
console.log(` ${
|
|
19869
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
19870
|
-
console.log(` Hash: ${
|
|
19871
|
-
console.log(` Pinned: ${
|
|
19844
|
+
console.log(` ${chalk21.cyan(key)} ${chalk21.gray(entry.label)}`);
|
|
19845
|
+
console.log(` Tools (${entry.toolCount}): ${chalk21.white(entry.toolNames.join(", "))}`);
|
|
19846
|
+
console.log(` Hash: ${chalk21.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
19847
|
+
console.log(` Pinned: ${chalk21.gray(entry.pinnedAt)}`);
|
|
19872
19848
|
console.log("");
|
|
19873
19849
|
}
|
|
19874
19850
|
});
|
|
@@ -19879,127 +19855,127 @@ function registerMcpPinCommand(program2) {
|
|
|
19879
19855
|
try {
|
|
19880
19856
|
pins = readMcpPins();
|
|
19881
19857
|
} catch {
|
|
19882
|
-
console.error(
|
|
19883
|
-
console.error(
|
|
19858
|
+
console.error(chalk21.red("\n\u274C Pin file is corrupt."));
|
|
19859
|
+
console.error(chalk21.yellow(" Run: node9 mcp pin reset\n"));
|
|
19884
19860
|
process.exit(1);
|
|
19885
19861
|
}
|
|
19886
19862
|
if (!pins.servers[serverKey]) {
|
|
19887
|
-
console.error(
|
|
19863
|
+
console.error(chalk21.red(`
|
|
19888
19864
|
\u274C No pin found for server key "${serverKey}"
|
|
19889
19865
|
`));
|
|
19890
|
-
console.error(`Run ${
|
|
19866
|
+
console.error(`Run ${chalk21.cyan("node9 mcp pin list")} to see pinned servers.
|
|
19891
19867
|
`);
|
|
19892
19868
|
process.exit(1);
|
|
19893
19869
|
}
|
|
19894
19870
|
const label = pins.servers[serverKey].label;
|
|
19895
19871
|
removePin2(serverKey);
|
|
19896
|
-
console.log(
|
|
19897
|
-
\u{1F513} Pin removed for ${
|
|
19898
|
-
console.log(
|
|
19899
|
-
console.log(
|
|
19872
|
+
console.log(chalk21.green(`
|
|
19873
|
+
\u{1F513} Pin removed for ${chalk21.cyan(serverKey)}`));
|
|
19874
|
+
console.log(chalk21.gray(` Server: ${label}`));
|
|
19875
|
+
console.log(chalk21.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
19900
19876
|
});
|
|
19901
19877
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
19902
19878
|
const result = readMcpPinsSafe();
|
|
19903
19879
|
if (!result.ok && result.reason === "missing") {
|
|
19904
|
-
console.log(
|
|
19880
|
+
console.log(chalk21.gray("\nNo pins to clear.\n"));
|
|
19905
19881
|
return;
|
|
19906
19882
|
}
|
|
19907
19883
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
19908
19884
|
clearAllPins2();
|
|
19909
|
-
console.log(
|
|
19885
|
+
console.log(chalk21.green(`
|
|
19910
19886
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
19911
|
-
console.log(
|
|
19887
|
+
console.log(chalk21.gray(" Next connection to each server will re-pin.\n"));
|
|
19912
19888
|
});
|
|
19913
19889
|
}
|
|
19914
19890
|
|
|
19915
19891
|
// src/cli/commands/sync.ts
|
|
19916
19892
|
init_sync();
|
|
19917
|
-
import
|
|
19893
|
+
import chalk22 from "chalk";
|
|
19918
19894
|
function registerSyncCommand(program2) {
|
|
19919
19895
|
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
19920
19896
|
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
19921
|
-
process.stdout.write(
|
|
19897
|
+
process.stdout.write(chalk22.cyan("Syncing cloud policy rules\u2026"));
|
|
19922
19898
|
const result = await runCloudSync();
|
|
19923
19899
|
process.stdout.write("\n");
|
|
19924
19900
|
if (!result.ok) {
|
|
19925
|
-
console.error(
|
|
19901
|
+
console.error(chalk22.red(`\u2717 ${result.reason}`));
|
|
19926
19902
|
process.exit(1);
|
|
19927
19903
|
}
|
|
19928
19904
|
if (result.unchanged) {
|
|
19929
19905
|
console.log(
|
|
19930
|
-
|
|
19906
|
+
chalk22.green(
|
|
19931
19907
|
`\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
|
|
19932
19908
|
)
|
|
19933
19909
|
);
|
|
19934
|
-
console.log(
|
|
19935
|
-
console.log(
|
|
19910
|
+
console.log(chalk22.gray(` Cached at: ${result.fetchedAt}`));
|
|
19911
|
+
console.log(chalk22.gray(` Server returned 304 (no changes since last sync)`));
|
|
19936
19912
|
} else {
|
|
19937
19913
|
console.log(
|
|
19938
|
-
|
|
19914
|
+
chalk22.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
19939
19915
|
);
|
|
19940
|
-
console.log(
|
|
19941
|
-
console.log(
|
|
19916
|
+
console.log(chalk22.gray(` Cached at: ${result.fetchedAt}`));
|
|
19917
|
+
console.log(chalk22.gray(` File: ~/.node9/rules-cache.json`));
|
|
19942
19918
|
}
|
|
19943
19919
|
});
|
|
19944
19920
|
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
19945
19921
|
const status = getCloudSyncStatus();
|
|
19946
19922
|
if (!status.cached) {
|
|
19947
|
-
console.log(
|
|
19923
|
+
console.log(chalk22.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
19948
19924
|
return;
|
|
19949
19925
|
}
|
|
19950
19926
|
const rules = getCloudRules() ?? [];
|
|
19951
19927
|
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
19952
19928
|
console.log(
|
|
19953
|
-
|
|
19954
|
-
Cloud policy rules`) +
|
|
19929
|
+
chalk22.bold(`
|
|
19930
|
+
Cloud policy rules`) + chalk22.gray(
|
|
19955
19931
|
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
19956
19932
|
`
|
|
19957
19933
|
)
|
|
19958
19934
|
);
|
|
19959
19935
|
if (rules.length === 0) {
|
|
19960
|
-
console.log(
|
|
19936
|
+
console.log(chalk22.gray(" No rules defined in cloud policy.\n"));
|
|
19961
19937
|
return;
|
|
19962
19938
|
}
|
|
19963
19939
|
for (const rule of rules) {
|
|
19964
19940
|
const r = rule;
|
|
19965
|
-
const verdictColor = r.verdict === "block" ?
|
|
19941
|
+
const verdictColor = r.verdict === "block" ? chalk22.red : r.verdict === "allow" ? chalk22.green : chalk22.yellow;
|
|
19966
19942
|
console.log(
|
|
19967
19943
|
` ${verdictColor(
|
|
19968
19944
|
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
19969
|
-
)} ${
|
|
19945
|
+
)} ${chalk22.white(String(r.name ?? "(unnamed)"))}`
|
|
19970
19946
|
);
|
|
19971
|
-
if (r.reason) console.log(
|
|
19947
|
+
if (r.reason) console.log(chalk22.gray(` ${String(r.reason)}`));
|
|
19972
19948
|
}
|
|
19973
19949
|
console.log("");
|
|
19974
19950
|
});
|
|
19975
19951
|
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
19976
19952
|
const s = getCloudSyncStatus();
|
|
19977
19953
|
if (!s.cached) {
|
|
19978
|
-
console.log(
|
|
19954
|
+
console.log(chalk22.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
19979
19955
|
return;
|
|
19980
19956
|
}
|
|
19981
19957
|
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
19982
19958
|
console.log(`
|
|
19983
|
-
Rules : ${
|
|
19959
|
+
Rules : ${chalk22.green(String(s.rules))} cloud rules loaded`);
|
|
19984
19960
|
console.log(
|
|
19985
|
-
` Synced : ${
|
|
19961
|
+
` Synced : ${chalk22.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
|
|
19986
19962
|
);
|
|
19987
19963
|
if (s.workspaceId) {
|
|
19988
|
-
console.log(` Workspace: ${
|
|
19964
|
+
console.log(` Workspace: ${chalk22.gray(s.workspaceId)}`);
|
|
19989
19965
|
}
|
|
19990
19966
|
if (s.panicMode) {
|
|
19991
19967
|
console.log(
|
|
19992
|
-
` ${
|
|
19968
|
+
` ${chalk22.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk22.dim("(every review-verdict becomes block)")
|
|
19993
19969
|
);
|
|
19994
19970
|
}
|
|
19995
19971
|
if (s.shadowMode) {
|
|
19996
19972
|
console.log(
|
|
19997
|
-
` ${
|
|
19973
|
+
` ${chalk22.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk22.dim("(blocks become would-block log entries)")
|
|
19998
19974
|
);
|
|
19999
19975
|
}
|
|
20000
19976
|
if (s.syncIntervalHours) {
|
|
20001
19977
|
console.log(
|
|
20002
|
-
|
|
19978
|
+
chalk22.gray(
|
|
20003
19979
|
` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
|
|
20004
19980
|
)
|
|
20005
19981
|
);
|
|
@@ -20010,7 +19986,7 @@ function registerSyncCommand(program2) {
|
|
|
20010
19986
|
|
|
20011
19987
|
// src/cli/commands/agents.ts
|
|
20012
19988
|
init_setup();
|
|
20013
|
-
import
|
|
19989
|
+
import chalk23 from "chalk";
|
|
20014
19990
|
var SETUP_FN = {
|
|
20015
19991
|
claude: setupClaude,
|
|
20016
19992
|
gemini: setupGemini,
|
|
@@ -20039,23 +20015,23 @@ function registerAgentsCommand(program2) {
|
|
|
20039
20015
|
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
20040
20016
|
console.log(" " + "\u2500".repeat(44));
|
|
20041
20017
|
for (const s of statuses) {
|
|
20042
|
-
const installed = s.installed ?
|
|
20043
|
-
const wired = !s.installed ?
|
|
20044
|
-
const mode = s.mode ?
|
|
20045
|
-
const hint = s.installed && !s.wired ?
|
|
20018
|
+
const installed = s.installed ? chalk23.green("\u2713") : chalk23.gray("\u2717");
|
|
20019
|
+
const wired = !s.installed ? chalk23.gray("\u2014") : s.wired ? chalk23.green("\u2713") : chalk23.yellow("\u2717");
|
|
20020
|
+
const mode = s.mode ? chalk23.gray(s.mode) : chalk23.gray("\u2014");
|
|
20021
|
+
const hint = s.installed && !s.wired ? chalk23.gray(` \u2190 node9 agents add ${s.name}`) : "";
|
|
20046
20022
|
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
20047
20023
|
}
|
|
20048
20024
|
console.log("");
|
|
20049
20025
|
if (!anyInstalled) {
|
|
20050
20026
|
console.log(
|
|
20051
|
-
|
|
20027
|
+
chalk23.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + chalk23.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
|
|
20052
20028
|
);
|
|
20053
20029
|
return;
|
|
20054
20030
|
}
|
|
20055
20031
|
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
20056
20032
|
if (unwired.length > 0) {
|
|
20057
20033
|
console.log(
|
|
20058
|
-
|
|
20034
|
+
chalk23.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk23.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
20059
20035
|
);
|
|
20060
20036
|
}
|
|
20061
20037
|
});
|
|
@@ -20063,7 +20039,7 @@ function registerAgentsCommand(program2) {
|
|
|
20063
20039
|
const name = agent.toLowerCase();
|
|
20064
20040
|
const fn = SETUP_FN[name];
|
|
20065
20041
|
if (!fn) {
|
|
20066
|
-
console.error(
|
|
20042
|
+
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
20067
20043
|
process.exit(1);
|
|
20068
20044
|
}
|
|
20069
20045
|
await fn();
|
|
@@ -20072,14 +20048,14 @@ function registerAgentsCommand(program2) {
|
|
|
20072
20048
|
const name = agent.toLowerCase();
|
|
20073
20049
|
const fn = TEARDOWN_FN[name];
|
|
20074
20050
|
if (!fn) {
|
|
20075
|
-
console.error(
|
|
20051
|
+
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
20076
20052
|
process.exit(1);
|
|
20077
20053
|
}
|
|
20078
|
-
console.log(
|
|
20054
|
+
console.log(chalk23.cyan(`
|
|
20079
20055
|
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
20080
20056
|
`));
|
|
20081
20057
|
fn();
|
|
20082
|
-
console.log(
|
|
20058
|
+
console.log(chalk23.gray("\n Restart the agent for changes to take effect."));
|
|
20083
20059
|
});
|
|
20084
20060
|
}
|
|
20085
20061
|
|
|
@@ -20087,7 +20063,7 @@ function registerAgentsCommand(program2) {
|
|
|
20087
20063
|
init_scan();
|
|
20088
20064
|
|
|
20089
20065
|
// src/cli/commands/sessions.ts
|
|
20090
|
-
import
|
|
20066
|
+
import chalk24 from "chalk";
|
|
20091
20067
|
import fs40 from "fs";
|
|
20092
20068
|
import path42 from "path";
|
|
20093
20069
|
import os36 from "os";
|
|
@@ -20596,11 +20572,11 @@ function toolInputSummary(tool, input) {
|
|
|
20596
20572
|
}
|
|
20597
20573
|
function toolColor(tool) {
|
|
20598
20574
|
const t = tool.toLowerCase();
|
|
20599
|
-
if (t === "bash" || t === "execute_bash") return
|
|
20600
|
-
if (t === "write") return
|
|
20601
|
-
if (t === "edit" || t === "notebookedit") return
|
|
20602
|
-
if (t === "read") return
|
|
20603
|
-
return
|
|
20575
|
+
if (t === "bash" || t === "execute_bash") return chalk24.red;
|
|
20576
|
+
if (t === "write") return chalk24.green;
|
|
20577
|
+
if (t === "edit" || t === "notebookedit") return chalk24.yellow;
|
|
20578
|
+
if (t === "read") return chalk24.cyan;
|
|
20579
|
+
return chalk24.gray;
|
|
20604
20580
|
}
|
|
20605
20581
|
function barStr2(value, max, width) {
|
|
20606
20582
|
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
@@ -20610,7 +20586,7 @@ function barStr2(value, max, width) {
|
|
|
20610
20586
|
function colorBar2(value, max, width) {
|
|
20611
20587
|
const s = barStr2(value, max, width);
|
|
20612
20588
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
20613
|
-
return
|
|
20589
|
+
return chalk24.cyan(s.slice(0, filled)) + chalk24.dim(s.slice(filled));
|
|
20614
20590
|
}
|
|
20615
20591
|
function renderSummary(summaries) {
|
|
20616
20592
|
const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
|
|
@@ -20640,45 +20616,45 @@ function renderSummary(summaries) {
|
|
|
20640
20616
|
}
|
|
20641
20617
|
const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
20642
20618
|
const W = 20;
|
|
20643
|
-
console.log(
|
|
20619
|
+
console.log(chalk24.dim(" " + "\u2500".repeat(70)));
|
|
20644
20620
|
console.log(
|
|
20645
|
-
" " +
|
|
20621
|
+
" " + chalk24.bold.white(String(summaries.length).padEnd(4)) + chalk24.dim("sessions ") + chalk24.bold.yellow(fmtCost3(totalCost).padEnd(10)) + chalk24.dim("total ") + chalk24.bold.white(String(totalTools).padEnd(6)) + chalk24.dim("tool calls ") + chalk24.bold.white(String(totalFiles)) + chalk24.dim(" files modified") + (totalBlocked > 0 ? chalk24.dim(" ") + chalk24.red.bold(String(totalBlocked)) + chalk24.dim(" blocked by node9") : "")
|
|
20646
20622
|
);
|
|
20647
20623
|
console.log(
|
|
20648
|
-
" " +
|
|
20624
|
+
" " + chalk24.dim("avg ") + chalk24.white(fmtCost3(avgCost).padEnd(10)) + chalk24.dim("/session ") + chalk24.green(String(snapshots)) + chalk24.dim(` of ${summaries.length} sessions had snapshots`)
|
|
20649
20625
|
);
|
|
20650
20626
|
console.log("");
|
|
20651
|
-
console.log(" " +
|
|
20627
|
+
console.log(" " + chalk24.dim("Tool breakdown:"));
|
|
20652
20628
|
const maxGroup = Math.max(...Object.values(groups));
|
|
20653
20629
|
for (const [label, count] of Object.entries(groups)) {
|
|
20654
20630
|
if (count === 0) continue;
|
|
20655
20631
|
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
20656
20632
|
console.log(
|
|
20657
|
-
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " +
|
|
20633
|
+
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk24.white(String(count).padStart(4)) + chalk24.dim(` (${String(pct)}%)`)
|
|
20658
20634
|
);
|
|
20659
20635
|
}
|
|
20660
20636
|
console.log("");
|
|
20661
20637
|
if (topProjects.length > 1) {
|
|
20662
|
-
console.log(" " +
|
|
20638
|
+
console.log(" " + chalk24.dim("Cost by project:"));
|
|
20663
20639
|
const maxProjCost = topProjects[0][1];
|
|
20664
20640
|
for (const [proj, cost] of topProjects) {
|
|
20665
20641
|
console.log(
|
|
20666
|
-
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " +
|
|
20642
|
+
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + chalk24.yellow(fmtCost3(cost))
|
|
20667
20643
|
);
|
|
20668
20644
|
}
|
|
20669
20645
|
console.log("");
|
|
20670
20646
|
}
|
|
20671
|
-
console.log(
|
|
20647
|
+
console.log(chalk24.dim(" " + "\u2500".repeat(70)));
|
|
20672
20648
|
console.log("");
|
|
20673
20649
|
}
|
|
20674
20650
|
function renderList(summaries, totalCost) {
|
|
20675
20651
|
if (summaries.length === 0) {
|
|
20676
|
-
console.log(
|
|
20652
|
+
console.log(chalk24.yellow(" No sessions found in the requested range.\n"));
|
|
20677
20653
|
return;
|
|
20678
20654
|
}
|
|
20679
|
-
const totalLabel = totalCost > 0 ?
|
|
20655
|
+
const totalLabel = totalCost > 0 ? chalk24.dim(" ~" + fmtCost3(totalCost) + " total") : "";
|
|
20680
20656
|
console.log(
|
|
20681
|
-
" " +
|
|
20657
|
+
" " + chalk24.white(String(summaries.length)) + chalk24.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
|
|
20682
20658
|
);
|
|
20683
20659
|
console.log("");
|
|
20684
20660
|
let lastGroup = "";
|
|
@@ -20686,49 +20662,49 @@ function renderList(summaries, totalCost) {
|
|
|
20686
20662
|
const activeDate = fmtDate2(s.lastActiveTime);
|
|
20687
20663
|
const group = activeDate + " " + s.projectLabel;
|
|
20688
20664
|
if (group !== lastGroup) {
|
|
20689
|
-
console.log(
|
|
20665
|
+
console.log(chalk24.dim(" \u2500\u2500\u2500 ") + chalk24.bold(activeDate) + chalk24.dim(" " + s.projectLabel));
|
|
20690
20666
|
lastGroup = group;
|
|
20691
20667
|
}
|
|
20692
20668
|
const startDate = fmtDate2(s.startTime);
|
|
20693
|
-
const dateRange = startDate !== activeDate ?
|
|
20694
|
-
const timeStr =
|
|
20695
|
-
const prompt =
|
|
20696
|
-
const tools = s.toolCalls.length > 0 ?
|
|
20697
|
-
const cost = s.costUSD > 0 ?
|
|
20698
|
-
const blocked = s.blockedCalls.length > 0 ?
|
|
20699
|
-
const snap = s.hasSnapshot ?
|
|
20700
|
-
const agentBadge = s.agent === "gemini" ?
|
|
20701
|
-
const sid =
|
|
20669
|
+
const dateRange = startDate !== activeDate ? chalk24.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
|
|
20670
|
+
const timeStr = chalk24.dim(fmtTime(s.startTime));
|
|
20671
|
+
const prompt = chalk24.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
|
|
20672
|
+
const tools = s.toolCalls.length > 0 ? chalk24.dim(String(s.toolCalls.length).padStart(3) + " tools") : chalk24.dim(" 0 tools");
|
|
20673
|
+
const cost = s.costUSD > 0 ? chalk24.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
20674
|
+
const blocked = s.blockedCalls.length > 0 ? chalk24.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
20675
|
+
const snap = s.hasSnapshot ? chalk24.green(" \u{1F4F8}") : "";
|
|
20676
|
+
const agentBadge = s.agent === "gemini" ? chalk24.blue(" [Gemini]") : s.agent === "codex" ? chalk24.magenta(" [Codex]") : chalk24.cyan(" [Claude]");
|
|
20677
|
+
const sid = chalk24.dim(" " + s.sessionId.slice(0, 8));
|
|
20702
20678
|
console.log(
|
|
20703
20679
|
` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
|
|
20704
20680
|
);
|
|
20705
20681
|
}
|
|
20706
20682
|
console.log("");
|
|
20707
20683
|
console.log(
|
|
20708
|
-
|
|
20684
|
+
chalk24.dim(" Run") + " " + chalk24.cyan("node9 sessions --detail <session-id>") + chalk24.dim(" for full tool trace.")
|
|
20709
20685
|
);
|
|
20710
20686
|
console.log("");
|
|
20711
20687
|
}
|
|
20712
20688
|
function renderDetail(s) {
|
|
20713
20689
|
console.log("");
|
|
20714
|
-
console.log(
|
|
20690
|
+
console.log(chalk24.bold(" Session ") + chalk24.dim(s.sessionId));
|
|
20715
20691
|
console.log(
|
|
20716
|
-
|
|
20692
|
+
chalk24.bold(" Prompt ") + chalk24.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
20717
20693
|
);
|
|
20718
|
-
console.log(
|
|
20694
|
+
console.log(chalk24.bold(" Project ") + chalk24.white(s.projectLabel));
|
|
20719
20695
|
if (s.agent) {
|
|
20720
|
-
const agentLabel2 = s.agent === "gemini" ?
|
|
20721
|
-
console.log(
|
|
20696
|
+
const agentLabel2 = s.agent === "gemini" ? chalk24.blue("Gemini CLI") : s.agent === "codex" ? chalk24.magenta("Codex") : chalk24.cyan("Claude Code");
|
|
20697
|
+
console.log(chalk24.bold(" Agent ") + agentLabel2);
|
|
20722
20698
|
}
|
|
20723
|
-
console.log(
|
|
20699
|
+
console.log(chalk24.bold(" When ") + chalk24.white(fmtDateTime(s.startTime)));
|
|
20724
20700
|
if (s.costUSD > 0)
|
|
20725
|
-
console.log(
|
|
20701
|
+
console.log(chalk24.bold(" Cost ") + chalk24.yellow("~" + fmtCost3(s.costUSD)));
|
|
20726
20702
|
console.log(
|
|
20727
|
-
|
|
20703
|
+
chalk24.bold(" Snapshot ") + (s.hasSnapshot ? chalk24.green("\u2713 taken") : chalk24.dim("none"))
|
|
20728
20704
|
);
|
|
20729
20705
|
console.log("");
|
|
20730
20706
|
if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
|
|
20731
|
-
console.log(
|
|
20707
|
+
console.log(chalk24.dim(" No tool calls recorded.\n"));
|
|
20732
20708
|
return;
|
|
20733
20709
|
}
|
|
20734
20710
|
const timeline = [
|
|
@@ -20741,32 +20717,32 @@ function renderDetail(s) {
|
|
|
20741
20717
|
});
|
|
20742
20718
|
const headerParts = [`Tool calls (${s.toolCalls.length})`];
|
|
20743
20719
|
if (s.blockedCalls.length > 0)
|
|
20744
|
-
headerParts.push(
|
|
20745
|
-
console.log(
|
|
20720
|
+
headerParts.push(chalk24.red(`${s.blockedCalls.length} blocked by node9`));
|
|
20721
|
+
console.log(chalk24.bold(" " + headerParts.join(" \xB7 ")));
|
|
20746
20722
|
console.log("");
|
|
20747
20723
|
for (const entry of timeline) {
|
|
20748
20724
|
if (entry.kind === "tool") {
|
|
20749
20725
|
const tc = entry.tc;
|
|
20750
20726
|
const colorFn = toolColor(tc.tool);
|
|
20751
20727
|
const toolPad = colorFn(tc.tool.padEnd(16));
|
|
20752
|
-
const detail =
|
|
20753
|
-
const ts = tc.timestamp ?
|
|
20728
|
+
const detail = chalk24.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
|
|
20729
|
+
const ts = tc.timestamp ? chalk24.dim(fmtTime(tc.timestamp) + " ") : " ";
|
|
20754
20730
|
console.log(` ${ts}${toolPad} ${detail}`);
|
|
20755
20731
|
} else {
|
|
20756
20732
|
const bc = entry.bc;
|
|
20757
|
-
const ts = bc.timestamp ?
|
|
20758
|
-
const label =
|
|
20759
|
-
const toolName =
|
|
20760
|
-
const argsSummary = bc.args ?
|
|
20761
|
-
const reason = bc.checkedBy ?
|
|
20733
|
+
const ts = bc.timestamp ? chalk24.dim(fmtTime(bc.timestamp) + " ") : " ";
|
|
20734
|
+
const label = chalk24.red("\u{1F6D1} BLOCKED".padEnd(16));
|
|
20735
|
+
const toolName = chalk24.red(bc.tool.padEnd(10));
|
|
20736
|
+
const argsSummary = bc.args ? chalk24.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : chalk24.dim("[args not logged]");
|
|
20737
|
+
const reason = bc.checkedBy ? chalk24.dim(" \u2190 " + bc.checkedBy) : "";
|
|
20762
20738
|
console.log(` ${ts}${label} ${toolName} ${argsSummary}${reason}`);
|
|
20763
20739
|
}
|
|
20764
20740
|
}
|
|
20765
20741
|
console.log("");
|
|
20766
20742
|
if (s.modifiedFiles.length > 0) {
|
|
20767
|
-
console.log(
|
|
20743
|
+
console.log(chalk24.bold(` Files modified (${s.modifiedFiles.length}):`));
|
|
20768
20744
|
for (const f of s.modifiedFiles) {
|
|
20769
|
-
console.log(" " +
|
|
20745
|
+
console.log(" " + chalk24.yellow(f));
|
|
20770
20746
|
}
|
|
20771
20747
|
console.log("");
|
|
20772
20748
|
}
|
|
@@ -20774,19 +20750,19 @@ function renderDetail(s) {
|
|
|
20774
20750
|
function registerSessionsCommand(program2) {
|
|
20775
20751
|
program2.command("sessions").description("Show what your AI agent did \u2014 sessions, tool calls, cost, and file changes").option("--all", "Show all sessions (default: last 7 days)").option("--days <n>", "Show last N days of sessions", "7").option("--detail <sessionId>", "Show full tool trace for a session").action((options) => {
|
|
20776
20752
|
console.log("");
|
|
20777
|
-
console.log(
|
|
20753
|
+
console.log(chalk24.cyan.bold("\u{1F4CB} node9 sessions") + chalk24.dim(" \u2014 what your AI agent did"));
|
|
20778
20754
|
console.log("");
|
|
20779
20755
|
const historyPath = path42.join(os36.homedir(), ".claude", "history.jsonl");
|
|
20780
20756
|
if (!fs40.existsSync(historyPath)) {
|
|
20781
|
-
console.log(
|
|
20782
|
-
console.log(
|
|
20757
|
+
console.log(chalk24.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
20758
|
+
console.log(chalk24.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
20783
20759
|
return;
|
|
20784
20760
|
}
|
|
20785
20761
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
20786
20762
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
20787
|
-
console.log(
|
|
20763
|
+
console.log(chalk24.dim(" " + rangeLabel));
|
|
20788
20764
|
console.log("");
|
|
20789
|
-
process.stdout.write(
|
|
20765
|
+
process.stdout.write(chalk24.dim(" Loading\u2026"));
|
|
20790
20766
|
const summaries = buildSessions(days);
|
|
20791
20767
|
if (process.stdout.isTTY) {
|
|
20792
20768
|
process.stdout.clearLine(0);
|
|
@@ -20799,8 +20775,8 @@ function registerSessionsCommand(program2) {
|
|
|
20799
20775
|
(s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
|
|
20800
20776
|
);
|
|
20801
20777
|
if (!target) {
|
|
20802
|
-
console.log(
|
|
20803
|
-
console.log(
|
|
20778
|
+
console.log(chalk24.red(` Session not found: ${options.detail}`));
|
|
20779
|
+
console.log(chalk24.dim(" Run `node9 sessions` to list recent sessions.\n"));
|
|
20804
20780
|
return;
|
|
20805
20781
|
}
|
|
20806
20782
|
renderDetail(target);
|
|
@@ -20813,7 +20789,7 @@ function registerSessionsCommand(program2) {
|
|
|
20813
20789
|
}
|
|
20814
20790
|
|
|
20815
20791
|
// src/cli/commands/skill-pin.ts
|
|
20816
|
-
import
|
|
20792
|
+
import chalk25 from "chalk";
|
|
20817
20793
|
import fs41 from "fs";
|
|
20818
20794
|
import os37 from "os";
|
|
20819
20795
|
import path43 from "path";
|
|
@@ -20833,29 +20809,29 @@ function registerSkillPinCommand(program2) {
|
|
|
20833
20809
|
const result = readSkillPinsSafe();
|
|
20834
20810
|
if (!result.ok) {
|
|
20835
20811
|
if (result.reason === "missing") {
|
|
20836
|
-
console.log(
|
|
20812
|
+
console.log(chalk25.gray("\nNo skill roots are pinned yet."));
|
|
20837
20813
|
console.log(
|
|
20838
|
-
|
|
20814
|
+
chalk25.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
20839
20815
|
);
|
|
20840
20816
|
return;
|
|
20841
20817
|
}
|
|
20842
|
-
console.error(
|
|
20818
|
+
console.error(chalk25.red(`
|
|
20843
20819
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
20844
|
-
console.error(
|
|
20820
|
+
console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
|
|
20845
20821
|
process.exit(1);
|
|
20846
20822
|
}
|
|
20847
20823
|
const entries = Object.entries(result.pins.roots);
|
|
20848
20824
|
if (entries.length === 0) {
|
|
20849
|
-
console.log(
|
|
20825
|
+
console.log(chalk25.gray("\nNo skill roots are pinned yet.\n"));
|
|
20850
20826
|
return;
|
|
20851
20827
|
}
|
|
20852
|
-
console.log(
|
|
20828
|
+
console.log(chalk25.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
20853
20829
|
for (const [key, entry] of entries) {
|
|
20854
|
-
const missing = entry.exists ? "" :
|
|
20855
|
-
console.log(` ${
|
|
20830
|
+
const missing = entry.exists ? "" : chalk25.yellow(" (not present at pin time)");
|
|
20831
|
+
console.log(` ${chalk25.cyan(key)} ${chalk25.gray(entry.rootPath)}${missing}`);
|
|
20856
20832
|
console.log(` Files (${entry.fileCount})`);
|
|
20857
|
-
console.log(` Hash: ${
|
|
20858
|
-
console.log(` Pinned: ${
|
|
20833
|
+
console.log(` Hash: ${chalk25.gray(entry.contentHash.slice(0, 16))}...`);
|
|
20834
|
+
console.log(` Pinned: ${chalk25.gray(entry.pinnedAt)}
|
|
20859
20835
|
`);
|
|
20860
20836
|
}
|
|
20861
20837
|
});
|
|
@@ -20864,39 +20840,39 @@ function registerSkillPinCommand(program2) {
|
|
|
20864
20840
|
try {
|
|
20865
20841
|
pins = readSkillPins();
|
|
20866
20842
|
} catch {
|
|
20867
|
-
console.error(
|
|
20868
|
-
console.error(
|
|
20843
|
+
console.error(chalk25.red("\n\u274C Pin file is corrupt."));
|
|
20844
|
+
console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
|
|
20869
20845
|
process.exit(1);
|
|
20870
20846
|
}
|
|
20871
20847
|
if (!pins.roots[rootKey]) {
|
|
20872
|
-
console.error(
|
|
20848
|
+
console.error(chalk25.red(`
|
|
20873
20849
|
\u274C No pin found for root key "${rootKey}"
|
|
20874
20850
|
`));
|
|
20875
|
-
console.error(`Run ${
|
|
20851
|
+
console.error(`Run ${chalk25.cyan("node9 skill pin list")} to see pinned roots.
|
|
20876
20852
|
`);
|
|
20877
20853
|
process.exit(1);
|
|
20878
20854
|
}
|
|
20879
20855
|
const rootPath = pins.roots[rootKey].rootPath;
|
|
20880
20856
|
removePin(rootKey);
|
|
20881
20857
|
wipeSkillSessions();
|
|
20882
|
-
console.log(
|
|
20883
|
-
\u{1F513} Pin removed for ${
|
|
20884
|
-
console.log(
|
|
20885
|
-
console.log(
|
|
20858
|
+
console.log(chalk25.green(`
|
|
20859
|
+
\u{1F513} Pin removed for ${chalk25.cyan(rootKey)}`));
|
|
20860
|
+
console.log(chalk25.gray(` ${rootPath}`));
|
|
20861
|
+
console.log(chalk25.gray(" Next session will re-pin with current state.\n"));
|
|
20886
20862
|
});
|
|
20887
20863
|
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
20888
20864
|
const result = readSkillPinsSafe();
|
|
20889
20865
|
if (!result.ok && result.reason === "missing") {
|
|
20890
20866
|
wipeSkillSessions();
|
|
20891
|
-
console.log(
|
|
20867
|
+
console.log(chalk25.gray("\nNo pins to clear.\n"));
|
|
20892
20868
|
return;
|
|
20893
20869
|
}
|
|
20894
20870
|
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
20895
20871
|
clearAllPins();
|
|
20896
20872
|
wipeSkillSessions();
|
|
20897
|
-
console.log(
|
|
20873
|
+
console.log(chalk25.green(`
|
|
20898
20874
|
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
20899
|
-
console.log(
|
|
20875
|
+
console.log(chalk25.gray(" Next session will re-pin with current state.\n"));
|
|
20900
20876
|
});
|
|
20901
20877
|
}
|
|
20902
20878
|
|
|
@@ -20904,7 +20880,7 @@ function registerSkillPinCommand(program2) {
|
|
|
20904
20880
|
import fs42 from "fs";
|
|
20905
20881
|
import os38 from "os";
|
|
20906
20882
|
import path44 from "path";
|
|
20907
|
-
import
|
|
20883
|
+
import chalk26 from "chalk";
|
|
20908
20884
|
var DECISIONS_FILE2 = path44.join(os38.homedir(), ".node9", "decisions.json");
|
|
20909
20885
|
function readDecisions() {
|
|
20910
20886
|
try {
|
|
@@ -20933,55 +20909,55 @@ function registerDecisionsCommand(program2) {
|
|
|
20933
20909
|
const decisions = readDecisions();
|
|
20934
20910
|
const entries = Object.entries(decisions);
|
|
20935
20911
|
if (entries.length === 0) {
|
|
20936
|
-
console.log(
|
|
20912
|
+
console.log(chalk26.gray(" No persistent decisions stored."));
|
|
20937
20913
|
console.log(
|
|
20938
|
-
|
|
20939
|
-
`) +
|
|
20914
|
+
chalk26.gray(` File: ${DECISIONS_FILE2}
|
|
20915
|
+
`) + chalk26.gray(' Decisions are written when you click "Always Allow" or')
|
|
20940
20916
|
);
|
|
20941
|
-
console.log(
|
|
20917
|
+
console.log(chalk26.gray(' "Always Deny" in node9 tail or the native popup.'));
|
|
20942
20918
|
return;
|
|
20943
20919
|
}
|
|
20944
|
-
console.log(
|
|
20920
|
+
console.log(chalk26.bold(`
|
|
20945
20921
|
Persistent decisions (${entries.length})
|
|
20946
20922
|
`));
|
|
20947
20923
|
const w = Math.max(...entries.map(([k]) => k.length));
|
|
20948
20924
|
for (const [tool, verdict] of entries.sort()) {
|
|
20949
|
-
const colored = verdict === "allow" ?
|
|
20925
|
+
const colored = verdict === "allow" ? chalk26.green(verdict) : chalk26.red(verdict);
|
|
20950
20926
|
console.log(` ${tool.padEnd(w)} ${colored}`);
|
|
20951
20927
|
}
|
|
20952
20928
|
console.log(
|
|
20953
|
-
|
|
20929
|
+
chalk26.gray(`
|
|
20954
20930
|
Stored in ${DECISIONS_FILE2}
|
|
20955
|
-
`) +
|
|
20931
|
+
`) + chalk26.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
|
|
20956
20932
|
);
|
|
20957
20933
|
});
|
|
20958
20934
|
cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
|
|
20959
20935
|
const decisions = readDecisions();
|
|
20960
20936
|
if (!(toolName in decisions)) {
|
|
20961
|
-
console.log(
|
|
20937
|
+
console.log(chalk26.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
|
|
20962
20938
|
process.exitCode = 1;
|
|
20963
20939
|
return;
|
|
20964
20940
|
}
|
|
20965
20941
|
delete decisions[toolName];
|
|
20966
20942
|
writeDecisions(decisions);
|
|
20967
|
-
console.log(
|
|
20943
|
+
console.log(chalk26.green(` \u2713 Cleared persistent decision for "${toolName}".`));
|
|
20968
20944
|
});
|
|
20969
20945
|
cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
|
|
20970
20946
|
const decisions = readDecisions();
|
|
20971
20947
|
const count = Object.keys(decisions).length;
|
|
20972
20948
|
if (count === 0) {
|
|
20973
|
-
console.log(
|
|
20949
|
+
console.log(chalk26.gray(" Nothing to clear \u2014 no persistent decisions stored."));
|
|
20974
20950
|
return;
|
|
20975
20951
|
}
|
|
20976
20952
|
writeDecisions({});
|
|
20977
20953
|
console.log(
|
|
20978
|
-
|
|
20954
|
+
chalk26.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
|
|
20979
20955
|
);
|
|
20980
20956
|
});
|
|
20981
20957
|
}
|
|
20982
20958
|
|
|
20983
20959
|
// src/cli/commands/dlp.ts
|
|
20984
|
-
import
|
|
20960
|
+
import chalk27 from "chalk";
|
|
20985
20961
|
import fs43 from "fs";
|
|
20986
20962
|
import path45 from "path";
|
|
20987
20963
|
import os39 from "os";
|
|
@@ -21036,14 +21012,14 @@ function registerDlpCommand(program2) {
|
|
|
21036
21012
|
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
21037
21013
|
const findings = loadDlpFindings();
|
|
21038
21014
|
if (findings.length === 0) {
|
|
21039
|
-
console.log(
|
|
21015
|
+
console.log(chalk27.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
21040
21016
|
return;
|
|
21041
21017
|
}
|
|
21042
21018
|
const resolved = loadResolved();
|
|
21043
21019
|
for (const e of findings) resolved.add(entryKey(e));
|
|
21044
21020
|
saveResolved(resolved);
|
|
21045
21021
|
console.log(
|
|
21046
|
-
|
|
21022
|
+
chalk27.green(
|
|
21047
21023
|
`
|
|
21048
21024
|
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
21049
21025
|
`
|
|
@@ -21057,47 +21033,47 @@ function registerDlpCommand(program2) {
|
|
|
21057
21033
|
const resolvedCount = findings.length - open.length;
|
|
21058
21034
|
console.log("");
|
|
21059
21035
|
console.log(
|
|
21060
|
-
|
|
21036
|
+
chalk27.bold.cyan("\u{1F510} node9 dlp") + chalk27.dim(" \u2014 secrets found in Claude response text")
|
|
21061
21037
|
);
|
|
21062
21038
|
console.log("");
|
|
21063
21039
|
if (open.length === 0) {
|
|
21064
21040
|
if (resolvedCount > 0) {
|
|
21065
|
-
console.log(
|
|
21041
|
+
console.log(chalk27.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
21066
21042
|
} else {
|
|
21067
21043
|
console.log(
|
|
21068
|
-
|
|
21044
|
+
chalk27.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
21069
21045
|
);
|
|
21070
21046
|
}
|
|
21071
21047
|
console.log("");
|
|
21072
21048
|
return;
|
|
21073
21049
|
}
|
|
21074
21050
|
console.log(
|
|
21075
|
-
|
|
21051
|
+
chalk27.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk27.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
21076
21052
|
);
|
|
21077
21053
|
console.log("");
|
|
21078
21054
|
console.log(
|
|
21079
|
-
|
|
21055
|
+
chalk27.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
21080
21056
|
);
|
|
21081
|
-
console.log(
|
|
21057
|
+
console.log(chalk27.dim(" Rotate each affected key immediately.\n"));
|
|
21082
21058
|
for (const e of open) {
|
|
21083
21059
|
console.log(
|
|
21084
|
-
" " +
|
|
21060
|
+
" " + chalk27.red("\u25CF") + " " + chalk27.white(e.dlpPattern ?? "Secret") + chalk27.dim(" " + fmtDate3(e.ts))
|
|
21085
21061
|
);
|
|
21086
21062
|
if (e.dlpSample) {
|
|
21087
|
-
console.log(" " +
|
|
21063
|
+
console.log(" " + chalk27.dim("Sample: ") + chalk27.yellow(stripAnsi(e.dlpSample)));
|
|
21088
21064
|
}
|
|
21089
21065
|
if (e.project) {
|
|
21090
|
-
console.log(" " +
|
|
21066
|
+
console.log(" " + chalk27.dim("Project: ") + chalk27.dim(stripAnsi(e.project)));
|
|
21091
21067
|
}
|
|
21092
21068
|
console.log("");
|
|
21093
21069
|
}
|
|
21094
|
-
console.log(" " +
|
|
21095
|
-
console.log(" " +
|
|
21070
|
+
console.log(" " + chalk27.bold("Next steps:"));
|
|
21071
|
+
console.log(" " + chalk27.cyan("1.") + " Rotate any exposed keys shown above");
|
|
21096
21072
|
console.log(
|
|
21097
|
-
" " +
|
|
21073
|
+
" " + chalk27.cyan("2.") + " Run " + chalk27.white("node9 dlp resolve") + " to acknowledge"
|
|
21098
21074
|
);
|
|
21099
21075
|
console.log(
|
|
21100
|
-
" " +
|
|
21076
|
+
" " + chalk27.cyan("3.") + " Run " + chalk27.white("node9 report") + " for full audit history"
|
|
21101
21077
|
);
|
|
21102
21078
|
console.log("");
|
|
21103
21079
|
});
|
|
@@ -21105,7 +21081,7 @@ function registerDlpCommand(program2) {
|
|
|
21105
21081
|
|
|
21106
21082
|
// src/cli/commands/mask.ts
|
|
21107
21083
|
init_dlp();
|
|
21108
|
-
import
|
|
21084
|
+
import chalk28 from "chalk";
|
|
21109
21085
|
import fs44 from "fs";
|
|
21110
21086
|
import path46 from "path";
|
|
21111
21087
|
import os40 from "os";
|
|
@@ -21241,12 +21217,12 @@ function registerMaskCommand(program2) {
|
|
|
21241
21217
|
}
|
|
21242
21218
|
}) : allFiles;
|
|
21243
21219
|
if (filtered.length === 0) {
|
|
21244
|
-
console.log(
|
|
21220
|
+
console.log(chalk28.yellow(" No session files found."));
|
|
21245
21221
|
return;
|
|
21246
21222
|
}
|
|
21247
21223
|
console.log("");
|
|
21248
21224
|
if (dryRun) {
|
|
21249
|
-
console.log(
|
|
21225
|
+
console.log(chalk28.dim(" Dry run \u2014 no files will be modified.\n"));
|
|
21250
21226
|
}
|
|
21251
21227
|
let totalFiles = 0;
|
|
21252
21228
|
let totalLines = 0;
|
|
@@ -21262,23 +21238,23 @@ function registerMaskCommand(program2) {
|
|
|
21262
21238
|
});
|
|
21263
21239
|
const verb = dryRun ? "Would redact" : "Redacted";
|
|
21264
21240
|
console.log(
|
|
21265
|
-
" " +
|
|
21241
|
+
" " + chalk28.dim(shortPath.slice(0, 60).padEnd(62)) + chalk28.red(`${verb}: `) + chalk28.yellow(patterns.join(", ")) + chalk28.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
|
|
21266
21242
|
);
|
|
21267
21243
|
}
|
|
21268
21244
|
}
|
|
21269
21245
|
console.log("");
|
|
21270
21246
|
if (totalFiles === 0) {
|
|
21271
|
-
console.log(
|
|
21247
|
+
console.log(chalk28.green(" No secrets found in session history."));
|
|
21272
21248
|
} else {
|
|
21273
21249
|
const verb = dryRun ? "would be modified" : "modified";
|
|
21274
21250
|
console.log(
|
|
21275
|
-
|
|
21251
|
+
chalk28.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk28.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
|
|
21276
21252
|
);
|
|
21277
|
-
console.log(" Patterns: " +
|
|
21253
|
+
console.log(" Patterns: " + chalk28.yellow(totalPatterns.join(", ")));
|
|
21278
21254
|
if (!dryRun) {
|
|
21279
21255
|
console.log("");
|
|
21280
21256
|
console.log(
|
|
21281
|
-
|
|
21257
|
+
chalk28.dim(
|
|
21282
21258
|
" Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
|
|
21283
21259
|
)
|
|
21284
21260
|
);
|
|
@@ -21342,17 +21318,17 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
21342
21318
|
fs47.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
21343
21319
|
}
|
|
21344
21320
|
if (options.profile && profileName !== "default") {
|
|
21345
|
-
console.log(
|
|
21346
|
-
console.log(
|
|
21321
|
+
console.log(chalk30.green(`\u2705 Profile "${profileName}" saved`));
|
|
21322
|
+
console.log(chalk30.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
21347
21323
|
} else if (options.local) {
|
|
21348
|
-
console.log(
|
|
21349
|
-
console.log(
|
|
21324
|
+
console.log(chalk30.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
21325
|
+
console.log(chalk30.gray(` All decisions stay on this machine.`));
|
|
21350
21326
|
} else {
|
|
21351
|
-
console.log(
|
|
21352
|
-
console.log(
|
|
21327
|
+
console.log(chalk30.green(`\u2705 Logged in \u2014 agent mode`));
|
|
21328
|
+
console.log(chalk30.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
21353
21329
|
}
|
|
21354
21330
|
});
|
|
21355
|
-
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
21331
|
+
program.command("addto", { hidden: true }).description("Integrate Node9 with an AI agent").addHelpText(
|
|
21356
21332
|
"after",
|
|
21357
21333
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21358
21334
|
).argument(
|
|
@@ -21367,13 +21343,13 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
21367
21343
|
if (target === "vscode") return await setupVSCode();
|
|
21368
21344
|
if (target === "hud") return setupHud();
|
|
21369
21345
|
console.error(
|
|
21370
|
-
|
|
21346
|
+
chalk30.red(
|
|
21371
21347
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21372
21348
|
)
|
|
21373
21349
|
);
|
|
21374
21350
|
process.exit(1);
|
|
21375
21351
|
});
|
|
21376
|
-
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
21352
|
+
program.command("setup", { hidden: true }).description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
21377
21353
|
"after",
|
|
21378
21354
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21379
21355
|
).argument(
|
|
@@ -21381,17 +21357,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
21381
21357
|
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
21382
21358
|
).action(async (target) => {
|
|
21383
21359
|
if (!target) {
|
|
21384
|
-
console.log(
|
|
21385
|
-
console.log(" Usage: " +
|
|
21360
|
+
console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
21361
|
+
console.log(" Usage: " + chalk30.white("node9 setup <target>") + "\n");
|
|
21386
21362
|
console.log(" Targets:");
|
|
21387
|
-
console.log(" " +
|
|
21388
|
-
console.log(" " +
|
|
21389
|
-
console.log(" " +
|
|
21390
|
-
console.log(" " +
|
|
21391
|
-
console.log(" " +
|
|
21392
|
-
console.log(" " +
|
|
21363
|
+
console.log(" " + chalk30.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
21364
|
+
console.log(" " + chalk30.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
21365
|
+
console.log(" " + chalk30.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
21366
|
+
console.log(" " + chalk30.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
21367
|
+
console.log(" " + chalk30.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
21368
|
+
console.log(" " + chalk30.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
21393
21369
|
process.stdout.write(
|
|
21394
|
-
" " +
|
|
21370
|
+
" " + chalk30.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
21395
21371
|
);
|
|
21396
21372
|
console.log("");
|
|
21397
21373
|
return;
|
|
@@ -21405,13 +21381,13 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
21405
21381
|
if (t === "vscode") return await setupVSCode();
|
|
21406
21382
|
if (t === "hud") return setupHud();
|
|
21407
21383
|
console.error(
|
|
21408
|
-
|
|
21384
|
+
chalk30.red(
|
|
21409
21385
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21410
21386
|
)
|
|
21411
21387
|
);
|
|
21412
21388
|
process.exit(1);
|
|
21413
21389
|
});
|
|
21414
|
-
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
21390
|
+
program.command("removefrom", { hidden: true }).description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
21415
21391
|
"after",
|
|
21416
21392
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21417
21393
|
).argument(
|
|
@@ -21428,33 +21404,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
21428
21404
|
else if (target === "hud") fn = teardownHud;
|
|
21429
21405
|
else {
|
|
21430
21406
|
console.error(
|
|
21431
|
-
|
|
21407
|
+
chalk30.red(
|
|
21432
21408
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21433
21409
|
)
|
|
21434
21410
|
);
|
|
21435
21411
|
process.exit(1);
|
|
21436
21412
|
}
|
|
21437
|
-
console.log(
|
|
21413
|
+
console.log(chalk30.cyan(`
|
|
21438
21414
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
21439
21415
|
`));
|
|
21440
21416
|
try {
|
|
21441
21417
|
fn();
|
|
21442
21418
|
} catch (err2) {
|
|
21443
|
-
console.error(
|
|
21419
|
+
console.error(chalk30.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21444
21420
|
process.exit(1);
|
|
21445
21421
|
}
|
|
21446
|
-
console.log(
|
|
21422
|
+
console.log(chalk30.gray("\n Restart the agent for changes to take effect."));
|
|
21447
21423
|
});
|
|
21448
21424
|
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) => {
|
|
21449
|
-
console.log(
|
|
21450
|
-
console.log(
|
|
21425
|
+
console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
21426
|
+
console.log(chalk30.bold("Stopping daemon..."));
|
|
21451
21427
|
try {
|
|
21452
21428
|
stopDaemon();
|
|
21453
|
-
console.log(
|
|
21429
|
+
console.log(chalk30.green(" \u2705 Daemon stopped"));
|
|
21454
21430
|
} catch {
|
|
21455
|
-
console.log(
|
|
21431
|
+
console.log(chalk30.blue(" \u2139\uFE0F Daemon was not running"));
|
|
21456
21432
|
}
|
|
21457
|
-
console.log(
|
|
21433
|
+
console.log(chalk30.bold("\nRemoving hooks..."));
|
|
21458
21434
|
let teardownFailed = false;
|
|
21459
21435
|
for (const [label, fn] of [
|
|
21460
21436
|
["Claude", teardownClaude],
|
|
@@ -21469,7 +21445,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
21469
21445
|
} catch (err2) {
|
|
21470
21446
|
teardownFailed = true;
|
|
21471
21447
|
console.error(
|
|
21472
|
-
|
|
21448
|
+
chalk30.red(
|
|
21473
21449
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
21474
21450
|
)
|
|
21475
21451
|
);
|
|
@@ -21486,28 +21462,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
21486
21462
|
fs47.rmSync(node9Dir, { recursive: true });
|
|
21487
21463
|
if (fs47.existsSync(node9Dir)) {
|
|
21488
21464
|
console.error(
|
|
21489
|
-
|
|
21465
|
+
chalk30.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
21490
21466
|
);
|
|
21491
21467
|
} else {
|
|
21492
|
-
console.log(
|
|
21468
|
+
console.log(chalk30.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
21493
21469
|
}
|
|
21494
21470
|
} else {
|
|
21495
|
-
console.log(
|
|
21471
|
+
console.log(chalk30.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
21496
21472
|
}
|
|
21497
21473
|
} else {
|
|
21498
|
-
console.log(
|
|
21474
|
+
console.log(chalk30.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
21499
21475
|
}
|
|
21500
21476
|
} else {
|
|
21501
21477
|
console.log(
|
|
21502
|
-
|
|
21478
|
+
chalk30.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
21503
21479
|
);
|
|
21504
21480
|
}
|
|
21505
21481
|
if (teardownFailed) {
|
|
21506
|
-
console.error(
|
|
21482
|
+
console.error(chalk30.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
21507
21483
|
process.exit(1);
|
|
21508
21484
|
}
|
|
21509
|
-
console.log(
|
|
21510
|
-
console.log(
|
|
21485
|
+
console.log(chalk30.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
21486
|
+
console.log(chalk30.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
21511
21487
|
});
|
|
21512
21488
|
registerDoctorCommand(program, version);
|
|
21513
21489
|
program.command("explain").description(
|
|
@@ -21520,7 +21496,7 @@ program.command("explain").description(
|
|
|
21520
21496
|
try {
|
|
21521
21497
|
args = JSON.parse(trimmed);
|
|
21522
21498
|
} catch {
|
|
21523
|
-
console.error(
|
|
21499
|
+
console.error(chalk30.red(`
|
|
21524
21500
|
\u274C Invalid JSON: ${trimmed}
|
|
21525
21501
|
`));
|
|
21526
21502
|
process.exit(1);
|
|
@@ -21531,54 +21507,54 @@ program.command("explain").description(
|
|
|
21531
21507
|
}
|
|
21532
21508
|
const result = await explainPolicy(tool, args);
|
|
21533
21509
|
console.log("");
|
|
21534
|
-
console.log(
|
|
21510
|
+
console.log(chalk30.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
21535
21511
|
console.log("");
|
|
21536
|
-
console.log(` ${
|
|
21512
|
+
console.log(` ${chalk30.bold("Tool:")} ${chalk30.white(result.tool)}`);
|
|
21537
21513
|
if (argsRaw) {
|
|
21538
21514
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
21539
|
-
console.log(` ${
|
|
21515
|
+
console.log(` ${chalk30.bold("Input:")} ${chalk30.gray(preview2)}`);
|
|
21540
21516
|
}
|
|
21541
21517
|
console.log("");
|
|
21542
|
-
console.log(
|
|
21518
|
+
console.log(chalk30.bold("Config Sources (Waterfall):"));
|
|
21543
21519
|
for (const tier of result.waterfall) {
|
|
21544
|
-
const num3 =
|
|
21520
|
+
const num3 = chalk30.gray(` ${tier.tier}.`);
|
|
21545
21521
|
const label = tier.label.padEnd(16);
|
|
21546
21522
|
let statusStr;
|
|
21547
21523
|
if (tier.tier === 1) {
|
|
21548
|
-
statusStr =
|
|
21524
|
+
statusStr = chalk30.gray(tier.note ?? "");
|
|
21549
21525
|
} else if (tier.status === "active") {
|
|
21550
|
-
const loc = tier.path ?
|
|
21551
|
-
const note = tier.note ?
|
|
21552
|
-
statusStr =
|
|
21526
|
+
const loc = tier.path ? chalk30.gray(tier.path) : "";
|
|
21527
|
+
const note = tier.note ? chalk30.gray(`(${tier.note})`) : "";
|
|
21528
|
+
statusStr = chalk30.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
21553
21529
|
} else {
|
|
21554
|
-
statusStr =
|
|
21530
|
+
statusStr = chalk30.gray("\u25CB " + (tier.note ?? "not found"));
|
|
21555
21531
|
}
|
|
21556
|
-
console.log(`${num3} ${
|
|
21532
|
+
console.log(`${num3} ${chalk30.white(label)} ${statusStr}`);
|
|
21557
21533
|
}
|
|
21558
21534
|
console.log("");
|
|
21559
|
-
console.log(
|
|
21535
|
+
console.log(chalk30.bold("Policy Evaluation:"));
|
|
21560
21536
|
for (const step of result.steps) {
|
|
21561
21537
|
const isFinal = step.isFinal;
|
|
21562
21538
|
let icon;
|
|
21563
|
-
if (step.outcome === "allow") icon =
|
|
21564
|
-
else if (step.outcome === "review") icon =
|
|
21565
|
-
else if (step.outcome === "skip") icon =
|
|
21566
|
-
else icon =
|
|
21539
|
+
if (step.outcome === "allow") icon = chalk30.green(" \u2705");
|
|
21540
|
+
else if (step.outcome === "review") icon = chalk30.red(" \u{1F534}");
|
|
21541
|
+
else if (step.outcome === "skip") icon = chalk30.gray(" \u2500 ");
|
|
21542
|
+
else icon = chalk30.gray(" \u25CB ");
|
|
21567
21543
|
const name = step.name.padEnd(18);
|
|
21568
|
-
const nameStr = isFinal ?
|
|
21569
|
-
const detail = isFinal ?
|
|
21570
|
-
const arrow = isFinal ?
|
|
21544
|
+
const nameStr = isFinal ? chalk30.white.bold(name) : chalk30.white(name);
|
|
21545
|
+
const detail = isFinal ? chalk30.white(step.detail) : chalk30.gray(step.detail);
|
|
21546
|
+
const arrow = isFinal ? chalk30.yellow(" \u2190 STOP") : "";
|
|
21571
21547
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
21572
21548
|
}
|
|
21573
21549
|
console.log("");
|
|
21574
21550
|
if (result.decision === "allow") {
|
|
21575
|
-
console.log(
|
|
21551
|
+
console.log(chalk30.green.bold(" Decision: \u2705 ALLOW") + chalk30.gray(" \u2014 no approval needed"));
|
|
21576
21552
|
} else {
|
|
21577
21553
|
console.log(
|
|
21578
|
-
|
|
21554
|
+
chalk30.red.bold(" Decision: \u{1F534} REVIEW") + chalk30.gray(" \u2014 human approval required")
|
|
21579
21555
|
);
|
|
21580
21556
|
if (result.blockedByLabel) {
|
|
21581
|
-
console.log(
|
|
21557
|
+
console.log(chalk30.gray(` Reason: ${result.blockedByLabel}`));
|
|
21582
21558
|
}
|
|
21583
21559
|
}
|
|
21584
21560
|
console.log("");
|
|
@@ -21593,7 +21569,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
21593
21569
|
try {
|
|
21594
21570
|
await startTail2(options);
|
|
21595
21571
|
} catch (err2) {
|
|
21596
|
-
console.error(
|
|
21572
|
+
console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21597
21573
|
process.exit(1);
|
|
21598
21574
|
}
|
|
21599
21575
|
});
|
|
@@ -21604,11 +21580,10 @@ program.command("monitor").description("Live interactive dashboard \u2014 activi
|
|
|
21604
21580
|
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
21605
21581
|
await mod.startMonitor();
|
|
21606
21582
|
} catch (err2) {
|
|
21607
|
-
console.error(
|
|
21583
|
+
console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21608
21584
|
process.exit(1);
|
|
21609
21585
|
}
|
|
21610
21586
|
});
|
|
21611
|
-
registerWatchCommand(program);
|
|
21612
21587
|
registerMcpGatewayCommand(program);
|
|
21613
21588
|
registerMcpServerCommand(program);
|
|
21614
21589
|
registerMcpPinCommand(program);
|
|
@@ -21616,7 +21591,7 @@ registerSkillPinCommand(program);
|
|
|
21616
21591
|
registerDecisionsCommand(program);
|
|
21617
21592
|
registerCheckCommand(program);
|
|
21618
21593
|
registerLogCommand(program);
|
|
21619
|
-
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
21594
|
+
program.command("hud", { hidden: true }).description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
21620
21595
|
"after",
|
|
21621
21596
|
`
|
|
21622
21597
|
Outputs up to 3 lines to stdout, then exits:
|
|
@@ -21660,7 +21635,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
21660
21635
|
const ms = parseDuration(options.duration);
|
|
21661
21636
|
if (ms === null) {
|
|
21662
21637
|
console.error(
|
|
21663
|
-
|
|
21638
|
+
chalk30.red(`
|
|
21664
21639
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
21665
21640
|
`)
|
|
21666
21641
|
);
|
|
@@ -21668,20 +21643,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
21668
21643
|
}
|
|
21669
21644
|
pauseNode9(ms, options.duration);
|
|
21670
21645
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
21671
|
-
console.log(
|
|
21646
|
+
console.log(chalk30.yellow(`
|
|
21672
21647
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
21673
|
-
console.log(
|
|
21674
|
-
console.log(
|
|
21648
|
+
console.log(chalk30.gray(` All tool calls will be allowed without review.`));
|
|
21649
|
+
console.log(chalk30.gray(` Run "node9 resume" to re-enable early.
|
|
21675
21650
|
`));
|
|
21676
21651
|
});
|
|
21677
21652
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
21678
21653
|
const { paused } = checkPause();
|
|
21679
21654
|
if (!paused) {
|
|
21680
|
-
console.log(
|
|
21655
|
+
console.log(chalk30.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
21681
21656
|
return;
|
|
21682
21657
|
}
|
|
21683
21658
|
resumeNode9();
|
|
21684
|
-
console.log(
|
|
21659
|
+
console.log(chalk30.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
21685
21660
|
});
|
|
21686
21661
|
var HOOK_BASED_AGENTS = {
|
|
21687
21662
|
claude: "claude",
|
|
@@ -21694,15 +21669,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21694
21669
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
21695
21670
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
21696
21671
|
console.error(
|
|
21697
|
-
|
|
21672
|
+
chalk30.yellow(`
|
|
21698
21673
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
21699
21674
|
);
|
|
21700
|
-
console.error(
|
|
21675
|
+
console.error(chalk30.white(`
|
|
21701
21676
|
"${target}" uses its own hook system. Use:`));
|
|
21702
21677
|
console.error(
|
|
21703
|
-
|
|
21678
|
+
chalk30.green(` node9 addto ${target} `) + chalk30.gray("# one-time setup")
|
|
21704
21679
|
);
|
|
21705
|
-
console.error(
|
|
21680
|
+
console.error(chalk30.green(` ${target} `) + chalk30.gray("# run normally"));
|
|
21706
21681
|
process.exit(1);
|
|
21707
21682
|
}
|
|
21708
21683
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -21719,7 +21694,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21719
21694
|
}
|
|
21720
21695
|
);
|
|
21721
21696
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
21722
|
-
console.error(
|
|
21697
|
+
console.error(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
21723
21698
|
const daemonReady = await autoStartDaemonAndWait();
|
|
21724
21699
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
21725
21700
|
}
|
|
@@ -21732,12 +21707,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21732
21707
|
}
|
|
21733
21708
|
if (!result.approved) {
|
|
21734
21709
|
console.error(
|
|
21735
|
-
|
|
21710
|
+
chalk30.red(`
|
|
21736
21711
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
21737
21712
|
);
|
|
21738
21713
|
process.exit(1);
|
|
21739
21714
|
}
|
|
21740
|
-
console.error(
|
|
21715
|
+
console.error(chalk30.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
21741
21716
|
await runProxy(fullCommand);
|
|
21742
21717
|
} else {
|
|
21743
21718
|
program.help();
|