@node9/proxy 1.20.1 → 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/README.md +35 -36
- package/dist/cli.js +367 -403
- package/dist/cli.mjs +366 -402
- package/dist/scan-ink.mjs +1681 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -10485,7 +10485,7 @@ function originForRule(ruleName, sections) {
|
|
|
10485
10485
|
return "";
|
|
10486
10486
|
}
|
|
10487
10487
|
function registerScanCommand(program2) {
|
|
10488
|
-
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option(
|
|
10488
|
+
program2.command("scan").description("Forecast: scan agent history and show what node9 would catch if installed").option("--all", "Scan all history (default: last 90 days)").option("--days <n>", "Scan last N days of history", "90").option("--top <n>", "Max findings to show per rule (default: 5)", "5").option("--drill-down", "Show all findings with full commands and session IDs").option("--compact", "Compact one-screen scorecard \u2014 for screenshots and sharing").option("--narrative", "Severity-grouped report \u2014 for video / dramatic sharing").option("--classic", "Original chalk-based scorecard layout (default: new Ink-rendered view)").option(
|
|
10489
10489
|
"--json",
|
|
10490
10490
|
"Emit machine-readable JSON to stdout (suppresses banner, progress, and renderer)"
|
|
10491
10491
|
).option(
|
|
@@ -10519,7 +10519,8 @@ function registerScanCommand(program2) {
|
|
|
10519
10519
|
})();
|
|
10520
10520
|
const isWired = getAgentsStatus().some((a) => a.wired);
|
|
10521
10521
|
const screenshotMode = options.compact || options.narrative;
|
|
10522
|
-
const
|
|
10522
|
+
const useInk = !options.classic && !drillDown;
|
|
10523
|
+
const quiet = screenshotMode || options.json || useInk;
|
|
10523
10524
|
if (!quiet) {
|
|
10524
10525
|
console.log("");
|
|
10525
10526
|
if (!isWired) {
|
|
@@ -10657,6 +10658,7 @@ function registerScanCommand(program2) {
|
|
|
10657
10658
|
});
|
|
10658
10659
|
return;
|
|
10659
10660
|
}
|
|
10661
|
+
const useInkForHero = !options.classic && !drillDown;
|
|
10660
10662
|
if (totalFindings === 0 && scan.dlpFindings.length === 0) {
|
|
10661
10663
|
console.log(chalk5.green(" \u2705 No risky operations found in your history."));
|
|
10662
10664
|
console.log(
|
|
@@ -10676,9 +10678,11 @@ function registerScanCommand(program2) {
|
|
|
10676
10678
|
const since = daysAgo === 0 ? "today" : daysAgo === 1 ? "yesterday" : `${daysAgo} days ago`;
|
|
10677
10679
|
return chalk5.dim(" \xB7 ") + arrow + chalk5.dim(` since ${since}`);
|
|
10678
10680
|
})();
|
|
10679
|
-
|
|
10680
|
-
|
|
10681
|
-
|
|
10681
|
+
if (!useInkForHero) {
|
|
10682
|
+
console.log(
|
|
10683
|
+
" " + (score.band === "critical" ? chalk5.red.bold("\u26A0 ") : "") + chalk5.bold("Security Score ") + score.color.bold(`${blast.score}/100`) + " " + severityDisplay + trendSuffix + chalk5.dim(" \xB7 ") + (totalRisky > 0 ? chalk5.red.bold(`${totalRisky} risky operation${totalRisky !== 1 ? "s" : ""}`) : chalk5.green("No risky operations"))
|
|
10684
|
+
);
|
|
10685
|
+
}
|
|
10682
10686
|
const cardParts = [];
|
|
10683
10687
|
if (scan.dlpFindings.length > 0) {
|
|
10684
10688
|
cardParts.push(
|
|
@@ -10707,10 +10711,10 @@ function registerScanCommand(program2) {
|
|
|
10707
10711
|
chalk5.red("\u{1F52D} ") + chalk5.red.bold(String(blastExposures)) + chalk5.dim(" exposures")
|
|
10708
10712
|
);
|
|
10709
10713
|
}
|
|
10710
|
-
if (cardParts.length > 0) {
|
|
10714
|
+
if (cardParts.length > 0 && !useInkForHero) {
|
|
10711
10715
|
console.log(" " + cardParts.join(chalk5.dim(" ")));
|
|
10712
10716
|
}
|
|
10713
|
-
if (scan.totalCostUSD > 0) {
|
|
10717
|
+
if (scan.totalCostUSD > 0 && !useInkForHero) {
|
|
10714
10718
|
console.log(
|
|
10715
10719
|
" " + chalk5.dim("AI spend ") + chalk5.bold(fmtCost(scan.totalCostUSD)) + (summary.loopWastedUSD > 0 ? chalk5.dim(" \xB7 wasted on loops ") + chalk5.yellow("~" + fmtCost(summary.loopWastedUSD)) : "")
|
|
10716
10720
|
);
|
|
@@ -10722,16 +10726,38 @@ function registerScanCommand(program2) {
|
|
|
10722
10726
|
)
|
|
10723
10727
|
);
|
|
10724
10728
|
}
|
|
10725
|
-
|
|
10729
|
+
if (!useInkForHero) {
|
|
10730
|
+
console.log("");
|
|
10731
|
+
}
|
|
10726
10732
|
if (!drillDown) {
|
|
10727
|
-
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10733
|
+
const useInk2 = !options.classic;
|
|
10734
|
+
if (useInk2) {
|
|
10735
|
+
const scanInkPath = path21.join(__dirname, "scan-ink.mjs");
|
|
10736
|
+
const dynamicImport = new Function("id", "return import(id)");
|
|
10737
|
+
const mod = await dynamicImport(`file://${scanInkPath}`);
|
|
10738
|
+
const rangeLabel2 = options.all ? "all time" : `last ${options.days ?? 90} days`;
|
|
10739
|
+
mod.renderScanScorecardInk(
|
|
10740
|
+
{
|
|
10741
|
+
scan,
|
|
10742
|
+
summary,
|
|
10743
|
+
blast,
|
|
10744
|
+
blastExposures,
|
|
10745
|
+
blockedCount,
|
|
10746
|
+
reviewCount
|
|
10747
|
+
},
|
|
10748
|
+
rangeLabel2
|
|
10749
|
+
);
|
|
10750
|
+
console.log("");
|
|
10751
|
+
} else {
|
|
10752
|
+
renderPanelScorecard({
|
|
10753
|
+
scan,
|
|
10754
|
+
summary,
|
|
10755
|
+
blast,
|
|
10756
|
+
blastExposures,
|
|
10757
|
+
blockedCount,
|
|
10758
|
+
reviewCount
|
|
10759
|
+
});
|
|
10760
|
+
}
|
|
10735
10761
|
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
10736
10762
|
console.log(" " + chalk5.green(cta));
|
|
10737
10763
|
console.log(
|
|
@@ -13602,12 +13628,12 @@ __export(tail_exports, {
|
|
|
13602
13628
|
startTail: () => startTail
|
|
13603
13629
|
});
|
|
13604
13630
|
import http2 from "http";
|
|
13605
|
-
import
|
|
13631
|
+
import chalk29 from "chalk";
|
|
13606
13632
|
import fs45 from "fs";
|
|
13607
13633
|
import os41 from "os";
|
|
13608
13634
|
import path47 from "path";
|
|
13609
13635
|
import readline6 from "readline";
|
|
13610
|
-
import { spawn as
|
|
13636
|
+
import { spawn as spawn8 } from "child_process";
|
|
13611
13637
|
function shortenPathSummary(s) {
|
|
13612
13638
|
if (!s || !s.startsWith("/")) return s;
|
|
13613
13639
|
const parts = s.split("/").filter(Boolean);
|
|
@@ -13682,10 +13708,10 @@ function readSessionUsage() {
|
|
|
13682
13708
|
}
|
|
13683
13709
|
}
|
|
13684
13710
|
function formatContextStat(stat) {
|
|
13685
|
-
const pctColor = stat.fillPct >= 80 ?
|
|
13711
|
+
const pctColor = stat.fillPct >= 80 ? chalk29.red : stat.fillPct >= 50 ? chalk29.yellow : chalk29.cyan;
|
|
13686
13712
|
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
13687
13713
|
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
13688
|
-
return
|
|
13714
|
+
return chalk29.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk29.dim(
|
|
13689
13715
|
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
13690
13716
|
);
|
|
13691
13717
|
}
|
|
@@ -13708,11 +13734,11 @@ function agentLabel(agent, mcpServer, sessionId) {
|
|
|
13708
13734
|
const tag = sessionTag(sessionId);
|
|
13709
13735
|
const tagSuffix = tag ? `\xB7${tag}` : "";
|
|
13710
13736
|
if (!agent || agent === "Terminal") {
|
|
13711
|
-
return mcpServer ?
|
|
13737
|
+
return mcpServer ? chalk29.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
13712
13738
|
}
|
|
13713
13739
|
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
13714
|
-
if (!short) return mcpServer ?
|
|
13715
|
-
return mcpServer ?
|
|
13740
|
+
if (!short) return mcpServer ? chalk29.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
13741
|
+
return mcpServer ? chalk29.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : chalk29.dim(`[${short}${tagSuffix}] `);
|
|
13716
13742
|
}
|
|
13717
13743
|
function formatBase(activity) {
|
|
13718
13744
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
@@ -13720,20 +13746,20 @@ function formatBase(activity) {
|
|
|
13720
13746
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
13721
13747
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os41.homedir(), "~");
|
|
13722
13748
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
13723
|
-
return `${
|
|
13749
|
+
return `${chalk29.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk29.white.bold(toolName)} ${chalk29.dim(argsPreview)}`;
|
|
13724
13750
|
}
|
|
13725
13751
|
function renderResult(activity, result) {
|
|
13726
13752
|
const base = formatBase(activity);
|
|
13727
13753
|
let status;
|
|
13728
13754
|
if (result.status === "allow") {
|
|
13729
|
-
status =
|
|
13755
|
+
status = chalk29.green("\u2713 ALLOW");
|
|
13730
13756
|
} else if (result.status === "dlp") {
|
|
13731
|
-
status =
|
|
13757
|
+
status = chalk29.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
13732
13758
|
} else {
|
|
13733
|
-
status =
|
|
13759
|
+
status = chalk29.red("\u2717 BLOCK");
|
|
13734
13760
|
}
|
|
13735
13761
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
13736
|
-
const costSuffix = cost == null ? "" :
|
|
13762
|
+
const costSuffix = cost == null ? "" : chalk29.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
13737
13763
|
if (process.stdout.isTTY) {
|
|
13738
13764
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
13739
13765
|
readline6.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -13750,7 +13776,7 @@ function renderResult(activity, result) {
|
|
|
13750
13776
|
}
|
|
13751
13777
|
function renderPending(activity) {
|
|
13752
13778
|
if (!process.stdout.isTTY) return;
|
|
13753
|
-
const line = `${formatBase(activity)} ${
|
|
13779
|
+
const line = `${formatBase(activity)} ${chalk29.yellow("\u25CF \u2026")}`;
|
|
13754
13780
|
pendingShownForId = activity.id;
|
|
13755
13781
|
pendingWrappedLines = wrappedLineCount(line);
|
|
13756
13782
|
process.stdout.write(`${line}\r`);
|
|
@@ -13762,7 +13788,7 @@ async function ensureDaemon() {
|
|
|
13762
13788
|
const { port } = JSON.parse(fs45.readFileSync(PID_FILE, "utf-8"));
|
|
13763
13789
|
pidPort = port;
|
|
13764
13790
|
} catch {
|
|
13765
|
-
console.error(
|
|
13791
|
+
console.error(chalk29.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
13766
13792
|
}
|
|
13767
13793
|
}
|
|
13768
13794
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -13773,8 +13799,8 @@ async function ensureDaemon() {
|
|
|
13773
13799
|
if (res.ok) return checkPort;
|
|
13774
13800
|
} catch {
|
|
13775
13801
|
}
|
|
13776
|
-
console.log(
|
|
13777
|
-
const child =
|
|
13802
|
+
console.log(chalk29.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
13803
|
+
const child = spawn8(process.execPath, [process.argv[1], "daemon"], {
|
|
13778
13804
|
detached: true,
|
|
13779
13805
|
stdio: "ignore",
|
|
13780
13806
|
env: { ...process.env, NODE9_AUTO_STARTED: "1" }
|
|
@@ -13790,7 +13816,7 @@ async function ensureDaemon() {
|
|
|
13790
13816
|
} catch {
|
|
13791
13817
|
}
|
|
13792
13818
|
}
|
|
13793
|
-
console.error(
|
|
13819
|
+
console.error(chalk29.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
13794
13820
|
process.exit(1);
|
|
13795
13821
|
}
|
|
13796
13822
|
function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
@@ -13859,7 +13885,7 @@ function buildCardLines(req, localCount = 0) {
|
|
|
13859
13885
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
13860
13886
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
13861
13887
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
13862
|
-
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${
|
|
13888
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET2}${chalk29.dim(`(${req.agent})`)}` : "";
|
|
13863
13889
|
const lines = [
|
|
13864
13890
|
``,
|
|
13865
13891
|
`${BOLD2}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET2}`,
|
|
@@ -13928,7 +13954,7 @@ function approverStatusLine() {
|
|
|
13928
13954
|
const a = readApproversFromDisk();
|
|
13929
13955
|
const fmt = (label, key) => {
|
|
13930
13956
|
const on = a[key] !== false;
|
|
13931
|
-
return `[${key[0]}]${label.slice(1)} ${on ?
|
|
13957
|
+
return `[${key[0]}]${label.slice(1)} ${on ? chalk29.green("\u2713") : chalk29.dim("\u2717")}`;
|
|
13932
13958
|
};
|
|
13933
13959
|
return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
13934
13960
|
}
|
|
@@ -13973,7 +13999,7 @@ async function startTail(options = {}) {
|
|
|
13973
13999
|
req2.end();
|
|
13974
14000
|
});
|
|
13975
14001
|
if (result.ok) {
|
|
13976
|
-
console.log(
|
|
14002
|
+
console.log(chalk29.green("\u2713 Flight Recorder buffer cleared."));
|
|
13977
14003
|
} else if (result.code === "ECONNREFUSED") {
|
|
13978
14004
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
13979
14005
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -14019,7 +14045,7 @@ async function startTail(options = {}) {
|
|
|
14019
14045
|
const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
14020
14046
|
if (channel) {
|
|
14021
14047
|
toggleApprover(channel);
|
|
14022
|
-
console.log(
|
|
14048
|
+
console.log(chalk29.dim(` Approvers: ${approverStatusLine()}`));
|
|
14023
14049
|
}
|
|
14024
14050
|
};
|
|
14025
14051
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -14085,7 +14111,7 @@ async function startTail(options = {}) {
|
|
|
14085
14111
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
14086
14112
|
)
|
|
14087
14113
|
);
|
|
14088
|
-
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");
|
|
14089
14115
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${decisionStamp} ${GRAY}(terminal)${RESET2}`, ``);
|
|
14090
14116
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
14091
14117
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -14136,7 +14162,7 @@ async function startTail(options = {}) {
|
|
|
14136
14162
|
);
|
|
14137
14163
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
14138
14164
|
if (externalDecision) {
|
|
14139
|
-
const source = externalDecision === "allow" ?
|
|
14165
|
+
const source = externalDecision === "allow" ? chalk29.green("\u2713 ALLOWED") : chalk29.red("\u2717 DENIED");
|
|
14140
14166
|
stampedLines.push(` ${BOLD2}\u2192${RESET2} ${source} ${GRAY}(external)${RESET2}`, ``);
|
|
14141
14167
|
}
|
|
14142
14168
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -14184,25 +14210,25 @@ async function startTail(options = {}) {
|
|
|
14184
14210
|
if (unackedDlp > 0) {
|
|
14185
14211
|
console.log("");
|
|
14186
14212
|
console.log(
|
|
14187
|
-
|
|
14213
|
+
chalk29.bgRed.white.bold(
|
|
14188
14214
|
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
14189
14215
|
)
|
|
14190
14216
|
);
|
|
14191
14217
|
}
|
|
14192
14218
|
} catch {
|
|
14193
14219
|
}
|
|
14194
|
-
console.log(
|
|
14220
|
+
console.log(chalk29.cyan.bold(`
|
|
14195
14221
|
\u{1F6F0}\uFE0F Node9 tail`));
|
|
14196
14222
|
if (canApprove) {
|
|
14197
|
-
console.log(
|
|
14198
|
-
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`));
|
|
14199
14225
|
}
|
|
14200
14226
|
const ctxStat = readSessionUsage();
|
|
14201
14227
|
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
14202
14228
|
if (options.history) {
|
|
14203
|
-
console.log(
|
|
14229
|
+
console.log(chalk29.dim("Showing history + live events.\n"));
|
|
14204
14230
|
} else {
|
|
14205
|
-
console.log(
|
|
14231
|
+
console.log(chalk29.dim("Showing live events only. Use --history to include past.\n"));
|
|
14206
14232
|
}
|
|
14207
14233
|
process.on("SIGINT", () => {
|
|
14208
14234
|
exitIdleMode();
|
|
@@ -14212,7 +14238,7 @@ async function startTail(options = {}) {
|
|
|
14212
14238
|
readline6.clearLine(process.stdout, 0);
|
|
14213
14239
|
readline6.cursorTo(process.stdout, 0);
|
|
14214
14240
|
}
|
|
14215
|
-
console.log(
|
|
14241
|
+
console.log(chalk29.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
14216
14242
|
process.exit(0);
|
|
14217
14243
|
});
|
|
14218
14244
|
const STALL_THRESHOLD_MS = 6e4;
|
|
@@ -14224,7 +14250,7 @@ async function startTail(options = {}) {
|
|
|
14224
14250
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
14225
14251
|
console.log("");
|
|
14226
14252
|
console.log(
|
|
14227
|
-
|
|
14253
|
+
chalk29.yellow(
|
|
14228
14254
|
"\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
|
|
14229
14255
|
)
|
|
14230
14256
|
);
|
|
@@ -14241,7 +14267,7 @@ async function startTail(options = {}) {
|
|
|
14241
14267
|
},
|
|
14242
14268
|
(res) => {
|
|
14243
14269
|
if (res.statusCode !== 200) {
|
|
14244
|
-
console.error(
|
|
14270
|
+
console.error(chalk29.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
14245
14271
|
process.exit(1);
|
|
14246
14272
|
}
|
|
14247
14273
|
if (canApprove) enterIdleMode();
|
|
@@ -14272,7 +14298,7 @@ async function startTail(options = {}) {
|
|
|
14272
14298
|
readline6.clearLine(process.stdout, 0);
|
|
14273
14299
|
readline6.cursorTo(process.stdout, 0);
|
|
14274
14300
|
}
|
|
14275
|
-
console.log(
|
|
14301
|
+
console.log(chalk29.red("\n\u274C Daemon disconnected."));
|
|
14276
14302
|
process.exit(1);
|
|
14277
14303
|
});
|
|
14278
14304
|
}
|
|
@@ -14285,7 +14311,7 @@ async function startTail(options = {}) {
|
|
|
14285
14311
|
const parsed = JSON.parse(rawData);
|
|
14286
14312
|
const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
|
|
14287
14313
|
console.log("");
|
|
14288
|
-
console.log(
|
|
14314
|
+
console.log(chalk29.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
|
|
14289
14315
|
} catch {
|
|
14290
14316
|
}
|
|
14291
14317
|
return;
|
|
@@ -14370,9 +14396,9 @@ async function startTail(options = {}) {
|
|
|
14370
14396
|
const rawSummary = data.argsSummary ?? data.tool;
|
|
14371
14397
|
const summary = shortenPathSummary(rawSummary);
|
|
14372
14398
|
const fileCount = data.fileCount ?? 0;
|
|
14373
|
-
const files = fileCount > 0 ?
|
|
14399
|
+
const files = fileCount > 0 ? chalk29.dim(` \xB7 ${fileCount} file${fileCount === 1 ? "" : "s"}`) : "";
|
|
14374
14400
|
process.stdout.write(
|
|
14375
|
-
`${
|
|
14401
|
+
`${chalk29.dim(time)} ${chalk29.cyan("\u{1F4F8} snapshot")} ${chalk29.dim(hash)} ${summary}${files}
|
|
14376
14402
|
`
|
|
14377
14403
|
);
|
|
14378
14404
|
return;
|
|
@@ -14389,18 +14415,18 @@ async function startTail(options = {}) {
|
|
|
14389
14415
|
if (event === "execution-result") {
|
|
14390
14416
|
const exec = data;
|
|
14391
14417
|
const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
|
|
14392
|
-
const arrow = exec.isError ?
|
|
14418
|
+
const arrow = exec.isError ? chalk29.red(" \u21B3 \u2717") : chalk29.green(" \u21B3 \u2713");
|
|
14393
14419
|
const label = agentLabel(exec.agent, exec.mcpServer);
|
|
14394
14420
|
const tool = (exec.tool ?? "").slice(0, 16);
|
|
14395
|
-
const duration = typeof exec.durationMs === "number" ?
|
|
14421
|
+
const duration = typeof exec.durationMs === "number" ? chalk29.dim(` (${exec.durationMs}ms)`) : "";
|
|
14396
14422
|
console.log(
|
|
14397
|
-
`${
|
|
14423
|
+
`${chalk29.gray(time)} ${arrow} ${label}${chalk29.dim(tool)}${chalk29.dim(" completed")}${duration}`
|
|
14398
14424
|
);
|
|
14399
14425
|
}
|
|
14400
14426
|
}
|
|
14401
14427
|
req.on("error", (err2) => {
|
|
14402
14428
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
14403
|
-
console.error(
|
|
14429
|
+
console.error(chalk29.red(`
|
|
14404
14430
|
\u274C ${msg}`));
|
|
14405
14431
|
process.exit(1);
|
|
14406
14432
|
});
|
|
@@ -14836,7 +14862,7 @@ init_core();
|
|
|
14836
14862
|
init_setup();
|
|
14837
14863
|
init_daemon2();
|
|
14838
14864
|
import { Command } from "commander";
|
|
14839
|
-
import
|
|
14865
|
+
import chalk30 from "chalk";
|
|
14840
14866
|
import fs47 from "fs";
|
|
14841
14867
|
import path49 from "path";
|
|
14842
14868
|
import os43 from "os";
|
|
@@ -15630,7 +15656,7 @@ function detectAiAgent(payload) {
|
|
|
15630
15656
|
return "Terminal";
|
|
15631
15657
|
}
|
|
15632
15658
|
function registerCheckCommand(program2) {
|
|
15633
|
-
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) => {
|
|
15634
15660
|
const processPayload = async (raw) => {
|
|
15635
15661
|
try {
|
|
15636
15662
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
@@ -16028,7 +16054,7 @@ function sanitize3(value) {
|
|
|
16028
16054
|
return value.replace(/[\x00-\x1F\x7F]/g, "");
|
|
16029
16055
|
}
|
|
16030
16056
|
function registerLogCommand(program2) {
|
|
16031
|
-
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) => {
|
|
16032
16058
|
const logPayload = async (raw) => {
|
|
16033
16059
|
try {
|
|
16034
16060
|
if (!raw || raw.trim() === "") process.exit(0);
|
|
@@ -18194,7 +18220,7 @@ function registerInitCommand(program2) {
|
|
|
18194
18220
|
const agentList = found.join(", ");
|
|
18195
18221
|
console.log(chalk16.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
18196
18222
|
console.log("");
|
|
18197
|
-
console.log(chalk16.white(" Watch live: ") + chalk16.cyan("node9
|
|
18223
|
+
console.log(chalk16.white(" Watch live: ") + chalk16.cyan("node9 monitor"));
|
|
18198
18224
|
console.log("");
|
|
18199
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"));
|
|
18200
18226
|
console.log(
|
|
@@ -18491,72 +18517,11 @@ function registerUndoCommand(program2) {
|
|
|
18491
18517
|
});
|
|
18492
18518
|
}
|
|
18493
18519
|
|
|
18494
|
-
// src/cli/commands/watch.ts
|
|
18495
|
-
init_daemon();
|
|
18496
|
-
import chalk19 from "chalk";
|
|
18497
|
-
import { spawn as spawn7, spawnSync as spawnSync4 } from "child_process";
|
|
18498
|
-
function registerWatchCommand(program2) {
|
|
18499
|
-
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) => {
|
|
18500
|
-
let port = DAEMON_PORT;
|
|
18501
|
-
try {
|
|
18502
|
-
const res = await fetch(`http://127.0.0.1:${DAEMON_PORT}/settings`, {
|
|
18503
|
-
signal: AbortSignal.timeout(500)
|
|
18504
|
-
});
|
|
18505
|
-
if (res.ok) {
|
|
18506
|
-
const data = await res.json();
|
|
18507
|
-
if (typeof data.port === "number") port = data.port;
|
|
18508
|
-
} else {
|
|
18509
|
-
throw new Error("not running");
|
|
18510
|
-
}
|
|
18511
|
-
} catch {
|
|
18512
|
-
console.error(chalk19.dim("\u{1F6E1}\uFE0F Starting Node9 daemon (watch mode)..."));
|
|
18513
|
-
const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
|
|
18514
|
-
detached: true,
|
|
18515
|
-
stdio: "ignore",
|
|
18516
|
-
env: { ...process.env, NODE9_AUTO_STARTED: "1", NODE9_WATCH_MODE: "1" }
|
|
18517
|
-
});
|
|
18518
|
-
child.unref();
|
|
18519
|
-
let ready = false;
|
|
18520
|
-
for (let i = 0; i < 20; i++) {
|
|
18521
|
-
await new Promise((r) => setTimeout(r, 250));
|
|
18522
|
-
try {
|
|
18523
|
-
const r = await fetch(`http://127.0.0.1:${DAEMON_PORT}/settings`, {
|
|
18524
|
-
signal: AbortSignal.timeout(500)
|
|
18525
|
-
});
|
|
18526
|
-
if (r.ok) {
|
|
18527
|
-
ready = true;
|
|
18528
|
-
break;
|
|
18529
|
-
}
|
|
18530
|
-
} catch {
|
|
18531
|
-
}
|
|
18532
|
-
}
|
|
18533
|
-
if (!ready) {
|
|
18534
|
-
console.error(chalk19.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
18535
|
-
process.exit(1);
|
|
18536
|
-
}
|
|
18537
|
-
}
|
|
18538
|
-
console.error(
|
|
18539
|
-
chalk19.cyan.bold("\u{1F6E1}\uFE0F Node9 watch") + chalk19.dim(` \u2192 localhost:${port}`) + chalk19.dim(
|
|
18540
|
-
"\n Tip: run `node9 tail` in another terminal to review and approve AI actions.\n"
|
|
18541
|
-
)
|
|
18542
|
-
);
|
|
18543
|
-
const result = spawnSync4(cmd, args, {
|
|
18544
|
-
stdio: "inherit",
|
|
18545
|
-
env: { ...process.env, NODE9_WATCH_MODE: "1" }
|
|
18546
|
-
});
|
|
18547
|
-
if (result.error) {
|
|
18548
|
-
console.error(chalk19.red(`\u274C Failed to run command: ${result.error.message}`));
|
|
18549
|
-
process.exit(1);
|
|
18550
|
-
}
|
|
18551
|
-
process.exit(result.status ?? 0);
|
|
18552
|
-
});
|
|
18553
|
-
}
|
|
18554
|
-
|
|
18555
18520
|
// src/mcp-gateway/index.ts
|
|
18556
18521
|
init_orchestrator();
|
|
18557
18522
|
import readline4 from "readline";
|
|
18558
|
-
import
|
|
18559
|
-
import { spawn as
|
|
18523
|
+
import chalk19 from "chalk";
|
|
18524
|
+
import { spawn as spawn7 } from "child_process";
|
|
18560
18525
|
import { execa as execa2 } from "execa";
|
|
18561
18526
|
init_provenance();
|
|
18562
18527
|
|
|
@@ -18716,13 +18681,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18716
18681
|
const prov = checkProvenance(executable);
|
|
18717
18682
|
if (prov.trustLevel === "suspect") {
|
|
18718
18683
|
console.error(
|
|
18719
|
-
|
|
18684
|
+
chalk19.red(
|
|
18720
18685
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
18721
18686
|
)
|
|
18722
18687
|
);
|
|
18723
|
-
console.error(
|
|
18688
|
+
console.error(chalk19.red(" Verify this binary is trusted before proceeding."));
|
|
18724
18689
|
}
|
|
18725
|
-
console.error(
|
|
18690
|
+
console.error(chalk19.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
18726
18691
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
18727
18692
|
"NODE_OPTIONS",
|
|
18728
18693
|
"NODE_PATH",
|
|
@@ -18741,7 +18706,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18741
18706
|
const safeEnv = Object.fromEntries(
|
|
18742
18707
|
Object.entries(process.env).filter(([k]) => !UPSTREAM_INJECTOR_VARS.has(k))
|
|
18743
18708
|
);
|
|
18744
|
-
const child =
|
|
18709
|
+
const child = spawn7(executable, cmdArgs, {
|
|
18745
18710
|
stdio: ["pipe", "pipe", "inherit"],
|
|
18746
18711
|
// control stdin/stdout; inherit stderr
|
|
18747
18712
|
shell: false,
|
|
@@ -18835,10 +18800,10 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18835
18800
|
mcpServer
|
|
18836
18801
|
});
|
|
18837
18802
|
if (!result.approved) {
|
|
18838
|
-
console.error(
|
|
18803
|
+
console.error(chalk19.red(`
|
|
18839
18804
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
18840
|
-
console.error(
|
|
18841
|
-
console.error(
|
|
18805
|
+
console.error(chalk19.gray(` Tool: ${toolName}`));
|
|
18806
|
+
console.error(chalk19.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
18842
18807
|
`));
|
|
18843
18808
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
18844
18809
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -18950,7 +18915,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18950
18915
|
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
18951
18916
|
pinState = "validated";
|
|
18952
18917
|
console.error(
|
|
18953
|
-
|
|
18918
|
+
chalk19.green(
|
|
18954
18919
|
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
18955
18920
|
)
|
|
18956
18921
|
);
|
|
@@ -18963,11 +18928,11 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18963
18928
|
} else if (pinStatus === "corrupt") {
|
|
18964
18929
|
pinState = "quarantined";
|
|
18965
18930
|
console.error(
|
|
18966
|
-
|
|
18931
|
+
chalk19.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
18967
18932
|
);
|
|
18968
|
-
console.error(
|
|
18933
|
+
console.error(chalk19.red(" Tool calls are blocked until the pin file is repaired."));
|
|
18969
18934
|
console.error(
|
|
18970
|
-
|
|
18935
|
+
chalk19.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
18971
18936
|
`)
|
|
18972
18937
|
);
|
|
18973
18938
|
const errorResponse = {
|
|
@@ -18984,13 +18949,13 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
18984
18949
|
} else {
|
|
18985
18950
|
pinState = "quarantined";
|
|
18986
18951
|
console.error(
|
|
18987
|
-
|
|
18952
|
+
chalk19.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
18988
18953
|
);
|
|
18989
18954
|
console.error(
|
|
18990
|
-
|
|
18955
|
+
chalk19.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
18991
18956
|
);
|
|
18992
|
-
console.error(
|
|
18993
|
-
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}
|
|
18994
18959
|
`));
|
|
18995
18960
|
const errorResponse = {
|
|
18996
18961
|
jsonrpc: "2.0",
|
|
@@ -19033,7 +18998,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
19033
18998
|
const toolName = callId !== void 0 ? pendingCallNames.get(callId) ?? "unknown" : "unknown";
|
|
19034
18999
|
if (callId !== void 0) pendingCallNames.delete(callId);
|
|
19035
19000
|
console.error(
|
|
19036
|
-
|
|
19001
|
+
chalk19.yellow(
|
|
19037
19002
|
`\u26A1 Node9: Large MCP response from '${toolName}' (${(line.length / 1024).toFixed(0)}KB) \u2014 context window enlarged`
|
|
19038
19003
|
)
|
|
19039
19004
|
);
|
|
@@ -19087,7 +19052,7 @@ import readline5 from "readline";
|
|
|
19087
19052
|
import fs39 from "fs";
|
|
19088
19053
|
import os35 from "os";
|
|
19089
19054
|
import path41 from "path";
|
|
19090
|
-
import { spawnSync as
|
|
19055
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
19091
19056
|
init_core();
|
|
19092
19057
|
init_daemon();
|
|
19093
19058
|
init_shields();
|
|
@@ -19561,7 +19526,7 @@ function handleRuleAdd(args) {
|
|
|
19561
19526
|
return `Rule "${name}" added to ~/.node9/config.json \u2014 verdict: ${verdict} when ${field} matches "${pattern}"`;
|
|
19562
19527
|
}
|
|
19563
19528
|
function runCliCommand(subArgs) {
|
|
19564
|
-
const result =
|
|
19529
|
+
const result = spawnSync4(process.execPath, [process.argv[1], ...subArgs], {
|
|
19565
19530
|
encoding: "utf-8",
|
|
19566
19531
|
timeout: 6e4,
|
|
19567
19532
|
// Disable colors — stdout is piped (not a TTY), chalk auto-detects, but be explicit
|
|
@@ -19756,7 +19721,7 @@ function registerMcpServerCommand(program2) {
|
|
|
19756
19721
|
|
|
19757
19722
|
// src/cli/commands/trust.ts
|
|
19758
19723
|
init_trusted_hosts();
|
|
19759
|
-
import
|
|
19724
|
+
import chalk20 from "chalk";
|
|
19760
19725
|
function isValidHost(host) {
|
|
19761
19726
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
19762
19727
|
}
|
|
@@ -19766,51 +19731,51 @@ function registerTrustCommand(program2) {
|
|
|
19766
19731
|
const normalized = normalizeHost(host.trim());
|
|
19767
19732
|
if (!isValidHost(normalized)) {
|
|
19768
19733
|
console.error(
|
|
19769
|
-
|
|
19734
|
+
chalk20.red(`
|
|
19770
19735
|
\u274C Invalid host: "${host}"
|
|
19771
|
-
`) +
|
|
19736
|
+
`) + chalk20.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
19772
19737
|
);
|
|
19773
19738
|
process.exit(1);
|
|
19774
19739
|
}
|
|
19775
19740
|
addTrustedHost(normalized);
|
|
19776
|
-
console.log(
|
|
19741
|
+
console.log(chalk20.green(`
|
|
19777
19742
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
19778
19743
|
console.log(
|
|
19779
|
-
|
|
19744
|
+
chalk20.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
19780
19745
|
);
|
|
19781
19746
|
});
|
|
19782
19747
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
19783
19748
|
const normalized = normalizeHost(host.trim());
|
|
19784
19749
|
const removed = removeTrustedHost(normalized);
|
|
19785
19750
|
if (!removed) {
|
|
19786
|
-
console.error(
|
|
19751
|
+
console.error(chalk20.yellow(`
|
|
19787
19752
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
19788
19753
|
`));
|
|
19789
19754
|
process.exit(1);
|
|
19790
19755
|
}
|
|
19791
|
-
console.log(
|
|
19756
|
+
console.log(chalk20.green(`
|
|
19792
19757
|
\u2705 ${normalized} removed from trusted hosts.
|
|
19793
19758
|
`));
|
|
19794
19759
|
});
|
|
19795
19760
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
19796
19761
|
const hosts = readTrustedHosts();
|
|
19797
19762
|
if (hosts.length === 0) {
|
|
19798
|
-
console.log(
|
|
19799
|
-
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")}
|
|
19800
19765
|
`);
|
|
19801
19766
|
return;
|
|
19802
19767
|
}
|
|
19803
|
-
console.log(
|
|
19768
|
+
console.log(chalk20.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
19804
19769
|
for (const entry of hosts) {
|
|
19805
19770
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
19806
|
-
console.log(` ${
|
|
19771
|
+
console.log(` ${chalk20.cyan(entry.host.padEnd(40))} ${chalk20.gray(`added ${date}`)}`);
|
|
19807
19772
|
}
|
|
19808
19773
|
console.log("");
|
|
19809
19774
|
});
|
|
19810
19775
|
}
|
|
19811
19776
|
|
|
19812
19777
|
// src/cli/commands/mcp-pin.ts
|
|
19813
|
-
import
|
|
19778
|
+
import chalk21 from "chalk";
|
|
19814
19779
|
function registerMcpPinCommand(program2) {
|
|
19815
19780
|
const pinCmd = program2.command("mcp").description("Manage MCP server tool definition pinning (rug pull defense)");
|
|
19816
19781
|
const pinSubCmd = pinCmd.command("pin").description("Manage pinned MCP server tool definitions");
|
|
@@ -19818,31 +19783,31 @@ function registerMcpPinCommand(program2) {
|
|
|
19818
19783
|
const result = readMcpPinsSafe();
|
|
19819
19784
|
if (!result.ok) {
|
|
19820
19785
|
if (result.reason === "missing") {
|
|
19821
|
-
console.log(
|
|
19786
|
+
console.log(chalk21.gray("\nNo MCP servers are pinned yet."));
|
|
19822
19787
|
console.log(
|
|
19823
|
-
|
|
19788
|
+
chalk21.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
19824
19789
|
);
|
|
19825
19790
|
return;
|
|
19826
19791
|
}
|
|
19827
|
-
console.error(
|
|
19792
|
+
console.error(chalk21.red(`
|
|
19828
19793
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
19829
|
-
console.error(
|
|
19794
|
+
console.error(chalk21.yellow(" Run: node9 mcp pin reset\n"));
|
|
19830
19795
|
process.exit(1);
|
|
19831
19796
|
}
|
|
19832
19797
|
const entries = Object.entries(result.pins.servers);
|
|
19833
19798
|
if (entries.length === 0) {
|
|
19834
|
-
console.log(
|
|
19799
|
+
console.log(chalk21.gray("\nNo MCP servers are pinned yet."));
|
|
19835
19800
|
console.log(
|
|
19836
|
-
|
|
19801
|
+
chalk21.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
19837
19802
|
);
|
|
19838
19803
|
return;
|
|
19839
19804
|
}
|
|
19840
|
-
console.log(
|
|
19805
|
+
console.log(chalk21.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
19841
19806
|
for (const [key, entry] of entries) {
|
|
19842
|
-
console.log(` ${
|
|
19843
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
19844
|
-
console.log(` Hash: ${
|
|
19845
|
-
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)}`);
|
|
19846
19811
|
console.log("");
|
|
19847
19812
|
}
|
|
19848
19813
|
});
|
|
@@ -19853,127 +19818,127 @@ function registerMcpPinCommand(program2) {
|
|
|
19853
19818
|
try {
|
|
19854
19819
|
pins = readMcpPins();
|
|
19855
19820
|
} catch {
|
|
19856
|
-
console.error(
|
|
19857
|
-
console.error(
|
|
19821
|
+
console.error(chalk21.red("\n\u274C Pin file is corrupt."));
|
|
19822
|
+
console.error(chalk21.yellow(" Run: node9 mcp pin reset\n"));
|
|
19858
19823
|
process.exit(1);
|
|
19859
19824
|
}
|
|
19860
19825
|
if (!pins.servers[serverKey]) {
|
|
19861
|
-
console.error(
|
|
19826
|
+
console.error(chalk21.red(`
|
|
19862
19827
|
\u274C No pin found for server key "${serverKey}"
|
|
19863
19828
|
`));
|
|
19864
|
-
console.error(`Run ${
|
|
19829
|
+
console.error(`Run ${chalk21.cyan("node9 mcp pin list")} to see pinned servers.
|
|
19865
19830
|
`);
|
|
19866
19831
|
process.exit(1);
|
|
19867
19832
|
}
|
|
19868
19833
|
const label = pins.servers[serverKey].label;
|
|
19869
19834
|
removePin2(serverKey);
|
|
19870
|
-
console.log(
|
|
19871
|
-
\u{1F513} Pin removed for ${
|
|
19872
|
-
console.log(
|
|
19873
|
-
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"));
|
|
19874
19839
|
});
|
|
19875
19840
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
19876
19841
|
const result = readMcpPinsSafe();
|
|
19877
19842
|
if (!result.ok && result.reason === "missing") {
|
|
19878
|
-
console.log(
|
|
19843
|
+
console.log(chalk21.gray("\nNo pins to clear.\n"));
|
|
19879
19844
|
return;
|
|
19880
19845
|
}
|
|
19881
19846
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
19882
19847
|
clearAllPins2();
|
|
19883
|
-
console.log(
|
|
19848
|
+
console.log(chalk21.green(`
|
|
19884
19849
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
19885
|
-
console.log(
|
|
19850
|
+
console.log(chalk21.gray(" Next connection to each server will re-pin.\n"));
|
|
19886
19851
|
});
|
|
19887
19852
|
}
|
|
19888
19853
|
|
|
19889
19854
|
// src/cli/commands/sync.ts
|
|
19890
19855
|
init_sync();
|
|
19891
|
-
import
|
|
19856
|
+
import chalk22 from "chalk";
|
|
19892
19857
|
function registerSyncCommand(program2) {
|
|
19893
19858
|
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
19894
19859
|
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
19895
|
-
process.stdout.write(
|
|
19860
|
+
process.stdout.write(chalk22.cyan("Syncing cloud policy rules\u2026"));
|
|
19896
19861
|
const result = await runCloudSync();
|
|
19897
19862
|
process.stdout.write("\n");
|
|
19898
19863
|
if (!result.ok) {
|
|
19899
|
-
console.error(
|
|
19864
|
+
console.error(chalk22.red(`\u2717 ${result.reason}`));
|
|
19900
19865
|
process.exit(1);
|
|
19901
19866
|
}
|
|
19902
19867
|
if (result.unchanged) {
|
|
19903
19868
|
console.log(
|
|
19904
|
-
|
|
19869
|
+
chalk22.green(
|
|
19905
19870
|
`\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
|
|
19906
19871
|
)
|
|
19907
19872
|
);
|
|
19908
|
-
console.log(
|
|
19909
|
-
console.log(
|
|
19873
|
+
console.log(chalk22.gray(` Cached at: ${result.fetchedAt}`));
|
|
19874
|
+
console.log(chalk22.gray(` Server returned 304 (no changes since last sync)`));
|
|
19910
19875
|
} else {
|
|
19911
19876
|
console.log(
|
|
19912
|
-
|
|
19877
|
+
chalk22.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
19913
19878
|
);
|
|
19914
|
-
console.log(
|
|
19915
|
-
console.log(
|
|
19879
|
+
console.log(chalk22.gray(` Cached at: ${result.fetchedAt}`));
|
|
19880
|
+
console.log(chalk22.gray(` File: ~/.node9/rules-cache.json`));
|
|
19916
19881
|
}
|
|
19917
19882
|
});
|
|
19918
19883
|
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
19919
19884
|
const status = getCloudSyncStatus();
|
|
19920
19885
|
if (!status.cached) {
|
|
19921
|
-
console.log(
|
|
19886
|
+
console.log(chalk22.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
19922
19887
|
return;
|
|
19923
19888
|
}
|
|
19924
19889
|
const rules = getCloudRules() ?? [];
|
|
19925
19890
|
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
19926
19891
|
console.log(
|
|
19927
|
-
|
|
19928
|
-
Cloud policy rules`) +
|
|
19892
|
+
chalk22.bold(`
|
|
19893
|
+
Cloud policy rules`) + chalk22.gray(
|
|
19929
19894
|
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
19930
19895
|
`
|
|
19931
19896
|
)
|
|
19932
19897
|
);
|
|
19933
19898
|
if (rules.length === 0) {
|
|
19934
|
-
console.log(
|
|
19899
|
+
console.log(chalk22.gray(" No rules defined in cloud policy.\n"));
|
|
19935
19900
|
return;
|
|
19936
19901
|
}
|
|
19937
19902
|
for (const rule of rules) {
|
|
19938
19903
|
const r = rule;
|
|
19939
|
-
const verdictColor = r.verdict === "block" ?
|
|
19904
|
+
const verdictColor = r.verdict === "block" ? chalk22.red : r.verdict === "allow" ? chalk22.green : chalk22.yellow;
|
|
19940
19905
|
console.log(
|
|
19941
19906
|
` ${verdictColor(
|
|
19942
19907
|
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
19943
|
-
)} ${
|
|
19908
|
+
)} ${chalk22.white(String(r.name ?? "(unnamed)"))}`
|
|
19944
19909
|
);
|
|
19945
|
-
if (r.reason) console.log(
|
|
19910
|
+
if (r.reason) console.log(chalk22.gray(` ${String(r.reason)}`));
|
|
19946
19911
|
}
|
|
19947
19912
|
console.log("");
|
|
19948
19913
|
});
|
|
19949
19914
|
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
19950
19915
|
const s = getCloudSyncStatus();
|
|
19951
19916
|
if (!s.cached) {
|
|
19952
|
-
console.log(
|
|
19917
|
+
console.log(chalk22.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
19953
19918
|
return;
|
|
19954
19919
|
}
|
|
19955
19920
|
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
19956
19921
|
console.log(`
|
|
19957
|
-
Rules : ${
|
|
19922
|
+
Rules : ${chalk22.green(String(s.rules))} cloud rules loaded`);
|
|
19958
19923
|
console.log(
|
|
19959
|
-
` Synced : ${
|
|
19924
|
+
` Synced : ${chalk22.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
|
|
19960
19925
|
);
|
|
19961
19926
|
if (s.workspaceId) {
|
|
19962
|
-
console.log(` Workspace: ${
|
|
19927
|
+
console.log(` Workspace: ${chalk22.gray(s.workspaceId)}`);
|
|
19963
19928
|
}
|
|
19964
19929
|
if (s.panicMode) {
|
|
19965
19930
|
console.log(
|
|
19966
|
-
` ${
|
|
19931
|
+
` ${chalk22.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk22.dim("(every review-verdict becomes block)")
|
|
19967
19932
|
);
|
|
19968
19933
|
}
|
|
19969
19934
|
if (s.shadowMode) {
|
|
19970
19935
|
console.log(
|
|
19971
|
-
` ${
|
|
19936
|
+
` ${chalk22.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk22.dim("(blocks become would-block log entries)")
|
|
19972
19937
|
);
|
|
19973
19938
|
}
|
|
19974
19939
|
if (s.syncIntervalHours) {
|
|
19975
19940
|
console.log(
|
|
19976
|
-
|
|
19941
|
+
chalk22.gray(
|
|
19977
19942
|
` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
|
|
19978
19943
|
)
|
|
19979
19944
|
);
|
|
@@ -19984,7 +19949,7 @@ function registerSyncCommand(program2) {
|
|
|
19984
19949
|
|
|
19985
19950
|
// src/cli/commands/agents.ts
|
|
19986
19951
|
init_setup();
|
|
19987
|
-
import
|
|
19952
|
+
import chalk23 from "chalk";
|
|
19988
19953
|
var SETUP_FN = {
|
|
19989
19954
|
claude: setupClaude,
|
|
19990
19955
|
gemini: setupGemini,
|
|
@@ -20013,23 +19978,23 @@ function registerAgentsCommand(program2) {
|
|
|
20013
19978
|
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
20014
19979
|
console.log(" " + "\u2500".repeat(44));
|
|
20015
19980
|
for (const s of statuses) {
|
|
20016
|
-
const installed = s.installed ?
|
|
20017
|
-
const wired = !s.installed ?
|
|
20018
|
-
const mode = s.mode ?
|
|
20019
|
-
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}`) : "";
|
|
20020
19985
|
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
20021
19986
|
}
|
|
20022
19987
|
console.log("");
|
|
20023
19988
|
if (!anyInstalled) {
|
|
20024
19989
|
console.log(
|
|
20025
|
-
|
|
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")
|
|
20026
19991
|
);
|
|
20027
19992
|
return;
|
|
20028
19993
|
}
|
|
20029
19994
|
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
20030
19995
|
if (unwired.length > 0) {
|
|
20031
19996
|
console.log(
|
|
20032
|
-
|
|
19997
|
+
chalk23.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk23.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
20033
19998
|
);
|
|
20034
19999
|
}
|
|
20035
20000
|
});
|
|
@@ -20037,7 +20002,7 @@ function registerAgentsCommand(program2) {
|
|
|
20037
20002
|
const name = agent.toLowerCase();
|
|
20038
20003
|
const fn = SETUP_FN[name];
|
|
20039
20004
|
if (!fn) {
|
|
20040
|
-
console.error(
|
|
20005
|
+
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
20041
20006
|
process.exit(1);
|
|
20042
20007
|
}
|
|
20043
20008
|
await fn();
|
|
@@ -20046,14 +20011,14 @@ function registerAgentsCommand(program2) {
|
|
|
20046
20011
|
const name = agent.toLowerCase();
|
|
20047
20012
|
const fn = TEARDOWN_FN[name];
|
|
20048
20013
|
if (!fn) {
|
|
20049
|
-
console.error(
|
|
20014
|
+
console.error(chalk23.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
20050
20015
|
process.exit(1);
|
|
20051
20016
|
}
|
|
20052
|
-
console.log(
|
|
20017
|
+
console.log(chalk23.cyan(`
|
|
20053
20018
|
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
20054
20019
|
`));
|
|
20055
20020
|
fn();
|
|
20056
|
-
console.log(
|
|
20021
|
+
console.log(chalk23.gray("\n Restart the agent for changes to take effect."));
|
|
20057
20022
|
});
|
|
20058
20023
|
}
|
|
20059
20024
|
|
|
@@ -20061,7 +20026,7 @@ function registerAgentsCommand(program2) {
|
|
|
20061
20026
|
init_scan();
|
|
20062
20027
|
|
|
20063
20028
|
// src/cli/commands/sessions.ts
|
|
20064
|
-
import
|
|
20029
|
+
import chalk24 from "chalk";
|
|
20065
20030
|
import fs40 from "fs";
|
|
20066
20031
|
import path42 from "path";
|
|
20067
20032
|
import os36 from "os";
|
|
@@ -20570,11 +20535,11 @@ function toolInputSummary(tool, input) {
|
|
|
20570
20535
|
}
|
|
20571
20536
|
function toolColor(tool) {
|
|
20572
20537
|
const t = tool.toLowerCase();
|
|
20573
|
-
if (t === "bash" || t === "execute_bash") return
|
|
20574
|
-
if (t === "write") return
|
|
20575
|
-
if (t === "edit" || t === "notebookedit") return
|
|
20576
|
-
if (t === "read") return
|
|
20577
|
-
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;
|
|
20578
20543
|
}
|
|
20579
20544
|
function barStr2(value, max, width) {
|
|
20580
20545
|
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
@@ -20584,7 +20549,7 @@ function barStr2(value, max, width) {
|
|
|
20584
20549
|
function colorBar2(value, max, width) {
|
|
20585
20550
|
const s = barStr2(value, max, width);
|
|
20586
20551
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
20587
|
-
return
|
|
20552
|
+
return chalk24.cyan(s.slice(0, filled)) + chalk24.dim(s.slice(filled));
|
|
20588
20553
|
}
|
|
20589
20554
|
function renderSummary(summaries) {
|
|
20590
20555
|
const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
|
|
@@ -20614,45 +20579,45 @@ function renderSummary(summaries) {
|
|
|
20614
20579
|
}
|
|
20615
20580
|
const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
20616
20581
|
const W = 20;
|
|
20617
|
-
console.log(
|
|
20582
|
+
console.log(chalk24.dim(" " + "\u2500".repeat(70)));
|
|
20618
20583
|
console.log(
|
|
20619
|
-
" " +
|
|
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") : "")
|
|
20620
20585
|
);
|
|
20621
20586
|
console.log(
|
|
20622
|
-
" " +
|
|
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`)
|
|
20623
20588
|
);
|
|
20624
20589
|
console.log("");
|
|
20625
|
-
console.log(" " +
|
|
20590
|
+
console.log(" " + chalk24.dim("Tool breakdown:"));
|
|
20626
20591
|
const maxGroup = Math.max(...Object.values(groups));
|
|
20627
20592
|
for (const [label, count] of Object.entries(groups)) {
|
|
20628
20593
|
if (count === 0) continue;
|
|
20629
20594
|
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
20630
20595
|
console.log(
|
|
20631
|
-
" " + 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)}%)`)
|
|
20632
20597
|
);
|
|
20633
20598
|
}
|
|
20634
20599
|
console.log("");
|
|
20635
20600
|
if (topProjects.length > 1) {
|
|
20636
|
-
console.log(" " +
|
|
20601
|
+
console.log(" " + chalk24.dim("Cost by project:"));
|
|
20637
20602
|
const maxProjCost = topProjects[0][1];
|
|
20638
20603
|
for (const [proj, cost] of topProjects) {
|
|
20639
20604
|
console.log(
|
|
20640
|
-
" " + 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))
|
|
20641
20606
|
);
|
|
20642
20607
|
}
|
|
20643
20608
|
console.log("");
|
|
20644
20609
|
}
|
|
20645
|
-
console.log(
|
|
20610
|
+
console.log(chalk24.dim(" " + "\u2500".repeat(70)));
|
|
20646
20611
|
console.log("");
|
|
20647
20612
|
}
|
|
20648
20613
|
function renderList(summaries, totalCost) {
|
|
20649
20614
|
if (summaries.length === 0) {
|
|
20650
|
-
console.log(
|
|
20615
|
+
console.log(chalk24.yellow(" No sessions found in the requested range.\n"));
|
|
20651
20616
|
return;
|
|
20652
20617
|
}
|
|
20653
|
-
const totalLabel = totalCost > 0 ?
|
|
20618
|
+
const totalLabel = totalCost > 0 ? chalk24.dim(" ~" + fmtCost3(totalCost) + " total") : "";
|
|
20654
20619
|
console.log(
|
|
20655
|
-
" " +
|
|
20620
|
+
" " + chalk24.white(String(summaries.length)) + chalk24.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
|
|
20656
20621
|
);
|
|
20657
20622
|
console.log("");
|
|
20658
20623
|
let lastGroup = "";
|
|
@@ -20660,49 +20625,49 @@ function renderList(summaries, totalCost) {
|
|
|
20660
20625
|
const activeDate = fmtDate2(s.lastActiveTime);
|
|
20661
20626
|
const group = activeDate + " " + s.projectLabel;
|
|
20662
20627
|
if (group !== lastGroup) {
|
|
20663
|
-
console.log(
|
|
20628
|
+
console.log(chalk24.dim(" \u2500\u2500\u2500 ") + chalk24.bold(activeDate) + chalk24.dim(" " + s.projectLabel));
|
|
20664
20629
|
lastGroup = group;
|
|
20665
20630
|
}
|
|
20666
20631
|
const startDate = fmtDate2(s.startTime);
|
|
20667
|
-
const dateRange = startDate !== activeDate ?
|
|
20668
|
-
const timeStr =
|
|
20669
|
-
const prompt =
|
|
20670
|
-
const tools = s.toolCalls.length > 0 ?
|
|
20671
|
-
const cost = s.costUSD > 0 ?
|
|
20672
|
-
const blocked = s.blockedCalls.length > 0 ?
|
|
20673
|
-
const snap = s.hasSnapshot ?
|
|
20674
|
-
const agentBadge = s.agent === "gemini" ?
|
|
20675
|
-
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));
|
|
20676
20641
|
console.log(
|
|
20677
20642
|
` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
|
|
20678
20643
|
);
|
|
20679
20644
|
}
|
|
20680
20645
|
console.log("");
|
|
20681
20646
|
console.log(
|
|
20682
|
-
|
|
20647
|
+
chalk24.dim(" Run") + " " + chalk24.cyan("node9 sessions --detail <session-id>") + chalk24.dim(" for full tool trace.")
|
|
20683
20648
|
);
|
|
20684
20649
|
console.log("");
|
|
20685
20650
|
}
|
|
20686
20651
|
function renderDetail(s) {
|
|
20687
20652
|
console.log("");
|
|
20688
|
-
console.log(
|
|
20653
|
+
console.log(chalk24.bold(" Session ") + chalk24.dim(s.sessionId));
|
|
20689
20654
|
console.log(
|
|
20690
|
-
|
|
20655
|
+
chalk24.bold(" Prompt ") + chalk24.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
20691
20656
|
);
|
|
20692
|
-
console.log(
|
|
20657
|
+
console.log(chalk24.bold(" Project ") + chalk24.white(s.projectLabel));
|
|
20693
20658
|
if (s.agent) {
|
|
20694
|
-
const agentLabel2 = s.agent === "gemini" ?
|
|
20695
|
-
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);
|
|
20696
20661
|
}
|
|
20697
|
-
console.log(
|
|
20662
|
+
console.log(chalk24.bold(" When ") + chalk24.white(fmtDateTime(s.startTime)));
|
|
20698
20663
|
if (s.costUSD > 0)
|
|
20699
|
-
console.log(
|
|
20664
|
+
console.log(chalk24.bold(" Cost ") + chalk24.yellow("~" + fmtCost3(s.costUSD)));
|
|
20700
20665
|
console.log(
|
|
20701
|
-
|
|
20666
|
+
chalk24.bold(" Snapshot ") + (s.hasSnapshot ? chalk24.green("\u2713 taken") : chalk24.dim("none"))
|
|
20702
20667
|
);
|
|
20703
20668
|
console.log("");
|
|
20704
20669
|
if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
|
|
20705
|
-
console.log(
|
|
20670
|
+
console.log(chalk24.dim(" No tool calls recorded.\n"));
|
|
20706
20671
|
return;
|
|
20707
20672
|
}
|
|
20708
20673
|
const timeline = [
|
|
@@ -20715,32 +20680,32 @@ function renderDetail(s) {
|
|
|
20715
20680
|
});
|
|
20716
20681
|
const headerParts = [`Tool calls (${s.toolCalls.length})`];
|
|
20717
20682
|
if (s.blockedCalls.length > 0)
|
|
20718
|
-
headerParts.push(
|
|
20719
|
-
console.log(
|
|
20683
|
+
headerParts.push(chalk24.red(`${s.blockedCalls.length} blocked by node9`));
|
|
20684
|
+
console.log(chalk24.bold(" " + headerParts.join(" \xB7 ")));
|
|
20720
20685
|
console.log("");
|
|
20721
20686
|
for (const entry of timeline) {
|
|
20722
20687
|
if (entry.kind === "tool") {
|
|
20723
20688
|
const tc = entry.tc;
|
|
20724
20689
|
const colorFn = toolColor(tc.tool);
|
|
20725
20690
|
const toolPad = colorFn(tc.tool.padEnd(16));
|
|
20726
|
-
const detail =
|
|
20727
|
-
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) + " ") : " ";
|
|
20728
20693
|
console.log(` ${ts}${toolPad} ${detail}`);
|
|
20729
20694
|
} else {
|
|
20730
20695
|
const bc = entry.bc;
|
|
20731
|
-
const ts = bc.timestamp ?
|
|
20732
|
-
const label =
|
|
20733
|
-
const toolName =
|
|
20734
|
-
const argsSummary = bc.args ?
|
|
20735
|
-
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) : "";
|
|
20736
20701
|
console.log(` ${ts}${label} ${toolName} ${argsSummary}${reason}`);
|
|
20737
20702
|
}
|
|
20738
20703
|
}
|
|
20739
20704
|
console.log("");
|
|
20740
20705
|
if (s.modifiedFiles.length > 0) {
|
|
20741
|
-
console.log(
|
|
20706
|
+
console.log(chalk24.bold(` Files modified (${s.modifiedFiles.length}):`));
|
|
20742
20707
|
for (const f of s.modifiedFiles) {
|
|
20743
|
-
console.log(" " +
|
|
20708
|
+
console.log(" " + chalk24.yellow(f));
|
|
20744
20709
|
}
|
|
20745
20710
|
console.log("");
|
|
20746
20711
|
}
|
|
@@ -20748,19 +20713,19 @@ function renderDetail(s) {
|
|
|
20748
20713
|
function registerSessionsCommand(program2) {
|
|
20749
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) => {
|
|
20750
20715
|
console.log("");
|
|
20751
|
-
console.log(
|
|
20716
|
+
console.log(chalk24.cyan.bold("\u{1F4CB} node9 sessions") + chalk24.dim(" \u2014 what your AI agent did"));
|
|
20752
20717
|
console.log("");
|
|
20753
20718
|
const historyPath = path42.join(os36.homedir(), ".claude", "history.jsonl");
|
|
20754
20719
|
if (!fs40.existsSync(historyPath)) {
|
|
20755
|
-
console.log(
|
|
20756
|
-
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"));
|
|
20757
20722
|
return;
|
|
20758
20723
|
}
|
|
20759
20724
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
20760
20725
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
20761
|
-
console.log(
|
|
20726
|
+
console.log(chalk24.dim(" " + rangeLabel));
|
|
20762
20727
|
console.log("");
|
|
20763
|
-
process.stdout.write(
|
|
20728
|
+
process.stdout.write(chalk24.dim(" Loading\u2026"));
|
|
20764
20729
|
const summaries = buildSessions(days);
|
|
20765
20730
|
if (process.stdout.isTTY) {
|
|
20766
20731
|
process.stdout.clearLine(0);
|
|
@@ -20773,8 +20738,8 @@ function registerSessionsCommand(program2) {
|
|
|
20773
20738
|
(s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
|
|
20774
20739
|
);
|
|
20775
20740
|
if (!target) {
|
|
20776
|
-
console.log(
|
|
20777
|
-
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"));
|
|
20778
20743
|
return;
|
|
20779
20744
|
}
|
|
20780
20745
|
renderDetail(target);
|
|
@@ -20787,7 +20752,7 @@ function registerSessionsCommand(program2) {
|
|
|
20787
20752
|
}
|
|
20788
20753
|
|
|
20789
20754
|
// src/cli/commands/skill-pin.ts
|
|
20790
|
-
import
|
|
20755
|
+
import chalk25 from "chalk";
|
|
20791
20756
|
import fs41 from "fs";
|
|
20792
20757
|
import os37 from "os";
|
|
20793
20758
|
import path43 from "path";
|
|
@@ -20807,29 +20772,29 @@ function registerSkillPinCommand(program2) {
|
|
|
20807
20772
|
const result = readSkillPinsSafe();
|
|
20808
20773
|
if (!result.ok) {
|
|
20809
20774
|
if (result.reason === "missing") {
|
|
20810
|
-
console.log(
|
|
20775
|
+
console.log(chalk25.gray("\nNo skill roots are pinned yet."));
|
|
20811
20776
|
console.log(
|
|
20812
|
-
|
|
20777
|
+
chalk25.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
20813
20778
|
);
|
|
20814
20779
|
return;
|
|
20815
20780
|
}
|
|
20816
|
-
console.error(
|
|
20781
|
+
console.error(chalk25.red(`
|
|
20817
20782
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
20818
|
-
console.error(
|
|
20783
|
+
console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
|
|
20819
20784
|
process.exit(1);
|
|
20820
20785
|
}
|
|
20821
20786
|
const entries = Object.entries(result.pins.roots);
|
|
20822
20787
|
if (entries.length === 0) {
|
|
20823
|
-
console.log(
|
|
20788
|
+
console.log(chalk25.gray("\nNo skill roots are pinned yet.\n"));
|
|
20824
20789
|
return;
|
|
20825
20790
|
}
|
|
20826
|
-
console.log(
|
|
20791
|
+
console.log(chalk25.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
20827
20792
|
for (const [key, entry] of entries) {
|
|
20828
|
-
const missing = entry.exists ? "" :
|
|
20829
|
-
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}`);
|
|
20830
20795
|
console.log(` Files (${entry.fileCount})`);
|
|
20831
|
-
console.log(` Hash: ${
|
|
20832
|
-
console.log(` Pinned: ${
|
|
20796
|
+
console.log(` Hash: ${chalk25.gray(entry.contentHash.slice(0, 16))}...`);
|
|
20797
|
+
console.log(` Pinned: ${chalk25.gray(entry.pinnedAt)}
|
|
20833
20798
|
`);
|
|
20834
20799
|
}
|
|
20835
20800
|
});
|
|
@@ -20838,39 +20803,39 @@ function registerSkillPinCommand(program2) {
|
|
|
20838
20803
|
try {
|
|
20839
20804
|
pins = readSkillPins();
|
|
20840
20805
|
} catch {
|
|
20841
|
-
console.error(
|
|
20842
|
-
console.error(
|
|
20806
|
+
console.error(chalk25.red("\n\u274C Pin file is corrupt."));
|
|
20807
|
+
console.error(chalk25.yellow(" Run: node9 skill pin reset\n"));
|
|
20843
20808
|
process.exit(1);
|
|
20844
20809
|
}
|
|
20845
20810
|
if (!pins.roots[rootKey]) {
|
|
20846
|
-
console.error(
|
|
20811
|
+
console.error(chalk25.red(`
|
|
20847
20812
|
\u274C No pin found for root key "${rootKey}"
|
|
20848
20813
|
`));
|
|
20849
|
-
console.error(`Run ${
|
|
20814
|
+
console.error(`Run ${chalk25.cyan("node9 skill pin list")} to see pinned roots.
|
|
20850
20815
|
`);
|
|
20851
20816
|
process.exit(1);
|
|
20852
20817
|
}
|
|
20853
20818
|
const rootPath = pins.roots[rootKey].rootPath;
|
|
20854
20819
|
removePin(rootKey);
|
|
20855
20820
|
wipeSkillSessions();
|
|
20856
|
-
console.log(
|
|
20857
|
-
\u{1F513} Pin removed for ${
|
|
20858
|
-
console.log(
|
|
20859
|
-
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"));
|
|
20860
20825
|
});
|
|
20861
20826
|
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
20862
20827
|
const result = readSkillPinsSafe();
|
|
20863
20828
|
if (!result.ok && result.reason === "missing") {
|
|
20864
20829
|
wipeSkillSessions();
|
|
20865
|
-
console.log(
|
|
20830
|
+
console.log(chalk25.gray("\nNo pins to clear.\n"));
|
|
20866
20831
|
return;
|
|
20867
20832
|
}
|
|
20868
20833
|
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
20869
20834
|
clearAllPins();
|
|
20870
20835
|
wipeSkillSessions();
|
|
20871
|
-
console.log(
|
|
20836
|
+
console.log(chalk25.green(`
|
|
20872
20837
|
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
20873
|
-
console.log(
|
|
20838
|
+
console.log(chalk25.gray(" Next session will re-pin with current state.\n"));
|
|
20874
20839
|
});
|
|
20875
20840
|
}
|
|
20876
20841
|
|
|
@@ -20878,7 +20843,7 @@ function registerSkillPinCommand(program2) {
|
|
|
20878
20843
|
import fs42 from "fs";
|
|
20879
20844
|
import os38 from "os";
|
|
20880
20845
|
import path44 from "path";
|
|
20881
|
-
import
|
|
20846
|
+
import chalk26 from "chalk";
|
|
20882
20847
|
var DECISIONS_FILE2 = path44.join(os38.homedir(), ".node9", "decisions.json");
|
|
20883
20848
|
function readDecisions() {
|
|
20884
20849
|
try {
|
|
@@ -20907,55 +20872,55 @@ function registerDecisionsCommand(program2) {
|
|
|
20907
20872
|
const decisions = readDecisions();
|
|
20908
20873
|
const entries = Object.entries(decisions);
|
|
20909
20874
|
if (entries.length === 0) {
|
|
20910
|
-
console.log(
|
|
20875
|
+
console.log(chalk26.gray(" No persistent decisions stored."));
|
|
20911
20876
|
console.log(
|
|
20912
|
-
|
|
20913
|
-
`) +
|
|
20877
|
+
chalk26.gray(` File: ${DECISIONS_FILE2}
|
|
20878
|
+
`) + chalk26.gray(' Decisions are written when you click "Always Allow" or')
|
|
20914
20879
|
);
|
|
20915
|
-
console.log(
|
|
20880
|
+
console.log(chalk26.gray(' "Always Deny" in node9 tail or the native popup.'));
|
|
20916
20881
|
return;
|
|
20917
20882
|
}
|
|
20918
|
-
console.log(
|
|
20883
|
+
console.log(chalk26.bold(`
|
|
20919
20884
|
Persistent decisions (${entries.length})
|
|
20920
20885
|
`));
|
|
20921
20886
|
const w = Math.max(...entries.map(([k]) => k.length));
|
|
20922
20887
|
for (const [tool, verdict] of entries.sort()) {
|
|
20923
|
-
const colored = verdict === "allow" ?
|
|
20888
|
+
const colored = verdict === "allow" ? chalk26.green(verdict) : chalk26.red(verdict);
|
|
20924
20889
|
console.log(` ${tool.padEnd(w)} ${colored}`);
|
|
20925
20890
|
}
|
|
20926
20891
|
console.log(
|
|
20927
|
-
|
|
20892
|
+
chalk26.gray(`
|
|
20928
20893
|
Stored in ${DECISIONS_FILE2}
|
|
20929
|
-
`) +
|
|
20894
|
+
`) + chalk26.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
|
|
20930
20895
|
);
|
|
20931
20896
|
});
|
|
20932
20897
|
cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
|
|
20933
20898
|
const decisions = readDecisions();
|
|
20934
20899
|
if (!(toolName in decisions)) {
|
|
20935
|
-
console.log(
|
|
20900
|
+
console.log(chalk26.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
|
|
20936
20901
|
process.exitCode = 1;
|
|
20937
20902
|
return;
|
|
20938
20903
|
}
|
|
20939
20904
|
delete decisions[toolName];
|
|
20940
20905
|
writeDecisions(decisions);
|
|
20941
|
-
console.log(
|
|
20906
|
+
console.log(chalk26.green(` \u2713 Cleared persistent decision for "${toolName}".`));
|
|
20942
20907
|
});
|
|
20943
20908
|
cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
|
|
20944
20909
|
const decisions = readDecisions();
|
|
20945
20910
|
const count = Object.keys(decisions).length;
|
|
20946
20911
|
if (count === 0) {
|
|
20947
|
-
console.log(
|
|
20912
|
+
console.log(chalk26.gray(" Nothing to clear \u2014 no persistent decisions stored."));
|
|
20948
20913
|
return;
|
|
20949
20914
|
}
|
|
20950
20915
|
writeDecisions({});
|
|
20951
20916
|
console.log(
|
|
20952
|
-
|
|
20917
|
+
chalk26.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
|
|
20953
20918
|
);
|
|
20954
20919
|
});
|
|
20955
20920
|
}
|
|
20956
20921
|
|
|
20957
20922
|
// src/cli/commands/dlp.ts
|
|
20958
|
-
import
|
|
20923
|
+
import chalk27 from "chalk";
|
|
20959
20924
|
import fs43 from "fs";
|
|
20960
20925
|
import path45 from "path";
|
|
20961
20926
|
import os39 from "os";
|
|
@@ -21010,14 +20975,14 @@ function registerDlpCommand(program2) {
|
|
|
21010
20975
|
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
21011
20976
|
const findings = loadDlpFindings();
|
|
21012
20977
|
if (findings.length === 0) {
|
|
21013
|
-
console.log(
|
|
20978
|
+
console.log(chalk27.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
21014
20979
|
return;
|
|
21015
20980
|
}
|
|
21016
20981
|
const resolved = loadResolved();
|
|
21017
20982
|
for (const e of findings) resolved.add(entryKey(e));
|
|
21018
20983
|
saveResolved(resolved);
|
|
21019
20984
|
console.log(
|
|
21020
|
-
|
|
20985
|
+
chalk27.green(
|
|
21021
20986
|
`
|
|
21022
20987
|
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
21023
20988
|
`
|
|
@@ -21031,47 +20996,47 @@ function registerDlpCommand(program2) {
|
|
|
21031
20996
|
const resolvedCount = findings.length - open.length;
|
|
21032
20997
|
console.log("");
|
|
21033
20998
|
console.log(
|
|
21034
|
-
|
|
20999
|
+
chalk27.bold.cyan("\u{1F510} node9 dlp") + chalk27.dim(" \u2014 secrets found in Claude response text")
|
|
21035
21000
|
);
|
|
21036
21001
|
console.log("");
|
|
21037
21002
|
if (open.length === 0) {
|
|
21038
21003
|
if (resolvedCount > 0) {
|
|
21039
|
-
console.log(
|
|
21004
|
+
console.log(chalk27.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
21040
21005
|
} else {
|
|
21041
21006
|
console.log(
|
|
21042
|
-
|
|
21007
|
+
chalk27.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
21043
21008
|
);
|
|
21044
21009
|
}
|
|
21045
21010
|
console.log("");
|
|
21046
21011
|
return;
|
|
21047
21012
|
}
|
|
21048
21013
|
console.log(
|
|
21049
|
-
|
|
21014
|
+
chalk27.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk27.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
21050
21015
|
);
|
|
21051
21016
|
console.log("");
|
|
21052
21017
|
console.log(
|
|
21053
|
-
|
|
21018
|
+
chalk27.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
21054
21019
|
);
|
|
21055
|
-
console.log(
|
|
21020
|
+
console.log(chalk27.dim(" Rotate each affected key immediately.\n"));
|
|
21056
21021
|
for (const e of open) {
|
|
21057
21022
|
console.log(
|
|
21058
|
-
" " +
|
|
21023
|
+
" " + chalk27.red("\u25CF") + " " + chalk27.white(e.dlpPattern ?? "Secret") + chalk27.dim(" " + fmtDate3(e.ts))
|
|
21059
21024
|
);
|
|
21060
21025
|
if (e.dlpSample) {
|
|
21061
|
-
console.log(" " +
|
|
21026
|
+
console.log(" " + chalk27.dim("Sample: ") + chalk27.yellow(stripAnsi(e.dlpSample)));
|
|
21062
21027
|
}
|
|
21063
21028
|
if (e.project) {
|
|
21064
|
-
console.log(" " +
|
|
21029
|
+
console.log(" " + chalk27.dim("Project: ") + chalk27.dim(stripAnsi(e.project)));
|
|
21065
21030
|
}
|
|
21066
21031
|
console.log("");
|
|
21067
21032
|
}
|
|
21068
|
-
console.log(" " +
|
|
21069
|
-
console.log(" " +
|
|
21033
|
+
console.log(" " + chalk27.bold("Next steps:"));
|
|
21034
|
+
console.log(" " + chalk27.cyan("1.") + " Rotate any exposed keys shown above");
|
|
21070
21035
|
console.log(
|
|
21071
|
-
" " +
|
|
21036
|
+
" " + chalk27.cyan("2.") + " Run " + chalk27.white("node9 dlp resolve") + " to acknowledge"
|
|
21072
21037
|
);
|
|
21073
21038
|
console.log(
|
|
21074
|
-
" " +
|
|
21039
|
+
" " + chalk27.cyan("3.") + " Run " + chalk27.white("node9 report") + " for full audit history"
|
|
21075
21040
|
);
|
|
21076
21041
|
console.log("");
|
|
21077
21042
|
});
|
|
@@ -21079,7 +21044,7 @@ function registerDlpCommand(program2) {
|
|
|
21079
21044
|
|
|
21080
21045
|
// src/cli/commands/mask.ts
|
|
21081
21046
|
init_dlp();
|
|
21082
|
-
import
|
|
21047
|
+
import chalk28 from "chalk";
|
|
21083
21048
|
import fs44 from "fs";
|
|
21084
21049
|
import path46 from "path";
|
|
21085
21050
|
import os40 from "os";
|
|
@@ -21215,12 +21180,12 @@ function registerMaskCommand(program2) {
|
|
|
21215
21180
|
}
|
|
21216
21181
|
}) : allFiles;
|
|
21217
21182
|
if (filtered.length === 0) {
|
|
21218
|
-
console.log(
|
|
21183
|
+
console.log(chalk28.yellow(" No session files found."));
|
|
21219
21184
|
return;
|
|
21220
21185
|
}
|
|
21221
21186
|
console.log("");
|
|
21222
21187
|
if (dryRun) {
|
|
21223
|
-
console.log(
|
|
21188
|
+
console.log(chalk28.dim(" Dry run \u2014 no files will be modified.\n"));
|
|
21224
21189
|
}
|
|
21225
21190
|
let totalFiles = 0;
|
|
21226
21191
|
let totalLines = 0;
|
|
@@ -21236,23 +21201,23 @@ function registerMaskCommand(program2) {
|
|
|
21236
21201
|
});
|
|
21237
21202
|
const verb = dryRun ? "Would redact" : "Redacted";
|
|
21238
21203
|
console.log(
|
|
21239
|
-
" " +
|
|
21204
|
+
" " + chalk28.dim(shortPath.slice(0, 60).padEnd(62)) + chalk28.red(`${verb}: `) + chalk28.yellow(patterns.join(", ")) + chalk28.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
|
|
21240
21205
|
);
|
|
21241
21206
|
}
|
|
21242
21207
|
}
|
|
21243
21208
|
console.log("");
|
|
21244
21209
|
if (totalFiles === 0) {
|
|
21245
|
-
console.log(
|
|
21210
|
+
console.log(chalk28.green(" No secrets found in session history."));
|
|
21246
21211
|
} else {
|
|
21247
21212
|
const verb = dryRun ? "would be modified" : "modified";
|
|
21248
21213
|
console.log(
|
|
21249
|
-
|
|
21214
|
+
chalk28.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk28.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
|
|
21250
21215
|
);
|
|
21251
|
-
console.log(" Patterns: " +
|
|
21216
|
+
console.log(" Patterns: " + chalk28.yellow(totalPatterns.join(", ")));
|
|
21252
21217
|
if (!dryRun) {
|
|
21253
21218
|
console.log("");
|
|
21254
21219
|
console.log(
|
|
21255
|
-
|
|
21220
|
+
chalk28.dim(
|
|
21256
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."
|
|
21257
21222
|
)
|
|
21258
21223
|
);
|
|
@@ -21316,17 +21281,17 @@ program.command("login").argument("<apiKey>").option("--local", "Save key for au
|
|
|
21316
21281
|
fs47.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
21317
21282
|
}
|
|
21318
21283
|
if (options.profile && profileName !== "default") {
|
|
21319
|
-
console.log(
|
|
21320
|
-
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`));
|
|
21321
21286
|
} else if (options.local) {
|
|
21322
|
-
console.log(
|
|
21323
|
-
console.log(
|
|
21287
|
+
console.log(chalk30.green(`\u2705 Privacy mode \u{1F6E1}\uFE0F`));
|
|
21288
|
+
console.log(chalk30.gray(` All decisions stay on this machine.`));
|
|
21324
21289
|
} else {
|
|
21325
|
-
console.log(
|
|
21326
|
-
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.`));
|
|
21327
21292
|
}
|
|
21328
21293
|
});
|
|
21329
|
-
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(
|
|
21330
21295
|
"after",
|
|
21331
21296
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21332
21297
|
).argument(
|
|
@@ -21341,13 +21306,13 @@ program.command("addto").description("Integrate Node9 with an AI agent").addHelp
|
|
|
21341
21306
|
if (target === "vscode") return await setupVSCode();
|
|
21342
21307
|
if (target === "hud") return setupHud();
|
|
21343
21308
|
console.error(
|
|
21344
|
-
|
|
21309
|
+
chalk30.red(
|
|
21345
21310
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21346
21311
|
)
|
|
21347
21312
|
);
|
|
21348
21313
|
process.exit(1);
|
|
21349
21314
|
});
|
|
21350
|
-
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(
|
|
21351
21316
|
"after",
|
|
21352
21317
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21353
21318
|
).argument(
|
|
@@ -21355,17 +21320,17 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
21355
21320
|
"The agent to protect: claude | gemini | cursor | codex | windsurf | vscode | hud"
|
|
21356
21321
|
).action(async (target) => {
|
|
21357
21322
|
if (!target) {
|
|
21358
|
-
console.log(
|
|
21359
|
-
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");
|
|
21360
21325
|
console.log(" Targets:");
|
|
21361
|
-
console.log(" " +
|
|
21362
|
-
console.log(" " +
|
|
21363
|
-
console.log(" " +
|
|
21364
|
-
console.log(" " +
|
|
21365
|
-
console.log(" " +
|
|
21366
|
-
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)");
|
|
21367
21332
|
process.stdout.write(
|
|
21368
|
-
" " +
|
|
21333
|
+
" " + chalk30.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
21369
21334
|
);
|
|
21370
21335
|
console.log("");
|
|
21371
21336
|
return;
|
|
@@ -21379,13 +21344,13 @@ program.command("setup").description('Alias for "addto" \u2014 integrate Node9 w
|
|
|
21379
21344
|
if (t === "vscode") return await setupVSCode();
|
|
21380
21345
|
if (t === "hud") return setupHud();
|
|
21381
21346
|
console.error(
|
|
21382
|
-
|
|
21347
|
+
chalk30.red(
|
|
21383
21348
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21384
21349
|
)
|
|
21385
21350
|
);
|
|
21386
21351
|
process.exit(1);
|
|
21387
21352
|
});
|
|
21388
|
-
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(
|
|
21389
21354
|
"after",
|
|
21390
21355
|
"\n Supported targets: claude gemini cursor codex windsurf vscode hud"
|
|
21391
21356
|
).argument(
|
|
@@ -21402,33 +21367,33 @@ program.command("removefrom").description("Remove Node9 hooks from an AI agent c
|
|
|
21402
21367
|
else if (target === "hud") fn = teardownHud;
|
|
21403
21368
|
else {
|
|
21404
21369
|
console.error(
|
|
21405
|
-
|
|
21370
|
+
chalk30.red(
|
|
21406
21371
|
`Unknown target: "${target}". Supported: claude, gemini, cursor, codex, windsurf, vscode, hud`
|
|
21407
21372
|
)
|
|
21408
21373
|
);
|
|
21409
21374
|
process.exit(1);
|
|
21410
21375
|
}
|
|
21411
|
-
console.log(
|
|
21376
|
+
console.log(chalk30.cyan(`
|
|
21412
21377
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${target}...
|
|
21413
21378
|
`));
|
|
21414
21379
|
try {
|
|
21415
21380
|
fn();
|
|
21416
21381
|
} catch (err2) {
|
|
21417
|
-
console.error(
|
|
21382
|
+
console.error(chalk30.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21418
21383
|
process.exit(1);
|
|
21419
21384
|
}
|
|
21420
|
-
console.log(
|
|
21385
|
+
console.log(chalk30.gray("\n Restart the agent for changes to take effect."));
|
|
21421
21386
|
});
|
|
21422
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) => {
|
|
21423
|
-
console.log(
|
|
21424
|
-
console.log(
|
|
21388
|
+
console.log(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
21389
|
+
console.log(chalk30.bold("Stopping daemon..."));
|
|
21425
21390
|
try {
|
|
21426
21391
|
stopDaemon();
|
|
21427
|
-
console.log(
|
|
21392
|
+
console.log(chalk30.green(" \u2705 Daemon stopped"));
|
|
21428
21393
|
} catch {
|
|
21429
|
-
console.log(
|
|
21394
|
+
console.log(chalk30.blue(" \u2139\uFE0F Daemon was not running"));
|
|
21430
21395
|
}
|
|
21431
|
-
console.log(
|
|
21396
|
+
console.log(chalk30.bold("\nRemoving hooks..."));
|
|
21432
21397
|
let teardownFailed = false;
|
|
21433
21398
|
for (const [label, fn] of [
|
|
21434
21399
|
["Claude", teardownClaude],
|
|
@@ -21443,7 +21408,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
21443
21408
|
} catch (err2) {
|
|
21444
21409
|
teardownFailed = true;
|
|
21445
21410
|
console.error(
|
|
21446
|
-
|
|
21411
|
+
chalk30.red(
|
|
21447
21412
|
` \u26A0\uFE0F Failed to remove ${label} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
21448
21413
|
)
|
|
21449
21414
|
);
|
|
@@ -21460,28 +21425,28 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
21460
21425
|
fs47.rmSync(node9Dir, { recursive: true });
|
|
21461
21426
|
if (fs47.existsSync(node9Dir)) {
|
|
21462
21427
|
console.error(
|
|
21463
|
-
|
|
21428
|
+
chalk30.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
21464
21429
|
);
|
|
21465
21430
|
} else {
|
|
21466
|
-
console.log(
|
|
21431
|
+
console.log(chalk30.green("\n \u2705 Deleted ~/.node9/ (config, audit log, credentials)"));
|
|
21467
21432
|
}
|
|
21468
21433
|
} else {
|
|
21469
|
-
console.log(
|
|
21434
|
+
console.log(chalk30.yellow("\n Skipped \u2014 ~/.node9/ was not deleted."));
|
|
21470
21435
|
}
|
|
21471
21436
|
} else {
|
|
21472
|
-
console.log(
|
|
21437
|
+
console.log(chalk30.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
21473
21438
|
}
|
|
21474
21439
|
} else {
|
|
21475
21440
|
console.log(
|
|
21476
|
-
|
|
21441
|
+
chalk30.gray("\n ~/.node9/ kept \u2014 run with --purge to delete config and audit log")
|
|
21477
21442
|
);
|
|
21478
21443
|
}
|
|
21479
21444
|
if (teardownFailed) {
|
|
21480
|
-
console.error(
|
|
21445
|
+
console.error(chalk30.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
21481
21446
|
process.exit(1);
|
|
21482
21447
|
}
|
|
21483
|
-
console.log(
|
|
21484
|
-
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"));
|
|
21485
21450
|
});
|
|
21486
21451
|
registerDoctorCommand(program, version);
|
|
21487
21452
|
program.command("explain").description(
|
|
@@ -21494,7 +21459,7 @@ program.command("explain").description(
|
|
|
21494
21459
|
try {
|
|
21495
21460
|
args = JSON.parse(trimmed);
|
|
21496
21461
|
} catch {
|
|
21497
|
-
console.error(
|
|
21462
|
+
console.error(chalk30.red(`
|
|
21498
21463
|
\u274C Invalid JSON: ${trimmed}
|
|
21499
21464
|
`));
|
|
21500
21465
|
process.exit(1);
|
|
@@ -21505,54 +21470,54 @@ program.command("explain").description(
|
|
|
21505
21470
|
}
|
|
21506
21471
|
const result = await explainPolicy(tool, args);
|
|
21507
21472
|
console.log("");
|
|
21508
|
-
console.log(
|
|
21473
|
+
console.log(chalk30.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
21509
21474
|
console.log("");
|
|
21510
|
-
console.log(` ${
|
|
21475
|
+
console.log(` ${chalk30.bold("Tool:")} ${chalk30.white(result.tool)}`);
|
|
21511
21476
|
if (argsRaw) {
|
|
21512
21477
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
21513
|
-
console.log(` ${
|
|
21478
|
+
console.log(` ${chalk30.bold("Input:")} ${chalk30.gray(preview2)}`);
|
|
21514
21479
|
}
|
|
21515
21480
|
console.log("");
|
|
21516
|
-
console.log(
|
|
21481
|
+
console.log(chalk30.bold("Config Sources (Waterfall):"));
|
|
21517
21482
|
for (const tier of result.waterfall) {
|
|
21518
|
-
const num3 =
|
|
21483
|
+
const num3 = chalk30.gray(` ${tier.tier}.`);
|
|
21519
21484
|
const label = tier.label.padEnd(16);
|
|
21520
21485
|
let statusStr;
|
|
21521
21486
|
if (tier.tier === 1) {
|
|
21522
|
-
statusStr =
|
|
21487
|
+
statusStr = chalk30.gray(tier.note ?? "");
|
|
21523
21488
|
} else if (tier.status === "active") {
|
|
21524
|
-
const loc = tier.path ?
|
|
21525
|
-
const note = tier.note ?
|
|
21526
|
-
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 : "");
|
|
21527
21492
|
} else {
|
|
21528
|
-
statusStr =
|
|
21493
|
+
statusStr = chalk30.gray("\u25CB " + (tier.note ?? "not found"));
|
|
21529
21494
|
}
|
|
21530
|
-
console.log(`${num3} ${
|
|
21495
|
+
console.log(`${num3} ${chalk30.white(label)} ${statusStr}`);
|
|
21531
21496
|
}
|
|
21532
21497
|
console.log("");
|
|
21533
|
-
console.log(
|
|
21498
|
+
console.log(chalk30.bold("Policy Evaluation:"));
|
|
21534
21499
|
for (const step of result.steps) {
|
|
21535
21500
|
const isFinal = step.isFinal;
|
|
21536
21501
|
let icon;
|
|
21537
|
-
if (step.outcome === "allow") icon =
|
|
21538
|
-
else if (step.outcome === "review") icon =
|
|
21539
|
-
else if (step.outcome === "skip") icon =
|
|
21540
|
-
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 ");
|
|
21541
21506
|
const name = step.name.padEnd(18);
|
|
21542
|
-
const nameStr = isFinal ?
|
|
21543
|
-
const detail = isFinal ?
|
|
21544
|
-
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") : "";
|
|
21545
21510
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
21546
21511
|
}
|
|
21547
21512
|
console.log("");
|
|
21548
21513
|
if (result.decision === "allow") {
|
|
21549
|
-
console.log(
|
|
21514
|
+
console.log(chalk30.green.bold(" Decision: \u2705 ALLOW") + chalk30.gray(" \u2014 no approval needed"));
|
|
21550
21515
|
} else {
|
|
21551
21516
|
console.log(
|
|
21552
|
-
|
|
21517
|
+
chalk30.red.bold(" Decision: \u{1F534} REVIEW") + chalk30.gray(" \u2014 human approval required")
|
|
21553
21518
|
);
|
|
21554
21519
|
if (result.blockedByLabel) {
|
|
21555
|
-
console.log(
|
|
21520
|
+
console.log(chalk30.gray(` Reason: ${result.blockedByLabel}`));
|
|
21556
21521
|
}
|
|
21557
21522
|
}
|
|
21558
21523
|
console.log("");
|
|
@@ -21567,7 +21532,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
21567
21532
|
try {
|
|
21568
21533
|
await startTail2(options);
|
|
21569
21534
|
} catch (err2) {
|
|
21570
|
-
console.error(
|
|
21535
|
+
console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21571
21536
|
process.exit(1);
|
|
21572
21537
|
}
|
|
21573
21538
|
});
|
|
@@ -21578,11 +21543,10 @@ program.command("monitor").description("Live interactive dashboard \u2014 activi
|
|
|
21578
21543
|
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
21579
21544
|
await mod.startMonitor();
|
|
21580
21545
|
} catch (err2) {
|
|
21581
|
-
console.error(
|
|
21546
|
+
console.error(chalk30.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
21582
21547
|
process.exit(1);
|
|
21583
21548
|
}
|
|
21584
21549
|
});
|
|
21585
|
-
registerWatchCommand(program);
|
|
21586
21550
|
registerMcpGatewayCommand(program);
|
|
21587
21551
|
registerMcpServerCommand(program);
|
|
21588
21552
|
registerMcpPinCommand(program);
|
|
@@ -21590,7 +21554,7 @@ registerSkillPinCommand(program);
|
|
|
21590
21554
|
registerDecisionsCommand(program);
|
|
21591
21555
|
registerCheckCommand(program);
|
|
21592
21556
|
registerLogCommand(program);
|
|
21593
|
-
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(
|
|
21594
21558
|
"after",
|
|
21595
21559
|
`
|
|
21596
21560
|
Outputs up to 3 lines to stdout, then exits:
|
|
@@ -21634,7 +21598,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
21634
21598
|
const ms = parseDuration(options.duration);
|
|
21635
21599
|
if (ms === null) {
|
|
21636
21600
|
console.error(
|
|
21637
|
-
|
|
21601
|
+
chalk30.red(`
|
|
21638
21602
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
21639
21603
|
`)
|
|
21640
21604
|
);
|
|
@@ -21642,20 +21606,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
21642
21606
|
}
|
|
21643
21607
|
pauseNode9(ms, options.duration);
|
|
21644
21608
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
21645
|
-
console.log(
|
|
21609
|
+
console.log(chalk30.yellow(`
|
|
21646
21610
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
21647
|
-
console.log(
|
|
21648
|
-
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.
|
|
21649
21613
|
`));
|
|
21650
21614
|
});
|
|
21651
21615
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
21652
21616
|
const { paused } = checkPause();
|
|
21653
21617
|
if (!paused) {
|
|
21654
|
-
console.log(
|
|
21618
|
+
console.log(chalk30.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
21655
21619
|
return;
|
|
21656
21620
|
}
|
|
21657
21621
|
resumeNode9();
|
|
21658
|
-
console.log(
|
|
21622
|
+
console.log(chalk30.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
21659
21623
|
});
|
|
21660
21624
|
var HOOK_BASED_AGENTS = {
|
|
21661
21625
|
claude: "claude",
|
|
@@ -21668,15 +21632,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21668
21632
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
21669
21633
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
21670
21634
|
console.error(
|
|
21671
|
-
|
|
21635
|
+
chalk30.yellow(`
|
|
21672
21636
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
21673
21637
|
);
|
|
21674
|
-
console.error(
|
|
21638
|
+
console.error(chalk30.white(`
|
|
21675
21639
|
"${target}" uses its own hook system. Use:`));
|
|
21676
21640
|
console.error(
|
|
21677
|
-
|
|
21641
|
+
chalk30.green(` node9 addto ${target} `) + chalk30.gray("# one-time setup")
|
|
21678
21642
|
);
|
|
21679
|
-
console.error(
|
|
21643
|
+
console.error(chalk30.green(` ${target} `) + chalk30.gray("# run normally"));
|
|
21680
21644
|
process.exit(1);
|
|
21681
21645
|
}
|
|
21682
21646
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -21693,7 +21657,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21693
21657
|
}
|
|
21694
21658
|
);
|
|
21695
21659
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
21696
|
-
console.error(
|
|
21660
|
+
console.error(chalk30.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
21697
21661
|
const daemonReady = await autoStartDaemonAndWait();
|
|
21698
21662
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
21699
21663
|
}
|
|
@@ -21706,12 +21670,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
21706
21670
|
}
|
|
21707
21671
|
if (!result.approved) {
|
|
21708
21672
|
console.error(
|
|
21709
|
-
|
|
21673
|
+
chalk30.red(`
|
|
21710
21674
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
21711
21675
|
);
|
|
21712
21676
|
process.exit(1);
|
|
21713
21677
|
}
|
|
21714
|
-
console.error(
|
|
21678
|
+
console.error(chalk30.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
21715
21679
|
await runProxy(fullCommand);
|
|
21716
21680
|
} else {
|
|
21717
21681
|
program.help();
|