@node9/proxy 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +996 -906
- package/dist/cli.mjs +995 -905
- package/dist/index.js +3 -0
- package/dist/index.mjs +3 -0
- package/dist/scan-ink.mjs +100 -54
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -5422,11 +5422,13 @@ function getConfig(cwd) {
|
|
|
5422
5422
|
}
|
|
5423
5423
|
const shieldOverrides = readShieldOverrides();
|
|
5424
5424
|
const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
|
|
5425
|
+
const appliedShields = [];
|
|
5425
5426
|
const cloudManagedSet = new Set(cloudManagedShields);
|
|
5426
5427
|
for (const shieldName of activeShieldNames) {
|
|
5427
5428
|
const isCloudMandated = cloudManagedSet.has(shieldName);
|
|
5428
5429
|
const shield = isCloudMandated ? BUILTIN_SHIELDS[shieldName] : getShield(shieldName);
|
|
5429
5430
|
if (!shield) continue;
|
|
5431
|
+
appliedShields.push(shieldName);
|
|
5430
5432
|
const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
5431
5433
|
const ruleOverrides = isCloudMandated ? {} : shieldOverrides[shieldName] ?? {};
|
|
5432
5434
|
for (const rule of shield.smartRules) {
|
|
@@ -5448,6 +5450,7 @@ function getConfig(cwd) {
|
|
|
5448
5450
|
if (!existingWords.has(word)) mergedPolicy.dangerousWords.push(word);
|
|
5449
5451
|
}
|
|
5450
5452
|
}
|
|
5453
|
+
mergedPolicy.appliedShields = appliedShields.sort();
|
|
5451
5454
|
const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
5452
5455
|
const cc = mergedPolicy.commandChecks ?? {};
|
|
5453
5456
|
const advisoryKnobKey = (name) => {
|
|
@@ -12299,6 +12302,18 @@ function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
|
12299
12302
|
out.push(chalk3.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
12300
12303
|
return out;
|
|
12301
12304
|
}
|
|
12305
|
+
function originForRule(ruleName, sections, enabled) {
|
|
12306
|
+
for (const section of sections) {
|
|
12307
|
+
if (section.rules.some((r) => r.name === ruleName)) {
|
|
12308
|
+
if (section.sourceType === "default") return "default";
|
|
12309
|
+
if (section.sourceType === "shield") {
|
|
12310
|
+
const key = section.shieldKey ?? section.id;
|
|
12311
|
+
return enabled.has(key) ? `\u2713 shield:${key}` : `needs shield:${key}`;
|
|
12312
|
+
}
|
|
12313
|
+
}
|
|
12314
|
+
}
|
|
12315
|
+
return "";
|
|
12316
|
+
}
|
|
12302
12317
|
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
12303
12318
|
const t = new Date(timestamp).getTime();
|
|
12304
12319
|
if (Number.isNaN(t)) return "?";
|
|
@@ -15080,9 +15095,12 @@ function renderCompactScorecard(input) {
|
|
|
15080
15095
|
chalk5.bold("\u{1F6E1} Node9 Scan") + chalk5.dim(" \xB7 ") + chalk5.white(num(scan.sessions)) + chalk5.dim(" sessions \xB7 ") + chalk5.white(num(scan.totalToolCalls)) + chalk5.dim(" tool calls") + (dateRange ? chalk5.dim(" \xB7 " + dateRange) : "")
|
|
15081
15096
|
);
|
|
15082
15097
|
console.log("");
|
|
15083
|
-
const
|
|
15098
|
+
const exposurePaths = blast.reachable.length;
|
|
15099
|
+
const exposureEnvs = blast.envFindings.length;
|
|
15084
15100
|
console.log(
|
|
15085
|
-
chalk5.bold("
|
|
15101
|
+
chalk5.bold("Exposure: ") + (exposurePaths > 0 ? chalk5.red.bold : chalk5.green.bold)(
|
|
15102
|
+
`${exposurePaths} path${exposurePaths !== 1 ? "s" : ""}` + (exposureEnvs > 0 ? ` + ${exposureEnvs} env var${exposureEnvs !== 1 ? "s" : ""}` : "")
|
|
15103
|
+
) + chalk5.dim(" reachable by the agent")
|
|
15086
15104
|
);
|
|
15087
15105
|
if (scan.totalCostUSD > 0) {
|
|
15088
15106
|
console.log(
|
|
@@ -15107,9 +15125,9 @@ function renderCompactScorecard(input) {
|
|
|
15107
15125
|
const realLoops = scan.loopFindings.filter((l) => l.kind !== "long-iteration");
|
|
15108
15126
|
const longIterations = scan.loopFindings.filter((l) => l.kind === "long-iteration");
|
|
15109
15127
|
if (realLoops.length > 0) {
|
|
15110
|
-
const { wastePct } = computeLoopWaste(realLoops, scan.totalToolCalls);
|
|
15128
|
+
const { wastePct, wastedCalls } = computeLoopWaste(realLoops, scan.totalToolCalls);
|
|
15111
15129
|
const wasteParts = [];
|
|
15112
|
-
if (
|
|
15130
|
+
if (wastedCalls > 0) wasteParts.push(`${wastedCalls} wasted calls (${wastePct}% of all calls)`);
|
|
15113
15131
|
if (summary.loopWastedUSD > 0) wasteParts.push("~" + fmtCost(summary.loopWastedUSD));
|
|
15114
15132
|
const wasteSummary = wasteParts.length ? `(${wasteParts.join(" \xB7 ")})` : "";
|
|
15115
15133
|
console.log(
|
|
@@ -15187,10 +15205,10 @@ function renderNarrativeScorecard(input) {
|
|
|
15187
15205
|
});
|
|
15188
15206
|
}
|
|
15189
15207
|
if (scan.loopFindings.length > 0) {
|
|
15190
|
-
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
15208
|
+
const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
15191
15209
|
const cost = summary.loopWastedUSD > 0 ? `, ~${fmtCost(summary.loopWastedUSD)} wasted` : "";
|
|
15192
15210
|
medium.push({
|
|
15193
|
-
label: `${scan.loopFindings.length} agent loops (${wastePct}% of calls${cost})`,
|
|
15211
|
+
label: `${scan.loopFindings.length} agent loops (${wastedCalls} wasted calls, ${wastePct}% of calls${cost})`,
|
|
15194
15212
|
count: scan.loopFindings.length
|
|
15195
15213
|
});
|
|
15196
15214
|
}
|
|
@@ -15206,9 +15224,12 @@ function renderNarrativeScorecard(input) {
|
|
|
15206
15224
|
chalk5.bold("\u{1F6E1} Node9 Scan") + chalk5.dim(" \xB7 ") + chalk5.white(num(scan.sessions)) + chalk5.dim(" sessions") + (scan.totalCostUSD > 0 ? chalk5.dim(" \xB7 ") + chalk5.bold(fmtCost(scan.totalCostUSD)) + chalk5.dim(" spend") : "") + (dateRange ? chalk5.dim(" \xB7 " + dateRange) : "")
|
|
15207
15225
|
);
|
|
15208
15226
|
console.log("");
|
|
15209
|
-
const
|
|
15227
|
+
const nPaths = blast.reachable.length;
|
|
15228
|
+
const nEnvs = blast.envFindings.length;
|
|
15210
15229
|
console.log(
|
|
15211
|
-
(
|
|
15230
|
+
(nPaths > 0 ? chalk5.red.bold("\u26A0 ") : "") + chalk5.bold("Exposure: ") + (nPaths > 0 ? chalk5.red.bold : chalk5.green.bold)(
|
|
15231
|
+
`${nPaths} path${nPaths !== 1 ? "s" : ""}` + (nEnvs > 0 ? ` + ${nEnvs} env var${nEnvs !== 1 ? "s" : ""}` : "")
|
|
15232
|
+
) + chalk5.dim(" reachable by the agent")
|
|
15212
15233
|
);
|
|
15213
15234
|
console.log("");
|
|
15214
15235
|
if (criticalCount > 0) {
|
|
@@ -15272,6 +15293,7 @@ function shortRule(name, width) {
|
|
|
15272
15293
|
}
|
|
15273
15294
|
function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
15274
15295
|
const { scan, summary, blast, blastExposures, blockedCount, reviewCount } = input;
|
|
15296
|
+
const enabledSet = new Set(input.enabledShields ?? []);
|
|
15275
15297
|
const topLines = [];
|
|
15276
15298
|
if (scan.dlpFindings.length > 0) {
|
|
15277
15299
|
const latest = scan.dlpFindings[0];
|
|
@@ -15298,13 +15320,13 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15298
15320
|
);
|
|
15299
15321
|
}
|
|
15300
15322
|
if (scan.loopFindings.length > 0) {
|
|
15301
|
-
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
15323
|
+
const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
15302
15324
|
const byTool = /* @__PURE__ */ new Map();
|
|
15303
15325
|
for (const f of scan.loopFindings) {
|
|
15304
15326
|
byTool.set(f.toolName, (byTool.get(f.toolName) ?? 0) + Math.max(0, f.count - 1));
|
|
15305
15327
|
}
|
|
15306
15328
|
const top = [...byTool.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
15307
|
-
const wasteSuffix =
|
|
15329
|
+
const wasteSuffix = wastedCalls > 0 ? `, ${wastedCalls} wasted calls \xB7 ${wastePct}% of calls` : "";
|
|
15308
15330
|
const detail = top ? `(${top[0]} dominates${wasteSuffix})` : "";
|
|
15309
15331
|
topLines.push(
|
|
15310
15332
|
mkLine(
|
|
@@ -15315,10 +15337,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15315
15337
|
);
|
|
15316
15338
|
}
|
|
15317
15339
|
if (blastExposures > 0) {
|
|
15318
|
-
const
|
|
15319
|
-
const pjDiscount = PROTECTIVE_SHIELD_DISCOUNTS["project-jail"] ?? 0;
|
|
15320
|
-
const pjBonus = Math.round(exposed2 * pjDiscount);
|
|
15321
|
-
const cta = pjBonus > 0 ? ` \u2192 enable project-jail (+${pjBonus} pts)` : "";
|
|
15340
|
+
const cta = enabledSet.has("project-jail") ? " \u2192 project-jail \u2713 enabled" : " \u2192 enable project-jail";
|
|
15322
15341
|
topLines.push(
|
|
15323
15342
|
mkLine(
|
|
15324
15343
|
["\u{1F52D} ", chalk5.red],
|
|
@@ -15360,7 +15379,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15360
15379
|
const blockedLines = [];
|
|
15361
15380
|
const ruleEntries = topRulesByVerdict(summary.sections, "block", 12);
|
|
15362
15381
|
for (const r of ruleEntries) {
|
|
15363
|
-
const origin = originForRule(r.name, summary.sections);
|
|
15382
|
+
const origin = originForRule(r.name, summary.sections, enabledSet);
|
|
15364
15383
|
blockedLines.push(
|
|
15365
15384
|
mkLine(
|
|
15366
15385
|
["\u2717 ", chalk5.red],
|
|
@@ -15379,7 +15398,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15379
15398
|
const reviewLines = [];
|
|
15380
15399
|
const ruleEntries = topRulesByVerdict(summary.sections, "review", 12);
|
|
15381
15400
|
for (const r of ruleEntries) {
|
|
15382
|
-
const origin = originForRule(r.name, summary.sections);
|
|
15401
|
+
const origin = originForRule(r.name, summary.sections, enabledSet);
|
|
15383
15402
|
reviewLines.push(
|
|
15384
15403
|
mkLine(
|
|
15385
15404
|
// VS-16 (U+FE0F) forces emoji-presentation so string-width
|
|
@@ -15399,7 +15418,7 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15399
15418
|
console.log("");
|
|
15400
15419
|
}
|
|
15401
15420
|
if (scan.loopFindings.length > 0) {
|
|
15402
|
-
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
15421
|
+
const { wastePct, wastedCalls } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
15403
15422
|
const byTool = /* @__PURE__ */ new Map();
|
|
15404
15423
|
let totalRepeats = 0;
|
|
15405
15424
|
for (const f of scan.loopFindings) {
|
|
@@ -15429,8 +15448,8 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15429
15448
|
loopLines.push(mkLine([`\xD7${num(f.count).padEnd(4)} `, chalk5.bold], [target, chalk5.dim]));
|
|
15430
15449
|
}
|
|
15431
15450
|
}
|
|
15432
|
-
const wasteSuffix =
|
|
15433
|
-
const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length}
|
|
15451
|
+
const wasteSuffix = wastedCalls > 0 ? ` \xB7 ${wastedCalls} wasted calls (${wastePct}% of all calls)` : "";
|
|
15452
|
+
const title = `AGENT LOOPS \xB7 ${scan.loopFindings.length} pattern${scan.loopFindings.length !== 1 ? "s" : ""}${wasteSuffix}`;
|
|
15434
15453
|
for (const ln of boxPanel(title, loopLines)) console.log(" " + ln);
|
|
15435
15454
|
console.log("");
|
|
15436
15455
|
}
|
|
@@ -15456,7 +15475,6 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15456
15475
|
console.log("");
|
|
15457
15476
|
}
|
|
15458
15477
|
const shieldImpacts = rollupByShield(summary.sections);
|
|
15459
|
-
const exposed = Math.max(0, 100 - blast.score);
|
|
15460
15478
|
const shieldLines = [];
|
|
15461
15479
|
const ranked = [...shieldImpacts].sort((a, b) => {
|
|
15462
15480
|
const aDiscount = PROTECTIVE_SHIELD_DISCOUNTS[a.shieldName] ?? 0;
|
|
@@ -15467,16 +15485,16 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15467
15485
|
for (const impact of ranked) {
|
|
15468
15486
|
if (impact.totalCatches === 0) continue;
|
|
15469
15487
|
const discount = PROTECTIVE_SHIELD_DISCOUNTS[impact.shieldName] ?? 0;
|
|
15470
|
-
const
|
|
15488
|
+
const isOn = enabledSet.has(impact.shieldName);
|
|
15471
15489
|
const icon = discount > 0 ? "\u{1F6E1}\uFE0F " : "\u2610 ";
|
|
15472
|
-
const wouldCatch = `
|
|
15473
|
-
const
|
|
15490
|
+
const wouldCatch = `catches ${impact.totalCatches} op${impact.totalCatches !== 1 ? "s" : ""}`;
|
|
15491
|
+
const suffix = isOn ? "\u2713 enabled" : discount > 0 ? "\u2192 blocks these reads in-path" : "";
|
|
15474
15492
|
shieldLines.push(
|
|
15475
15493
|
mkLine(
|
|
15476
15494
|
[icon, discount > 0 ? chalk5.cyan : chalk5.dim],
|
|
15477
15495
|
[impact.shieldName.padEnd(14), chalk5.bold],
|
|
15478
15496
|
[wouldCatch.padEnd(22), chalk5.dim],
|
|
15479
|
-
[
|
|
15497
|
+
[suffix, isOn || discount > 0 ? chalk5.green.bold : chalk5.dim]
|
|
15480
15498
|
)
|
|
15481
15499
|
);
|
|
15482
15500
|
if (impact.topRuleLabels.length > 0) {
|
|
@@ -15487,36 +15505,43 @@ function renderPanelScorecard(input, now = /* @__PURE__ */ new Date()) {
|
|
|
15487
15505
|
const hitShieldSet = new Set(
|
|
15488
15506
|
shieldImpacts.filter((i) => i.totalCatches > 0).map((i) => i.shieldName)
|
|
15489
15507
|
);
|
|
15490
|
-
const
|
|
15508
|
+
const zeroHitAll = Object.keys(BUILTIN_SHIELDS).filter((name) => !hitShieldSet.has(name)).sort();
|
|
15509
|
+
const zeroHitBuiltins = zeroHitAll.filter((name) => !enabledSet.has(name));
|
|
15510
|
+
const zeroHitEnabled = zeroHitAll.filter((name) => enabledSet.has(name));
|
|
15511
|
+
if (zeroHitEnabled.length > 0) {
|
|
15512
|
+
shieldLines.push(mkLine([""]));
|
|
15513
|
+
shieldLines.push(
|
|
15514
|
+
mkLine([
|
|
15515
|
+
`\u2713 active: ${zeroHitEnabled.join(" \xB7 ")} (no hits in this history)`,
|
|
15516
|
+
chalk5.green.dim
|
|
15517
|
+
])
|
|
15518
|
+
);
|
|
15519
|
+
}
|
|
15491
15520
|
if (zeroHitBuiltins.length > 0) {
|
|
15492
15521
|
shieldLines.push(mkLine([""]));
|
|
15493
15522
|
shieldLines.push(mkLine([zeroHitBuiltins.join(" \xB7 "), chalk5.dim]));
|
|
15494
15523
|
shieldLines.push(mkLine([" no hits in your history \u2014 install proactively", chalk5.dim]));
|
|
15495
15524
|
}
|
|
15496
15525
|
const topRec = ranked.find(
|
|
15497
|
-
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0
|
|
15526
|
+
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0 && !enabledSet.has(r.shieldName)
|
|
15527
|
+
);
|
|
15528
|
+
const allCovered = !topRec && ranked.some(
|
|
15529
|
+
(r) => r.totalCatches > 0 && (PROTECTIVE_SHIELD_DISCOUNTS[r.shieldName] ?? 0) > 0 && enabledSet.has(r.shieldName)
|
|
15498
15530
|
);
|
|
15499
15531
|
if (topRec) {
|
|
15500
|
-
const
|
|
15501
|
-
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 +${bonus} pts)`;
|
|
15532
|
+
const cta = `\u2192 node9 shield enable ${topRec.shieldName} (start here \u2014 widest coverage)`;
|
|
15502
15533
|
shieldLines.push(mkLine([""]));
|
|
15503
15534
|
shieldLines.push(mkLine([cta, chalk5.cyan]));
|
|
15535
|
+
} else if (allCovered) {
|
|
15536
|
+
shieldLines.push(mkLine([""]));
|
|
15537
|
+
shieldLines.push(mkLine(["\u2713 recommended shields are enabled", chalk5.green.bold]));
|
|
15504
15538
|
}
|
|
15505
15539
|
if (shieldLines.length > 0) {
|
|
15506
|
-
const title = "SHIELDS \xB7 install node9 + enable these to catch what we found";
|
|
15540
|
+
const title = enabledSet.size > 0 ? "SHIELDS \xB7 your coverage vs what we found" : "SHIELDS \xB7 install node9 + enable these to catch what we found";
|
|
15507
15541
|
for (const ln of boxPanel(title, shieldLines)) console.log(" " + ln);
|
|
15508
15542
|
console.log("");
|
|
15509
15543
|
}
|
|
15510
15544
|
}
|
|
15511
|
-
function originForRule(ruleName, sections) {
|
|
15512
|
-
for (const section of sections) {
|
|
15513
|
-
if (section.rules.some((r) => r.name === ruleName)) {
|
|
15514
|
-
if (section.sourceType === "default") return "default";
|
|
15515
|
-
if (section.sourceType === "shield") return `needs shield:${section.shieldKey ?? section.id}`;
|
|
15516
|
-
}
|
|
15517
|
-
}
|
|
15518
|
-
return "";
|
|
15519
|
-
}
|
|
15520
15545
|
function registerScanCommand(program2) {
|
|
15521
15546
|
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(
|
|
15522
15547
|
"--json",
|
|
@@ -15551,6 +15576,8 @@ function registerScanCommand(program2) {
|
|
|
15551
15576
|
return d;
|
|
15552
15577
|
})();
|
|
15553
15578
|
const isWired = getAgentsStatus().some((a) => a.wired);
|
|
15579
|
+
const enabledShields = getConfig().policy.appliedShields ?? [];
|
|
15580
|
+
const enabledShieldSet = new Set(enabledShields);
|
|
15554
15581
|
const screenshotMode = options.compact || options.narrative;
|
|
15555
15582
|
const useInk = !options.classic && !drillDown;
|
|
15556
15583
|
const quiet = screenshotMode || options.json || useInk;
|
|
@@ -15701,7 +15728,8 @@ function registerScanCommand(program2) {
|
|
|
15701
15728
|
blast,
|
|
15702
15729
|
blastExposures,
|
|
15703
15730
|
blockedCount,
|
|
15704
|
-
reviewCount
|
|
15731
|
+
reviewCount,
|
|
15732
|
+
enabledShields
|
|
15705
15733
|
});
|
|
15706
15734
|
return;
|
|
15707
15735
|
}
|
|
@@ -15712,7 +15740,8 @@ function registerScanCommand(program2) {
|
|
|
15712
15740
|
blast,
|
|
15713
15741
|
blastExposures,
|
|
15714
15742
|
blockedCount,
|
|
15715
|
-
reviewCount
|
|
15743
|
+
reviewCount,
|
|
15744
|
+
enabledShields
|
|
15716
15745
|
});
|
|
15717
15746
|
return;
|
|
15718
15747
|
}
|
|
@@ -15755,7 +15784,7 @@ function registerScanCommand(program2) {
|
|
|
15755
15784
|
}
|
|
15756
15785
|
if (scan.loopFindings.length > 0) {
|
|
15757
15786
|
const { wastePct } = computeLoopWaste(scan.loopFindings, scan.totalToolCalls);
|
|
15758
|
-
const wasteSuffix = wastePct > 0 ? chalk5.dim(` (${wastePct}%
|
|
15787
|
+
const wasteSuffix = wastePct > 0 ? chalk5.dim(` (${wastePct}% of calls)`) : "";
|
|
15759
15788
|
cardParts.push(
|
|
15760
15789
|
chalk5.yellow("\u{1F501} ") + chalk5.yellow.bold(String(scan.loopFindings.length)) + chalk5.dim(" loops") + wasteSuffix
|
|
15761
15790
|
);
|
|
@@ -15802,7 +15831,8 @@ function registerScanCommand(program2) {
|
|
|
15802
15831
|
blast,
|
|
15803
15832
|
blastExposures,
|
|
15804
15833
|
blockedCount,
|
|
15805
|
-
reviewCount
|
|
15834
|
+
reviewCount,
|
|
15835
|
+
enabledShields
|
|
15806
15836
|
},
|
|
15807
15837
|
rangeLabel2
|
|
15808
15838
|
);
|
|
@@ -15814,7 +15844,8 @@ function registerScanCommand(program2) {
|
|
|
15814
15844
|
blast,
|
|
15815
15845
|
blastExposures,
|
|
15816
15846
|
blockedCount,
|
|
15817
|
-
reviewCount
|
|
15847
|
+
reviewCount,
|
|
15848
|
+
enabledShields
|
|
15818
15849
|
});
|
|
15819
15850
|
}
|
|
15820
15851
|
const cta = isWired ? "\u2705 node9 is active" : "\u2192 install node9 to enable protection";
|
|
@@ -15924,7 +15955,7 @@ function registerScanCommand(program2) {
|
|
|
15924
15955
|
for (const section of summary.sections) {
|
|
15925
15956
|
const reviewRules = section.rules.filter((r) => r.verdict !== "block");
|
|
15926
15957
|
if (reviewRules.length === 0) continue;
|
|
15927
|
-
const enableHint = section.shieldKey ? chalk5.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
|
|
15958
|
+
const enableHint = section.shieldKey ? enabledShieldSet.has(section.shieldKey) ? chalk5.green(` \u2713 ${section.shieldKey} enabled`) : chalk5.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
|
|
15928
15959
|
console.log(" " + chalk5.dim("\u2500".repeat(70)));
|
|
15929
15960
|
console.log(
|
|
15930
15961
|
" " + chalk5.bold(section.label) + (section.subtitle ? chalk5.dim(` \xB7 ${section.subtitle}`) : "") + " " + chalk5.yellow(`${section.reviewCount} review`) + enableHint
|
|
@@ -15934,10 +15965,20 @@ function registerScanCommand(program2) {
|
|
|
15934
15965
|
}
|
|
15935
15966
|
console.log("");
|
|
15936
15967
|
}
|
|
15937
|
-
const
|
|
15968
|
+
const hitShieldIds = new Set(
|
|
15938
15969
|
summary.sections.filter((s) => s.sourceType === "shield" && s.shieldKey).map((s) => s.shieldKey)
|
|
15939
15970
|
);
|
|
15940
|
-
const
|
|
15971
|
+
const noHitShields = Object.keys(SHIELDS).filter((n) => !hitShieldIds.has(n)).sort();
|
|
15972
|
+
const quietEnabled = noHitShields.filter((n) => enabledShieldSet.has(n));
|
|
15973
|
+
const emptyShields = noHitShields.filter((n) => !enabledShieldSet.has(n));
|
|
15974
|
+
if (quietEnabled.length > 0) {
|
|
15975
|
+
console.log(" " + chalk5.dim("\u2500".repeat(70)));
|
|
15976
|
+
console.log(
|
|
15977
|
+
" " + chalk5.bold("\u{1F6E1} Active Shields") + chalk5.dim(" \xB7 enabled, no hits in this history")
|
|
15978
|
+
);
|
|
15979
|
+
console.log(" " + chalk5.green("\u2713 ") + chalk5.dim(quietEnabled.join(" \xB7 ")));
|
|
15980
|
+
console.log("");
|
|
15981
|
+
}
|
|
15941
15982
|
if (emptyShields.length > 0) {
|
|
15942
15983
|
console.log(" " + chalk5.dim("\u2500".repeat(70)));
|
|
15943
15984
|
console.log(
|
|
@@ -18064,6 +18105,9 @@ async function annotateCoverage(findings, ctx) {
|
|
|
18064
18105
|
const verdicts = probe.paths.map((p) => scanFilePath(p)?.severity ?? null);
|
|
18065
18106
|
if (verdicts.length === 0 || verdicts.some((v) => v === null)) {
|
|
18066
18107
|
f.coverage = coverageFromVerdict("allow", env);
|
|
18108
|
+
if ((config.policy.appliedShields ?? []).includes("project-jail") && f.fix?.includes("shield enable project-jail")) {
|
|
18109
|
+
f.fix = "project-jail is already enabled but does not cover the file(s) listed here. Fix it now: run `node9 jail add <file>` for each file above.";
|
|
18110
|
+
}
|
|
18067
18111
|
} else {
|
|
18068
18112
|
const worst = verdicts.some((v) => v === "review") ? "review" : "block";
|
|
18069
18113
|
f.coverage = coverageFromVerdict(worst, env, "node9 DLP");
|
|
@@ -46549,7 +46593,7 @@ __export(tail_exports, {
|
|
|
46549
46593
|
startTail: () => startTail
|
|
46550
46594
|
});
|
|
46551
46595
|
import http5 from "http";
|
|
46552
|
-
import
|
|
46596
|
+
import chalk40 from "chalk";
|
|
46553
46597
|
import fs73 from "fs";
|
|
46554
46598
|
import os61 from "os";
|
|
46555
46599
|
import path68 from "path";
|
|
@@ -46632,10 +46676,10 @@ function readSessionUsage() {
|
|
|
46632
46676
|
}
|
|
46633
46677
|
}
|
|
46634
46678
|
function formatContextStat(stat) {
|
|
46635
|
-
const pctColor = stat.fillPct >= 80 ?
|
|
46679
|
+
const pctColor = stat.fillPct >= 80 ? chalk40.red : stat.fillPct >= 50 ? chalk40.yellow : chalk40.cyan;
|
|
46636
46680
|
const k = (n) => `${Math.round(n / 1e3)}k`;
|
|
46637
46681
|
const modelShort = stat.model.replace(/@.*$/, "").replace(/-\d{8}$/, "").replace(/^claude-/, "");
|
|
46638
|
-
return
|
|
46682
|
+
return chalk40.dim("ctx: ") + pctColor(`${stat.fillPct}%`) + chalk40.dim(
|
|
46639
46683
|
` (${k(stat.inputTokens)}/${k(getModelContextLimit(stat.model))} out ${k(stat.outputTokens)} \xB7 ${modelShort})`
|
|
46640
46684
|
);
|
|
46641
46685
|
}
|
|
@@ -46658,11 +46702,11 @@ function agentLabel(agent, mcpServer, sessionId) {
|
|
|
46658
46702
|
const tag = sessionTag(sessionId);
|
|
46659
46703
|
const tagSuffix = tag ? `\xB7${tag}` : "";
|
|
46660
46704
|
if (!agent || agent === "Terminal") {
|
|
46661
|
-
return mcpServer ?
|
|
46705
|
+
return mcpServer ? chalk40.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
46662
46706
|
}
|
|
46663
46707
|
const short = agent === "Claude Code" ? "Claude" : agent === "Gemini CLI" ? "Gemini" : agent === "Unknown Agent" ? "" : agent.split(" ")[0];
|
|
46664
|
-
if (!short) return mcpServer ?
|
|
46665
|
-
return mcpServer ?
|
|
46708
|
+
if (!short) return mcpServer ? chalk40.dim(`[\u2192 ${mcpServer}] `) : "";
|
|
46709
|
+
return mcpServer ? chalk40.dim(`[${short}${tagSuffix} \u2192 ${mcpServer}] `) : chalk40.dim(`[${short}${tagSuffix}] `);
|
|
46666
46710
|
}
|
|
46667
46711
|
function formatBase(activity) {
|
|
46668
46712
|
const time = new Date(activity.ts).toLocaleTimeString([], { hour12: false });
|
|
@@ -46670,20 +46714,20 @@ function formatBase(activity) {
|
|
|
46670
46714
|
const toolName = activity.tool.slice(0, 16).padEnd(16);
|
|
46671
46715
|
const argsStr = JSON.stringify(activity.args ?? {}).replace(/\s+/g, " ").replaceAll(os61.homedir(), "~");
|
|
46672
46716
|
const argsPreview = argsStr.length > 70 ? argsStr.slice(0, 70) + "\u2026" : argsStr;
|
|
46673
|
-
return `${
|
|
46717
|
+
return `${chalk40.gray(time)} ${icon} ${agentLabel(activity.agent, activity.mcpServer, activity.sessionId)}${chalk40.white.bold(toolName)} ${chalk40.dim(argsPreview)}`;
|
|
46674
46718
|
}
|
|
46675
46719
|
function renderResult(activity, result) {
|
|
46676
46720
|
const base = formatBase(activity);
|
|
46677
46721
|
let status;
|
|
46678
46722
|
if (result.status === "allow") {
|
|
46679
|
-
status =
|
|
46723
|
+
status = chalk40.green("\u2713 ALLOW");
|
|
46680
46724
|
} else if (result.status === "dlp") {
|
|
46681
|
-
status =
|
|
46725
|
+
status = chalk40.bgRed.white.bold(" \u{1F6E1}\uFE0F DLP ");
|
|
46682
46726
|
} else {
|
|
46683
|
-
status =
|
|
46727
|
+
status = chalk40.red("\u2717 BLOCK");
|
|
46684
46728
|
}
|
|
46685
46729
|
const cost = result.costEstimate ?? activity.costEstimate;
|
|
46686
|
-
const costSuffix = cost == null ? "" :
|
|
46730
|
+
const costSuffix = cost == null ? "" : chalk40.dim(` ~$${cost >= 1e-3 ? cost.toFixed(3) : "0.000"}`);
|
|
46687
46731
|
if (process.stdout.isTTY) {
|
|
46688
46732
|
if (pendingShownForId === activity.id && pendingWrappedLines > 1) {
|
|
46689
46733
|
readline5.moveCursor(process.stdout, 0, -(pendingWrappedLines - 1));
|
|
@@ -46700,7 +46744,7 @@ function renderResult(activity, result) {
|
|
|
46700
46744
|
}
|
|
46701
46745
|
function renderPending(activity) {
|
|
46702
46746
|
if (!process.stdout.isTTY) return;
|
|
46703
|
-
const line = `${formatBase(activity)} ${
|
|
46747
|
+
const line = `${formatBase(activity)} ${chalk40.yellow("\u25CF \u2026")}`;
|
|
46704
46748
|
pendingShownForId = activity.id;
|
|
46705
46749
|
pendingWrappedLines = wrappedLineCount(line);
|
|
46706
46750
|
process.stdout.write(`${line}\r`);
|
|
@@ -46712,7 +46756,7 @@ async function ensureDaemon() {
|
|
|
46712
46756
|
const { port } = JSON.parse(fs73.readFileSync(PID_FILE, "utf-8"));
|
|
46713
46757
|
pidPort = port;
|
|
46714
46758
|
} catch {
|
|
46715
|
-
console.error(
|
|
46759
|
+
console.error(chalk40.dim("\u26A0\uFE0F Could not read PID file; falling back to default port."));
|
|
46716
46760
|
}
|
|
46717
46761
|
}
|
|
46718
46762
|
const checkPort = pidPort ?? DAEMON_PORT;
|
|
@@ -46723,7 +46767,7 @@ async function ensureDaemon() {
|
|
|
46723
46767
|
if (res.ok) return checkPort;
|
|
46724
46768
|
} catch {
|
|
46725
46769
|
}
|
|
46726
|
-
console.log(
|
|
46770
|
+
console.log(chalk40.dim("\u{1F6E1}\uFE0F Starting Node9 daemon..."));
|
|
46727
46771
|
const startupFd = openStartupLogFd();
|
|
46728
46772
|
recordStartupState("starting");
|
|
46729
46773
|
const child = spawn7(process.execPath, [process.argv[1], "daemon"], {
|
|
@@ -46749,7 +46793,7 @@ async function ensureDaemon() {
|
|
|
46749
46793
|
} catch {
|
|
46750
46794
|
}
|
|
46751
46795
|
}
|
|
46752
|
-
console.error(
|
|
46796
|
+
console.error(chalk40.red("\u274C Daemon failed to start. Try: node9 daemon start"));
|
|
46753
46797
|
process.exit(1);
|
|
46754
46798
|
}
|
|
46755
46799
|
function postDecisionHttp(id, decision, authToken, port, opts) {
|
|
@@ -46818,7 +46862,7 @@ function buildCardLines(req, localCount = 0) {
|
|
|
46818
46862
|
const severityIcon = isBlock ? `${RED}\u{1F6D1}` : `${YELLOW}\u26A0 `;
|
|
46819
46863
|
const rawDesc = req.riskMetadata?.ruleDescription ?? "";
|
|
46820
46864
|
const description = rawDesc ? cleanReason(rawDesc) : "";
|
|
46821
|
-
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET}${
|
|
46865
|
+
const agentSuffix = req.agent && req.agent !== "Terminal" ? ` ${RESET}${chalk40.dim(`(${req.agent})`)}` : "";
|
|
46822
46866
|
const lines = [
|
|
46823
46867
|
``,
|
|
46824
46868
|
`${BOLD}${CYAN}\u2554\u2550\u2550 Node9 Approval Required \u2550\u2550\u2557${RESET}`,
|
|
@@ -46887,7 +46931,7 @@ function approverStatusLine() {
|
|
|
46887
46931
|
const a = readApproversFromDisk();
|
|
46888
46932
|
const fmt = (label2, key) => {
|
|
46889
46933
|
const on = a[key] !== false;
|
|
46890
|
-
return `[${key[0]}]${label2.slice(1)} ${on ?
|
|
46934
|
+
return `[${key[0]}]${label2.slice(1)} ${on ? chalk40.green("\u2713") : chalk40.dim("\u2717")}`;
|
|
46891
46935
|
};
|
|
46892
46936
|
return `${fmt("native", "native")} ${fmt("cloud", "cloud")} ${fmt("terminal", "terminal")}`;
|
|
46893
46937
|
}
|
|
@@ -46932,7 +46976,7 @@ async function startTail(options = {}) {
|
|
|
46932
46976
|
req2.end();
|
|
46933
46977
|
});
|
|
46934
46978
|
if (result.ok) {
|
|
46935
|
-
console.log(
|
|
46979
|
+
console.log(chalk40.green("\u2713 Flight Recorder buffer cleared."));
|
|
46936
46980
|
} else if (result.code === "ECONNREFUSED") {
|
|
46937
46981
|
throw new Error("Daemon is not running. Start it with: node9 daemon start");
|
|
46938
46982
|
} else if (result.code === "ETIMEDOUT") {
|
|
@@ -46978,7 +47022,7 @@ async function startTail(options = {}) {
|
|
|
46978
47022
|
const channel = name === "n" ? "native" : name === "c" ? "cloud" : name === "t" ? "terminal" : null;
|
|
46979
47023
|
if (channel) {
|
|
46980
47024
|
toggleApprover(channel);
|
|
46981
|
-
console.log(
|
|
47025
|
+
console.log(chalk40.dim(` Approvers: ${approverStatusLine()}`));
|
|
46982
47026
|
}
|
|
46983
47027
|
};
|
|
46984
47028
|
process.stdin.on("keypress", idleKeypressHandler);
|
|
@@ -47044,7 +47088,7 @@ async function startTail(options = {}) {
|
|
|
47044
47088
|
localAllowCounts.get(req2.toolName) ?? 0
|
|
47045
47089
|
)
|
|
47046
47090
|
);
|
|
47047
|
-
const decisionStamp = action === "always-allow" ?
|
|
47091
|
+
const decisionStamp = action === "always-allow" ? chalk40.yellow("\u2605 ALWAYS ALLOW") : action === "trust" ? chalk40.cyan("\u23F1 TRUST 30m") : action === "allow" ? chalk40.green("\u2713 ALLOWED") : action === "redirect" ? chalk40.yellow("\u21A9 REDIRECT AI") : chalk40.red("\u2717 DENIED");
|
|
47048
47092
|
stampedLines.push(` ${BOLD}\u2192${RESET} ${decisionStamp} ${GRAY}(terminal)${RESET}`, ``);
|
|
47049
47093
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
47050
47094
|
process.stdout.write(SHOW_CURSOR);
|
|
@@ -47095,7 +47139,7 @@ async function startTail(options = {}) {
|
|
|
47095
47139
|
);
|
|
47096
47140
|
const stampedLines = buildCardLines(req2, priorCount);
|
|
47097
47141
|
if (externalDecision) {
|
|
47098
|
-
const source = externalDecision === "allow" ?
|
|
47142
|
+
const source = externalDecision === "allow" ? chalk40.green("\u2713 ALLOWED") : chalk40.red("\u2717 DENIED");
|
|
47099
47143
|
stampedLines.push(` ${BOLD}\u2192${RESET} ${source} ${GRAY}(external)${RESET}`, ``);
|
|
47100
47144
|
}
|
|
47101
47145
|
for (const line of stampedLines) process.stdout.write(line + "\n");
|
|
@@ -47143,25 +47187,25 @@ async function startTail(options = {}) {
|
|
|
47143
47187
|
if (unackedDlp > 0) {
|
|
47144
47188
|
console.log("");
|
|
47145
47189
|
console.log(
|
|
47146
|
-
|
|
47190
|
+
chalk40.bgRed.white.bold(
|
|
47147
47191
|
` \u26A0\uFE0F DLP ALERT: ${unackedDlp} secret${unackedDlp !== 1 ? "s" : ""} found in Claude response text \u2014 run: node9 dlp `
|
|
47148
47192
|
)
|
|
47149
47193
|
);
|
|
47150
47194
|
}
|
|
47151
47195
|
} catch {
|
|
47152
47196
|
}
|
|
47153
|
-
console.log(
|
|
47197
|
+
console.log(chalk40.cyan.bold(`
|
|
47154
47198
|
\u{1F6F0}\uFE0F Node9 tail`));
|
|
47155
47199
|
if (canApprove) {
|
|
47156
|
-
console.log(
|
|
47157
|
-
console.log(
|
|
47200
|
+
console.log(chalk40.dim("Card: [\u21B5/y] Allow [n] Deny [a] Always [t] Trust 30m"));
|
|
47201
|
+
console.log(chalk40.dim(`Approvers (toggle): ${approverStatusLine()} [q] quit`));
|
|
47158
47202
|
}
|
|
47159
47203
|
const ctxStat = readSessionUsage();
|
|
47160
47204
|
if (ctxStat) console.log(" " + formatContextStat(ctxStat));
|
|
47161
47205
|
if (options.history) {
|
|
47162
|
-
console.log(
|
|
47206
|
+
console.log(chalk40.dim("Showing history + live events.\n"));
|
|
47163
47207
|
} else {
|
|
47164
|
-
console.log(
|
|
47208
|
+
console.log(chalk40.dim("Showing live events only. Use --history to include past.\n"));
|
|
47165
47209
|
}
|
|
47166
47210
|
process.on("SIGINT", () => {
|
|
47167
47211
|
exitIdleMode();
|
|
@@ -47171,7 +47215,7 @@ async function startTail(options = {}) {
|
|
|
47171
47215
|
readline5.clearLine(process.stdout, 0);
|
|
47172
47216
|
readline5.cursorTo(process.stdout, 0);
|
|
47173
47217
|
}
|
|
47174
|
-
console.log(
|
|
47218
|
+
console.log(chalk40.dim("\n\u{1F6F0}\uFE0F Disconnected."));
|
|
47175
47219
|
process.exit(0);
|
|
47176
47220
|
});
|
|
47177
47221
|
const STALL_THRESHOLD_MS = 6e4;
|
|
@@ -47183,7 +47227,7 @@ async function startTail(options = {}) {
|
|
|
47183
47227
|
if (Date.now() - auditMtime >= STALL_THRESHOLD_MS) return;
|
|
47184
47228
|
console.log("");
|
|
47185
47229
|
console.log(
|
|
47186
|
-
|
|
47230
|
+
chalk40.yellow(
|
|
47187
47231
|
"\u26A0\uFE0F Tail appears stalled \u2014 hooks are firing but no events are arriving. Try: node9 daemon restart"
|
|
47188
47232
|
)
|
|
47189
47233
|
);
|
|
@@ -47200,7 +47244,7 @@ async function startTail(options = {}) {
|
|
|
47200
47244
|
},
|
|
47201
47245
|
(res) => {
|
|
47202
47246
|
if (res.statusCode !== 200) {
|
|
47203
|
-
console.error(
|
|
47247
|
+
console.error(chalk40.red(`Failed to connect: HTTP ${res.statusCode}`));
|
|
47204
47248
|
process.exit(1);
|
|
47205
47249
|
}
|
|
47206
47250
|
if (canApprove) enterIdleMode();
|
|
@@ -47231,7 +47275,7 @@ async function startTail(options = {}) {
|
|
|
47231
47275
|
readline5.clearLine(process.stdout, 0);
|
|
47232
47276
|
readline5.cursorTo(process.stdout, 0);
|
|
47233
47277
|
}
|
|
47234
|
-
console.log(
|
|
47278
|
+
console.log(chalk40.red("\n\u274C Daemon disconnected."));
|
|
47235
47279
|
process.exit(1);
|
|
47236
47280
|
});
|
|
47237
47281
|
}
|
|
@@ -47244,7 +47288,7 @@ async function startTail(options = {}) {
|
|
|
47244
47288
|
const parsed = JSON.parse(rawData);
|
|
47245
47289
|
const msg = parsed.message ?? "Flight recorder is down \u2014 run: node9 daemon restart";
|
|
47246
47290
|
console.log("");
|
|
47247
|
-
console.log(
|
|
47291
|
+
console.log(chalk40.bgRed.white.bold(` \u26A0\uFE0F ${msg} `));
|
|
47248
47292
|
} catch {
|
|
47249
47293
|
}
|
|
47250
47294
|
return;
|
|
@@ -47335,18 +47379,18 @@ async function startTail(options = {}) {
|
|
|
47335
47379
|
if (event === "execution-result") {
|
|
47336
47380
|
const exec = data;
|
|
47337
47381
|
const time = new Date(Date.now()).toLocaleTimeString([], { hour12: false });
|
|
47338
|
-
const arrow = exec.isError ?
|
|
47382
|
+
const arrow = exec.isError ? chalk40.red(" \u21B3 \u2717") : chalk40.green(" \u21B3 \u2713");
|
|
47339
47383
|
const label2 = agentLabel(exec.agent, exec.mcpServer);
|
|
47340
47384
|
const tool = (exec.tool ?? "").slice(0, 16);
|
|
47341
|
-
const duration = typeof exec.durationMs === "number" ?
|
|
47385
|
+
const duration = typeof exec.durationMs === "number" ? chalk40.dim(` (${exec.durationMs}ms)`) : "";
|
|
47342
47386
|
console.log(
|
|
47343
|
-
`${
|
|
47387
|
+
`${chalk40.gray(time)} ${arrow} ${label2}${chalk40.dim(tool)}${chalk40.dim(" completed")}${duration}`
|
|
47344
47388
|
);
|
|
47345
47389
|
}
|
|
47346
47390
|
}
|
|
47347
47391
|
req.on("error", (err2) => {
|
|
47348
47392
|
const msg = err2.code === "ECONNREFUSED" ? "Daemon is not running. Start it with: node9 daemon start" : err2.message;
|
|
47349
|
-
console.error(
|
|
47393
|
+
console.error(chalk40.red(`
|
|
47350
47394
|
\u274C ${msg}`));
|
|
47351
47395
|
process.exit(1);
|
|
47352
47396
|
});
|
|
@@ -47818,6 +47862,7 @@ init_hook_baseline();
|
|
|
47818
47862
|
init_agent_wiring();
|
|
47819
47863
|
|
|
47820
47864
|
// src/credentials.ts
|
|
47865
|
+
init_config();
|
|
47821
47866
|
import * as fs17 from "fs";
|
|
47822
47867
|
import * as os16 from "os";
|
|
47823
47868
|
import * as path19 from "path";
|
|
@@ -47855,30 +47900,27 @@ function writeCredentialsAndConfig(apiKey, opts = {}) {
|
|
|
47855
47900
|
config.settings = {};
|
|
47856
47901
|
}
|
|
47857
47902
|
const s = config.settings;
|
|
47858
|
-
const
|
|
47859
|
-
|
|
47860
|
-
|
|
47861
|
-
|
|
47862
|
-
terminal:
|
|
47903
|
+
const existing = s.approvers && typeof s.approvers === "object" ? s.approvers : {};
|
|
47904
|
+
const d = DEFAULT_CONFIG.settings.approvers;
|
|
47905
|
+
s.approvers = {
|
|
47906
|
+
native: typeof existing.native === "boolean" ? existing.native : d.native,
|
|
47907
|
+
terminal: typeof existing.terminal === "boolean" ? existing.terminal : d.terminal,
|
|
47908
|
+
cloud: !opts.isLocal
|
|
47863
47909
|
};
|
|
47864
|
-
if (opts.isLocal) {
|
|
47865
|
-
approvers.cloud = false;
|
|
47866
|
-
}
|
|
47867
|
-
s.approvers = approvers;
|
|
47868
47910
|
if (!fs17.existsSync(path19.dirname(configPath))) {
|
|
47869
47911
|
fs17.mkdirSync(path19.dirname(configPath), { recursive: true });
|
|
47870
47912
|
}
|
|
47871
47913
|
fs17.writeFileSync(configPath, JSON.stringify(config, null, 2), {
|
|
47872
47914
|
mode: 384
|
|
47873
47915
|
});
|
|
47874
|
-
effectiveCloud =
|
|
47916
|
+
effectiveCloud = !opts.isLocal;
|
|
47875
47917
|
}
|
|
47876
47918
|
return { profileName, effectiveCloud };
|
|
47877
47919
|
}
|
|
47878
47920
|
|
|
47879
47921
|
// src/cli.ts
|
|
47880
47922
|
init_daemon2();
|
|
47881
|
-
import
|
|
47923
|
+
import chalk41 from "chalk";
|
|
47882
47924
|
import fs75 from "fs";
|
|
47883
47925
|
import path70 from "path";
|
|
47884
47926
|
import os63 from "os";
|
|
@@ -48157,8 +48199,85 @@ async function autoStartDaemonAndWait() {
|
|
|
48157
48199
|
return false;
|
|
48158
48200
|
}
|
|
48159
48201
|
|
|
48160
|
-
// src/
|
|
48202
|
+
// src/onboarding.ts
|
|
48203
|
+
import chalk9 from "chalk";
|
|
48204
|
+
init_setup();
|
|
48205
|
+
init_sync();
|
|
48161
48206
|
init_service();
|
|
48207
|
+
init_daemon();
|
|
48208
|
+
init_config();
|
|
48209
|
+
async function onboardMachine(apiKey, opts = {}) {
|
|
48210
|
+
const steps = [];
|
|
48211
|
+
const wired = [];
|
|
48212
|
+
const namedProfile = !!opts.profileName && opts.profileName !== "default";
|
|
48213
|
+
try {
|
|
48214
|
+
writeCredentialsAndConfig(apiKey, { profileName: opts.profileName });
|
|
48215
|
+
steps.push({ name: "credentials", ok: true, detail: "saved to ~/.node9" });
|
|
48216
|
+
} catch (e) {
|
|
48217
|
+
steps.push({
|
|
48218
|
+
name: "credentials",
|
|
48219
|
+
ok: false,
|
|
48220
|
+
detail: e instanceof Error ? e.message : String(e)
|
|
48221
|
+
});
|
|
48222
|
+
return { ok: false, steps, wired };
|
|
48223
|
+
}
|
|
48224
|
+
process.env.NODE9_NONINTERACTIVE = "1";
|
|
48225
|
+
try {
|
|
48226
|
+
wired.push(...await setupDetectedAgents());
|
|
48227
|
+
steps.push({
|
|
48228
|
+
name: "agents",
|
|
48229
|
+
ok: true,
|
|
48230
|
+
detail: wired.length ? `wired: ${wired.join(", ")}` : "none detected yet: run `node9 init` after installing one"
|
|
48231
|
+
});
|
|
48232
|
+
} catch (e) {
|
|
48233
|
+
steps.push({ name: "agents", ok: false, detail: e instanceof Error ? e.message : String(e) });
|
|
48234
|
+
}
|
|
48235
|
+
if (namedProfile || isTestingMode()) {
|
|
48236
|
+
const why = namedProfile ? "skipped (named profile)" : "skipped (testing mode)";
|
|
48237
|
+
steps.push({ name: "policy-sync", ok: true, detail: why });
|
|
48238
|
+
steps.push({ name: "register", ok: true, detail: why });
|
|
48239
|
+
} else {
|
|
48240
|
+
const sync = await runCloudSync();
|
|
48241
|
+
steps.push(
|
|
48242
|
+
sync.ok ? {
|
|
48243
|
+
name: "policy-sync",
|
|
48244
|
+
ok: true,
|
|
48245
|
+
detail: `${sync.rules} cloud rule${sync.rules === 1 ? "" : "s"} active`
|
|
48246
|
+
} : { name: "policy-sync", ok: false, detail: sync.reason }
|
|
48247
|
+
);
|
|
48248
|
+
const push = await runPolicyPush();
|
|
48249
|
+
steps.push(
|
|
48250
|
+
push.ok ? { name: "register", ok: true, detail: "machine visible in the dashboard" } : { name: "register", ok: false, detail: push.reason }
|
|
48251
|
+
);
|
|
48252
|
+
const healed = ensureAutostartHealthy(!!getConfig().settings.autoStartDaemon);
|
|
48253
|
+
const detail = healed === "repaired" ? "autostart re-enabled (survives reboot)" : isDaemonRunning() ? "running" : healed === "unsupported" ? "no background service on this platform: starts on agent activity" : "starts on agent activity";
|
|
48254
|
+
steps.push({ name: "daemon", ok: true, detail });
|
|
48255
|
+
}
|
|
48256
|
+
const required = ["credentials", "policy-sync", "register"];
|
|
48257
|
+
const ok2 = steps.filter((s) => required.includes(s.name)).every((s) => s.ok);
|
|
48258
|
+
return { ok: ok2, steps, wired };
|
|
48259
|
+
}
|
|
48260
|
+
var STEP_LABELS = {
|
|
48261
|
+
credentials: "Credentials",
|
|
48262
|
+
agents: "Agents",
|
|
48263
|
+
"policy-sync": "Cloud policy",
|
|
48264
|
+
register: "Registered",
|
|
48265
|
+
daemon: "Daemon"
|
|
48266
|
+
};
|
|
48267
|
+
function renderOnboardOutcome(out, opts = {}) {
|
|
48268
|
+
const suffix = opts.workspaceName ? ` to ${opts.workspaceName}` : "";
|
|
48269
|
+
const lines = [
|
|
48270
|
+
out.ok ? chalk9.green(`\u2705 Connected${suffix}`) : chalk9.red(`\u2717 Connection incomplete${suffix}`)
|
|
48271
|
+
];
|
|
48272
|
+
for (const s of out.steps) {
|
|
48273
|
+
const mark = s.ok ? chalk9.green("\u2713") : chalk9.red("\u2717");
|
|
48274
|
+
lines.push(` ${mark} ${STEP_LABELS[s.name]}: ${s.detail}`);
|
|
48275
|
+
}
|
|
48276
|
+
if (!out.ok) {
|
|
48277
|
+
lines.push(chalk9.yellow(" Fix the failed step, then verify with: node9 sync"));
|
|
48278
|
+
}
|
|
48279
|
+
return lines.join("\n");
|
|
48280
|
+
}
|
|
48162
48281
|
|
|
48163
48282
|
// src/cli/commands/check.ts
|
|
48164
48283
|
init_orchestrator();
|
|
@@ -48166,7 +48285,7 @@ init_state();
|
|
|
48166
48285
|
init_daemon();
|
|
48167
48286
|
init_startup_log();
|
|
48168
48287
|
init_config();
|
|
48169
|
-
import
|
|
48288
|
+
import chalk10 from "chalk";
|
|
48170
48289
|
import fs49 from "fs";
|
|
48171
48290
|
import { spawn as spawn4 } from "child_process";
|
|
48172
48291
|
import path47 from "path";
|
|
@@ -48569,11 +48688,11 @@ RAW: ${raw}
|
|
|
48569
48688
|
const ttyFd = fs49.openSync("/dev/tty", "w");
|
|
48570
48689
|
fs49.writeSync(
|
|
48571
48690
|
ttyFd,
|
|
48572
|
-
|
|
48691
|
+
chalk10.bgRed.white.bold(`
|
|
48573
48692
|
\u{1F6A8} NODE9 DLP \u2014 PROMPT BLOCKED
|
|
48574
|
-
`) +
|
|
48575
|
-
`) +
|
|
48576
|
-
`) +
|
|
48693
|
+
`) + chalk10.red(` ${dlpMatch.patternName} detected in your prompt.
|
|
48694
|
+
`) + chalk10.gray(` Match: ${dlpMatch.redactedSample}
|
|
48695
|
+
`) + chalk10.cyan(` Edit the prompt to remove the credential and resubmit.
|
|
48577
48696
|
|
|
48578
48697
|
`)
|
|
48579
48698
|
);
|
|
@@ -48680,18 +48799,18 @@ RAW: ${raw}
|
|
|
48680
48799
|
ttyFd = fs49.openSync("/dev/tty", "w");
|
|
48681
48800
|
const writeTty = (line) => fs49.writeSync(ttyFd, line + "\n");
|
|
48682
48801
|
if (blockedByContext.includes("DLP") || blockedByContext.includes("Secret Detected") || blockedByContext.includes("Credential Review")) {
|
|
48683
|
-
writeTty(
|
|
48802
|
+
writeTty(chalk10.bgRed.white.bold(`
|
|
48684
48803
|
\u{1F6A8} NODE9 DLP ALERT \u2014 CREDENTIAL DETECTED `));
|
|
48685
|
-
writeTty(
|
|
48804
|
+
writeTty(chalk10.red.bold(` A sensitive secret was found in the tool arguments!`));
|
|
48686
48805
|
} else {
|
|
48687
|
-
writeTty(
|
|
48806
|
+
writeTty(chalk10.red(`
|
|
48688
48807
|
\u{1F6D1} Node9 blocked "${toolName}"`));
|
|
48689
48808
|
}
|
|
48690
|
-
if (result2?.ruleDescription) writeTty(
|
|
48691
|
-
writeTty(
|
|
48692
|
-
if (result2?.changeHint) writeTty(
|
|
48809
|
+
if (result2?.ruleDescription) writeTty(chalk10.white(` ${result2.ruleDescription}`));
|
|
48810
|
+
writeTty(chalk10.gray(` Triggered by: ${blockedByContext}`));
|
|
48811
|
+
if (result2?.changeHint) writeTty(chalk10.cyan(` To change: ${result2.changeHint}`));
|
|
48693
48812
|
if (result2?.recoveryCommand)
|
|
48694
|
-
writeTty(
|
|
48813
|
+
writeTty(chalk10.green(` \u{1F4A1} Run: ${result2.recoveryCommand}`));
|
|
48695
48814
|
writeTty("");
|
|
48696
48815
|
} catch {
|
|
48697
48816
|
} finally {
|
|
@@ -48761,7 +48880,7 @@ RAW: ${raw}
|
|
|
48761
48880
|
const ttyFd = fs49.openSync("/dev/tty", "w");
|
|
48762
48881
|
fs49.writeSync(
|
|
48763
48882
|
ttyFd,
|
|
48764
|
-
|
|
48883
|
+
chalk10.yellow(
|
|
48765
48884
|
`
|
|
48766
48885
|
\u26A0\uFE0F Node9: review requested for "${toolName}" \u2014 answer in the prompt.
|
|
48767
48886
|
`
|
|
@@ -48822,15 +48941,15 @@ RAW: ${raw}
|
|
|
48822
48941
|
try {
|
|
48823
48942
|
ttyFd = fs49.openSync("/dev/tty", "w");
|
|
48824
48943
|
const w = (line) => fs49.writeSync(ttyFd, line + "\n");
|
|
48825
|
-
w(
|
|
48944
|
+
w(chalk10.yellow(`
|
|
48826
48945
|
\u26A0\uFE0F Node9: installed skill drift detected`));
|
|
48827
|
-
w(
|
|
48946
|
+
w(chalk10.gray(` ${detail}`));
|
|
48828
48947
|
w(
|
|
48829
|
-
|
|
48948
|
+
chalk10.gray(
|
|
48830
48949
|
` If you updated a plugin, acknowledge the change to clear this warning.`
|
|
48831
48950
|
)
|
|
48832
48951
|
);
|
|
48833
|
-
if (recoveryCmd) w(
|
|
48952
|
+
if (recoveryCmd) w(chalk10.green(` \u{1F4A1} Run: ${recoveryCmd}`));
|
|
48834
48953
|
w("");
|
|
48835
48954
|
} catch {
|
|
48836
48955
|
} finally {
|
|
@@ -48943,7 +49062,7 @@ RAW: ${raw}
|
|
|
48943
49062
|
const tty = fs49.openSync("/dev/tty", "w");
|
|
48944
49063
|
fs49.writeSync(
|
|
48945
49064
|
tty,
|
|
48946
|
-
|
|
49065
|
+
chalk10.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically...\n")
|
|
48947
49066
|
);
|
|
48948
49067
|
fs49.closeSync(tty);
|
|
48949
49068
|
} catch {
|
|
@@ -49300,7 +49419,7 @@ function registerLogCommand(program2) {
|
|
|
49300
49419
|
// src/cli/commands/shield.ts
|
|
49301
49420
|
init_shields();
|
|
49302
49421
|
init_build();
|
|
49303
|
-
import
|
|
49422
|
+
import chalk11 from "chalk";
|
|
49304
49423
|
import fs52 from "fs";
|
|
49305
49424
|
import path50 from "path";
|
|
49306
49425
|
import os46 from "os";
|
|
@@ -49411,30 +49530,30 @@ function registerShieldCommand(program2) {
|
|
|
49411
49530
|
shieldCmd.command("enable <service>").description("Enable a security shield for a specific service").action((service) => {
|
|
49412
49531
|
const name = resolveShieldName(service);
|
|
49413
49532
|
if (!name) {
|
|
49414
|
-
console.error(
|
|
49533
|
+
console.error(chalk11.red(`
|
|
49415
49534
|
\u274C Unknown shield: "${service}"
|
|
49416
49535
|
`));
|
|
49417
|
-
console.log(`Run ${
|
|
49536
|
+
console.log(`Run ${chalk11.cyan("node9 shield list")} to see available shields.
|
|
49418
49537
|
`);
|
|
49419
49538
|
process.exit(1);
|
|
49420
49539
|
}
|
|
49421
49540
|
const shield = getShield(name);
|
|
49422
49541
|
const active = readActiveShields();
|
|
49423
49542
|
if (active.includes(name)) {
|
|
49424
|
-
console.log(
|
|
49543
|
+
console.log(chalk11.yellow(`
|
|
49425
49544
|
\u2139\uFE0F Shield "${name}" is already active.
|
|
49426
49545
|
`));
|
|
49427
49546
|
return;
|
|
49428
49547
|
}
|
|
49429
49548
|
writeActiveShields([...active, name]);
|
|
49430
|
-
console.log(
|
|
49549
|
+
console.log(chalk11.green(`
|
|
49431
49550
|
\u{1F6E1}\uFE0F Shield "${name}" enabled.`));
|
|
49432
|
-
console.log(
|
|
49551
|
+
console.log(chalk11.gray(` ${shield.smartRules.length} smart rules now active.`));
|
|
49433
49552
|
if (shield.dangerousWords.length > 0)
|
|
49434
|
-
console.log(
|
|
49553
|
+
console.log(chalk11.gray(` ${shield.dangerousWords.length} dangerous words now active.`));
|
|
49435
49554
|
if (name === "filesystem") {
|
|
49436
49555
|
console.log(
|
|
49437
|
-
|
|
49556
|
+
chalk11.yellow(
|
|
49438
49557
|
`
|
|
49439
49558
|
\u26A0\uFE0F Note: filesystem rules cover common rm -rf patterns but not all variants.
|
|
49440
49559
|
Tools like unlink, find -delete, or language-level file ops are not intercepted.`
|
|
@@ -49446,45 +49565,45 @@ function registerShieldCommand(program2) {
|
|
|
49446
49565
|
shieldCmd.command("disable <service>").description("Disable a security shield").action((service) => {
|
|
49447
49566
|
const name = resolveShieldName(service);
|
|
49448
49567
|
if (!name) {
|
|
49449
|
-
console.error(
|
|
49568
|
+
console.error(chalk11.red(`
|
|
49450
49569
|
\u274C Unknown shield: "${service}"
|
|
49451
49570
|
`));
|
|
49452
|
-
console.log(`Run ${
|
|
49571
|
+
console.log(`Run ${chalk11.cyan("node9 shield list")} to see available shields.
|
|
49453
49572
|
`);
|
|
49454
49573
|
process.exit(1);
|
|
49455
49574
|
}
|
|
49456
49575
|
const active = readActiveShields();
|
|
49457
49576
|
if (!active.includes(name)) {
|
|
49458
|
-
console.log(
|
|
49577
|
+
console.log(chalk11.yellow(`
|
|
49459
49578
|
\u2139\uFE0F Shield "${name}" is not active.
|
|
49460
49579
|
`));
|
|
49461
49580
|
return;
|
|
49462
49581
|
}
|
|
49463
49582
|
writeActiveShields(active.filter((s) => s !== name));
|
|
49464
|
-
console.log(
|
|
49583
|
+
console.log(chalk11.green(`
|
|
49465
49584
|
\u{1F6E1}\uFE0F Shield "${name}" disabled.
|
|
49466
49585
|
`));
|
|
49467
49586
|
});
|
|
49468
49587
|
shieldCmd.command("list").description("Show available shields (add --community to browse the marketplace)").option("--community", "List shields available from the community marketplace").action((opts) => {
|
|
49469
49588
|
if (opts.community) {
|
|
49470
|
-
console.log(
|
|
49471
|
-
console.log(
|
|
49589
|
+
console.log(chalk11.bold("\n\u{1F6E1}\uFE0F Community Shield Marketplace\n"));
|
|
49590
|
+
console.log(chalk11.gray(" Fetching index\u2026\n"));
|
|
49472
49591
|
httpsFetch(COMMUNITY_INDEX_URL).then((body) => {
|
|
49473
49592
|
const entries = JSON.parse(body);
|
|
49474
49593
|
const installed = new Set(listShields().map((s) => s.name));
|
|
49475
49594
|
for (const e of entries) {
|
|
49476
|
-
const tag = installed.has(e.name) ?
|
|
49595
|
+
const tag = installed.has(e.name) ? chalk11.green("installed") : chalk11.gray("available");
|
|
49477
49596
|
console.log(
|
|
49478
|
-
` ${tag} ${
|
|
49597
|
+
` ${tag} ${chalk11.cyan(e.name.padEnd(12))} ${e.description} ${chalk11.gray(`by ${e.author}`)}`
|
|
49479
49598
|
);
|
|
49480
49599
|
}
|
|
49481
49600
|
console.log("");
|
|
49482
49601
|
console.log(
|
|
49483
|
-
|
|
49602
|
+
chalk11.gray(` Install a shield: ${chalk11.cyan("node9 shield install <name>")}
|
|
49484
49603
|
`)
|
|
49485
49604
|
);
|
|
49486
49605
|
}).catch((err2) => {
|
|
49487
|
-
console.error(
|
|
49606
|
+
console.error(chalk11.red(`
|
|
49488
49607
|
\u274C Could not fetch community index: ${String(err2)}
|
|
49489
49608
|
`));
|
|
49490
49609
|
process.exit(1);
|
|
@@ -49493,59 +49612,59 @@ function registerShieldCommand(program2) {
|
|
|
49493
49612
|
}
|
|
49494
49613
|
const active = new Set(readActiveShields());
|
|
49495
49614
|
const cloud = readCloudShields();
|
|
49496
|
-
console.log(
|
|
49497
|
-
console.log(
|
|
49615
|
+
console.log(chalk11.bold("\n\u{1F6E1}\uFE0F Available Shields\n"));
|
|
49616
|
+
console.log(chalk11.gray(" \u25CF local \xB7 \u2601 cloud\n"));
|
|
49498
49617
|
for (const shield of listShields()) {
|
|
49499
49618
|
const isLocal = active.has(shield.name);
|
|
49500
49619
|
const isCloud = cloud.has(shield.name);
|
|
49501
|
-
const status = isLocal && isCloud ?
|
|
49502
|
-
const via = isCloud && !isLocal ?
|
|
49620
|
+
const status = isLocal && isCloud ? chalk11.green("\u25CF\u2601 enabled ") : isLocal ? chalk11.green("\u25CF enabled ") : isCloud ? chalk11.cyan("\u2601 cloud ") : chalk11.gray("\u25CB disabled");
|
|
49621
|
+
const via = isCloud && !isLocal ? chalk11.gray(" (via dashboard)") : "";
|
|
49503
49622
|
console.log(
|
|
49504
|
-
` ${status} ${
|
|
49623
|
+
` ${status} ${chalk11.cyan(shield.name.padEnd(12))} ${shield.description}${via}`
|
|
49505
49624
|
);
|
|
49506
49625
|
if (shield.aliases.length > 0)
|
|
49507
|
-
console.log(
|
|
49626
|
+
console.log(chalk11.gray(` aliases: ${shield.aliases.join(", ")}`));
|
|
49508
49627
|
}
|
|
49509
49628
|
console.log("");
|
|
49510
49629
|
console.log(
|
|
49511
|
-
|
|
49630
|
+
chalk11.gray(` Browse community shields: ${chalk11.cyan("node9 shield list --community")}
|
|
49512
49631
|
`)
|
|
49513
49632
|
);
|
|
49514
49633
|
});
|
|
49515
49634
|
shieldCmd.command("status").description("Show active shields and their individual rules with verdicts").action(() => {
|
|
49516
49635
|
const active = readActiveShields();
|
|
49517
49636
|
if (active.length === 0) {
|
|
49518
|
-
console.error(
|
|
49519
|
-
console.error(`Run ${
|
|
49637
|
+
console.error(chalk11.yellow("\n\u2139\uFE0F No shields are active.\n"));
|
|
49638
|
+
console.error(`Run ${chalk11.cyan("node9 shield list")} to see available shields.
|
|
49520
49639
|
`);
|
|
49521
49640
|
return;
|
|
49522
49641
|
}
|
|
49523
49642
|
const overrides = readShieldOverrides();
|
|
49524
|
-
console.error(
|
|
49643
|
+
console.error(chalk11.bold("\n\u{1F6E1}\uFE0F Active Shields\n"));
|
|
49525
49644
|
for (const name of active) {
|
|
49526
49645
|
const shield = getShield(name);
|
|
49527
49646
|
if (!shield) continue;
|
|
49528
|
-
console.error(` ${
|
|
49647
|
+
console.error(` ${chalk11.green("\u25CF")} ${chalk11.cyan(name)} \u2014 ${shield.description}`);
|
|
49529
49648
|
const ruleOverrides = overrides[name] ?? {};
|
|
49530
49649
|
for (const rule of shield.smartRules) {
|
|
49531
49650
|
const shortName = rule.name ? rule.name.replace(`shield:${name}:`, "") : "(unnamed)";
|
|
49532
49651
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
49533
49652
|
const effectiveVerdict = overrideVerdict ?? rule.verdict;
|
|
49534
|
-
const verdictLabel = effectiveVerdict === "block" ?
|
|
49535
|
-
const overrideNote = overrideVerdict ?
|
|
49653
|
+
const verdictLabel = effectiveVerdict === "block" ? chalk11.red("block ") : effectiveVerdict === "review" ? chalk11.yellow("review") : chalk11.green("allow ");
|
|
49654
|
+
const overrideNote = overrideVerdict ? chalk11.gray(` \u2190 overridden (was: ${rule.verdict})`) : "";
|
|
49536
49655
|
console.error(
|
|
49537
|
-
` ${verdictLabel} ${shortName.padEnd(24)} ${
|
|
49656
|
+
` ${verdictLabel} ${shortName.padEnd(24)} ${chalk11.gray(rule.reason ?? "")}${overrideNote}`
|
|
49538
49657
|
);
|
|
49539
49658
|
}
|
|
49540
49659
|
if (shield.dangerousWords.length > 0) {
|
|
49541
|
-
console.error(
|
|
49660
|
+
console.error(chalk11.gray(` words: ${shield.dangerousWords.join(", ")}`));
|
|
49542
49661
|
}
|
|
49543
49662
|
console.error("");
|
|
49544
49663
|
}
|
|
49545
49664
|
if (Object.keys(overrides).length > 0) {
|
|
49546
49665
|
console.error(
|
|
49547
|
-
|
|
49548
|
-
` Tip: run ${
|
|
49666
|
+
chalk11.gray(
|
|
49667
|
+
` Tip: run ${chalk11.cyan("node9 shield unset <shield> <rule>")} to remove an override.
|
|
49549
49668
|
`
|
|
49550
49669
|
)
|
|
49551
49670
|
);
|
|
@@ -49554,24 +49673,24 @@ function registerShieldCommand(program2) {
|
|
|
49554
49673
|
shieldCmd.command("set <service> <rule> <verdict>").description("Override the verdict for a specific shield rule (block, review, or allow)").option("--force", "Required when setting verdict to allow (silences a block rule)").action((service, rule, verdict, opts) => {
|
|
49555
49674
|
const name = resolveShieldName(service);
|
|
49556
49675
|
if (!name) {
|
|
49557
|
-
console.error(
|
|
49676
|
+
console.error(chalk11.red(`
|
|
49558
49677
|
\u274C Unknown shield: "${service}"
|
|
49559
49678
|
`));
|
|
49560
|
-
console.error(`Run ${
|
|
49679
|
+
console.error(`Run ${chalk11.cyan("node9 shield list")} to see available shields.
|
|
49561
49680
|
`);
|
|
49562
49681
|
process.exit(1);
|
|
49563
49682
|
}
|
|
49564
49683
|
if (!readActiveShields().includes(name)) {
|
|
49565
|
-
console.error(
|
|
49684
|
+
console.error(chalk11.red(`
|
|
49566
49685
|
\u274C Shield "${name}" is not active. Enable it first:
|
|
49567
49686
|
`));
|
|
49568
|
-
console.error(` ${
|
|
49687
|
+
console.error(` ${chalk11.cyan(`node9 shield enable ${name}`)}
|
|
49569
49688
|
`);
|
|
49570
49689
|
process.exit(1);
|
|
49571
49690
|
}
|
|
49572
49691
|
if (!isShieldVerdict(verdict)) {
|
|
49573
49692
|
console.error(
|
|
49574
|
-
|
|
49693
|
+
chalk11.red(`
|
|
49575
49694
|
\u274C Invalid verdict "${verdict}". Use: block, review, or allow
|
|
49576
49695
|
`)
|
|
49577
49696
|
);
|
|
@@ -49579,12 +49698,12 @@ function registerShieldCommand(program2) {
|
|
|
49579
49698
|
}
|
|
49580
49699
|
if (verdict === "allow" && !opts.force) {
|
|
49581
49700
|
console.error(
|
|
49582
|
-
|
|
49701
|
+
chalk11.red(`
|
|
49583
49702
|
\u26A0\uFE0F Setting a verdict to "allow" silences the rule entirely.
|
|
49584
|
-
`) +
|
|
49703
|
+
`) + chalk11.yellow(
|
|
49585
49704
|
` This disables a shield protection. If you are sure, re-run with --force:
|
|
49586
49705
|
`
|
|
49587
|
-
) +
|
|
49706
|
+
) + chalk11.cyan(`
|
|
49588
49707
|
node9 shield set ${service} ${rule} allow --force
|
|
49589
49708
|
`)
|
|
49590
49709
|
);
|
|
@@ -49593,13 +49712,13 @@ function registerShieldCommand(program2) {
|
|
|
49593
49712
|
const ruleName = resolveShieldRule(name, rule);
|
|
49594
49713
|
if (!ruleName) {
|
|
49595
49714
|
const shield = getShield(name);
|
|
49596
|
-
console.error(
|
|
49715
|
+
console.error(chalk11.red(`
|
|
49597
49716
|
\u274C Unknown rule "${rule}" for shield "${name}".
|
|
49598
49717
|
`));
|
|
49599
49718
|
console.error(" Available rules:");
|
|
49600
49719
|
for (const r of shield?.smartRules ?? []) {
|
|
49601
49720
|
const short = r.name ? r.name.replace(`shield:${name}:`, "") : "";
|
|
49602
|
-
console.error(` ${
|
|
49721
|
+
console.error(` ${chalk11.cyan(short)}`);
|
|
49603
49722
|
}
|
|
49604
49723
|
console.error("");
|
|
49605
49724
|
process.exit(1);
|
|
@@ -49609,33 +49728,33 @@ function registerShieldCommand(program2) {
|
|
|
49609
49728
|
appendConfigAudit({ event: "shield-override-allow", shield: name, rule: ruleName });
|
|
49610
49729
|
}
|
|
49611
49730
|
const shortName = ruleName.replace(`shield:${name}:`, "");
|
|
49612
|
-
const verdictLabel = verdict === "block" ?
|
|
49731
|
+
const verdictLabel = verdict === "block" ? chalk11.red("block") : verdict === "review" ? chalk11.yellow("review") : chalk11.green("allow");
|
|
49613
49732
|
if (verdict === "allow") {
|
|
49614
49733
|
console.error(
|
|
49615
|
-
|
|
49616
|
-
\u26A0\uFE0F ${name}/${shortName} \u2192 ${verdictLabel}`) +
|
|
49734
|
+
chalk11.yellow(`
|
|
49735
|
+
\u26A0\uFE0F ${name}/${shortName} \u2192 ${verdictLabel}`) + chalk11.gray(" (rule silenced \u2014 use `node9 shield unset` to restore)\n")
|
|
49617
49736
|
);
|
|
49618
49737
|
} else {
|
|
49619
|
-
console.error(
|
|
49738
|
+
console.error(chalk11.green(`
|
|
49620
49739
|
\u2705 ${name}/${shortName} \u2192 ${verdictLabel}
|
|
49621
49740
|
`));
|
|
49622
49741
|
}
|
|
49623
49742
|
console.error(
|
|
49624
|
-
|
|
49743
|
+
chalk11.gray(` Run ${chalk11.cyan("node9 shield status")} to see all active rules.
|
|
49625
49744
|
`)
|
|
49626
49745
|
);
|
|
49627
49746
|
});
|
|
49628
49747
|
shieldCmd.command("unset <service> <rule>").description("Remove a verdict override, restoring the shield default").action((service, rule) => {
|
|
49629
49748
|
const name = resolveShieldName(service);
|
|
49630
49749
|
if (!name) {
|
|
49631
|
-
console.error(
|
|
49750
|
+
console.error(chalk11.red(`
|
|
49632
49751
|
\u274C Unknown shield: "${service}"
|
|
49633
49752
|
`));
|
|
49634
49753
|
process.exit(1);
|
|
49635
49754
|
}
|
|
49636
49755
|
const ruleName = resolveShieldRule(name, rule);
|
|
49637
49756
|
if (!ruleName) {
|
|
49638
|
-
console.error(
|
|
49757
|
+
console.error(chalk11.red(`
|
|
49639
49758
|
\u274C Unknown rule "${rule}" for shield "${name}".
|
|
49640
49759
|
`));
|
|
49641
49760
|
process.exit(1);
|
|
@@ -49643,7 +49762,7 @@ function registerShieldCommand(program2) {
|
|
|
49643
49762
|
clearShieldOverride(name, ruleName);
|
|
49644
49763
|
const shortName = ruleName.replace(`shield:${name}:`, "");
|
|
49645
49764
|
console.error(
|
|
49646
|
-
|
|
49765
|
+
chalk11.green(`
|
|
49647
49766
|
\u2705 Override removed \u2014 ${name}/${shortName} restored to default.
|
|
49648
49767
|
`)
|
|
49649
49768
|
);
|
|
@@ -49651,7 +49770,7 @@ function registerShieldCommand(program2) {
|
|
|
49651
49770
|
shieldCmd.command("install <name>").description("Install a shield from the community marketplace into ~/.node9/shields/").action((name) => {
|
|
49652
49771
|
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
49653
49772
|
console.error(
|
|
49654
|
-
|
|
49773
|
+
chalk11.red(
|
|
49655
49774
|
`
|
|
49656
49775
|
\u274C Invalid shield name: only alphanumeric characters, hyphens, and underscores are allowed
|
|
49657
49776
|
`
|
|
@@ -49659,16 +49778,16 @@ function registerShieldCommand(program2) {
|
|
|
49659
49778
|
);
|
|
49660
49779
|
process.exit(1);
|
|
49661
49780
|
}
|
|
49662
|
-
console.log(
|
|
49781
|
+
console.log(chalk11.bold(`
|
|
49663
49782
|
\u{1F6E1}\uFE0F Installing shield "${name}"\u2026
|
|
49664
49783
|
`));
|
|
49665
49784
|
httpsFetch(COMMUNITY_INDEX_URL).then((indexBody) => {
|
|
49666
49785
|
const entries = JSON.parse(indexBody);
|
|
49667
49786
|
const entry = entries.find((e) => e.name === name);
|
|
49668
49787
|
if (!entry) {
|
|
49669
|
-
const names = entries.map((e) =>
|
|
49788
|
+
const names = entries.map((e) => chalk11.cyan(e.name)).join(", ");
|
|
49670
49789
|
console.error(
|
|
49671
|
-
|
|
49790
|
+
chalk11.red(`\u274C Shield "${name}" not found in the community marketplace.
|
|
49672
49791
|
`)
|
|
49673
49792
|
);
|
|
49674
49793
|
console.error(` Available: ${names}
|
|
@@ -49680,15 +49799,15 @@ function registerShieldCommand(program2) {
|
|
|
49680
49799
|
const shieldJson = JSON.parse(shieldBody);
|
|
49681
49800
|
installShield(name, shieldJson);
|
|
49682
49801
|
console.log(
|
|
49683
|
-
|
|
49802
|
+
chalk11.green(`\u2705 Shield "${name}" installed to ~/.node9/shields/${name}.json`)
|
|
49684
49803
|
);
|
|
49685
49804
|
console.log(
|
|
49686
|
-
|
|
49805
|
+
chalk11.gray(` Activate it with: ${chalk11.cyan(`node9 shield enable ${name}`)}
|
|
49687
49806
|
`)
|
|
49688
49807
|
);
|
|
49689
49808
|
appendConfigAudit({ event: "shield-install", shield: name });
|
|
49690
49809
|
}).catch((err2) => {
|
|
49691
|
-
console.error(
|
|
49810
|
+
console.error(chalk11.red(`
|
|
49692
49811
|
\u274C Install failed: ${String(err2)}
|
|
49693
49812
|
`));
|
|
49694
49813
|
process.exit(1);
|
|
@@ -49699,7 +49818,7 @@ function registerShieldCommand(program2) {
|
|
|
49699
49818
|
(name, opts) => {
|
|
49700
49819
|
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
49701
49820
|
console.error(
|
|
49702
|
-
|
|
49821
|
+
chalk11.red(
|
|
49703
49822
|
`
|
|
49704
49823
|
\u274C Invalid shield name: only alphanumeric characters, hyphens, and underscores are allowed
|
|
49705
49824
|
`
|
|
@@ -49714,7 +49833,7 @@ function registerShieldCommand(program2) {
|
|
|
49714
49833
|
raw = JSON.parse(fs52.readFileSync(opts.fromFile, "utf-8"));
|
|
49715
49834
|
} catch (err2) {
|
|
49716
49835
|
console.error(
|
|
49717
|
-
|
|
49836
|
+
chalk11.red(`
|
|
49718
49837
|
\u274C Could not read/parse ${opts.fromFile}: ${String(err2)}
|
|
49719
49838
|
`)
|
|
49720
49839
|
);
|
|
@@ -49738,25 +49857,25 @@ function registerShieldCommand(program2) {
|
|
|
49738
49857
|
viaMcp: false
|
|
49739
49858
|
});
|
|
49740
49859
|
if (!res.ok) {
|
|
49741
|
-
console.error(
|
|
49860
|
+
console.error(chalk11.red(`
|
|
49742
49861
|
\u274C ${res.error}
|
|
49743
49862
|
`));
|
|
49744
49863
|
process.exit(1);
|
|
49745
49864
|
return;
|
|
49746
49865
|
}
|
|
49747
49866
|
console.log(
|
|
49748
|
-
|
|
49749
|
-
\u2705 Shield "${name}" created`) +
|
|
49867
|
+
chalk11.green(`
|
|
49868
|
+
\u2705 Shield "${name}" created`) + chalk11.gray(` \u2014 ${res.ruleCount} rule(s) \u2192 ${res.path}`)
|
|
49750
49869
|
);
|
|
49751
49870
|
if (res.enabled) {
|
|
49752
|
-
console.log(
|
|
49871
|
+
console.log(chalk11.gray(` Active now.`));
|
|
49753
49872
|
} else {
|
|
49754
49873
|
console.log(
|
|
49755
|
-
|
|
49874
|
+
chalk11.gray(` Activate it with: ${chalk11.cyan(`node9 shield enable ${name}`)}`)
|
|
49756
49875
|
);
|
|
49757
49876
|
}
|
|
49758
49877
|
console.log(
|
|
49759
|
-
|
|
49878
|
+
chalk11.gray(` Preview a call: ${chalk11.cyan(`node9 explain bash "<command>"`)}
|
|
49760
49879
|
`)
|
|
49761
49880
|
);
|
|
49762
49881
|
}
|
|
@@ -49769,32 +49888,32 @@ function registerConfigShowCommand(program2) {
|
|
|
49769
49888
|
const config = getConfig();
|
|
49770
49889
|
const active = readActiveShields();
|
|
49771
49890
|
const overrides = readShieldOverrides();
|
|
49772
|
-
console.error(
|
|
49773
|
-
const modeLabel = config.settings.mode === "audit" ?
|
|
49891
|
+
console.error(chalk11.bold("\n\u{1F50D} Node9 Effective Configuration\n"));
|
|
49892
|
+
const modeLabel = config.settings.mode === "audit" ? chalk11.blue("audit") : config.settings.mode === "strict" ? chalk11.red("strict") : chalk11.white("standard");
|
|
49774
49893
|
console.error(` Mode: ${modeLabel}
|
|
49775
49894
|
`);
|
|
49776
49895
|
if (active.length > 0) {
|
|
49777
|
-
console.error(
|
|
49896
|
+
console.error(chalk11.bold(" \u2500\u2500 Active Shields \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"));
|
|
49778
49897
|
for (const name of active) {
|
|
49779
49898
|
const shield = getShield(name);
|
|
49780
49899
|
if (!shield) continue;
|
|
49781
49900
|
const ruleOverrides = overrides[name] ?? {};
|
|
49782
49901
|
console.error(`
|
|
49783
|
-
${
|
|
49902
|
+
${chalk11.green("\u25CF")} ${chalk11.cyan(name)}`);
|
|
49784
49903
|
for (const rule of shield.smartRules) {
|
|
49785
49904
|
const shortName = rule.name ? rule.name.replace(`shield:${name}:`, "") : "(unnamed)";
|
|
49786
49905
|
const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
|
|
49787
49906
|
const effectiveVerdict = overrideVerdict ?? rule.verdict;
|
|
49788
|
-
const vLabel = effectiveVerdict === "block" ?
|
|
49789
|
-
const note = overrideVerdict ?
|
|
49907
|
+
const vLabel = effectiveVerdict === "block" ? chalk11.red("block ") : effectiveVerdict === "review" ? chalk11.yellow("review") : chalk11.green("allow ");
|
|
49908
|
+
const note = overrideVerdict ? chalk11.gray(` \u2190 overridden`) : "";
|
|
49790
49909
|
console.error(` ${vLabel} ${shortName}${note}`);
|
|
49791
49910
|
}
|
|
49792
49911
|
}
|
|
49793
49912
|
console.error("");
|
|
49794
49913
|
} else {
|
|
49795
|
-
console.error(
|
|
49914
|
+
console.error(chalk11.gray(" No shields active. Run `node9 shield list` to see options.\n"));
|
|
49796
49915
|
}
|
|
49797
|
-
console.error(
|
|
49916
|
+
console.error(chalk11.bold(" \u2500\u2500 Built-in Rules (always on) \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"));
|
|
49798
49917
|
for (const rule of config.policy.smartRules) {
|
|
49799
49918
|
const isShieldRule = rule.name?.startsWith("shield:");
|
|
49800
49919
|
const isAdvisory = [
|
|
@@ -49805,11 +49924,11 @@ function registerConfigShowCommand(program2) {
|
|
|
49805
49924
|
"review-drop-column-sql"
|
|
49806
49925
|
].includes(rule.name ?? "");
|
|
49807
49926
|
if (isShieldRule || isAdvisory) continue;
|
|
49808
|
-
const vLabel = rule.verdict === "block" ?
|
|
49809
|
-
console.error(` ${vLabel} ${
|
|
49927
|
+
const vLabel = rule.verdict === "block" ? chalk11.red("block ") : rule.verdict === "review" ? chalk11.yellow("review") : chalk11.green("allow ");
|
|
49928
|
+
console.error(` ${vLabel} ${chalk11.gray(rule.name ?? rule.tool)}`);
|
|
49810
49929
|
}
|
|
49811
49930
|
console.error("");
|
|
49812
|
-
console.error(
|
|
49931
|
+
console.error(chalk11.bold(" \u2500\u2500 Safe by Default (advisory, overridable) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
49813
49932
|
const advisoryNames = /* @__PURE__ */ new Set([
|
|
49814
49933
|
"review-rm",
|
|
49815
49934
|
"allow-rm-safe-paths",
|
|
@@ -49819,12 +49938,12 @@ function registerConfigShowCommand(program2) {
|
|
|
49819
49938
|
]);
|
|
49820
49939
|
for (const rule of config.policy.smartRules) {
|
|
49821
49940
|
if (!advisoryNames.has(rule.name ?? "")) continue;
|
|
49822
|
-
const vLabel = rule.verdict === "block" ?
|
|
49823
|
-
console.error(` ${vLabel} ${
|
|
49941
|
+
const vLabel = rule.verdict === "block" ? chalk11.red("block ") : rule.verdict === "review" ? chalk11.yellow("review") : chalk11.green("allow ");
|
|
49942
|
+
console.error(` ${vLabel} ${chalk11.gray(rule.name ?? rule.tool)}`);
|
|
49824
49943
|
}
|
|
49825
49944
|
console.error("");
|
|
49826
|
-
console.error(
|
|
49827
|
-
console.error(` ${
|
|
49945
|
+
console.error(chalk11.bold(" \u2500\u2500 Dangerous Words \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"));
|
|
49946
|
+
console.error(` ${chalk11.gray(config.policy.dangerousWords.join(", "))}
|
|
49828
49947
|
`);
|
|
49829
49948
|
});
|
|
49830
49949
|
}
|
|
@@ -49836,7 +49955,7 @@ init_config();
|
|
|
49836
49955
|
init_agent_wiring();
|
|
49837
49956
|
init_sync();
|
|
49838
49957
|
init_service();
|
|
49839
|
-
import
|
|
49958
|
+
import chalk12 from "chalk";
|
|
49840
49959
|
import fs54 from "fs";
|
|
49841
49960
|
import path52 from "path";
|
|
49842
49961
|
import os48 from "os";
|
|
@@ -49918,21 +50037,21 @@ function registerDoctorCommand(program2, version2) {
|
|
|
49918
50037
|
const homeDir2 = os48.homedir();
|
|
49919
50038
|
let failures = 0;
|
|
49920
50039
|
function pass(msg) {
|
|
49921
|
-
console.log(
|
|
50040
|
+
console.log(chalk12.green(" \u2705 ") + msg);
|
|
49922
50041
|
}
|
|
49923
50042
|
function fail(msg, hint) {
|
|
49924
|
-
console.log(
|
|
49925
|
-
if (hint) console.log(
|
|
50043
|
+
console.log(chalk12.red(" \u274C ") + msg);
|
|
50044
|
+
if (hint) console.log(chalk12.gray(" " + hint));
|
|
49926
50045
|
failures++;
|
|
49927
50046
|
}
|
|
49928
50047
|
function warn(msg, hint) {
|
|
49929
|
-
console.log(
|
|
49930
|
-
if (hint) console.log(
|
|
50048
|
+
console.log(chalk12.yellow(" \u26A0\uFE0F ") + msg);
|
|
50049
|
+
if (hint) console.log(chalk12.gray(" " + hint));
|
|
49931
50050
|
}
|
|
49932
50051
|
function section(title) {
|
|
49933
|
-
console.log("\n" +
|
|
50052
|
+
console.log("\n" + chalk12.bold(title));
|
|
49934
50053
|
}
|
|
49935
|
-
console.log(
|
|
50054
|
+
console.log(chalk12.cyan.bold(`
|
|
49936
50055
|
\u{1F6E1}\uFE0F Node9 Doctor v${version2}
|
|
49937
50056
|
`));
|
|
49938
50057
|
section("Binary");
|
|
@@ -50013,7 +50132,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
50013
50132
|
}
|
|
50014
50133
|
if (notConfigured.length > 0) {
|
|
50015
50134
|
console.log(
|
|
50016
|
-
|
|
50135
|
+
chalk12.gray(
|
|
50017
50136
|
` \xB7 Not configured: ${notConfigured.join(", ")} \u2014 run \`node9 agents add <agent>\` if you use one`
|
|
50018
50137
|
)
|
|
50019
50138
|
);
|
|
@@ -50043,7 +50162,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
50043
50162
|
if (cause) {
|
|
50044
50163
|
const suffix = cause.detail ? ` \u2014 ${cause.detail}` : "";
|
|
50045
50164
|
console.log(
|
|
50046
|
-
|
|
50165
|
+
chalk12.gray(
|
|
50047
50166
|
` ${cause.label} ${agoLabel(cause.at.toISOString())}: ${cause.kind}${suffix}`
|
|
50048
50167
|
)
|
|
50049
50168
|
);
|
|
@@ -50112,9 +50231,9 @@ function registerDoctorCommand(program2, version2) {
|
|
|
50112
50231
|
}
|
|
50113
50232
|
console.log("");
|
|
50114
50233
|
if (failures === 0) {
|
|
50115
|
-
console.log(
|
|
50234
|
+
console.log(chalk12.green.bold(" All checks passed. Node9 is ready.\n"));
|
|
50116
50235
|
} else {
|
|
50117
|
-
console.log(
|
|
50236
|
+
console.log(chalk12.red.bold(` ${failures} check(s) failed. See hints above.
|
|
50118
50237
|
`));
|
|
50119
50238
|
process.exit(1);
|
|
50120
50239
|
}
|
|
@@ -50123,7 +50242,7 @@ function registerDoctorCommand(program2, version2) {
|
|
|
50123
50242
|
|
|
50124
50243
|
// src/cli/commands/audit.ts
|
|
50125
50244
|
init_decision();
|
|
50126
|
-
import
|
|
50245
|
+
import chalk13 from "chalk";
|
|
50127
50246
|
import fs55 from "fs";
|
|
50128
50247
|
import path53 from "path";
|
|
50129
50248
|
import os49 from "os";
|
|
@@ -50142,7 +50261,7 @@ function registerAuditCommand(program2) {
|
|
|
50142
50261
|
const logPath = path53.join(os49.homedir(), ".node9", "audit.log");
|
|
50143
50262
|
if (!fs55.existsSync(logPath)) {
|
|
50144
50263
|
console.log(
|
|
50145
|
-
|
|
50264
|
+
chalk13.yellow("No audit logs found. Run node9 with an agent to generate entries.")
|
|
50146
50265
|
);
|
|
50147
50266
|
return;
|
|
50148
50267
|
}
|
|
@@ -50174,38 +50293,38 @@ function registerAuditCommand(program2) {
|
|
|
50174
50293
|
return;
|
|
50175
50294
|
}
|
|
50176
50295
|
if (entries.length === 0) {
|
|
50177
|
-
console.log(
|
|
50296
|
+
console.log(chalk13.yellow("No matching audit entries."));
|
|
50178
50297
|
return;
|
|
50179
50298
|
}
|
|
50180
50299
|
console.log(
|
|
50181
50300
|
`
|
|
50182
|
-
${
|
|
50301
|
+
${chalk13.bold("Node9 Audit Log")} ${chalk13.dim(`(${entries.length} entries)`)}`
|
|
50183
50302
|
);
|
|
50184
|
-
console.log(
|
|
50303
|
+
console.log(chalk13.dim(" " + "\u2500".repeat(65)));
|
|
50185
50304
|
console.log(
|
|
50186
50305
|
` ${"Time".padEnd(12)} ${"Tool".padEnd(18)} ${"Result".padEnd(10)} ${"By".padEnd(15)} Agent`
|
|
50187
50306
|
);
|
|
50188
|
-
console.log(
|
|
50307
|
+
console.log(chalk13.dim(" " + "\u2500".repeat(65)));
|
|
50189
50308
|
for (const e of entries) {
|
|
50190
50309
|
const time = formatRelativeTime(String(e.ts)).padEnd(12);
|
|
50191
50310
|
const tool = String(e.tool).slice(0, 17).padEnd(18);
|
|
50192
|
-
const result = e.view.outcome === "allow" ?
|
|
50311
|
+
const result = e.view.outcome === "allow" ? chalk13.green(e.view.label.padEnd(14)) : e.view.outcome === "deny" ? chalk13.red(e.view.label.padEnd(14)) : e.view.outcome === "observe" ? chalk13.yellow(e.view.label.padEnd(14)) : chalk13.gray(e.view.label.padEnd(14));
|
|
50193
50312
|
const checker = String(e.checkedBy || "unknown").slice(0, 14).padEnd(15);
|
|
50194
50313
|
const agent = String(e.agent || "unknown");
|
|
50195
50314
|
console.log(` ${time} ${tool} ${result} ${checker} ${agent}`);
|
|
50196
50315
|
}
|
|
50197
50316
|
const allowed = entries.filter((e) => e.view.outcome === "allow").length;
|
|
50198
50317
|
const denied = entries.filter((e) => e.view.outcome === "deny").length;
|
|
50199
|
-
console.log(
|
|
50318
|
+
console.log(chalk13.dim(" " + "\u2500".repeat(65)));
|
|
50200
50319
|
console.log(
|
|
50201
|
-
` ${entries.length} entries | ${
|
|
50320
|
+
` ${entries.length} entries | ${chalk13.green(allowed + " allowed")} | ${chalk13.red(denied + " denied")}
|
|
50202
50321
|
`
|
|
50203
50322
|
);
|
|
50204
50323
|
});
|
|
50205
50324
|
}
|
|
50206
50325
|
|
|
50207
50326
|
// src/cli/commands/report.ts
|
|
50208
|
-
import
|
|
50327
|
+
import chalk14 from "chalk";
|
|
50209
50328
|
|
|
50210
50329
|
// src/cli/aggregate/report-audit.ts
|
|
50211
50330
|
init_costSync();
|
|
@@ -50982,7 +51101,7 @@ function barStr(value, max, width) {
|
|
|
50982
51101
|
function colorBar(value, max, width) {
|
|
50983
51102
|
const s = barStr(value, max, width);
|
|
50984
51103
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
50985
|
-
return
|
|
51104
|
+
return chalk14.cyan(s.slice(0, filled)) + chalk14.dim(s.slice(filled));
|
|
50986
51105
|
}
|
|
50987
51106
|
function fmtDate(d) {
|
|
50988
51107
|
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
@@ -51008,14 +51127,14 @@ function registerReportCommand(program2) {
|
|
|
51008
51127
|
if (data.unackedDlp > 0 && !options.json) {
|
|
51009
51128
|
console.log("");
|
|
51010
51129
|
console.log(
|
|
51011
|
-
|
|
51130
|
+
chalk14.bgRed.white.bold(
|
|
51012
51131
|
` \u26A0\uFE0F DLP ALERT: ${data.unackedDlp} secret${data.unackedDlp !== 1 ? "s" : ""} found in Claude response text `
|
|
51013
|
-
) + " " +
|
|
51132
|
+
) + " " + chalk14.yellow("\u2192 run: node9 dlp")
|
|
51014
51133
|
);
|
|
51015
51134
|
}
|
|
51016
51135
|
if (!hasAuditFile && !options.json) {
|
|
51017
51136
|
console.log(
|
|
51018
|
-
|
|
51137
|
+
chalk14.yellow("\n No audit data found. Run node9 with Claude Code to generate entries.\n")
|
|
51019
51138
|
);
|
|
51020
51139
|
return;
|
|
51021
51140
|
}
|
|
@@ -51025,7 +51144,7 @@ function registerReportCommand(program2) {
|
|
|
51025
51144
|
return;
|
|
51026
51145
|
}
|
|
51027
51146
|
if (data.total === 0) {
|
|
51028
|
-
console.log(
|
|
51147
|
+
console.log(chalk14.yellow(`
|
|
51029
51148
|
No activity for period "${period}".
|
|
51030
51149
|
`));
|
|
51031
51150
|
return;
|
|
@@ -51083,7 +51202,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51083
51202
|
const BAR = Math.max(6, Math.min(14, COL - LABEL - 8));
|
|
51084
51203
|
const TOOL_COUNT_W = Math.max(...topTools.map(([, v]) => num2(v.calls).length), 1);
|
|
51085
51204
|
const BLOCK_COUNT_W = Math.max(...topBlocks.map(([, v]) => num2(v).length), 1);
|
|
51086
|
-
const line =
|
|
51205
|
+
const line = chalk14.dim("\u2500".repeat(W - 2));
|
|
51087
51206
|
const periodLabel = {
|
|
51088
51207
|
today: "Today",
|
|
51089
51208
|
"7d": "Last 7 Days",
|
|
@@ -51093,7 +51212,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51093
51212
|
};
|
|
51094
51213
|
console.log("");
|
|
51095
51214
|
console.log(
|
|
51096
|
-
" " +
|
|
51215
|
+
" " + chalk14.bold.cyan("\u{1F6E1} node9 Report") + chalk14.dim(" \xB7 ") + chalk14.white(periodLabel[period]) + chalk14.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + chalk14.dim(` ${num2(total)} events`) + (excludeTests ? chalk14.dim(` \u2013tests (\u2013${excludedTests})`) : "")
|
|
51097
51216
|
);
|
|
51098
51217
|
console.log(" " + line);
|
|
51099
51218
|
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
@@ -51102,73 +51221,73 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51102
51221
|
if (priorBlockRate === null) return "";
|
|
51103
51222
|
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
51104
51223
|
if (delta === 0) return "";
|
|
51105
|
-
return " " + (delta > 0 ?
|
|
51224
|
+
return " " + (delta > 0 ? chalk14.red(`\u25B2${delta}%`) : chalk14.green(`\u25BC${Math.abs(delta)}%`)) + chalk14.dim(" vs prior");
|
|
51106
51225
|
})();
|
|
51107
51226
|
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
51108
51227
|
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
51109
|
-
const ratioLabel = reads > 0 ?
|
|
51110
|
-
const testLabel = testPasses + testFails > 0 ?
|
|
51228
|
+
const ratioLabel = reads > 0 ? chalk14.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk14.dim("edit/read \u2013");
|
|
51229
|
+
const testLabel = testPasses + testFails > 0 ? chalk14.dim("tests ") + chalk14.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk14.red(`${testFails}\u2717`) : "") : chalk14.dim("tests \u2013");
|
|
51111
51230
|
console.log("");
|
|
51112
|
-
console.log(" " +
|
|
51113
|
-
console.log(" " +
|
|
51231
|
+
console.log(" " + chalk14.bold("Protection Summary"));
|
|
51232
|
+
console.log(" " + chalk14.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
51114
51233
|
console.log(
|
|
51115
|
-
" " +
|
|
51234
|
+
" " + chalk14.dim("Intercepted") + " " + chalk14.white(num2(total)) + chalk14.dim(" tool calls")
|
|
51116
51235
|
);
|
|
51117
51236
|
console.log("");
|
|
51118
51237
|
const COL1 = 18;
|
|
51119
51238
|
const summaryRow = (icon, label2, count, note, colorFn = (s) => s) => {
|
|
51120
51239
|
const countStr = colorFn(num2(count));
|
|
51121
|
-
const noteStr = note ?
|
|
51122
|
-
console.log(" " + icon + " " +
|
|
51240
|
+
const noteStr = note ? chalk14.dim(" " + note) : "";
|
|
51241
|
+
console.log(" " + icon + " " + chalk14.white(label2.padEnd(COL1)) + countStr + noteStr);
|
|
51123
51242
|
};
|
|
51124
51243
|
summaryRow(
|
|
51125
|
-
userApproved > 0 ?
|
|
51244
|
+
userApproved > 0 ? chalk14.green("\u2705") : chalk14.dim("\u2705"),
|
|
51126
51245
|
"User approved",
|
|
51127
51246
|
userApproved,
|
|
51128
51247
|
userApproved === 0 ? "no popups this period" : void 0,
|
|
51129
|
-
userApproved > 0 ? (s) =>
|
|
51248
|
+
userApproved > 0 ? (s) => chalk14.green(s) : (s) => chalk14.dim(s)
|
|
51130
51249
|
);
|
|
51131
51250
|
summaryRow(
|
|
51132
|
-
userDenied > 0 ?
|
|
51251
|
+
userDenied > 0 ? chalk14.red("\u{1F6AB}") : chalk14.dim("\u{1F6AB}"),
|
|
51133
51252
|
"User denied",
|
|
51134
51253
|
userDenied,
|
|
51135
51254
|
void 0,
|
|
51136
|
-
userDenied > 0 ? (s) =>
|
|
51255
|
+
userDenied > 0 ? (s) => chalk14.red(s) : (s) => chalk14.dim(s)
|
|
51137
51256
|
);
|
|
51138
51257
|
summaryRow(
|
|
51139
|
-
timedOut > 0 ?
|
|
51258
|
+
timedOut > 0 ? chalk14.yellow("\u23F1") : chalk14.dim("\u23F1"),
|
|
51140
51259
|
"Timed out",
|
|
51141
51260
|
timedOut,
|
|
51142
51261
|
timedOut > 0 ? "no approval response" : void 0,
|
|
51143
|
-
timedOut > 0 ? (s) =>
|
|
51262
|
+
timedOut > 0 ? (s) => chalk14.yellow(s) : (s) => chalk14.dim(s)
|
|
51144
51263
|
);
|
|
51145
51264
|
summaryRow(
|
|
51146
|
-
hardBlocked > 0 ?
|
|
51265
|
+
hardBlocked > 0 ? chalk14.red("\u{1F6D1}") : chalk14.dim("\u{1F6D1}"),
|
|
51147
51266
|
"Auto-blocked",
|
|
51148
51267
|
hardBlocked,
|
|
51149
51268
|
void 0,
|
|
51150
|
-
hardBlocked > 0 ? (s) =>
|
|
51269
|
+
hardBlocked > 0 ? (s) => chalk14.red(s) : (s) => chalk14.dim(s)
|
|
51151
51270
|
);
|
|
51152
51271
|
summaryRow(
|
|
51153
|
-
dlpBlocked > 0 ?
|
|
51272
|
+
dlpBlocked > 0 ? chalk14.yellow("\u{1F6A8}") : chalk14.dim("\u{1F6A8}"),
|
|
51154
51273
|
"DLP blocked",
|
|
51155
51274
|
dlpBlocked,
|
|
51156
51275
|
void 0,
|
|
51157
|
-
dlpBlocked > 0 ? (s) =>
|
|
51276
|
+
dlpBlocked > 0 ? (s) => chalk14.yellow(s) : (s) => chalk14.dim(s)
|
|
51158
51277
|
);
|
|
51159
51278
|
summaryRow(
|
|
51160
|
-
observeDlp > 0 ?
|
|
51279
|
+
observeDlp > 0 ? chalk14.blue("\u{1F441}") : chalk14.dim("\u{1F441}"),
|
|
51161
51280
|
"DLP (observe)",
|
|
51162
51281
|
observeDlp,
|
|
51163
51282
|
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
51164
|
-
observeDlp > 0 ? (s) =>
|
|
51283
|
+
observeDlp > 0 ? (s) => chalk14.blue(s) : (s) => chalk14.dim(s)
|
|
51165
51284
|
);
|
|
51166
51285
|
summaryRow(
|
|
51167
|
-
loopHits > 0 ?
|
|
51286
|
+
loopHits > 0 ? chalk14.yellow("\u{1F504}") : chalk14.dim("\u{1F504}"),
|
|
51168
51287
|
"Loops detected",
|
|
51169
51288
|
loopHits,
|
|
51170
51289
|
void 0,
|
|
51171
|
-
loopHits > 0 ? (s) =>
|
|
51290
|
+
loopHits > 0 ? (s) => chalk14.yellow(s) : (s) => chalk14.dim(s)
|
|
51172
51291
|
);
|
|
51173
51292
|
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
51174
51293
|
console.log("");
|
|
@@ -51177,12 +51296,12 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51177
51296
|
console.log("");
|
|
51178
51297
|
{
|
|
51179
51298
|
const d = dimensions;
|
|
51180
|
-
console.log(" " +
|
|
51181
|
-
console.log(" " +
|
|
51299
|
+
console.log(" " + chalk14.bold("By dimension") + chalk14.dim(" what fired, per governed area"));
|
|
51300
|
+
console.log(" " + chalk14.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
51182
51301
|
const dimRow = (icon, label2, active, detail) => {
|
|
51183
|
-
const l = active ?
|
|
51184
|
-
const v = active ? detail :
|
|
51185
|
-
console.log(" " + (active ? icon :
|
|
51302
|
+
const l = active ? chalk14.white(label2.padEnd(14)) : chalk14.dim(label2.padEnd(14));
|
|
51303
|
+
const v = active ? detail : chalk14.dim(detail);
|
|
51304
|
+
console.log(" " + (active ? icon : chalk14.dim(icon)) + " " + l + v);
|
|
51186
51305
|
};
|
|
51187
51306
|
dimRow("\u{1F310}", "Network", d.network.blocked > 0, `${num2(d.network.blocked)} egress blocked`);
|
|
51188
51307
|
dimRow(
|
|
@@ -51217,9 +51336,9 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51217
51336
|
const toolHeaderRaw = "Top Tools";
|
|
51218
51337
|
const blockHeaderRaw = "Top Blocks";
|
|
51219
51338
|
console.log(
|
|
51220
|
-
" " +
|
|
51339
|
+
" " + chalk14.bold(toolHeaderRaw) + " ".repeat(COL - toolHeaderRaw.length) + " " + chalk14.bold(blockHeaderRaw)
|
|
51221
51340
|
);
|
|
51222
|
-
console.log(" " +
|
|
51341
|
+
console.log(" " + chalk14.dim("\u2500".repeat(COL)) + " " + chalk14.dim("\u2500".repeat(COL)));
|
|
51223
51342
|
const rows = Math.max(topTools.length, topBlocks.length, 1);
|
|
51224
51343
|
for (let i = 0; i < rows; i++) {
|
|
51225
51344
|
let leftStyled = " ".repeat(COL);
|
|
@@ -51230,7 +51349,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51230
51349
|
const b = colorBar(calls, maxTool, BAR);
|
|
51231
51350
|
const rawLen = LABEL + BAR + 1 + TOOL_COUNT_W;
|
|
51232
51351
|
const pad = Math.max(0, COL - rawLen);
|
|
51233
|
-
leftStyled =
|
|
51352
|
+
leftStyled = chalk14.white(label2.padEnd(LABEL)) + b + " " + chalk14.white(countStr) + " ".repeat(pad);
|
|
51234
51353
|
}
|
|
51235
51354
|
let rightStyled = "";
|
|
51236
51355
|
if (i < topBlocks.length) {
|
|
@@ -51239,33 +51358,33 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51239
51358
|
const label2 = readable.length > LABEL - 1 ? readable.slice(0, LABEL - 2) + "\u2026" : readable;
|
|
51240
51359
|
const countStr = num2(count).padStart(BLOCK_COUNT_W);
|
|
51241
51360
|
const b = colorBar(count, maxBlock, BAR);
|
|
51242
|
-
rightStyled =
|
|
51361
|
+
rightStyled = chalk14.white(label2.padEnd(LABEL)) + b + " " + chalk14.red(countStr);
|
|
51243
51362
|
}
|
|
51244
51363
|
console.log(" " + leftStyled + " " + rightStyled);
|
|
51245
51364
|
}
|
|
51246
51365
|
if (topBlocks.length === 0) {
|
|
51247
|
-
console.log(" " + " ".repeat(COL) + " " +
|
|
51366
|
+
console.log(" " + " ".repeat(COL) + " " + chalk14.dim("nothing blocked \u2713"));
|
|
51248
51367
|
}
|
|
51249
51368
|
if (agentMap.size >= 1) {
|
|
51250
51369
|
console.log("");
|
|
51251
|
-
console.log(" " +
|
|
51252
|
-
console.log(" " +
|
|
51370
|
+
console.log(" " + chalk14.bold("Agents"));
|
|
51371
|
+
console.log(" " + chalk14.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
51253
51372
|
const maxAgent = Math.max(...agentMap.values(), 1);
|
|
51254
51373
|
for (const [agent, count] of [...agentMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
51255
51374
|
const label2 = agent.slice(0, LABEL - 1);
|
|
51256
51375
|
const b = colorBar(count, maxAgent, BAR);
|
|
51257
|
-
console.log(" " +
|
|
51376
|
+
console.log(" " + chalk14.white(label2.padEnd(LABEL)) + b + " " + chalk14.white(num2(count)));
|
|
51258
51377
|
}
|
|
51259
51378
|
}
|
|
51260
51379
|
if (mcpMap.size > 0) {
|
|
51261
51380
|
console.log("");
|
|
51262
|
-
console.log(" " +
|
|
51263
|
-
console.log(" " +
|
|
51381
|
+
console.log(" " + chalk14.bold("MCP Servers"));
|
|
51382
|
+
console.log(" " + chalk14.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
51264
51383
|
const maxMcp = Math.max(...mcpMap.values(), 1);
|
|
51265
51384
|
for (const [server, count] of [...mcpMap.entries()].sort((a, b) => b[1] - a[1])) {
|
|
51266
51385
|
const label2 = server.slice(0, LABEL - 1).padEnd(LABEL);
|
|
51267
51386
|
const b = colorBar(count, maxMcp, BAR);
|
|
51268
|
-
console.log(" " +
|
|
51387
|
+
console.log(" " + chalk14.white(label2) + b + " " + chalk14.white(num2(count)));
|
|
51269
51388
|
}
|
|
51270
51389
|
}
|
|
51271
51390
|
if (hourMap.size > 0) {
|
|
@@ -51276,23 +51395,23 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51276
51395
|
return BLOCKS[Math.round(v / maxHour * 8)];
|
|
51277
51396
|
}).join("");
|
|
51278
51397
|
console.log("");
|
|
51279
|
-
console.log(" " +
|
|
51280
|
-
console.log(" " +
|
|
51281
|
-
console.log(" " +
|
|
51398
|
+
console.log(" " + chalk14.bold("Hour of Day") + chalk14.dim(" (local, 0h \u2013 23h)"));
|
|
51399
|
+
console.log(" " + chalk14.cyan(bar));
|
|
51400
|
+
console.log(" " + chalk14.dim("0h" + " ".repeat(10) + "12h" + " ".repeat(7) + "23h"));
|
|
51282
51401
|
}
|
|
51283
51402
|
if (dailyList.length > 1) {
|
|
51284
51403
|
console.log("");
|
|
51285
|
-
console.log(" " +
|
|
51286
|
-
console.log(" " +
|
|
51404
|
+
console.log(" " + chalk14.bold("Daily Activity"));
|
|
51405
|
+
console.log(" " + chalk14.dim("\u2500".repeat(W - 2)));
|
|
51287
51406
|
const DAY_BAR = Math.max(8, Math.min(30, W - 36));
|
|
51288
51407
|
for (const [dateKey, { calls, blocked: db }] of dailyList) {
|
|
51289
51408
|
const label2 = fmtDate(dateKey).padEnd(10);
|
|
51290
51409
|
const b = colorBar(calls, maxDaily, DAY_BAR);
|
|
51291
51410
|
const dayCost = costByDay.get(dateKey);
|
|
51292
|
-
const costNote = dayCost ?
|
|
51293
|
-
const blockNote = db > 0 ?
|
|
51411
|
+
const costNote = dayCost ? chalk14.magenta(` ${fmtCost2(dayCost)}`) : "";
|
|
51412
|
+
const blockNote = db > 0 ? chalk14.red(` ${db} blocked`) : "";
|
|
51294
51413
|
console.log(
|
|
51295
|
-
" " +
|
|
51414
|
+
" " + chalk14.dim(label2) + " " + b + " " + chalk14.white(num2(calls)) + blockNote + costNote
|
|
51296
51415
|
);
|
|
51297
51416
|
}
|
|
51298
51417
|
}
|
|
@@ -51300,26 +51419,26 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51300
51419
|
if (totalTokens > 0) {
|
|
51301
51420
|
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
51302
51421
|
console.log("");
|
|
51303
|
-
console.log(" " +
|
|
51304
|
-
console.log(" " +
|
|
51422
|
+
console.log(" " + chalk14.bold("Tokens") + " " + chalk14.dim(`${num2(totalTokens)} total`));
|
|
51423
|
+
console.log(" " + chalk14.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
51305
51424
|
const TOK_BAR = Math.max(6, Math.min(20, W - 30));
|
|
51306
51425
|
const TOK_LABEL = 14;
|
|
51307
51426
|
const maxNonCache = Math.max(costInputTokens, costOutputTokens, costCacheWrite, 1);
|
|
51308
51427
|
const nonCacheRows = [
|
|
51309
|
-
["Input", costInputTokens,
|
|
51310
|
-
["Output", costOutputTokens,
|
|
51311
|
-
["Cache write", costCacheWrite,
|
|
51428
|
+
["Input", costInputTokens, chalk14.cyan(num2(costInputTokens))],
|
|
51429
|
+
["Output", costOutputTokens, chalk14.white(num2(costOutputTokens))],
|
|
51430
|
+
["Cache write", costCacheWrite, chalk14.yellow(num2(costCacheWrite))]
|
|
51312
51431
|
];
|
|
51313
51432
|
for (const [label2, count, colored] of nonCacheRows) {
|
|
51314
51433
|
if (count === 0) continue;
|
|
51315
51434
|
const b = colorBar(count, maxNonCache, TOK_BAR);
|
|
51316
|
-
console.log(" " +
|
|
51435
|
+
console.log(" " + chalk14.white(label2.padEnd(TOK_LABEL)) + b + " " + colored);
|
|
51317
51436
|
}
|
|
51318
51437
|
if (costCacheRead > 0) {
|
|
51319
51438
|
const cacheBar = colorBar(costCacheRead, costCacheRead, TOK_BAR);
|
|
51320
|
-
const pct = cacheHitPct > 0 ?
|
|
51439
|
+
const pct = cacheHitPct > 0 ? chalk14.dim(` ${cacheHitPct}% hit rate`) : "";
|
|
51321
51440
|
console.log(
|
|
51322
|
-
" " +
|
|
51441
|
+
" " + chalk14.white("Cache read".padEnd(TOK_LABEL)) + cacheBar + " " + chalk14.green(num2(costCacheRead)) + pct
|
|
51323
51442
|
);
|
|
51324
51443
|
}
|
|
51325
51444
|
}
|
|
@@ -51328,13 +51447,13 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51328
51447
|
const avgPerDay = costUSD / periodDays;
|
|
51329
51448
|
const cacheHitPct = costInputTokens + costCacheRead > 0 ? Math.round(costCacheRead / (costInputTokens + costCacheRead) * 100) : 0;
|
|
51330
51449
|
const costHeaderRight = [
|
|
51331
|
-
|
|
51332
|
-
|
|
51333
|
-
cacheHitPct > 0 ?
|
|
51334
|
-
].filter(Boolean).join(
|
|
51450
|
+
chalk14.yellow(fmtCost2(costUSD)),
|
|
51451
|
+
chalk14.dim(`avg ${fmtCost2(avgPerDay)}/day`),
|
|
51452
|
+
cacheHitPct > 0 ? chalk14.dim(`${cacheHitPct}% cache hit`) : null
|
|
51453
|
+
].filter(Boolean).join(chalk14.dim(" \xB7 "));
|
|
51335
51454
|
console.log("");
|
|
51336
|
-
console.log(" " +
|
|
51337
|
-
console.log(" " +
|
|
51455
|
+
console.log(" " + chalk14.bold("Cost") + " " + costHeaderRight);
|
|
51456
|
+
console.log(" " + chalk14.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
51338
51457
|
if (codexUSD > 0)
|
|
51339
51458
|
costByModel.set("codex (openai)", (costByModel.get("codex (openai)") ?? 0) + codexUSD);
|
|
51340
51459
|
if (geminiUSD > 0)
|
|
@@ -51347,35 +51466,35 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51347
51466
|
const label2 = model.length > MODEL_LABEL - 1 ? model.slice(0, MODEL_LABEL - 2) + "\u2026" : model;
|
|
51348
51467
|
const b = colorBar(cost, maxModelCost, MODEL_BAR);
|
|
51349
51468
|
console.log(
|
|
51350
|
-
" " +
|
|
51469
|
+
" " + chalk14.white(label2.padEnd(MODEL_LABEL)) + b + " " + chalk14.yellow(fmtCost2(cost))
|
|
51351
51470
|
);
|
|
51352
51471
|
}
|
|
51353
51472
|
}
|
|
51354
51473
|
if (responseDlpEntries.length > 0) {
|
|
51355
51474
|
console.log("");
|
|
51356
51475
|
console.log(
|
|
51357
|
-
" " +
|
|
51476
|
+
" " + chalk14.red.bold("\u26A0\uFE0F Response DLP") + chalk14.dim(" \xB7 ") + chalk14.red(
|
|
51358
51477
|
`${responseDlpEntries.length} secret${responseDlpEntries.length !== 1 ? "s" : ""} found in Claude response text`
|
|
51359
51478
|
)
|
|
51360
51479
|
);
|
|
51361
|
-
console.log(" " +
|
|
51480
|
+
console.log(" " + chalk14.dim("\u2500".repeat(Math.min(60, W - 4))));
|
|
51362
51481
|
console.log(
|
|
51363
|
-
" " +
|
|
51482
|
+
" " + chalk14.yellow("These were NOT blocked \u2014 Claude included them in response prose.")
|
|
51364
51483
|
);
|
|
51365
|
-
console.log(" " +
|
|
51484
|
+
console.log(" " + chalk14.yellow("Rotate affected keys immediately."));
|
|
51366
51485
|
for (const e of responseDlpEntries.slice(0, 5)) {
|
|
51367
|
-
const ts =
|
|
51368
|
-
const pattern =
|
|
51369
|
-
const sample =
|
|
51486
|
+
const ts = chalk14.dim(fmtDate(e.ts) + " ");
|
|
51487
|
+
const pattern = chalk14.red(e.dlpPattern ?? "DLP");
|
|
51488
|
+
const sample = chalk14.gray(e.dlpSample ?? "");
|
|
51370
51489
|
console.log(` ${ts}${pattern} ${sample}`);
|
|
51371
51490
|
}
|
|
51372
51491
|
if (responseDlpEntries.length > 5) {
|
|
51373
|
-
console.log(
|
|
51492
|
+
console.log(chalk14.dim(` \u2026 and ${responseDlpEntries.length - 5} more`));
|
|
51374
51493
|
}
|
|
51375
51494
|
}
|
|
51376
51495
|
console.log("");
|
|
51377
51496
|
console.log(
|
|
51378
|
-
" " +
|
|
51497
|
+
" " + chalk14.dim("node9 audit --deny") + chalk14.dim(" \xB7 ") + chalk14.dim("node9 report --period today|7d|30d|month --no-tests")
|
|
51379
51498
|
);
|
|
51380
51499
|
console.log("");
|
|
51381
51500
|
}
|
|
@@ -51384,7 +51503,7 @@ function renderTerminalReport(data, responseDlpEntries, excludeTests) {
|
|
|
51384
51503
|
init_daemon();
|
|
51385
51504
|
init_startup_log();
|
|
51386
51505
|
init_daemon2();
|
|
51387
|
-
import
|
|
51506
|
+
import chalk15 from "chalk";
|
|
51388
51507
|
import { spawn as spawn5 } from "child_process";
|
|
51389
51508
|
import fs57 from "fs";
|
|
51390
51509
|
var VALID_ACTIONS = "start | stop | restart | status | install | uninstall";
|
|
@@ -51398,26 +51517,26 @@ function registerDaemonCommand(program2) {
|
|
|
51398
51517
|
if (cmd === "install") {
|
|
51399
51518
|
const result = installDaemonService();
|
|
51400
51519
|
if (!result.ok) {
|
|
51401
|
-
console.error(
|
|
51520
|
+
console.error(chalk15.red(`\u2717 ${result.reason}`));
|
|
51402
51521
|
process.exit(1);
|
|
51403
51522
|
}
|
|
51404
51523
|
if (result.alreadyInstalled) {
|
|
51405
|
-
console.log(
|
|
51524
|
+
console.log(chalk15.green(`\u2713 Daemon service reinstalled (${result.platform})`));
|
|
51406
51525
|
} else {
|
|
51407
|
-
console.log(
|
|
51408
|
-
console.log(
|
|
51526
|
+
console.log(chalk15.green(`\u2713 Daemon installed as login service (${result.platform})`));
|
|
51527
|
+
console.log(chalk15.gray(" The daemon will now start automatically on login."));
|
|
51409
51528
|
}
|
|
51410
51529
|
process.exit(0);
|
|
51411
51530
|
}
|
|
51412
51531
|
if (cmd === "uninstall") {
|
|
51413
51532
|
const result = uninstallDaemonService();
|
|
51414
51533
|
if (!result.ok) {
|
|
51415
|
-
console.error(
|
|
51534
|
+
console.error(chalk15.red(`\u2717 ${result.reason}`));
|
|
51416
51535
|
process.exit(1);
|
|
51417
51536
|
}
|
|
51418
|
-
console.log(
|
|
51419
|
-
console.log(
|
|
51420
|
-
console.log(
|
|
51537
|
+
console.log(chalk15.green(`\u2713 Daemon service removed (${result.platform})`));
|
|
51538
|
+
console.log(chalk15.gray(" The daemon will no longer start automatically on login."));
|
|
51539
|
+
console.log(chalk15.gray(" To stop the running daemon: node9 daemon stop"));
|
|
51421
51540
|
process.exit(0);
|
|
51422
51541
|
}
|
|
51423
51542
|
if (cmd === "stop") return stopDaemon();
|
|
@@ -51446,24 +51565,24 @@ function registerDaemonCommand(program2) {
|
|
|
51446
51565
|
}
|
|
51447
51566
|
}
|
|
51448
51567
|
if (child.pid) {
|
|
51449
|
-
console.log(
|
|
51450
|
-
console.log(
|
|
51568
|
+
console.log(chalk15.green(`\u2713 Daemon relaunching (PID ${child.pid})`));
|
|
51569
|
+
console.log(chalk15.gray(" Confirm with: node9 status"));
|
|
51451
51570
|
} else {
|
|
51452
|
-
console.error(
|
|
51571
|
+
console.error(chalk15.red("\u2717 Failed to restart daemon \u2014 spawn returned no PID"));
|
|
51453
51572
|
process.exit(1);
|
|
51454
51573
|
}
|
|
51455
51574
|
process.exit(0);
|
|
51456
51575
|
}
|
|
51457
51576
|
if (cmd === "status") return daemonStatus();
|
|
51458
51577
|
if (cmd !== "start" && action !== void 0) {
|
|
51459
|
-
console.error(
|
|
51578
|
+
console.error(chalk15.red(`Unknown daemon action: "${action}". Use: ${VALID_ACTIONS}`));
|
|
51460
51579
|
process.exit(1);
|
|
51461
51580
|
}
|
|
51462
51581
|
if (options.watch) {
|
|
51463
51582
|
process.env.NODE9_WATCH_MODE = "1";
|
|
51464
51583
|
setTimeout(() => {
|
|
51465
51584
|
console.log(
|
|
51466
|
-
|
|
51585
|
+
chalk15.cyan(`\u{1F6F0}\uFE0F Flight Recorder running. Open another terminal and run:`) + chalk15.bold(" node9 tail")
|
|
51467
51586
|
);
|
|
51468
51587
|
}, 600);
|
|
51469
51588
|
startDaemon();
|
|
@@ -51485,10 +51604,10 @@ function registerDaemonCommand(program2) {
|
|
|
51485
51604
|
);
|
|
51486
51605
|
child.unref();
|
|
51487
51606
|
console.log(
|
|
51488
|
-
|
|
51607
|
+
chalk15.green(`
|
|
51489
51608
|
\u{1F6E1}\uFE0F Node9 daemon launching in background (PID ${child.pid})`)
|
|
51490
51609
|
);
|
|
51491
|
-
console.log(
|
|
51610
|
+
console.log(chalk15.gray(" Confirm with: node9 status"));
|
|
51492
51611
|
} finally {
|
|
51493
51612
|
if (startupFd !== void 0) {
|
|
51494
51613
|
try {
|
|
@@ -51511,27 +51630,27 @@ init_build_id();
|
|
|
51511
51630
|
init_agent_wiring();
|
|
51512
51631
|
init_sync();
|
|
51513
51632
|
init_service();
|
|
51514
|
-
import
|
|
51633
|
+
import chalk16 from "chalk";
|
|
51515
51634
|
import fs58 from "fs";
|
|
51516
51635
|
import path55 from "path";
|
|
51517
51636
|
import os51 from "os";
|
|
51518
51637
|
function printAgentSection(label2, hookPairs, wrapped) {
|
|
51519
|
-
console.log(
|
|
51638
|
+
console.log(chalk16.bold(` ${label2}`));
|
|
51520
51639
|
for (const { name, present } of hookPairs) {
|
|
51521
51640
|
if (present) {
|
|
51522
|
-
console.log(
|
|
51641
|
+
console.log(chalk16.green(` \u2713 ${name}`));
|
|
51523
51642
|
} else {
|
|
51524
|
-
console.log(
|
|
51643
|
+
console.log(chalk16.red(` \u2717 ${name}`) + chalk16.gray(" (not wired)"));
|
|
51525
51644
|
}
|
|
51526
51645
|
}
|
|
51527
51646
|
if (wrapped === null) return;
|
|
51528
51647
|
if (wrapped.length > 0) {
|
|
51529
|
-
console.log(
|
|
51648
|
+
console.log(chalk16.cyan(` MCP proxied:`));
|
|
51530
51649
|
for (const entry of wrapped) {
|
|
51531
|
-
console.log(
|
|
51650
|
+
console.log(chalk16.gray(` \u2022 ${entry}`));
|
|
51532
51651
|
}
|
|
51533
51652
|
} else {
|
|
51534
|
-
console.log(
|
|
51653
|
+
console.log(chalk16.gray(` MCP proxied: none`));
|
|
51535
51654
|
}
|
|
51536
51655
|
}
|
|
51537
51656
|
function registerStatusCommand(program2) {
|
|
@@ -51542,29 +51661,29 @@ function registerStatusCommand(program2) {
|
|
|
51542
51661
|
const settings = mergedConfig.settings;
|
|
51543
51662
|
console.log("");
|
|
51544
51663
|
if (creds && settings.approvers.cloud) {
|
|
51545
|
-
console.log(
|
|
51664
|
+
console.log(chalk16.green(" \u25CF Agent mode") + chalk16.gray(" \u2014 cloud team policy enforced"));
|
|
51546
51665
|
const health = readSyncHealth();
|
|
51547
51666
|
if (isPolicyStale(Date.now(), health)) {
|
|
51548
51667
|
const when = health.lastCheckedAt ? `last synced ${agoLabel(health.lastCheckedAt)}` : "never synced";
|
|
51549
51668
|
const fails = health.consecutiveFailures > 0 ? ` \xB7 ${health.consecutiveFailures} failed attempt${health.consecutiveFailures === 1 ? "" : "s"}${health.lastError ? ` (${health.lastError})` : ""}` : "";
|
|
51550
|
-
console.log(
|
|
51551
|
-
console.log(
|
|
51669
|
+
console.log(chalk16.yellow(" \u26A0 Policy sync STALE") + chalk16.gray(` \u2014 ${when}${fails}`));
|
|
51670
|
+
console.log(chalk16.gray(" the cached policy is still enforced \u2014 run: node9 doctor"));
|
|
51552
51671
|
} else if (health.lastCheckedAt) {
|
|
51553
|
-
console.log(
|
|
51672
|
+
console.log(chalk16.gray(` \u21B3 policy synced ${agoLabel(health.lastCheckedAt)}`));
|
|
51554
51673
|
}
|
|
51555
51674
|
} else if (creds && !settings.approvers.cloud) {
|
|
51556
51675
|
console.log(
|
|
51557
|
-
|
|
51676
|
+
chalk16.blue(" \u25CF Privacy mode \u{1F6E1}\uFE0F") + chalk16.gray(" \u2014 all decisions stay on this machine")
|
|
51558
51677
|
);
|
|
51559
51678
|
} else {
|
|
51560
51679
|
console.log(
|
|
51561
|
-
|
|
51680
|
+
chalk16.yellow(" \u25CB Privacy mode \u{1F6E1}\uFE0F") + chalk16.gray(" \u2014 no API key (Local rules only)")
|
|
51562
51681
|
);
|
|
51563
51682
|
}
|
|
51564
51683
|
console.log("");
|
|
51565
51684
|
if (daemonRunning) {
|
|
51566
51685
|
console.log(
|
|
51567
|
-
|
|
51686
|
+
chalk16.green(" \u25CF Daemon running") + chalk16.gray(` \u2192 http://127.0.0.1:${DAEMON_PORT}/`)
|
|
51568
51687
|
);
|
|
51569
51688
|
const probe = await probeDaemonHealth();
|
|
51570
51689
|
const drift = describeBuildDrift(
|
|
@@ -51572,10 +51691,10 @@ function registerStatusCommand(program2) {
|
|
|
51572
51691
|
CURRENT_BUILD
|
|
51573
51692
|
);
|
|
51574
51693
|
if (drift) {
|
|
51575
|
-
console.log(
|
|
51694
|
+
console.log(chalk16.yellow(` \u26A0 ${drift}`) + chalk16.gray(" \u2014 run: node9 doctor"));
|
|
51576
51695
|
}
|
|
51577
51696
|
} else {
|
|
51578
|
-
console.log(
|
|
51697
|
+
console.log(chalk16.gray(" \u25CB Daemon stopped"));
|
|
51579
51698
|
}
|
|
51580
51699
|
const autostart = autostartAdvice({
|
|
51581
51700
|
installed: isDaemonServiceInstalled(),
|
|
@@ -51584,39 +51703,39 @@ function registerStatusCommand(program2) {
|
|
|
51584
51703
|
});
|
|
51585
51704
|
if (autostart) {
|
|
51586
51705
|
console.log(
|
|
51587
|
-
|
|
51706
|
+
chalk16.yellow(" \u26A0 daemon autostart not active") + chalk16.gray(" \u2014 won't survive reboot; run: node9 doctor")
|
|
51588
51707
|
);
|
|
51589
51708
|
}
|
|
51590
51709
|
if (undoLeftoverPaths().length > 0) {
|
|
51591
51710
|
console.log(
|
|
51592
|
-
|
|
51711
|
+
chalk16.gray(" \u25CB Undo Engine") + chalk16.gray(" \u2192 removed \u2014 old snapshot files remain (node9 undo --purge)")
|
|
51593
51712
|
);
|
|
51594
51713
|
}
|
|
51595
51714
|
console.log("");
|
|
51596
|
-
const modeLabel = settings.mode === "audit" ?
|
|
51715
|
+
const modeLabel = settings.mode === "audit" ? chalk16.blue("audit") : settings.mode === "strict" ? chalk16.red("strict") : chalk16.white("standard");
|
|
51597
51716
|
console.log(` Mode: ${modeLabel}`);
|
|
51598
51717
|
const projectConfig = path55.join(process.cwd(), "node9.config.json");
|
|
51599
51718
|
const globalConfig = path55.join(os51.homedir(), ".node9", "config.json");
|
|
51600
51719
|
console.log(
|
|
51601
|
-
` Local: ${fs58.existsSync(projectConfig) ?
|
|
51720
|
+
` Local: ${fs58.existsSync(projectConfig) ? chalk16.green("Active (node9.config.json)") : chalk16.gray("Not present")}`
|
|
51602
51721
|
);
|
|
51603
51722
|
console.log(
|
|
51604
|
-
` Global: ${fs58.existsSync(globalConfig) ?
|
|
51723
|
+
` Global: ${fs58.existsSync(globalConfig) ? chalk16.green("Active (~/.node9/config.json)") : chalk16.gray("Not present")}`
|
|
51605
51724
|
);
|
|
51606
51725
|
if (mergedConfig.policy.sandboxPaths.length > 0) {
|
|
51607
51726
|
console.log(
|
|
51608
|
-
` Sandbox: ${
|
|
51727
|
+
` Sandbox: ${chalk16.green(`${mergedConfig.policy.sandboxPaths.length} safe zones active`)}`
|
|
51609
51728
|
);
|
|
51610
51729
|
}
|
|
51611
51730
|
const wiring = getAgentWiring(os51.homedir()).filter((a) => a.present);
|
|
51612
51731
|
if (wiring.length > 0) {
|
|
51613
51732
|
console.log("");
|
|
51614
|
-
console.log(
|
|
51733
|
+
console.log(chalk16.bold(" Agent Wiring:"));
|
|
51615
51734
|
console.log("");
|
|
51616
51735
|
for (const a of wiring) {
|
|
51617
51736
|
if (a.wireState === "invalid") {
|
|
51618
51737
|
console.error(
|
|
51619
|
-
|
|
51738
|
+
chalk16.yellow(
|
|
51620
51739
|
` \u26A0\uFE0F ${a.label} config at ${a.settingsPath} is not valid ${a.configFormat} \u2014 showing as unwired.`
|
|
51621
51740
|
)
|
|
51622
51741
|
);
|
|
@@ -51634,7 +51753,7 @@ function registerStatusCommand(program2) {
|
|
|
51634
51753
|
const expiresAt = pauseState.expiresAt ? new Date(pauseState.expiresAt).toLocaleTimeString() : "indefinitely";
|
|
51635
51754
|
console.log("");
|
|
51636
51755
|
console.log(
|
|
51637
|
-
|
|
51756
|
+
chalk16.yellow(` \u23F8 PAUSED until ${expiresAt}`) + chalk16.gray(" \u2014 all tool calls allowed")
|
|
51638
51757
|
);
|
|
51639
51758
|
}
|
|
51640
51759
|
console.log("");
|
|
@@ -51647,7 +51766,7 @@ init_setup();
|
|
|
51647
51766
|
init_shields();
|
|
51648
51767
|
init_service();
|
|
51649
51768
|
init_core();
|
|
51650
|
-
import
|
|
51769
|
+
import chalk17 from "chalk";
|
|
51651
51770
|
import fs59 from "fs";
|
|
51652
51771
|
import path56 from "path";
|
|
51653
51772
|
import os52 from "os";
|
|
@@ -51696,11 +51815,11 @@ function registerInitCommand(program2) {
|
|
|
51696
51815
|
"Non-interactive: enable bash-safe + filesystem + project-jail shields without prompting"
|
|
51697
51816
|
).action(
|
|
51698
51817
|
async (options) => {
|
|
51699
|
-
console.log(
|
|
51818
|
+
console.log(chalk17.cyan.bold("\n\u{1F6E1}\uFE0F Node9 Init\n"));
|
|
51700
51819
|
{
|
|
51701
51820
|
const migrated = migrateRenamedRuleKeys();
|
|
51702
51821
|
for (const m of migrated) {
|
|
51703
|
-
console.log(
|
|
51822
|
+
console.log(chalk17.dim(` \u{1F527} Rule renamed: ${m.oldKey} \u2192 ${m.newKey}`));
|
|
51704
51823
|
}
|
|
51705
51824
|
}
|
|
51706
51825
|
let chosenMode = options.mode.toLowerCase();
|
|
@@ -51712,7 +51831,7 @@ function registerInitCommand(program2) {
|
|
|
51712
51831
|
if (options.recommended) {
|
|
51713
51832
|
enableShields = true;
|
|
51714
51833
|
console.log(
|
|
51715
|
-
|
|
51834
|
+
chalk17.dim(
|
|
51716
51835
|
" Recommended mode: enabling bash-safe + filesystem + project-jail shields"
|
|
51717
51836
|
)
|
|
51718
51837
|
);
|
|
@@ -51731,7 +51850,7 @@ function registerInitCommand(program2) {
|
|
|
51731
51850
|
const hasNewShields = DEFAULT_SHIELDS.some((s) => !current.includes(s));
|
|
51732
51851
|
if (hasNewShields) writeActiveShields(merged);
|
|
51733
51852
|
} catch (err2) {
|
|
51734
|
-
console.log(
|
|
51853
|
+
console.log(chalk17.yellow(` \u26A0\uFE0F Could not update shields: ${String(err2)}`));
|
|
51735
51854
|
}
|
|
51736
51855
|
}
|
|
51737
51856
|
console.log("");
|
|
@@ -51746,12 +51865,12 @@ function registerInitCommand(program2) {
|
|
|
51746
51865
|
settings.mode = chosenMode;
|
|
51747
51866
|
existing.settings = settings;
|
|
51748
51867
|
fs59.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
51749
|
-
console.log(
|
|
51868
|
+
console.log(chalk17.green(`\u2705 Mode updated: ${chosenMode}`));
|
|
51750
51869
|
} else {
|
|
51751
|
-
console.log(
|
|
51870
|
+
console.log(chalk17.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
51752
51871
|
}
|
|
51753
51872
|
} catch {
|
|
51754
|
-
console.log(
|
|
51873
|
+
console.log(chalk17.blue(`\u2139\uFE0F Config already exists: ${configPath}`));
|
|
51755
51874
|
}
|
|
51756
51875
|
} else {
|
|
51757
51876
|
const configToSave = {
|
|
@@ -51761,8 +51880,8 @@ function registerInitCommand(program2) {
|
|
|
51761
51880
|
const dir = path56.dirname(configPath);
|
|
51762
51881
|
if (!fs59.existsSync(dir)) fs59.mkdirSync(dir, { recursive: true });
|
|
51763
51882
|
fs59.writeFileSync(configPath, JSON.stringify(configToSave, null, 2) + "\n");
|
|
51764
|
-
console.log(
|
|
51765
|
-
console.log(
|
|
51883
|
+
console.log(chalk17.green(`\u2705 Config created: ${configPath}`));
|
|
51884
|
+
console.log(chalk17.gray(` Mode: ${chosenMode}`));
|
|
51766
51885
|
}
|
|
51767
51886
|
if (options.skipSetup) return;
|
|
51768
51887
|
console.log("");
|
|
@@ -51772,24 +51891,24 @@ function registerInitCommand(program2) {
|
|
|
51772
51891
|
);
|
|
51773
51892
|
if (found.length === 0) {
|
|
51774
51893
|
console.log(
|
|
51775
|
-
|
|
51894
|
+
chalk17.gray(
|
|
51776
51895
|
"No AI agents detected. Install one of the supported agents (Claude Code, Codex, Antigravity, Gemini CLI, GitHub Copilot CLI, Cursor, Windsurf, VSCode, Claude Desktop, Opencode, Pi, or Hermes Agent)."
|
|
51777
51896
|
)
|
|
51778
51897
|
);
|
|
51779
51898
|
console.log(
|
|
51780
|
-
|
|
51899
|
+
chalk17.gray(
|
|
51781
51900
|
"then run: node9 agents add <claude|codex|antigravity|gemini|copilot|cursor|windsurf|vscode|claudeDesktop|opencode|pi|hermes>"
|
|
51782
51901
|
)
|
|
51783
51902
|
);
|
|
51784
51903
|
return;
|
|
51785
51904
|
}
|
|
51786
|
-
console.log(
|
|
51905
|
+
console.log(chalk17.bold("Detected agents:"));
|
|
51787
51906
|
for (const agent of found) {
|
|
51788
|
-
console.log(
|
|
51907
|
+
console.log(chalk17.green(` \u2713 ${agent}`));
|
|
51789
51908
|
}
|
|
51790
51909
|
console.log("");
|
|
51791
51910
|
for (const agent of found) {
|
|
51792
|
-
console.log(
|
|
51911
|
+
console.log(chalk17.bold(`Wiring ${agent}...`));
|
|
51793
51912
|
await setupAgent(agent);
|
|
51794
51913
|
console.log("");
|
|
51795
51914
|
}
|
|
@@ -51805,31 +51924,31 @@ function registerInitCommand(program2) {
|
|
|
51805
51924
|
const result = installDaemonService();
|
|
51806
51925
|
if (result.ok) {
|
|
51807
51926
|
console.log(
|
|
51808
|
-
|
|
51927
|
+
chalk17.green(` \u2713 Daemon installed as login service (${result.platform})`)
|
|
51809
51928
|
);
|
|
51810
51929
|
} else {
|
|
51811
|
-
console.log(
|
|
51812
|
-
console.log(
|
|
51930
|
+
console.log(chalk17.yellow(` \u26A0\uFE0F Could not install service: ${result.reason}`));
|
|
51931
|
+
console.log(chalk17.gray(" You can try again later with: node9 daemon install"));
|
|
51813
51932
|
}
|
|
51814
51933
|
}
|
|
51815
51934
|
} else if (isDaemonServiceEnabled()) {
|
|
51816
|
-
console.log(
|
|
51935
|
+
console.log(chalk17.green(" \u2713 Daemon login service already installed & enabled"));
|
|
51817
51936
|
} else {
|
|
51818
51937
|
const healed = ensureAutostartHealthy(!!getConfig().settings.autoStartDaemon);
|
|
51819
51938
|
console.log(
|
|
51820
|
-
healed === "repaired" ?
|
|
51939
|
+
healed === "repaired" ? chalk17.green(" \u2713 Re-enabled daemon login service (was installed but disabled)") : chalk17.gray(" \xB7 Daemon login service is disabled (autostart off) \u2014 left as-is")
|
|
51821
51940
|
);
|
|
51822
51941
|
}
|
|
51823
51942
|
if (!isTestingMode()) {
|
|
51824
|
-
process.stdout.write(
|
|
51943
|
+
process.stdout.write(chalk17.dim(" Starting daemon..."));
|
|
51825
51944
|
const started = await autoStartDaemonAndWait();
|
|
51826
51945
|
if (started) {
|
|
51827
51946
|
process.stdout.write(
|
|
51828
|
-
"\r" +
|
|
51947
|
+
"\r" + chalk17.green(" \u2713 Daemon started \u2014 protection is active") + "\n"
|
|
51829
51948
|
);
|
|
51830
51949
|
} else {
|
|
51831
51950
|
process.stdout.write(
|
|
51832
|
-
"\r" +
|
|
51951
|
+
"\r" + chalk17.dim(" Daemon will start on next login ") + "\n"
|
|
51833
51952
|
);
|
|
51834
51953
|
}
|
|
51835
51954
|
}
|
|
@@ -51845,15 +51964,15 @@ function registerInitCommand(program2) {
|
|
|
51845
51964
|
console.log("");
|
|
51846
51965
|
}
|
|
51847
51966
|
const agentList = found.join(", ");
|
|
51848
|
-
console.log(
|
|
51967
|
+
console.log(chalk17.green.bold(`\u{1F6E1}\uFE0F Node9 is protecting ${agentList}!`));
|
|
51849
51968
|
console.log("");
|
|
51850
|
-
console.log(
|
|
51969
|
+
console.log(chalk17.white(" Watch live: ") + chalk17.cyan("node9 monitor"));
|
|
51851
51970
|
console.log("");
|
|
51852
|
-
console.log(
|
|
51971
|
+
console.log(chalk17.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"));
|
|
51853
51972
|
console.log(
|
|
51854
|
-
|
|
51973
|
+
chalk17.white(" Team dashboard + full audit trail \u2192 ") + chalk17.cyan.bold("https://node9.ai")
|
|
51855
51974
|
);
|
|
51856
|
-
console.log(
|
|
51975
|
+
console.log(chalk17.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"));
|
|
51857
51976
|
}
|
|
51858
51977
|
);
|
|
51859
51978
|
}
|
|
@@ -51862,7 +51981,7 @@ function registerInitCommand(program2) {
|
|
|
51862
51981
|
init_agent_wiring();
|
|
51863
51982
|
init_setup();
|
|
51864
51983
|
init_hook_baseline();
|
|
51865
|
-
import
|
|
51984
|
+
import chalk18 from "chalk";
|
|
51866
51985
|
import fs60 from "fs";
|
|
51867
51986
|
var hasHookSurface = (a) => a.hooks.length > 0;
|
|
51868
51987
|
function backupForHeal(file) {
|
|
@@ -51872,7 +51991,7 @@ function backupForHeal(file) {
|
|
|
51872
51991
|
}
|
|
51873
51992
|
}
|
|
51874
51993
|
async function runHeal(name) {
|
|
51875
|
-
console.log(
|
|
51994
|
+
console.log(chalk18.cyan.bold("\n\u{1FA79} Node9 Heal\n"));
|
|
51876
51995
|
const baseline = loadHookBaseline();
|
|
51877
51996
|
const wiring = getAgentWiring();
|
|
51878
51997
|
let candidates2 = wiring.filter(
|
|
@@ -51885,7 +52004,7 @@ async function runHeal(name) {
|
|
|
51885
52004
|
);
|
|
51886
52005
|
if (candidates2.length === 0) {
|
|
51887
52006
|
console.error(
|
|
51888
|
-
|
|
52007
|
+
chalk18.red(
|
|
51889
52008
|
`Nothing to heal for "${name}" \u2014 it's either healthy, not governed by node9, or not installed.`
|
|
51890
52009
|
)
|
|
51891
52010
|
);
|
|
@@ -51894,13 +52013,13 @@ async function runHeal(name) {
|
|
|
51894
52013
|
}
|
|
51895
52014
|
}
|
|
51896
52015
|
if (candidates2.length === 0) {
|
|
51897
|
-
console.log(
|
|
52016
|
+
console.log(chalk18.green(" \u2713 All governed agents are healthy \u2014 nothing to heal."));
|
|
51898
52017
|
const wireable = wiring.filter(
|
|
51899
52018
|
(a) => !baseline[a.id] && a.installed && hasHookSurface(a) && a.wireState !== "wired"
|
|
51900
52019
|
);
|
|
51901
52020
|
if (wireable.length > 0) {
|
|
51902
52021
|
console.log(
|
|
51903
|
-
|
|
52022
|
+
chalk18.gray(` To govern ${wireable.map((a) => a.label).join(", ")}, run: `) + chalk18.bold("node9 init")
|
|
51904
52023
|
);
|
|
51905
52024
|
}
|
|
51906
52025
|
return { healed: [] };
|
|
@@ -51916,15 +52035,15 @@ async function runHeal(name) {
|
|
|
51916
52035
|
if (notified.delete(a.id)) notifiedChanged = true;
|
|
51917
52036
|
} catch (err2) {
|
|
51918
52037
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
51919
|
-
console.error(
|
|
52038
|
+
console.error(chalk18.red(` \u2717 failed to heal ${a.label}: ${msg}`));
|
|
51920
52039
|
process.exitCode = 1;
|
|
51921
52040
|
}
|
|
51922
52041
|
}
|
|
51923
52042
|
if (notifiedChanged) saveNotified(notified);
|
|
51924
52043
|
if (healed.length > 0) {
|
|
51925
|
-
console.log(
|
|
52044
|
+
console.log(chalk18.bold(`
|
|
51926
52045
|
\u2713 Re-installed node9 hooks for ${healed.join(", ")}.`));
|
|
51927
|
-
console.log(
|
|
52046
|
+
console.log(chalk18.gray(` Restart ${healed.join(", ")} to activate the restored protection.`));
|
|
51928
52047
|
}
|
|
51929
52048
|
return { healed };
|
|
51930
52049
|
}
|
|
@@ -51940,9 +52059,7 @@ function registerHealCommand(program2) {
|
|
|
51940
52059
|
import http4 from "http";
|
|
51941
52060
|
import https7 from "https";
|
|
51942
52061
|
import { URL as URL4 } from "url";
|
|
51943
|
-
import
|
|
51944
|
-
init_setup();
|
|
51945
|
-
init_sync();
|
|
52062
|
+
import chalk19 from "chalk";
|
|
51946
52063
|
var DEFAULT_CONNECT_URL = "https://api.node9.ai/api/v1/cli/connect";
|
|
51947
52064
|
function resolveConnectUrl(apiUrl) {
|
|
51948
52065
|
if (apiUrl) return apiUrl;
|
|
@@ -52005,84 +52122,66 @@ function registerConnectCommand(program2) {
|
|
|
52005
52122
|
try {
|
|
52006
52123
|
resp = await postConnect(resolveConnectUrl(options.apiUrl), token);
|
|
52007
52124
|
} catch (e) {
|
|
52008
|
-
console.error(
|
|
52125
|
+
console.error(chalk19.red(`\u2717 ${e instanceof Error ? e.message : "Connect failed."}`));
|
|
52009
52126
|
process.exitCode = 1;
|
|
52010
52127
|
return;
|
|
52011
52128
|
}
|
|
52012
|
-
|
|
52013
|
-
|
|
52014
|
-
|
|
52015
|
-
try {
|
|
52016
|
-
wired = await setupDetectedAgents();
|
|
52017
|
-
} catch {
|
|
52018
|
-
}
|
|
52019
|
-
if (!isTestingMode()) {
|
|
52020
|
-
try {
|
|
52021
|
-
await runCloudSync();
|
|
52022
|
-
} catch {
|
|
52023
|
-
}
|
|
52024
|
-
}
|
|
52025
|
-
console.log(chalk18.green(`\u2705 Connected to ${resp.workspaceName}`));
|
|
52026
|
-
if (wired.length) {
|
|
52027
|
-
console.log(chalk18.gray(` Wired: ${wired.join(", ")}`));
|
|
52028
|
-
} else {
|
|
52029
|
-
console.log(
|
|
52030
|
-
chalk18.gray(" No agents detected yet \u2014 run `node9 init` after installing one.")
|
|
52031
|
-
);
|
|
52032
|
-
}
|
|
52129
|
+
const outcome = await onboardMachine(resp.apiKey, { profileName: options.profile });
|
|
52130
|
+
console.log(renderOnboardOutcome(outcome, { workspaceName: resp.workspaceName }));
|
|
52131
|
+
if (!outcome.ok) process.exitCode = 1;
|
|
52033
52132
|
});
|
|
52034
52133
|
}
|
|
52035
52134
|
|
|
52036
52135
|
// src/cli/commands/undo.ts
|
|
52037
|
-
import
|
|
52136
|
+
import chalk20 from "chalk";
|
|
52038
52137
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
52039
52138
|
function removalNotice() {
|
|
52040
|
-
return
|
|
52139
|
+
return chalk20.yellow("\n\u2139\uFE0F The undo feature has been removed.\n") + chalk20.gray(
|
|
52041
52140
|
" Its snapshot store had no size ceiling \u2014 it grew by roughly 19 MB per\n minute of agent work and took one machine to 378 GB. Snapshots are no\n longer taken, and no setting turns them back on.\n"
|
|
52042
52141
|
);
|
|
52043
52142
|
}
|
|
52044
52143
|
function leftoverNotice(paths) {
|
|
52045
52144
|
if (paths.length === 0) return "";
|
|
52046
|
-
return
|
|
52047
|
-
`)).join("") +
|
|
52145
|
+
return chalk20.gray("\n Files the feature left behind are still on disk:\n") + paths.map((p) => chalk20.gray(` ${p}
|
|
52146
|
+
`)).join("") + chalk20.gray("\n node9 does not delete them for you. To remove them:\n") + chalk20.cyan(" node9 undo --purge\n");
|
|
52048
52147
|
}
|
|
52049
52148
|
async function runPurge(assumeYes) {
|
|
52050
52149
|
const paths = undoLeftoverPaths();
|
|
52051
52150
|
if (paths.length === 0) {
|
|
52052
|
-
console.log(
|
|
52151
|
+
console.log(chalk20.green("\n\u2705 Nothing to remove \u2014 the undo store is already gone.\n"));
|
|
52053
52152
|
return;
|
|
52054
52153
|
}
|
|
52055
|
-
console.log(
|
|
52056
|
-
for (const p of paths) console.log(
|
|
52154
|
+
console.log(chalk20.yellow("\n\u26A0\uFE0F This will permanently delete:\n"));
|
|
52155
|
+
for (const p of paths) console.log(chalk20.gray(` ${p}`));
|
|
52057
52156
|
console.log(
|
|
52058
|
-
|
|
52157
|
+
chalk20.gray(
|
|
52059
52158
|
"\n These hold snapshots of your own source code. Nothing else in\n ~/.node9 is touched (config, audit log and credentials stay).\n"
|
|
52060
52159
|
)
|
|
52061
52160
|
);
|
|
52062
52161
|
if (!assumeYes) {
|
|
52063
52162
|
if (!process.stdin.isTTY) {
|
|
52064
52163
|
console.error(
|
|
52065
|
-
|
|
52164
|
+
chalk20.red("\n Refusing to delete without confirmation.") + chalk20.gray("\n No terminal is attached \u2014 re-run with --yes to confirm.\n")
|
|
52066
52165
|
);
|
|
52067
52166
|
process.exitCode = 1;
|
|
52068
52167
|
return;
|
|
52069
52168
|
}
|
|
52070
52169
|
const ok2 = await confirm2({ message: "Delete these files?", default: false });
|
|
52071
52170
|
if (!ok2) {
|
|
52072
|
-
console.log(
|
|
52171
|
+
console.log(chalk20.gray("\n Skipped \u2014 nothing was deleted.\n"));
|
|
52073
52172
|
return;
|
|
52074
52173
|
}
|
|
52075
52174
|
}
|
|
52076
52175
|
const result = purgeUndoLeftovers();
|
|
52077
|
-
for (const p of result.deleted) console.log(
|
|
52176
|
+
for (const p of result.deleted) console.log(chalk20.green(` \u2705 removed ${p}`));
|
|
52078
52177
|
for (const s of result.skipped)
|
|
52079
|
-
console.log(
|
|
52080
|
-
for (const f of result.failed) console.error(
|
|
52178
|
+
console.log(chalk20.yellow(` \u26A0\uFE0F skipped ${s.path} \u2014 ${s.reason}`));
|
|
52179
|
+
for (const f of result.failed) console.error(chalk20.red(` \u274C ${f.path} \u2014 ${f.reason}`));
|
|
52081
52180
|
if (result.skipped.length > 0 || result.failed.length > 0) {
|
|
52082
|
-
console.error(
|
|
52181
|
+
console.error(chalk20.red("\n Some files remain \u2014 see above.\n"));
|
|
52083
52182
|
process.exitCode = 1;
|
|
52084
52183
|
} else {
|
|
52085
|
-
console.log(
|
|
52184
|
+
console.log(chalk20.green("\n Done.\n"));
|
|
52086
52185
|
}
|
|
52087
52186
|
}
|
|
52088
52187
|
function registerUndoCommand(program2) {
|
|
@@ -52101,7 +52200,7 @@ init_orchestrator();
|
|
|
52101
52200
|
init_cloud();
|
|
52102
52201
|
init_config();
|
|
52103
52202
|
import readline3 from "readline";
|
|
52104
|
-
import
|
|
52203
|
+
import chalk21 from "chalk";
|
|
52105
52204
|
import { spawn as spawn6 } from "child_process";
|
|
52106
52205
|
import { execa as execa2 } from "execa";
|
|
52107
52206
|
init_provenance();
|
|
@@ -52206,13 +52305,13 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
52206
52305
|
const prov = checkProvenance(executable);
|
|
52207
52306
|
if (prov.trustLevel === "suspect") {
|
|
52208
52307
|
console.error(
|
|
52209
|
-
|
|
52308
|
+
chalk21.red(
|
|
52210
52309
|
`\u26A0\uFE0F Node9: Upstream MCP server binary is suspect \u2014 ${prov.reason} (${prov.resolvedPath})`
|
|
52211
52310
|
)
|
|
52212
52311
|
);
|
|
52213
|
-
console.error(
|
|
52312
|
+
console.error(chalk21.red(" Verify this binary is trusted before proceeding."));
|
|
52214
52313
|
}
|
|
52215
|
-
console.error(
|
|
52314
|
+
console.error(chalk21.green(`\u{1F680} Node9 MCP Gateway: Monitoring [${upstreamCommand}]`));
|
|
52216
52315
|
const UPSTREAM_INJECTOR_VARS = /* @__PURE__ */ new Set([
|
|
52217
52316
|
"NODE_OPTIONS",
|
|
52218
52317
|
"NODE_PATH",
|
|
@@ -52328,10 +52427,10 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
52328
52427
|
serverKey
|
|
52329
52428
|
});
|
|
52330
52429
|
if (!result.approved) {
|
|
52331
|
-
console.error(
|
|
52430
|
+
console.error(chalk21.red(`
|
|
52332
52431
|
\u{1F6D1} Node9 MCP Gateway: Action Blocked`));
|
|
52333
|
-
console.error(
|
|
52334
|
-
console.error(
|
|
52432
|
+
console.error(chalk21.gray(` Tool: ${toolName}`));
|
|
52433
|
+
console.error(chalk21.gray(` Reason: ${result.reason ?? "Security Policy"}
|
|
52335
52434
|
`));
|
|
52336
52435
|
const blockedByLabel = result.blockedByLabel ?? result.reason ?? "Security Policy";
|
|
52337
52436
|
const isHumanDecision = blockedByLabel.toLowerCase().includes("user") || blockedByLabel.toLowerCase().includes("daemon") || blockedByLabel.toLowerCase().includes("decision");
|
|
@@ -52456,7 +52555,7 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
52456
52555
|
updatePin(serverKey, upstreamCommand, currentHash, toolNames);
|
|
52457
52556
|
pinState = "validated";
|
|
52458
52557
|
console.error(
|
|
52459
|
-
|
|
52558
|
+
chalk21.green(
|
|
52460
52559
|
`\u{1F512} Node9: Pinned ${toolNames.length} tool definition(s) for this MCP server`
|
|
52461
52560
|
)
|
|
52462
52561
|
);
|
|
@@ -52469,11 +52568,11 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
52469
52568
|
} else if (pinStatus === "corrupt") {
|
|
52470
52569
|
pinState = "quarantined";
|
|
52471
52570
|
console.error(
|
|
52472
|
-
|
|
52571
|
+
chalk21.red("\n\u{1F6A8} Node9: MCP pin file is corrupt or unreadable \u2014 session quarantined!")
|
|
52473
52572
|
);
|
|
52474
|
-
console.error(
|
|
52573
|
+
console.error(chalk21.red(" Tool calls are blocked until the pin file is repaired."));
|
|
52475
52574
|
console.error(
|
|
52476
|
-
|
|
52575
|
+
chalk21.yellow(` Run: node9 mcp pin reset (to clear and re-pin on next connect)
|
|
52477
52576
|
`)
|
|
52478
52577
|
);
|
|
52479
52578
|
const errorResponse = {
|
|
@@ -52490,13 +52589,13 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
52490
52589
|
} else {
|
|
52491
52590
|
pinState = "quarantined";
|
|
52492
52591
|
console.error(
|
|
52493
|
-
|
|
52592
|
+
chalk21.red("\n\u{1F6A8} Node9: MCP tool definitions have changed since last verified!")
|
|
52494
52593
|
);
|
|
52495
52594
|
console.error(
|
|
52496
|
-
|
|
52595
|
+
chalk21.red(" This could indicate a supply chain attack (tool poisoning / rug pull).")
|
|
52497
52596
|
);
|
|
52498
|
-
console.error(
|
|
52499
|
-
console.error(
|
|
52597
|
+
console.error(chalk21.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
52598
|
+
console.error(chalk21.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
52500
52599
|
`));
|
|
52501
52600
|
reportPinMismatchToCloud(
|
|
52502
52601
|
serverKey,
|
|
@@ -52544,7 +52643,7 @@ async function runMcpGateway(upstreamCommand, configName) {
|
|
|
52544
52643
|
const toolName = callId !== void 0 ? pendingCallNames.get(callId) ?? "unknown" : "unknown";
|
|
52545
52644
|
if (callId !== void 0) pendingCallNames.delete(callId);
|
|
52546
52645
|
console.error(
|
|
52547
|
-
|
|
52646
|
+
chalk21.yellow(
|
|
52548
52647
|
`\u26A1 Node9: Large MCP response from '${toolName}' (${(line.length / 1024).toFixed(0)}KB) \u2014 context window enlarged`
|
|
52549
52648
|
)
|
|
52550
52649
|
);
|
|
@@ -53397,7 +53496,7 @@ function registerMcpServerCommand(program2) {
|
|
|
53397
53496
|
|
|
53398
53497
|
// src/cli/commands/trust.ts
|
|
53399
53498
|
init_trusted_hosts();
|
|
53400
|
-
import
|
|
53499
|
+
import chalk22 from "chalk";
|
|
53401
53500
|
function isValidHost(host) {
|
|
53402
53501
|
return /^(\*\.)?[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/.test(host);
|
|
53403
53502
|
}
|
|
@@ -53407,44 +53506,44 @@ function registerTrustCommand(program2) {
|
|
|
53407
53506
|
const normalized = normalizeHost(host.trim());
|
|
53408
53507
|
if (!isValidHost(normalized)) {
|
|
53409
53508
|
console.error(
|
|
53410
|
-
|
|
53509
|
+
chalk22.red(`
|
|
53411
53510
|
\u274C Invalid host: "${host}"
|
|
53412
|
-
`) +
|
|
53511
|
+
`) + chalk22.gray(" Use an FQDN like api.mycompany.com or *.mycompany.com\n")
|
|
53413
53512
|
);
|
|
53414
53513
|
process.exit(1);
|
|
53415
53514
|
}
|
|
53416
53515
|
addTrustedHost(normalized);
|
|
53417
|
-
console.log(
|
|
53516
|
+
console.log(chalk22.green(`
|
|
53418
53517
|
\u2705 ${normalized} added to trusted hosts.`));
|
|
53419
53518
|
console.log(
|
|
53420
|
-
|
|
53519
|
+
chalk22.gray(" Pipe-chain blocks to this host: critical \u2192 review, high \u2192 allow\n")
|
|
53421
53520
|
);
|
|
53422
53521
|
});
|
|
53423
53522
|
trustCmd.command("remove <host>").description("Remove a trusted host").action((host) => {
|
|
53424
53523
|
const normalized = normalizeHost(host.trim());
|
|
53425
53524
|
const removed = removeTrustedHost(normalized);
|
|
53426
53525
|
if (!removed) {
|
|
53427
|
-
console.error(
|
|
53526
|
+
console.error(chalk22.yellow(`
|
|
53428
53527
|
\u26A0\uFE0F "${normalized}" is not in the trusted hosts list.
|
|
53429
53528
|
`));
|
|
53430
53529
|
process.exit(1);
|
|
53431
53530
|
}
|
|
53432
|
-
console.log(
|
|
53531
|
+
console.log(chalk22.green(`
|
|
53433
53532
|
\u2705 ${normalized} removed from trusted hosts.
|
|
53434
53533
|
`));
|
|
53435
53534
|
});
|
|
53436
53535
|
trustCmd.command("list").description("Show all trusted hosts").action(() => {
|
|
53437
53536
|
const hosts = readTrustedHosts();
|
|
53438
53537
|
if (hosts.length === 0) {
|
|
53439
|
-
console.log(
|
|
53440
|
-
console.log(` Add one: ${
|
|
53538
|
+
console.log(chalk22.gray("\n No trusted hosts configured.\n"));
|
|
53539
|
+
console.log(` Add one: ${chalk22.cyan("node9 trust add api.mycompany.com")}
|
|
53441
53540
|
`);
|
|
53442
53541
|
return;
|
|
53443
53542
|
}
|
|
53444
|
-
console.log(
|
|
53543
|
+
console.log(chalk22.bold("\n\u{1F513} Trusted Hosts\n"));
|
|
53445
53544
|
for (const entry of hosts) {
|
|
53446
53545
|
const date = new Date(entry.addedAt).toLocaleDateString();
|
|
53447
|
-
console.log(` ${
|
|
53546
|
+
console.log(` ${chalk22.cyan(entry.host.padEnd(40))} ${chalk22.gray(`added ${date}`)}`);
|
|
53448
53547
|
}
|
|
53449
53548
|
console.log("");
|
|
53450
53549
|
});
|
|
@@ -53452,13 +53551,13 @@ function registerTrustCommand(program2) {
|
|
|
53452
53551
|
|
|
53453
53552
|
// src/cli/commands/mcp-pin.ts
|
|
53454
53553
|
init_mcp_pin();
|
|
53455
|
-
import
|
|
53554
|
+
import chalk24 from "chalk";
|
|
53456
53555
|
import fs63 from "fs";
|
|
53457
53556
|
|
|
53458
53557
|
// src/cli/commands/mcp-gateway-cmd.ts
|
|
53459
53558
|
init_mcp_wrap();
|
|
53460
53559
|
init_mcp_status();
|
|
53461
|
-
import
|
|
53560
|
+
import chalk23 from "chalk";
|
|
53462
53561
|
var STATE_WORD = {
|
|
53463
53562
|
gatewayed: "governed",
|
|
53464
53563
|
"node9-self": "node9",
|
|
@@ -53467,10 +53566,10 @@ var STATE_WORD = {
|
|
|
53467
53566
|
};
|
|
53468
53567
|
function stateChip(state) {
|
|
53469
53568
|
const w = STATE_WORD[state].padEnd(12);
|
|
53470
|
-
if (state === "gatewayed") return
|
|
53471
|
-
if (state === "node9-self") return
|
|
53472
|
-
if (state === "remote") return
|
|
53473
|
-
return
|
|
53569
|
+
if (state === "gatewayed") return chalk23.green(w);
|
|
53570
|
+
if (state === "node9-self") return chalk23.gray(w);
|
|
53571
|
+
if (state === "remote") return chalk23.gray(w);
|
|
53572
|
+
return chalk23.yellow(w);
|
|
53474
53573
|
}
|
|
53475
53574
|
function relativeAge(ms, now) {
|
|
53476
53575
|
const s = Math.max(0, Math.round((now - ms) / 1e3));
|
|
@@ -53484,15 +53583,15 @@ function relativeAge(ms, now) {
|
|
|
53484
53583
|
function connectionChip(e, now) {
|
|
53485
53584
|
switch (e.connection) {
|
|
53486
53585
|
case "connected":
|
|
53487
|
-
return
|
|
53586
|
+
return chalk23.green(
|
|
53488
53587
|
`\u2713 connected${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`
|
|
53489
53588
|
);
|
|
53490
53589
|
case "stale":
|
|
53491
|
-
return
|
|
53590
|
+
return chalk23.yellow(`\u26A0 stale${e.lastSeenAt ? ` (${relativeAge(e.lastSeenAt, now)})` : ""}`);
|
|
53492
53591
|
case "pending-launch":
|
|
53493
|
-
return
|
|
53592
|
+
return chalk23.cyan(`\u2026 pending \u2014 restart ${e.agentLabel} to connect`);
|
|
53494
53593
|
case "unlaunchable":
|
|
53495
|
-
return
|
|
53594
|
+
return chalk23.red(
|
|
53496
53595
|
`\u2717 can't launch \u2014 ${(e.missingEnv ?? []).map((v) => `$${v}`).join(", ")} not set`
|
|
53497
53596
|
);
|
|
53498
53597
|
default:
|
|
@@ -53517,7 +53616,7 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
53517
53616
|
const now = Date.now();
|
|
53518
53617
|
const rows = resolveMcpStatus(void 0, process.env, now);
|
|
53519
53618
|
if (rows.length === 0) {
|
|
53520
|
-
console.error(
|
|
53619
|
+
console.error(chalk23.gray("No MCP servers found in any agent config."));
|
|
53521
53620
|
return;
|
|
53522
53621
|
}
|
|
53523
53622
|
for (const e of rows) {
|
|
@@ -53531,20 +53630,20 @@ function registerMcpGatewayCommand2(mcp) {
|
|
|
53531
53630
|
const pending2 = rows.filter((e) => e.connection === "pending-launch").length;
|
|
53532
53631
|
if (ungoverned > 0) {
|
|
53533
53632
|
console.error(
|
|
53534
|
-
|
|
53633
|
+
chalk23.yellow(
|
|
53535
53634
|
`
|
|
53536
|
-
${ungoverned} ungoverned \u2014 run ` +
|
|
53635
|
+
${ungoverned} ungoverned \u2014 run ` + chalk23.bold("node9 mcp gateway --all") + " to govern."
|
|
53537
53636
|
)
|
|
53538
53637
|
);
|
|
53539
53638
|
}
|
|
53540
53639
|
if (pending2 > 0) {
|
|
53541
53640
|
console.error(
|
|
53542
|
-
|
|
53641
|
+
chalk23.cyan(`${pending2} governed but not yet connected \u2014 restart the agent to activate.`)
|
|
53543
53642
|
);
|
|
53544
53643
|
}
|
|
53545
53644
|
if (unlaunchable > 0) {
|
|
53546
53645
|
console.error(
|
|
53547
|
-
|
|
53646
|
+
chalk23.red(
|
|
53548
53647
|
`${unlaunchable} can't launch \u2014 set the missing env var(s) where the agent starts (a restart alone won't fix it).`
|
|
53549
53648
|
)
|
|
53550
53649
|
);
|
|
@@ -53563,27 +53662,27 @@ ${ungoverned} ungoverned \u2014 run ` + chalk22.bold("node9 mcp gateway --all")
|
|
|
53563
53662
|
} else if (name) {
|
|
53564
53663
|
const matches = resolve(name, opts.agent);
|
|
53565
53664
|
if (matches.length === 0) {
|
|
53566
|
-
console.error(
|
|
53665
|
+
console.error(chalk23.red(`No MCP server named "${name}" found.`));
|
|
53567
53666
|
process.exitCode = 1;
|
|
53568
53667
|
return;
|
|
53569
53668
|
}
|
|
53570
53669
|
if (matches.length > 1) {
|
|
53571
53670
|
console.error(
|
|
53572
|
-
|
|
53671
|
+
chalk23.red(`"${name}" exists in ${matches.length} agents \u2014 pass --agent <id> (`) + matches.map((m) => m.agent).join(", ") + ")."
|
|
53573
53672
|
);
|
|
53574
53673
|
process.exitCode = 1;
|
|
53575
53674
|
return;
|
|
53576
53675
|
}
|
|
53577
53676
|
targets = matches;
|
|
53578
53677
|
} else {
|
|
53579
|
-
console.error(
|
|
53678
|
+
console.error(chalk23.red("Pass a server name or --all. See `node9 mcp status`."));
|
|
53580
53679
|
process.exitCode = 1;
|
|
53581
53680
|
return;
|
|
53582
53681
|
}
|
|
53583
53682
|
const doable = targets.filter((e) => e.state === targetState);
|
|
53584
53683
|
if (doable.length === 0) {
|
|
53585
53684
|
console.error(
|
|
53586
|
-
|
|
53685
|
+
chalk23.gray(
|
|
53587
53686
|
opts.rewrap ? "Nothing to do \u2014 no governed servers to refresh." : "Nothing to do \u2014 already governed (or node9-self)."
|
|
53588
53687
|
)
|
|
53589
53688
|
);
|
|
@@ -53597,20 +53696,20 @@ ${ungoverned} ungoverned \u2014 run ` + chalk22.bold("node9 mcp gateway --all")
|
|
|
53597
53696
|
else wrapEntry(e);
|
|
53598
53697
|
agents.add(e.agentLabel);
|
|
53599
53698
|
const verb = opts.rewrap ? "refreshed" : "governed";
|
|
53600
|
-
console.error(
|
|
53699
|
+
console.error(chalk23.green(`\u2713 ${verb} ${e.name}`) + chalk23.gray(` (${e.agentLabel})`));
|
|
53601
53700
|
} catch (err2) {
|
|
53602
53701
|
failed++;
|
|
53603
53702
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
53604
53703
|
console.error(
|
|
53605
|
-
|
|
53704
|
+
chalk23.red(`\u2717 failed ${e.name}`) + chalk23.gray(` (${e.agentLabel}): ${msg}`)
|
|
53606
53705
|
);
|
|
53607
53706
|
}
|
|
53608
53707
|
}
|
|
53609
53708
|
if (failed > 0) process.exitCode = 1;
|
|
53610
53709
|
if (agents.size > 0) {
|
|
53611
53710
|
console.error(
|
|
53612
|
-
|
|
53613
|
-
Restart ${[...agents].join(", ")}`) +
|
|
53711
|
+
chalk23.bold(`
|
|
53712
|
+
Restart ${[...agents].join(", ")}`) + chalk23.gray(" to activate. Undo any server with ") + chalk23.cyan("node9 mcp ungateway <name>") + chalk23.gray(".")
|
|
53614
53713
|
);
|
|
53615
53714
|
}
|
|
53616
53715
|
}
|
|
@@ -53618,19 +53717,19 @@ Restart ${[...agents].join(", ")}`) + chalk22.gray(" to activate. Undo any serve
|
|
|
53618
53717
|
mcp.command("ungateway <name>").description("Restore an MCP server to its original (un-route it from node9)").option("--agent <id>", "disambiguate a name in more than one agent").action((name, opts) => {
|
|
53619
53718
|
const matches = resolve(name, opts.agent).filter((e2) => e2.state === "gatewayed");
|
|
53620
53719
|
if (matches.length === 0) {
|
|
53621
|
-
console.error(
|
|
53720
|
+
console.error(chalk23.red(`No governed MCP server named "${name}" found.`));
|
|
53622
53721
|
process.exitCode = 1;
|
|
53623
53722
|
return;
|
|
53624
53723
|
}
|
|
53625
53724
|
if (matches.length > 1) {
|
|
53626
|
-
console.error(
|
|
53725
|
+
console.error(chalk23.red(`"${name}" is governed in multiple agents \u2014 pass --agent <id>.`));
|
|
53627
53726
|
process.exitCode = 1;
|
|
53628
53727
|
return;
|
|
53629
53728
|
}
|
|
53630
53729
|
const e = matches[0];
|
|
53631
53730
|
const orig = fromGateway(e.raw);
|
|
53632
53731
|
if (!orig) {
|
|
53633
|
-
console.error(
|
|
53732
|
+
console.error(chalk23.red(`Couldn't parse the original command for "${name}".`));
|
|
53634
53733
|
process.exitCode = 1;
|
|
53635
53734
|
return;
|
|
53636
53735
|
}
|
|
@@ -53638,12 +53737,12 @@ Restart ${[...agents].join(", ")}`) + chalk22.gray(" to activate. Undo any serve
|
|
|
53638
53737
|
writeMcpEntry(e.mcpFile, e.format, e.name, orig);
|
|
53639
53738
|
} catch (err2) {
|
|
53640
53739
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
53641
|
-
console.error(
|
|
53740
|
+
console.error(chalk23.red(`Failed to restore "${name}": ${msg}`));
|
|
53642
53741
|
process.exitCode = 1;
|
|
53643
53742
|
return;
|
|
53644
53743
|
}
|
|
53645
53744
|
console.error(
|
|
53646
|
-
|
|
53745
|
+
chalk23.green(`\u2713 restored ${e.name}`) + chalk23.gray(` (${e.agentLabel}) \u2014 restart ${e.agentLabel} to apply.`)
|
|
53647
53746
|
);
|
|
53648
53747
|
});
|
|
53649
53748
|
}
|
|
@@ -53669,14 +53768,14 @@ function registerMcpPinCommand(program2) {
|
|
|
53669
53768
|
}
|
|
53670
53769
|
}
|
|
53671
53770
|
if (repoCorrupt) {
|
|
53672
|
-
console.error(
|
|
53771
|
+
console.error(chalk24.red(`
|
|
53673
53772
|
\u274C Repo pin file at ${found.path} is corrupt or unreadable.`));
|
|
53674
53773
|
process.exit(1);
|
|
53675
53774
|
}
|
|
53676
53775
|
if (!homeResult.ok && homeResult.reason === "corrupt") {
|
|
53677
|
-
console.error(
|
|
53776
|
+
console.error(chalk24.red(`
|
|
53678
53777
|
\u274C Home pin file is corrupt: ${homeResult.detail}`));
|
|
53679
|
-
console.error(
|
|
53778
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53680
53779
|
process.exit(1);
|
|
53681
53780
|
}
|
|
53682
53781
|
const homeEntries = homeResult.ok ? homeResult.pins.servers : {};
|
|
@@ -53688,25 +53787,25 @@ function registerMcpPinCommand(program2) {
|
|
|
53688
53787
|
merged.set(key, { entry, source: "repo" });
|
|
53689
53788
|
}
|
|
53690
53789
|
if (merged.size === 0) {
|
|
53691
|
-
console.log(
|
|
53790
|
+
console.log(chalk24.gray("\nNo MCP servers are pinned yet."));
|
|
53692
53791
|
console.log(
|
|
53693
|
-
|
|
53792
|
+
chalk24.gray("Pins are created automatically when the MCP gateway first connects.\n")
|
|
53694
53793
|
);
|
|
53695
53794
|
return;
|
|
53696
53795
|
}
|
|
53697
|
-
console.log(
|
|
53796
|
+
console.log(chalk24.bold("\n\u{1F512} Pinned MCP Servers\n"));
|
|
53698
53797
|
const showSource = found.source === "repo";
|
|
53699
53798
|
for (const [key, { entry, source }] of merged) {
|
|
53700
|
-
const tag = showSource ? ` ${
|
|
53701
|
-
console.log(` ${
|
|
53702
|
-
console.log(` Tools (${entry.toolCount}): ${
|
|
53703
|
-
console.log(` Hash: ${
|
|
53704
|
-
console.log(` Pinned: ${
|
|
53799
|
+
const tag = showSource ? ` ${chalk24.yellow(`[${source}]`)}` : "";
|
|
53800
|
+
console.log(` ${chalk24.cyan(key)}${tag} ${chalk24.gray(entry.label)}`);
|
|
53801
|
+
console.log(` Tools (${entry.toolCount}): ${chalk24.white(entry.toolNames.join(", "))}`);
|
|
53802
|
+
console.log(` Hash: ${chalk24.gray(entry.toolsHash.slice(0, 16))}...`);
|
|
53803
|
+
console.log(` Pinned: ${chalk24.gray(entry.pinnedAt)}`);
|
|
53705
53804
|
console.log("");
|
|
53706
53805
|
}
|
|
53707
53806
|
if (showSource) {
|
|
53708
|
-
console.log(
|
|
53709
|
-
console.log(
|
|
53807
|
+
console.log(chalk24.gray(` [repo] entries come from ${found.path}`));
|
|
53808
|
+
console.log(chalk24.gray(" [home] entries come from ~/.node9/mcp-pins.json\n"));
|
|
53710
53809
|
}
|
|
53711
53810
|
});
|
|
53712
53811
|
pinSubCmd.command("promote <serverKey>").description(
|
|
@@ -53716,22 +53815,22 @@ function registerMcpPinCommand(program2) {
|
|
|
53716
53815
|
const { repoPath, created } = promotePin(serverKey, process.cwd());
|
|
53717
53816
|
if (created) {
|
|
53718
53817
|
console.log(
|
|
53719
|
-
|
|
53818
|
+
chalk24.green(
|
|
53720
53819
|
`
|
|
53721
|
-
\u2705 Created ${repoPath} with the promoted pin for ${
|
|
53820
|
+
\u2705 Created ${repoPath} with the promoted pin for ${chalk24.cyan(serverKey)}.`
|
|
53722
53821
|
)
|
|
53723
53822
|
);
|
|
53724
53823
|
} else {
|
|
53725
|
-
console.log(
|
|
53726
|
-
\u2705 Promoted ${
|
|
53824
|
+
console.log(chalk24.green(`
|
|
53825
|
+
\u2705 Promoted ${chalk24.cyan(serverKey)} into ${repoPath}.`));
|
|
53727
53826
|
}
|
|
53728
|
-
console.log(
|
|
53729
|
-
console.log(
|
|
53730
|
-
console.log(
|
|
53827
|
+
console.log(chalk24.gray(" Review the change and commit it:"));
|
|
53828
|
+
console.log(chalk24.cyan(` git add ${repoPath}`));
|
|
53829
|
+
console.log(chalk24.cyan(` git commit -m "pin ${serverKey} (node9)"`));
|
|
53731
53830
|
console.log("");
|
|
53732
53831
|
} catch (err2) {
|
|
53733
53832
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
53734
|
-
console.error(
|
|
53833
|
+
console.error(chalk24.red(`
|
|
53735
53834
|
\u274C ${msg}
|
|
53736
53835
|
`));
|
|
53737
53836
|
process.exit(1);
|
|
@@ -53744,36 +53843,36 @@ function registerMcpPinCommand(program2) {
|
|
|
53744
53843
|
try {
|
|
53745
53844
|
pins = readMcpPins();
|
|
53746
53845
|
} catch {
|
|
53747
|
-
console.error(
|
|
53748
|
-
console.error(
|
|
53846
|
+
console.error(chalk24.red("\n\u274C Pin file is corrupt."));
|
|
53847
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53749
53848
|
process.exit(1);
|
|
53750
53849
|
}
|
|
53751
53850
|
if (!pins.servers[serverKey]) {
|
|
53752
|
-
console.error(
|
|
53851
|
+
console.error(chalk24.red(`
|
|
53753
53852
|
\u274C No pin found for server key "${serverKey}"
|
|
53754
53853
|
`));
|
|
53755
|
-
console.error(`Run ${
|
|
53854
|
+
console.error(`Run ${chalk24.cyan("node9 mcp pin list")} to see pinned servers.
|
|
53756
53855
|
`);
|
|
53757
53856
|
process.exit(1);
|
|
53758
53857
|
}
|
|
53759
53858
|
const label2 = pins.servers[serverKey].label;
|
|
53760
53859
|
removePin(serverKey);
|
|
53761
|
-
console.log(
|
|
53762
|
-
\u{1F513} Pin removed for ${
|
|
53763
|
-
console.log(
|
|
53764
|
-
console.log(
|
|
53860
|
+
console.log(chalk24.green(`
|
|
53861
|
+
\u{1F513} Pin removed for ${chalk24.cyan(serverKey)}`));
|
|
53862
|
+
console.log(chalk24.gray(` Server: ${label2}`));
|
|
53863
|
+
console.log(chalk24.gray(" Next connection will re-pin with current tool definitions.\n"));
|
|
53765
53864
|
});
|
|
53766
53865
|
pinSubCmd.command("reset").description("Clear all MCP pins (next connection to each server will re-pin)").action(() => {
|
|
53767
53866
|
const result = readMcpPinsSafe();
|
|
53768
53867
|
if (!result.ok && result.reason === "missing") {
|
|
53769
|
-
console.log(
|
|
53868
|
+
console.log(chalk24.gray("\nNo pins to clear.\n"));
|
|
53770
53869
|
return;
|
|
53771
53870
|
}
|
|
53772
53871
|
const count = result.ok ? Object.keys(result.pins.servers).length : "?";
|
|
53773
53872
|
clearAllPins();
|
|
53774
|
-
console.log(
|
|
53873
|
+
console.log(chalk24.green(`
|
|
53775
53874
|
\u{1F513} Cleared ${count} MCP pin(s).`));
|
|
53776
|
-
console.log(
|
|
53875
|
+
console.log(chalk24.gray(" Next connection to each server will re-pin.\n"));
|
|
53777
53876
|
});
|
|
53778
53877
|
pinCmd.command("forget [serverKey]").option("--stale", "Remove all stale (orphaned) servers at once").description("Remove a server pin that is no longer configured in any agent").action((serverKey, opts) => {
|
|
53779
53878
|
if (opts.stale) {
|
|
@@ -53782,7 +53881,7 @@ function registerMcpPinCommand(program2) {
|
|
|
53782
53881
|
}
|
|
53783
53882
|
if (!serverKey) {
|
|
53784
53883
|
console.error(
|
|
53785
|
-
|
|
53884
|
+
chalk24.red("\n\u274C Please provide a server key, or use --stale to remove all orphans.\n")
|
|
53786
53885
|
);
|
|
53787
53886
|
process.exit(1);
|
|
53788
53887
|
}
|
|
@@ -53790,15 +53889,15 @@ function registerMcpPinCommand(program2) {
|
|
|
53790
53889
|
try {
|
|
53791
53890
|
pins = readMcpPins();
|
|
53792
53891
|
} catch {
|
|
53793
|
-
console.error(
|
|
53794
|
-
console.error(
|
|
53892
|
+
console.error(chalk24.red("\n\u274C Pin file is corrupt."));
|
|
53893
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53795
53894
|
process.exit(1);
|
|
53796
53895
|
}
|
|
53797
53896
|
if (!pins.servers[serverKey]) {
|
|
53798
|
-
console.error(
|
|
53897
|
+
console.error(chalk24.red(`
|
|
53799
53898
|
\u274C No pin found for server key "${serverKey}"
|
|
53800
53899
|
`));
|
|
53801
|
-
console.error(`Run ${
|
|
53900
|
+
console.error(`Run ${chalk24.cyan("node9 mcp pin list")} to see pinned servers.
|
|
53802
53901
|
`);
|
|
53803
53902
|
process.exit(1);
|
|
53804
53903
|
}
|
|
@@ -53806,10 +53905,10 @@ function registerMcpPinCommand(program2) {
|
|
|
53806
53905
|
const liveKeys = inventoryServerKeys(inv);
|
|
53807
53906
|
if (liveKeys.has(serverKey)) {
|
|
53808
53907
|
const agent = "an agent config";
|
|
53809
|
-
console.error(
|
|
53908
|
+
console.error(chalk24.red(`
|
|
53810
53909
|
\u274C Server "${serverKey}" is still configured in ${agent}.`));
|
|
53811
53910
|
console.error(
|
|
53812
|
-
|
|
53911
|
+
chalk24.yellow(
|
|
53813
53912
|
` Remove it from the agent config first, then run: node9 mcp forget ${serverKey}
|
|
53814
53913
|
`
|
|
53815
53914
|
)
|
|
@@ -53818,10 +53917,10 @@ function registerMcpPinCommand(program2) {
|
|
|
53818
53917
|
}
|
|
53819
53918
|
const label2 = pins.servers[serverKey].label;
|
|
53820
53919
|
removePin(serverKey);
|
|
53821
|
-
console.log(
|
|
53822
|
-
\u2713 Forgot server ${
|
|
53823
|
-
console.log(
|
|
53824
|
-
console.log(
|
|
53920
|
+
console.log(chalk24.green(`
|
|
53921
|
+
\u2713 Forgot server ${chalk24.cyan(serverKey)}`));
|
|
53922
|
+
console.log(chalk24.gray(` Was: ${label2}`));
|
|
53923
|
+
console.log(chalk24.gray(" Pin removed \u2014 server will no longer appear in the dashboard.\n"));
|
|
53825
53924
|
});
|
|
53826
53925
|
}
|
|
53827
53926
|
function forgetAllStale() {
|
|
@@ -53829,16 +53928,16 @@ function forgetAllStale() {
|
|
|
53829
53928
|
try {
|
|
53830
53929
|
pins = readMcpPins();
|
|
53831
53930
|
} catch {
|
|
53832
|
-
console.error(
|
|
53833
|
-
console.error(
|
|
53931
|
+
console.error(chalk24.red("\n\u274C Pin file is corrupt."));
|
|
53932
|
+
console.error(chalk24.yellow(" Run: node9 mcp pin reset\n"));
|
|
53834
53933
|
process.exit(1);
|
|
53835
53934
|
}
|
|
53836
53935
|
const inv = inventoryMcp();
|
|
53837
53936
|
const liveKeys = inventoryServerKeys(inv);
|
|
53838
53937
|
if (liveKeys.size === 0) {
|
|
53839
|
-
console.error(
|
|
53938
|
+
console.error(chalk24.red("\n\u274C No live MCP servers detected \u2014 refusing to remove every pin."));
|
|
53840
53939
|
console.error(
|
|
53841
|
-
|
|
53940
|
+
chalk24.yellow(
|
|
53842
53941
|
" This usually means an agent config is missing or unreadable, not that\n every server is gone. Check your agent configs, or remove one at a\n time: node9 mcp forget <key>\n"
|
|
53843
53942
|
)
|
|
53844
53943
|
);
|
|
@@ -53846,118 +53945,118 @@ function forgetAllStale() {
|
|
|
53846
53945
|
}
|
|
53847
53946
|
const stale = Object.entries(pins.servers).filter(([sk]) => !liveKeys.has(sk));
|
|
53848
53947
|
if (stale.length === 0) {
|
|
53849
|
-
console.log(
|
|
53948
|
+
console.log(chalk24.gray("\nNo stale servers to remove.\n"));
|
|
53850
53949
|
return;
|
|
53851
53950
|
}
|
|
53852
53951
|
for (const [sk, pin] of stale) {
|
|
53853
53952
|
delete pins.servers[sk];
|
|
53854
|
-
console.log(
|
|
53953
|
+
console.log(chalk24.green(` \u2713 ${chalk24.cyan(sk)} ${chalk24.gray(pin.label)}`));
|
|
53855
53954
|
}
|
|
53856
53955
|
writeMcpPins(pins);
|
|
53857
|
-
console.log(
|
|
53956
|
+
console.log(chalk24.green(`
|
|
53858
53957
|
Removed ${stale.length} stale server(s).
|
|
53859
53958
|
`));
|
|
53860
53959
|
}
|
|
53861
53960
|
|
|
53862
53961
|
// src/cli/commands/sync.ts
|
|
53863
53962
|
init_sync();
|
|
53864
|
-
import
|
|
53963
|
+
import chalk25 from "chalk";
|
|
53865
53964
|
function registerSyncCommand(program2) {
|
|
53866
53965
|
const policy = program2.command("policy").description("Manage cloud policy rules");
|
|
53867
53966
|
policy.command("push").description("Push this machine's effective policy to the node9 dashboard").action(async () => {
|
|
53868
|
-
process.stdout.write(
|
|
53967
|
+
process.stdout.write(chalk25.cyan("Pushing policy to the dashboard\u2026"));
|
|
53869
53968
|
const result = await runPolicyPush();
|
|
53870
53969
|
process.stdout.write("\n");
|
|
53871
53970
|
if (!result.ok) {
|
|
53872
|
-
console.error(
|
|
53971
|
+
console.error(chalk25.red(`\u2717 ${result.reason}`));
|
|
53873
53972
|
process.exit(1);
|
|
53874
53973
|
}
|
|
53875
|
-
console.log(
|
|
53876
|
-
console.log(
|
|
53974
|
+
console.log(chalk25.green("\u2713 Policy mirrored to the dashboard"));
|
|
53975
|
+
console.log(chalk25.gray(" See it under Security Policy \u2192 Machines"));
|
|
53877
53976
|
});
|
|
53878
53977
|
policy.command("sync").description("Sync cloud policy rules to local cache (~/.node9/rules-cache.json)").action(async () => {
|
|
53879
|
-
process.stdout.write(
|
|
53978
|
+
process.stdout.write(chalk25.cyan("Syncing cloud policy rules\u2026"));
|
|
53880
53979
|
const result = await runCloudSync();
|
|
53881
53980
|
process.stdout.write("\n");
|
|
53882
53981
|
if (!result.ok) {
|
|
53883
|
-
console.error(
|
|
53982
|
+
console.error(chalk25.red(`\u2717 ${result.reason}`));
|
|
53884
53983
|
process.exit(1);
|
|
53885
53984
|
}
|
|
53886
53985
|
if (result.unchanged) {
|
|
53887
53986
|
console.log(
|
|
53888
|
-
|
|
53987
|
+
chalk25.green(
|
|
53889
53988
|
`\u2713 Already up to date \u2014 ${result.rules} rule${result.rules === 1 ? "" : "s"} cached`
|
|
53890
53989
|
)
|
|
53891
53990
|
);
|
|
53892
|
-
console.log(
|
|
53893
|
-
console.log(
|
|
53991
|
+
console.log(chalk25.gray(` Cached at: ${result.fetchedAt}`));
|
|
53992
|
+
console.log(chalk25.gray(` Server returned 304 (no changes since last sync)`));
|
|
53894
53993
|
} else {
|
|
53895
53994
|
console.log(
|
|
53896
|
-
|
|
53995
|
+
chalk25.green(`\u2713 Synced ${result.rules} rule${result.rules === 1 ? "" : "s"} from cloud`)
|
|
53897
53996
|
);
|
|
53898
|
-
console.log(
|
|
53899
|
-
console.log(
|
|
53997
|
+
console.log(chalk25.gray(` Cached at: ${result.fetchedAt}`));
|
|
53998
|
+
console.log(chalk25.gray(` File: ~/.node9/rules-cache.json`));
|
|
53900
53999
|
}
|
|
53901
54000
|
});
|
|
53902
54001
|
policy.command("show").description("List all cloud policy rules in the local cache").action(() => {
|
|
53903
54002
|
const status = getCloudSyncStatus();
|
|
53904
54003
|
if (!status.cached) {
|
|
53905
|
-
console.log(
|
|
54004
|
+
console.log(chalk25.yellow("\n No cloud rules cached \u2014 run: node9 policy sync\n"));
|
|
53906
54005
|
return;
|
|
53907
54006
|
}
|
|
53908
54007
|
const rules = getCloudRules() ?? [];
|
|
53909
54008
|
const age = Math.round((Date.now() - new Date(status.fetchedAt).getTime()) / 6e4);
|
|
53910
54009
|
console.log(
|
|
53911
|
-
|
|
53912
|
-
Cloud policy rules`) +
|
|
54010
|
+
chalk25.bold(`
|
|
54011
|
+
Cloud policy rules`) + chalk25.gray(
|
|
53913
54012
|
` (${rules.length} rule${rules.length === 1 ? "" : "s"}, synced ${age}m ago)
|
|
53914
54013
|
`
|
|
53915
54014
|
)
|
|
53916
54015
|
);
|
|
53917
54016
|
if (rules.length === 0) {
|
|
53918
|
-
console.log(
|
|
54017
|
+
console.log(chalk25.gray(" No rules defined in cloud policy.\n"));
|
|
53919
54018
|
return;
|
|
53920
54019
|
}
|
|
53921
54020
|
for (const rule of rules) {
|
|
53922
54021
|
const r = rule;
|
|
53923
|
-
const verdictColor = r.verdict === "block" ?
|
|
54022
|
+
const verdictColor = r.verdict === "block" ? chalk25.red : r.verdict === "allow" ? chalk25.green : chalk25.yellow;
|
|
53924
54023
|
console.log(
|
|
53925
54024
|
` ${verdictColor(
|
|
53926
54025
|
String(r.verdict ?? "unknown").toUpperCase().padEnd(6)
|
|
53927
|
-
)} ${
|
|
54026
|
+
)} ${chalk25.white(String(r.name ?? "(unnamed)"))}`
|
|
53928
54027
|
);
|
|
53929
|
-
if (r.reason) console.log(
|
|
54028
|
+
if (r.reason) console.log(chalk25.gray(` ${String(r.reason)}`));
|
|
53930
54029
|
}
|
|
53931
54030
|
console.log("");
|
|
53932
54031
|
});
|
|
53933
54032
|
policy.command("status").description("Show current cloud policy cache status").action(() => {
|
|
53934
54033
|
const s = getCloudSyncStatus();
|
|
53935
54034
|
if (!s.cached) {
|
|
53936
|
-
console.log(
|
|
54035
|
+
console.log(chalk25.yellow("\n No cache yet \u2014 run: node9 policy sync\n"));
|
|
53937
54036
|
return;
|
|
53938
54037
|
}
|
|
53939
54038
|
const age = Math.round((Date.now() - new Date(s.fetchedAt).getTime()) / 6e4);
|
|
53940
54039
|
console.log(`
|
|
53941
|
-
Rules : ${
|
|
54040
|
+
Rules : ${chalk25.green(String(s.rules))} cloud rules loaded`);
|
|
53942
54041
|
console.log(
|
|
53943
|
-
` Synced : ${
|
|
54042
|
+
` Synced : ${chalk25.gray(`${age} minute${age === 1 ? "" : "s"} ago`)} (${s.fetchedAt})`
|
|
53944
54043
|
);
|
|
53945
54044
|
if (s.workspaceId) {
|
|
53946
|
-
console.log(` Workspace: ${
|
|
54045
|
+
console.log(` Workspace: ${chalk25.gray(s.workspaceId)}`);
|
|
53947
54046
|
}
|
|
53948
54047
|
if (s.panicMode) {
|
|
53949
54048
|
console.log(
|
|
53950
|
-
` ${
|
|
54049
|
+
` ${chalk25.red.bold("\u{1F6A8} Panic mode : ON")} ` + chalk25.dim("(every review-verdict becomes block)")
|
|
53951
54050
|
);
|
|
53952
54051
|
}
|
|
53953
54052
|
if (s.shadowMode) {
|
|
53954
54053
|
console.log(
|
|
53955
|
-
` ${
|
|
54054
|
+
` ${chalk25.yellow.bold("\u{1F441} Shadow mode : ON")} ` + chalk25.dim("(blocks become would-block log entries)")
|
|
53956
54055
|
);
|
|
53957
54056
|
}
|
|
53958
54057
|
if (s.syncIntervalHours) {
|
|
53959
54058
|
console.log(
|
|
53960
|
-
|
|
54059
|
+
chalk25.gray(
|
|
53961
54060
|
` Polling : every ${s.syncIntervalHours} hour${s.syncIntervalHours === 1 ? "" : "s"}`
|
|
53962
54061
|
)
|
|
53963
54062
|
);
|
|
@@ -53968,7 +54067,7 @@ function registerSyncCommand(program2) {
|
|
|
53968
54067
|
|
|
53969
54068
|
// src/cli/commands/agents.ts
|
|
53970
54069
|
init_setup();
|
|
53971
|
-
import
|
|
54070
|
+
import chalk26 from "chalk";
|
|
53972
54071
|
var SETUP_FN = {
|
|
53973
54072
|
claude: setupClaude,
|
|
53974
54073
|
gemini: setupGemini,
|
|
@@ -54011,30 +54110,30 @@ function registerAgentsCommand(program2) {
|
|
|
54011
54110
|
console.log(` ${"Agent".padEnd(14)}${"Installed".padEnd(11)}${"Wired".padEnd(8)}Mode`);
|
|
54012
54111
|
console.log(" " + "\u2500".repeat(44));
|
|
54013
54112
|
for (const s of statuses) {
|
|
54014
|
-
const installed = s.installed ?
|
|
54015
|
-
const wired = !s.installed ?
|
|
54016
|
-
const mode = s.mode ?
|
|
54017
|
-
const hint = s.installed && !s.wired ?
|
|
54113
|
+
const installed = s.installed ? chalk26.green("\u2713") : chalk26.gray("\u2717");
|
|
54114
|
+
const wired = !s.installed ? chalk26.gray("\u2014") : s.wired ? chalk26.green("\u2713") : chalk26.yellow("\u2717");
|
|
54115
|
+
const mode = s.mode ? chalk26.gray(s.mode) : chalk26.gray("\u2014");
|
|
54116
|
+
const hint = s.installed && !s.wired ? chalk26.gray(` \u2190 node9 agents add ${s.name}`) : "";
|
|
54018
54117
|
console.log(` ${s.label.padEnd(14)}${installed} ${wired} ${mode}${hint}`);
|
|
54019
54118
|
}
|
|
54020
54119
|
console.log("");
|
|
54021
54120
|
if (!anyInstalled) {
|
|
54022
54121
|
console.log(
|
|
54023
|
-
|
|
54122
|
+
chalk26.gray(" No AI agents detected. Install Claude Code, Gemini CLI, Cursor,\n") + chalk26.gray(" Windsurf, VSCode, or Codex then run: node9 agents list\n")
|
|
54024
54123
|
);
|
|
54025
54124
|
return;
|
|
54026
54125
|
}
|
|
54027
54126
|
const unwired = statuses.filter((s) => s.installed && !s.wired);
|
|
54028
54127
|
if (unwired.length > 0) {
|
|
54029
54128
|
console.log(
|
|
54030
|
-
|
|
54129
|
+
chalk26.yellow(` ${unwired.length} agent(s) not yet wired. Run: `) + chalk26.white(`node9 agents add ${unwired[0].name}`) + "\n"
|
|
54031
54130
|
);
|
|
54032
54131
|
}
|
|
54033
54132
|
if (statuses.some((s) => s.name === "gemini" && s.installed)) {
|
|
54034
54133
|
console.log(
|
|
54035
|
-
|
|
54134
|
+
chalk26.yellow(
|
|
54036
54135
|
" \u26A0\uFE0F Gemini CLI stops serving AI Pro/Ultra and free tiers on 2026-06-18.\n"
|
|
54037
|
-
) +
|
|
54136
|
+
) + chalk26.gray(" Migrate to Antigravity: ") + chalk26.white("node9 agents add antigravity") + "\n"
|
|
54038
54137
|
);
|
|
54039
54138
|
}
|
|
54040
54139
|
});
|
|
@@ -54042,7 +54141,7 @@ function registerAgentsCommand(program2) {
|
|
|
54042
54141
|
const name = resolveAgentName(agent);
|
|
54043
54142
|
const fn = SETUP_FN[name];
|
|
54044
54143
|
if (!fn) {
|
|
54045
|
-
console.error(
|
|
54144
|
+
console.error(chalk26.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
54046
54145
|
process.exit(1);
|
|
54047
54146
|
}
|
|
54048
54147
|
await fn();
|
|
@@ -54051,14 +54150,14 @@ function registerAgentsCommand(program2) {
|
|
|
54051
54150
|
const name = resolveAgentName(agent);
|
|
54052
54151
|
const fn = TEARDOWN_FN[name];
|
|
54053
54152
|
if (!fn) {
|
|
54054
|
-
console.error(
|
|
54153
|
+
console.error(chalk26.red(`Unknown agent: "${agent}". Supported: ${AGENT_NAMES.join(", ")}`));
|
|
54055
54154
|
process.exit(1);
|
|
54056
54155
|
}
|
|
54057
|
-
console.log(
|
|
54156
|
+
console.log(chalk26.cyan(`
|
|
54058
54157
|
\u{1F6E1}\uFE0F Node9: removing from ${name}...
|
|
54059
54158
|
`));
|
|
54060
54159
|
fn();
|
|
54061
|
-
console.log(
|
|
54160
|
+
console.log(chalk26.gray("\n Restart the agent for changes to take effect."));
|
|
54062
54161
|
});
|
|
54063
54162
|
}
|
|
54064
54163
|
|
|
@@ -54067,21 +54166,21 @@ init_scan();
|
|
|
54067
54166
|
|
|
54068
54167
|
// src/cli/commands/posture.ts
|
|
54069
54168
|
init_posture();
|
|
54070
|
-
import
|
|
54169
|
+
import chalk28 from "chalk";
|
|
54071
54170
|
|
|
54072
54171
|
// src/posture/render.ts
|
|
54073
54172
|
init_score();
|
|
54074
|
-
import
|
|
54173
|
+
import chalk27 from "chalk";
|
|
54075
54174
|
var ICON = {
|
|
54076
|
-
critical:
|
|
54077
|
-
high:
|
|
54078
|
-
medium:
|
|
54079
|
-
advisory:
|
|
54175
|
+
critical: chalk27.red("\u274C"),
|
|
54176
|
+
high: chalk27.red("\u274C"),
|
|
54177
|
+
medium: chalk27.yellow("\u26A0\uFE0F "),
|
|
54178
|
+
advisory: chalk27.gray("\u26A0\uFE0F ")
|
|
54080
54179
|
};
|
|
54081
54180
|
var TIER_LABEL = {
|
|
54082
|
-
good:
|
|
54083
|
-
"at-risk":
|
|
54084
|
-
critical:
|
|
54181
|
+
good: chalk27.green("Good"),
|
|
54182
|
+
"at-risk": chalk27.yellow("At risk"),
|
|
54183
|
+
critical: chalk27.red("Critical")
|
|
54085
54184
|
};
|
|
54086
54185
|
function wrap(text, width) {
|
|
54087
54186
|
const out = [];
|
|
@@ -54099,7 +54198,7 @@ function wrap(text, width) {
|
|
|
54099
54198
|
}
|
|
54100
54199
|
var LABEL_WIDTH = 14;
|
|
54101
54200
|
function label(category) {
|
|
54102
|
-
return
|
|
54201
|
+
return chalk27.bold(category.padEnd(Math.max(LABEL_WIDTH, category.length + 1)));
|
|
54103
54202
|
}
|
|
54104
54203
|
function guardedWhat(f) {
|
|
54105
54204
|
if (f.detail.length === 0) return "this";
|
|
@@ -54109,31 +54208,31 @@ function guardedWhat(f) {
|
|
|
54109
54208
|
}
|
|
54110
54209
|
function renderFinding(f, showWeight = false, displayLabel = f.category) {
|
|
54111
54210
|
const lines = [];
|
|
54112
|
-
const wt = showWeight && f.scoreWeight ?
|
|
54211
|
+
const wt = showWeight && f.scoreWeight ? chalk27.cyan.bold(`+${f.scoreWeight} `) : "";
|
|
54113
54212
|
lines.push(` ${ICON[f.severity]} ${label(displayLabel)}${wt}${f.title}`);
|
|
54114
54213
|
const indent = " ".repeat(2 + 3 + LABEL_WIDTH);
|
|
54115
54214
|
const width = 80 - indent.length;
|
|
54116
54215
|
for (const s of [f.what, f.why, f.who]) {
|
|
54117
|
-
if (s) for (const l of wrap(s, width)) lines.push(indent +
|
|
54216
|
+
if (s) for (const l of wrap(s, width)) lines.push(indent + chalk27.gray(l));
|
|
54118
54217
|
}
|
|
54119
|
-
for (const d of f.detail) lines.push(indent +
|
|
54218
|
+
for (const d of f.detail) lines.push(indent + chalk27.gray(d));
|
|
54120
54219
|
if (f.fix) {
|
|
54121
54220
|
let first = true;
|
|
54122
54221
|
for (const seg of f.fix.split("\n")) {
|
|
54123
54222
|
for (const l of wrap(seg, width - 2)) {
|
|
54124
|
-
lines.push(indent +
|
|
54223
|
+
lines.push(indent + chalk27.cyan(first ? "\u2192 " + l : " " + l));
|
|
54125
54224
|
first = false;
|
|
54126
54225
|
}
|
|
54127
54226
|
}
|
|
54128
54227
|
}
|
|
54129
54228
|
const tradeoff = [
|
|
54130
|
-
[f.gain, "gain: ",
|
|
54131
|
-
[f.cost, "cost: ",
|
|
54229
|
+
[f.gain, "gain: ", chalk27.green],
|
|
54230
|
+
[f.cost, "cost: ", chalk27.yellow]
|
|
54132
54231
|
];
|
|
54133
54232
|
for (const [text, lbl, color2] of tradeoff) {
|
|
54134
54233
|
if (!text) continue;
|
|
54135
54234
|
wrap(text, width - 6).forEach((l, i) => {
|
|
54136
|
-
lines.push(indent + (i === 0 ? color2(lbl) : " ") +
|
|
54235
|
+
lines.push(indent + (i === 0 ? color2(lbl) : " ") + chalk27.gray(l));
|
|
54137
54236
|
});
|
|
54138
54237
|
}
|
|
54139
54238
|
return lines;
|
|
@@ -54143,7 +54242,7 @@ function renderPosture(result) {
|
|
|
54143
54242
|
const tier = TIER_LABEL[result.tier];
|
|
54144
54243
|
lines.push("");
|
|
54145
54244
|
lines.push(
|
|
54146
|
-
|
|
54245
|
+
chalk27.cyan.bold(`\u{1F6E1}\uFE0F Node9 Posture`) + chalk27.gray(` \u2014 ${result.agent}`) + ` ${chalk27.bold(`Score: ${result.score}/100`)} (${tier})`
|
|
54147
54246
|
);
|
|
54148
54247
|
const headroom = openHeadroom(result.findings);
|
|
54149
54248
|
if (headroom > 0) {
|
|
@@ -54151,15 +54250,15 @@ function renderPosture(result) {
|
|
|
54151
54250
|
(f) => f.coverage?.state !== "covered" && f.coverage?.state !== "cant-fix" && !f.scoreWeight && f.severity !== "advisory"
|
|
54152
54251
|
).length;
|
|
54153
54252
|
const note = openExposures > 0 ? `Of the gap to 100: ${headroom} pts is optional hardening you can choose to turn on (the \u{1F512} tier below, each at some cost to flexibility); the rest is the open findings \u2014 the flagged rows below. Fix those first.` : `${headroom} pts of headroom \u2014 optional hardening you can choose to turn on (the \u{1F512} tier below), each at some cost to flexibility.`;
|
|
54154
|
-
for (const l of wrap(note, 76)) lines.push(" " +
|
|
54253
|
+
for (const l of wrap(note, 76)) lines.push(" " + chalk27.gray(l));
|
|
54155
54254
|
}
|
|
54156
54255
|
lines.push("");
|
|
54157
54256
|
if (result.headline) {
|
|
54158
54257
|
const indent = " ";
|
|
54159
|
-
lines.push(` ${
|
|
54160
|
-
for (const l of wrap(result.headline.risk, 74)) lines.push(indent +
|
|
54258
|
+
lines.push(` ${chalk27.red.bold("\u{1F525} Biggest risk")}`);
|
|
54259
|
+
for (const l of wrap(result.headline.risk, 74)) lines.push(indent + chalk27.white(l));
|
|
54161
54260
|
const action = wrap(`Do this first: ${result.headline.action}`, 72);
|
|
54162
|
-
action.forEach((l, i) => lines.push(indent +
|
|
54261
|
+
action.forEach((l, i) => lines.push(indent + chalk27.cyan(i === 0 ? "\u2192 " + l : " " + l)));
|
|
54163
54262
|
lines.push("");
|
|
54164
54263
|
}
|
|
54165
54264
|
const covered = result.findings.filter((f) => f.coverage?.state === "covered");
|
|
@@ -54169,13 +54268,13 @@ function renderPosture(result) {
|
|
|
54169
54268
|
);
|
|
54170
54269
|
const openLabel = (f) => collision.has(f.category) ? `${f.category} (exposed)` : f.category;
|
|
54171
54270
|
if (covered.length > 0) {
|
|
54172
|
-
lines.push(" " +
|
|
54271
|
+
lines.push(" " + chalk27.green("\u{1F7E2} ON NOW \u2014 node9 is enforcing these (your floor)"));
|
|
54173
54272
|
for (const f of covered) {
|
|
54174
54273
|
const gated = f.coverage?.level === "review" ? "approval-gating" : "blocking";
|
|
54175
54274
|
const via = f.coverage?.via ?? "node9";
|
|
54176
54275
|
const lbl = collision.has(f.category) ? `${f.category} (guarded)` : f.category;
|
|
54177
54276
|
lines.push(
|
|
54178
|
-
` ${
|
|
54277
|
+
` ${chalk27.green("\u2705")} ${label(lbl)}${chalk27.gray(`${via} is ${gated} ${guardedWhat(f)}`)}`
|
|
54179
54278
|
);
|
|
54180
54279
|
}
|
|
54181
54280
|
lines.push("");
|
|
@@ -54184,24 +54283,24 @@ function renderPosture(result) {
|
|
|
54184
54283
|
const reduceOpen = open.filter((f) => f.owner !== "node9" && f.node9Reduces);
|
|
54185
54284
|
const osOpen = open.filter((f) => f.owner !== "node9" && !f.node9Reduces);
|
|
54186
54285
|
if (node9Open.length > 0) {
|
|
54187
|
-
lines.push(" " +
|
|
54286
|
+
lines.push(" " + chalk27.cyan.bold("\u{1F527} node9 can fix these \u2014 run the command"));
|
|
54188
54287
|
for (const f of node9Open) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
54189
54288
|
}
|
|
54190
54289
|
if (reduceOpen.length > 0) {
|
|
54191
54290
|
if (node9Open.length > 0) lines.push("");
|
|
54192
|
-
lines.push(" " +
|
|
54291
|
+
lines.push(" " + chalk27.yellow.bold("\u{1F512} AVAILABLE \u2014 turn on to harden (each has a tradeoff)"));
|
|
54193
54292
|
for (const f of reduceOpen) lines.push(...renderFinding(f, true, openLabel(f)));
|
|
54194
54293
|
}
|
|
54195
54294
|
if (osOpen.length > 0) {
|
|
54196
54295
|
if (node9Open.length > 0 || reduceOpen.length > 0) lines.push("");
|
|
54197
|
-
lines.push(" " +
|
|
54296
|
+
lines.push(" " + chalk27.bold("\u{1F9F1} YOUR PART \u2014 node9 can't fix these (OS-level)"));
|
|
54198
54297
|
for (const f of osOpen) lines.push(...renderFinding(f, false, openLabel(f)));
|
|
54199
54298
|
}
|
|
54200
54299
|
for (const cat of result.passedCategories) {
|
|
54201
|
-
lines.push(` ${
|
|
54300
|
+
lines.push(` ${chalk27.green("\u2705")} ${label(cat)}${chalk27.gray("no issues found")}`);
|
|
54202
54301
|
}
|
|
54203
54302
|
for (const cat of result.erroredCategories) {
|
|
54204
|
-
lines.push(` ${
|
|
54303
|
+
lines.push(` ${chalk27.gray("\u2022")} ${label(cat)}${chalk27.gray("could not be checked")}`);
|
|
54205
54304
|
}
|
|
54206
54305
|
lines.push("");
|
|
54207
54306
|
const crit = open.filter((f) => f.severity === "critical").length;
|
|
@@ -54209,16 +54308,16 @@ function renderPosture(result) {
|
|
|
54209
54308
|
const med = open.filter((f) => f.severity === "medium").length;
|
|
54210
54309
|
const adv = open.filter((f) => f.severity === "advisory").length;
|
|
54211
54310
|
const parts = [];
|
|
54212
|
-
if (crit) parts.push(
|
|
54213
|
-
if (high) parts.push(
|
|
54214
|
-
if (med) parts.push(
|
|
54215
|
-
if (adv) parts.push(
|
|
54216
|
-
const summary = parts.length ? parts.join(" \xB7 ") :
|
|
54311
|
+
if (crit) parts.push(chalk27.red(`${crit} critical`));
|
|
54312
|
+
if (high) parts.push(chalk27.red(`${high} high`));
|
|
54313
|
+
if (med) parts.push(chalk27.yellow(`${med} medium`));
|
|
54314
|
+
if (adv) parts.push(chalk27.gray(`${adv} advisory`));
|
|
54315
|
+
const summary = parts.length ? parts.join(" \xB7 ") : chalk27.green("no findings");
|
|
54217
54316
|
lines.push(` ${summary}`);
|
|
54218
54317
|
lines.push("");
|
|
54219
|
-
lines.push(" " +
|
|
54318
|
+
lines.push(" " + chalk27.bold("Track this across your fleet & keep it green:"));
|
|
54220
54319
|
lines.push(
|
|
54221
|
-
" " +
|
|
54320
|
+
" " + chalk27.dim("\u2192 ") + chalk27.cyan.underline("https://node9.ai/auth/signup?ref=cli_posture")
|
|
54222
54321
|
);
|
|
54223
54322
|
lines.push("");
|
|
54224
54323
|
return lines.join("\n");
|
|
@@ -54238,11 +54337,11 @@ function registerPostureCommand(program2) {
|
|
|
54238
54337
|
if (opts.ship) {
|
|
54239
54338
|
const creds = readCredentials();
|
|
54240
54339
|
if (!creds) {
|
|
54241
|
-
console.error(
|
|
54340
|
+
console.error(chalk28.gray(" Run `node9 login` to ship this to your dashboard."));
|
|
54242
54341
|
} else {
|
|
54243
54342
|
const ok2 = await shipPosture(result, creds);
|
|
54244
54343
|
console.error(
|
|
54245
|
-
ok2 ?
|
|
54344
|
+
ok2 ? chalk28.gray(" \u2713 Shipped to your node9 dashboard.") : chalk28.gray(" Could not reach the dashboard \u2014 saved locally only.")
|
|
54246
54345
|
);
|
|
54247
54346
|
}
|
|
54248
54347
|
}
|
|
@@ -54251,7 +54350,7 @@ function registerPostureCommand(program2) {
|
|
|
54251
54350
|
}
|
|
54252
54351
|
|
|
54253
54352
|
// src/cli/commands/scan-repo.ts
|
|
54254
|
-
import
|
|
54353
|
+
import chalk30 from "chalk";
|
|
54255
54354
|
|
|
54256
54355
|
// src/ci-check/fetch.ts
|
|
54257
54356
|
var import_undici = __toESM(require_undici());
|
|
@@ -55394,7 +55493,7 @@ async function scanRepo(input, onProgress) {
|
|
|
55394
55493
|
}
|
|
55395
55494
|
|
|
55396
55495
|
// src/ci-check/render.ts
|
|
55397
|
-
import
|
|
55496
|
+
import chalk29 from "chalk";
|
|
55398
55497
|
var ICON2 = {
|
|
55399
55498
|
critical: "\u{1F534}",
|
|
55400
55499
|
high: "\u{1F534}",
|
|
@@ -55402,39 +55501,39 @@ var ICON2 = {
|
|
|
55402
55501
|
advisory: "\u{1F7E2}"
|
|
55403
55502
|
};
|
|
55404
55503
|
var COLOR = {
|
|
55405
|
-
critical:
|
|
55406
|
-
high:
|
|
55407
|
-
medium:
|
|
55408
|
-
advisory:
|
|
55504
|
+
critical: chalk29.red.bold,
|
|
55505
|
+
high: chalk29.red,
|
|
55506
|
+
medium: chalk29.yellow,
|
|
55507
|
+
advisory: chalk29.gray
|
|
55409
55508
|
};
|
|
55410
55509
|
var ACTION_URL = "https://github.com/marketplace/actions/node9-agent-security-check?ref=cli_scan_repo";
|
|
55411
55510
|
function renderCta(res) {
|
|
55412
55511
|
const L = [];
|
|
55413
|
-
L.push(
|
|
55512
|
+
L.push(chalk29.dim(" " + "\u2500".repeat(63)));
|
|
55414
55513
|
if (res.worst === "critical" || res.worst === "high") {
|
|
55415
55514
|
const n = res.findings.filter((f) => f.severity === "critical" || f.severity === "high").length;
|
|
55416
55515
|
L.push(
|
|
55417
|
-
" " +
|
|
55516
|
+
" " + chalk29.red.bold(
|
|
55418
55517
|
`\u{1F534} ${n} ${n === 1 ? "issue" : "issues"} to fix \u2014 then stop the next at the PR.`
|
|
55419
55518
|
)
|
|
55420
55519
|
);
|
|
55421
55520
|
L.push("");
|
|
55422
|
-
L.push(" " +
|
|
55521
|
+
L.push(" " + chalk29.bold("Catch this class of issue on every PR, automatically:"));
|
|
55423
55522
|
} else if (res.worst) {
|
|
55424
|
-
L.push(" " +
|
|
55523
|
+
L.push(" " + chalk29.yellow("\u{1F7E1} Review the findings above, then keep it covered:"));
|
|
55425
55524
|
L.push("");
|
|
55426
|
-
L.push(" " +
|
|
55525
|
+
L.push(" " + chalk29.bold("Check every PR for agent-CI risk:"));
|
|
55427
55526
|
} else if (res.incomplete) {
|
|
55428
|
-
L.push(" " +
|
|
55527
|
+
L.push(" " + chalk29.yellow.bold("\u26A0\uFE0F Incomplete \u2014 not a clean bill of health."));
|
|
55429
55528
|
L.push("");
|
|
55430
|
-
L.push(" " +
|
|
55529
|
+
L.push(" " + chalk29.bold("Get a complete check on every PR (CI reads the tree directly):"));
|
|
55431
55530
|
} else {
|
|
55432
|
-
L.push(" " +
|
|
55531
|
+
L.push(" " + chalk29.green("\u2705 Agent CI is well-configured \u2014 0 unmitigated issues."));
|
|
55433
55532
|
L.push("");
|
|
55434
|
-
L.push(" " +
|
|
55533
|
+
L.push(" " + chalk29.bold("Keep it green as you add agent workflows \u2014 check every PR:"));
|
|
55435
55534
|
}
|
|
55436
|
-
L.push(" " +
|
|
55437
|
-
L.push(" " +
|
|
55535
|
+
L.push(" " + chalk29.dim("\u2192 ") + chalk29.cyan.underline(ACTION_URL));
|
|
55536
|
+
L.push(" " + chalk29.gray(" zero setup \xB7 no token \xB7 runs in your CI"));
|
|
55438
55537
|
return L;
|
|
55439
55538
|
}
|
|
55440
55539
|
function ownedHint(source) {
|
|
@@ -55443,13 +55542,13 @@ function ownedHint(source) {
|
|
|
55443
55542
|
function renderScan(res) {
|
|
55444
55543
|
const L = [];
|
|
55445
55544
|
const n = res.findings.length;
|
|
55446
|
-
const head = res.worst === "critical" || res.worst === "high" ?
|
|
55447
|
-
L.push(`\u{1F6E1}\uFE0F ${
|
|
55448
|
-
L.push(
|
|
55545
|
+
const head = res.worst === "critical" || res.worst === "high" ? chalk29.red.bold("\u26A0\uFE0F agent-security risk found") : res.worst ? chalk29.yellow("agent-security notes") : res.incomplete ? chalk29.yellow.bold("\u26A0\uFE0F INCOMPLETE \u2014 could not read all files") : chalk29.green("\u2705 agent-security: clean");
|
|
55546
|
+
L.push(`\u{1F6E1}\uFE0F ${chalk29.bold("node9 scan-repo")} \xB7 ${res.source} \xB7 ${head}`);
|
|
55547
|
+
L.push(chalk29.gray(` inspected ${res.inspected.length} config file(s), ${n} finding(s)`));
|
|
55449
55548
|
if (res.incomplete) {
|
|
55450
55549
|
const rateLimited = res.notes.some((nt) => /rate limit/i.test(nt));
|
|
55451
55550
|
L.push(
|
|
55452
|
-
|
|
55551
|
+
chalk29.yellow.bold(
|
|
55453
55552
|
rateLimited ? " \u26A0\uFE0F Rate-limited \u2014 some files were unread. NOT a clean bill of health; set GITHUB_TOKEN (or run `gh auth login`) and re-run." : " \u26A0\uFE0F A network error left some files unread. NOT a clean bill of health; re-run."
|
|
55454
55553
|
)
|
|
55455
55554
|
);
|
|
@@ -55457,26 +55556,26 @@ function renderScan(res) {
|
|
|
55457
55556
|
L.push("");
|
|
55458
55557
|
for (const f of res.findings) {
|
|
55459
55558
|
L.push(
|
|
55460
|
-
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${
|
|
55559
|
+
`${ICON2[f.severity]} ${COLOR[f.severity](f.severity.toUpperCase())} ${chalk29.bold(f.title)}`
|
|
55461
55560
|
);
|
|
55462
|
-
L.push(
|
|
55561
|
+
L.push(chalk29.gray(` ${f.file}${f.line ? ":" + f.line : ""} \xB7 ${f.check}`));
|
|
55463
55562
|
for (const s of f.signals) L.push(` \u2022 ${s}`);
|
|
55464
|
-
if (f.mitigations?.length) L.push(
|
|
55465
|
-
L.push(
|
|
55563
|
+
if (f.mitigations?.length) L.push(chalk29.gray(` \u2713 mitigated: ${f.mitigations.join("; ")}`));
|
|
55564
|
+
L.push(chalk29.cyan(` \u2192 ${f.fix}`));
|
|
55466
55565
|
L.push("");
|
|
55467
55566
|
}
|
|
55468
55567
|
if (n === 0 && !res.incomplete) {
|
|
55469
55568
|
L.push(
|
|
55470
|
-
|
|
55569
|
+
chalk29.gray(" No committed agent hooks, injectable workflows, or unpinned MCP servers.")
|
|
55471
55570
|
);
|
|
55472
55571
|
L.push("");
|
|
55473
55572
|
}
|
|
55474
|
-
for (const note of res.notes) L.push(
|
|
55573
|
+
for (const note of res.notes) L.push(chalk29.gray(` note: ${note}`));
|
|
55475
55574
|
const hasHigh = res.findings.some((f) => f.severity === "critical" || f.severity === "high");
|
|
55476
55575
|
if (hasHigh && !ownedHint(res.source)) {
|
|
55477
55576
|
L.push("");
|
|
55478
55577
|
L.push(
|
|
55479
|
-
|
|
55578
|
+
chalk29.yellow(
|
|
55480
55579
|
" \u26A0\uFE0F This looks like a live issue on a repo you may not own. Disclose it privately\n to the maintainers \u2014 do not publish it. (node9 never weaponizes findings.)"
|
|
55481
55580
|
)
|
|
55482
55581
|
);
|
|
@@ -55525,7 +55624,7 @@ function makeProgress(target, quiet) {
|
|
|
55525
55624
|
let frame = 0;
|
|
55526
55625
|
let last = `scanning ${target}\u2026`;
|
|
55527
55626
|
const timer = setInterval(() => {
|
|
55528
|
-
process.stderr.write(`\r${
|
|
55627
|
+
process.stderr.write(`\r${chalk30.cyan(SPIN[frame++ % SPIN.length])} ${last} `);
|
|
55529
55628
|
}, 80);
|
|
55530
55629
|
return {
|
|
55531
55630
|
onProgress: (p) => {
|
|
@@ -55560,13 +55659,13 @@ function registerScanRepoCommand(program2) {
|
|
|
55560
55659
|
// src/cli/commands/egress.ts
|
|
55561
55660
|
init_config();
|
|
55562
55661
|
init_dist();
|
|
55563
|
-
import
|
|
55662
|
+
import chalk31 from "chalk";
|
|
55564
55663
|
function guard(fn) {
|
|
55565
55664
|
try {
|
|
55566
55665
|
fn();
|
|
55567
55666
|
return true;
|
|
55568
55667
|
} catch (err2) {
|
|
55569
|
-
console.error(
|
|
55668
|
+
console.error(chalk31.red(`
|
|
55570
55669
|
\u2717 ${err2.message}
|
|
55571
55670
|
`));
|
|
55572
55671
|
process.exitCode = 1;
|
|
@@ -55581,19 +55680,19 @@ function addHost(list, host) {
|
|
|
55581
55680
|
}
|
|
55582
55681
|
function showStatus() {
|
|
55583
55682
|
const e = getConfig().policy.egress;
|
|
55584
|
-
const state = !e.enabled ?
|
|
55585
|
-
console.log(
|
|
55683
|
+
const state = !e.enabled ? chalk31.red("OFF \u2014 your agent can reach any host") : e.mode === "block" ? chalk31.green("LOCKED (block) \u2014 unknown hosts are denied") : chalk31.yellow("WATCHING (review) \u2014 unknown hosts prompt you");
|
|
55684
|
+
console.log(chalk31.cyan.bold("\n\u{1F310} Egress control"));
|
|
55586
55685
|
console.log(" State: " + state);
|
|
55587
55686
|
console.log(
|
|
55588
|
-
|
|
55687
|
+
chalk31.gray(
|
|
55589
55688
|
` ${DEFAULT_EGRESS_ALLOWLIST.length} common dev/LLM hosts are always allowed (github, npm, pypi, anthropic, \u2026).`
|
|
55590
55689
|
)
|
|
55591
55690
|
);
|
|
55592
55691
|
if (e.allow.length) console.log(" Your allow: " + e.allow.join(", "));
|
|
55593
55692
|
if (e.deny.length) console.log(" Your deny: " + e.deny.join(", "));
|
|
55594
55693
|
if (!e.enabled) {
|
|
55595
|
-
console.log(
|
|
55596
|
-
console.log(
|
|
55694
|
+
console.log(chalk31.gray("\n Turn it on: node9 egress watch (prompt on unknown hosts)"));
|
|
55695
|
+
console.log(chalk31.gray(" node9 egress lock (hard-block unknown hosts)"));
|
|
55597
55696
|
}
|
|
55598
55697
|
console.log("");
|
|
55599
55698
|
}
|
|
@@ -55601,36 +55700,36 @@ function registerEgressCommand(program2) {
|
|
|
55601
55700
|
const egress = program2.command("egress").description("Control where your agent can send data (egress allowlist)");
|
|
55602
55701
|
egress.command("watch").description("Prompt before the agent reaches an unknown host (review mode)").action(() => {
|
|
55603
55702
|
if (!mutate({ enabled: true, mode: "review" })) return;
|
|
55604
|
-
console.log(
|
|
55703
|
+
console.log(chalk31.green("\n\u2713 Egress is now watched (review mode)."));
|
|
55605
55704
|
console.log(
|
|
55606
|
-
|
|
55705
|
+
chalk31.gray(" Routine hosts (LLM APIs, package registries, localhost) are allowed.")
|
|
55607
55706
|
);
|
|
55608
55707
|
console.log(
|
|
55609
|
-
|
|
55708
|
+
chalk31.gray(" An unknown host will prompt you \u2014 run `node9 egress lock` to hard-block.\n")
|
|
55610
55709
|
);
|
|
55611
55710
|
});
|
|
55612
55711
|
egress.command("lock").description("Block the agent from reaching unknown hosts (block mode)").action(() => {
|
|
55613
55712
|
if (!mutate({ enabled: true, mode: "block" })) return;
|
|
55614
|
-
console.log(
|
|
55615
|
-
console.log(
|
|
55616
|
-
console.log(
|
|
55713
|
+
console.log(chalk31.green("\n\u2713 Egress is now locked (block mode)."));
|
|
55714
|
+
console.log(chalk31.gray(" Routine hosts are still allowed; unknown hosts are denied."));
|
|
55715
|
+
console.log(chalk31.gray(" Allow a specific host with `node9 egress allow <host>`.\n"));
|
|
55617
55716
|
});
|
|
55618
55717
|
egress.command("allow <host>").description("Allow an extra host (glob, e.g. *.mycorp.com)").action((host) => {
|
|
55619
55718
|
if (!addHost("allow", host)) return;
|
|
55620
|
-
console.log(
|
|
55719
|
+
console.log(chalk31.green(`
|
|
55621
55720
|
\u2713 Allowed egress to ${host}.
|
|
55622
55721
|
`));
|
|
55623
55722
|
});
|
|
55624
55723
|
egress.command("deny <host>").description("Block an extra host (deny always wins)").action((host) => {
|
|
55625
55724
|
if (!addHost("deny", host)) return;
|
|
55626
|
-
console.log(
|
|
55725
|
+
console.log(chalk31.green(`
|
|
55627
55726
|
\u2713 Denied egress to ${host}.
|
|
55628
55727
|
`));
|
|
55629
55728
|
});
|
|
55630
55729
|
egress.command("off").description("Turn egress control off").action(() => {
|
|
55631
55730
|
if (!mutate({ enabled: false })) return;
|
|
55632
55731
|
console.log(
|
|
55633
|
-
|
|
55732
|
+
chalk31.yellow("\n\u2713 Egress control is off \u2014 the agent can reach any host again.\n")
|
|
55634
55733
|
);
|
|
55635
55734
|
});
|
|
55636
55735
|
egress.action(showStatus);
|
|
@@ -55639,7 +55738,7 @@ function registerEgressCommand(program2) {
|
|
|
55639
55738
|
// src/cli/commands/jail.ts
|
|
55640
55739
|
init_jail();
|
|
55641
55740
|
init_audit();
|
|
55642
|
-
import
|
|
55741
|
+
import chalk32 from "chalk";
|
|
55643
55742
|
var BUILTIN_JAIL = [
|
|
55644
55743
|
"~/.ssh \u2014 SSH private keys",
|
|
55645
55744
|
"~/.aws \u2014 AWS credentials",
|
|
@@ -55655,20 +55754,20 @@ function registerJailCommand(program2) {
|
|
|
55655
55754
|
regenerateUserJail(paths);
|
|
55656
55755
|
appendConfigAudit({ event: "jail-add", path: p.trim(), verdict });
|
|
55657
55756
|
} catch (err2) {
|
|
55658
|
-
console.error(
|
|
55757
|
+
console.error(chalk32.red(`
|
|
55659
55758
|
\u274C ${err2.message}
|
|
55660
55759
|
`));
|
|
55661
55760
|
process.exit(1);
|
|
55662
55761
|
return;
|
|
55663
55762
|
}
|
|
55664
|
-
console.log(
|
|
55763
|
+
console.log(chalk32.green(`
|
|
55665
55764
|
\u2705 Jailed ${p} (${verdict}).`));
|
|
55666
55765
|
console.log(
|
|
55667
|
-
|
|
55766
|
+
chalk32.gray(
|
|
55668
55767
|
` AI reads of this path now ${verdict === "block" ? "BLOCK" : "require approval"}.`
|
|
55669
55768
|
)
|
|
55670
55769
|
);
|
|
55671
|
-
console.log(
|
|
55770
|
+
console.log(chalk32.gray(` Preview: ${chalk32.cyan(`node9 explain bash "cat ${p}"`)}
|
|
55672
55771
|
`));
|
|
55673
55772
|
});
|
|
55674
55773
|
jail.command("remove <path>").description("Remove a user-added jail path (built-in paths are not removable)").action((p) => {
|
|
@@ -55676,17 +55775,17 @@ function registerJailCommand(program2) {
|
|
|
55676
55775
|
try {
|
|
55677
55776
|
result = removeJailPath(p);
|
|
55678
55777
|
} catch (err2) {
|
|
55679
|
-
console.error(
|
|
55778
|
+
console.error(chalk32.red(`
|
|
55680
55779
|
\u274C ${err2.message}
|
|
55681
55780
|
`));
|
|
55682
55781
|
process.exit(1);
|
|
55683
55782
|
return;
|
|
55684
55783
|
}
|
|
55685
55784
|
if (!result.removed) {
|
|
55686
|
-
console.error(
|
|
55785
|
+
console.error(chalk32.yellow(`
|
|
55687
55786
|
\u2139\uFE0F "${p}" is not a user-added jail path.
|
|
55688
55787
|
`));
|
|
55689
|
-
console.error(
|
|
55788
|
+
console.error(chalk32.gray(` Run ${chalk32.cyan("node9 jail list")} to see your paths.
|
|
55690
55789
|
`));
|
|
55691
55790
|
process.exit(1);
|
|
55692
55791
|
return;
|
|
@@ -55695,39 +55794,39 @@ function registerJailCommand(program2) {
|
|
|
55695
55794
|
regenerateUserJail(result.paths);
|
|
55696
55795
|
appendConfigAudit({ event: "jail-remove", path: p.trim() });
|
|
55697
55796
|
} catch (err2) {
|
|
55698
|
-
console.error(
|
|
55797
|
+
console.error(chalk32.red(`
|
|
55699
55798
|
\u274C ${err2.message}
|
|
55700
55799
|
`));
|
|
55701
55800
|
process.exit(1);
|
|
55702
55801
|
return;
|
|
55703
55802
|
}
|
|
55704
|
-
console.log(
|
|
55803
|
+
console.log(chalk32.green(`
|
|
55705
55804
|
\u2705 Removed ${p} from the jail.
|
|
55706
55805
|
`));
|
|
55707
55806
|
});
|
|
55708
55807
|
jail.command("list").description("Show built-in + user-added jail paths").action(() => {
|
|
55709
|
-
console.log(
|
|
55710
|
-
console.log(
|
|
55711
|
-
for (const b of BUILTIN_JAIL) console.log(` ${
|
|
55808
|
+
console.log(chalk32.bold("\n\u{1F512} Credential Jail\n"));
|
|
55809
|
+
console.log(chalk32.gray(" Built-in (always on, not removable):"));
|
|
55810
|
+
for (const b of BUILTIN_JAIL) console.log(` ${chalk32.gray("\u2022")} ${b}`);
|
|
55712
55811
|
console.log("");
|
|
55713
55812
|
let user;
|
|
55714
55813
|
try {
|
|
55715
55814
|
user = readJailPaths();
|
|
55716
55815
|
} catch (err2) {
|
|
55717
|
-
console.error(
|
|
55816
|
+
console.error(chalk32.red(` \u2717 ${err2.message}
|
|
55718
55817
|
`));
|
|
55719
55818
|
process.exit(1);
|
|
55720
55819
|
return;
|
|
55721
55820
|
}
|
|
55722
55821
|
if (user.length === 0) {
|
|
55723
55822
|
console.log(
|
|
55724
|
-
|
|
55823
|
+
chalk32.gray(" Your paths: (none) \u2014 add one with ") + chalk32.cyan("node9 jail add <path>")
|
|
55725
55824
|
);
|
|
55726
55825
|
} else {
|
|
55727
|
-
console.log(
|
|
55826
|
+
console.log(chalk32.gray(" Your paths (removable):"));
|
|
55728
55827
|
for (const u of user) {
|
|
55729
|
-
const v = u.verdict === "block" ?
|
|
55730
|
-
console.log(` ${v} ${
|
|
55828
|
+
const v = u.verdict === "block" ? chalk32.red("block ") : chalk32.yellow("review");
|
|
55829
|
+
console.log(` ${v} ${chalk32.cyan(u.path)}`);
|
|
55731
55830
|
}
|
|
55732
55831
|
}
|
|
55733
55832
|
console.log("");
|
|
@@ -55736,7 +55835,7 @@ function registerJailCommand(program2) {
|
|
|
55736
55835
|
|
|
55737
55836
|
// src/cli/commands/sandbox.ts
|
|
55738
55837
|
init_config();
|
|
55739
|
-
import
|
|
55838
|
+
import chalk33 from "chalk";
|
|
55740
55839
|
import fs67 from "fs";
|
|
55741
55840
|
import path62 from "path";
|
|
55742
55841
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
@@ -55973,16 +56072,16 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55973
56072
|
const p = sandboxConfigPath();
|
|
55974
56073
|
if (fs67.existsSync(p)) {
|
|
55975
56074
|
console.log(
|
|
55976
|
-
|
|
56075
|
+
chalk33.yellow(` ${SANDBOX_CONFIG_FILE} already exists \u2014 leaving it untouched.`)
|
|
55977
56076
|
);
|
|
55978
56077
|
return;
|
|
55979
56078
|
}
|
|
55980
56079
|
fs67.writeFileSync(p, scaffoldSandboxYaml(agent));
|
|
55981
56080
|
console.log(
|
|
55982
|
-
|
|
56081
|
+
chalk33.green(` \u2713 wrote ${SANDBOX_CONFIG_FILE}`) + chalk33.dim(` (agent: ${agent})`)
|
|
55983
56082
|
);
|
|
55984
56083
|
console.log(
|
|
55985
|
-
|
|
56084
|
+
chalk33.dim(" Edit it (mounts / allow / expose), then: ") + chalk33.cyan("node9 sandbox run")
|
|
55986
56085
|
);
|
|
55987
56086
|
});
|
|
55988
56087
|
cmd.command("run [agent]").description("Build (if needed) + run the agent jailed. Extra args after -- go to the agent.").allowUnknownOption(true).allowExcessArguments(true).action((agentArg, _opts, command) => {
|
|
@@ -55992,7 +56091,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
55992
56091
|
const engine = detectEngine(sandbox.runtime.engine);
|
|
55993
56092
|
if (!engine.available) {
|
|
55994
56093
|
console.error(
|
|
55995
|
-
|
|
56094
|
+
chalk33.red(` ${sandbox.runtime.engine} not found.`) + chalk33.dim(` Install it first \u2014 node9 sandbox needs a container runtime.`)
|
|
55996
56095
|
);
|
|
55997
56096
|
process.exit(1);
|
|
55998
56097
|
}
|
|
@@ -56005,11 +56104,11 @@ function registerSandboxCommand(program2, version2) {
|
|
|
56005
56104
|
});
|
|
56006
56105
|
if (compiled.rejected.length) {
|
|
56007
56106
|
console.log(
|
|
56008
|
-
|
|
56107
|
+
chalk33.yellow(` \u26A0 ignoring invalid allow hosts: ${compiled.rejected.join(", ")}`)
|
|
56009
56108
|
);
|
|
56010
56109
|
}
|
|
56011
56110
|
if (compiled.denied.length) {
|
|
56012
|
-
console.log(
|
|
56111
|
+
console.log(chalk33.dim(` (denied: ${compiled.denied.join(", ")})`));
|
|
56013
56112
|
}
|
|
56014
56113
|
const allowlistPath = writeAllowlist(cwd, compiled.allow);
|
|
56015
56114
|
const dockerfile = renderDockerfile(sandbox, node9Version2);
|
|
@@ -56022,12 +56121,12 @@ function registerSandboxCommand(program2, version2) {
|
|
|
56022
56121
|
const imageExists = spawnSync5(sandbox.runtime.engine, ["image", "inspect", image], { stdio: "ignore" }).status === 0;
|
|
56023
56122
|
const needBuild = sandbox.runtime.rebuild === "always" || !imageExists || sandbox.runtime.rebuild !== "never" && lastHash !== hash;
|
|
56024
56123
|
if (needBuild) {
|
|
56025
|
-
console.log(
|
|
56124
|
+
console.log(chalk33.dim(` building ${image} \u2026`));
|
|
56026
56125
|
const b = spawnSync5(sandbox.runtime.engine, ["build", "-t", image, buildDir], {
|
|
56027
56126
|
stdio: "inherit"
|
|
56028
56127
|
});
|
|
56029
56128
|
if (b.status !== 0) {
|
|
56030
|
-
console.error(
|
|
56129
|
+
console.error(chalk33.red(" build failed."));
|
|
56031
56130
|
process.exit(b.status ?? 1);
|
|
56032
56131
|
}
|
|
56033
56132
|
fs67.writeFileSync(hashFile, hash);
|
|
@@ -56045,15 +56144,15 @@ function registerSandboxCommand(program2, version2) {
|
|
|
56045
56144
|
if (sandbox.node9.mountAgentCredentials) {
|
|
56046
56145
|
const creds = agentCredentialsMount(sandbox.agent);
|
|
56047
56146
|
if (fs67.existsSync(creds.hostPath)) {
|
|
56048
|
-
console.log(
|
|
56147
|
+
console.log(chalk33.dim(` mounting ${creds.hostPath} (agent credentials, rw)`));
|
|
56049
56148
|
} else {
|
|
56050
56149
|
console.log(
|
|
56051
|
-
|
|
56150
|
+
chalk33.yellow(` \u26A0 ${creds.hostPath} not found \u2014 `) + chalk33.dim(`the agent must auth via an env key in env.pass.`)
|
|
56052
56151
|
);
|
|
56053
56152
|
}
|
|
56054
56153
|
}
|
|
56055
56154
|
console.log(
|
|
56056
|
-
|
|
56155
|
+
chalk33.green(` \u{1F6E1}\uFE0F ${sandbox.agent} jailed \u2014 ${compiled.allow.length} hosts allowed
|
|
56057
56156
|
`)
|
|
56058
56157
|
);
|
|
56059
56158
|
const r = spawnSync5(sandbox.runtime.engine, runArgs, { stdio: "inherit" });
|
|
@@ -56062,7 +56161,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
56062
56161
|
cmd.command("tail").description("Stream the sandbox's audit log (host-side)").action(() => {
|
|
56063
56162
|
const auditPath = path62.join(sandboxDataDir(), "audit.log");
|
|
56064
56163
|
if (!fs67.existsSync(auditPath)) {
|
|
56065
|
-
console.log(
|
|
56164
|
+
console.log(chalk33.dim(" no sandbox audit yet."));
|
|
56066
56165
|
return;
|
|
56067
56166
|
}
|
|
56068
56167
|
spawnSync5("tail", ["-f", auditPath], { stdio: "inherit" });
|
|
@@ -56070,7 +56169,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
56070
56169
|
cmd.command("logs").description("Dump the sandbox's audit log").action(() => {
|
|
56071
56170
|
const auditPath = path62.join(sandboxDataDir(), "audit.log");
|
|
56072
56171
|
if (!fs67.existsSync(auditPath)) {
|
|
56073
|
-
console.log(
|
|
56172
|
+
console.log(chalk33.dim(" no sandbox audit yet."));
|
|
56074
56173
|
return;
|
|
56075
56174
|
}
|
|
56076
56175
|
process.stdout.write(fs67.readFileSync(auditPath, "utf-8"));
|
|
@@ -56088,7 +56187,7 @@ function registerSandboxCommand(program2, version2) {
|
|
|
56088
56187
|
});
|
|
56089
56188
|
}
|
|
56090
56189
|
fs67.rmSync(path62.join(cwd, ".node9", "sandbox"), { recursive: true, force: true });
|
|
56091
|
-
console.log(
|
|
56190
|
+
console.log(chalk33.green(" \u2713 sandbox image + build + data removed."));
|
|
56092
56191
|
});
|
|
56093
56192
|
}
|
|
56094
56193
|
|
|
@@ -56098,7 +56197,7 @@ init_scan_summary();
|
|
|
56098
56197
|
init_litellm();
|
|
56099
56198
|
init_cost_gemini();
|
|
56100
56199
|
init_cost_codex();
|
|
56101
|
-
import
|
|
56200
|
+
import chalk34 from "chalk";
|
|
56102
56201
|
import fs68 from "fs";
|
|
56103
56202
|
import path63 from "path";
|
|
56104
56203
|
import os56 from "os";
|
|
@@ -56590,11 +56689,11 @@ function toolInputSummary(tool, input) {
|
|
|
56590
56689
|
}
|
|
56591
56690
|
function toolColor(tool) {
|
|
56592
56691
|
const t = tool.toLowerCase();
|
|
56593
|
-
if (t === "bash" || t === "execute_bash") return
|
|
56594
|
-
if (t === "write") return
|
|
56595
|
-
if (t === "edit" || t === "notebookedit") return
|
|
56596
|
-
if (t === "read") return
|
|
56597
|
-
return
|
|
56692
|
+
if (t === "bash" || t === "execute_bash") return chalk34.red;
|
|
56693
|
+
if (t === "write") return chalk34.green;
|
|
56694
|
+
if (t === "edit" || t === "notebookedit") return chalk34.yellow;
|
|
56695
|
+
if (t === "read") return chalk34.cyan;
|
|
56696
|
+
return chalk34.gray;
|
|
56598
56697
|
}
|
|
56599
56698
|
function barStr2(value, max, width) {
|
|
56600
56699
|
if (max === 0 || width <= 0) return "\u2591".repeat(width);
|
|
@@ -56604,7 +56703,7 @@ function barStr2(value, max, width) {
|
|
|
56604
56703
|
function colorBar2(value, max, width) {
|
|
56605
56704
|
const s = barStr2(value, max, width);
|
|
56606
56705
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
56607
|
-
return
|
|
56706
|
+
return chalk34.cyan(s.slice(0, filled)) + chalk34.dim(s.slice(filled));
|
|
56608
56707
|
}
|
|
56609
56708
|
function renderSummary(summaries) {
|
|
56610
56709
|
const totalTools = summaries.reduce((n, s) => n + s.toolCalls.length, 0);
|
|
@@ -56634,45 +56733,45 @@ function renderSummary(summaries) {
|
|
|
56634
56733
|
}
|
|
56635
56734
|
const topProjects = [...projCosts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
56636
56735
|
const W = 20;
|
|
56637
|
-
console.log(
|
|
56736
|
+
console.log(chalk34.dim(" " + "\u2500".repeat(70)));
|
|
56638
56737
|
console.log(
|
|
56639
|
-
" " +
|
|
56738
|
+
" " + chalk34.bold.white(String(summaries.length).padEnd(4)) + chalk34.dim("sessions ") + chalk34.bold.yellow(fmtCost3(totalCost).padEnd(10)) + chalk34.dim("total ") + chalk34.bold.white(String(totalTools).padEnd(6)) + chalk34.dim("tool calls ") + chalk34.bold.white(String(totalFiles)) + chalk34.dim(" files modified") + (totalBlocked > 0 ? chalk34.dim(" ") + chalk34.red.bold(String(totalBlocked)) + chalk34.dim(" blocked by node9") : "")
|
|
56640
56739
|
);
|
|
56641
56740
|
console.log(
|
|
56642
|
-
" " +
|
|
56741
|
+
" " + chalk34.dim("avg ") + chalk34.white(fmtCost3(avgCost).padEnd(10)) + chalk34.dim("/session ") + chalk34.green(String(snapshots)) + chalk34.dim(` of ${summaries.length} sessions had snapshots`)
|
|
56643
56742
|
);
|
|
56644
56743
|
console.log("");
|
|
56645
|
-
console.log(" " +
|
|
56744
|
+
console.log(" " + chalk34.dim("Tool breakdown:"));
|
|
56646
56745
|
const maxGroup = Math.max(...Object.values(groups));
|
|
56647
56746
|
for (const [label2, count] of Object.entries(groups)) {
|
|
56648
56747
|
if (count === 0) continue;
|
|
56649
56748
|
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
56650
56749
|
console.log(
|
|
56651
|
-
" " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " +
|
|
56750
|
+
" " + label2.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk34.white(String(count).padStart(4)) + chalk34.dim(` (${String(pct)}%)`)
|
|
56652
56751
|
);
|
|
56653
56752
|
}
|
|
56654
56753
|
console.log("");
|
|
56655
56754
|
if (topProjects.length > 1) {
|
|
56656
|
-
console.log(" " +
|
|
56755
|
+
console.log(" " + chalk34.dim("Cost by project:"));
|
|
56657
56756
|
const maxProjCost = topProjects[0][1];
|
|
56658
56757
|
for (const [proj, cost] of topProjects) {
|
|
56659
56758
|
console.log(
|
|
56660
|
-
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " +
|
|
56759
|
+
" " + proj.slice(0, 28).padEnd(28) + " " + colorBar2(cost, maxProjCost, W) + " " + chalk34.yellow(fmtCost3(cost))
|
|
56661
56760
|
);
|
|
56662
56761
|
}
|
|
56663
56762
|
console.log("");
|
|
56664
56763
|
}
|
|
56665
|
-
console.log(
|
|
56764
|
+
console.log(chalk34.dim(" " + "\u2500".repeat(70)));
|
|
56666
56765
|
console.log("");
|
|
56667
56766
|
}
|
|
56668
56767
|
function renderList(summaries, totalCost) {
|
|
56669
56768
|
if (summaries.length === 0) {
|
|
56670
|
-
console.log(
|
|
56769
|
+
console.log(chalk34.yellow(" No sessions found in the requested range.\n"));
|
|
56671
56770
|
return;
|
|
56672
56771
|
}
|
|
56673
|
-
const totalLabel = totalCost > 0 ?
|
|
56772
|
+
const totalLabel = totalCost > 0 ? chalk34.dim(" ~" + fmtCost3(totalCost) + " total") : "";
|
|
56674
56773
|
console.log(
|
|
56675
|
-
" " +
|
|
56774
|
+
" " + chalk34.white(String(summaries.length)) + chalk34.dim(` session${summaries.length !== 1 ? "s" : ""}`) + totalLabel
|
|
56676
56775
|
);
|
|
56677
56776
|
console.log("");
|
|
56678
56777
|
let lastGroup = "";
|
|
@@ -56680,51 +56779,51 @@ function renderList(summaries, totalCost) {
|
|
|
56680
56779
|
const activeDate = fmtDate2(s.lastActiveTime);
|
|
56681
56780
|
const group = activeDate + " " + s.projectLabel;
|
|
56682
56781
|
if (group !== lastGroup) {
|
|
56683
|
-
console.log(
|
|
56782
|
+
console.log(chalk34.dim(" \u2500\u2500\u2500 ") + chalk34.bold(activeDate) + chalk34.dim(" " + s.projectLabel));
|
|
56684
56783
|
lastGroup = group;
|
|
56685
56784
|
}
|
|
56686
56785
|
const startDate = fmtDate2(s.startTime);
|
|
56687
|
-
const dateRange = startDate !== activeDate ?
|
|
56688
|
-
const timeStr =
|
|
56689
|
-
const prompt =
|
|
56690
|
-
const tools = s.toolCalls.length > 0 ?
|
|
56691
|
-
const cost = s.costUSD > 0 ?
|
|
56692
|
-
const blocked = s.blockedCalls.length > 0 ?
|
|
56693
|
-
const snap = s.hasSnapshot ?
|
|
56694
|
-
const agentBadge =
|
|
56786
|
+
const dateRange = startDate !== activeDate ? chalk34.dim(" (" + startDate + " \u2192 " + activeDate + ")") : "";
|
|
56787
|
+
const timeStr = chalk34.dim(fmtTime(s.startTime));
|
|
56788
|
+
const prompt = chalk34.white(truncate(s.firstPrompt.replace(/\n/g, " "), 50).padEnd(50));
|
|
56789
|
+
const tools = s.toolCalls.length > 0 ? chalk34.dim(String(s.toolCalls.length).padStart(3) + " tools") : chalk34.dim(" 0 tools");
|
|
56790
|
+
const cost = s.costUSD > 0 ? chalk34.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
56791
|
+
const blocked = s.blockedCalls.length > 0 ? chalk34.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
56792
|
+
const snap = s.hasSnapshot ? chalk34.green(" \u{1F4F8}") : "";
|
|
56793
|
+
const agentBadge = chalk34[agentColorName(s.agent ?? "claude")](
|
|
56695
56794
|
" " + agentBadgeText(s.agent ?? "claude", 0)
|
|
56696
56795
|
);
|
|
56697
|
-
const sid =
|
|
56796
|
+
const sid = chalk34.dim(" " + s.sessionId.slice(0, 8));
|
|
56698
56797
|
console.log(
|
|
56699
56798
|
` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}${dateRange}`
|
|
56700
56799
|
);
|
|
56701
56800
|
}
|
|
56702
56801
|
console.log("");
|
|
56703
56802
|
console.log(
|
|
56704
|
-
|
|
56803
|
+
chalk34.dim(" Run") + " " + chalk34.cyan("node9 sessions --detail <session-id>") + chalk34.dim(" for full tool trace.")
|
|
56705
56804
|
);
|
|
56706
56805
|
console.log("");
|
|
56707
56806
|
}
|
|
56708
56807
|
function renderDetail(s) {
|
|
56709
56808
|
console.log("");
|
|
56710
|
-
console.log(
|
|
56809
|
+
console.log(chalk34.bold(" Session ") + chalk34.dim(s.sessionId));
|
|
56711
56810
|
console.log(
|
|
56712
|
-
|
|
56811
|
+
chalk34.bold(" Prompt ") + chalk34.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
56713
56812
|
);
|
|
56714
|
-
console.log(
|
|
56813
|
+
console.log(chalk34.bold(" Project ") + chalk34.white(s.projectLabel));
|
|
56715
56814
|
if (s.agent) {
|
|
56716
|
-
const agentLabel2 =
|
|
56717
|
-
console.log(
|
|
56815
|
+
const agentLabel2 = chalk34[agentColorName(s.agent)](agentDisplayName(s.agent));
|
|
56816
|
+
console.log(chalk34.bold(" Agent ") + agentLabel2);
|
|
56718
56817
|
}
|
|
56719
|
-
console.log(
|
|
56818
|
+
console.log(chalk34.bold(" When ") + chalk34.white(fmtDateTime(s.startTime)));
|
|
56720
56819
|
if (s.costUSD > 0)
|
|
56721
|
-
console.log(
|
|
56820
|
+
console.log(chalk34.bold(" Cost ") + chalk34.yellow("~" + fmtCost3(s.costUSD)));
|
|
56722
56821
|
console.log(
|
|
56723
|
-
|
|
56822
|
+
chalk34.bold(" Snapshot ") + (s.hasSnapshot ? chalk34.green("\u2713 taken") : chalk34.dim("none"))
|
|
56724
56823
|
);
|
|
56725
56824
|
console.log("");
|
|
56726
56825
|
if (s.toolCalls.length === 0 && s.blockedCalls.length === 0) {
|
|
56727
|
-
console.log(
|
|
56826
|
+
console.log(chalk34.dim(" No tool calls recorded.\n"));
|
|
56728
56827
|
return;
|
|
56729
56828
|
}
|
|
56730
56829
|
const timeline = [
|
|
@@ -56737,32 +56836,32 @@ function renderDetail(s) {
|
|
|
56737
56836
|
});
|
|
56738
56837
|
const headerParts = [`Tool calls (${s.toolCalls.length})`];
|
|
56739
56838
|
if (s.blockedCalls.length > 0)
|
|
56740
|
-
headerParts.push(
|
|
56741
|
-
console.log(
|
|
56839
|
+
headerParts.push(chalk34.red(`${s.blockedCalls.length} blocked by node9`));
|
|
56840
|
+
console.log(chalk34.bold(" " + headerParts.join(" \xB7 ")));
|
|
56742
56841
|
console.log("");
|
|
56743
56842
|
for (const entry of timeline) {
|
|
56744
56843
|
if (entry.kind === "tool") {
|
|
56745
56844
|
const tc = entry.tc;
|
|
56746
56845
|
const colorFn = toolColor(tc.tool);
|
|
56747
56846
|
const toolPad = colorFn(tc.tool.padEnd(16));
|
|
56748
|
-
const detail =
|
|
56749
|
-
const ts = tc.timestamp ?
|
|
56847
|
+
const detail = chalk34.gray(truncate(toolInputSummary(tc.tool, tc.input), 70));
|
|
56848
|
+
const ts = tc.timestamp ? chalk34.dim(fmtTime(tc.timestamp) + " ") : " ";
|
|
56750
56849
|
console.log(` ${ts}${toolPad} ${detail}`);
|
|
56751
56850
|
} else {
|
|
56752
56851
|
const bc = entry.bc;
|
|
56753
|
-
const ts = bc.timestamp ?
|
|
56754
|
-
const label2 =
|
|
56755
|
-
const toolName =
|
|
56756
|
-
const argsSummary = bc.args ?
|
|
56757
|
-
const reason = bc.checkedBy ?
|
|
56852
|
+
const ts = bc.timestamp ? chalk34.dim(fmtTime(bc.timestamp) + " ") : " ";
|
|
56853
|
+
const label2 = chalk34.red("\u{1F6D1} BLOCKED".padEnd(16));
|
|
56854
|
+
const toolName = chalk34.red(bc.tool.padEnd(10));
|
|
56855
|
+
const argsSummary = bc.args ? chalk34.gray(truncate(toolInputSummary(bc.tool, bc.args), 40)) : chalk34.dim("[args not logged]");
|
|
56856
|
+
const reason = bc.checkedBy ? chalk34.dim(" \u2190 " + bc.checkedBy) : "";
|
|
56758
56857
|
console.log(` ${ts}${label2} ${toolName} ${argsSummary}${reason}`);
|
|
56759
56858
|
}
|
|
56760
56859
|
}
|
|
56761
56860
|
console.log("");
|
|
56762
56861
|
if (s.modifiedFiles.length > 0) {
|
|
56763
|
-
console.log(
|
|
56862
|
+
console.log(chalk34.bold(` Files modified (${s.modifiedFiles.length}):`));
|
|
56764
56863
|
for (const f of s.modifiedFiles) {
|
|
56765
|
-
console.log(" " +
|
|
56864
|
+
console.log(" " + chalk34.yellow(f));
|
|
56766
56865
|
}
|
|
56767
56866
|
console.log("");
|
|
56768
56867
|
}
|
|
@@ -56770,13 +56869,13 @@ function renderDetail(s) {
|
|
|
56770
56869
|
function registerSessionsCommand(program2) {
|
|
56771
56870
|
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) => {
|
|
56772
56871
|
console.log("");
|
|
56773
|
-
console.log(
|
|
56872
|
+
console.log(chalk34.cyan.bold("\u{1F4CB} node9 sessions") + chalk34.dim(" \u2014 what your AI agent did"));
|
|
56774
56873
|
console.log("");
|
|
56775
56874
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
56776
56875
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
56777
|
-
console.log(
|
|
56876
|
+
console.log(chalk34.dim(" " + rangeLabel));
|
|
56778
56877
|
console.log("");
|
|
56779
|
-
process.stdout.write(
|
|
56878
|
+
process.stdout.write(chalk34.dim(" Loading\u2026"));
|
|
56780
56879
|
const summaries = buildSessions(days);
|
|
56781
56880
|
if (process.stdout.isTTY) {
|
|
56782
56881
|
process.stdout.clearLine(0);
|
|
@@ -56789,8 +56888,8 @@ function registerSessionsCommand(program2) {
|
|
|
56789
56888
|
(s) => s.sessionId === options.detail || s.sessionId.startsWith(options.detail)
|
|
56790
56889
|
);
|
|
56791
56890
|
if (!target) {
|
|
56792
|
-
console.log(
|
|
56793
|
-
console.log(
|
|
56891
|
+
console.log(chalk34.red(` Session not found: ${options.detail}`));
|
|
56892
|
+
console.log(chalk34.dim(" Run `node9 sessions` to list recent sessions.\n"));
|
|
56794
56893
|
return;
|
|
56795
56894
|
}
|
|
56796
56895
|
renderDetail(target);
|
|
@@ -56804,7 +56903,7 @@ function registerSessionsCommand(program2) {
|
|
|
56804
56903
|
|
|
56805
56904
|
// src/cli/commands/session-taint.ts
|
|
56806
56905
|
init_daemon();
|
|
56807
|
-
import
|
|
56906
|
+
import chalk35 from "chalk";
|
|
56808
56907
|
function resolveSessionId(records, query) {
|
|
56809
56908
|
const exact = records.find((r) => r.sessionId === query);
|
|
56810
56909
|
if (exact) return { record: exact };
|
|
@@ -56830,22 +56929,22 @@ function registerSessionTaintCommand(program2) {
|
|
|
56830
56929
|
const records = await listSessionTaints();
|
|
56831
56930
|
console.log("");
|
|
56832
56931
|
if (records.length === 0) {
|
|
56833
|
-
console.log(
|
|
56834
|
-
console.log(
|
|
56932
|
+
console.log(chalk35.dim(" No tainted sessions."));
|
|
56933
|
+
console.log(chalk35.dim(" (Taint lives in the daemon \u2014 a stopped daemon has none.)") + "\n");
|
|
56835
56934
|
return;
|
|
56836
56935
|
}
|
|
56837
56936
|
console.log(
|
|
56838
|
-
" " +
|
|
56937
|
+
" " + chalk35.bold(String(records.length)) + chalk35.dim(` tainted session${records.length !== 1 ? "s" : ""}`)
|
|
56839
56938
|
);
|
|
56840
56939
|
console.log("");
|
|
56841
56940
|
for (const r of records) {
|
|
56842
56941
|
console.log(
|
|
56843
|
-
" " +
|
|
56942
|
+
" " + chalk35.yellow(r.sessionId.slice(0, 8).padEnd(10)) + chalk35.red(r.source) + sourceGap(r.source) + chalk35.dim("clears in " + fmtRemaining(r.expiresAt))
|
|
56844
56943
|
);
|
|
56845
56944
|
}
|
|
56846
56945
|
console.log("");
|
|
56847
56946
|
console.log(
|
|
56848
|
-
|
|
56947
|
+
chalk35.dim(" Run ") + chalk35.cyan("node9 session-taint clear <id>") + chalk35.dim(" to release one, or ") + chalk35.cyan("--all") + chalk35.dim(" for every session.") + "\n"
|
|
56849
56948
|
);
|
|
56850
56949
|
});
|
|
56851
56950
|
cmd.command("clear").description("Clear a session's taint so its next network/write action isn't held for review").argument("[sessionId]", "Session id to clear (the 8-char prefix from `list` is accepted)").option("--all", "Clear every session taint").action(async (sessionId, opts) => {
|
|
@@ -56853,32 +56952,32 @@ function registerSessionTaintCommand(program2) {
|
|
|
56853
56952
|
if (opts.all) {
|
|
56854
56953
|
const res2 = await clearSessionTaint({ all: true });
|
|
56855
56954
|
if (res2.daemonUnavailable) {
|
|
56856
|
-
console.log(
|
|
56955
|
+
console.log(chalk35.dim(" node9 daemon not running \u2014 no active taints to clear.") + "\n");
|
|
56857
56956
|
return;
|
|
56858
56957
|
}
|
|
56859
56958
|
console.log(
|
|
56860
|
-
|
|
56959
|
+
chalk35.green(" \u2713 ") + `Cleared ${chalk35.bold(String(res2.cleared))} session taint${res2.cleared !== 1 ? "s" : ""}.
|
|
56861
56960
|
`
|
|
56862
56961
|
);
|
|
56863
56962
|
return;
|
|
56864
56963
|
}
|
|
56865
56964
|
if (!sessionId) {
|
|
56866
|
-
console.log(
|
|
56867
|
-
console.log(
|
|
56965
|
+
console.log(chalk35.red(" Provide a session id or --all."));
|
|
56966
|
+
console.log(chalk35.dim(" Run `node9 session-taint list` to see tainted sessions.") + "\n");
|
|
56868
56967
|
return;
|
|
56869
56968
|
}
|
|
56870
56969
|
const records = await listSessionTaints();
|
|
56871
56970
|
if (records.length === 0) {
|
|
56872
|
-
console.log(
|
|
56971
|
+
console.log(chalk35.dim(" No tainted sessions \u2014 nothing to clear.") + "\n");
|
|
56873
56972
|
return;
|
|
56874
56973
|
}
|
|
56875
56974
|
const resolved = resolveSessionId(records, sessionId);
|
|
56876
56975
|
if ("error" in resolved) {
|
|
56877
56976
|
if (resolved.error === "not-found") {
|
|
56878
|
-
console.log(
|
|
56977
|
+
console.log(chalk35.red(` No tainted session matches "${sessionId}".`));
|
|
56879
56978
|
} else {
|
|
56880
|
-
console.log(
|
|
56881
|
-
for (const m of resolved.matches) console.log(
|
|
56979
|
+
console.log(chalk35.red(` "${sessionId}" is ambiguous \u2014 matches:`));
|
|
56980
|
+
for (const m of resolved.matches) console.log(chalk35.dim(" " + m));
|
|
56882
56981
|
}
|
|
56883
56982
|
console.log("");
|
|
56884
56983
|
return;
|
|
@@ -56886,18 +56985,18 @@ function registerSessionTaintCommand(program2) {
|
|
|
56886
56985
|
const res = await clearSessionTaint({ sessionId: resolved.record.sessionId });
|
|
56887
56986
|
if (res.cleared > 0) {
|
|
56888
56987
|
console.log(
|
|
56889
|
-
|
|
56988
|
+
chalk35.green(" \u2713 ") + `Cleared taint for ${chalk35.yellow(resolved.record.sessionId.slice(0, 8))} ` + chalk35.dim(`(was flagged by ${resolved.record.source}).`) + "\n"
|
|
56890
56989
|
);
|
|
56891
56990
|
} else {
|
|
56892
56991
|
console.log(
|
|
56893
|
-
|
|
56992
|
+
chalk35.dim(` Session ${resolved.record.sessionId.slice(0, 8)} was already clear.`) + "\n"
|
|
56894
56993
|
);
|
|
56895
56994
|
}
|
|
56896
56995
|
});
|
|
56897
56996
|
}
|
|
56898
56997
|
|
|
56899
56998
|
// src/cli/commands/skill-pin.ts
|
|
56900
|
-
import
|
|
56999
|
+
import chalk36 from "chalk";
|
|
56901
57000
|
import fs69 from "fs";
|
|
56902
57001
|
import os57 from "os";
|
|
56903
57002
|
import path64 from "path";
|
|
@@ -56917,29 +57016,29 @@ function registerSkillPinCommand(program2) {
|
|
|
56917
57016
|
const result = readSkillPinsSafe();
|
|
56918
57017
|
if (!result.ok) {
|
|
56919
57018
|
if (result.reason === "missing") {
|
|
56920
|
-
console.log(
|
|
57019
|
+
console.log(chalk36.gray("\nNo skill roots are pinned yet."));
|
|
56921
57020
|
console.log(
|
|
56922
|
-
|
|
57021
|
+
chalk36.gray("Pins are created automatically on the first tool call of each session.\n")
|
|
56923
57022
|
);
|
|
56924
57023
|
return;
|
|
56925
57024
|
}
|
|
56926
|
-
console.error(
|
|
57025
|
+
console.error(chalk36.red(`
|
|
56927
57026
|
\u274C Pin file is corrupt: ${result.detail}`));
|
|
56928
|
-
console.error(
|
|
57027
|
+
console.error(chalk36.yellow(" Run: node9 skill pin reset\n"));
|
|
56929
57028
|
process.exit(1);
|
|
56930
57029
|
}
|
|
56931
57030
|
const entries = Object.entries(result.pins.roots);
|
|
56932
57031
|
if (entries.length === 0) {
|
|
56933
|
-
console.log(
|
|
57032
|
+
console.log(chalk36.gray("\nNo skill roots are pinned yet.\n"));
|
|
56934
57033
|
return;
|
|
56935
57034
|
}
|
|
56936
|
-
console.log(
|
|
57035
|
+
console.log(chalk36.bold("\n\u{1F512} Pinned Skill Roots\n"));
|
|
56937
57036
|
for (const [key, entry] of entries) {
|
|
56938
|
-
const missing = entry.exists ? "" :
|
|
56939
|
-
console.log(` ${
|
|
57037
|
+
const missing = entry.exists ? "" : chalk36.yellow(" (not present at pin time)");
|
|
57038
|
+
console.log(` ${chalk36.cyan(key)} ${chalk36.gray(entry.rootPath)}${missing}`);
|
|
56940
57039
|
console.log(` Files (${entry.fileCount})`);
|
|
56941
|
-
console.log(` Hash: ${
|
|
56942
|
-
console.log(` Pinned: ${
|
|
57040
|
+
console.log(` Hash: ${chalk36.gray(entry.contentHash.slice(0, 16))}...`);
|
|
57041
|
+
console.log(` Pinned: ${chalk36.gray(entry.pinnedAt)}
|
|
56943
57042
|
`);
|
|
56944
57043
|
}
|
|
56945
57044
|
});
|
|
@@ -56948,39 +57047,39 @@ function registerSkillPinCommand(program2) {
|
|
|
56948
57047
|
try {
|
|
56949
57048
|
pins = readSkillPins();
|
|
56950
57049
|
} catch {
|
|
56951
|
-
console.error(
|
|
56952
|
-
console.error(
|
|
57050
|
+
console.error(chalk36.red("\n\u274C Pin file is corrupt."));
|
|
57051
|
+
console.error(chalk36.yellow(" Run: node9 skill pin reset\n"));
|
|
56953
57052
|
process.exit(1);
|
|
56954
57053
|
}
|
|
56955
57054
|
if (!pins.roots[rootKey]) {
|
|
56956
|
-
console.error(
|
|
57055
|
+
console.error(chalk36.red(`
|
|
56957
57056
|
\u274C No pin found for root key "${rootKey}"
|
|
56958
57057
|
`));
|
|
56959
|
-
console.error(`Run ${
|
|
57058
|
+
console.error(`Run ${chalk36.cyan("node9 skill pin list")} to see pinned roots.
|
|
56960
57059
|
`);
|
|
56961
57060
|
process.exit(1);
|
|
56962
57061
|
}
|
|
56963
57062
|
const rootPath = pins.roots[rootKey].rootPath;
|
|
56964
57063
|
removePin2(rootKey);
|
|
56965
57064
|
wipeSkillSessions();
|
|
56966
|
-
console.log(
|
|
56967
|
-
\u{1F513} Pin removed for ${
|
|
56968
|
-
console.log(
|
|
56969
|
-
console.log(
|
|
57065
|
+
console.log(chalk36.green(`
|
|
57066
|
+
\u{1F513} Pin removed for ${chalk36.cyan(rootKey)}`));
|
|
57067
|
+
console.log(chalk36.gray(` ${rootPath}`));
|
|
57068
|
+
console.log(chalk36.gray(" Next session will re-pin with current state.\n"));
|
|
56970
57069
|
});
|
|
56971
57070
|
pinSubCmd.command("reset").description("Clear all skill pins and wipe session verification flags").action(() => {
|
|
56972
57071
|
const result = readSkillPinsSafe();
|
|
56973
57072
|
if (!result.ok && result.reason === "missing") {
|
|
56974
57073
|
wipeSkillSessions();
|
|
56975
|
-
console.log(
|
|
57074
|
+
console.log(chalk36.gray("\nNo pins to clear.\n"));
|
|
56976
57075
|
return;
|
|
56977
57076
|
}
|
|
56978
57077
|
const count = result.ok ? Object.keys(result.pins.roots).length : "?";
|
|
56979
57078
|
clearAllPins2();
|
|
56980
57079
|
wipeSkillSessions();
|
|
56981
|
-
console.log(
|
|
57080
|
+
console.log(chalk36.green(`
|
|
56982
57081
|
\u{1F513} Cleared ${count} skill pin(s).`));
|
|
56983
|
-
console.log(
|
|
57082
|
+
console.log(chalk36.gray(" Next session will re-pin with current state.\n"));
|
|
56984
57083
|
});
|
|
56985
57084
|
}
|
|
56986
57085
|
|
|
@@ -56988,7 +57087,7 @@ function registerSkillPinCommand(program2) {
|
|
|
56988
57087
|
import fs70 from "fs";
|
|
56989
57088
|
import os58 from "os";
|
|
56990
57089
|
import path65 from "path";
|
|
56991
|
-
import
|
|
57090
|
+
import chalk37 from "chalk";
|
|
56992
57091
|
var DECISIONS_FILE2 = path65.join(os58.homedir(), ".node9", "decisions.json");
|
|
56993
57092
|
function readDecisions() {
|
|
56994
57093
|
try {
|
|
@@ -57017,55 +57116,55 @@ function registerDecisionsCommand(program2) {
|
|
|
57017
57116
|
const decisions = readDecisions();
|
|
57018
57117
|
const entries = Object.entries(decisions);
|
|
57019
57118
|
if (entries.length === 0) {
|
|
57020
|
-
console.log(
|
|
57119
|
+
console.log(chalk37.gray(" No persistent decisions stored."));
|
|
57021
57120
|
console.log(
|
|
57022
|
-
|
|
57023
|
-
`) +
|
|
57121
|
+
chalk37.gray(` File: ${DECISIONS_FILE2}
|
|
57122
|
+
`) + chalk37.gray(' Decisions are written when you click "Always Allow" or')
|
|
57024
57123
|
);
|
|
57025
|
-
console.log(
|
|
57124
|
+
console.log(chalk37.gray(' "Always Deny" in node9 tail or the native popup.'));
|
|
57026
57125
|
return;
|
|
57027
57126
|
}
|
|
57028
|
-
console.log(
|
|
57127
|
+
console.log(chalk37.bold(`
|
|
57029
57128
|
Persistent decisions (${entries.length})
|
|
57030
57129
|
`));
|
|
57031
57130
|
const w = Math.max(...entries.map(([k]) => k.length));
|
|
57032
57131
|
for (const [tool, verdict] of entries.sort()) {
|
|
57033
|
-
const colored = verdict === "allow" ?
|
|
57132
|
+
const colored = verdict === "allow" ? chalk37.green(verdict) : chalk37.red(verdict);
|
|
57034
57133
|
console.log(` ${tool.padEnd(w)} ${colored}`);
|
|
57035
57134
|
}
|
|
57036
57135
|
console.log(
|
|
57037
|
-
|
|
57136
|
+
chalk37.gray(`
|
|
57038
57137
|
Stored in ${DECISIONS_FILE2}
|
|
57039
|
-
`) +
|
|
57138
|
+
`) + chalk37.gray(" Run `node9 decisions clear <tool>` to remove an entry.")
|
|
57040
57139
|
);
|
|
57041
57140
|
});
|
|
57042
57141
|
cmd.command("clear <toolName>").description("Remove a persistent decision for one tool").action((toolName) => {
|
|
57043
57142
|
const decisions = readDecisions();
|
|
57044
57143
|
if (!(toolName in decisions)) {
|
|
57045
|
-
console.log(
|
|
57144
|
+
console.log(chalk37.yellow(` No persistent decision for "${toolName}". Nothing to clear.`));
|
|
57046
57145
|
process.exitCode = 1;
|
|
57047
57146
|
return;
|
|
57048
57147
|
}
|
|
57049
57148
|
delete decisions[toolName];
|
|
57050
57149
|
writeDecisions(decisions);
|
|
57051
|
-
console.log(
|
|
57150
|
+
console.log(chalk37.green(` \u2713 Cleared persistent decision for "${toolName}".`));
|
|
57052
57151
|
});
|
|
57053
57152
|
cmd.command("clear-all").description("Remove every persistent decision (irreversible)").action(() => {
|
|
57054
57153
|
const decisions = readDecisions();
|
|
57055
57154
|
const count = Object.keys(decisions).length;
|
|
57056
57155
|
if (count === 0) {
|
|
57057
|
-
console.log(
|
|
57156
|
+
console.log(chalk37.gray(" Nothing to clear \u2014 no persistent decisions stored."));
|
|
57058
57157
|
return;
|
|
57059
57158
|
}
|
|
57060
57159
|
writeDecisions({});
|
|
57061
57160
|
console.log(
|
|
57062
|
-
|
|
57161
|
+
chalk37.green(` \u2713 Cleared ${count} persistent decision${count === 1 ? "" : "s"}.`)
|
|
57063
57162
|
);
|
|
57064
57163
|
});
|
|
57065
57164
|
}
|
|
57066
57165
|
|
|
57067
57166
|
// src/cli/commands/dlp.ts
|
|
57068
|
-
import
|
|
57167
|
+
import chalk38 from "chalk";
|
|
57069
57168
|
import fs71 from "fs";
|
|
57070
57169
|
import path66 from "path";
|
|
57071
57170
|
import os59 from "os";
|
|
@@ -57120,14 +57219,14 @@ function registerDlpCommand(program2) {
|
|
|
57120
57219
|
cmd.command("resolve").description("Mark all current DLP findings as resolved").action(() => {
|
|
57121
57220
|
const findings = loadDlpFindings();
|
|
57122
57221
|
if (findings.length === 0) {
|
|
57123
|
-
console.log(
|
|
57222
|
+
console.log(chalk38.green("\n \u2705 No response-DLP findings to resolve.\n"));
|
|
57124
57223
|
return;
|
|
57125
57224
|
}
|
|
57126
57225
|
const resolved = loadResolved();
|
|
57127
57226
|
for (const e of findings) resolved.add(entryKey2(e));
|
|
57128
57227
|
saveResolved(resolved);
|
|
57129
57228
|
console.log(
|
|
57130
|
-
|
|
57229
|
+
chalk38.green(
|
|
57131
57230
|
`
|
|
57132
57231
|
\u2705 ${findings.length} finding${findings.length !== 1 ? "s" : ""} marked as resolved.
|
|
57133
57232
|
`
|
|
@@ -57141,47 +57240,47 @@ function registerDlpCommand(program2) {
|
|
|
57141
57240
|
const resolvedCount = findings.length - open.length;
|
|
57142
57241
|
console.log("");
|
|
57143
57242
|
console.log(
|
|
57144
|
-
|
|
57243
|
+
chalk38.bold.cyan("\u{1F510} node9 dlp") + chalk38.dim(" \u2014 secrets found in Claude response text")
|
|
57145
57244
|
);
|
|
57146
57245
|
console.log("");
|
|
57147
57246
|
if (open.length === 0) {
|
|
57148
57247
|
if (resolvedCount > 0) {
|
|
57149
|
-
console.log(
|
|
57248
|
+
console.log(chalk38.green(` \u2705 No open findings \xB7 ${resolvedCount} previously resolved`));
|
|
57150
57249
|
} else {
|
|
57151
57250
|
console.log(
|
|
57152
|
-
|
|
57251
|
+
chalk38.green(" \u2705 No findings \u2014 Claude has not leaked secrets in response text")
|
|
57153
57252
|
);
|
|
57154
57253
|
}
|
|
57155
57254
|
console.log("");
|
|
57156
57255
|
return;
|
|
57157
57256
|
}
|
|
57158
57257
|
console.log(
|
|
57159
|
-
|
|
57258
|
+
chalk38.bgRed.white.bold(` \u26A0\uFE0F ${open.length} open finding${open.length !== 1 ? "s" : ""} `) + chalk38.dim(resolvedCount > 0 ? ` (${resolvedCount} resolved)` : "")
|
|
57160
57259
|
);
|
|
57161
57260
|
console.log("");
|
|
57162
57261
|
console.log(
|
|
57163
|
-
|
|
57262
|
+
chalk38.dim(" These secrets were included in Claude's response text \u2014 NOT blocked.")
|
|
57164
57263
|
);
|
|
57165
|
-
console.log(
|
|
57264
|
+
console.log(chalk38.dim(" Rotate each affected key immediately.\n"));
|
|
57166
57265
|
for (const e of open) {
|
|
57167
57266
|
console.log(
|
|
57168
|
-
" " +
|
|
57267
|
+
" " + chalk38.red("\u25CF") + " " + chalk38.white(e.dlpPattern ?? "Secret") + chalk38.dim(" " + fmtDate3(e.ts))
|
|
57169
57268
|
);
|
|
57170
57269
|
if (e.dlpSample) {
|
|
57171
|
-
console.log(" " +
|
|
57270
|
+
console.log(" " + chalk38.dim("Sample: ") + chalk38.yellow(stripAnsi(e.dlpSample)));
|
|
57172
57271
|
}
|
|
57173
57272
|
if (e.project) {
|
|
57174
|
-
console.log(" " +
|
|
57273
|
+
console.log(" " + chalk38.dim("Project: ") + chalk38.dim(stripAnsi(e.project)));
|
|
57175
57274
|
}
|
|
57176
57275
|
console.log("");
|
|
57177
57276
|
}
|
|
57178
|
-
console.log(" " +
|
|
57179
|
-
console.log(" " +
|
|
57277
|
+
console.log(" " + chalk38.bold("Next steps:"));
|
|
57278
|
+
console.log(" " + chalk38.cyan("1.") + " Rotate any exposed keys shown above");
|
|
57180
57279
|
console.log(
|
|
57181
|
-
" " +
|
|
57280
|
+
" " + chalk38.cyan("2.") + " Run " + chalk38.white("node9 dlp resolve") + " to acknowledge"
|
|
57182
57281
|
);
|
|
57183
57282
|
console.log(
|
|
57184
|
-
" " +
|
|
57283
|
+
" " + chalk38.cyan("3.") + " Run " + chalk38.white("node9 report") + " for full audit history"
|
|
57185
57284
|
);
|
|
57186
57285
|
console.log("");
|
|
57187
57286
|
});
|
|
@@ -57189,7 +57288,7 @@ function registerDlpCommand(program2) {
|
|
|
57189
57288
|
|
|
57190
57289
|
// src/cli/commands/mask.ts
|
|
57191
57290
|
init_dlp();
|
|
57192
|
-
import
|
|
57291
|
+
import chalk39 from "chalk";
|
|
57193
57292
|
import fs72 from "fs";
|
|
57194
57293
|
import path67 from "path";
|
|
57195
57294
|
import os60 from "os";
|
|
@@ -57325,12 +57424,12 @@ function registerMaskCommand(program2) {
|
|
|
57325
57424
|
}
|
|
57326
57425
|
}) : allFiles;
|
|
57327
57426
|
if (filtered.length === 0) {
|
|
57328
|
-
console.log(
|
|
57427
|
+
console.log(chalk39.yellow(" No session files found."));
|
|
57329
57428
|
return;
|
|
57330
57429
|
}
|
|
57331
57430
|
console.log("");
|
|
57332
57431
|
if (dryRun) {
|
|
57333
|
-
console.log(
|
|
57432
|
+
console.log(chalk39.dim(" Dry run \u2014 no files will be modified.\n"));
|
|
57334
57433
|
}
|
|
57335
57434
|
let totalFiles = 0;
|
|
57336
57435
|
let totalLines = 0;
|
|
@@ -57346,23 +57445,23 @@ function registerMaskCommand(program2) {
|
|
|
57346
57445
|
});
|
|
57347
57446
|
const verb = dryRun ? "Would redact" : "Redacted";
|
|
57348
57447
|
console.log(
|
|
57349
|
-
" " +
|
|
57448
|
+
" " + chalk39.dim(shortPath.slice(0, 60).padEnd(62)) + chalk39.red(`${verb}: `) + chalk39.yellow(patterns.join(", ")) + chalk39.dim(` (${redactedLines} line${redactedLines !== 1 ? "s" : ""})`)
|
|
57350
57449
|
);
|
|
57351
57450
|
}
|
|
57352
57451
|
}
|
|
57353
57452
|
console.log("");
|
|
57354
57453
|
if (totalFiles === 0) {
|
|
57355
|
-
console.log(
|
|
57454
|
+
console.log(chalk39.green(" No secrets found in session history."));
|
|
57356
57455
|
} else {
|
|
57357
57456
|
const verb = dryRun ? "would be modified" : "modified";
|
|
57358
57457
|
console.log(
|
|
57359
|
-
|
|
57458
|
+
chalk39.bold(` ${totalFiles} file${totalFiles !== 1 ? "s" : ""} ${verb}`) + chalk39.dim(`, ${totalLines} line${totalLines !== 1 ? "s" : ""} redacted`)
|
|
57360
57459
|
);
|
|
57361
|
-
console.log(" Patterns: " +
|
|
57460
|
+
console.log(" Patterns: " + chalk39.yellow(totalPatterns.join(", ")));
|
|
57362
57461
|
if (!dryRun) {
|
|
57363
57462
|
console.log("");
|
|
57364
57463
|
console.log(
|
|
57365
|
-
|
|
57464
|
+
chalk39.dim(
|
|
57366
57465
|
" Note: secrets were already sent to the AI provider during the active session.\n This cleans your local disk only. Rotate any exposed keys."
|
|
57367
57466
|
)
|
|
57368
57467
|
);
|
|
@@ -57379,40 +57478,31 @@ var { version } = JSON.parse(
|
|
|
57379
57478
|
);
|
|
57380
57479
|
var program = new Command();
|
|
57381
57480
|
program.name("node9").description("The Sudo Command for AI Agents").version(version);
|
|
57382
|
-
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action((apiKey, options) => {
|
|
57383
|
-
|
|
57384
|
-
profileName
|
|
57385
|
-
|
|
57386
|
-
|
|
57387
|
-
|
|
57388
|
-
console.log(
|
|
57389
|
-
console.log(
|
|
57390
|
-
|
|
57391
|
-
|
|
57392
|
-
|
|
57393
|
-
|
|
57394
|
-
|
|
57395
|
-
|
|
57396
|
-
|
|
57397
|
-
console.log(
|
|
57398
|
-
chalk40.gray(` To enable team policy + dashboard sync: set it to true, or re-init.`)
|
|
57399
|
-
);
|
|
57400
|
-
}
|
|
57401
|
-
} else {
|
|
57402
|
-
console.log(chalk40.green(`\u2705 Logged in \u2014 agent mode`));
|
|
57403
|
-
console.log(chalk40.gray(` Team policy enforced for all calls via Node9 cloud.`));
|
|
57404
|
-
if (!isTestingMode()) {
|
|
57405
|
-
const healed = ensureAutostartHealthy(!!getConfig().settings.autoStartDaemon);
|
|
57406
|
-
if (healed === "repaired")
|
|
57407
|
-
console.log(chalk40.green(` \u2713 Re-enabled daemon autostart (survives reboot)`));
|
|
57408
|
-
}
|
|
57481
|
+
program.command("login").argument("<apiKey>").option("--local", "Save key for audit/logging only \u2014 local config still controls all decisions").option("--profile <name>", 'Save as a named profile (default: "default")').action(async (apiKey, options) => {
|
|
57482
|
+
if (options.profile && options.profile !== "default") {
|
|
57483
|
+
const { profileName } = writeCredentialsAndConfig(apiKey, {
|
|
57484
|
+
profileName: options.profile,
|
|
57485
|
+
isLocal: options.local
|
|
57486
|
+
});
|
|
57487
|
+
console.log(chalk41.green(`\u2705 Profile "${profileName}" saved`));
|
|
57488
|
+
console.log(chalk41.gray(` Switch to it per-session: NODE9_PROFILE=${profileName} claude`));
|
|
57489
|
+
return;
|
|
57490
|
+
}
|
|
57491
|
+
if (options.local) {
|
|
57492
|
+
writeCredentialsAndConfig(apiKey, { isLocal: true });
|
|
57493
|
+
console.log(chalk41.green(`\u2705 Key saved \u2014 Privacy mode \u{1F6E1}\uFE0F`));
|
|
57494
|
+
console.log(chalk41.gray(` All decisions stay on this machine. Nothing syncs to the cloud.`));
|
|
57495
|
+
return;
|
|
57409
57496
|
}
|
|
57497
|
+
const outcome = await onboardMachine(apiKey);
|
|
57498
|
+
console.log(renderOnboardOutcome(outcome));
|
|
57499
|
+
if (!outcome.ok) process.exitCode = 1;
|
|
57410
57500
|
});
|
|
57411
57501
|
program.command("signup").description("Create your node9 account / open the dashboard in your browser").option("--login", "Open the login page instead of signup").action((options) => {
|
|
57412
57502
|
const route = options.login ? "auth/login" : "auth/signup";
|
|
57413
57503
|
const url = `https://node9.ai/${route}?ref=cli_cmd`;
|
|
57414
57504
|
console.log("");
|
|
57415
|
-
console.log(" " +
|
|
57505
|
+
console.log(" " + chalk41.dim("Opening ") + chalk41.cyan.underline(url));
|
|
57416
57506
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
57417
57507
|
try {
|
|
57418
57508
|
const child = spawn8(opener, [url], {
|
|
@@ -57445,7 +57535,7 @@ program.command("addto", { hidden: true }).description("Integrate Node9 with an
|
|
|
57445
57535
|
if (target === "hermes") return setupHermes();
|
|
57446
57536
|
if (target === "hud") return setupHud();
|
|
57447
57537
|
console.error(
|
|
57448
|
-
|
|
57538
|
+
chalk41.red(
|
|
57449
57539
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
57450
57540
|
)
|
|
57451
57541
|
);
|
|
@@ -57459,20 +57549,20 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
57459
57549
|
"The agent to protect: claude | antigravity | copilot | gemini | cursor | codex | windsurf | vscode | hud"
|
|
57460
57550
|
).action(async (target) => {
|
|
57461
57551
|
if (!target) {
|
|
57462
|
-
console.log(
|
|
57463
|
-
console.log(" Usage: " +
|
|
57552
|
+
console.log(chalk41.cyan("\n\u{1F6E1}\uFE0F Node9 Setup \u2014 integrate with your AI agent\n"));
|
|
57553
|
+
console.log(" Usage: " + chalk41.white("node9 setup <target>") + "\n");
|
|
57464
57554
|
console.log(" Targets:");
|
|
57465
|
-
console.log(" " +
|
|
57466
|
-
console.log(" " +
|
|
57467
|
-
console.log(" " +
|
|
57468
|
-
console.log(" " +
|
|
57469
|
-
console.log(" " +
|
|
57470
|
-
console.log(" " +
|
|
57471
|
-
console.log(" " +
|
|
57472
|
-
console.log(" " +
|
|
57473
|
-
console.log(" " +
|
|
57555
|
+
console.log(" " + chalk41.green("claude") + " \u2014 Claude Code (hook mode)");
|
|
57556
|
+
console.log(" " + chalk41.green("gemini") + " \u2014 Gemini CLI (hook mode)");
|
|
57557
|
+
console.log(" " + chalk41.green("antigravity") + " \u2014 Antigravity / agy (hook mode)");
|
|
57558
|
+
console.log(" " + chalk41.green("copilot") + " \u2014 GitHub Copilot CLI (hook mode)");
|
|
57559
|
+
console.log(" " + chalk41.green("cursor") + " \u2014 Cursor (MCP proxy)");
|
|
57560
|
+
console.log(" " + chalk41.green("codex") + " \u2014 OpenAI Codex CLI (MCP proxy)");
|
|
57561
|
+
console.log(" " + chalk41.green("windsurf") + " \u2014 Windsurf (MCP proxy)");
|
|
57562
|
+
console.log(" " + chalk41.green("vscode") + " \u2014 VSCode / Copilot (MCP proxy)");
|
|
57563
|
+
console.log(" " + chalk41.green("hermes") + " \u2014 Hermes Agent (hook mode)");
|
|
57474
57564
|
process.stdout.write(
|
|
57475
|
-
" " +
|
|
57565
|
+
" " + chalk41.green("hud") + " \u2014 Claude Code security statusline\n"
|
|
57476
57566
|
);
|
|
57477
57567
|
console.log("");
|
|
57478
57568
|
return;
|
|
@@ -57489,7 +57579,7 @@ program.command("setup", { hidden: true }).description('Alias for "addto" \u2014
|
|
|
57489
57579
|
if (t === "hermes") return setupHermes();
|
|
57490
57580
|
if (t === "hud") return setupHud();
|
|
57491
57581
|
console.error(
|
|
57492
|
-
|
|
57582
|
+
chalk41.red(
|
|
57493
57583
|
`Unknown target: "${target}". Supported: claude, antigravity, copilot, gemini, cursor, codex, windsurf, vscode, hermes, hud`
|
|
57494
57584
|
)
|
|
57495
57585
|
);
|
|
@@ -57500,36 +57590,36 @@ program.command("removefrom", { hidden: true }).description("Remove Node9 hooks
|
|
|
57500
57590
|
const agent = resolveAgentTeardown(target);
|
|
57501
57591
|
if (!agent) {
|
|
57502
57592
|
console.error(
|
|
57503
|
-
|
|
57593
|
+
chalk41.red(`Unknown target: "${target}". Supported: ${agentTeardownTargets().join(", ")}`)
|
|
57504
57594
|
);
|
|
57505
57595
|
process.exit(1);
|
|
57506
57596
|
return;
|
|
57507
57597
|
}
|
|
57508
|
-
console.log(
|
|
57598
|
+
console.log(chalk41.cyan(`
|
|
57509
57599
|
\u{1F6E1}\uFE0F Node9: removing hooks from ${agent.label}...
|
|
57510
57600
|
`));
|
|
57511
57601
|
try {
|
|
57512
57602
|
agent.fn();
|
|
57513
57603
|
} catch (err2) {
|
|
57514
|
-
console.error(
|
|
57604
|
+
console.error(chalk41.red(` \u26A0\uFE0F Failed: ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
57515
57605
|
process.exit(1);
|
|
57516
57606
|
}
|
|
57517
|
-
console.log(
|
|
57607
|
+
console.log(chalk41.gray("\n Restart the agent for changes to take effect."));
|
|
57518
57608
|
});
|
|
57519
57609
|
function undoLeftoverNote() {
|
|
57520
57610
|
if (undoLeftoverPaths().length === 0) return "";
|
|
57521
57611
|
return "\n Snapshots from the removed undo feature also remain.\n Remove them with: node9 undo --purge";
|
|
57522
57612
|
}
|
|
57523
57613
|
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) => {
|
|
57524
|
-
console.log(
|
|
57525
|
-
console.log(
|
|
57614
|
+
console.log(chalk41.cyan("\n\u{1F6E1}\uFE0F Node9 Uninstall\n"));
|
|
57615
|
+
console.log(chalk41.bold("Stopping daemon..."));
|
|
57526
57616
|
try {
|
|
57527
57617
|
stopDaemon();
|
|
57528
|
-
console.log(
|
|
57618
|
+
console.log(chalk41.green(" \u2705 Daemon stopped"));
|
|
57529
57619
|
} catch {
|
|
57530
|
-
console.log(
|
|
57620
|
+
console.log(chalk41.blue(" \u2139\uFE0F Daemon was not running"));
|
|
57531
57621
|
}
|
|
57532
|
-
console.log(
|
|
57622
|
+
console.log(chalk41.bold("\nRemoving hooks..."));
|
|
57533
57623
|
let teardownFailed = false;
|
|
57534
57624
|
for (const { label: label2, fn } of AGENT_TEARDOWNS) {
|
|
57535
57625
|
try {
|
|
@@ -57537,7 +57627,7 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
57537
57627
|
} catch (err2) {
|
|
57538
57628
|
teardownFailed = true;
|
|
57539
57629
|
console.error(
|
|
57540
|
-
|
|
57630
|
+
chalk41.red(
|
|
57541
57631
|
` \u26A0\uFE0F Failed to remove ${label2} hooks: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
57542
57632
|
)
|
|
57543
57633
|
);
|
|
@@ -57547,12 +57637,12 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
57547
57637
|
try {
|
|
57548
57638
|
const residual = getAgentWiring().filter((a) => a.wireState === "wired");
|
|
57549
57639
|
if (residual.length === 0) {
|
|
57550
|
-
console.log(
|
|
57640
|
+
console.log(chalk41.green(" \u2705 Verified \u2014 no node9 hooks or plugin shims remain"));
|
|
57551
57641
|
} else {
|
|
57552
57642
|
teardownFailed = true;
|
|
57553
|
-
console.error(
|
|
57643
|
+
console.error(chalk41.red(" \u26A0\uFE0F Still wired after teardown:"));
|
|
57554
57644
|
for (const a of residual) {
|
|
57555
|
-
console.error(
|
|
57645
|
+
console.error(chalk41.red(` \u2022 ${a.label} \u2014 ${a.settingsPath}`));
|
|
57556
57646
|
}
|
|
57557
57647
|
}
|
|
57558
57648
|
} catch {
|
|
@@ -57569,11 +57659,11 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
57569
57659
|
fs75.rmSync(node9Dir, { recursive: true });
|
|
57570
57660
|
if (fs75.existsSync(node9Dir)) {
|
|
57571
57661
|
console.error(
|
|
57572
|
-
|
|
57662
|
+
chalk41.red("\n \u26A0\uFE0F ~/.node9/ could not be fully deleted \u2014 remove it manually.")
|
|
57573
57663
|
);
|
|
57574
57664
|
} else {
|
|
57575
57665
|
console.log(
|
|
57576
|
-
|
|
57666
|
+
chalk41.green(
|
|
57577
57667
|
`
|
|
57578
57668
|
\u2705 Deleted ~/.node9/ (config, audit log, credentials${alsoSnapshots})`
|
|
57579
57669
|
)
|
|
@@ -57581,27 +57671,27 @@ program.command("uninstall").description("Remove all Node9 hooks and optionally
|
|
|
57581
57671
|
}
|
|
57582
57672
|
} else {
|
|
57583
57673
|
console.log(
|
|
57584
|
-
|
|
57674
|
+
chalk41.yellow(`
|
|
57585
57675
|
Skipped \u2014 ~/.node9/ was not deleted.${undoLeftoverNote()}`)
|
|
57586
57676
|
);
|
|
57587
57677
|
}
|
|
57588
57678
|
} else {
|
|
57589
|
-
console.log(
|
|
57679
|
+
console.log(chalk41.blue("\n \u2139\uFE0F ~/.node9/ not found \u2014 nothing to delete"));
|
|
57590
57680
|
}
|
|
57591
57681
|
} else {
|
|
57592
57682
|
console.log(
|
|
57593
|
-
|
|
57683
|
+
chalk41.gray(
|
|
57594
57684
|
`
|
|
57595
57685
|
~/.node9/ kept \u2014 run with --purge to delete config and audit log${undoLeftoverNote()}`
|
|
57596
57686
|
)
|
|
57597
57687
|
);
|
|
57598
57688
|
}
|
|
57599
57689
|
if (teardownFailed) {
|
|
57600
|
-
console.error(
|
|
57690
|
+
console.error(chalk41.red("\n \u26A0\uFE0F Some hooks could not be removed \u2014 see errors above."));
|
|
57601
57691
|
process.exit(1);
|
|
57602
57692
|
}
|
|
57603
|
-
console.log(
|
|
57604
|
-
console.log(
|
|
57693
|
+
console.log(chalk41.green.bold("\n\u{1F6E1}\uFE0F Node9 removed. Run: npm uninstall -g node9-ai"));
|
|
57694
|
+
console.log(chalk41.gray(" Restart any open AI agent sessions for changes to take effect.\n"));
|
|
57605
57695
|
});
|
|
57606
57696
|
registerDoctorCommand(program, version);
|
|
57607
57697
|
program.command("explain").description(
|
|
@@ -57614,7 +57704,7 @@ program.command("explain").description(
|
|
|
57614
57704
|
try {
|
|
57615
57705
|
args = JSON.parse(trimmed);
|
|
57616
57706
|
} catch {
|
|
57617
|
-
console.error(
|
|
57707
|
+
console.error(chalk41.red(`
|
|
57618
57708
|
\u274C Invalid JSON: ${trimmed}
|
|
57619
57709
|
`));
|
|
57620
57710
|
process.exit(1);
|
|
@@ -57625,62 +57715,62 @@ program.command("explain").description(
|
|
|
57625
57715
|
}
|
|
57626
57716
|
const result = await explainPolicy(tool, args);
|
|
57627
57717
|
console.log("");
|
|
57628
|
-
console.log(
|
|
57718
|
+
console.log(chalk41.cyan.bold("\u{1F6E1}\uFE0F Node9 Explain"));
|
|
57629
57719
|
console.log("");
|
|
57630
|
-
console.log(` ${
|
|
57720
|
+
console.log(` ${chalk41.bold("Tool:")} ${chalk41.white(result.tool)}`);
|
|
57631
57721
|
if (argsRaw) {
|
|
57632
57722
|
const preview2 = argsRaw.length > 80 ? argsRaw.slice(0, 77) + "\u2026" : argsRaw;
|
|
57633
|
-
console.log(` ${
|
|
57723
|
+
console.log(` ${chalk41.bold("Input:")} ${chalk41.gray(preview2)}`);
|
|
57634
57724
|
}
|
|
57635
57725
|
console.log("");
|
|
57636
|
-
console.log(
|
|
57726
|
+
console.log(chalk41.bold("Config Sources (Waterfall):"));
|
|
57637
57727
|
for (const tier of result.waterfall) {
|
|
57638
|
-
const num3 =
|
|
57728
|
+
const num3 = chalk41.gray(` ${tier.tier}.`);
|
|
57639
57729
|
const label2 = tier.label.padEnd(16);
|
|
57640
57730
|
let statusStr;
|
|
57641
57731
|
if (tier.tier === 1) {
|
|
57642
|
-
statusStr =
|
|
57732
|
+
statusStr = chalk41.gray(tier.note ?? "");
|
|
57643
57733
|
} else if (tier.status === "active") {
|
|
57644
|
-
const loc = tier.path ?
|
|
57645
|
-
const note = tier.note ?
|
|
57646
|
-
statusStr =
|
|
57734
|
+
const loc = tier.path ? chalk41.gray(tier.path) : "";
|
|
57735
|
+
const note = tier.note ? chalk41.gray(`(${tier.note})`) : "";
|
|
57736
|
+
statusStr = chalk41.green("\u2713 active") + (loc ? " " + loc : "") + (note ? " " + note : "");
|
|
57647
57737
|
} else {
|
|
57648
|
-
statusStr =
|
|
57738
|
+
statusStr = chalk41.gray("\u25CB " + (tier.note ?? "not found"));
|
|
57649
57739
|
}
|
|
57650
|
-
console.log(`${num3} ${
|
|
57740
|
+
console.log(`${num3} ${chalk41.white(label2)} ${statusStr}`);
|
|
57651
57741
|
}
|
|
57652
57742
|
console.log("");
|
|
57653
|
-
console.log(
|
|
57743
|
+
console.log(chalk41.bold("Policy Evaluation:"));
|
|
57654
57744
|
for (const step of result.steps) {
|
|
57655
57745
|
const isFinal = step.isFinal;
|
|
57656
57746
|
let icon;
|
|
57657
|
-
if (step.outcome === "allow") icon =
|
|
57658
|
-
else if (step.outcome === "block") icon =
|
|
57659
|
-
else if (step.outcome === "review") icon =
|
|
57660
|
-
else if (step.outcome === "skip") icon =
|
|
57661
|
-
else icon =
|
|
57747
|
+
if (step.outcome === "allow") icon = chalk41.green(" \u2705");
|
|
57748
|
+
else if (step.outcome === "block") icon = chalk41.red(" \u{1F6D1}");
|
|
57749
|
+
else if (step.outcome === "review") icon = chalk41.red(" \u{1F534}");
|
|
57750
|
+
else if (step.outcome === "skip") icon = chalk41.gray(" \u2500 ");
|
|
57751
|
+
else icon = chalk41.gray(" \u25CB ");
|
|
57662
57752
|
const name = step.name.padEnd(18);
|
|
57663
|
-
const nameStr = isFinal ?
|
|
57664
|
-
const detail = isFinal ?
|
|
57665
|
-
const arrow = isFinal ?
|
|
57753
|
+
const nameStr = isFinal ? chalk41.white.bold(name) : chalk41.white(name);
|
|
57754
|
+
const detail = isFinal ? chalk41.white(step.detail) : chalk41.gray(step.detail);
|
|
57755
|
+
const arrow = isFinal ? chalk41.yellow(" \u2190 STOP") : "";
|
|
57666
57756
|
console.log(`${icon} ${nameStr} ${detail}${arrow}`);
|
|
57667
57757
|
}
|
|
57668
57758
|
console.log("");
|
|
57669
57759
|
if (result.decision === "allow") {
|
|
57670
|
-
console.log(
|
|
57760
|
+
console.log(chalk41.green.bold(" Decision: \u2705 ALLOW") + chalk41.gray(" \u2014 no approval needed"));
|
|
57671
57761
|
} else if (result.decision === "block") {
|
|
57672
57762
|
console.log(
|
|
57673
|
-
|
|
57763
|
+
chalk41.red.bold(" Decision: \u{1F6D1} BLOCK") + chalk41.gray(" \u2014 this action is blocked")
|
|
57674
57764
|
);
|
|
57675
57765
|
if (result.blockedByLabel) {
|
|
57676
|
-
console.log(
|
|
57766
|
+
console.log(chalk41.gray(` Reason: ${result.blockedByLabel}`));
|
|
57677
57767
|
}
|
|
57678
57768
|
} else {
|
|
57679
57769
|
console.log(
|
|
57680
|
-
|
|
57770
|
+
chalk41.red.bold(" Decision: \u{1F534} REVIEW") + chalk41.gray(" \u2014 human approval required")
|
|
57681
57771
|
);
|
|
57682
57772
|
if (result.blockedByLabel) {
|
|
57683
|
-
console.log(
|
|
57773
|
+
console.log(chalk41.gray(` Reason: ${result.blockedByLabel}`));
|
|
57684
57774
|
}
|
|
57685
57775
|
}
|
|
57686
57776
|
console.log("");
|
|
@@ -57697,7 +57787,7 @@ program.command("tail").description("Stream live agent activity to the terminal"
|
|
|
57697
57787
|
try {
|
|
57698
57788
|
await startTail2(options);
|
|
57699
57789
|
} catch (err2) {
|
|
57700
|
-
console.error(
|
|
57790
|
+
console.error(chalk41.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
57701
57791
|
process.exit(1);
|
|
57702
57792
|
}
|
|
57703
57793
|
});
|
|
@@ -57708,7 +57798,7 @@ program.command("monitor").description("Live interactive dashboard \u2014 activi
|
|
|
57708
57798
|
const mod = await dynamicImport(`file://${dashboardPath}`);
|
|
57709
57799
|
await mod.startMonitor();
|
|
57710
57800
|
} catch (err2) {
|
|
57711
|
-
console.error(
|
|
57801
|
+
console.error(chalk41.red(`\u274C ${err2 instanceof Error ? err2.message : String(err2)}`));
|
|
57712
57802
|
process.exit(1);
|
|
57713
57803
|
}
|
|
57714
57804
|
});
|
|
@@ -57763,7 +57853,7 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
57763
57853
|
const ms = parseDuration(options.duration);
|
|
57764
57854
|
if (ms === null) {
|
|
57765
57855
|
console.error(
|
|
57766
|
-
|
|
57856
|
+
chalk41.red(`
|
|
57767
57857
|
\u274C Invalid duration: "${options.duration}". Use format like 15m, 1h, 30s.
|
|
57768
57858
|
`)
|
|
57769
57859
|
);
|
|
@@ -57771,20 +57861,20 @@ program.command("pause").description("Temporarily disable Node9 protection for a
|
|
|
57771
57861
|
}
|
|
57772
57862
|
pauseNode9(ms, options.duration);
|
|
57773
57863
|
const expiresAt = new Date(Date.now() + ms).toLocaleTimeString();
|
|
57774
|
-
console.log(
|
|
57864
|
+
console.log(chalk41.yellow(`
|
|
57775
57865
|
\u23F8 Node9 paused until ${expiresAt}`));
|
|
57776
|
-
console.log(
|
|
57777
|
-
console.log(
|
|
57866
|
+
console.log(chalk41.gray(` All tool calls will be allowed without review.`));
|
|
57867
|
+
console.log(chalk41.gray(` Run "node9 resume" to re-enable early.
|
|
57778
57868
|
`));
|
|
57779
57869
|
});
|
|
57780
57870
|
program.command("resume").description("Re-enable Node9 protection immediately").action(() => {
|
|
57781
57871
|
const { paused } = checkPause();
|
|
57782
57872
|
if (!paused) {
|
|
57783
|
-
console.log(
|
|
57873
|
+
console.log(chalk41.gray("\nNode9 is already active \u2014 nothing to resume.\n"));
|
|
57784
57874
|
return;
|
|
57785
57875
|
}
|
|
57786
57876
|
resumeNode9();
|
|
57787
|
-
console.log(
|
|
57877
|
+
console.log(chalk41.green("\n\u25B6 Node9 resumed \u2014 protection is active.\n"));
|
|
57788
57878
|
});
|
|
57789
57879
|
var HOOK_BASED_AGENTS = {
|
|
57790
57880
|
claude: "claude",
|
|
@@ -57800,15 +57890,15 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
57800
57890
|
if (HOOK_BASED_AGENTS[firstArg2] !== void 0) {
|
|
57801
57891
|
const target = HOOK_BASED_AGENTS[firstArg2];
|
|
57802
57892
|
console.error(
|
|
57803
|
-
|
|
57893
|
+
chalk41.yellow(`
|
|
57804
57894
|
\u26A0\uFE0F Node9 proxy mode does not support "${target}" directly.`)
|
|
57805
57895
|
);
|
|
57806
|
-
console.error(
|
|
57896
|
+
console.error(chalk41.white(`
|
|
57807
57897
|
"${target}" uses its own hook system. Use:`));
|
|
57808
57898
|
console.error(
|
|
57809
|
-
|
|
57899
|
+
chalk41.green(` node9 addto ${target} `) + chalk41.gray("# one-time setup")
|
|
57810
57900
|
);
|
|
57811
|
-
console.error(
|
|
57901
|
+
console.error(chalk41.green(` ${target} `) + chalk41.gray("# run normally"));
|
|
57812
57902
|
process.exit(1);
|
|
57813
57903
|
}
|
|
57814
57904
|
const runArgs = firstArg2 === "shell" ? commandArgs.slice(1) : commandArgs;
|
|
@@ -57825,7 +57915,7 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
57825
57915
|
}
|
|
57826
57916
|
);
|
|
57827
57917
|
if (result.noApprovalMechanism && !isDaemonRunning() && !process.env.NODE9_NO_AUTO_DAEMON && getConfig().settings.autoStartDaemon) {
|
|
57828
|
-
console.error(
|
|
57918
|
+
console.error(chalk41.cyan("\n\u{1F6E1}\uFE0F Node9: Starting approval daemon automatically..."));
|
|
57829
57919
|
const daemonReady = await autoStartDaemonAndWait();
|
|
57830
57920
|
if (daemonReady) result = await authorizeHeadless("shell", { command: fullCommand });
|
|
57831
57921
|
}
|
|
@@ -57838,12 +57928,12 @@ program.argument("[command...]", "The agent command to run (e.g., gemini)").acti
|
|
|
57838
57928
|
}
|
|
57839
57929
|
if (!result.approved) {
|
|
57840
57930
|
console.error(
|
|
57841
|
-
|
|
57931
|
+
chalk41.red(`
|
|
57842
57932
|
\u274C Node9 Blocked: ${result.reason || "Dangerous command detected."}`)
|
|
57843
57933
|
);
|
|
57844
57934
|
process.exit(1);
|
|
57845
57935
|
}
|
|
57846
|
-
console.error(
|
|
57936
|
+
console.error(chalk41.green("\n\u2705 Approved \u2014 running command...\n"));
|
|
57847
57937
|
await runProxy(fullCommand);
|
|
57848
57938
|
} else {
|
|
57849
57939
|
program.help();
|