@node9/proxy 1.11.2 → 1.11.3
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 +421 -50
- package/dist/cli.mjs +421 -50
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -116,7 +116,7 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
116
116
|
}
|
|
117
117
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
118
118
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
119
|
-
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
119
|
+
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
120
120
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
121
121
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
122
|
tool: toolName,
|
|
@@ -8647,10 +8647,10 @@ function bold(s) {
|
|
|
8647
8647
|
function color(c, s) {
|
|
8648
8648
|
return `${c}${s}${RESET3}`;
|
|
8649
8649
|
}
|
|
8650
|
-
function progressBar(
|
|
8651
|
-
const filled = Math.round(Math.min(
|
|
8650
|
+
function progressBar(pct, warnAt = 70, critAt = 85) {
|
|
8651
|
+
const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
|
|
8652
8652
|
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
8653
|
-
const c =
|
|
8653
|
+
const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
|
|
8654
8654
|
return `${c}${bar}${RESET3}`;
|
|
8655
8655
|
}
|
|
8656
8656
|
function formatTimeLeft(resetsAt) {
|
|
@@ -8866,15 +8866,15 @@ function renderContextLine(stdin) {
|
|
|
8866
8866
|
}
|
|
8867
8867
|
const rl = stdin.rate_limits;
|
|
8868
8868
|
if (rl?.five_hour?.used_percentage !== void 0) {
|
|
8869
|
-
const
|
|
8870
|
-
const bar = progressBar(
|
|
8869
|
+
const pct = Math.round(rl.five_hour.used_percentage);
|
|
8870
|
+
const bar = progressBar(pct, 60, 80);
|
|
8871
8871
|
const left = formatTimeLeft(rl.five_hour.resets_at);
|
|
8872
|
-
parts.push(`${dim("\u2502")} 5h ${bar} ${
|
|
8872
|
+
parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
|
|
8873
8873
|
}
|
|
8874
8874
|
if (rl?.seven_day?.used_percentage !== void 0) {
|
|
8875
|
-
const
|
|
8876
|
-
const bar = progressBar(
|
|
8877
|
-
parts.push(`${dim("\u2502")} 7d ${bar} ${
|
|
8875
|
+
const pct = Math.round(rl.seven_day.used_percentage);
|
|
8876
|
+
const bar = progressBar(pct, 60, 80);
|
|
8877
|
+
parts.push(`${dim("\u2502")} 7d ${bar} ${pct}%`);
|
|
8878
8878
|
}
|
|
8879
8879
|
if (parts.length === 0) return null;
|
|
8880
8880
|
return parts.join(" ");
|
|
@@ -11747,8 +11747,8 @@ function buildTestTimestamps(allEntries) {
|
|
|
11747
11747
|
return testTs;
|
|
11748
11748
|
}
|
|
11749
11749
|
function isTestEntry(entry, testTs) {
|
|
11750
|
-
if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
|
|
11751
11750
|
if (entry.testRun === true) return true;
|
|
11751
|
+
if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
|
|
11752
11752
|
const cmd = entry.args?.command;
|
|
11753
11753
|
if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
|
|
11754
11754
|
const t = new Date(entry.ts).getTime();
|
|
@@ -11806,10 +11806,6 @@ function colorBar(value, max, width) {
|
|
|
11806
11806
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
11807
11807
|
return import_chalk9.default.cyan(s.slice(0, filled)) + import_chalk9.default.dim(s.slice(filled));
|
|
11808
11808
|
}
|
|
11809
|
-
function pct(num3, total) {
|
|
11810
|
-
if (total === 0) return "\u2013";
|
|
11811
|
-
return Math.round(num3 / total * 100) + "%";
|
|
11812
|
-
}
|
|
11813
11809
|
function fmtDate(d) {
|
|
11814
11810
|
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
11815
11811
|
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
@@ -11980,9 +11976,12 @@ function registerReportCommand(program2) {
|
|
|
11980
11976
|
`));
|
|
11981
11977
|
return;
|
|
11982
11978
|
}
|
|
11983
|
-
let
|
|
11984
|
-
let
|
|
11985
|
-
let
|
|
11979
|
+
let userApproved = 0;
|
|
11980
|
+
let userDenied = 0;
|
|
11981
|
+
let timedOut = 0;
|
|
11982
|
+
let hardBlocked = 0;
|
|
11983
|
+
let dlpBlocked = 0;
|
|
11984
|
+
let observeDlp = 0;
|
|
11986
11985
|
let loopHits = 0;
|
|
11987
11986
|
let testPasses = 0;
|
|
11988
11987
|
let testFails = 0;
|
|
@@ -11995,9 +11994,16 @@ function registerReportCommand(program2) {
|
|
|
11995
11994
|
for (const e of entries) {
|
|
11996
11995
|
const allow = isAllow(e.decision);
|
|
11997
11996
|
const dateKey = e.ts.slice(0, 10);
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
11997
|
+
const userInteracted = e.source === "daemon";
|
|
11998
|
+
if (userInteracted) {
|
|
11999
|
+
if (allow) userApproved++;
|
|
12000
|
+
else userDenied++;
|
|
12001
|
+
} else if (!allow) {
|
|
12002
|
+
if (e.checkedBy === "timeout") timedOut++;
|
|
12003
|
+
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
12004
|
+
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
12005
|
+
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
12006
|
+
}
|
|
12001
12007
|
if (e.checkedBy === "loop-detected") loopHits++;
|
|
12002
12008
|
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
12003
12009
|
t.calls++;
|
|
@@ -12048,25 +12054,84 @@ function registerReportCommand(program2) {
|
|
|
12048
12054
|
" " + import_chalk9.default.bold.cyan("\u{1F6E1} node9 Report") + import_chalk9.default.dim(" \xB7 ") + import_chalk9.default.white(periodLabel[period]) + import_chalk9.default.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + import_chalk9.default.dim(` ${num(total)} events`) + (excludeTests ? import_chalk9.default.dim(` \u2013tests (\u2013${filteredTestCount})`) : "")
|
|
12049
12055
|
);
|
|
12050
12056
|
console.log(" " + line);
|
|
12051
|
-
|
|
12052
|
-
const
|
|
12053
|
-
const dlpLabel = dlpHits > 0 ? import_chalk9.default.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : import_chalk9.default.dim("\u{1F6A8} 0 DLP hits");
|
|
12054
|
-
const loopLabel = loopHits > 0 ? import_chalk9.default.yellow(`\u{1F504} ${loopHits} loops`) : import_chalk9.default.dim("\u{1F504} 0 loops");
|
|
12055
|
-
const currentRate = total > 0 ? blocked / total : 0;
|
|
12057
|
+
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
12058
|
+
const currentRate = total > 0 ? totalBlocked / total : 0;
|
|
12056
12059
|
const trendLabel = (() => {
|
|
12057
|
-
if (priorBlockRate === null) return
|
|
12060
|
+
if (priorBlockRate === null) return "";
|
|
12058
12061
|
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
12059
|
-
|
|
12060
|
-
return import_chalk9.default.
|
|
12062
|
+
if (delta === 0) return "";
|
|
12063
|
+
return " " + (delta > 0 ? import_chalk9.default.red(`\u25B2${delta}%`) : import_chalk9.default.green(`\u25BC${Math.abs(delta)}%`)) + import_chalk9.default.dim(" vs prior");
|
|
12061
12064
|
})();
|
|
12062
12065
|
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
12063
12066
|
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
12064
12067
|
const ratioLabel = reads > 0 ? import_chalk9.default.dim(`edit/read ${(edits / reads).toFixed(1)}`) : import_chalk9.default.dim("edit/read \u2013");
|
|
12065
12068
|
const testLabel = testPasses + testFails > 0 ? import_chalk9.default.dim("tests ") + import_chalk9.default.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + import_chalk9.default.red(`${testFails}\u2717`) : "") : import_chalk9.default.dim("tests \u2013");
|
|
12069
|
+
console.log("");
|
|
12070
|
+
console.log(" " + import_chalk9.default.bold("Protection Summary"));
|
|
12071
|
+
console.log(" " + import_chalk9.default.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
12066
12072
|
console.log(
|
|
12067
|
-
" " + import_chalk9.default.
|
|
12073
|
+
" " + import_chalk9.default.dim("Intercepted") + " " + import_chalk9.default.white(num(total)) + import_chalk9.default.dim(" tool calls")
|
|
12074
|
+
);
|
|
12075
|
+
console.log("");
|
|
12076
|
+
const COL1 = 18;
|
|
12077
|
+
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
12078
|
+
const countStr = colorFn(num(count));
|
|
12079
|
+
const noteStr = note ? import_chalk9.default.dim(" " + note) : "";
|
|
12080
|
+
console.log(" " + icon + " " + import_chalk9.default.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
12081
|
+
};
|
|
12082
|
+
summaryRow(
|
|
12083
|
+
userApproved > 0 ? import_chalk9.default.green("\u2705") : import_chalk9.default.dim("\u2705"),
|
|
12084
|
+
"User approved",
|
|
12085
|
+
userApproved,
|
|
12086
|
+
userApproved === 0 ? "no popups this period" : void 0,
|
|
12087
|
+
userApproved > 0 ? (s) => import_chalk9.default.green(s) : (s) => import_chalk9.default.dim(s)
|
|
12088
|
+
);
|
|
12089
|
+
summaryRow(
|
|
12090
|
+
userDenied > 0 ? import_chalk9.default.red("\u{1F6AB}") : import_chalk9.default.dim("\u{1F6AB}"),
|
|
12091
|
+
"User denied",
|
|
12092
|
+
userDenied,
|
|
12093
|
+
void 0,
|
|
12094
|
+
userDenied > 0 ? (s) => import_chalk9.default.red(s) : (s) => import_chalk9.default.dim(s)
|
|
12068
12095
|
);
|
|
12069
|
-
|
|
12096
|
+
summaryRow(
|
|
12097
|
+
timedOut > 0 ? import_chalk9.default.yellow("\u23F1") : import_chalk9.default.dim("\u23F1"),
|
|
12098
|
+
"Timed out",
|
|
12099
|
+
timedOut,
|
|
12100
|
+
timedOut > 0 ? "no approval response" : void 0,
|
|
12101
|
+
timedOut > 0 ? (s) => import_chalk9.default.yellow(s) : (s) => import_chalk9.default.dim(s)
|
|
12102
|
+
);
|
|
12103
|
+
summaryRow(
|
|
12104
|
+
hardBlocked > 0 ? import_chalk9.default.red("\u{1F6D1}") : import_chalk9.default.dim("\u{1F6D1}"),
|
|
12105
|
+
"Auto-blocked",
|
|
12106
|
+
hardBlocked,
|
|
12107
|
+
void 0,
|
|
12108
|
+
hardBlocked > 0 ? (s) => import_chalk9.default.red(s) : (s) => import_chalk9.default.dim(s)
|
|
12109
|
+
);
|
|
12110
|
+
summaryRow(
|
|
12111
|
+
dlpBlocked > 0 ? import_chalk9.default.yellow("\u{1F6A8}") : import_chalk9.default.dim("\u{1F6A8}"),
|
|
12112
|
+
"DLP blocked",
|
|
12113
|
+
dlpBlocked,
|
|
12114
|
+
void 0,
|
|
12115
|
+
dlpBlocked > 0 ? (s) => import_chalk9.default.yellow(s) : (s) => import_chalk9.default.dim(s)
|
|
12116
|
+
);
|
|
12117
|
+
summaryRow(
|
|
12118
|
+
observeDlp > 0 ? import_chalk9.default.blue("\u{1F441}") : import_chalk9.default.dim("\u{1F441}"),
|
|
12119
|
+
"DLP (observe)",
|
|
12120
|
+
observeDlp,
|
|
12121
|
+
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
12122
|
+
observeDlp > 0 ? (s) => import_chalk9.default.blue(s) : (s) => import_chalk9.default.dim(s)
|
|
12123
|
+
);
|
|
12124
|
+
summaryRow(
|
|
12125
|
+
loopHits > 0 ? import_chalk9.default.yellow("\u{1F504}") : import_chalk9.default.dim("\u{1F504}"),
|
|
12126
|
+
"Loops detected",
|
|
12127
|
+
loopHits,
|
|
12128
|
+
void 0,
|
|
12129
|
+
loopHits > 0 ? (s) => import_chalk9.default.yellow(s) : (s) => import_chalk9.default.dim(s)
|
|
12130
|
+
);
|
|
12131
|
+
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
12132
|
+
console.log("");
|
|
12133
|
+
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
12134
|
+
}
|
|
12070
12135
|
console.log("");
|
|
12071
12136
|
const toolHeaderRaw = "Top Tools";
|
|
12072
12137
|
const blockHeaderRaw = "Top Blocks";
|
|
@@ -14247,6 +14312,22 @@ function claudeModelPrice2(model) {
|
|
|
14247
14312
|
}
|
|
14248
14313
|
return null;
|
|
14249
14314
|
}
|
|
14315
|
+
var GEMINI_PRICING = {
|
|
14316
|
+
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
14317
|
+
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
14318
|
+
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
14319
|
+
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
14320
|
+
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
14321
|
+
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
14322
|
+
};
|
|
14323
|
+
function geminiModelPrice(model) {
|
|
14324
|
+
const base = model.replace(/-preview$/, "").replace(/-exp$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
14325
|
+
for (const [key, p] of Object.entries(GEMINI_PRICING)) {
|
|
14326
|
+
if (base === key || base.startsWith(key)) return p;
|
|
14327
|
+
}
|
|
14328
|
+
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
14329
|
+
return null;
|
|
14330
|
+
}
|
|
14250
14331
|
function num2(n) {
|
|
14251
14332
|
return n.toLocaleString();
|
|
14252
14333
|
}
|
|
@@ -14389,7 +14470,8 @@ function scanClaudeHistory(startDate) {
|
|
|
14389
14470
|
redactedSample: dlpMatch.redactedSample,
|
|
14390
14471
|
toolName,
|
|
14391
14472
|
timestamp: entry.timestamp ?? "",
|
|
14392
|
-
project: projLabel
|
|
14473
|
+
project: projLabel,
|
|
14474
|
+
agent: "claude"
|
|
14393
14475
|
});
|
|
14394
14476
|
}
|
|
14395
14477
|
}
|
|
@@ -14408,7 +14490,135 @@ function scanClaudeHistory(startDate) {
|
|
|
14408
14490
|
toolName,
|
|
14409
14491
|
input,
|
|
14410
14492
|
timestamp: entry.timestamp ?? "",
|
|
14411
|
-
project: projLabel
|
|
14493
|
+
project: projLabel,
|
|
14494
|
+
agent: "claude"
|
|
14495
|
+
});
|
|
14496
|
+
}
|
|
14497
|
+
break;
|
|
14498
|
+
}
|
|
14499
|
+
}
|
|
14500
|
+
}
|
|
14501
|
+
}
|
|
14502
|
+
}
|
|
14503
|
+
return result;
|
|
14504
|
+
}
|
|
14505
|
+
function scanGeminiHistory(startDate) {
|
|
14506
|
+
const tmpDir = import_path36.default.join(import_os29.default.homedir(), ".gemini", "tmp");
|
|
14507
|
+
const result = {
|
|
14508
|
+
filesScanned: 0,
|
|
14509
|
+
sessions: 0,
|
|
14510
|
+
totalToolCalls: 0,
|
|
14511
|
+
bashCalls: 0,
|
|
14512
|
+
findings: [],
|
|
14513
|
+
dlpFindings: [],
|
|
14514
|
+
totalCostUSD: 0,
|
|
14515
|
+
firstDate: null,
|
|
14516
|
+
lastDate: null
|
|
14517
|
+
};
|
|
14518
|
+
if (!import_fs33.default.existsSync(tmpDir)) return result;
|
|
14519
|
+
let slugDirs;
|
|
14520
|
+
try {
|
|
14521
|
+
slugDirs = import_fs33.default.readdirSync(tmpDir);
|
|
14522
|
+
} catch {
|
|
14523
|
+
return result;
|
|
14524
|
+
}
|
|
14525
|
+
const ruleSources = buildRuleSources();
|
|
14526
|
+
for (const slug of slugDirs) {
|
|
14527
|
+
const slugPath = import_path36.default.join(tmpDir, slug);
|
|
14528
|
+
try {
|
|
14529
|
+
if (!import_fs33.default.statSync(slugPath).isDirectory()) continue;
|
|
14530
|
+
} catch {
|
|
14531
|
+
continue;
|
|
14532
|
+
}
|
|
14533
|
+
let projLabel = slug;
|
|
14534
|
+
try {
|
|
14535
|
+
projLabel = import_fs33.default.readFileSync(import_path36.default.join(slugPath, ".project_root"), "utf-8").trim().replace(import_os29.default.homedir(), "~").slice(0, 40);
|
|
14536
|
+
} catch {
|
|
14537
|
+
}
|
|
14538
|
+
const chatsDir = import_path36.default.join(slugPath, "chats");
|
|
14539
|
+
if (!import_fs33.default.existsSync(chatsDir)) continue;
|
|
14540
|
+
let chatFiles;
|
|
14541
|
+
try {
|
|
14542
|
+
chatFiles = import_fs33.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
14543
|
+
} catch {
|
|
14544
|
+
continue;
|
|
14545
|
+
}
|
|
14546
|
+
for (const chatFile of chatFiles) {
|
|
14547
|
+
result.filesScanned++;
|
|
14548
|
+
let raw;
|
|
14549
|
+
try {
|
|
14550
|
+
raw = import_fs33.default.readFileSync(import_path36.default.join(chatsDir, chatFile), "utf-8");
|
|
14551
|
+
} catch {
|
|
14552
|
+
continue;
|
|
14553
|
+
}
|
|
14554
|
+
let session;
|
|
14555
|
+
try {
|
|
14556
|
+
session = JSON.parse(raw);
|
|
14557
|
+
} catch {
|
|
14558
|
+
continue;
|
|
14559
|
+
}
|
|
14560
|
+
result.sessions++;
|
|
14561
|
+
for (const msg of session.messages ?? []) {
|
|
14562
|
+
if (msg.type !== "gemini") continue;
|
|
14563
|
+
if (startDate && msg.timestamp && new Date(msg.timestamp) < startDate) continue;
|
|
14564
|
+
if (msg.timestamp) {
|
|
14565
|
+
if (!result.firstDate || msg.timestamp < result.firstDate)
|
|
14566
|
+
result.firstDate = msg.timestamp;
|
|
14567
|
+
if (!result.lastDate || msg.timestamp > result.lastDate) result.lastDate = msg.timestamp;
|
|
14568
|
+
}
|
|
14569
|
+
const tokens = msg.tokens;
|
|
14570
|
+
const model = msg.model;
|
|
14571
|
+
if (tokens && model) {
|
|
14572
|
+
const p = geminiModelPrice(model);
|
|
14573
|
+
if (p) {
|
|
14574
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
14575
|
+
result.totalCostUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
|
|
14576
|
+
}
|
|
14577
|
+
}
|
|
14578
|
+
for (const tc of msg.toolCalls ?? []) {
|
|
14579
|
+
result.totalToolCalls++;
|
|
14580
|
+
const toolName = tc.name ?? "";
|
|
14581
|
+
const toolNameLower = toolName.toLowerCase();
|
|
14582
|
+
const input = tc.args ?? {};
|
|
14583
|
+
if (toolNameLower === "run_shell_command" || toolNameLower === "shell") {
|
|
14584
|
+
result.bashCalls++;
|
|
14585
|
+
}
|
|
14586
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
14587
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
14588
|
+
continue;
|
|
14589
|
+
const dlpMatch = scanArgs(input);
|
|
14590
|
+
if (dlpMatch) {
|
|
14591
|
+
const isDupe = result.dlpFindings.some(
|
|
14592
|
+
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
14593
|
+
);
|
|
14594
|
+
if (!isDupe) {
|
|
14595
|
+
result.dlpFindings.push({
|
|
14596
|
+
patternName: dlpMatch.patternName,
|
|
14597
|
+
redactedSample: dlpMatch.redactedSample,
|
|
14598
|
+
toolName,
|
|
14599
|
+
timestamp: msg.timestamp ?? "",
|
|
14600
|
+
project: projLabel,
|
|
14601
|
+
agent: "gemini"
|
|
14602
|
+
});
|
|
14603
|
+
}
|
|
14604
|
+
}
|
|
14605
|
+
for (const source of ruleSources) {
|
|
14606
|
+
const { rule } = source;
|
|
14607
|
+
if (rule.verdict === "allow") continue;
|
|
14608
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
14609
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
14610
|
+
const inputPreview = preview(input, 120);
|
|
14611
|
+
const isDupe = result.findings.some(
|
|
14612
|
+
(f) => f.source.rule.name === rule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
14613
|
+
);
|
|
14614
|
+
if (!isDupe) {
|
|
14615
|
+
result.findings.push({
|
|
14616
|
+
source,
|
|
14617
|
+
toolName,
|
|
14618
|
+
input,
|
|
14619
|
+
timestamp: msg.timestamp ?? "",
|
|
14620
|
+
project: projLabel,
|
|
14621
|
+
agent: "gemini"
|
|
14412
14622
|
});
|
|
14413
14623
|
}
|
|
14414
14624
|
break;
|
|
@@ -14419,6 +14629,21 @@ function scanClaudeHistory(startDate) {
|
|
|
14419
14629
|
}
|
|
14420
14630
|
return result;
|
|
14421
14631
|
}
|
|
14632
|
+
function mergeScans(a, b) {
|
|
14633
|
+
const dates = [a.firstDate, b.firstDate].filter(Boolean);
|
|
14634
|
+
const lastDates = [a.lastDate, b.lastDate].filter(Boolean);
|
|
14635
|
+
return {
|
|
14636
|
+
filesScanned: a.filesScanned + b.filesScanned,
|
|
14637
|
+
sessions: a.sessions + b.sessions,
|
|
14638
|
+
totalToolCalls: a.totalToolCalls + b.totalToolCalls,
|
|
14639
|
+
bashCalls: a.bashCalls + b.bashCalls,
|
|
14640
|
+
findings: [...a.findings, ...b.findings],
|
|
14641
|
+
dlpFindings: [...a.dlpFindings, ...b.dlpFindings],
|
|
14642
|
+
totalCostUSD: a.totalCostUSD + b.totalCostUSD,
|
|
14643
|
+
firstDate: dates.length ? dates.sort()[0] : null,
|
|
14644
|
+
lastDate: lastDates.length ? lastDates.sort().at(-1) : null
|
|
14645
|
+
};
|
|
14646
|
+
}
|
|
14422
14647
|
function registerScanCommand(program2) {
|
|
14423
14648
|
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 shield", "5").action((options) => {
|
|
14424
14649
|
const topN = Math.max(1, parseInt(options.top, 10) || 5);
|
|
@@ -14431,23 +14656,25 @@ function registerScanCommand(program2) {
|
|
|
14431
14656
|
console.log("");
|
|
14432
14657
|
console.log(import_chalk21.default.cyan.bold("\u{1F50D} node9 scan") + import_chalk21.default.dim(" \u2014 what would node9 catch?"));
|
|
14433
14658
|
console.log("");
|
|
14434
|
-
const projectsDir = import_path36.default.join(import_os29.default.homedir(), ".claude", "projects");
|
|
14435
|
-
if (!import_fs33.default.existsSync(projectsDir)) {
|
|
14436
|
-
console.log(import_chalk21.default.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
14437
|
-
console.log(import_chalk21.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14438
|
-
return;
|
|
14439
|
-
}
|
|
14440
14659
|
process.stdout.write(import_chalk21.default.dim(" Scanning\u2026"));
|
|
14441
|
-
const
|
|
14660
|
+
const claudeScan = scanClaudeHistory(startDate);
|
|
14661
|
+
const geminiScan = scanGeminiHistory(startDate);
|
|
14662
|
+
const scan = mergeScans(claudeScan, geminiScan);
|
|
14442
14663
|
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
14443
14664
|
if (scan.filesScanned === 0) {
|
|
14444
|
-
console.log(import_chalk21.default.yellow(" No
|
|
14665
|
+
console.log(import_chalk21.default.yellow(" No session history found."));
|
|
14666
|
+
console.log(
|
|
14667
|
+
import_chalk21.default.gray(
|
|
14668
|
+
" Supported: Claude Code (~/.claude/projects/) \xB7 Gemini CLI (~/.gemini/tmp/)\n"
|
|
14669
|
+
)
|
|
14670
|
+
);
|
|
14445
14671
|
return;
|
|
14446
14672
|
}
|
|
14447
14673
|
const rangeLabel = options.all ? import_chalk21.default.dim("all time") : import_chalk21.default.dim(`last ${options.days ?? 90} days`);
|
|
14448
14674
|
const dateRange = scan.firstDate && scan.lastDate ? import_chalk21.default.dim(` ${fmtTs(scan.firstDate)} \u2013 ${fmtTs(scan.lastDate)}`) : "";
|
|
14675
|
+
const sessionBreakdown = claudeScan.sessions > 0 && geminiScan.sessions > 0 ? import_chalk21.default.dim("(") + import_chalk21.default.cyan(String(claudeScan.sessions)) + import_chalk21.default.dim(" Claude \xB7 ") + import_chalk21.default.blue(String(geminiScan.sessions)) + import_chalk21.default.dim(" Gemini)") : "";
|
|
14449
14676
|
console.log(
|
|
14450
|
-
" " + import_chalk21.default.white(num2(scan.sessions)) + import_chalk21.default.dim(" sessions ") + import_chalk21.default.white(num2(scan.totalToolCalls)) + import_chalk21.default.dim(" tool calls ") + import_chalk21.default.white(num2(scan.bashCalls)) + import_chalk21.default.dim(" bash commands ") + rangeLabel + dateRange
|
|
14677
|
+
" " + import_chalk21.default.white(num2(scan.sessions)) + import_chalk21.default.dim(" sessions ") + sessionBreakdown + (sessionBreakdown ? " " : "") + import_chalk21.default.white(num2(scan.totalToolCalls)) + import_chalk21.default.dim(" tool calls ") + import_chalk21.default.white(num2(scan.bashCalls)) + import_chalk21.default.dim(" bash commands ") + rangeLabel + dateRange
|
|
14451
14678
|
);
|
|
14452
14679
|
console.log("");
|
|
14453
14680
|
const byShield = /* @__PURE__ */ new Map();
|
|
@@ -14499,8 +14726,9 @@ function registerScanCommand(program2) {
|
|
|
14499
14726
|
for (const f of shown) {
|
|
14500
14727
|
const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
|
|
14501
14728
|
const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
14502
|
-
const
|
|
14503
|
-
|
|
14729
|
+
const agentBadge = f.agent === "gemini" ? import_chalk21.default.blue("[Gemini] ") : import_chalk21.default.cyan("[Claude] ");
|
|
14730
|
+
const cmd = import_chalk21.default.gray(preview(f.input, 45));
|
|
14731
|
+
console.log(` ${ts}${proj}${agentBadge}${cmd}`);
|
|
14504
14732
|
}
|
|
14505
14733
|
if (ruleFindings.length > topN) {
|
|
14506
14734
|
console.log(
|
|
@@ -14524,8 +14752,9 @@ function registerScanCommand(program2) {
|
|
|
14524
14752
|
for (const f of shownDlp) {
|
|
14525
14753
|
const ts = f.timestamp ? import_chalk21.default.dim(fmtTs(f.timestamp) + " ") : "";
|
|
14526
14754
|
const proj = import_chalk21.default.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
14755
|
+
const agentBadge = f.agent === "gemini" ? import_chalk21.default.blue("[Gemini] ") : import_chalk21.default.cyan("[Claude] ");
|
|
14527
14756
|
console.log(
|
|
14528
|
-
` ${ts}${proj}` + import_chalk21.default.yellow(f.patternName) + import_chalk21.default.dim(" ") + import_chalk21.default.gray(f.redactedSample)
|
|
14757
|
+
` ${ts}${proj}${agentBadge}` + import_chalk21.default.yellow(f.patternName) + import_chalk21.default.dim(" ") + import_chalk21.default.gray(f.redactedSample)
|
|
14529
14758
|
);
|
|
14530
14759
|
}
|
|
14531
14760
|
if (scan.dlpFindings.length > topN) {
|
|
@@ -14540,7 +14769,7 @@ function registerScanCommand(program2) {
|
|
|
14540
14769
|
}
|
|
14541
14770
|
if (scan.totalCostUSD > 0) {
|
|
14542
14771
|
console.log(
|
|
14543
|
-
" " + import_chalk21.default.bold("
|
|
14772
|
+
" " + import_chalk21.default.bold("Agent spend:") + " " + import_chalk21.default.yellow(fmtCost2(scan.totalCostUSD)) + import_chalk21.default.dim(" (for per-period breakdown: node9 report)")
|
|
14544
14773
|
);
|
|
14545
14774
|
console.log("");
|
|
14546
14775
|
}
|
|
@@ -14584,6 +14813,22 @@ function modelPrice(model) {
|
|
|
14584
14813
|
}
|
|
14585
14814
|
return null;
|
|
14586
14815
|
}
|
|
14816
|
+
var GEMINI_PRICING2 = {
|
|
14817
|
+
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
14818
|
+
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
14819
|
+
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
14820
|
+
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
14821
|
+
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
14822
|
+
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
14823
|
+
};
|
|
14824
|
+
function geminiModelPrice2(model) {
|
|
14825
|
+
const base = model.replace(/-preview$/, "").replace(/-exp$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
14826
|
+
for (const [key, p] of Object.entries(GEMINI_PRICING2)) {
|
|
14827
|
+
if (base === key || base.startsWith(key)) return p;
|
|
14828
|
+
}
|
|
14829
|
+
if (base.includes("flash")) return GEMINI_PRICING2["gemini-2.0-flash"];
|
|
14830
|
+
return null;
|
|
14831
|
+
}
|
|
14587
14832
|
function encodeProjectPath(projectPath) {
|
|
14588
14833
|
return projectPath.replace(/\//g, "-");
|
|
14589
14834
|
}
|
|
@@ -14699,6 +14944,123 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
14699
14944
|
}
|
|
14700
14945
|
return result;
|
|
14701
14946
|
}
|
|
14947
|
+
function buildGeminiSessions(days, allAuditEntries) {
|
|
14948
|
+
const tmpDir = import_path37.default.join(import_os30.default.homedir(), ".gemini", "tmp");
|
|
14949
|
+
if (!import_fs34.default.existsSync(tmpDir)) return [];
|
|
14950
|
+
const cutoff = days !== null ? (() => {
|
|
14951
|
+
const d = /* @__PURE__ */ new Date();
|
|
14952
|
+
d.setDate(d.getDate() - days);
|
|
14953
|
+
d.setHours(0, 0, 0, 0);
|
|
14954
|
+
return d;
|
|
14955
|
+
})() : null;
|
|
14956
|
+
let slugDirs;
|
|
14957
|
+
try {
|
|
14958
|
+
slugDirs = import_fs34.default.readdirSync(tmpDir);
|
|
14959
|
+
} catch {
|
|
14960
|
+
return [];
|
|
14961
|
+
}
|
|
14962
|
+
const summaries = [];
|
|
14963
|
+
for (const slug of slugDirs) {
|
|
14964
|
+
const slugPath = import_path37.default.join(tmpDir, slug);
|
|
14965
|
+
try {
|
|
14966
|
+
if (!import_fs34.default.statSync(slugPath).isDirectory()) continue;
|
|
14967
|
+
} catch {
|
|
14968
|
+
continue;
|
|
14969
|
+
}
|
|
14970
|
+
let projectRoot = import_path37.default.join(import_os30.default.homedir(), slug);
|
|
14971
|
+
try {
|
|
14972
|
+
projectRoot = import_fs34.default.readFileSync(import_path37.default.join(slugPath, ".project_root"), "utf-8").trim();
|
|
14973
|
+
} catch {
|
|
14974
|
+
}
|
|
14975
|
+
const chatsDir = import_path37.default.join(slugPath, "chats");
|
|
14976
|
+
if (!import_fs34.default.existsSync(chatsDir)) continue;
|
|
14977
|
+
let chatFiles;
|
|
14978
|
+
try {
|
|
14979
|
+
chatFiles = import_fs34.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
14980
|
+
} catch {
|
|
14981
|
+
continue;
|
|
14982
|
+
}
|
|
14983
|
+
for (const chatFile of chatFiles) {
|
|
14984
|
+
let raw;
|
|
14985
|
+
try {
|
|
14986
|
+
raw = import_fs34.default.readFileSync(import_path37.default.join(chatsDir, chatFile), "utf-8");
|
|
14987
|
+
} catch {
|
|
14988
|
+
continue;
|
|
14989
|
+
}
|
|
14990
|
+
let session;
|
|
14991
|
+
try {
|
|
14992
|
+
session = JSON.parse(raw);
|
|
14993
|
+
} catch {
|
|
14994
|
+
continue;
|
|
14995
|
+
}
|
|
14996
|
+
const startTime = session.startTime ?? "";
|
|
14997
|
+
if (!startTime) continue;
|
|
14998
|
+
if (cutoff && new Date(startTime) < cutoff) continue;
|
|
14999
|
+
let firstPrompt = "";
|
|
15000
|
+
for (const msg of session.messages ?? []) {
|
|
15001
|
+
if (msg.type === "user") {
|
|
15002
|
+
const content = msg.content;
|
|
15003
|
+
if (Array.isArray(content) && content[0]?.text) {
|
|
15004
|
+
firstPrompt = content[0].text;
|
|
15005
|
+
} else if (typeof content === "string") {
|
|
15006
|
+
firstPrompt = content;
|
|
15007
|
+
}
|
|
15008
|
+
break;
|
|
15009
|
+
}
|
|
15010
|
+
}
|
|
15011
|
+
const toolCalls = [];
|
|
15012
|
+
let costUSD = 0;
|
|
15013
|
+
const modifiedFiles = [];
|
|
15014
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
15015
|
+
let lastToolTs = "";
|
|
15016
|
+
for (const msg of session.messages ?? []) {
|
|
15017
|
+
if (msg.type !== "gemini") continue;
|
|
15018
|
+
const tokens = msg.tokens;
|
|
15019
|
+
const model = msg.model;
|
|
15020
|
+
if (tokens && model) {
|
|
15021
|
+
const p = geminiModelPrice2(model);
|
|
15022
|
+
if (p) {
|
|
15023
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
15024
|
+
costUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
|
|
15025
|
+
}
|
|
15026
|
+
}
|
|
15027
|
+
for (const tc of msg.toolCalls ?? []) {
|
|
15028
|
+
const tool = tc.name ?? "";
|
|
15029
|
+
const input = tc.args ?? {};
|
|
15030
|
+
const ts = msg.timestamp ?? "";
|
|
15031
|
+
toolCalls.push({ tool, input, timestamp: ts });
|
|
15032
|
+
if (ts > lastToolTs) lastToolTs = ts;
|
|
15033
|
+
const toolLower = tool.toLowerCase();
|
|
15034
|
+
if (toolLower === "write_file" || toolLower === "edit_file" || toolLower === "create_file" || toolLower === "overwrite_file") {
|
|
15035
|
+
const fp = input.file_path ?? input.path ?? input.filename;
|
|
15036
|
+
if (typeof fp === "string" && !seenFiles.has(fp)) {
|
|
15037
|
+
seenFiles.add(fp);
|
|
15038
|
+
modifiedFiles.push(fp);
|
|
15039
|
+
}
|
|
15040
|
+
}
|
|
15041
|
+
}
|
|
15042
|
+
}
|
|
15043
|
+
const windowEnd = new Date(
|
|
15044
|
+
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
15045
|
+
).toISOString();
|
|
15046
|
+
const blockedCalls = auditEntriesInWindow(allAuditEntries, startTime, windowEnd);
|
|
15047
|
+
summaries.push({
|
|
15048
|
+
sessionId: session.sessionId ?? chatFile.replace(".json", ""),
|
|
15049
|
+
project: projectRoot,
|
|
15050
|
+
projectLabel: projectLabel(projectRoot),
|
|
15051
|
+
firstPrompt,
|
|
15052
|
+
startTime,
|
|
15053
|
+
toolCalls,
|
|
15054
|
+
blockedCalls,
|
|
15055
|
+
costUSD,
|
|
15056
|
+
hasSnapshot: false,
|
|
15057
|
+
modifiedFiles,
|
|
15058
|
+
agent: "gemini"
|
|
15059
|
+
});
|
|
15060
|
+
}
|
|
15061
|
+
}
|
|
15062
|
+
return summaries;
|
|
15063
|
+
}
|
|
14702
15064
|
function buildSessions(days, historyPath) {
|
|
14703
15065
|
const hPath = historyPath ?? import_path37.default.join(import_os30.default.homedir(), ".claude", "history.jsonl");
|
|
14704
15066
|
let historyRaw;
|
|
@@ -14749,9 +15111,13 @@ function buildSessions(days, historyPath) {
|
|
|
14749
15111
|
blockedCalls,
|
|
14750
15112
|
costUSD,
|
|
14751
15113
|
hasSnapshot,
|
|
14752
|
-
modifiedFiles
|
|
15114
|
+
modifiedFiles,
|
|
15115
|
+
agent: "claude"
|
|
14753
15116
|
});
|
|
14754
15117
|
}
|
|
15118
|
+
if (!historyPath) {
|
|
15119
|
+
summaries.push(...buildGeminiSessions(days, allAuditEntries));
|
|
15120
|
+
}
|
|
14755
15121
|
summaries.sort((a, b) => a.startTime > b.startTime ? -1 : 1);
|
|
14756
15122
|
return summaries;
|
|
14757
15123
|
}
|
|
@@ -14863,9 +15229,9 @@ function renderSummary(summaries) {
|
|
|
14863
15229
|
const maxGroup = Math.max(...Object.values(groups));
|
|
14864
15230
|
for (const [label, count] of Object.entries(groups)) {
|
|
14865
15231
|
if (count === 0) continue;
|
|
14866
|
-
const
|
|
15232
|
+
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
14867
15233
|
console.log(
|
|
14868
|
-
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk22.default.white(String(count).padStart(4)) + import_chalk22.default.dim(` (${String(
|
|
15234
|
+
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + import_chalk22.default.white(String(count).padStart(4)) + import_chalk22.default.dim(` (${String(pct)}%)`)
|
|
14869
15235
|
);
|
|
14870
15236
|
}
|
|
14871
15237
|
console.log("");
|
|
@@ -14907,8 +15273,9 @@ function renderList(summaries, totalCost) {
|
|
|
14907
15273
|
const cost = s.costUSD > 0 ? import_chalk22.default.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
14908
15274
|
const blocked = s.blockedCalls.length > 0 ? import_chalk22.default.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
14909
15275
|
const snap = s.hasSnapshot ? import_chalk22.default.green(" \u{1F4F8}") : "";
|
|
15276
|
+
const agentBadge = s.agent === "gemini" ? import_chalk22.default.blue(" [Gemini]") : import_chalk22.default.cyan(" [Claude]");
|
|
14910
15277
|
const sid = import_chalk22.default.dim(" " + s.sessionId.slice(0, 8));
|
|
14911
|
-
console.log(` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${sid}`);
|
|
15278
|
+
console.log(` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}`);
|
|
14912
15279
|
}
|
|
14913
15280
|
console.log("");
|
|
14914
15281
|
console.log(
|
|
@@ -14923,6 +15290,10 @@ function renderDetail(s) {
|
|
|
14923
15290
|
import_chalk22.default.bold(" Prompt ") + import_chalk22.default.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
14924
15291
|
);
|
|
14925
15292
|
console.log(import_chalk22.default.bold(" Project ") + import_chalk22.default.white(s.projectLabel));
|
|
15293
|
+
if (s.agent) {
|
|
15294
|
+
const agentLabel2 = s.agent === "gemini" ? import_chalk22.default.blue("Gemini CLI") : import_chalk22.default.cyan("Claude Code");
|
|
15295
|
+
console.log(import_chalk22.default.bold(" Agent ") + agentLabel2);
|
|
15296
|
+
}
|
|
14926
15297
|
console.log(import_chalk22.default.bold(" When ") + import_chalk22.default.white(fmtDateTime(s.startTime)));
|
|
14927
15298
|
if (s.costUSD > 0)
|
|
14928
15299
|
console.log(import_chalk22.default.bold(" Cost ") + import_chalk22.default.yellow("~" + fmtCost3(s.costUSD)));
|
package/dist/cli.mjs
CHANGED
|
@@ -97,7 +97,7 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
97
97
|
}
|
|
98
98
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
99
99
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
100
|
-
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
100
|
+
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
101
101
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
102
102
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
103
103
|
tool: toolName,
|
|
@@ -8627,10 +8627,10 @@ function bold(s) {
|
|
|
8627
8627
|
function color(c, s) {
|
|
8628
8628
|
return `${c}${s}${RESET3}`;
|
|
8629
8629
|
}
|
|
8630
|
-
function progressBar(
|
|
8631
|
-
const filled = Math.round(Math.min(
|
|
8630
|
+
function progressBar(pct, warnAt = 70, critAt = 85) {
|
|
8631
|
+
const filled = Math.round(Math.min(pct, 100) / 100 * BAR_WIDTH);
|
|
8632
8632
|
const bar = BAR_FILLED.repeat(filled) + BAR_EMPTY.repeat(BAR_WIDTH - filled);
|
|
8633
|
-
const c =
|
|
8633
|
+
const c = pct >= critAt ? RED2 : pct >= warnAt ? YELLOW2 : GREEN2;
|
|
8634
8634
|
return `${c}${bar}${RESET3}`;
|
|
8635
8635
|
}
|
|
8636
8636
|
function formatTimeLeft(resetsAt) {
|
|
@@ -8846,15 +8846,15 @@ function renderContextLine(stdin) {
|
|
|
8846
8846
|
}
|
|
8847
8847
|
const rl = stdin.rate_limits;
|
|
8848
8848
|
if (rl?.five_hour?.used_percentage !== void 0) {
|
|
8849
|
-
const
|
|
8850
|
-
const bar = progressBar(
|
|
8849
|
+
const pct = Math.round(rl.five_hour.used_percentage);
|
|
8850
|
+
const bar = progressBar(pct, 60, 80);
|
|
8851
8851
|
const left = formatTimeLeft(rl.five_hour.resets_at);
|
|
8852
|
-
parts.push(`${dim("\u2502")} 5h ${bar} ${
|
|
8852
|
+
parts.push(`${dim("\u2502")} 5h ${bar} ${pct}%${left}`);
|
|
8853
8853
|
}
|
|
8854
8854
|
if (rl?.seven_day?.used_percentage !== void 0) {
|
|
8855
|
-
const
|
|
8856
|
-
const bar = progressBar(
|
|
8857
|
-
parts.push(`${dim("\u2502")} 7d ${bar} ${
|
|
8855
|
+
const pct = Math.round(rl.seven_day.used_percentage);
|
|
8856
|
+
const bar = progressBar(pct, 60, 80);
|
|
8857
|
+
parts.push(`${dim("\u2502")} 7d ${bar} ${pct}%`);
|
|
8858
8858
|
}
|
|
8859
8859
|
if (parts.length === 0) return null;
|
|
8860
8860
|
return parts.join(" ");
|
|
@@ -11723,8 +11723,8 @@ function buildTestTimestamps(allEntries) {
|
|
|
11723
11723
|
return testTs;
|
|
11724
11724
|
}
|
|
11725
11725
|
function isTestEntry(entry, testTs) {
|
|
11726
|
-
if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
|
|
11727
11726
|
if (entry.testRun === true) return true;
|
|
11727
|
+
if (entry.tool !== "Bash" && entry.tool !== "bash") return false;
|
|
11728
11728
|
const cmd = entry.args?.command;
|
|
11729
11729
|
if (typeof cmd === "string") return TEST_COMMAND_RE3.test(cmd);
|
|
11730
11730
|
const t = new Date(entry.ts).getTime();
|
|
@@ -11782,10 +11782,6 @@ function colorBar(value, max, width) {
|
|
|
11782
11782
|
const filled = Math.max(1, Math.round(max > 0 ? value / max * width : 0));
|
|
11783
11783
|
return chalk9.cyan(s.slice(0, filled)) + chalk9.dim(s.slice(filled));
|
|
11784
11784
|
}
|
|
11785
|
-
function pct(num3, total) {
|
|
11786
|
-
if (total === 0) return "\u2013";
|
|
11787
|
-
return Math.round(num3 / total * 100) + "%";
|
|
11788
|
-
}
|
|
11789
11785
|
function fmtDate(d) {
|
|
11790
11786
|
const date = typeof d === "string" ? /* @__PURE__ */ new Date(d + "T12:00:00") : d;
|
|
11791
11787
|
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
@@ -11956,9 +11952,12 @@ function registerReportCommand(program2) {
|
|
|
11956
11952
|
`));
|
|
11957
11953
|
return;
|
|
11958
11954
|
}
|
|
11959
|
-
let
|
|
11960
|
-
let
|
|
11961
|
-
let
|
|
11955
|
+
let userApproved = 0;
|
|
11956
|
+
let userDenied = 0;
|
|
11957
|
+
let timedOut = 0;
|
|
11958
|
+
let hardBlocked = 0;
|
|
11959
|
+
let dlpBlocked = 0;
|
|
11960
|
+
let observeDlp = 0;
|
|
11962
11961
|
let loopHits = 0;
|
|
11963
11962
|
let testPasses = 0;
|
|
11964
11963
|
let testFails = 0;
|
|
@@ -11971,9 +11970,16 @@ function registerReportCommand(program2) {
|
|
|
11971
11970
|
for (const e of entries) {
|
|
11972
11971
|
const allow = isAllow(e.decision);
|
|
11973
11972
|
const dateKey = e.ts.slice(0, 10);
|
|
11974
|
-
|
|
11975
|
-
|
|
11976
|
-
|
|
11973
|
+
const userInteracted = e.source === "daemon";
|
|
11974
|
+
if (userInteracted) {
|
|
11975
|
+
if (allow) userApproved++;
|
|
11976
|
+
else userDenied++;
|
|
11977
|
+
} else if (!allow) {
|
|
11978
|
+
if (e.checkedBy === "timeout") timedOut++;
|
|
11979
|
+
else if (e.checkedBy === "observe-mode-dlp-would-block") observeDlp++;
|
|
11980
|
+
else if (isDlp(e.checkedBy)) dlpBlocked++;
|
|
11981
|
+
else if (e.checkedBy !== "loop-detected") hardBlocked++;
|
|
11982
|
+
}
|
|
11977
11983
|
if (e.checkedBy === "loop-detected") loopHits++;
|
|
11978
11984
|
const t = toolMap.get(e.tool) ?? { calls: 0, blocked: 0 };
|
|
11979
11985
|
t.calls++;
|
|
@@ -12024,25 +12030,84 @@ function registerReportCommand(program2) {
|
|
|
12024
12030
|
" " + chalk9.bold.cyan("\u{1F6E1} node9 Report") + chalk9.dim(" \xB7 ") + chalk9.white(periodLabel[period]) + chalk9.dim(` ${fmtDate(start)} \u2013 ${fmtDate(end)}`) + chalk9.dim(` ${num(total)} events`) + (excludeTests ? chalk9.dim(` \u2013tests (\u2013${filteredTestCount})`) : "")
|
|
12025
12031
|
);
|
|
12026
12032
|
console.log(" " + line);
|
|
12027
|
-
|
|
12028
|
-
const
|
|
12029
|
-
const dlpLabel = dlpHits > 0 ? chalk9.yellow(`\u{1F6A8} ${dlpHits} DLP hits`) : chalk9.dim("\u{1F6A8} 0 DLP hits");
|
|
12030
|
-
const loopLabel = loopHits > 0 ? chalk9.yellow(`\u{1F504} ${loopHits} loops`) : chalk9.dim("\u{1F504} 0 loops");
|
|
12031
|
-
const currentRate = total > 0 ? blocked / total : 0;
|
|
12033
|
+
const totalBlocked = timedOut + hardBlocked + dlpBlocked + loopHits + userDenied;
|
|
12034
|
+
const currentRate = total > 0 ? totalBlocked / total : 0;
|
|
12032
12035
|
const trendLabel = (() => {
|
|
12033
|
-
if (priorBlockRate === null) return
|
|
12036
|
+
if (priorBlockRate === null) return "";
|
|
12034
12037
|
const delta = Math.round((currentRate - priorBlockRate) * 100);
|
|
12035
|
-
|
|
12036
|
-
return chalk9.
|
|
12038
|
+
if (delta === 0) return "";
|
|
12039
|
+
return " " + (delta > 0 ? chalk9.red(`\u25B2${delta}%`) : chalk9.green(`\u25BC${Math.abs(delta)}%`)) + chalk9.dim(" vs prior");
|
|
12037
12040
|
})();
|
|
12038
12041
|
const reads = toolMap.get("Read")?.calls ?? 0;
|
|
12039
12042
|
const edits = (toolMap.get("Edit")?.calls ?? 0) + (toolMap.get("Write")?.calls ?? 0);
|
|
12040
12043
|
const ratioLabel = reads > 0 ? chalk9.dim(`edit/read ${(edits / reads).toFixed(1)}`) : chalk9.dim("edit/read \u2013");
|
|
12041
12044
|
const testLabel = testPasses + testFails > 0 ? chalk9.dim("tests ") + chalk9.green(`${testPasses}\u2713`) + (testFails > 0 ? " " + chalk9.red(`${testFails}\u2717`) : "") : chalk9.dim("tests \u2013");
|
|
12045
|
+
console.log("");
|
|
12046
|
+
console.log(" " + chalk9.bold("Protection Summary"));
|
|
12047
|
+
console.log(" " + chalk9.dim("\u2500".repeat(Math.min(50, W - 4))));
|
|
12042
12048
|
console.log(
|
|
12043
|
-
" " + chalk9.
|
|
12049
|
+
" " + chalk9.dim("Intercepted") + " " + chalk9.white(num(total)) + chalk9.dim(" tool calls")
|
|
12050
|
+
);
|
|
12051
|
+
console.log("");
|
|
12052
|
+
const COL1 = 18;
|
|
12053
|
+
const summaryRow = (icon, label, count, note, colorFn = (s) => s) => {
|
|
12054
|
+
const countStr = colorFn(num(count));
|
|
12055
|
+
const noteStr = note ? chalk9.dim(" " + note) : "";
|
|
12056
|
+
console.log(" " + icon + " " + chalk9.white(label.padEnd(COL1)) + countStr + noteStr);
|
|
12057
|
+
};
|
|
12058
|
+
summaryRow(
|
|
12059
|
+
userApproved > 0 ? chalk9.green("\u2705") : chalk9.dim("\u2705"),
|
|
12060
|
+
"User approved",
|
|
12061
|
+
userApproved,
|
|
12062
|
+
userApproved === 0 ? "no popups this period" : void 0,
|
|
12063
|
+
userApproved > 0 ? (s) => chalk9.green(s) : (s) => chalk9.dim(s)
|
|
12064
|
+
);
|
|
12065
|
+
summaryRow(
|
|
12066
|
+
userDenied > 0 ? chalk9.red("\u{1F6AB}") : chalk9.dim("\u{1F6AB}"),
|
|
12067
|
+
"User denied",
|
|
12068
|
+
userDenied,
|
|
12069
|
+
void 0,
|
|
12070
|
+
userDenied > 0 ? (s) => chalk9.red(s) : (s) => chalk9.dim(s)
|
|
12044
12071
|
);
|
|
12045
|
-
|
|
12072
|
+
summaryRow(
|
|
12073
|
+
timedOut > 0 ? chalk9.yellow("\u23F1") : chalk9.dim("\u23F1"),
|
|
12074
|
+
"Timed out",
|
|
12075
|
+
timedOut,
|
|
12076
|
+
timedOut > 0 ? "no approval response" : void 0,
|
|
12077
|
+
timedOut > 0 ? (s) => chalk9.yellow(s) : (s) => chalk9.dim(s)
|
|
12078
|
+
);
|
|
12079
|
+
summaryRow(
|
|
12080
|
+
hardBlocked > 0 ? chalk9.red("\u{1F6D1}") : chalk9.dim("\u{1F6D1}"),
|
|
12081
|
+
"Auto-blocked",
|
|
12082
|
+
hardBlocked,
|
|
12083
|
+
void 0,
|
|
12084
|
+
hardBlocked > 0 ? (s) => chalk9.red(s) : (s) => chalk9.dim(s)
|
|
12085
|
+
);
|
|
12086
|
+
summaryRow(
|
|
12087
|
+
dlpBlocked > 0 ? chalk9.yellow("\u{1F6A8}") : chalk9.dim("\u{1F6A8}"),
|
|
12088
|
+
"DLP blocked",
|
|
12089
|
+
dlpBlocked,
|
|
12090
|
+
void 0,
|
|
12091
|
+
dlpBlocked > 0 ? (s) => chalk9.yellow(s) : (s) => chalk9.dim(s)
|
|
12092
|
+
);
|
|
12093
|
+
summaryRow(
|
|
12094
|
+
observeDlp > 0 ? chalk9.blue("\u{1F441}") : chalk9.dim("\u{1F441}"),
|
|
12095
|
+
"DLP (observe)",
|
|
12096
|
+
observeDlp,
|
|
12097
|
+
observeDlp > 0 ? "would-block in strict mode" : void 0,
|
|
12098
|
+
observeDlp > 0 ? (s) => chalk9.blue(s) : (s) => chalk9.dim(s)
|
|
12099
|
+
);
|
|
12100
|
+
summaryRow(
|
|
12101
|
+
loopHits > 0 ? chalk9.yellow("\u{1F504}") : chalk9.dim("\u{1F504}"),
|
|
12102
|
+
"Loops detected",
|
|
12103
|
+
loopHits,
|
|
12104
|
+
void 0,
|
|
12105
|
+
loopHits > 0 ? (s) => chalk9.yellow(s) : (s) => chalk9.dim(s)
|
|
12106
|
+
);
|
|
12107
|
+
if (trendLabel || ratioLabel || testPasses + testFails > 0) {
|
|
12108
|
+
console.log("");
|
|
12109
|
+
console.log(" " + ratioLabel + " " + testLabel + trendLabel);
|
|
12110
|
+
}
|
|
12046
12111
|
console.log("");
|
|
12047
12112
|
const toolHeaderRaw = "Top Tools";
|
|
12048
12113
|
const blockHeaderRaw = "Top Blocks";
|
|
@@ -14223,6 +14288,22 @@ function claudeModelPrice2(model) {
|
|
|
14223
14288
|
}
|
|
14224
14289
|
return null;
|
|
14225
14290
|
}
|
|
14291
|
+
var GEMINI_PRICING = {
|
|
14292
|
+
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
14293
|
+
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
14294
|
+
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
14295
|
+
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
14296
|
+
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
14297
|
+
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
14298
|
+
};
|
|
14299
|
+
function geminiModelPrice(model) {
|
|
14300
|
+
const base = model.replace(/-preview$/, "").replace(/-exp$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
14301
|
+
for (const [key, p] of Object.entries(GEMINI_PRICING)) {
|
|
14302
|
+
if (base === key || base.startsWith(key)) return p;
|
|
14303
|
+
}
|
|
14304
|
+
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
14305
|
+
return null;
|
|
14306
|
+
}
|
|
14226
14307
|
function num2(n) {
|
|
14227
14308
|
return n.toLocaleString();
|
|
14228
14309
|
}
|
|
@@ -14365,7 +14446,8 @@ function scanClaudeHistory(startDate) {
|
|
|
14365
14446
|
redactedSample: dlpMatch.redactedSample,
|
|
14366
14447
|
toolName,
|
|
14367
14448
|
timestamp: entry.timestamp ?? "",
|
|
14368
|
-
project: projLabel
|
|
14449
|
+
project: projLabel,
|
|
14450
|
+
agent: "claude"
|
|
14369
14451
|
});
|
|
14370
14452
|
}
|
|
14371
14453
|
}
|
|
@@ -14384,7 +14466,135 @@ function scanClaudeHistory(startDate) {
|
|
|
14384
14466
|
toolName,
|
|
14385
14467
|
input,
|
|
14386
14468
|
timestamp: entry.timestamp ?? "",
|
|
14387
|
-
project: projLabel
|
|
14469
|
+
project: projLabel,
|
|
14470
|
+
agent: "claude"
|
|
14471
|
+
});
|
|
14472
|
+
}
|
|
14473
|
+
break;
|
|
14474
|
+
}
|
|
14475
|
+
}
|
|
14476
|
+
}
|
|
14477
|
+
}
|
|
14478
|
+
}
|
|
14479
|
+
return result;
|
|
14480
|
+
}
|
|
14481
|
+
function scanGeminiHistory(startDate) {
|
|
14482
|
+
const tmpDir = path36.join(os29.homedir(), ".gemini", "tmp");
|
|
14483
|
+
const result = {
|
|
14484
|
+
filesScanned: 0,
|
|
14485
|
+
sessions: 0,
|
|
14486
|
+
totalToolCalls: 0,
|
|
14487
|
+
bashCalls: 0,
|
|
14488
|
+
findings: [],
|
|
14489
|
+
dlpFindings: [],
|
|
14490
|
+
totalCostUSD: 0,
|
|
14491
|
+
firstDate: null,
|
|
14492
|
+
lastDate: null
|
|
14493
|
+
};
|
|
14494
|
+
if (!fs33.existsSync(tmpDir)) return result;
|
|
14495
|
+
let slugDirs;
|
|
14496
|
+
try {
|
|
14497
|
+
slugDirs = fs33.readdirSync(tmpDir);
|
|
14498
|
+
} catch {
|
|
14499
|
+
return result;
|
|
14500
|
+
}
|
|
14501
|
+
const ruleSources = buildRuleSources();
|
|
14502
|
+
for (const slug of slugDirs) {
|
|
14503
|
+
const slugPath = path36.join(tmpDir, slug);
|
|
14504
|
+
try {
|
|
14505
|
+
if (!fs33.statSync(slugPath).isDirectory()) continue;
|
|
14506
|
+
} catch {
|
|
14507
|
+
continue;
|
|
14508
|
+
}
|
|
14509
|
+
let projLabel = slug;
|
|
14510
|
+
try {
|
|
14511
|
+
projLabel = fs33.readFileSync(path36.join(slugPath, ".project_root"), "utf-8").trim().replace(os29.homedir(), "~").slice(0, 40);
|
|
14512
|
+
} catch {
|
|
14513
|
+
}
|
|
14514
|
+
const chatsDir = path36.join(slugPath, "chats");
|
|
14515
|
+
if (!fs33.existsSync(chatsDir)) continue;
|
|
14516
|
+
let chatFiles;
|
|
14517
|
+
try {
|
|
14518
|
+
chatFiles = fs33.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
14519
|
+
} catch {
|
|
14520
|
+
continue;
|
|
14521
|
+
}
|
|
14522
|
+
for (const chatFile of chatFiles) {
|
|
14523
|
+
result.filesScanned++;
|
|
14524
|
+
let raw;
|
|
14525
|
+
try {
|
|
14526
|
+
raw = fs33.readFileSync(path36.join(chatsDir, chatFile), "utf-8");
|
|
14527
|
+
} catch {
|
|
14528
|
+
continue;
|
|
14529
|
+
}
|
|
14530
|
+
let session;
|
|
14531
|
+
try {
|
|
14532
|
+
session = JSON.parse(raw);
|
|
14533
|
+
} catch {
|
|
14534
|
+
continue;
|
|
14535
|
+
}
|
|
14536
|
+
result.sessions++;
|
|
14537
|
+
for (const msg of session.messages ?? []) {
|
|
14538
|
+
if (msg.type !== "gemini") continue;
|
|
14539
|
+
if (startDate && msg.timestamp && new Date(msg.timestamp) < startDate) continue;
|
|
14540
|
+
if (msg.timestamp) {
|
|
14541
|
+
if (!result.firstDate || msg.timestamp < result.firstDate)
|
|
14542
|
+
result.firstDate = msg.timestamp;
|
|
14543
|
+
if (!result.lastDate || msg.timestamp > result.lastDate) result.lastDate = msg.timestamp;
|
|
14544
|
+
}
|
|
14545
|
+
const tokens = msg.tokens;
|
|
14546
|
+
const model = msg.model;
|
|
14547
|
+
if (tokens && model) {
|
|
14548
|
+
const p = geminiModelPrice(model);
|
|
14549
|
+
if (p) {
|
|
14550
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
14551
|
+
result.totalCostUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
|
|
14552
|
+
}
|
|
14553
|
+
}
|
|
14554
|
+
for (const tc of msg.toolCalls ?? []) {
|
|
14555
|
+
result.totalToolCalls++;
|
|
14556
|
+
const toolName = tc.name ?? "";
|
|
14557
|
+
const toolNameLower = toolName.toLowerCase();
|
|
14558
|
+
const input = tc.args ?? {};
|
|
14559
|
+
if (toolNameLower === "run_shell_command" || toolNameLower === "shell") {
|
|
14560
|
+
result.bashCalls++;
|
|
14561
|
+
}
|
|
14562
|
+
const rawCmd = String(input.command ?? "").trimStart();
|
|
14563
|
+
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd))
|
|
14564
|
+
continue;
|
|
14565
|
+
const dlpMatch = scanArgs(input);
|
|
14566
|
+
if (dlpMatch) {
|
|
14567
|
+
const isDupe = result.dlpFindings.some(
|
|
14568
|
+
(f) => f.patternName === dlpMatch.patternName && f.redactedSample === dlpMatch.redactedSample && f.project === projLabel
|
|
14569
|
+
);
|
|
14570
|
+
if (!isDupe) {
|
|
14571
|
+
result.dlpFindings.push({
|
|
14572
|
+
patternName: dlpMatch.patternName,
|
|
14573
|
+
redactedSample: dlpMatch.redactedSample,
|
|
14574
|
+
toolName,
|
|
14575
|
+
timestamp: msg.timestamp ?? "",
|
|
14576
|
+
project: projLabel,
|
|
14577
|
+
agent: "gemini"
|
|
14578
|
+
});
|
|
14579
|
+
}
|
|
14580
|
+
}
|
|
14581
|
+
for (const source of ruleSources) {
|
|
14582
|
+
const { rule } = source;
|
|
14583
|
+
if (rule.verdict === "allow") continue;
|
|
14584
|
+
if (rule.tool && !matchesPattern(toolNameLower, rule.tool)) continue;
|
|
14585
|
+
if (!evaluateSmartConditions(input, rule)) continue;
|
|
14586
|
+
const inputPreview = preview(input, 120);
|
|
14587
|
+
const isDupe = result.findings.some(
|
|
14588
|
+
(f) => f.source.rule.name === rule.name && preview(f.input, 120) === inputPreview && f.project === projLabel
|
|
14589
|
+
);
|
|
14590
|
+
if (!isDupe) {
|
|
14591
|
+
result.findings.push({
|
|
14592
|
+
source,
|
|
14593
|
+
toolName,
|
|
14594
|
+
input,
|
|
14595
|
+
timestamp: msg.timestamp ?? "",
|
|
14596
|
+
project: projLabel,
|
|
14597
|
+
agent: "gemini"
|
|
14388
14598
|
});
|
|
14389
14599
|
}
|
|
14390
14600
|
break;
|
|
@@ -14395,6 +14605,21 @@ function scanClaudeHistory(startDate) {
|
|
|
14395
14605
|
}
|
|
14396
14606
|
return result;
|
|
14397
14607
|
}
|
|
14608
|
+
function mergeScans(a, b) {
|
|
14609
|
+
const dates = [a.firstDate, b.firstDate].filter(Boolean);
|
|
14610
|
+
const lastDates = [a.lastDate, b.lastDate].filter(Boolean);
|
|
14611
|
+
return {
|
|
14612
|
+
filesScanned: a.filesScanned + b.filesScanned,
|
|
14613
|
+
sessions: a.sessions + b.sessions,
|
|
14614
|
+
totalToolCalls: a.totalToolCalls + b.totalToolCalls,
|
|
14615
|
+
bashCalls: a.bashCalls + b.bashCalls,
|
|
14616
|
+
findings: [...a.findings, ...b.findings],
|
|
14617
|
+
dlpFindings: [...a.dlpFindings, ...b.dlpFindings],
|
|
14618
|
+
totalCostUSD: a.totalCostUSD + b.totalCostUSD,
|
|
14619
|
+
firstDate: dates.length ? dates.sort()[0] : null,
|
|
14620
|
+
lastDate: lastDates.length ? lastDates.sort().at(-1) : null
|
|
14621
|
+
};
|
|
14622
|
+
}
|
|
14398
14623
|
function registerScanCommand(program2) {
|
|
14399
14624
|
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 shield", "5").action((options) => {
|
|
14400
14625
|
const topN = Math.max(1, parseInt(options.top, 10) || 5);
|
|
@@ -14407,23 +14632,25 @@ function registerScanCommand(program2) {
|
|
|
14407
14632
|
console.log("");
|
|
14408
14633
|
console.log(chalk21.cyan.bold("\u{1F50D} node9 scan") + chalk21.dim(" \u2014 what would node9 catch?"));
|
|
14409
14634
|
console.log("");
|
|
14410
|
-
const projectsDir = path36.join(os29.homedir(), ".claude", "projects");
|
|
14411
|
-
if (!fs33.existsSync(projectsDir)) {
|
|
14412
|
-
console.log(chalk21.yellow(" No Claude history found at ~/.claude/projects/"));
|
|
14413
|
-
console.log(chalk21.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
14414
|
-
return;
|
|
14415
|
-
}
|
|
14416
14635
|
process.stdout.write(chalk21.dim(" Scanning\u2026"));
|
|
14417
|
-
const
|
|
14636
|
+
const claudeScan = scanClaudeHistory(startDate);
|
|
14637
|
+
const geminiScan = scanGeminiHistory(startDate);
|
|
14638
|
+
const scan = mergeScans(claudeScan, geminiScan);
|
|
14418
14639
|
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
14419
14640
|
if (scan.filesScanned === 0) {
|
|
14420
|
-
console.log(chalk21.yellow(" No
|
|
14641
|
+
console.log(chalk21.yellow(" No session history found."));
|
|
14642
|
+
console.log(
|
|
14643
|
+
chalk21.gray(
|
|
14644
|
+
" Supported: Claude Code (~/.claude/projects/) \xB7 Gemini CLI (~/.gemini/tmp/)\n"
|
|
14645
|
+
)
|
|
14646
|
+
);
|
|
14421
14647
|
return;
|
|
14422
14648
|
}
|
|
14423
14649
|
const rangeLabel = options.all ? chalk21.dim("all time") : chalk21.dim(`last ${options.days ?? 90} days`);
|
|
14424
14650
|
const dateRange = scan.firstDate && scan.lastDate ? chalk21.dim(` ${fmtTs(scan.firstDate)} \u2013 ${fmtTs(scan.lastDate)}`) : "";
|
|
14651
|
+
const sessionBreakdown = claudeScan.sessions > 0 && geminiScan.sessions > 0 ? chalk21.dim("(") + chalk21.cyan(String(claudeScan.sessions)) + chalk21.dim(" Claude \xB7 ") + chalk21.blue(String(geminiScan.sessions)) + chalk21.dim(" Gemini)") : "";
|
|
14425
14652
|
console.log(
|
|
14426
|
-
" " + chalk21.white(num2(scan.sessions)) + chalk21.dim(" sessions ") + chalk21.white(num2(scan.totalToolCalls)) + chalk21.dim(" tool calls ") + chalk21.white(num2(scan.bashCalls)) + chalk21.dim(" bash commands ") + rangeLabel + dateRange
|
|
14653
|
+
" " + chalk21.white(num2(scan.sessions)) + chalk21.dim(" sessions ") + sessionBreakdown + (sessionBreakdown ? " " : "") + chalk21.white(num2(scan.totalToolCalls)) + chalk21.dim(" tool calls ") + chalk21.white(num2(scan.bashCalls)) + chalk21.dim(" bash commands ") + rangeLabel + dateRange
|
|
14427
14654
|
);
|
|
14428
14655
|
console.log("");
|
|
14429
14656
|
const byShield = /* @__PURE__ */ new Map();
|
|
@@ -14475,8 +14702,9 @@ function registerScanCommand(program2) {
|
|
|
14475
14702
|
for (const f of shown) {
|
|
14476
14703
|
const ts = f.timestamp ? chalk21.dim(fmtTs(f.timestamp) + " ") : "";
|
|
14477
14704
|
const proj = chalk21.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
14478
|
-
const
|
|
14479
|
-
|
|
14705
|
+
const agentBadge = f.agent === "gemini" ? chalk21.blue("[Gemini] ") : chalk21.cyan("[Claude] ");
|
|
14706
|
+
const cmd = chalk21.gray(preview(f.input, 45));
|
|
14707
|
+
console.log(` ${ts}${proj}${agentBadge}${cmd}`);
|
|
14480
14708
|
}
|
|
14481
14709
|
if (ruleFindings.length > topN) {
|
|
14482
14710
|
console.log(
|
|
@@ -14500,8 +14728,9 @@ function registerScanCommand(program2) {
|
|
|
14500
14728
|
for (const f of shownDlp) {
|
|
14501
14729
|
const ts = f.timestamp ? chalk21.dim(fmtTs(f.timestamp) + " ") : "";
|
|
14502
14730
|
const proj = chalk21.dim(f.project.slice(0, 22).padEnd(22) + " ");
|
|
14731
|
+
const agentBadge = f.agent === "gemini" ? chalk21.blue("[Gemini] ") : chalk21.cyan("[Claude] ");
|
|
14503
14732
|
console.log(
|
|
14504
|
-
` ${ts}${proj}` + chalk21.yellow(f.patternName) + chalk21.dim(" ") + chalk21.gray(f.redactedSample)
|
|
14733
|
+
` ${ts}${proj}${agentBadge}` + chalk21.yellow(f.patternName) + chalk21.dim(" ") + chalk21.gray(f.redactedSample)
|
|
14505
14734
|
);
|
|
14506
14735
|
}
|
|
14507
14736
|
if (scan.dlpFindings.length > topN) {
|
|
@@ -14516,7 +14745,7 @@ function registerScanCommand(program2) {
|
|
|
14516
14745
|
}
|
|
14517
14746
|
if (scan.totalCostUSD > 0) {
|
|
14518
14747
|
console.log(
|
|
14519
|
-
" " + chalk21.bold("
|
|
14748
|
+
" " + chalk21.bold("Agent spend:") + " " + chalk21.yellow(fmtCost2(scan.totalCostUSD)) + chalk21.dim(" (for per-period breakdown: node9 report)")
|
|
14520
14749
|
);
|
|
14521
14750
|
console.log("");
|
|
14522
14751
|
}
|
|
@@ -14560,6 +14789,22 @@ function modelPrice(model) {
|
|
|
14560
14789
|
}
|
|
14561
14790
|
return null;
|
|
14562
14791
|
}
|
|
14792
|
+
var GEMINI_PRICING2 = {
|
|
14793
|
+
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
14794
|
+
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
14795
|
+
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
14796
|
+
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
14797
|
+
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
14798
|
+
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
14799
|
+
};
|
|
14800
|
+
function geminiModelPrice2(model) {
|
|
14801
|
+
const base = model.replace(/-preview$/, "").replace(/-exp$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
14802
|
+
for (const [key, p] of Object.entries(GEMINI_PRICING2)) {
|
|
14803
|
+
if (base === key || base.startsWith(key)) return p;
|
|
14804
|
+
}
|
|
14805
|
+
if (base.includes("flash")) return GEMINI_PRICING2["gemini-2.0-flash"];
|
|
14806
|
+
return null;
|
|
14807
|
+
}
|
|
14563
14808
|
function encodeProjectPath(projectPath) {
|
|
14564
14809
|
return projectPath.replace(/\//g, "-");
|
|
14565
14810
|
}
|
|
@@ -14675,6 +14920,123 @@ function auditEntriesInWindow(entries, windowStart, windowEnd) {
|
|
|
14675
14920
|
}
|
|
14676
14921
|
return result;
|
|
14677
14922
|
}
|
|
14923
|
+
function buildGeminiSessions(days, allAuditEntries) {
|
|
14924
|
+
const tmpDir = path37.join(os30.homedir(), ".gemini", "tmp");
|
|
14925
|
+
if (!fs34.existsSync(tmpDir)) return [];
|
|
14926
|
+
const cutoff = days !== null ? (() => {
|
|
14927
|
+
const d = /* @__PURE__ */ new Date();
|
|
14928
|
+
d.setDate(d.getDate() - days);
|
|
14929
|
+
d.setHours(0, 0, 0, 0);
|
|
14930
|
+
return d;
|
|
14931
|
+
})() : null;
|
|
14932
|
+
let slugDirs;
|
|
14933
|
+
try {
|
|
14934
|
+
slugDirs = fs34.readdirSync(tmpDir);
|
|
14935
|
+
} catch {
|
|
14936
|
+
return [];
|
|
14937
|
+
}
|
|
14938
|
+
const summaries = [];
|
|
14939
|
+
for (const slug of slugDirs) {
|
|
14940
|
+
const slugPath = path37.join(tmpDir, slug);
|
|
14941
|
+
try {
|
|
14942
|
+
if (!fs34.statSync(slugPath).isDirectory()) continue;
|
|
14943
|
+
} catch {
|
|
14944
|
+
continue;
|
|
14945
|
+
}
|
|
14946
|
+
let projectRoot = path37.join(os30.homedir(), slug);
|
|
14947
|
+
try {
|
|
14948
|
+
projectRoot = fs34.readFileSync(path37.join(slugPath, ".project_root"), "utf-8").trim();
|
|
14949
|
+
} catch {
|
|
14950
|
+
}
|
|
14951
|
+
const chatsDir = path37.join(slugPath, "chats");
|
|
14952
|
+
if (!fs34.existsSync(chatsDir)) continue;
|
|
14953
|
+
let chatFiles;
|
|
14954
|
+
try {
|
|
14955
|
+
chatFiles = fs34.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
14956
|
+
} catch {
|
|
14957
|
+
continue;
|
|
14958
|
+
}
|
|
14959
|
+
for (const chatFile of chatFiles) {
|
|
14960
|
+
let raw;
|
|
14961
|
+
try {
|
|
14962
|
+
raw = fs34.readFileSync(path37.join(chatsDir, chatFile), "utf-8");
|
|
14963
|
+
} catch {
|
|
14964
|
+
continue;
|
|
14965
|
+
}
|
|
14966
|
+
let session;
|
|
14967
|
+
try {
|
|
14968
|
+
session = JSON.parse(raw);
|
|
14969
|
+
} catch {
|
|
14970
|
+
continue;
|
|
14971
|
+
}
|
|
14972
|
+
const startTime = session.startTime ?? "";
|
|
14973
|
+
if (!startTime) continue;
|
|
14974
|
+
if (cutoff && new Date(startTime) < cutoff) continue;
|
|
14975
|
+
let firstPrompt = "";
|
|
14976
|
+
for (const msg of session.messages ?? []) {
|
|
14977
|
+
if (msg.type === "user") {
|
|
14978
|
+
const content = msg.content;
|
|
14979
|
+
if (Array.isArray(content) && content[0]?.text) {
|
|
14980
|
+
firstPrompt = content[0].text;
|
|
14981
|
+
} else if (typeof content === "string") {
|
|
14982
|
+
firstPrompt = content;
|
|
14983
|
+
}
|
|
14984
|
+
break;
|
|
14985
|
+
}
|
|
14986
|
+
}
|
|
14987
|
+
const toolCalls = [];
|
|
14988
|
+
let costUSD = 0;
|
|
14989
|
+
const modifiedFiles = [];
|
|
14990
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
14991
|
+
let lastToolTs = "";
|
|
14992
|
+
for (const msg of session.messages ?? []) {
|
|
14993
|
+
if (msg.type !== "gemini") continue;
|
|
14994
|
+
const tokens = msg.tokens;
|
|
14995
|
+
const model = msg.model;
|
|
14996
|
+
if (tokens && model) {
|
|
14997
|
+
const p = geminiModelPrice2(model);
|
|
14998
|
+
if (p) {
|
|
14999
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
15000
|
+
costUSD += nonCached * p.i + tokens.cached * p.cr + tokens.output * p.o;
|
|
15001
|
+
}
|
|
15002
|
+
}
|
|
15003
|
+
for (const tc of msg.toolCalls ?? []) {
|
|
15004
|
+
const tool = tc.name ?? "";
|
|
15005
|
+
const input = tc.args ?? {};
|
|
15006
|
+
const ts = msg.timestamp ?? "";
|
|
15007
|
+
toolCalls.push({ tool, input, timestamp: ts });
|
|
15008
|
+
if (ts > lastToolTs) lastToolTs = ts;
|
|
15009
|
+
const toolLower = tool.toLowerCase();
|
|
15010
|
+
if (toolLower === "write_file" || toolLower === "edit_file" || toolLower === "create_file" || toolLower === "overwrite_file") {
|
|
15011
|
+
const fp = input.file_path ?? input.path ?? input.filename;
|
|
15012
|
+
if (typeof fp === "string" && !seenFiles.has(fp)) {
|
|
15013
|
+
seenFiles.add(fp);
|
|
15014
|
+
modifiedFiles.push(fp);
|
|
15015
|
+
}
|
|
15016
|
+
}
|
|
15017
|
+
}
|
|
15018
|
+
}
|
|
15019
|
+
const windowEnd = new Date(
|
|
15020
|
+
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
15021
|
+
).toISOString();
|
|
15022
|
+
const blockedCalls = auditEntriesInWindow(allAuditEntries, startTime, windowEnd);
|
|
15023
|
+
summaries.push({
|
|
15024
|
+
sessionId: session.sessionId ?? chatFile.replace(".json", ""),
|
|
15025
|
+
project: projectRoot,
|
|
15026
|
+
projectLabel: projectLabel(projectRoot),
|
|
15027
|
+
firstPrompt,
|
|
15028
|
+
startTime,
|
|
15029
|
+
toolCalls,
|
|
15030
|
+
blockedCalls,
|
|
15031
|
+
costUSD,
|
|
15032
|
+
hasSnapshot: false,
|
|
15033
|
+
modifiedFiles,
|
|
15034
|
+
agent: "gemini"
|
|
15035
|
+
});
|
|
15036
|
+
}
|
|
15037
|
+
}
|
|
15038
|
+
return summaries;
|
|
15039
|
+
}
|
|
14678
15040
|
function buildSessions(days, historyPath) {
|
|
14679
15041
|
const hPath = historyPath ?? path37.join(os30.homedir(), ".claude", "history.jsonl");
|
|
14680
15042
|
let historyRaw;
|
|
@@ -14725,9 +15087,13 @@ function buildSessions(days, historyPath) {
|
|
|
14725
15087
|
blockedCalls,
|
|
14726
15088
|
costUSD,
|
|
14727
15089
|
hasSnapshot,
|
|
14728
|
-
modifiedFiles
|
|
15090
|
+
modifiedFiles,
|
|
15091
|
+
agent: "claude"
|
|
14729
15092
|
});
|
|
14730
15093
|
}
|
|
15094
|
+
if (!historyPath) {
|
|
15095
|
+
summaries.push(...buildGeminiSessions(days, allAuditEntries));
|
|
15096
|
+
}
|
|
14731
15097
|
summaries.sort((a, b) => a.startTime > b.startTime ? -1 : 1);
|
|
14732
15098
|
return summaries;
|
|
14733
15099
|
}
|
|
@@ -14839,9 +15205,9 @@ function renderSummary(summaries) {
|
|
|
14839
15205
|
const maxGroup = Math.max(...Object.values(groups));
|
|
14840
15206
|
for (const [label, count] of Object.entries(groups)) {
|
|
14841
15207
|
if (count === 0) continue;
|
|
14842
|
-
const
|
|
15208
|
+
const pct = totalTools > 0 ? Math.round(count / totalTools * 100) : 0;
|
|
14843
15209
|
console.log(
|
|
14844
|
-
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk22.white(String(count).padStart(4)) + chalk22.dim(` (${String(
|
|
15210
|
+
" " + label.padEnd(6) + " " + colorBar2(count, maxGroup, W) + " " + chalk22.white(String(count).padStart(4)) + chalk22.dim(` (${String(pct)}%)`)
|
|
14845
15211
|
);
|
|
14846
15212
|
}
|
|
14847
15213
|
console.log("");
|
|
@@ -14883,8 +15249,9 @@ function renderList(summaries, totalCost) {
|
|
|
14883
15249
|
const cost = s.costUSD > 0 ? chalk22.dim(" " + fmtCost3(s.costUSD).padEnd(8)) : " ";
|
|
14884
15250
|
const blocked = s.blockedCalls.length > 0 ? chalk22.red(" \u{1F6D1} " + String(s.blockedCalls.length)) : "";
|
|
14885
15251
|
const snap = s.hasSnapshot ? chalk22.green(" \u{1F4F8}") : "";
|
|
15252
|
+
const agentBadge = s.agent === "gemini" ? chalk22.blue(" [Gemini]") : chalk22.cyan(" [Claude]");
|
|
14886
15253
|
const sid = chalk22.dim(" " + s.sessionId.slice(0, 8));
|
|
14887
|
-
console.log(` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${sid}`);
|
|
15254
|
+
console.log(` ${timeStr} ${prompt} ${tools}${cost}${blocked}${snap}${agentBadge}${sid}`);
|
|
14888
15255
|
}
|
|
14889
15256
|
console.log("");
|
|
14890
15257
|
console.log(
|
|
@@ -14899,6 +15266,10 @@ function renderDetail(s) {
|
|
|
14899
15266
|
chalk22.bold(" Prompt ") + chalk22.white(s.firstPrompt.replace(/\n/g, " ").slice(0, 120))
|
|
14900
15267
|
);
|
|
14901
15268
|
console.log(chalk22.bold(" Project ") + chalk22.white(s.projectLabel));
|
|
15269
|
+
if (s.agent) {
|
|
15270
|
+
const agentLabel2 = s.agent === "gemini" ? chalk22.blue("Gemini CLI") : chalk22.cyan("Claude Code");
|
|
15271
|
+
console.log(chalk22.bold(" Agent ") + agentLabel2);
|
|
15272
|
+
}
|
|
14902
15273
|
console.log(chalk22.bold(" When ") + chalk22.white(fmtDateTime(s.startTime)));
|
|
14903
15274
|
if (s.costUSD > 0)
|
|
14904
15275
|
console.log(chalk22.bold(" Cost ") + chalk22.yellow("~" + fmtCost3(s.costUSD)));
|
package/dist/index.js
CHANGED
|
@@ -116,7 +116,7 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
116
116
|
}
|
|
117
117
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
118
118
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
119
|
-
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
119
|
+
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
120
120
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
121
121
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
122
122
|
tool: toolName,
|
package/dist/index.mjs
CHANGED
|
@@ -96,7 +96,7 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
96
96
|
}
|
|
97
97
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
98
98
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
99
|
-
const testRun = isTestCall(toolName, args) ? { testRun: true } : {};
|
|
99
|
+
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
100
100
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
101
101
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
102
102
|
tool: toolName,
|