@node9/proxy 1.21.0 → 1.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +325 -387
- package/dist/cli.mjs +324 -386
- 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);
|
|
@@ -18220,7 +18220,7 @@ function registerInitCommand(program2) {
|
|
|
18220
18220
|
const agentList = found.join(", ");
|
|
18221
18221
|
console.log(chalk16.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
18222
18222
|
console.log("");
|
|
18223
|
-
console.log(chalk16.white(" Watch live: ") + chalk16.cyan("node9
|
|
18223
|
+
console.log(chalk16.white(" Watch live: ") + chalk16.cyan("node9 monitor"));
|
|
18224
18224
|
console.log("");
|
|
18225
18225
|
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
18226
|
console.log(
|
|
@@ -18517,72 +18517,11 @@ function registerUndoCommand(program2) {
|
|
|
18517
18517
|
});
|
|
18518
18518
|
}
|
|
18519
18519
|
|
|
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
18520
|
// src/mcp-gateway/index.ts
|
|
18582
18521
|
init_orchestrator();
|
|
18583
18522
|
import readline4 from "readline";
|
|
18584
|
-
import
|
|
18585
|
-
import { spawn as
|
|
18523
|
+
import chalk19 from "chalk";
|
|
18524
|
+
import { spawn as spawn7 } from "child_process";
|
|
18586
18525
|
import { execa as execa2 } from "execa";
|
|
18587
18526
|
init_provenance();
|
|
18588
18527
|
|
|
@@ -18742,13 +18681,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18742
18681
|
const prov = checkProvenance(executable);
|
|
18743
18682
|
if (prov.trustLevel === "suspect") {
|
|
18744
18683
|
console.error(
|
|
18745
|
-
|
|
18684
|
+
chalk19.red(
|
|
18746
18685
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
18747
18686
|
)
|
|
18748
18687
|
);
|
|
18749
|
-
console.error(
|
|
18688
|
+
console.error(chalk19.red(" Verify this binary is trusted before proceeding."));
|
|
18750
18689
|
}
|
|
18751
|
-
console.error(
|
|
18690
|
+
console.error(chalk19.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
18752
18691
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
18753
18692
|
"NODE_OPTIONS",
|
|
18754
18693
|
"NODE_PATH",
|
|
@@ -18767,7 +18706,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18767
18706
|
const safeEnv = Object.fromEntries(
|
|
18768
18707
|
Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
|
|
18769
18708
|
);
|
|
18770
|
-
const child =
|
|
18709
|
+
const child = spawn7(executable, cmdArgs, {
|
|
18771
18710
|
stdio: ["pipe", "pipe", "inherit"],
|
|
18772
18711
|
// control stdin/stdout; inherit stderr
|
|
18773
18712
|
shell: false,
|
|
@@ -18861,10 +18800,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18861
18800
|
mcpServer
|
|
18862
18801
|
});
|
|
18863
18802
|
if (!result.approved) {
|
|
18864
|
-
console.error(
|
|
18803
|
+
console.error(chalk19.red(`
|
|
18865
18804
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
18866
|
-
console.error(
|
|
18867
|
-
console.error(
|
|
18805
|
+
console.error(chalk19.gray(` Tool: ${toolName}`));
|
|
18806
|
+
console.error(chalk19.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
18868
18807
|
`));
|
|
18869
18808
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
18870
18809
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -18976,7 +18915,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18976
18915
|
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
18977
18916
|
pinState = "validated";
|
|
18978
18917
|
console.error(
|
|
18979
|
-
|
|
18918
|
+
chalk19.green(
|
|
18980
18919
|
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
18981
18920
|
)
|
|
18982
18921
|
);
|
|
@@ -18989,11 +18928,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18989
18928
|
} else if (pinStatus === "corrupt") {
|
|
18990
18929
|
pinState = "quarantined";
|
|
18991
18930
|
console.error(
|
|
18992
|
-
|
|
18931
|
+
chalk19.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
18993
18932
|
);
|
|
18994
|
-
console.error(
|
|
18933
|
+
console.error(chalk19.red(" Tool calls are blocked until the pin file is repaired."));
|
|
18995
18934
|
console.error(
|
|
18996
|
-
|
|
18935
|
+
chalk19.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
18997
18936
|
`)
|
|
18998
18937
|
);
|
|
18999
18938
|
const errorResponse = {
|
|
@@ -19010,13 +18949,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
19010
18949
|
} else {
|
|
19011
18950
|
pinState = "quarantined";
|
|
19012
18951
|
console.error(
|
|
19013
|
-
|
|
18952
|
+
chalk19.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
19014
18953
|
);
|
|
19015
18954
|
console.error(
|
|
19016
|
-
|
|
18955
|
+
chalk19.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
19017
18956
|
);
|
|
19018
|
-
console.error(
|
|
19019
|
-
console.error(
|
|
18957
|
+
console.error(chalk19.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
18958
|
+
console.error(chalk19.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
19020
18959
|
`));
|
|
19021
18960
|
const errorResponse = {
|
|
19022
18961
|
jsonrpc: "2.0",
|
|
@@ -19059,7 +18998,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
19059
18998
|
const toolName = callId !== void 0 ? pendingCallNames.get(callId) ?? "unknown" : "unknown";
|
|
19060
18999
|
if (callId !== void 0) pendingCallNames.delete(callId);
|
|
19061
19000
|
console.error(
|
|
19062
|
-
|
|
19001
|
+
chalk19.yellow(
|
|
19063
19002
|
`\u26A1 Node9: Large MCP response from '${toolName}' (${(line.length / 1024).toFixed(0)}KB) \u2014 context window enlarged`
|
|
19064
19003
|
)
|
|
19065
19004
|
);
|
|
@@ -19113,7 +19052,7 @@ import readline5 from "readline";
|
|
|
19113
19052
|
import fs39 from "fs";
|
|
19114
19053
|
import os35 from "os";
|
|
19115
19054
|
import path41 from "path";
|
|
19116
|
-
import { spawnSync as
|
|
19055
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
19117
19056
|
init_core();
|
|
19118
19057
|
init_daemon();
|
|
19119
19058
|
init_shields();
|
|
@@ -19587,7 +19526,7 @@ function handleRuleAdd(args) {
|
|
|
19587
19526
|
return `Rule "${name}" added to ~/.node9/config.json \u2014 verdict: ${verdict} when ${field} matches "${pattern}"`;
|
|
19588
19527
|
}
|
|
19589
19528
|
function runCliCommand(subArgs) {
|
|
19590
|
-
const result =
|
|
19529
|
+
const result = spawnSync4(process.execPath, [process.argv[1], ...subArgs], {
|
|
19591
19530
|
encoding: "utf-8",
|
|
19592
19531
|
timeout: 6e4,
|
|
19593
19532
|
// Disable colors — stdout is piped (not a TTY), chalk auto-detects, but be explicit
|
|
@@ -19782,7 +19721,7 @@ function registerMcpServerCommand(program2) {
|
|
|
19782
19721
|
|
|
19783
19722
|
// src/cli/commands/trust.ts
|
|
19784
19723
|
init_trusted_hosts();
|
|
19785
|
-
import
|
|
19724
|
+
import chalk20 from "chalk";
|
|
19786
19725
|
function isValidHost(host) {
|
|
19787
19726
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
19788
19727
|
}
|
|
@@ -19792,51 +19731,51 @@ function registerTrustCommand(program2) {
|
|
|
19792
19731
|
const normalized = normalizeHost(host.trim());
|
|
19793
19732
|
if (!isValidHost(normalized)) {
|
|
19794
19733
|
console.error(
|
|
19795
|
-
|
|
19734
|
+
chalk20.red(`
|
|
19796
19735
|
\u274C Invalid host: "${host}"
|
|
19797
|
-
`) +
|
|
19736
|
+
`) + chalk20.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
19798
19737
|
);
|
|
19799
19738
|
process.exit(1);
|
|
19800
19739
|
}
|
|
19801
19740
|
addTrustedHost(normalized);
|
|
19802
|
-
console.log(
|
|
19741
|
+
console.log(chalk20.green(`
|
|
19803
19742
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
19804
19743
|
console.log(
|
|
19805
|
-
|
|
19744
|
+
chalk20.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
19806
19745
|
);
|
|
19807
19746
|
});
|
|
19808
19747
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
19809
19748
|
const normalized = normalizeHost(host.trim());
|
|
19810
19749
|
const removed = removeTrustedHost(normalized);
|
|
19811
19750
|
if (!removed) {
|
|
19812
|
-
console.error(
|
|
19751
|
+
console.error(chalk20.yellow(`
|
|
19813
19752
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
19814
19753
|
`));
|
|
19815
19754
|
process.exit(1);
|
|
19816
19755
|
}
|
|
19817
|
-
console.log(
|
|
19756
|
+
console.log(chalk20.green(`
|
|
19818
19757
|
\u2705 ${normalized} removed from trusted hosts.
|
|
19819
19758
|
`));
|
|
19820
19759
|
});
|
|
19821
19760
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
19822
19761
|
const hosts = readTrustedHosts();
|
|
19823
19762
|
if (hosts.length === 0) {
|
|
19824
|
-
console.log(
|
|
19825
|
-
console.log(` Add one: ${
|
|
19763
|
+
console.log(chalk20.gray("\n No trusted hosts configured.\n"));
|
|
19764
|
+
console.log(` Add one: ${chalk20.cyan("node9 trust add api.mycompany.com")}
|
|
19826
19765
|
`);
|
|
19827
19766
|
return;
|
|
19828
19767
|
}
|
|
19829
|
-
console.log(
|
|
19768
|
+
console.log(chalk20.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
19830
19769
|
for (const entry of hosts) {
|
|
19831
19770
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
19832
|
-
console.log(` ${
|
|
19771
|
+
console.log(` ${chalk20.cyan(entry.host.padEnd(40))} ${chalk20.gray(`added ${date}`)}`);
|
|
19833
19772
|
}
|
|
19834
19773
|
console.log("");
|
|
19835
19774
|
});
|
|
19836
19775
|
}
|
|
19837
19776
|
|
|
19838
19777
|
// src/cli/commands/mcp-pin.ts
|
|
19839
|
-
import
|
|
19778
|
+
import chalk21 from "chalk";
|
|
19840
19779
|
function registerMcpPinCommand(program2) {
|
|
19841
19780
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
19842
19781
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -19844,31 +19783,31 @@ function registerMcpPinCommand(program2) {
|
|
|
19844
19783
|
const result = readMcpPinsSafe();
|
|
19845
19784
|
if (!result.ok) {
|
|
19846
19785
|
if (result.reason === "missing") {
|
|
19847
|
-
console.log(
|
|
19786
|
+
console.log(chalk21.gray("\nNo MCP servers are pinned yet."));
|
|
19848
19787
|
console.log(
|
|
19849
|
-
|
|
19788
|
+
chalk21.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
19850
19789
|
);
|
|
19851
19790
|
return;
|
|
19852
19791
|
}
|
|
19853
|
-
console.error(
|
|
19792
|
+
console.error(chalk21.red(`
|
|
19854
19793
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
19855
|
-
console.error(
|
|
19794
|
+
console.error(chalk21.yellow(" Run: node9 mcp pin reset\n"));
|
|
19856
19795
|
process.exit(1);
|
|
19857
19796
|
}
|
|
19858
19797
|
const entries = Object.entries(result.pins.servers);
|
|
19859
19798
|
if (entries.length === 0) {
|
|
19860
|
-
console.log(
|
|
19799
|
+
console.log(chalk21.gray("\nNo MCP servers are pinned yet."));
|
|
19861
19800
|
console.log(
|
|
19862
|
-
|
|
19801
|
+
chalk21.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
19863
19802
|
);
|
|
19864
19803
|
return;
|
|
19865
19804
|
}
|
|
19866
|
-
console.log(
|
|
19805
|
+
console.log(chalk21.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
19867
19806
|
for (const [key, entry] of entries) {
|
|
19868
|
-
console.log(` ${
|
|
19869
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
19870
|
-
console.log(` Hash: ${
|
|
19871
|
-
console.log(` Pinned: ${
|
|
19807
|
+
console.log(` ${chalk21.cyan(key)} ${chalk21.gray(entry.label)}`);
|
|
19808
|
+
console.log(` Tools (${entry.toolCount}): ${chalk21.white(entry.toolNames.join(", "))}`);
|
|
19809
|
+
console.log(` Hash: ${chalk21.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
19810
|
+
console.log(` Pinned: ${chalk21.gray(entry.pinnedAt)}`);
|
|
19872
19811
|
console.log("");
|
|
19873
19812
|
}
|
|
19874
19813
|
});
|
|
@@ -19879,127 +19818,127 @@ function registerMcpPinCommand(program2) {
|
|
|
19879
19818
|
try {
|
|
19880
19819
|
pins = readMcpPins();
|
|
19881
19820
|
} catch {
|
|
19882
|
-
console.error(
|
|
19883
|
-
console.error(
|
|
19821
|
+
console.error(chalk21.red("\n\u274C Pin file is corrupt."));
|
|
19822
|
+
console.error(chalk21.yellow(" Run: node9 mcp pin reset\n"));
|
|
19884
19823
|
process.exit(1);
|
|
19885
19824
|
}
|
|
19886
19825
|
if (!pins.servers[serverKey]) {
|
|
19887
|
-
console.error(
|
|
19826
|
+
console.error(chalk21.red(`
|
|
19888
19827
|
\u274C No pin found for server key "${serverKey}"
|
|
19889
19828
|
`));
|
|
19890
|
-
console.error(`Run ${
|
|
19829
|
+
console.error(`Run ${chalk21.cyan("node9 mcp pin list")} to see pinned servers.
|
|
19891
19830
|
`);
|
|
19892
19831
|
process.exit(1);
|
|
19893
19832
|
}
|
|
19894
19833
|
const label = pins.servers[serverKey].label;
|
|
19895
19834
|
removePin2(serverKey);
|
|
19896
|
-
console.log(
|
|
19897
|
-
\u{1F513} Pin removed for ${
|
|
19898
|
-
console.log(
|
|
19899
|
-
console.log(
|
|
19835
|
+
console.log(chalk21.green(`
|
|
19836
|
+
\u{1F513} Pin removed for ${chalk21.cyan(serverKey)}`));
|
|
19837
|
+
console.log(chalk21.gray(` Server: ${label}`));
|
|
19838
|
+
console.log(chalk21.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
19900
19839
|
});
|
|
19901
19840
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
19902
19841
|
const result = readMcpPinsSafe();
|
|
19903
19842
|
if (!result.ok && result.reason === "missing") {
|
|
19904
|
-
console.log(
|
|
19843
|
+
console.log(chalk21.gray("\nNo pins to clear.\n"));
|
|
19905
19844
|
return;
|
|
19906
19845
|
}
|
|
19907
19846
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
19908
19847
|
clearAllPins2();
|
|
19909
|
-
console.log(
|
|
19848
|
+
console.log(chalk21.green(`
|
|
19910
19849
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
19911
|
-
console.log(
|
|
19850
|
+
console.log(chalk21.gray(" Next connection to each server will re-pin.\n"));
|
|
19912
19851
|
});
|
|
19913
19852
|
}
|
|
19914
19853
|
|
|
19915
19854
|
// src/cli/commands/sync.ts
|
|
19916
19855
|
init_sync();
|
|
19917
|
-
import
|
|
19856
|
+
import chalk22 from "chalk";
|
|
19918
19857
|
function registerSyncCommand(program2) {
|
|
19919
19858
|
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
19920
19859
|
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
19921
|
-
process.stdout.write(
|
|
19860
|
+
process.stdout.write(chalk22.cyan("Syncing cloud policy rules\u2026"));
|
|
19922
19861
|
const result = await runCloudSync();
|
|
19923
19862
|
process.stdout.write("\n");
|
|
19924
19863
|
if (!result.ok) {
|
|
19925
|
-
console.error(
|
|
19864
|
+
console.error(chalk22.red(`\u2717 ${result.reason}`));
|
|
19926
19865
|
process.exit(1);
|
|
19927
19866
|
}
|
|
19928
19867
|
if (result.unchanged) {
|
|
19929
19868
|
console.log(
|
|
19930
|
-
|
|
19869
|
+
chalk22.green(
|
|
19931
19870
|
`\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
|
|
19932
19871
|
)
|
|
19933
19872
|
);
|
|
19934
|
-
console.log(
|
|
19935
|
-
console.log(
|
|
19873
|
+
console.log(chalk22.gray(` Cached at: ${result.fetchedAt}`));
|
|
19874
|
+
console.log(chalk22.gray(` Server returned 304 (no changes since last sync)`));
|
|
19936
19875
|
} else {
|
|
19937
19876
|
console.log(
|
|
19938
|
-
|
|
19877
|
+
chalk22.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
19939
19878
|
);
|
|
19940
|
-
console.log(
|
|
19941
|
-
console.log(
|
|
19879
|
+
console.log(chalk22.gray(` Cached at: ${result.fetchedAt}`));
|
|
19880
|
+
console.log(chalk22.gray(` File: ~/.node9/rules-cache.json`));
|
|
19942
19881
|
}
|
|
19943
19882
|
});
|
|
19944
19883
|
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
19945
19884
|
const status = getCloudSyncStatus();
|
|
19946
19885
|
if (!status.cached) {
|
|
19947
|
-
console.log(
|
|
19886
|
+
console.log(chalk22.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
19948
19887
|
return;
|
|
19949
19888
|
}
|
|
19950
19889
|
const rules = getCloudRules() ?? [];
|
|
19951
19890
|
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
19952
19891
|
console.log(
|
|
19953
|
-
|
|
19954
|
-
Cloud policy rules`) +
|
|
19892
|
+
chalk22.bold(`
|
|
19893
|
+
Cloud policy rules`) + chalk22.gray(
|
|
19955
19894
|
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
19956
19895
|
`
|
|
19957
19896
|
)
|
|
19958
19897
|
);
|
|
19959
19898
|
if (rules.length === 0) {
|
|
19960
|
-
console.log(
|
|
19899
|
+
console.log(chalk22.gray(" No rules defined in cloud policy.\n"));
|
|
19961
19900
|
return;
|
|
19962
19901
|
}
|
|
19963
19902
|
for (const rule of rules) {
|
|
19964
19903
|
const r = rule;
|
|
19965
|
-
const verdictColor = r.verdict === "block" ?
|
|
19904
|
+
const verdictColor = r.verdict === "block" ? chalk22.red : r.verdict === "allow" ? chalk22.green : chalk22.yellow;
|
|
19966
19905
|
console.log(
|
|
19967
19906
|
` ${verdictColor(
|
|
19968
19907
|
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
19969
|
-
)} ${
|
|
19908
|
+
)} ${chalk22.white(String(r.name ?? "(unnamed)"))}`
|
|
19970
19909
|
);
|
|
19971
|
-
if (r.reason) console.log(
|
|
19910
|
+
if (r.reason) console.log(chalk22.gray(` ${String(r.reason)}`));
|
|
19972
19911
|
}
|
|
19973
19912
|
console.log("");
|
|
19974
19913
|
});
|
|
19975
19914
|
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
19976
19915
|
const s = getCloudSyncStatus();
|
|
19977
19916
|
if (!s.cached) {
|
|
19978
|
-
console.log(
|
|
19917
|
+
console.log(chalk22.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
19979
19918
|
return;
|
|
19980
19919
|
}
|
|
19981
19920
|
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
19982
19921
|
console.log(`
|
|
19983
|
-
Rules : ${
|
|
19922
|
+
Rules : ${chalk22.green(String(s.rules))} cloud rules loaded`);
|
|
19984
19923
|
console.log(
|
|
19985
|
-
` Synced : ${
|
|
19924
|
+
` Synced : ${chalk22.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
|
|
19986
19925
|
);
|
|
19987
19926
|
if (s.workspaceId) {
|
|
19988
|
-
console.log(` Workspace: ${
|
|
19927
|
+
console.log(` Workspace: ${chalk22.gray(s.workspaceId)}`);
|
|
19989
19928
|
}
|
|
19990
19929
|
if (s.panicMode) {
|
|
19991
19930
|
console.log(
|
|
19992
|
-
` ${
|
|
19931
|
+
` ${chalk22.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk22.dim("(every review-verdict becomes block)")
|
|
19993
19932
|
);
|
|
19994
19933
|
}
|
|
19995
19934
|
if (s.shadowMode) {
|
|
19996
19935
|
console.log(
|
|
19997
|
-
` ${
|
|
19936
|
+
` ${chalk22.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk22.dim("(blocks become would-block log entries)")
|
|
19998
19937
|
);
|
|
19999
19938
|
}
|
|
20000
19939
|
if (s.syncIntervalHours) {
|
|
20001
19940
|
console.log(
|
|
20002
|
-
|
|
19941
|
+
chalk22.gray(
|
|
20003
19942
|
` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
|
|
20004
19943
|
)
|
|
20005
19944
|
);
|
|
@@ -20010,7 +19949,7 @@ function registerSyncCommand(program2) {
|
|
|
20010
19949
|
|
|
20011
19950
|
// src/cli/commands/agents.ts
|
|
20012
19951
|
init_setup();
|
|
20013
|
-
import
|
|
19952
|
+
import chalk23 from "chalk";
|
|
20014
19953
|
var SETUP_FN = {
|
|
20015
19954
|
claude: setupClaude,
|
|
20016
19955
|
gemini: setupGemini,
|
|
@@ -20039,23 +19978,23 @@ function registerAgentsCommand(program2) {
|
|
|
20039
19978
|
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
20040
19979
|
console.log(" " + "\u2500".repeat(44));
|
|
20041
19980
|
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 ?
|
|
19981
|
+
const installed = s.installed ? chalk23.green("\u2713") : chalk23.gray("\u2717");
|
|
19982
|
+
const wired = !s.installed ? chalk23.gray("\u2014") : s.wired ? chalk23.green("\u2713") : chalk23.yellow("\u2717");
|
|
19983
|
+
const mode = s.mode ? chalk23.gray(s.mode) : chalk23.gray("\u2014");
|
|
19984
|
+
const hint = s.installed && !s.wired ? chalk23.gray(` \u2190 node9 agents add ${s.name}`) : "";
|
|
20046
19985
|
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
20047
19986
|
}
|
|
20048
19987
|
console.log("");
|
|
20049
19988
|
if (!anyInstalled) {
|
|
20050
19989
|
console.log(
|
|
20051
|
-
|
|
19990
|
+
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
19991
|
);
|
|
20053
19992
|
return;
|
|
20054
19993
|
}
|
|
20055
19994
|
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
20056
19995
|
if (unwired.length > 0) {
|
|
20057
19996
|
console.log(
|
|
20058
|
-
|
|
19997
|
+
chalk23.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk23.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
20059
19998
|
);
|
|
20060
19999
|
}
|
|
20061
20000
|
});
|
|
@@ -20063,7 +20002,7 @@ function registerAgentsCommand(program2) {
|
|
|
20063
20002
|
const name = agent.toLowerCase();
|
|
20064
20003
|
const fn = SETUP_FN[name];
|
|
20065
20004
|
if (!fn) {
|
|
20066
|
-
console.error(
|
|
20005
|
+
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
20067
20006
|
process.exit(1);
|
|
20068
20007
|
}
|
|
20069
20008
|
await fn();
|
|
@@ -20072,14 +20011,14 @@ function registerAgentsCommand(program2) {
|
|
|
20072
20011
|
const name = agent.toLowerCase();
|
|
20073
20012
|
const fn = TEARDOWN_FN[name];
|
|
20074
20013
|
if (!fn) {
|
|
20075
|
-
console.error(
|
|
20014
|
+
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
20076
20015
|
process.exit(1);
|
|
20077
20016
|
}
|
|
20078
|
-
console.log(
|
|
20017
|
+
console.log(chalk23.cyan(`
|
|
20079
20018
|
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
20080
20019
|
`));
|
|
20081
20020
|
fn();
|
|
20082
|
-
console.log(
|
|
20021
|
+
console.log(chalk23.gray("\n Restart the agent for changes to take effect."));
|
|
20083
20022
|
});
|
|
20084
20023
|
}
|
|
20085
20024
|
|
|
@@ -20087,7 +20026,7 @@ function registerAgentsCommand(program2) {
|
|
|
20087
20026
|
init_scan();
|
|
20088
20027
|
|
|
20089
20028
|
// src/cli/commands/sessions.ts
|
|
20090
|
-
import
|
|
20029
|
+
import chalk24 from "chalk";
|
|
20091
20030
|
import fs40 from "fs";
|
|
20092
20031
|
import path42 from "path";
|
|
20093
20032
|
import os36 from "os";
|
|
@@ -20596,11 +20535,11 @@ function toolInputSummary(tool, input) {
|
|
|
20596
20535
|
}
|
|
20597
20536
|
function toolColor(tool) {
|
|
20598
20537
|
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
|
|
20538
|
+
if (t === "bash" || t === "execute_bash") return chalk24.red;
|
|
20539
|
+
if (t === "write") return chalk24.green;
|
|
20540
|
+
if (t === "edit" || t === "notebookedit") return chalk24.yellow;
|
|
20541
|
+
if (t === "read") return chalk24.cyan;
|
|
20542
|
+
return chalk24.gray;
|
|
20604
20543
|
}
|
|
20605
20544
|
function barStr2(value, max, width) {
|
|
20606
20545
|
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
@@ -20610,7 +20549,7 @@ function barStr2(value, max, width) {
|
|
|
20610
20549
|
function colorBar2(value, max, width) {
|
|
20611
20550
|
const s = barStr2(value, max, width);
|
|
20612
20551
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
20613
|
-
return
|
|
20552
|
+
return chalk24.cyan(s.slice(0, filled)) + chalk24.dim(s.slice(filled));
|
|
20614
20553
|
}
|
|
20615
20554
|
function renderSummary(summaries) {
|
|
20616
20555
|
const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
|
|
@@ -20640,45 +20579,45 @@ function renderSummary(summaries) {
|
|
|
20640
20579
|
}
|
|
20641
20580
|
const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
20642
20581
|
const W = 20;
|
|
20643
|
-
console.log(
|
|
20582
|
+
console.log(chalk24.dim(" " + "\u2500".repeat(70)));
|
|
20644
20583
|
console.log(
|
|
20645
|
-
" " +
|
|
20584
|
+
" " + 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
20585
|
);
|
|
20647
20586
|
console.log(
|
|
20648
|
-
" " +
|
|
20587
|
+
" " + 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
20588
|
);
|
|
20650
20589
|
console.log("");
|
|
20651
|
-
console.log(" " +
|
|
20590
|
+
console.log(" " + chalk24.dim("Tool breakdown:"));
|
|
20652
20591
|
const maxGroup = Math.max(...Object.values(groups));
|
|
20653
20592
|
for (const [label, count] of Object.entries(groups)) {
|
|
20654
20593
|
if (count === 0) continue;
|
|
20655
20594
|
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
20656
20595
|
console.log(
|
|
20657
|
-
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " +
|
|
20596
|
+
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk24.white(String(count).padStart(4)) + chalk24.dim(` (${String(pct)}%)`)
|
|
20658
20597
|
);
|
|
20659
20598
|
}
|
|
20660
20599
|
console.log("");
|
|
20661
20600
|
if (topProjects.length > 1) {
|
|
20662
|
-
console.log(" " +
|
|
20601
|
+
console.log(" " + chalk24.dim("Cost by project:"));
|
|
20663
20602
|
const maxProjCost = topProjects[0][1];
|
|
20664
20603
|
for (const [proj, cost] of topProjects) {
|
|
20665
20604
|
console.log(
|
|
20666
|
-
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " +
|
|
20605
|
+
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + chalk24.yellow(fmtCost3(cost))
|
|
20667
20606
|
);
|
|
20668
20607
|
}
|
|
20669
20608
|
console.log("");
|
|
20670
20609
|
}
|
|
20671
|
-
console.log(
|
|
20610
|
+
console.log(chalk24.dim(" " + "\u2500".repeat(70)));
|
|
20672
20611
|
console.log("");
|
|
20673
20612
|
}
|
|
20674
20613
|
function renderList(summaries, totalCost) {
|
|
20675
20614
|
if (summaries.length === 0) {
|
|
20676
|
-
console.log(
|
|
20615
|
+
console.log(chalk24.yellow(" No sessions found in the requested range.\n"));
|
|
20677
20616
|
return;
|
|
20678
20617
|
}
|
|
20679
|
-
const totalLabel = totalCost > 0 ?
|
|
20618
|
+
const totalLabel = totalCost > 0 ? chalk24.dim(" ~" + fmtCost3(totalCost) + " total") : "";
|
|
20680
20619
|
console.log(
|
|
20681
|
-
" " +
|
|
20620
|
+
" " + chalk24.white(String(summaries.length)) + chalk24.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
|
|
20682
20621
|
);
|
|
20683
20622
|
console.log("");
|
|
20684
20623
|
let lastGroup = "";
|
|
@@ -20686,49 +20625,49 @@ function renderList(summaries, totalCost) {
|
|
|
20686
20625
|
const activeDate = fmtDate2(s.lastActiveTime);
|
|
20687
20626
|
const group = activeDate + " " + s.projectLabel;
|
|
20688
20627
|
if (group !== lastGroup) {
|
|
20689
|
-
console.log(
|
|
20628
|
+
console.log(chalk24.dim(" \u2500\u2500\u2500 ") + chalk24.bold(activeDate) + chalk24.dim(" " + s.projectLabel));
|
|
20690
20629
|
lastGroup = group;
|
|
20691
20630
|
}
|
|
20692
20631
|
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 =
|
|
20632
|
+
const dateRange = startDate !== activeDate ? chalk24.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
|
|
20633
|
+
const timeStr = chalk24.dim(fmtTime(s.startTime));
|
|
20634
|
+
const prompt = chalk24.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
|
|
20635
|
+
const tools = s.toolCalls.length > 0 ? chalk24.dim(String(s.toolCalls.length).padStart(3) + " tools") : chalk24.dim(" 0 tools");
|
|
20636
|
+
const cost = s.costUSD > 0 ? chalk24.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
20637
|
+
const blocked = s.blockedCalls.length > 0 ? chalk24.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
20638
|
+
const snap = s.hasSnapshot ? chalk24.green(" \u{1F4F8}") : "";
|
|
20639
|
+
const agentBadge = s.agent === "gemini" ? chalk24.blue(" [Gemini]") : s.agent === "codex" ? chalk24.magenta(" [Codex]") : chalk24.cyan(" [Claude]");
|
|
20640
|
+
const sid = chalk24.dim(" " + s.sessionId.slice(0, 8));
|
|
20702
20641
|
console.log(
|
|
20703
20642
|
` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
|
|
20704
20643
|
);
|
|
20705
20644
|
}
|
|
20706
20645
|
console.log("");
|
|
20707
20646
|
console.log(
|
|
20708
|
-
|
|
20647
|
+
chalk24.dim(" Run") + " " + chalk24.cyan("node9 sessions --detail <session-id>") + chalk24.dim(" for full tool trace.")
|
|
20709
20648
|
);
|
|
20710
20649
|
console.log("");
|
|
20711
20650
|
}
|
|
20712
20651
|
function renderDetail(s) {
|
|
20713
20652
|
console.log("");
|
|
20714
|
-
console.log(
|
|
20653
|
+
console.log(chalk24.bold(" Session ") + chalk24.dim(s.sessionId));
|
|
20715
20654
|
console.log(
|
|
20716
|
-
|
|
20655
|
+
chalk24.bold(" Prompt ") + chalk24.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
20717
20656
|
);
|
|
20718
|
-
console.log(
|
|
20657
|
+
console.log(chalk24.bold(" Project ") + chalk24.white(s.projectLabel));
|
|
20719
20658
|
if (s.agent) {
|
|
20720
|
-
const agentLabel2 = s.agent === "gemini" ?
|
|
20721
|
-
console.log(
|
|
20659
|
+
const agentLabel2 = s.agent === "gemini" ? chalk24.blue("Gemini CLI") : s.agent === "codex" ? chalk24.magenta("Codex") : chalk24.cyan("Claude Code");
|
|
20660
|
+
console.log(chalk24.bold(" Agent ") + agentLabel2);
|
|
20722
20661
|
}
|
|
20723
|
-
console.log(
|
|
20662
|
+
console.log(chalk24.bold(" When ") + chalk24.white(fmtDateTime(s.startTime)));
|
|
20724
20663
|
if (s.costUSD > 0)
|
|
20725
|
-
console.log(
|
|
20664
|
+
console.log(chalk24.bold(" Cost ") + chalk24.yellow("~" + fmtCost3(s.costUSD)));
|
|
20726
20665
|
console.log(
|
|
20727
|
-
|
|
20666
|
+
chalk24.bold(" Snapshot ") + (s.hasSnapshot ? chalk24.green("\u2713 taken") : chalk24.dim("none"))
|
|
20728
20667
|
);
|
|
20729
20668
|
console.log("");
|
|
20730
20669
|
if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
|
|
20731
|
-
console.log(
|
|
20670
|
+
console.log(chalk24.dim(" No tool calls recorded.\n"));
|
|
20732
20671
|
return;
|
|
20733
20672
|
}
|
|
20734
20673
|
const timeline = [
|
|
@@ -20741,32 +20680,32 @@ function renderDetail(s) {
|
|
|
20741
20680
|
});
|
|
20742
20681
|
const headerParts = [`Tool calls (${s.toolCalls.length})`];
|
|
20743
20682
|
if (s.blockedCalls.length > 0)
|
|
20744
|
-
headerParts.push(
|
|
20745
|
-
console.log(
|
|
20683
|
+
headerParts.push(chalk24.red(`${s.blockedCalls.length} blocked by node9`));
|
|
20684
|
+
console.log(chalk24.bold(" " + headerParts.join(" \xB7 ")));
|
|
20746
20685
|
console.log("");
|
|
20747
20686
|
for (const entry of timeline) {
|
|
20748
20687
|
if (entry.kind === "tool") {
|
|
20749
20688
|
const tc = entry.tc;
|
|
20750
20689
|
const colorFn = toolColor(tc.tool);
|
|
20751
20690
|
const toolPad = colorFn(tc.tool.padEnd(16));
|
|
20752
|
-
const detail =
|
|
20753
|
-
const ts = tc.timestamp ?
|
|
20691
|
+
const detail = chalk24.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
|
|
20692
|
+
const ts = tc.timestamp ? chalk24.dim(fmtTime(tc.timestamp) + " ") : " ";
|
|
20754
20693
|
console.log(` ${ts}${toolPad} ${detail}`);
|
|
20755
20694
|
} else {
|
|
20756
20695
|
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 ?
|
|
20696
|
+
const ts = bc.timestamp ? chalk24.dim(fmtTime(bc.timestamp) + " ") : " ";
|
|
20697
|
+
const label = chalk24.red("\u{1F6D1} BLOCKED".padEnd(16));
|
|
20698
|
+
const toolName = chalk24.red(bc.tool.padEnd(10));
|
|
20699
|
+
const argsSummary = bc.args ? chalk24.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : chalk24.dim("[args not logged]");
|
|
20700
|
+
const reason = bc.checkedBy ? chalk24.dim(" \u2190 " + bc.checkedBy) : "";
|
|
20762
20701
|
console.log(` ${ts}${label} ${toolName} ${argsSummary}${reason}`);
|
|
20763
20702
|
}
|
|
20764
20703
|
}
|
|
20765
20704
|
console.log("");
|
|
20766
20705
|
if (s.modifiedFiles.length > 0) {
|
|
20767
|
-
console.log(
|
|
20706
|
+
console.log(chalk24.bold(` Files modified (${s.modifiedFiles.length}):`));
|
|
20768
20707
|
for (const f of s.modifiedFiles) {
|
|
20769
|
-
console.log(" " +
|
|
20708
|
+
console.log(" " + chalk24.yellow(f));
|
|
20770
20709
|
}
|
|
20771
20710
|
console.log("");
|
|
20772
20711
|
}
|
|
@@ -20774,19 +20713,19 @@ function renderDetail(s) {
|
|
|
20774
20713
|
function registerSessionsCommand(program2) {
|
|
20775
20714
|
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
20715
|
console.log("");
|
|
20777
|
-
console.log(
|
|
20716
|
+
console.log(chalk24.cyan.bold("\u{1F4CB} node9 sessions") + chalk24.dim(" \u2014 what your AI agent did"));
|
|
20778
20717
|
console.log("");
|
|
20779
20718
|
const historyPath = path42.join(os36.homedir(), ".claude", "history.jsonl");
|
|
20780
20719
|
if (!fs40.existsSync(historyPath)) {
|
|
20781
|
-
console.log(
|
|
20782
|
-
console.log(
|
|
20720
|
+
console.log(chalk24.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
20721
|
+
console.log(chalk24.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
20783
20722
|
return;
|
|
20784
20723
|
}
|
|
20785
20724
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
20786
20725
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
20787
|
-
console.log(
|
|
20726
|
+
console.log(chalk24.dim(" " + rangeLabel));
|
|
20788
20727
|
console.log("");
|
|
20789
|
-
process.stdout.write(
|
|
20728
|
+
process.stdout.write(chalk24.dim(" Loading\u2026"));
|
|
20790
20729
|
const summaries = buildSessions(days);
|
|
20791
20730
|
if (process.stdout.isTTY) {
|
|
20792
20731
|
process.stdout.clearLine(0);
|
|
@@ -20799,8 +20738,8 @@ function registerSessionsCommand(program2) {
|
|
|
20799
20738
|
(s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
|
|
20800
20739
|
);
|
|
20801
20740
|
if (!target) {
|
|
20802
|
-
console.log(
|
|
20803
|
-
console.log(
|
|
20741
|
+
console.log(chalk24.red(` Session not found: ${options.detail}`));
|
|
20742
|
+
console.log(chalk24.dim(" Run `node9 sessions` to list recent sessions.\n"));
|
|
20804
20743
|
return;
|
|
20805
20744
|
}
|
|
20806
20745
|
renderDetail(target);
|
|
@@ -20813,7 +20752,7 @@ function registerSessionsCommand(program2) {
|
|
|
20813
20752
|
}
|
|
20814
20753
|
|
|
20815
20754
|
// src/cli/commands/skill-pin.ts
|
|
20816
|
-
import
|
|
20755
|
+
import chalk25 from "chalk";
|
|
20817
20756
|
import fs41 from "fs";
|
|
20818
20757
|
import os37 from "os";
|
|
20819
20758
|
import path43 from "path";
|
|
@@ -20833,29 +20772,29 @@ function registerSkillPinCommand(program2) {
|
|
|
20833
20772
|
const result = readSkillPinsSafe();
|
|
20834
20773
|
if (!result.ok) {
|
|
20835
20774
|
if (result.reason === "missing") {
|
|
20836
|
-
console.log(
|
|
20775
|
+
console.log(chalk25.gray("\nNo skill roots are pinned yet."));
|
|
20837
20776
|
console.log(
|
|
20838
|
-
|
|
20777
|
+
chalk25.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
20839
20778
|
);
|
|
20840
20779
|
return;
|
|
20841
20780
|
}
|
|
20842
|
-
console.error(
|
|
20781
|
+
console.error(chalk25.red(`
|
|
20843
20782
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
20844
|
-
console.error(
|
|
20783
|
+
console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
|
|
20845
20784
|
process.exit(1);
|
|
20846
20785
|
}
|
|
20847
20786
|
const entries = Object.entries(result.pins.roots);
|
|
20848
20787
|
if (entries.length === 0) {
|
|
20849
|
-
console.log(
|
|
20788
|
+
console.log(chalk25.gray("\nNo skill roots are pinned yet.\n"));
|
|
20850
20789
|
return;
|
|
20851
20790
|
}
|
|
20852
|
-
console.log(
|
|
20791
|
+
console.log(chalk25.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
20853
20792
|
for (const [key, entry] of entries) {
|
|
20854
|
-
const missing = entry.exists ? "" :
|
|
20855
|
-
console.log(` ${
|
|
20793
|
+
const missing = entry.exists ? "" : chalk25.yellow(" (not present at pin time)");
|
|
20794
|
+
console.log(` ${chalk25.cyan(key)} ${chalk25.gray(entry.rootPath)}${missing}`);
|
|
20856
20795
|
console.log(` Files (${entry.fileCount})`);
|
|
20857
|
-
console.log(` Hash: ${
|
|
20858
|
-
console.log(` Pinned: ${
|
|
20796
|
+
console.log(` Hash: ${chalk25.gray(entry.contentHash.slice(0, 16))}...`);
|
|
20797
|
+
console.log(` Pinned: ${chalk25.gray(entry.pinnedAt)}
|
|
20859
20798
|
`);
|
|
20860
20799
|
}
|
|
20861
20800
|
});
|
|
@@ -20864,39 +20803,39 @@ function registerSkillPinCommand(program2) {
|
|
|
20864
20803
|
try {
|
|
20865
20804
|
pins = readSkillPins();
|
|
20866
20805
|
} catch {
|
|
20867
|
-
console.error(
|
|
20868
|
-
console.error(
|
|
20806
|
+
console.error(chalk25.red("\n\u274C Pin file is corrupt."));
|
|
20807
|
+
console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
|
|
20869
20808
|
process.exit(1);
|
|
20870
20809
|
}
|
|
20871
20810
|
if (!pins.roots[rootKey]) {
|
|
20872
|
-
console.error(
|
|
20811
|
+
console.error(chalk25.red(`
|
|
20873
20812
|
\u274C No pin found for root key "${rootKey}"
|
|
20874
20813
|
`));
|
|
20875
|
-
console.error(`Run ${
|
|
20814
|
+
console.error(`Run ${chalk25.cyan("node9 skill pin list")} to see pinned roots.
|
|
20876
20815
|
`);
|
|
20877
20816
|
process.exit(1);
|
|
20878
20817
|
}
|
|
20879
20818
|
const rootPath = pins.roots[rootKey].rootPath;
|
|
20880
20819
|
removePin(rootKey);
|
|
20881
20820
|
wipeSkillSessions();
|
|
20882
|
-
console.log(
|
|
20883
|
-
\u{1F513} Pin removed for ${
|
|
20884
|
-
console.log(
|
|
20885
|
-
console.log(
|
|
20821
|
+
console.log(chalk25.green(`
|
|
20822
|
+
\u{1F513} Pin removed for ${chalk25.cyan(rootKey)}`));
|
|
20823
|
+
console.log(chalk25.gray(` ${rootPath}`));
|
|
20824
|
+
console.log(chalk25.gray(" Next session will re-pin with current state.\n"));
|
|
20886
20825
|
});
|
|
20887
20826
|
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
20888
20827
|
const result = readSkillPinsSafe();
|
|
20889
20828
|
if (!result.ok && result.reason === "missing") {
|
|
20890
20829
|
wipeSkillSessions();
|
|
20891
|
-
console.log(
|
|
20830
|
+
console.log(chalk25.gray("\nNo pins to clear.\n"));
|
|
20892
20831
|
return;
|
|
20893
20832
|
}
|
|
20894
20833
|
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
20895
20834
|
clearAllPins();
|
|
20896
20835
|
wipeSkillSessions();
|
|
20897
|
-
console.log(
|
|
20836
|
+
console.log(chalk25.green(`
|
|
20898
20837
|
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
20899
|
-
console.log(
|
|
20838
|
+
console.log(chalk25.gray(" Next session will re-pin with current state.\n"));
|
|
20900
20839
|
});
|
|
20901
20840
|
}
|
|
20902
20841
|
|
|
@@ -20904,7 +20843,7 @@ function registerSkillPinCommand(program2) {
|
|
|
20904
20843
|
import fs42 from "fs";
|
|
20905
20844
|
import os38 from "os";
|
|
20906
20845
|
import path44 from "path";
|
|
20907
|
-
import
|
|
20846
|
+
import chalk26 from "chalk";
|
|
20908
20847
|
var DECISIONS_FILE2 = path44.join(os38.homedir(), ".node9", "decisions.json");
|
|
20909
20848
|
function readDecisions() {
|
|
20910
20849
|
try {
|
|
@@ -20933,55 +20872,55 @@ function registerDecisionsCommand(program2) {
|
|
|
20933
20872
|
const decisions = readDecisions();
|
|
20934
20873
|
const entries = Object.entries(decisions);
|
|
20935
20874
|
if (entries.length === 0) {
|
|
20936
|
-
console.log(
|
|
20875
|
+
console.log(chalk26.gray(" No persistent decisions stored."));
|
|
20937
20876
|
console.log(
|
|
20938
|
-
|
|
20939
|
-
`) +
|
|
20877
|
+
chalk26.gray(` File: ${DECISIONS_FILE2}
|
|
20878
|
+
`) + chalk26.gray(' Decisions are written when you click "Always Allow" or')
|
|
20940
20879
|
);
|
|
20941
|
-
console.log(
|
|
20880
|
+
console.log(chalk26.gray(' "Always Deny" in node9 tail or the native popup.'));
|
|
20942
20881
|
return;
|
|
20943
20882
|
}
|
|
20944
|
-
console.log(
|
|
20883
|
+
console.log(chalk26.bold(`
|
|
20945
20884
|
Persistent decisions (${entries.length})
|
|
20946
20885
|
`));
|
|
20947
20886
|
const w = Math.max(...entries.map(([k]) => k.length));
|
|
20948
20887
|
for (const [tool, verdict] of entries.sort()) {
|
|
20949
|
-
const colored = verdict === "allow" ?
|
|
20888
|
+
const colored = verdict === "allow" ? chalk26.green(verdict) : chalk26.red(verdict);
|
|
20950
20889
|
console.log(` ${tool.padEnd(w)} ${colored}`);
|
|
20951
20890
|
}
|
|
20952
20891
|
console.log(
|
|
20953
|
-
|
|
20892
|
+
chalk26.gray(`
|
|
20954
20893
|
Stored in ${DECISIONS_FILE2}
|
|
20955
|
-
`) +
|
|
20894
|
+
`) + chalk26.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
|
|
20956
20895
|
);
|
|
20957
20896
|
});
|
|
20958
20897
|
cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
|
|
20959
20898
|
const decisions = readDecisions();
|
|
20960
20899
|
if (!(toolName in decisions)) {
|
|
20961
|
-
console.log(
|
|
20900
|
+
console.log(chalk26.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
|
|
20962
20901
|
process.exitCode = 1;
|
|
20963
20902
|
return;
|
|
20964
20903
|
}
|
|
20965
20904
|
delete decisions[toolName];
|
|
20966
20905
|
writeDecisions(decisions);
|
|
20967
|
-
console.log(
|
|
20906
|
+
console.log(chalk26.green(` \u2713 Cleared persistent decision for "${toolName}".`));
|
|
20968
20907
|
});
|
|
20969
20908
|
cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
|
|
20970
20909
|
const decisions = readDecisions();
|
|
20971
20910
|
const count = Object.keys(decisions).length;
|
|
20972
20911
|
if (count === 0) {
|
|
20973
|
-
console.log(
|
|
20912
|
+
console.log(chalk26.gray(" Nothing to clear \u2014 no persistent decisions stored."));
|
|
20974
20913
|
return;
|
|
20975
20914
|
}
|
|
20976
20915
|
writeDecisions({});
|
|
20977
20916
|
console.log(
|
|
20978
|
-
|
|
20917
|
+
chalk26.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
|
|
20979
20918
|
);
|
|
20980
20919
|
});
|
|
20981
20920
|
}
|
|
20982
20921
|
|
|
20983
20922
|
// src/cli/commands/dlp.ts
|
|
20984
|
-
import
|
|
20923
|
+
import chalk27 from "chalk";
|
|
20985
20924
|
import fs43 from "fs";
|
|
20986
20925
|
import path45 from "path";
|
|
20987
20926
|
import os39 from "os";
|
|
@@ -21036,14 +20975,14 @@ function registerDlpCommand(program2) {
|
|
|
21036
20975
|
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
21037
20976
|
const findings = loadDlpFindings();
|
|
21038
20977
|
if (findings.length === 0) {
|
|
21039
|
-
console.log(
|
|
20978
|
+
console.log(chalk27.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
21040
20979
|
return;
|
|
21041
20980
|
}
|
|
21042
20981
|
const resolved = loadResolved();
|
|
21043
20982
|
for (const e of findings) resolved.add(entryKey(e));
|
|
21044
20983
|
saveResolved(resolved);
|
|
21045
20984
|
console.log(
|
|
21046
|
-
|
|
20985
|
+
chalk27.green(
|
|
21047
20986
|
`
|
|
21048
20987
|
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
21049
20988
|
`
|
|
@@ -21057,47 +20996,47 @@ function registerDlpCommand(program2) {
|
|
|
21057
20996
|
const resolvedCount = findings.length - open.length;
|
|
21058
20997
|
console.log("");
|
|
21059
20998
|
console.log(
|
|
21060
|
-
|
|
20999
|
+
chalk27.bold.cyan("\u{1F510} node9 dlp") + chalk27.dim(" \u2014 secrets found in Claude response text")
|
|
21061
21000
|
);
|
|
21062
21001
|
console.log("");
|
|
21063
21002
|
if (open.length === 0) {
|
|
21064
21003
|
if (resolvedCount > 0) {
|
|
21065
|
-
console.log(
|
|
21004
|
+
console.log(chalk27.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
21066
21005
|
} else {
|
|
21067
21006
|
console.log(
|
|
21068
|
-
|
|
21007
|
+
chalk27.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
21069
21008
|
);
|
|
21070
21009
|
}
|
|
21071
21010
|
console.log("");
|
|
21072
21011
|
return;
|
|
21073
21012
|
}
|
|
21074
21013
|
console.log(
|
|
21075
|
-
|
|
21014
|
+
chalk27.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk27.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
21076
21015
|
);
|
|
21077
21016
|
console.log("");
|
|
21078
21017
|
console.log(
|
|
21079
|
-
|
|
21018
|
+
chalk27.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
21080
21019
|
);
|
|
21081
|
-
console.log(
|
|
21020
|
+
console.log(chalk27.dim(" Rotate each affected key immediately.\n"));
|
|
21082
21021
|
for (const e of open) {
|
|
21083
21022
|
console.log(
|
|
21084
|
-
" " +
|
|
21023
|
+
" " + chalk27.red("\u25CF") + " " + chalk27.white(e.dlpPattern ?? "Secret") + chalk27.dim(" " + fmtDate3(e.ts))
|
|
21085
21024
|
);
|
|
21086
21025
|
if (e.dlpSample) {
|
|
21087
|
-
console.log(" " +
|
|
21026
|
+
console.log(" " + chalk27.dim("Sample: ") + chalk27.yellow(stripAnsi(e.dlpSample)));
|
|
21088
21027
|
}
|
|
21089
21028
|
if (e.project) {
|
|
21090
|
-
console.log(" " +
|
|
21029
|
+
console.log(" " + chalk27.dim("Project: ") + chalk27.dim(stripAnsi(e.project)));
|
|
21091
21030
|
}
|
|
21092
21031
|
console.log("");
|
|
21093
21032
|
}
|
|
21094
|
-
console.log(" " +
|
|
21095
|
-
console.log(" " +
|
|
21033
|
+
console.log(" " + chalk27.bold("Next steps:"));
|
|
21034
|
+
console.log(" " + chalk27.cyan("1.") + " Rotate any exposed keys shown above");
|
|
21096
21035
|
console.log(
|
|
21097
|
-
" " +
|
|
21036
|
+
" " + chalk27.cyan("2.") + " Run " + chalk27.white("node9 dlp resolve") + " to acknowledge"
|
|
21098
21037
|
);
|
|
21099
21038
|
console.log(
|
|
21100
|
-
" " +
|
|
21039
|
+
" " + chalk27.cyan("3.") + " Run " + chalk27.white("node9 report") + " for full audit history"
|
|
21101
21040
|
);
|
|
21102
21041
|
console.log("");
|
|
21103
21042
|
});
|
|
@@ -21105,7 +21044,7 @@ function registerDlpCommand(program2) {
|
|
|
21105
21044
|
|
|
21106
21045
|
// src/cli/commands/mask.ts
|
|
21107
21046
|
init_dlp();
|
|
21108
|
-
import
|
|
21047
|
+
import chalk28 from "chalk";
|
|
21109
21048
|
import fs44 from "fs";
|
|
21110
21049
|
import path46 from "path";
|
|
21111
21050
|
import os40 from "os";
|
|
@@ -21241,12 +21180,12 @@ function registerMaskCommand(program2) {
|
|
|
21241
21180
|
}
|
|
21242
21181
|
}) : allFiles;
|
|
21243
21182
|
if (filtered.length === 0) {
|
|
21244
|
-
console.log(
|
|
21183
|
+
console.log(chalk28.yellow(" No session files found."));
|
|
21245
21184
|
return;
|
|
21246
21185
|
}
|
|
21247
21186
|
console.log("");
|
|
21248
21187
|
if (dryRun) {
|
|
21249
|
-
console.log(
|
|
21188
|
+
console.log(chalk28.dim(" Dry run \u2014 no files will be modified.\n"));
|
|
21250
21189
|
}
|
|
21251
21190
|
let totalFiles = 0;
|
|
21252
21191
|
let totalLines = 0;
|
|
@@ -21262,23 +21201,23 @@ function registerMaskCommand(program2) {
|
|
|
21262
21201
|
});
|
|
21263
21202
|
const verb = dryRun ? "Would redact" : "Redacted";
|
|
21264
21203
|
console.log(
|
|
21265
|
-
" " +
|
|
21204
|
+
" " + chalk28.dim(shortPath.slice(0, 60).padEnd(62)) + chalk28.red(`${verb}: `) + chalk28.yellow(patterns.join(", ")) + chalk28.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
|
|
21266
21205
|
);
|
|
21267
21206
|
}
|
|
21268
21207
|
}
|
|
21269
21208
|
console.log("");
|
|
21270
21209
|
if (totalFiles === 0) {
|
|
21271
|
-
console.log(
|
|
21210
|
+
console.log(chalk28.green(" No secrets found in session history."));
|
|
21272
21211
|
} else {
|
|
21273
21212
|
const verb = dryRun ? "would be modified" : "modified";
|
|
21274
21213
|
console.log(
|
|
21275
|
-
|
|
21214
|
+
chalk28.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk28.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
|
|
21276
21215
|
);
|
|
21277
|
-
console.log(" Patterns: " +
|
|
21216
|
+
console.log(" Patterns: " + chalk28.yellow(totalPatterns.join(", ")));
|
|
21278
21217
|
if (!dryRun) {
|
|
21279
21218
|
console.log("");
|
|
21280
21219
|
console.log(
|
|
21281
|
-
|
|
21220
|
+
chalk28.dim(
|
|
21282
21221
|
" 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
21222
|
)
|
|
21284
21223
|
);
|
|
@@ -21342,17 +21281,17 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
21342
21281
|
fs47.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
21343
21282
|
}
|
|
21344
21283
|
if (options.profile && profileName !== "default") {
|
|
21345
|
-
console.log(
|
|
21346
|
-
console.log(
|
|
21284
|
+
console.log(chalk30.green(`\u2705 Profile "${profileName}" saved`));
|
|
21285
|
+
console.log(chalk30.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
21347
21286
|
} else if (options.local) {
|
|
21348
|
-
console.log(
|
|
21349
|
-
console.log(
|
|
21287
|
+
console.log(chalk30.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
21288
|
+
console.log(chalk30.gray(` All decisions stay on this machine.`));
|
|
21350
21289
|
} else {
|
|
21351
|
-
console.log(
|
|
21352
|
-
console.log(
|
|
21290
|
+
console.log(chalk30.green(`\u2705 Logged in \u2014 agent mode`));
|
|
21291
|
+
console.log(chalk30.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
21353
21292
|
}
|
|
21354
21293
|
});
|
|
21355
|
-
program.command("addto").description("Integrate Node9 with an AI agent").addHelpText(
|
|
21294
|
+
program.command("addto", { hidden: true }).description("Integrate Node9 with an AI agent").addHelpText(
|
|
21356
21295
|
"after",
|
|
21357
21296
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21358
21297
|
).argument(
|
|
@@ -21367,13 +21306,13 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
21367
21306
|
if (target === "vscode") return await setupVSCode();
|
|
21368
21307
|
if (target === "hud") return setupHud();
|
|
21369
21308
|
console.error(
|
|
21370
|
-
|
|
21309
|
+
chalk30.red(
|
|
21371
21310
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21372
21311
|
)
|
|
21373
21312
|
);
|
|
21374
21313
|
process.exit(1);
|
|
21375
21314
|
});
|
|
21376
|
-
program.command("setup").description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
21315
|
+
program.command("setup", { hidden: true }).description('Alias for "addto" \u2014 integrate Node9 with an AI agent').addHelpText(
|
|
21377
21316
|
"after",
|
|
21378
21317
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21379
21318
|
).argument(
|
|
@@ -21381,17 +21320,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
21381
21320
|
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
21382
21321
|
).action(async (target) => {
|
|
21383
21322
|
if (!target) {
|
|
21384
|
-
console.log(
|
|
21385
|
-
console.log(" Usage: " +
|
|
21323
|
+
console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
21324
|
+
console.log(" Usage: " + chalk30.white("node9 setup <target>") + "\n");
|
|
21386
21325
|
console.log(" Targets:");
|
|
21387
|
-
console.log(" " +
|
|
21388
|
-
console.log(" " +
|
|
21389
|
-
console.log(" " +
|
|
21390
|
-
console.log(" " +
|
|
21391
|
-
console.log(" " +
|
|
21392
|
-
console.log(" " +
|
|
21326
|
+
console.log(" " + chalk30.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
21327
|
+
console.log(" " + chalk30.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
21328
|
+
console.log(" " + chalk30.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
21329
|
+
console.log(" " + chalk30.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
21330
|
+
console.log(" " + chalk30.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
21331
|
+
console.log(" " + chalk30.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
21393
21332
|
process.stdout.write(
|
|
21394
|
-
" " +
|
|
21333
|
+
" " + chalk30.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
21395
21334
|
);
|
|
21396
21335
|
console.log("");
|
|
21397
21336
|
return;
|
|
@@ -21405,13 +21344,13 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
21405
21344
|
if (t === "vscode") return await setupVSCode();
|
|
21406
21345
|
if (t === "hud") return setupHud();
|
|
21407
21346
|
console.error(
|
|
21408
|
-
|
|
21347
|
+
chalk30.red(
|
|
21409
21348
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21410
21349
|
)
|
|
21411
21350
|
);
|
|
21412
21351
|
process.exit(1);
|
|
21413
21352
|
});
|
|
21414
|
-
program.command("removefrom").description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
21353
|
+
program.command("removefrom", { hidden: true }).description("Remove Node9 hooks from an AI agent configuration").addHelpText(
|
|
21415
21354
|
"after",
|
|
21416
21355
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21417
21356
|
).argument(
|
|
@@ -21428,33 +21367,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
21428
21367
|
else if (target === "hud") fn = teardownHud;
|
|
21429
21368
|
else {
|
|
21430
21369
|
console.error(
|
|
21431
|
-
|
|
21370
|
+
chalk30.red(
|
|
21432
21371
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21433
21372
|
)
|
|
21434
21373
|
);
|
|
21435
21374
|
process.exit(1);
|
|
21436
21375
|
}
|
|
21437
|
-
console.log(
|
|
21376
|
+
console.log(chalk30.cyan(`
|
|
21438
21377
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
21439
21378
|
`));
|
|
21440
21379
|
try {
|
|
21441
21380
|
fn();
|
|
21442
21381
|
} catch (err2) {
|
|
21443
|
-
console.error(
|
|
21382
|
+
console.error(chalk30.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21444
21383
|
process.exit(1);
|
|
21445
21384
|
}
|
|
21446
|
-
console.log(
|
|
21385
|
+
console.log(chalk30.gray("\n Restart the agent for changes to take effect."));
|
|
21447
21386
|
});
|
|
21448
21387
|
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(
|
|
21388
|
+
console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
21389
|
+
console.log(chalk30.bold("Stopping daemon..."));
|
|
21451
21390
|
try {
|
|
21452
21391
|
stopDaemon();
|
|
21453
|
-
console.log(
|
|
21392
|
+
console.log(chalk30.green(" \u2705 Daemon stopped"));
|
|
21454
21393
|
} catch {
|
|
21455
|
-
console.log(
|
|
21394
|
+
console.log(chalk30.blue(" \u2139\uFE0F Daemon was not running"));
|
|
21456
21395
|
}
|
|
21457
|
-
console.log(
|
|
21396
|
+
console.log(chalk30.bold("\nRemoving hooks..."));
|
|
21458
21397
|
let teardownFailed = false;
|
|
21459
21398
|
for (const [label, fn] of [
|
|
21460
21399
|
["Claude", teardownClaude],
|
|
@@ -21469,7 +21408,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
21469
21408
|
} catch (err2) {
|
|
21470
21409
|
teardownFailed = true;
|
|
21471
21410
|
console.error(
|
|
21472
|
-
|
|
21411
|
+
chalk30.red(
|
|
21473
21412
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
21474
21413
|
)
|
|
21475
21414
|
);
|
|
@@ -21486,28 +21425,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
21486
21425
|
fs47.rmSync(node9Dir, { recursive: true });
|
|
21487
21426
|
if (fs47.existsSync(node9Dir)) {
|
|
21488
21427
|
console.error(
|
|
21489
|
-
|
|
21428
|
+
chalk30.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
21490
21429
|
);
|
|
21491
21430
|
} else {
|
|
21492
|
-
console.log(
|
|
21431
|
+
console.log(chalk30.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
21493
21432
|
}
|
|
21494
21433
|
} else {
|
|
21495
|
-
console.log(
|
|
21434
|
+
console.log(chalk30.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
21496
21435
|
}
|
|
21497
21436
|
} else {
|
|
21498
|
-
console.log(
|
|
21437
|
+
console.log(chalk30.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
21499
21438
|
}
|
|
21500
21439
|
} else {
|
|
21501
21440
|
console.log(
|
|
21502
|
-
|
|
21441
|
+
chalk30.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
21503
21442
|
);
|
|
21504
21443
|
}
|
|
21505
21444
|
if (teardownFailed) {
|
|
21506
|
-
console.error(
|
|
21445
|
+
console.error(chalk30.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
21507
21446
|
process.exit(1);
|
|
21508
21447
|
}
|
|
21509
|
-
console.log(
|
|
21510
|
-
console.log(
|
|
21448
|
+
console.log(chalk30.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g @node9/proxy"));
|
|
21449
|
+
console.log(chalk30.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
21511
21450
|
});
|
|
21512
21451
|
registerDoctorCommand(program, version);
|
|
21513
21452
|
program.command("explain").description(
|
|
@@ -21520,7 +21459,7 @@ program.command("explain").description(
|
|
|
21520
21459
|
try {
|
|
21521
21460
|
args = JSON.parse(trimmed);
|
|
21522
21461
|
} catch {
|
|
21523
|
-
console.error(
|
|
21462
|
+
console.error(chalk30.red(`
|
|
21524
21463
|
\u274C Invalid JSON: ${trimmed}
|
|
21525
21464
|
`));
|
|
21526
21465
|
process.exit(1);
|
|
@@ -21531,54 +21470,54 @@ program.command("explain").description(
|
|
|
21531
21470
|
}
|
|
21532
21471
|
const result = await explainPolicy(tool, args);
|
|
21533
21472
|
console.log("");
|
|
21534
|
-
console.log(
|
|
21473
|
+
console.log(chalk30.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
21535
21474
|
console.log("");
|
|
21536
|
-
console.log(` ${
|
|
21475
|
+
console.log(` ${chalk30.bold("Tool:")} ${chalk30.white(result.tool)}`);
|
|
21537
21476
|
if (argsRaw) {
|
|
21538
21477
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
21539
|
-
console.log(` ${
|
|
21478
|
+
console.log(` ${chalk30.bold("Input:")} ${chalk30.gray(preview2)}`);
|
|
21540
21479
|
}
|
|
21541
21480
|
console.log("");
|
|
21542
|
-
console.log(
|
|
21481
|
+
console.log(chalk30.bold("Config Sources (Waterfall):"));
|
|
21543
21482
|
for (const tier of result.waterfall) {
|
|
21544
|
-
const num3 =
|
|
21483
|
+
const num3 = chalk30.gray(` ${tier.tier}.`);
|
|
21545
21484
|
const label = tier.label.padEnd(16);
|
|
21546
21485
|
let statusStr;
|
|
21547
21486
|
if (tier.tier === 1) {
|
|
21548
|
-
statusStr =
|
|
21487
|
+
statusStr = chalk30.gray(tier.note ?? "");
|
|
21549
21488
|
} else if (tier.status === "active") {
|
|
21550
|
-
const loc = tier.path ?
|
|
21551
|
-
const note = tier.note ?
|
|
21552
|
-
statusStr =
|
|
21489
|
+
const loc = tier.path ? chalk30.gray(tier.path) : "";
|
|
21490
|
+
const note = tier.note ? chalk30.gray(`(${tier.note})`) : "";
|
|
21491
|
+
statusStr = chalk30.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
21553
21492
|
} else {
|
|
21554
|
-
statusStr =
|
|
21493
|
+
statusStr = chalk30.gray("\u25CB " + (tier.note ?? "not found"));
|
|
21555
21494
|
}
|
|
21556
|
-
console.log(`${num3} ${
|
|
21495
|
+
console.log(`${num3} ${chalk30.white(label)} ${statusStr}`);
|
|
21557
21496
|
}
|
|
21558
21497
|
console.log("");
|
|
21559
|
-
console.log(
|
|
21498
|
+
console.log(chalk30.bold("Policy Evaluation:"));
|
|
21560
21499
|
for (const step of result.steps) {
|
|
21561
21500
|
const isFinal = step.isFinal;
|
|
21562
21501
|
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 =
|
|
21502
|
+
if (step.outcome === "allow") icon = chalk30.green(" \u2705");
|
|
21503
|
+
else if (step.outcome === "review") icon = chalk30.red(" \u{1F534}");
|
|
21504
|
+
else if (step.outcome === "skip") icon = chalk30.gray(" \u2500 ");
|
|
21505
|
+
else icon = chalk30.gray(" \u25CB ");
|
|
21567
21506
|
const name = step.name.padEnd(18);
|
|
21568
|
-
const nameStr = isFinal ?
|
|
21569
|
-
const detail = isFinal ?
|
|
21570
|
-
const arrow = isFinal ?
|
|
21507
|
+
const nameStr = isFinal ? chalk30.white.bold(name) : chalk30.white(name);
|
|
21508
|
+
const detail = isFinal ? chalk30.white(step.detail) : chalk30.gray(step.detail);
|
|
21509
|
+
const arrow = isFinal ? chalk30.yellow(" \u2190 STOP") : "";
|
|
21571
21510
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
21572
21511
|
}
|
|
21573
21512
|
console.log("");
|
|
21574
21513
|
if (result.decision === "allow") {
|
|
21575
|
-
console.log(
|
|
21514
|
+
console.log(chalk30.green.bold(" Decision: \u2705 ALLOW") + chalk30.gray(" \u2014 no approval needed"));
|
|
21576
21515
|
} else {
|
|
21577
21516
|
console.log(
|
|
21578
|
-
|
|
21517
|
+
chalk30.red.bold(" Decision: \u{1F534} REVIEW") + chalk30.gray(" \u2014 human approval required")
|
|
21579
21518
|
);
|
|
21580
21519
|
if (result.blockedByLabel) {
|
|
21581
|
-
console.log(
|
|
21520
|
+
console.log(chalk30.gray(` Reason: ${result.blockedByLabel}`));
|
|
21582
21521
|
}
|
|
21583
21522
|
}
|
|
21584
21523
|
console.log("");
|
|
@@ -21593,7 +21532,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
21593
21532
|
try {
|
|
21594
21533
|
await startTail2(options);
|
|
21595
21534
|
} catch (err2) {
|
|
21596
|
-
console.error(
|
|
21535
|
+
console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21597
21536
|
process.exit(1);
|
|
21598
21537
|
}
|
|
21599
21538
|
});
|
|
@@ -21604,11 +21543,10 @@ program.command("monitor").description("Live interactive dashboard \u2014 activi
|
|
|
21604
21543
|
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
21605
21544
|
await mod.startMonitor();
|
|
21606
21545
|
} catch (err2) {
|
|
21607
|
-
console.error(
|
|
21546
|
+
console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21608
21547
|
process.exit(1);
|
|
21609
21548
|
}
|
|
21610
21549
|
});
|
|
21611
|
-
registerWatchCommand(program);
|
|
21612
21550
|
registerMcpGatewayCommand(program);
|
|
21613
21551
|
registerMcpServerCommand(program);
|
|
21614
21552
|
registerMcpPinCommand(program);
|
|
@@ -21616,7 +21554,7 @@ registerSkillPinCommand(program);
|
|
|
21616
21554
|
registerDecisionsCommand(program);
|
|
21617
21555
|
registerCheckCommand(program);
|
|
21618
21556
|
registerLogCommand(program);
|
|
21619
|
-
program.command("hud").description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
21557
|
+
program.command("hud", { hidden: true }).description("Render node9 security statusline (spawned by Claude Code statusLine)").addHelpText(
|
|
21620
21558
|
"after",
|
|
21621
21559
|
`
|
|
21622
21560
|
Outputs up to 3 lines to stdout, then exits:
|
|
@@ -21660,7 +21598,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
21660
21598
|
const ms = parseDuration(options.duration);
|
|
21661
21599
|
if (ms === null) {
|
|
21662
21600
|
console.error(
|
|
21663
|
-
|
|
21601
|
+
chalk30.red(`
|
|
21664
21602
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
21665
21603
|
`)
|
|
21666
21604
|
);
|
|
@@ -21668,20 +21606,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
21668
21606
|
}
|
|
21669
21607
|
pauseNode9(ms, options.duration);
|
|
21670
21608
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
21671
|
-
console.log(
|
|
21609
|
+
console.log(chalk30.yellow(`
|
|
21672
21610
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
21673
|
-
console.log(
|
|
21674
|
-
console.log(
|
|
21611
|
+
console.log(chalk30.gray(` All tool calls will be allowed without review.`));
|
|
21612
|
+
console.log(chalk30.gray(` Run "node9 resume" to re-enable early.
|
|
21675
21613
|
`));
|
|
21676
21614
|
});
|
|
21677
21615
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
21678
21616
|
const { paused } = checkPause();
|
|
21679
21617
|
if (!paused) {
|
|
21680
|
-
console.log(
|
|
21618
|
+
console.log(chalk30.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
21681
21619
|
return;
|
|
21682
21620
|
}
|
|
21683
21621
|
resumeNode9();
|
|
21684
|
-
console.log(
|
|
21622
|
+
console.log(chalk30.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
21685
21623
|
});
|
|
21686
21624
|
var HOOK_BASED_AGENTS = {
|
|
21687
21625
|
claude: "claude",
|
|
@@ -21694,15 +21632,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21694
21632
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
21695
21633
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
21696
21634
|
console.error(
|
|
21697
|
-
|
|
21635
|
+
chalk30.yellow(`
|
|
21698
21636
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
21699
21637
|
);
|
|
21700
|
-
console.error(
|
|
21638
|
+
console.error(chalk30.white(`
|
|
21701
21639
|
"${target}" uses its own hook system. Use:`));
|
|
21702
21640
|
console.error(
|
|
21703
|
-
|
|
21641
|
+
chalk30.green(` node9 addto ${target} `) + chalk30.gray("# one-time setup")
|
|
21704
21642
|
);
|
|
21705
|
-
console.error(
|
|
21643
|
+
console.error(chalk30.green(` ${target} `) + chalk30.gray("# run normally"));
|
|
21706
21644
|
process.exit(1);
|
|
21707
21645
|
}
|
|
21708
21646
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -21719,7 +21657,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21719
21657
|
}
|
|
21720
21658
|
);
|
|
21721
21659
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
21722
|
-
console.error(
|
|
21660
|
+
console.error(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
21723
21661
|
const daemonReady = await autoStartDaemonAndWait();
|
|
21724
21662
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
21725
21663
|
}
|
|
@@ -21732,12 +21670,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21732
21670
|
}
|
|
21733
21671
|
if (!result.approved) {
|
|
21734
21672
|
console.error(
|
|
21735
|
-
|
|
21673
|
+
chalk30.red(`
|
|
21736
21674
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
21737
21675
|
);
|
|
21738
21676
|
process.exit(1);
|
|
21739
21677
|
}
|
|
21740
|
-
console.error(
|
|
21678
|
+
console.error(chalk30.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
21741
21679
|
await runProxy(fullCommand);
|
|
21742
21680
|
} else {
|
|
21743
21681
|
program.help();
|