@node9/proxy 1.33.0 → 1.34.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 +1116 -1099
- package/dist/cli.mjs +1114 -1097
- package/dist/dashboard.mjs +335 -174
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -9252,1113 +9252,1139 @@ var init_setup = __esm({
|
|
|
9252
9252
|
}
|
|
9253
9253
|
});
|
|
9254
9254
|
|
|
9255
|
-
// src/
|
|
9256
|
-
|
|
9257
|
-
|
|
9255
|
+
// src/pricing/litellm.ts
|
|
9256
|
+
import fs14 from "fs";
|
|
9257
|
+
import path16 from "path";
|
|
9258
|
+
import os13 from "os";
|
|
9259
|
+
function normalizeModel(raw) {
|
|
9260
|
+
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
9258
9261
|
}
|
|
9259
|
-
function
|
|
9260
|
-
|
|
9262
|
+
function readCache() {
|
|
9263
|
+
try {
|
|
9264
|
+
const raw = JSON.parse(fs14.readFileSync(CACHE_FILE(), "utf-8"));
|
|
9265
|
+
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
9266
|
+
return null;
|
|
9267
|
+
}
|
|
9268
|
+
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
9269
|
+
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
9270
|
+
return raw.prices;
|
|
9271
|
+
} catch {
|
|
9272
|
+
return null;
|
|
9273
|
+
}
|
|
9261
9274
|
}
|
|
9262
|
-
function
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9275
|
+
function writeCache(prices) {
|
|
9276
|
+
try {
|
|
9277
|
+
const target = CACHE_FILE();
|
|
9278
|
+
const dir = path16.dirname(target);
|
|
9279
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
9280
|
+
const tmp = target + ".tmp";
|
|
9281
|
+
const body = {
|
|
9282
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9283
|
+
prices
|
|
9284
|
+
};
|
|
9285
|
+
fs14.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
9286
|
+
fs14.renameSync(tmp, target);
|
|
9287
|
+
} catch (err2) {
|
|
9288
|
+
try {
|
|
9289
|
+
fs14.appendFileSync(
|
|
9290
|
+
HOOK_DEBUG_LOG,
|
|
9291
|
+
`[pricing] cache write failed: ${err2.message}
|
|
9292
|
+
`
|
|
9293
|
+
);
|
|
9294
|
+
} catch {
|
|
9295
|
+
}
|
|
9280
9296
|
}
|
|
9281
9297
|
}
|
|
9282
|
-
function
|
|
9283
|
-
if (typeof
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9298
|
+
function tupleFromLiteLLM(entry) {
|
|
9299
|
+
if (!entry || typeof entry !== "object") return null;
|
|
9300
|
+
const e = entry;
|
|
9301
|
+
const num3 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
9302
|
+
const inCost = num3(e.input_cost_per_token);
|
|
9303
|
+
const outCost = num3(e.output_cost_per_token);
|
|
9304
|
+
if (inCost === 0 && outCost === 0) return null;
|
|
9305
|
+
return [
|
|
9306
|
+
inCost,
|
|
9307
|
+
outCost,
|
|
9308
|
+
num3(e.cache_creation_input_token_cost),
|
|
9309
|
+
num3(e.cache_read_input_token_cost)
|
|
9310
|
+
];
|
|
9311
|
+
}
|
|
9312
|
+
async function fetchLiteLLMPricing() {
|
|
9313
|
+
try {
|
|
9314
|
+
const res = await fetch(LITELLM_URL, {
|
|
9315
|
+
signal: AbortSignal.timeout(15e3)
|
|
9316
|
+
});
|
|
9317
|
+
if (!res.ok) return null;
|
|
9318
|
+
const json = await res.json();
|
|
9319
|
+
if (!json || typeof json !== "object") return null;
|
|
9320
|
+
const out = {};
|
|
9321
|
+
for (const [key, value] of Object.entries(json)) {
|
|
9322
|
+
const tuple = tupleFromLiteLLM(value);
|
|
9323
|
+
if (tuple) out[key.toLowerCase()] = tuple;
|
|
9324
|
+
}
|
|
9325
|
+
if (Object.keys(out).length < 10) {
|
|
9326
|
+
return null;
|
|
9327
|
+
}
|
|
9328
|
+
return out;
|
|
9329
|
+
} catch {
|
|
9330
|
+
return null;
|
|
9292
9331
|
}
|
|
9293
9332
|
}
|
|
9294
|
-
function
|
|
9295
|
-
if (
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9333
|
+
async function ensurePricingLoaded() {
|
|
9334
|
+
if (memCache !== null && Date.now() - memCacheAt < TTL_MS) return;
|
|
9335
|
+
const fromDisk = readCache();
|
|
9336
|
+
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
9337
|
+
memCache = fromDisk;
|
|
9338
|
+
memCacheAt = Date.now();
|
|
9339
|
+
lookupCache.clear();
|
|
9340
|
+
return;
|
|
9341
|
+
}
|
|
9342
|
+
const fetched = await fetchLiteLLMPricing();
|
|
9343
|
+
if (fetched && Object.keys(fetched).length > 0) {
|
|
9344
|
+
memCache = fetched;
|
|
9345
|
+
memCacheAt = Date.now();
|
|
9346
|
+
writeCache(fetched);
|
|
9347
|
+
lookupCache.clear();
|
|
9348
|
+
return;
|
|
9349
|
+
}
|
|
9350
|
+
memCache = { ...BUNDLED_PRICING };
|
|
9351
|
+
memCacheAt = Date.now();
|
|
9352
|
+
lookupCache.clear();
|
|
9303
9353
|
}
|
|
9304
|
-
|
|
9305
|
-
|
|
9354
|
+
function pricingFor(model) {
|
|
9355
|
+
const norm = normalizeModel(model);
|
|
9356
|
+
const cached = lookupCache.get(norm);
|
|
9357
|
+
if (cached !== void 0) return cached;
|
|
9358
|
+
if (memCache === null && !diskChecked) {
|
|
9359
|
+
diskChecked = true;
|
|
9360
|
+
const disk = readCache();
|
|
9361
|
+
if (disk && Object.keys(disk).length > 0) {
|
|
9362
|
+
memCache = disk;
|
|
9363
|
+
memCacheAt = Date.now();
|
|
9364
|
+
}
|
|
9365
|
+
}
|
|
9366
|
+
const sources = [];
|
|
9367
|
+
if (memCache) sources.push(memCache);
|
|
9368
|
+
sources.push(BUNDLED_PRICING);
|
|
9369
|
+
let resolved = null;
|
|
9370
|
+
for (const source of sources) {
|
|
9371
|
+
const exact = source[norm];
|
|
9372
|
+
if (exact) {
|
|
9373
|
+
resolved = exact;
|
|
9374
|
+
break;
|
|
9375
|
+
}
|
|
9376
|
+
let best = null;
|
|
9377
|
+
for (const key of Object.keys(source)) {
|
|
9378
|
+
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
9379
|
+
best = key;
|
|
9380
|
+
}
|
|
9381
|
+
}
|
|
9382
|
+
if (best) {
|
|
9383
|
+
resolved = source[best];
|
|
9384
|
+
break;
|
|
9385
|
+
}
|
|
9386
|
+
}
|
|
9387
|
+
lookupCache.set(norm, resolved);
|
|
9388
|
+
return resolved;
|
|
9389
|
+
}
|
|
9390
|
+
var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
|
|
9391
|
+
var init_litellm = __esm({
|
|
9392
|
+
"src/pricing/litellm.ts"() {
|
|
9306
9393
|
"use strict";
|
|
9394
|
+
init_audit();
|
|
9395
|
+
LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
9396
|
+
BUNDLED_PRICING = {
|
|
9397
|
+
// Anthropic
|
|
9398
|
+
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9399
|
+
"claude-opus-4-1": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9400
|
+
"claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9401
|
+
"claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9402
|
+
"claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9403
|
+
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9404
|
+
"claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9405
|
+
"claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9406
|
+
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9407
|
+
"claude-haiku-4-5": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9408
|
+
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9409
|
+
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9410
|
+
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9411
|
+
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
9412
|
+
// OpenAI. gpt-5 family + o-series copied from the live LiteLLM table
|
|
9413
|
+
// (verified 2026-06-14) — the bundled gpt-5 was stale at $10/$30 vs the real
|
|
9414
|
+
// $1.25/$10, and Codex models (gpt-5-codex etc.) were absent, so the offline
|
|
9415
|
+
// fallback mispriced every Codex session. See cost-codex.codexPriceFor.
|
|
9416
|
+
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
9417
|
+
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
9418
|
+
"gpt-5": [125e-8, 1e-5, 0, 125e-9],
|
|
9419
|
+
"gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
9420
|
+
"gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
9421
|
+
o3: [2e-6, 8e-6, 0, 5e-7],
|
|
9422
|
+
"o4-mini": [11e-7, 44e-7, 0, 275e-9],
|
|
9423
|
+
// Google. Values copied from the live LiteLLM table (verified 2026-06-14)
|
|
9424
|
+
// so the bundled fallback prices the current Gemini tiers correctly offline
|
|
9425
|
+
// — the local cost readers were carrying a stale hardcoded copy where
|
|
9426
|
+
// gemini-2.5-flash read $0.15/$0.60 vs the real $0.30/$2.50 (~4× under on
|
|
9427
|
+
// output). See cost-gemini.geminiPriceFor (the single Gemini price source).
|
|
9428
|
+
"gemini-2.5-pro": [125e-8, 1e-5, 0, 125e-9],
|
|
9429
|
+
"gemini-2.5-flash": [3e-7, 25e-7, 0, 3e-8],
|
|
9430
|
+
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
9431
|
+
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
9432
|
+
};
|
|
9433
|
+
CACHE_FILE = () => path16.join(os13.homedir(), ".node9", "model-pricing.json");
|
|
9434
|
+
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9435
|
+
memCache = null;
|
|
9436
|
+
memCacheAt = 0;
|
|
9437
|
+
diskChecked = false;
|
|
9438
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
9307
9439
|
}
|
|
9308
9440
|
});
|
|
9309
9441
|
|
|
9310
|
-
// src/
|
|
9311
|
-
|
|
9312
|
-
|
|
9442
|
+
// src/cost-gemini.ts
|
|
9443
|
+
import fs15 from "fs";
|
|
9444
|
+
import os14 from "os";
|
|
9445
|
+
import path17 from "path";
|
|
9446
|
+
function geminiTmpDir() {
|
|
9447
|
+
return path17.join(os14.homedir(), ".gemini", "tmp");
|
|
9313
9448
|
}
|
|
9314
|
-
function
|
|
9315
|
-
|
|
9449
|
+
function geminiPriceFor(model) {
|
|
9450
|
+
let tuple = pricingFor(model);
|
|
9451
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
9452
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
9453
|
+
tuple = pricingFor(proxy);
|
|
9454
|
+
if (tuple) break;
|
|
9455
|
+
}
|
|
9456
|
+
}
|
|
9457
|
+
if (!tuple) return null;
|
|
9458
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
9316
9459
|
}
|
|
9317
|
-
function
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
return "magenta";
|
|
9323
|
-
case "antigravity":
|
|
9324
|
-
return "yellow";
|
|
9325
|
-
case "copilot":
|
|
9326
|
-
return "green";
|
|
9327
|
-
case "shell":
|
|
9328
|
-
return "yellow";
|
|
9329
|
-
default:
|
|
9330
|
-
return "cyan";
|
|
9460
|
+
function safeReaddir(dir) {
|
|
9461
|
+
try {
|
|
9462
|
+
return fs15.readdirSync(dir);
|
|
9463
|
+
} catch {
|
|
9464
|
+
return [];
|
|
9331
9465
|
}
|
|
9332
9466
|
}
|
|
9333
|
-
function
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
for (const
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
}
|
|
9350
|
-
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9351
|
-
stats.lastDate = a.scan.lastDate;
|
|
9467
|
+
function isDir(p) {
|
|
9468
|
+
try {
|
|
9469
|
+
return fs15.statSync(p).isDirectory();
|
|
9470
|
+
} catch {
|
|
9471
|
+
return false;
|
|
9472
|
+
}
|
|
9473
|
+
}
|
|
9474
|
+
function listGeminiSessionFiles(base) {
|
|
9475
|
+
const out = [];
|
|
9476
|
+
for (const project of safeReaddir(base)) {
|
|
9477
|
+
const chats = path17.join(base, project, "chats");
|
|
9478
|
+
if (!isDir(chats)) continue;
|
|
9479
|
+
for (const f of safeReaddir(chats)) {
|
|
9480
|
+
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
9481
|
+
out.push({ file: path17.join(chats, f), project });
|
|
9482
|
+
}
|
|
9352
9483
|
}
|
|
9353
9484
|
}
|
|
9354
|
-
|
|
9355
|
-
const allLeaks = agents.flatMap(
|
|
9356
|
-
(a) => a.scan.dlpFindings.map((f) => ({
|
|
9357
|
-
patternName: f.patternName,
|
|
9358
|
-
redactedSample: f.redactedSample,
|
|
9359
|
-
toolName: f.toolName,
|
|
9360
|
-
timestamp: f.timestamp,
|
|
9361
|
-
project: f.project,
|
|
9362
|
-
sessionId: f.sessionId,
|
|
9363
|
-
agent: f.agent
|
|
9364
|
-
}))
|
|
9365
|
-
);
|
|
9366
|
-
const allLoops = agents.flatMap(
|
|
9367
|
-
(a) => a.scan.loopFindings.map((f) => ({
|
|
9368
|
-
toolName: f.toolName,
|
|
9369
|
-
commandPreview: f.commandPreview,
|
|
9370
|
-
count: f.count,
|
|
9371
|
-
timestamp: f.timestamp,
|
|
9372
|
-
project: f.project,
|
|
9373
|
-
sessionId: f.sessionId,
|
|
9374
|
-
agent: f.agent,
|
|
9375
|
-
kind: f.kind
|
|
9376
|
-
}))
|
|
9377
|
-
);
|
|
9378
|
-
const byVerdict = {
|
|
9379
|
-
blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
|
|
9380
|
-
supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
|
|
9381
|
-
leaks: allLeaks.length,
|
|
9382
|
-
loops: allLoops.length
|
|
9383
|
-
};
|
|
9384
|
-
const byAgent = agents.map((a) => ({
|
|
9385
|
-
id: a.id,
|
|
9386
|
-
label: a.label,
|
|
9387
|
-
icon: a.icon,
|
|
9388
|
-
sessions: a.scan.sessions,
|
|
9389
|
-
findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
|
|
9390
|
-
costUSD: a.scan.totalCostUSD
|
|
9391
|
-
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
9392
|
-
const sections = buildSections(allFindings);
|
|
9393
|
-
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
9394
|
-
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
9395
|
-
return {
|
|
9396
|
-
stats,
|
|
9397
|
-
byVerdict,
|
|
9398
|
-
byAgent,
|
|
9399
|
-
sections,
|
|
9400
|
-
leaks: allLeaks,
|
|
9401
|
-
loops: allLoops,
|
|
9402
|
-
loopWastedUSD
|
|
9403
|
-
};
|
|
9485
|
+
return out;
|
|
9404
9486
|
}
|
|
9405
|
-
function
|
|
9406
|
-
const
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
blockedCount: 0,
|
|
9417
|
-
reviewCount: 0,
|
|
9418
|
-
rules: []
|
|
9419
|
-
};
|
|
9420
|
-
sectionMap.set(id, s);
|
|
9421
|
-
}
|
|
9422
|
-
return s;
|
|
9423
|
-
}
|
|
9424
|
-
const ruleMap = /* @__PURE__ */ new Map();
|
|
9425
|
-
for (const f of findings) {
|
|
9426
|
-
const src = f.source;
|
|
9427
|
-
const sourceType = src.sourceType;
|
|
9428
|
-
const shieldName = src.shieldName;
|
|
9429
|
-
const verdict = src.rule.verdict === "block" ? "block" : "review";
|
|
9430
|
-
let sectionId;
|
|
9431
|
-
let sectionLabel;
|
|
9432
|
-
let sectionSubtitle;
|
|
9433
|
-
let shieldKey;
|
|
9434
|
-
if (sourceType === "default") {
|
|
9435
|
-
sectionId = "default";
|
|
9436
|
-
sectionLabel = "Default Rules";
|
|
9437
|
-
sectionSubtitle = "built-in, always on";
|
|
9438
|
-
} else if (sourceType === "shield") {
|
|
9439
|
-
sectionId = `shield:${shieldName}`;
|
|
9440
|
-
sectionLabel = shieldName;
|
|
9441
|
-
sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
|
|
9442
|
-
shieldKey = shieldName;
|
|
9443
|
-
} else if (shieldName === "cloud") {
|
|
9444
|
-
sectionId = "cloud";
|
|
9445
|
-
sectionLabel = "Cloud Policy";
|
|
9446
|
-
sectionSubtitle = "synced from node9 cloud";
|
|
9447
|
-
} else {
|
|
9448
|
-
sectionId = "user";
|
|
9449
|
-
sectionLabel = "Your Rules";
|
|
9450
|
-
sectionSubtitle = "added in node9.config.json";
|
|
9487
|
+
function parseGeminiSession(lines, project) {
|
|
9488
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
9489
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9490
|
+
let runId = "";
|
|
9491
|
+
for (const raw of lines) {
|
|
9492
|
+
if (!raw.trim()) continue;
|
|
9493
|
+
let obj;
|
|
9494
|
+
try {
|
|
9495
|
+
obj = JSON.parse(raw);
|
|
9496
|
+
} catch {
|
|
9497
|
+
continue;
|
|
9451
9498
|
}
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
rule = {
|
|
9458
|
-
name: ruleDisplayName,
|
|
9459
|
-
verdict,
|
|
9460
|
-
reason: src.rule.reason ?? "",
|
|
9461
|
-
findings: []
|
|
9462
|
-
};
|
|
9463
|
-
ruleMap.set(ruleKey, rule);
|
|
9464
|
-
section.rules.push(rule);
|
|
9499
|
+
if (typeof obj.sessionId === "string" && !runId) runId = obj.sessionId;
|
|
9500
|
+
if (!obj.tokens || !obj.model || !obj.timestamp) continue;
|
|
9501
|
+
if (obj.id) {
|
|
9502
|
+
if (seenIds.has(obj.id)) continue;
|
|
9503
|
+
seenIds.add(obj.id);
|
|
9465
9504
|
}
|
|
9466
|
-
const
|
|
9467
|
-
|
|
9468
|
-
const
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9505
|
+
const price = geminiPriceFor(obj.model);
|
|
9506
|
+
if (!price) continue;
|
|
9507
|
+
const inp = obj.tokens.input ?? 0;
|
|
9508
|
+
const out = obj.tokens.output ?? 0;
|
|
9509
|
+
const cached = Math.min(obj.tokens.cached ?? 0, inp);
|
|
9510
|
+
const fresh = Math.max(0, inp - cached);
|
|
9511
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
9512
|
+
const date = obj.timestamp.slice(0, 10);
|
|
9513
|
+
const model = normalizeModel(obj.model);
|
|
9514
|
+
const key = `${date}::${model}`;
|
|
9515
|
+
const prev = byKey.get(key);
|
|
9516
|
+
if (prev) {
|
|
9517
|
+
prev.costUSD += cost;
|
|
9518
|
+
prev.inputTokens += fresh;
|
|
9519
|
+
prev.outputTokens += out;
|
|
9520
|
+
prev.cacheReadTokens += cached;
|
|
9521
|
+
} else {
|
|
9522
|
+
byKey.set(key, {
|
|
9523
|
+
date,
|
|
9524
|
+
model,
|
|
9525
|
+
workingDir: project,
|
|
9526
|
+
runId,
|
|
9527
|
+
costUSD: cost,
|
|
9528
|
+
inputTokens: fresh,
|
|
9529
|
+
outputTokens: out,
|
|
9530
|
+
cacheReadTokens: cached,
|
|
9531
|
+
cacheWriteTokens: 0
|
|
9478
9532
|
});
|
|
9479
9533
|
}
|
|
9480
|
-
if (verdict === "block") section.blockedCount++;
|
|
9481
|
-
else section.reviewCount++;
|
|
9482
9534
|
}
|
|
9483
|
-
const
|
|
9484
|
-
|
|
9485
|
-
const aTotal = a.blockedCount + a.reviewCount;
|
|
9486
|
-
const bTotal = b.blockedCount + b.reviewCount;
|
|
9487
|
-
if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
|
|
9488
|
-
return bTotal - aTotal;
|
|
9489
|
-
});
|
|
9490
|
-
for (const s of sections) {
|
|
9491
|
-
s.rules.sort((a, b) => {
|
|
9492
|
-
const aBlock = a.verdict === "block" ? 1 : 0;
|
|
9493
|
-
const bBlock = b.verdict === "block" ? 1 : 0;
|
|
9494
|
-
if (bBlock !== aBlock) return bBlock - aBlock;
|
|
9495
|
-
return b.findings.length - a.findings.length;
|
|
9496
|
-
});
|
|
9497
|
-
}
|
|
9498
|
-
return sections;
|
|
9499
|
-
}
|
|
9500
|
-
function previewCommand(input, max) {
|
|
9501
|
-
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9502
|
-
const s = String(raw).replace(/\s+/g, " ").trim();
|
|
9503
|
-
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9504
|
-
}
|
|
9505
|
-
function fullCommandOf(input) {
|
|
9506
|
-
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9507
|
-
return String(raw).replace(/\s+/g, " ").trim();
|
|
9535
|
+
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
9536
|
+
return [...byKey.values()];
|
|
9508
9537
|
}
|
|
9509
|
-
var
|
|
9510
|
-
var
|
|
9511
|
-
"src/
|
|
9538
|
+
var GEMINI_FALLBACK_MODELS, geminiSource;
|
|
9539
|
+
var init_cost_gemini = __esm({
|
|
9540
|
+
"src/cost-gemini.ts"() {
|
|
9512
9541
|
"use strict";
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
},
|
|
9554
|
-
{
|
|
9555
|
-
full: path16.join(home, ".ssh", "id_ecdsa"),
|
|
9556
|
-
label: "~/.ssh/id_ecdsa",
|
|
9557
|
-
description: "ECDSA private key \u2014 grants SSH access to your servers",
|
|
9558
|
-
score: 20
|
|
9559
|
-
},
|
|
9560
|
-
{
|
|
9561
|
-
full: path16.join(home, ".aws", "credentials"),
|
|
9562
|
-
label: "~/.aws/credentials",
|
|
9563
|
-
description: "AWS access keys \u2014 full cloud account access",
|
|
9564
|
-
score: 20
|
|
9565
|
-
},
|
|
9566
|
-
{
|
|
9567
|
-
full: path16.join(home, ".aws", "config"),
|
|
9568
|
-
label: "~/.aws/config",
|
|
9569
|
-
description: "AWS configuration \u2014 account and region settings",
|
|
9570
|
-
score: 5
|
|
9571
|
-
},
|
|
9572
|
-
{
|
|
9573
|
-
full: path16.join(home, ".config", "gcloud", "credentials.db"),
|
|
9574
|
-
label: "~/.config/gcloud/credentials.db",
|
|
9575
|
-
description: "Google Cloud credentials",
|
|
9576
|
-
score: 15
|
|
9577
|
-
},
|
|
9578
|
-
{
|
|
9579
|
-
full: path16.join(home, ".docker", "config.json"),
|
|
9580
|
-
label: "~/.docker/config.json",
|
|
9581
|
-
description: "Docker registry auth tokens",
|
|
9582
|
-
score: 10
|
|
9583
|
-
},
|
|
9584
|
-
{
|
|
9585
|
-
full: path16.join(home, ".netrc"),
|
|
9586
|
-
label: "~/.netrc",
|
|
9587
|
-
description: "FTP/HTTP credentials in plain text",
|
|
9588
|
-
score: 15
|
|
9589
|
-
},
|
|
9590
|
-
{
|
|
9591
|
-
full: path16.join(home, ".npmrc"),
|
|
9592
|
-
label: "~/.npmrc",
|
|
9593
|
-
description: "npm auth token \u2014 can publish packages as you",
|
|
9594
|
-
score: 10
|
|
9595
|
-
},
|
|
9596
|
-
{
|
|
9597
|
-
full: path16.join(home, ".node9", "credentials.json"),
|
|
9598
|
-
label: "~/.node9/credentials.json",
|
|
9599
|
-
description: "Node9 cloud API key",
|
|
9600
|
-
score: 10
|
|
9601
|
-
},
|
|
9602
|
-
{
|
|
9603
|
-
full: path16.join(cwd, ".env"),
|
|
9604
|
-
label: ".env (current folder)",
|
|
9605
|
-
description: "App secrets \u2014 database passwords, API keys",
|
|
9606
|
-
score: 20
|
|
9607
|
-
},
|
|
9608
|
-
{
|
|
9609
|
-
full: path16.join(cwd, ".env.local"),
|
|
9610
|
-
label: ".env.local (current folder)",
|
|
9611
|
-
description: "Local overrides \u2014 often contains real credentials",
|
|
9612
|
-
score: 15
|
|
9613
|
-
},
|
|
9614
|
-
{
|
|
9615
|
-
full: path16.join(cwd, ".env.production"),
|
|
9616
|
-
label: ".env.production (current folder)",
|
|
9617
|
-
description: "Production secrets",
|
|
9618
|
-
score: 20
|
|
9619
|
-
}
|
|
9620
|
-
];
|
|
9621
|
-
}
|
|
9622
|
-
function isReadable(filePath) {
|
|
9623
|
-
try {
|
|
9624
|
-
fs14.accessSync(filePath, fs14.constants.R_OK);
|
|
9625
|
-
return true;
|
|
9626
|
-
} catch {
|
|
9627
|
-
return false;
|
|
9628
|
-
}
|
|
9629
|
-
}
|
|
9630
|
-
function scoreLabel(score) {
|
|
9631
|
-
if (score >= 80) return chalk2.green(`${score}/100 Good`);
|
|
9632
|
-
if (score >= 50) return chalk2.yellow(`${score}/100 Moderate risk`);
|
|
9633
|
-
if (score >= 25) return chalk2.red(`${score}/100 High risk`);
|
|
9634
|
-
return chalk2.red.bold(`${score}/100 Critical`);
|
|
9635
|
-
}
|
|
9636
|
-
function runBlast() {
|
|
9637
|
-
const home = os13.homedir();
|
|
9638
|
-
const cwd = process.cwd();
|
|
9639
|
-
const paths = buildSensitivePaths(home, cwd);
|
|
9640
|
-
let scoreDeduction = 0;
|
|
9641
|
-
const reachable = [];
|
|
9642
|
-
for (const p of paths) {
|
|
9643
|
-
if (fs14.existsSync(p.full) && isReadable(p.full)) {
|
|
9644
|
-
reachable.push(p);
|
|
9645
|
-
scoreDeduction += p.score;
|
|
9646
|
-
}
|
|
9647
|
-
}
|
|
9648
|
-
const envFindings = [];
|
|
9649
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
9650
|
-
if (!value) continue;
|
|
9651
|
-
const match = scanArgs({ [key]: value });
|
|
9652
|
-
if (match) {
|
|
9653
|
-
envFindings.push({ key, patternName: match.patternName });
|
|
9654
|
-
scoreDeduction += 10;
|
|
9655
|
-
}
|
|
9656
|
-
}
|
|
9657
|
-
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
9658
|
-
}
|
|
9659
|
-
function registerBlastCommand(program2) {
|
|
9660
|
-
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
9661
|
-
const home = os13.homedir();
|
|
9662
|
-
const cwd = process.cwd();
|
|
9663
|
-
const { reachable, envFindings, score } = runBlast();
|
|
9664
|
-
console.log("");
|
|
9665
|
-
console.log(
|
|
9666
|
-
chalk2.bold(" \u{1F52D} Node9 Blast Radius") + chalk2.dim(" \xB7 what an AI agent can reach from here")
|
|
9667
|
-
);
|
|
9668
|
-
console.log(chalk2.dim(" Running in: ") + chalk2.white(cwd.replace(home, "~")));
|
|
9669
|
-
console.log("");
|
|
9670
|
-
if (reachable.length > 0) {
|
|
9671
|
-
console.log(" " + chalk2.red.bold("Sensitive files reachable:"));
|
|
9672
|
-
for (const p of reachable) {
|
|
9673
|
-
console.log(
|
|
9674
|
-
" " + chalk2.red("\u2717 ") + chalk2.yellow(p.label.padEnd(38)) + chalk2.dim(p.description)
|
|
9675
|
-
);
|
|
9676
|
-
}
|
|
9677
|
-
console.log("");
|
|
9678
|
-
}
|
|
9679
|
-
if (envFindings.length > 0) {
|
|
9680
|
-
console.log(" " + chalk2.red.bold("Secrets in active environment:"));
|
|
9681
|
-
for (const f of envFindings) {
|
|
9682
|
-
console.log(
|
|
9683
|
-
" " + chalk2.red("\u2717 ") + chalk2.yellow(f.key.padEnd(38)) + chalk2.dim(f.patternName)
|
|
9684
|
-
);
|
|
9542
|
+
init_litellm();
|
|
9543
|
+
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
9544
|
+
geminiSource = {
|
|
9545
|
+
id: "gemini",
|
|
9546
|
+
available() {
|
|
9547
|
+
try {
|
|
9548
|
+
return fs15.existsSync(geminiTmpDir());
|
|
9549
|
+
} catch {
|
|
9550
|
+
return false;
|
|
9551
|
+
}
|
|
9552
|
+
},
|
|
9553
|
+
collect(sinceMs) {
|
|
9554
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9555
|
+
for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
9556
|
+
try {
|
|
9557
|
+
if (sinceMs !== void 0 && fs15.statSync(file).mtimeMs < sinceMs) continue;
|
|
9558
|
+
} catch {
|
|
9559
|
+
continue;
|
|
9560
|
+
}
|
|
9561
|
+
let content;
|
|
9562
|
+
try {
|
|
9563
|
+
content = fs15.readFileSync(file, "utf8");
|
|
9564
|
+
} catch {
|
|
9565
|
+
continue;
|
|
9566
|
+
}
|
|
9567
|
+
for (const e of parseGeminiSession(content.split("\n"), project)) {
|
|
9568
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9569
|
+
const prev = combined.get(key);
|
|
9570
|
+
if (prev) {
|
|
9571
|
+
prev.costUSD += e.costUSD;
|
|
9572
|
+
prev.inputTokens += e.inputTokens;
|
|
9573
|
+
prev.outputTokens += e.outputTokens;
|
|
9574
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9575
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9576
|
+
} else {
|
|
9577
|
+
combined.set(key, { ...e });
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
9580
|
+
}
|
|
9581
|
+
return [...combined.values()];
|
|
9685
9582
|
}
|
|
9686
|
-
|
|
9687
|
-
}
|
|
9688
|
-
console.log(" " + chalk2.dim("\u2500".repeat(70)));
|
|
9689
|
-
if (reachable.length === 0 && envFindings.length === 0) {
|
|
9690
|
-
console.log(" " + chalk2.green("\u2705 No sensitive files or environment secrets found."));
|
|
9691
|
-
console.log(" Security Score: " + scoreLabel(score));
|
|
9692
|
-
} else {
|
|
9693
|
-
console.log(
|
|
9694
|
-
" Security Score: " + scoreLabel(score) + chalk2.dim(
|
|
9695
|
-
` (${reachable.length} file${reachable.length !== 1 ? "s" : ""}, ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""})`
|
|
9696
|
-
)
|
|
9697
|
-
);
|
|
9698
|
-
console.log("");
|
|
9699
|
-
console.log(
|
|
9700
|
-
chalk2.dim(
|
|
9701
|
-
" Every AI agent you start can read the files and env vars listed above.\n Run `node9 shield enable project-jail` to restrict agent file access.\n Run `node9 mask` to redact secrets from existing session history."
|
|
9702
|
-
)
|
|
9703
|
-
);
|
|
9704
|
-
}
|
|
9705
|
-
console.log("");
|
|
9706
|
-
});
|
|
9707
|
-
}
|
|
9708
|
-
var init_blast = __esm({
|
|
9709
|
-
"src/cli/commands/blast.ts"() {
|
|
9710
|
-
"use strict";
|
|
9711
|
-
init_dlp();
|
|
9583
|
+
};
|
|
9712
9584
|
}
|
|
9713
9585
|
});
|
|
9714
9586
|
|
|
9715
|
-
// src/
|
|
9716
|
-
import
|
|
9717
|
-
import
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
return { band: "critical", label: "Critical", color: chalk3.red };
|
|
9587
|
+
// src/cost-codex.ts
|
|
9588
|
+
import fs16 from "fs";
|
|
9589
|
+
import os15 from "os";
|
|
9590
|
+
import path18 from "path";
|
|
9591
|
+
function codexSessionsDir() {
|
|
9592
|
+
return path18.join(os15.homedir(), ".codex", "sessions");
|
|
9722
9593
|
}
|
|
9723
|
-
function
|
|
9724
|
-
|
|
9725
|
-
for (const f of findings) {
|
|
9726
|
-
counts.set(f.patternName, (counts.get(f.patternName) ?? 0) + 1);
|
|
9727
|
-
}
|
|
9728
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name, count]) => ({ name, count }));
|
|
9594
|
+
function codexPriceFor(model) {
|
|
9595
|
+
return pricingFor(model) ?? CODEX_FALLBACK;
|
|
9729
9596
|
}
|
|
9730
|
-
function
|
|
9731
|
-
const
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9597
|
+
function codexSessionCost(model, tokens) {
|
|
9598
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
9599
|
+
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
9600
|
+
return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
|
|
9601
|
+
}
|
|
9602
|
+
function listCodexSessionFiles(base) {
|
|
9603
|
+
const out = [];
|
|
9604
|
+
for (const y of safeReaddir2(base)) {
|
|
9605
|
+
const yp = path18.join(base, y);
|
|
9606
|
+
if (!isDir2(yp)) continue;
|
|
9607
|
+
for (const m of safeReaddir2(yp)) {
|
|
9608
|
+
const mp = path18.join(yp, m);
|
|
9609
|
+
if (!isDir2(mp)) continue;
|
|
9610
|
+
for (const d of safeReaddir2(mp)) {
|
|
9611
|
+
const dp = path18.join(mp, d);
|
|
9612
|
+
if (!isDir2(dp)) continue;
|
|
9613
|
+
for (const f of safeReaddir2(dp)) {
|
|
9614
|
+
if (f.endsWith(".jsonl")) out.push(path18.join(dp, f));
|
|
9615
|
+
}
|
|
9616
|
+
}
|
|
9736
9617
|
}
|
|
9737
9618
|
}
|
|
9738
|
-
return
|
|
9739
|
-
}
|
|
9740
|
-
function computeLoopWaste(loops, totalToolCalls) {
|
|
9741
|
-
const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
9742
|
-
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
9743
|
-
return { wastedCalls, wastePct };
|
|
9619
|
+
return out;
|
|
9744
9620
|
}
|
|
9745
|
-
function
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
const totalCatches = section.blockedCount + section.reviewCount;
|
|
9751
|
-
const topRuleLabels = [...section.rules].sort((a, b) => b.findings.length - a.findings.length).slice(0, topRulesPerShield).map((r) => r.findings.length > 1 ? `${r.name} \xD7${r.findings.length}` : r.name);
|
|
9752
|
-
out.push({
|
|
9753
|
-
shieldName: section.shieldKey,
|
|
9754
|
-
totalCatches,
|
|
9755
|
-
blockCatches: section.blockedCount,
|
|
9756
|
-
reviewCatches: section.reviewCount,
|
|
9757
|
-
topRuleLabels
|
|
9758
|
-
});
|
|
9621
|
+
function safeReaddir2(dir) {
|
|
9622
|
+
try {
|
|
9623
|
+
return fs16.readdirSync(dir);
|
|
9624
|
+
} catch {
|
|
9625
|
+
return [];
|
|
9759
9626
|
}
|
|
9760
|
-
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
9761
9627
|
}
|
|
9762
|
-
function
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
9768
|
-
const dashFill = "\u2500".repeat(Math.max(0, inner - stringWidth(titleSegment)));
|
|
9769
|
-
out.push(chalk3.dim("\u256D\u2500") + chalk3.bold(titleSegment) + chalk3.dim(`${dashFill}\u2500\u256E`));
|
|
9770
|
-
for (const line of bodyLines) {
|
|
9771
|
-
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
9772
|
-
out.push(chalk3.dim("\u2502 ") + line.rendered + padding + chalk3.dim(" \u2502"));
|
|
9628
|
+
function isDir2(p) {
|
|
9629
|
+
try {
|
|
9630
|
+
return fs16.statSync(p).isDirectory();
|
|
9631
|
+
} catch {
|
|
9632
|
+
return false;
|
|
9773
9633
|
}
|
|
9774
|
-
out.push(chalk3.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
9775
|
-
return out;
|
|
9776
9634
|
}
|
|
9777
|
-
function
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9635
|
+
function parseCodexSession(lines) {
|
|
9636
|
+
let sessionStart2 = "";
|
|
9637
|
+
let runId = "";
|
|
9638
|
+
let cwd = "";
|
|
9639
|
+
let model = "";
|
|
9640
|
+
let input = 0;
|
|
9641
|
+
let cached = 0;
|
|
9642
|
+
let output = 0;
|
|
9643
|
+
let sawUsage = false;
|
|
9644
|
+
for (const raw of lines) {
|
|
9645
|
+
if (!raw.trim()) continue;
|
|
9646
|
+
let entry;
|
|
9647
|
+
try {
|
|
9648
|
+
entry = JSON.parse(raw);
|
|
9649
|
+
} catch {
|
|
9650
|
+
continue;
|
|
9651
|
+
}
|
|
9652
|
+
const p = entry.payload ?? {};
|
|
9653
|
+
if (entry.type === "session_meta") {
|
|
9654
|
+
if (!sessionStart2 && typeof p["timestamp"] === "string") sessionStart2 = p["timestamp"];
|
|
9655
|
+
if (!runId && typeof p["id"] === "string") runId = p["id"];
|
|
9656
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9657
|
+
continue;
|
|
9658
|
+
}
|
|
9659
|
+
if (entry.type === "turn_context") {
|
|
9660
|
+
if (typeof p["model"] === "string") model = p["model"];
|
|
9661
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9662
|
+
continue;
|
|
9663
|
+
}
|
|
9664
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
9665
|
+
const info = p["info"] ?? {};
|
|
9666
|
+
const usage = info["total_token_usage"] ?? {};
|
|
9667
|
+
if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
|
|
9668
|
+
if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
|
|
9669
|
+
if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
|
|
9670
|
+
sawUsage = true;
|
|
9671
|
+
}
|
|
9672
|
+
}
|
|
9673
|
+
if (!sessionStart2 || !sawUsage) return null;
|
|
9674
|
+
const nonCached = Math.max(0, input - cached);
|
|
9675
|
+
if (nonCached === 0 && output === 0 && cached === 0) return null;
|
|
9676
|
+
const norm = normalizeModel(model || "gpt-5");
|
|
9677
|
+
const costUSD = codexSessionCost(model, { input, cached, output });
|
|
9678
|
+
return {
|
|
9679
|
+
date: sessionStart2.slice(0, 10),
|
|
9680
|
+
model: norm,
|
|
9681
|
+
workingDir: cwd,
|
|
9682
|
+
runId,
|
|
9683
|
+
costUSD,
|
|
9684
|
+
inputTokens: nonCached,
|
|
9685
|
+
outputTokens: output,
|
|
9686
|
+
cacheReadTokens: cached,
|
|
9687
|
+
cacheWriteTokens: 0
|
|
9688
|
+
};
|
|
9784
9689
|
}
|
|
9785
|
-
var
|
|
9786
|
-
var
|
|
9787
|
-
"src/
|
|
9690
|
+
var CODEX_FALLBACK, codexSource;
|
|
9691
|
+
var init_cost_codex = __esm({
|
|
9692
|
+
"src/cost-codex.ts"() {
|
|
9788
9693
|
"use strict";
|
|
9789
|
-
|
|
9694
|
+
init_litellm();
|
|
9695
|
+
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
9696
|
+
codexSource = {
|
|
9697
|
+
id: "codex",
|
|
9698
|
+
available() {
|
|
9699
|
+
try {
|
|
9700
|
+
return fs16.existsSync(codexSessionsDir());
|
|
9701
|
+
} catch {
|
|
9702
|
+
return false;
|
|
9703
|
+
}
|
|
9704
|
+
},
|
|
9705
|
+
collect(sinceMs) {
|
|
9706
|
+
const base = codexSessionsDir();
|
|
9707
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9708
|
+
for (const file of listCodexSessionFiles(base)) {
|
|
9709
|
+
try {
|
|
9710
|
+
if (sinceMs !== void 0 && fs16.statSync(file).mtimeMs < sinceMs) continue;
|
|
9711
|
+
} catch {
|
|
9712
|
+
continue;
|
|
9713
|
+
}
|
|
9714
|
+
let content;
|
|
9715
|
+
try {
|
|
9716
|
+
content = fs16.readFileSync(file, "utf8");
|
|
9717
|
+
} catch {
|
|
9718
|
+
continue;
|
|
9719
|
+
}
|
|
9720
|
+
const e = parseCodexSession(content.split("\n"));
|
|
9721
|
+
if (!e) continue;
|
|
9722
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9723
|
+
const prev = combined.get(key);
|
|
9724
|
+
if (prev) {
|
|
9725
|
+
prev.costUSD += e.costUSD;
|
|
9726
|
+
prev.inputTokens += e.inputTokens;
|
|
9727
|
+
prev.outputTokens += e.outputTokens;
|
|
9728
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9729
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9730
|
+
} else {
|
|
9731
|
+
combined.set(key, { ...e });
|
|
9732
|
+
}
|
|
9733
|
+
}
|
|
9734
|
+
return [...combined.values()];
|
|
9735
|
+
}
|
|
9736
|
+
};
|
|
9737
|
+
}
|
|
9738
|
+
});
|
|
9739
|
+
|
|
9740
|
+
// src/utils/hook-payload.ts
|
|
9741
|
+
function extractToolName(payload, defaultValue = "") {
|
|
9742
|
+
return payload.tool_name ?? payload.name ?? payload.toolCall?.name ?? defaultValue;
|
|
9743
|
+
}
|
|
9744
|
+
function extractToolInput(payload) {
|
|
9745
|
+
return payload.tool_input ?? payload.args ?? payload.toolCall?.args ?? {};
|
|
9746
|
+
}
|
|
9747
|
+
function canonicalToolName(name) {
|
|
9748
|
+
switch (name) {
|
|
9749
|
+
// Hermes Agent
|
|
9750
|
+
case "terminal":
|
|
9751
|
+
return "Bash";
|
|
9752
|
+
case "write_file":
|
|
9753
|
+
return "Write";
|
|
9754
|
+
case "patch":
|
|
9755
|
+
return "Edit";
|
|
9756
|
+
case "read_file":
|
|
9757
|
+
return "Read";
|
|
9758
|
+
case "search_files":
|
|
9759
|
+
return "Grep";
|
|
9760
|
+
// Antigravity (agy) — shell tool renamed from Gemini's run_shell_command
|
|
9761
|
+
case "run_command":
|
|
9762
|
+
return "Bash";
|
|
9763
|
+
default:
|
|
9764
|
+
return name;
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
function agentLabelFromFlag(flag) {
|
|
9768
|
+
if (typeof flag !== "string") return void 0;
|
|
9769
|
+
switch (flag.toLowerCase()) {
|
|
9770
|
+
case "antigravity":
|
|
9771
|
+
case "agy":
|
|
9772
|
+
return "Antigravity";
|
|
9773
|
+
case "copilot":
|
|
9774
|
+
return "GitHub Copilot";
|
|
9775
|
+
default:
|
|
9776
|
+
return void 0;
|
|
9790
9777
|
}
|
|
9791
|
-
}
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9778
|
+
}
|
|
9779
|
+
function canonicalToolInput(rawToolName, input) {
|
|
9780
|
+
if (rawToolName !== "run_command") return input;
|
|
9781
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
|
|
9782
|
+
const args = input;
|
|
9783
|
+
if (typeof args.CommandLine !== "string") return input;
|
|
9784
|
+
const { CommandLine, Cwd, ...rest } = args;
|
|
9785
|
+
const canonical = { ...rest, command: CommandLine };
|
|
9786
|
+
if (typeof Cwd === "string" && Cwd.length > 0) canonical.cwd = Cwd;
|
|
9787
|
+
return canonical;
|
|
9788
|
+
}
|
|
9789
|
+
var init_hook_payload = __esm({
|
|
9790
|
+
"src/utils/hook-payload.ts"() {
|
|
9797
9791
|
"use strict";
|
|
9798
|
-
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
9799
|
-
"project-jail": 0.7
|
|
9800
|
-
};
|
|
9801
9792
|
}
|
|
9802
9793
|
});
|
|
9803
9794
|
|
|
9804
|
-
// src/
|
|
9805
|
-
function
|
|
9806
|
-
|
|
9807
|
-
const { band } = classifyScore(blast.score);
|
|
9808
|
-
return {
|
|
9809
|
-
schemaVersion: 1,
|
|
9810
|
-
generatedAt,
|
|
9811
|
-
isWired,
|
|
9812
|
-
score: blast.score,
|
|
9813
|
-
band,
|
|
9814
|
-
totals: {
|
|
9815
|
-
blocked: summary.byVerdict.blocked,
|
|
9816
|
-
review: summary.byVerdict.supervised,
|
|
9817
|
-
leaks: summary.byVerdict.leaks,
|
|
9818
|
-
loops: summary.byVerdict.loops,
|
|
9819
|
-
blastExposures: blast.reachable.length + blast.envFindings.length
|
|
9820
|
-
},
|
|
9821
|
-
summary,
|
|
9822
|
-
blast: {
|
|
9823
|
-
score: blast.score,
|
|
9824
|
-
reachable: blast.reachable,
|
|
9825
|
-
envFindings: blast.envFindings
|
|
9826
|
-
}
|
|
9827
|
-
};
|
|
9795
|
+
// src/scan-summary.ts
|
|
9796
|
+
function agentDisplayName(agent) {
|
|
9797
|
+
return AGENT_LONG[agent] ?? "Claude Code";
|
|
9828
9798
|
}
|
|
9829
|
-
|
|
9830
|
-
"
|
|
9831
|
-
|
|
9832
|
-
|
|
9799
|
+
function agentBadgeText(agent, width = 10) {
|
|
9800
|
+
return `[${AGENT_SHORT[agent] ?? "Claude"}]`.padEnd(width);
|
|
9801
|
+
}
|
|
9802
|
+
function agentColorName(agent) {
|
|
9803
|
+
switch (agent) {
|
|
9804
|
+
case "gemini":
|
|
9805
|
+
return "blue";
|
|
9806
|
+
case "codex":
|
|
9807
|
+
return "magenta";
|
|
9808
|
+
case "antigravity":
|
|
9809
|
+
return "yellow";
|
|
9810
|
+
case "copilot":
|
|
9811
|
+
return "green";
|
|
9812
|
+
case "shell":
|
|
9813
|
+
return "yellow";
|
|
9814
|
+
default:
|
|
9815
|
+
return "cyan";
|
|
9833
9816
|
}
|
|
9834
|
-
});
|
|
9835
|
-
|
|
9836
|
-
// src/cli/render/scan-history.ts
|
|
9837
|
-
import fs15 from "fs";
|
|
9838
|
-
import path17 from "path";
|
|
9839
|
-
import os14 from "os";
|
|
9840
|
-
function defaultHistoryPath() {
|
|
9841
|
-
return path17.join(os14.homedir(), ".node9", "scan-history.json");
|
|
9842
9817
|
}
|
|
9843
|
-
function
|
|
9844
|
-
const
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9818
|
+
function buildScanSummary(agents) {
|
|
9819
|
+
const stats = {
|
|
9820
|
+
sessions: 0,
|
|
9821
|
+
totalToolCalls: 0,
|
|
9822
|
+
bashCalls: 0,
|
|
9823
|
+
totalCostUSD: 0,
|
|
9824
|
+
firstDate: null,
|
|
9825
|
+
lastDate: null
|
|
9826
|
+
};
|
|
9827
|
+
for (const a of agents) {
|
|
9828
|
+
stats.sessions += a.scan.sessions;
|
|
9829
|
+
stats.totalToolCalls += a.scan.totalToolCalls;
|
|
9830
|
+
stats.bashCalls += a.scan.bashCalls;
|
|
9831
|
+
stats.totalCostUSD += a.scan.totalCostUSD;
|
|
9832
|
+
if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
|
|
9833
|
+
stats.firstDate = a.scan.firstDate;
|
|
9834
|
+
}
|
|
9835
|
+
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9836
|
+
stats.lastDate = a.scan.lastDate;
|
|
9837
|
+
}
|
|
9855
9838
|
}
|
|
9839
|
+
const allFindings = agents.flatMap((a) => a.scan.findings);
|
|
9840
|
+
const allLeaks = agents.flatMap(
|
|
9841
|
+
(a) => a.scan.dlpFindings.map((f) => ({
|
|
9842
|
+
patternName: f.patternName,
|
|
9843
|
+
redactedSample: f.redactedSample,
|
|
9844
|
+
toolName: f.toolName,
|
|
9845
|
+
timestamp: f.timestamp,
|
|
9846
|
+
project: f.project,
|
|
9847
|
+
sessionId: f.sessionId,
|
|
9848
|
+
agent: f.agent
|
|
9849
|
+
}))
|
|
9850
|
+
);
|
|
9851
|
+
const allLoops = agents.flatMap(
|
|
9852
|
+
(a) => a.scan.loopFindings.map((f) => ({
|
|
9853
|
+
toolName: f.toolName,
|
|
9854
|
+
commandPreview: f.commandPreview,
|
|
9855
|
+
count: f.count,
|
|
9856
|
+
timestamp: f.timestamp,
|
|
9857
|
+
project: f.project,
|
|
9858
|
+
sessionId: f.sessionId,
|
|
9859
|
+
agent: f.agent,
|
|
9860
|
+
kind: f.kind
|
|
9861
|
+
}))
|
|
9862
|
+
);
|
|
9863
|
+
const byVerdict = {
|
|
9864
|
+
blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
|
|
9865
|
+
supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
|
|
9866
|
+
leaks: allLeaks.length,
|
|
9867
|
+
loops: allLoops.length
|
|
9868
|
+
};
|
|
9869
|
+
const byAgent = agents.map((a) => ({
|
|
9870
|
+
id: a.id,
|
|
9871
|
+
label: a.label,
|
|
9872
|
+
icon: a.icon,
|
|
9873
|
+
sessions: a.scan.sessions,
|
|
9874
|
+
findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
|
|
9875
|
+
costUSD: a.scan.totalCostUSD
|
|
9876
|
+
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
9877
|
+
const sections = buildSections(allFindings);
|
|
9878
|
+
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
9879
|
+
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
9880
|
+
return {
|
|
9881
|
+
stats,
|
|
9882
|
+
byVerdict,
|
|
9883
|
+
byAgent,
|
|
9884
|
+
sections,
|
|
9885
|
+
leaks: allLeaks,
|
|
9886
|
+
loops: allLoops,
|
|
9887
|
+
loopWastedUSD
|
|
9888
|
+
};
|
|
9856
9889
|
}
|
|
9857
|
-
function
|
|
9858
|
-
const
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9890
|
+
function buildSections(findings) {
|
|
9891
|
+
const sectionMap = /* @__PURE__ */ new Map();
|
|
9892
|
+
function ensureSection(id, label, subtitle, sourceType, shieldKey) {
|
|
9893
|
+
let s = sectionMap.get(id);
|
|
9894
|
+
if (!s) {
|
|
9895
|
+
s = {
|
|
9896
|
+
id,
|
|
9897
|
+
label,
|
|
9898
|
+
subtitle,
|
|
9899
|
+
sourceType,
|
|
9900
|
+
shieldKey,
|
|
9901
|
+
blockedCount: 0,
|
|
9902
|
+
reviewCount: 0,
|
|
9903
|
+
rules: []
|
|
9904
|
+
};
|
|
9905
|
+
sectionMap.set(id, s);
|
|
9871
9906
|
}
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9907
|
+
return s;
|
|
9908
|
+
}
|
|
9909
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
9910
|
+
for (const f of findings) {
|
|
9911
|
+
const src = f.source;
|
|
9912
|
+
const sourceType = src.sourceType;
|
|
9913
|
+
const shieldName = src.shieldName;
|
|
9914
|
+
const verdict = src.rule.verdict === "block" ? "block" : "review";
|
|
9915
|
+
let sectionId;
|
|
9916
|
+
let sectionLabel;
|
|
9917
|
+
let sectionSubtitle;
|
|
9918
|
+
let shieldKey;
|
|
9919
|
+
if (sourceType === "default") {
|
|
9920
|
+
sectionId = "default";
|
|
9921
|
+
sectionLabel = "Default Rules";
|
|
9922
|
+
sectionSubtitle = "built-in, always on";
|
|
9923
|
+
} else if (sourceType === "shield") {
|
|
9924
|
+
sectionId = `shield:${shieldName}`;
|
|
9925
|
+
sectionLabel = shieldName;
|
|
9926
|
+
sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
|
|
9927
|
+
shieldKey = shieldName;
|
|
9928
|
+
} else if (shieldName === "cloud") {
|
|
9929
|
+
sectionId = "cloud";
|
|
9930
|
+
sectionLabel = "Cloud Policy";
|
|
9931
|
+
sectionSubtitle = "synced from node9 cloud";
|
|
9932
|
+
} else {
|
|
9933
|
+
sectionId = "user";
|
|
9934
|
+
sectionLabel = "Your Rules";
|
|
9935
|
+
sectionSubtitle = "added in node9.config.json";
|
|
9875
9936
|
}
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9937
|
+
const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
|
|
9938
|
+
const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
|
|
9939
|
+
const ruleKey = sectionId + "::" + ruleDisplayName;
|
|
9940
|
+
let rule = ruleMap.get(ruleKey);
|
|
9941
|
+
if (!rule) {
|
|
9942
|
+
rule = {
|
|
9943
|
+
name: ruleDisplayName,
|
|
9944
|
+
verdict,
|
|
9945
|
+
reason: src.rule.reason ?? "",
|
|
9946
|
+
findings: []
|
|
9947
|
+
};
|
|
9948
|
+
ruleMap.set(ruleKey, rule);
|
|
9949
|
+
section.rules.push(rule);
|
|
9950
|
+
}
|
|
9951
|
+
const cmdPreview = previewCommand(f.input, 120);
|
|
9952
|
+
const fullCmd = fullCommandOf(f.input);
|
|
9953
|
+
const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
|
|
9954
|
+
if (!isDupe) {
|
|
9955
|
+
rule.findings.push({
|
|
9956
|
+
timestamp: f.timestamp ?? "",
|
|
9957
|
+
command: cmdPreview,
|
|
9958
|
+
fullCommand: fullCmd,
|
|
9959
|
+
project: f.project,
|
|
9960
|
+
sessionId: f.sessionId,
|
|
9961
|
+
agent: f.agent,
|
|
9962
|
+
toolName: f.toolName
|
|
9963
|
+
});
|
|
9964
|
+
}
|
|
9965
|
+
if (verdict === "block") section.blockedCount++;
|
|
9966
|
+
else section.reviewCount++;
|
|
9882
9967
|
}
|
|
9968
|
+
const sections = [...sectionMap.values()];
|
|
9969
|
+
sections.sort((a, b) => {
|
|
9970
|
+
const aTotal = a.blockedCount + a.reviewCount;
|
|
9971
|
+
const bTotal = b.blockedCount + b.reviewCount;
|
|
9972
|
+
if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
|
|
9973
|
+
return bTotal - aTotal;
|
|
9974
|
+
});
|
|
9975
|
+
for (const s of sections) {
|
|
9976
|
+
s.rules.sort((a, b) => {
|
|
9977
|
+
const aBlock = a.verdict === "block" ? 1 : 0;
|
|
9978
|
+
const bBlock = b.verdict === "block" ? 1 : 0;
|
|
9979
|
+
if (bBlock !== aBlock) return bBlock - aBlock;
|
|
9980
|
+
return b.findings.length - a.findings.length;
|
|
9981
|
+
});
|
|
9982
|
+
}
|
|
9983
|
+
return sections;
|
|
9883
9984
|
}
|
|
9884
|
-
function
|
|
9885
|
-
|
|
9886
|
-
const
|
|
9887
|
-
|
|
9888
|
-
const scoreDelta = current.score - previous.score;
|
|
9889
|
-
const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
|
|
9890
|
-
if (scoreDelta === 0 && daysAgo === 0) return null;
|
|
9891
|
-
return { scoreDelta, daysAgo };
|
|
9985
|
+
function previewCommand(input, max) {
|
|
9986
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9987
|
+
const s = String(raw).replace(/\s+/g, " ").trim();
|
|
9988
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9892
9989
|
}
|
|
9893
|
-
function
|
|
9894
|
-
|
|
9895
|
-
|
|
9896
|
-
return typeof r.timestamp === "string" && typeof r.score === "number" && typeof r.blocked === "number" && typeof r.review === "number" && typeof r.leaks === "number" && typeof r.loops === "number" && typeof r.totalCalls === "number";
|
|
9990
|
+
function fullCommandOf(input) {
|
|
9991
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9992
|
+
return String(raw).replace(/\s+/g, " ").trim();
|
|
9897
9993
|
}
|
|
9898
|
-
var
|
|
9899
|
-
var
|
|
9900
|
-
"src/
|
|
9994
|
+
var AGENT_SHORT, AGENT_LONG;
|
|
9995
|
+
var init_scan_summary = __esm({
|
|
9996
|
+
"src/scan-summary.ts"() {
|
|
9901
9997
|
"use strict";
|
|
9902
|
-
|
|
9998
|
+
init_shields();
|
|
9999
|
+
init_dist();
|
|
10000
|
+
init_dist();
|
|
10001
|
+
AGENT_SHORT = {
|
|
10002
|
+
claude: "Claude",
|
|
10003
|
+
gemini: "Gemini",
|
|
10004
|
+
codex: "Codex",
|
|
10005
|
+
antigravity: "Agy",
|
|
10006
|
+
copilot: "Copilot",
|
|
10007
|
+
shell: "Shell"
|
|
10008
|
+
};
|
|
10009
|
+
AGENT_LONG = {
|
|
10010
|
+
claude: "Claude Code",
|
|
10011
|
+
gemini: "Gemini CLI",
|
|
10012
|
+
codex: "Codex",
|
|
10013
|
+
antigravity: "Antigravity",
|
|
10014
|
+
copilot: "GitHub Copilot",
|
|
10015
|
+
shell: "Shell"
|
|
10016
|
+
};
|
|
9903
10017
|
}
|
|
9904
10018
|
});
|
|
9905
10019
|
|
|
9906
|
-
// src/
|
|
9907
|
-
import
|
|
9908
|
-
import
|
|
9909
|
-
import
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
}
|
|
9913
|
-
function readCache() {
|
|
9914
|
-
try {
|
|
9915
|
-
const raw = JSON.parse(fs16.readFileSync(CACHE_FILE(), "utf-8"));
|
|
9916
|
-
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
9917
|
-
return null;
|
|
9918
|
-
}
|
|
9919
|
-
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
9920
|
-
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
9921
|
-
return raw.prices;
|
|
9922
|
-
} catch {
|
|
9923
|
-
return null;
|
|
9924
|
-
}
|
|
9925
|
-
}
|
|
9926
|
-
function writeCache(prices) {
|
|
9927
|
-
try {
|
|
9928
|
-
const target = CACHE_FILE();
|
|
9929
|
-
const dir = path18.dirname(target);
|
|
9930
|
-
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
9931
|
-
const tmp = target + ".tmp";
|
|
9932
|
-
const body = {
|
|
9933
|
-
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9934
|
-
prices
|
|
9935
|
-
};
|
|
9936
|
-
fs16.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
9937
|
-
fs16.renameSync(tmp, target);
|
|
9938
|
-
} catch (err2) {
|
|
9939
|
-
try {
|
|
9940
|
-
fs16.appendFileSync(
|
|
9941
|
-
HOOK_DEBUG_LOG,
|
|
9942
|
-
`[pricing] cache write failed: ${err2.message}
|
|
9943
|
-
`
|
|
9944
|
-
);
|
|
9945
|
-
} catch {
|
|
9946
|
-
}
|
|
9947
|
-
}
|
|
9948
|
-
}
|
|
9949
|
-
function tupleFromLiteLLM(entry) {
|
|
9950
|
-
if (!entry || typeof entry !== "object") return null;
|
|
9951
|
-
const e = entry;
|
|
9952
|
-
const num3 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
9953
|
-
const inCost = num3(e.input_cost_per_token);
|
|
9954
|
-
const outCost = num3(e.output_cost_per_token);
|
|
9955
|
-
if (inCost === 0 && outCost === 0) return null;
|
|
10020
|
+
// src/cli/commands/blast.ts
|
|
10021
|
+
import chalk2 from "chalk";
|
|
10022
|
+
import fs17 from "fs";
|
|
10023
|
+
import path19 from "path";
|
|
10024
|
+
import os16 from "os";
|
|
10025
|
+
function buildSensitivePaths(home, cwd) {
|
|
9956
10026
|
return [
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
10027
|
+
{
|
|
10028
|
+
full: path19.join(home, ".ssh", "id_rsa"),
|
|
10029
|
+
label: "~/.ssh/id_rsa",
|
|
10030
|
+
description: "RSA private key \u2014 grants SSH access to your servers",
|
|
10031
|
+
score: 20
|
|
10032
|
+
},
|
|
10033
|
+
{
|
|
10034
|
+
full: path19.join(home, ".ssh", "id_ed25519"),
|
|
10035
|
+
label: "~/.ssh/id_ed25519",
|
|
10036
|
+
description: "Ed25519 private key \u2014 grants SSH access to your servers",
|
|
10037
|
+
score: 20
|
|
10038
|
+
},
|
|
10039
|
+
{
|
|
10040
|
+
full: path19.join(home, ".ssh", "id_ecdsa"),
|
|
10041
|
+
label: "~/.ssh/id_ecdsa",
|
|
10042
|
+
description: "ECDSA private key \u2014 grants SSH access to your servers",
|
|
10043
|
+
score: 20
|
|
10044
|
+
},
|
|
10045
|
+
{
|
|
10046
|
+
full: path19.join(home, ".aws", "credentials"),
|
|
10047
|
+
label: "~/.aws/credentials",
|
|
10048
|
+
description: "AWS access keys \u2014 full cloud account access",
|
|
10049
|
+
score: 20
|
|
10050
|
+
},
|
|
10051
|
+
{
|
|
10052
|
+
full: path19.join(home, ".aws", "config"),
|
|
10053
|
+
label: "~/.aws/config",
|
|
10054
|
+
description: "AWS configuration \u2014 account and region settings",
|
|
10055
|
+
score: 5
|
|
10056
|
+
},
|
|
10057
|
+
{
|
|
10058
|
+
full: path19.join(home, ".config", "gcloud", "credentials.db"),
|
|
10059
|
+
label: "~/.config/gcloud/credentials.db",
|
|
10060
|
+
description: "Google Cloud credentials",
|
|
10061
|
+
score: 15
|
|
10062
|
+
},
|
|
10063
|
+
{
|
|
10064
|
+
full: path19.join(home, ".docker", "config.json"),
|
|
10065
|
+
label: "~/.docker/config.json",
|
|
10066
|
+
description: "Docker registry auth tokens",
|
|
10067
|
+
score: 10
|
|
10068
|
+
},
|
|
10069
|
+
{
|
|
10070
|
+
full: path19.join(home, ".netrc"),
|
|
10071
|
+
label: "~/.netrc",
|
|
10072
|
+
description: "FTP/HTTP credentials in plain text",
|
|
10073
|
+
score: 15
|
|
10074
|
+
},
|
|
10075
|
+
{
|
|
10076
|
+
full: path19.join(home, ".npmrc"),
|
|
10077
|
+
label: "~/.npmrc",
|
|
10078
|
+
description: "npm auth token \u2014 can publish packages as you",
|
|
10079
|
+
score: 10
|
|
10080
|
+
},
|
|
10081
|
+
{
|
|
10082
|
+
full: path19.join(home, ".node9", "credentials.json"),
|
|
10083
|
+
label: "~/.node9/credentials.json",
|
|
10084
|
+
description: "Node9 cloud API key",
|
|
10085
|
+
score: 10
|
|
10086
|
+
},
|
|
10087
|
+
{
|
|
10088
|
+
full: path19.join(cwd, ".env"),
|
|
10089
|
+
label: ".env (current folder)",
|
|
10090
|
+
description: "App secrets \u2014 database passwords, API keys",
|
|
10091
|
+
score: 20
|
|
10092
|
+
},
|
|
10093
|
+
{
|
|
10094
|
+
full: path19.join(cwd, ".env.local"),
|
|
10095
|
+
label: ".env.local (current folder)",
|
|
10096
|
+
description: "Local overrides \u2014 often contains real credentials",
|
|
10097
|
+
score: 15
|
|
10098
|
+
},
|
|
10099
|
+
{
|
|
10100
|
+
full: path19.join(cwd, ".env.production"),
|
|
10101
|
+
label: ".env.production (current folder)",
|
|
10102
|
+
description: "Production secrets",
|
|
10103
|
+
score: 20
|
|
10104
|
+
}
|
|
9961
10105
|
];
|
|
9962
10106
|
}
|
|
9963
|
-
|
|
10107
|
+
function isReadable(filePath) {
|
|
9964
10108
|
try {
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
});
|
|
9968
|
-
if (!res.ok) return null;
|
|
9969
|
-
const json = await res.json();
|
|
9970
|
-
if (!json || typeof json !== "object") return null;
|
|
9971
|
-
const out = {};
|
|
9972
|
-
for (const [key, value] of Object.entries(json)) {
|
|
9973
|
-
const tuple = tupleFromLiteLLM(value);
|
|
9974
|
-
if (tuple) out[key.toLowerCase()] = tuple;
|
|
9975
|
-
}
|
|
9976
|
-
if (Object.keys(out).length < 10) {
|
|
9977
|
-
return null;
|
|
9978
|
-
}
|
|
9979
|
-
return out;
|
|
10109
|
+
fs17.accessSync(filePath, fs17.constants.R_OK);
|
|
10110
|
+
return true;
|
|
9980
10111
|
} catch {
|
|
9981
|
-
return
|
|
10112
|
+
return false;
|
|
9982
10113
|
}
|
|
9983
10114
|
}
|
|
9984
|
-
|
|
9985
|
-
if (
|
|
9986
|
-
|
|
9987
|
-
if (
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
10115
|
+
function scoreLabel(score) {
|
|
10116
|
+
if (score >= 80) return chalk2.green(`${score}/100 Good`);
|
|
10117
|
+
if (score >= 50) return chalk2.yellow(`${score}/100 Moderate risk`);
|
|
10118
|
+
if (score >= 25) return chalk2.red(`${score}/100 High risk`);
|
|
10119
|
+
return chalk2.red.bold(`${score}/100 Critical`);
|
|
10120
|
+
}
|
|
10121
|
+
function runBlast() {
|
|
10122
|
+
const home = os16.homedir();
|
|
10123
|
+
const cwd = process.cwd();
|
|
10124
|
+
const paths = buildSensitivePaths(home, cwd);
|
|
10125
|
+
let scoreDeduction = 0;
|
|
10126
|
+
const reachable = [];
|
|
10127
|
+
for (const p of paths) {
|
|
10128
|
+
if (fs17.existsSync(p.full) && isReadable(p.full)) {
|
|
10129
|
+
reachable.push(p);
|
|
10130
|
+
scoreDeduction += p.score;
|
|
10131
|
+
}
|
|
9992
10132
|
}
|
|
9993
|
-
const
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10133
|
+
const envFindings = [];
|
|
10134
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
10135
|
+
if (!value) continue;
|
|
10136
|
+
const match = scanArgs({ [key]: value });
|
|
10137
|
+
if (match) {
|
|
10138
|
+
envFindings.push({ key, patternName: match.patternName });
|
|
10139
|
+
scoreDeduction += 10;
|
|
10140
|
+
}
|
|
10000
10141
|
}
|
|
10001
|
-
|
|
10002
|
-
memCacheAt = Date.now();
|
|
10003
|
-
lookupCache.clear();
|
|
10142
|
+
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
10004
10143
|
}
|
|
10005
|
-
function
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10144
|
+
function registerBlastCommand(program2) {
|
|
10145
|
+
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
10146
|
+
const home = os16.homedir();
|
|
10147
|
+
const cwd = process.cwd();
|
|
10148
|
+
const { reachable, envFindings, score } = runBlast();
|
|
10149
|
+
console.log("");
|
|
10150
|
+
console.log(
|
|
10151
|
+
chalk2.bold(" \u{1F52D} Node9 Blast Radius") + chalk2.dim(" \xB7 what an AI agent can reach from here")
|
|
10152
|
+
);
|
|
10153
|
+
console.log(chalk2.dim(" Running in: ") + chalk2.white(cwd.replace(home, "~")));
|
|
10154
|
+
console.log("");
|
|
10155
|
+
if (reachable.length > 0) {
|
|
10156
|
+
console.log(" " + chalk2.red.bold("Sensitive files reachable:"));
|
|
10157
|
+
for (const p of reachable) {
|
|
10158
|
+
console.log(
|
|
10159
|
+
" " + chalk2.red("\u2717 ") + chalk2.yellow(p.label.padEnd(38)) + chalk2.dim(p.description)
|
|
10160
|
+
);
|
|
10161
|
+
}
|
|
10162
|
+
console.log("");
|
|
10018
10163
|
}
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10164
|
+
if (envFindings.length > 0) {
|
|
10165
|
+
console.log(" " + chalk2.red.bold("Secrets in active environment:"));
|
|
10166
|
+
for (const f of envFindings) {
|
|
10167
|
+
console.log(
|
|
10168
|
+
" " + chalk2.red("\u2717 ") + chalk2.yellow(f.key.padEnd(38)) + chalk2.dim(f.patternName)
|
|
10169
|
+
);
|
|
10023
10170
|
}
|
|
10171
|
+
console.log("");
|
|
10024
10172
|
}
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10173
|
+
console.log(" " + chalk2.dim("\u2500".repeat(70)));
|
|
10174
|
+
if (reachable.length === 0 && envFindings.length === 0) {
|
|
10175
|
+
console.log(" " + chalk2.green("\u2705 No sensitive files or environment secrets found."));
|
|
10176
|
+
console.log(" Security Score: " + scoreLabel(score));
|
|
10177
|
+
} else {
|
|
10178
|
+
console.log(
|
|
10179
|
+
" Security Score: " + scoreLabel(score) + chalk2.dim(
|
|
10180
|
+
` (${reachable.length} file${reachable.length !== 1 ? "s" : ""}, ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""})`
|
|
10181
|
+
)
|
|
10182
|
+
);
|
|
10183
|
+
console.log("");
|
|
10184
|
+
console.log(
|
|
10185
|
+
chalk2.dim(
|
|
10186
|
+
" Every AI agent you start can read the files and env vars listed above.\n Run `node9 shield enable project-jail` to restrict agent file access.\n Run `node9 mask` to redact secrets from existing session history."
|
|
10187
|
+
)
|
|
10188
|
+
);
|
|
10028
10189
|
}
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
return resolved;
|
|
10190
|
+
console.log("");
|
|
10191
|
+
});
|
|
10032
10192
|
}
|
|
10033
|
-
var
|
|
10034
|
-
|
|
10035
|
-
"src/pricing/litellm.ts"() {
|
|
10193
|
+
var init_blast = __esm({
|
|
10194
|
+
"src/cli/commands/blast.ts"() {
|
|
10036
10195
|
"use strict";
|
|
10037
|
-
|
|
10038
|
-
LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
10039
|
-
BUNDLED_PRICING = {
|
|
10040
|
-
// Anthropic
|
|
10041
|
-
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10042
|
-
"claude-opus-4-1": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10043
|
-
"claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10044
|
-
"claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10045
|
-
"claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10046
|
-
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10047
|
-
"claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10048
|
-
"claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10049
|
-
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10050
|
-
"claude-haiku-4-5": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10051
|
-
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10052
|
-
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10053
|
-
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10054
|
-
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
10055
|
-
// OpenAI
|
|
10056
|
-
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
10057
|
-
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
10058
|
-
"gpt-5": [1e-5, 3e-5, 0, 5e-6],
|
|
10059
|
-
// Google
|
|
10060
|
-
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
10061
|
-
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
10062
|
-
};
|
|
10063
|
-
CACHE_FILE = () => path18.join(os15.homedir(), ".node9", "model-pricing.json");
|
|
10064
|
-
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10065
|
-
memCache = null;
|
|
10066
|
-
memCacheAt = 0;
|
|
10067
|
-
lookupCache = /* @__PURE__ */ new Map();
|
|
10196
|
+
init_dlp();
|
|
10068
10197
|
}
|
|
10069
10198
|
});
|
|
10070
10199
|
|
|
10071
|
-
// src/
|
|
10072
|
-
import
|
|
10073
|
-
import
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10200
|
+
// src/cli/render/scan-derive.ts
|
|
10201
|
+
import chalk3 from "chalk";
|
|
10202
|
+
import stringWidth from "string-width";
|
|
10203
|
+
function classifyScore(score) {
|
|
10204
|
+
if (score >= 80) return { band: "good", label: "Good", color: chalk3.green };
|
|
10205
|
+
if (score >= 50) return { band: "at-risk", label: "At Risk", color: chalk3.yellow };
|
|
10206
|
+
return { band: "critical", label: "Critical", color: chalk3.red };
|
|
10077
10207
|
}
|
|
10078
|
-
function
|
|
10079
|
-
|
|
10208
|
+
function topDlpPatterns(findings, n) {
|
|
10209
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10210
|
+
for (const f of findings) {
|
|
10211
|
+
counts.set(f.patternName, (counts.get(f.patternName) ?? 0) + 1);
|
|
10212
|
+
}
|
|
10213
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name, count]) => ({ name, count }));
|
|
10080
10214
|
}
|
|
10081
|
-
function
|
|
10082
|
-
const
|
|
10083
|
-
for (const
|
|
10084
|
-
const
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
const mp = path19.join(yp, m);
|
|
10088
|
-
if (!isDir(mp)) continue;
|
|
10089
|
-
for (const d of safeReaddir(mp)) {
|
|
10090
|
-
const dp = path19.join(mp, d);
|
|
10091
|
-
if (!isDir(dp)) continue;
|
|
10092
|
-
for (const f of safeReaddir(dp)) {
|
|
10093
|
-
if (f.endsWith(".jsonl")) out.push(path19.join(dp, f));
|
|
10094
|
-
}
|
|
10095
|
-
}
|
|
10215
|
+
function topRulesByVerdict(sections, verdict, n) {
|
|
10216
|
+
const matched = [];
|
|
10217
|
+
for (const section of sections) {
|
|
10218
|
+
for (const rule of section.rules) {
|
|
10219
|
+
const matches = verdict === "block" ? rule.verdict === "block" : rule.verdict !== "block";
|
|
10220
|
+
if (matches) matched.push({ name: rule.name, count: rule.findings.length });
|
|
10096
10221
|
}
|
|
10097
10222
|
}
|
|
10098
|
-
return
|
|
10223
|
+
return matched.sort((a, b) => b.count - a.count).slice(0, n);
|
|
10099
10224
|
}
|
|
10100
|
-
function
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10225
|
+
function computeLoopWaste(loops, totalToolCalls) {
|
|
10226
|
+
const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
10227
|
+
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
10228
|
+
return { wastedCalls, wastePct };
|
|
10229
|
+
}
|
|
10230
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
10231
|
+
const out = [];
|
|
10232
|
+
for (const section of sections) {
|
|
10233
|
+
if (section.sourceType !== "shield") continue;
|
|
10234
|
+
if (!section.shieldKey) continue;
|
|
10235
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
10236
|
+
const topRuleLabels = [...section.rules].sort((a, b) => b.findings.length - a.findings.length).slice(0, topRulesPerShield).map((r) => r.findings.length > 1 ? `${r.name} \xD7${r.findings.length}` : r.name);
|
|
10237
|
+
out.push({
|
|
10238
|
+
shieldName: section.shieldKey,
|
|
10239
|
+
totalCatches,
|
|
10240
|
+
blockCatches: section.blockedCount,
|
|
10241
|
+
reviewCatches: section.reviewCount,
|
|
10242
|
+
topRuleLabels
|
|
10243
|
+
});
|
|
10105
10244
|
}
|
|
10245
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
10106
10246
|
}
|
|
10107
|
-
function
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10247
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
10248
|
+
const inner = width - 4;
|
|
10249
|
+
const out = [];
|
|
10250
|
+
const titlePad = ` ${title} `;
|
|
10251
|
+
const titleWidth = stringWidth(titlePad);
|
|
10252
|
+
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
10253
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - stringWidth(titleSegment)));
|
|
10254
|
+
out.push(chalk3.dim("\u256D\u2500") + chalk3.bold(titleSegment) + chalk3.dim(`${dashFill}\u2500\u256E`));
|
|
10255
|
+
for (const line of bodyLines) {
|
|
10256
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
10257
|
+
out.push(chalk3.dim("\u2502 ") + line.rendered + padding + chalk3.dim(" \u2502"));
|
|
10112
10258
|
}
|
|
10259
|
+
out.push(chalk3.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
10260
|
+
return out;
|
|
10113
10261
|
}
|
|
10114
|
-
function
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
entry = JSON.parse(raw);
|
|
10128
|
-
} catch {
|
|
10129
|
-
continue;
|
|
10130
|
-
}
|
|
10131
|
-
const p = entry.payload ?? {};
|
|
10132
|
-
if (entry.type === "session_meta") {
|
|
10133
|
-
if (!sessionStart2 && typeof p["timestamp"] === "string") sessionStart2 = p["timestamp"];
|
|
10134
|
-
if (!runId && typeof p["id"] === "string") runId = p["id"];
|
|
10135
|
-
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
10136
|
-
continue;
|
|
10137
|
-
}
|
|
10138
|
-
if (entry.type === "turn_context") {
|
|
10139
|
-
if (typeof p["model"] === "string") model = p["model"];
|
|
10140
|
-
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
10141
|
-
continue;
|
|
10142
|
-
}
|
|
10143
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
10144
|
-
const info = p["info"] ?? {};
|
|
10145
|
-
const usage = info["total_token_usage"] ?? {};
|
|
10146
|
-
if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
|
|
10147
|
-
if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
|
|
10148
|
-
if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
|
|
10149
|
-
sawUsage = true;
|
|
10150
|
-
}
|
|
10262
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
10263
|
+
const t = new Date(timestamp).getTime();
|
|
10264
|
+
if (Number.isNaN(t)) return "?";
|
|
10265
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
10266
|
+
if (days < 1) return "today";
|
|
10267
|
+
if (days > 90) return "90d+";
|
|
10268
|
+
return `${days}d`;
|
|
10269
|
+
}
|
|
10270
|
+
var PANEL_WIDTH;
|
|
10271
|
+
var init_scan_derive = __esm({
|
|
10272
|
+
"src/cli/render/scan-derive.ts"() {
|
|
10273
|
+
"use strict";
|
|
10274
|
+
PANEL_WIDTH = 76;
|
|
10151
10275
|
}
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10276
|
+
});
|
|
10277
|
+
|
|
10278
|
+
// src/protection.ts
|
|
10279
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
10280
|
+
var init_protection = __esm({
|
|
10281
|
+
"src/protection.ts"() {
|
|
10282
|
+
"use strict";
|
|
10283
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
10284
|
+
"project-jail": 0.7
|
|
10285
|
+
};
|
|
10286
|
+
}
|
|
10287
|
+
});
|
|
10288
|
+
|
|
10289
|
+
// src/cli/render/scan-json.ts
|
|
10290
|
+
function buildScanJson(input) {
|
|
10291
|
+
const { summary, blast, isWired, generatedAt } = input;
|
|
10292
|
+
const { band } = classifyScore(blast.score);
|
|
10158
10293
|
return {
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
10294
|
+
schemaVersion: 1,
|
|
10295
|
+
generatedAt,
|
|
10296
|
+
isWired,
|
|
10297
|
+
score: blast.score,
|
|
10298
|
+
band,
|
|
10299
|
+
totals: {
|
|
10300
|
+
blocked: summary.byVerdict.blocked,
|
|
10301
|
+
review: summary.byVerdict.supervised,
|
|
10302
|
+
leaks: summary.byVerdict.leaks,
|
|
10303
|
+
loops: summary.byVerdict.loops,
|
|
10304
|
+
blastExposures: blast.reachable.length + blast.envFindings.length
|
|
10305
|
+
},
|
|
10306
|
+
summary,
|
|
10307
|
+
blast: {
|
|
10308
|
+
score: blast.score,
|
|
10309
|
+
reachable: blast.reachable,
|
|
10310
|
+
envFindings: blast.envFindings
|
|
10311
|
+
}
|
|
10168
10312
|
};
|
|
10169
10313
|
}
|
|
10170
|
-
var
|
|
10171
|
-
|
|
10172
|
-
"src/cost-codex.ts"() {
|
|
10314
|
+
var init_scan_json = __esm({
|
|
10315
|
+
"src/cli/render/scan-json.ts"() {
|
|
10173
10316
|
"use strict";
|
|
10174
|
-
|
|
10175
|
-
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
10176
|
-
codexSource = {
|
|
10177
|
-
id: "codex",
|
|
10178
|
-
available() {
|
|
10179
|
-
try {
|
|
10180
|
-
return fs17.existsSync(codexSessionsDir());
|
|
10181
|
-
} catch {
|
|
10182
|
-
return false;
|
|
10183
|
-
}
|
|
10184
|
-
},
|
|
10185
|
-
collect(sinceMs) {
|
|
10186
|
-
const base = codexSessionsDir();
|
|
10187
|
-
const combined = /* @__PURE__ */ new Map();
|
|
10188
|
-
for (const file of listCodexSessionFiles(base)) {
|
|
10189
|
-
try {
|
|
10190
|
-
if (sinceMs !== void 0 && fs17.statSync(file).mtimeMs < sinceMs) continue;
|
|
10191
|
-
} catch {
|
|
10192
|
-
continue;
|
|
10193
|
-
}
|
|
10194
|
-
let content;
|
|
10195
|
-
try {
|
|
10196
|
-
content = fs17.readFileSync(file, "utf8");
|
|
10197
|
-
} catch {
|
|
10198
|
-
continue;
|
|
10199
|
-
}
|
|
10200
|
-
const e = parseCodexSession(content.split("\n"));
|
|
10201
|
-
if (!e) continue;
|
|
10202
|
-
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
10203
|
-
const prev = combined.get(key);
|
|
10204
|
-
if (prev) {
|
|
10205
|
-
prev.costUSD += e.costUSD;
|
|
10206
|
-
prev.inputTokens += e.inputTokens;
|
|
10207
|
-
prev.outputTokens += e.outputTokens;
|
|
10208
|
-
prev.cacheReadTokens += e.cacheReadTokens;
|
|
10209
|
-
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
10210
|
-
} else {
|
|
10211
|
-
combined.set(key, { ...e });
|
|
10212
|
-
}
|
|
10213
|
-
}
|
|
10214
|
-
return [...combined.values()];
|
|
10215
|
-
}
|
|
10216
|
-
};
|
|
10317
|
+
init_scan_derive();
|
|
10217
10318
|
}
|
|
10218
10319
|
});
|
|
10219
10320
|
|
|
10220
|
-
// src/
|
|
10321
|
+
// src/cli/render/scan-history.ts
|
|
10221
10322
|
import fs18 from "fs";
|
|
10222
|
-
import os17 from "os";
|
|
10223
10323
|
import path20 from "path";
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
function geminiPriceFor(model) {
|
|
10228
|
-
let tuple = pricingFor(model);
|
|
10229
|
-
if (!tuple && /^gemini-/i.test(model)) {
|
|
10230
|
-
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
10231
|
-
tuple = pricingFor(proxy);
|
|
10232
|
-
if (tuple) break;
|
|
10233
|
-
}
|
|
10234
|
-
}
|
|
10235
|
-
if (!tuple) return null;
|
|
10236
|
-
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
10324
|
+
import os17 from "os";
|
|
10325
|
+
function defaultHistoryPath() {
|
|
10326
|
+
return path20.join(os17.homedir(), ".node9", "scan-history.json");
|
|
10237
10327
|
}
|
|
10238
|
-
function
|
|
10328
|
+
function readPreviousScan(opts = {}) {
|
|
10329
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10239
10330
|
try {
|
|
10240
|
-
|
|
10331
|
+
if (!fs18.existsSync(filePath)) return null;
|
|
10332
|
+
const raw = fs18.readFileSync(filePath, "utf8");
|
|
10333
|
+
const parsed = JSON.parse(raw);
|
|
10334
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
10335
|
+
const last = parsed[parsed.length - 1];
|
|
10336
|
+
if (!isValidRecord(last)) return null;
|
|
10337
|
+
return last;
|
|
10241
10338
|
} catch {
|
|
10242
|
-
return
|
|
10339
|
+
return null;
|
|
10243
10340
|
}
|
|
10244
10341
|
}
|
|
10245
|
-
function
|
|
10342
|
+
function appendScanHistory(record, opts = {}) {
|
|
10343
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10344
|
+
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
10246
10345
|
try {
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
if (!isDir2(chats)) continue;
|
|
10257
|
-
for (const f of safeReaddir2(chats)) {
|
|
10258
|
-
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
10259
|
-
out.push({ file: path20.join(chats, f), project });
|
|
10346
|
+
fs18.mkdirSync(path20.dirname(filePath), { recursive: true });
|
|
10347
|
+
let history = [];
|
|
10348
|
+
if (fs18.existsSync(filePath)) {
|
|
10349
|
+
try {
|
|
10350
|
+
const parsed = JSON.parse(fs18.readFileSync(filePath, "utf8"));
|
|
10351
|
+
if (Array.isArray(parsed)) {
|
|
10352
|
+
history = parsed.filter(isValidRecord);
|
|
10353
|
+
}
|
|
10354
|
+
} catch {
|
|
10260
10355
|
}
|
|
10261
10356
|
}
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
function parseGeminiSession(lines, project) {
|
|
10266
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
10267
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
10268
|
-
let runId = "";
|
|
10269
|
-
for (const raw of lines) {
|
|
10270
|
-
if (!raw.trim()) continue;
|
|
10271
|
-
let obj;
|
|
10272
|
-
try {
|
|
10273
|
-
obj = JSON.parse(raw);
|
|
10274
|
-
} catch {
|
|
10275
|
-
continue;
|
|
10276
|
-
}
|
|
10277
|
-
if (typeof obj.sessionId === "string" && !runId) runId = obj.sessionId;
|
|
10278
|
-
if (!obj.tokens || !obj.model || !obj.timestamp) continue;
|
|
10279
|
-
if (obj.id) {
|
|
10280
|
-
if (seenIds.has(obj.id)) continue;
|
|
10281
|
-
seenIds.add(obj.id);
|
|
10282
|
-
}
|
|
10283
|
-
const price = geminiPriceFor(obj.model);
|
|
10284
|
-
if (!price) continue;
|
|
10285
|
-
const inp = obj.tokens.input ?? 0;
|
|
10286
|
-
const out = obj.tokens.output ?? 0;
|
|
10287
|
-
const cached = Math.min(obj.tokens.cached ?? 0, inp);
|
|
10288
|
-
const fresh = Math.max(0, inp - cached);
|
|
10289
|
-
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
10290
|
-
const date = obj.timestamp.slice(0, 10);
|
|
10291
|
-
const model = normalizeModel(obj.model);
|
|
10292
|
-
const key = `${date}::${model}`;
|
|
10293
|
-
const prev = byKey.get(key);
|
|
10294
|
-
if (prev) {
|
|
10295
|
-
prev.costUSD += cost;
|
|
10296
|
-
prev.inputTokens += fresh;
|
|
10297
|
-
prev.outputTokens += out;
|
|
10298
|
-
prev.cacheReadTokens += cached;
|
|
10299
|
-
} else {
|
|
10300
|
-
byKey.set(key, {
|
|
10301
|
-
date,
|
|
10302
|
-
model,
|
|
10303
|
-
workingDir: project,
|
|
10304
|
-
runId,
|
|
10305
|
-
costUSD: cost,
|
|
10306
|
-
inputTokens: fresh,
|
|
10307
|
-
outputTokens: out,
|
|
10308
|
-
cacheReadTokens: cached,
|
|
10309
|
-
cacheWriteTokens: 0
|
|
10310
|
-
});
|
|
10357
|
+
history.push(record);
|
|
10358
|
+
if (history.length > cap) {
|
|
10359
|
+
history = history.slice(history.length - cap);
|
|
10311
10360
|
}
|
|
10361
|
+
fs18.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
10362
|
+
} catch (err2) {
|
|
10363
|
+
process.stderr.write(
|
|
10364
|
+
`[node9] Warning: could not write scan-history.json: ${err2.message}
|
|
10365
|
+
`
|
|
10366
|
+
);
|
|
10312
10367
|
}
|
|
10313
|
-
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
10314
|
-
return [...byKey.values()];
|
|
10315
10368
|
}
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10369
|
+
function computeScanDelta(current, previous, now = Date.now()) {
|
|
10370
|
+
if (!previous) return null;
|
|
10371
|
+
const prevMs = Date.parse(previous.timestamp);
|
|
10372
|
+
if (Number.isNaN(prevMs)) return null;
|
|
10373
|
+
const scoreDelta = current.score - previous.score;
|
|
10374
|
+
const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
|
|
10375
|
+
if (scoreDelta === 0 && daysAgo === 0) return null;
|
|
10376
|
+
return { scoreDelta, daysAgo };
|
|
10377
|
+
}
|
|
10378
|
+
function isValidRecord(x) {
|
|
10379
|
+
if (typeof x !== "object" || x === null) return false;
|
|
10380
|
+
const r = x;
|
|
10381
|
+
return typeof r.timestamp === "string" && typeof r.score === "number" && typeof r.blocked === "number" && typeof r.review === "number" && typeof r.leaks === "number" && typeof r.loops === "number" && typeof r.totalCalls === "number";
|
|
10382
|
+
}
|
|
10383
|
+
var SCAN_HISTORY_CAP;
|
|
10384
|
+
var init_scan_history = __esm({
|
|
10385
|
+
"src/cli/render/scan-history.ts"() {
|
|
10319
10386
|
"use strict";
|
|
10320
|
-
|
|
10321
|
-
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
10322
|
-
geminiSource = {
|
|
10323
|
-
id: "gemini",
|
|
10324
|
-
available() {
|
|
10325
|
-
try {
|
|
10326
|
-
return fs18.existsSync(geminiTmpDir());
|
|
10327
|
-
} catch {
|
|
10328
|
-
return false;
|
|
10329
|
-
}
|
|
10330
|
-
},
|
|
10331
|
-
collect(sinceMs) {
|
|
10332
|
-
const combined = /* @__PURE__ */ new Map();
|
|
10333
|
-
for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
10334
|
-
try {
|
|
10335
|
-
if (sinceMs !== void 0 && fs18.statSync(file).mtimeMs < sinceMs) continue;
|
|
10336
|
-
} catch {
|
|
10337
|
-
continue;
|
|
10338
|
-
}
|
|
10339
|
-
let content;
|
|
10340
|
-
try {
|
|
10341
|
-
content = fs18.readFileSync(file, "utf8");
|
|
10342
|
-
} catch {
|
|
10343
|
-
continue;
|
|
10344
|
-
}
|
|
10345
|
-
for (const e of parseGeminiSession(content.split("\n"), project)) {
|
|
10346
|
-
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
10347
|
-
const prev = combined.get(key);
|
|
10348
|
-
if (prev) {
|
|
10349
|
-
prev.costUSD += e.costUSD;
|
|
10350
|
-
prev.inputTokens += e.inputTokens;
|
|
10351
|
-
prev.outputTokens += e.outputTokens;
|
|
10352
|
-
prev.cacheReadTokens += e.cacheReadTokens;
|
|
10353
|
-
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
10354
|
-
} else {
|
|
10355
|
-
combined.set(key, { ...e });
|
|
10356
|
-
}
|
|
10357
|
-
}
|
|
10358
|
-
}
|
|
10359
|
-
return [...combined.values()];
|
|
10360
|
-
}
|
|
10361
|
-
};
|
|
10387
|
+
SCAN_HISTORY_CAP = 30;
|
|
10362
10388
|
}
|
|
10363
10389
|
});
|
|
10364
10390
|
|
|
@@ -11402,19 +11428,15 @@ import path25 from "path";
|
|
|
11402
11428
|
import os22 from "os";
|
|
11403
11429
|
import stringWidth2 from "string-width";
|
|
11404
11430
|
function claudeModelPrice(model) {
|
|
11405
|
-
const
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
}
|
|
11409
|
-
return null;
|
|
11431
|
+
const t = pricingFor(model);
|
|
11432
|
+
if (!t) return null;
|
|
11433
|
+
const [i, o, cw, cr] = t;
|
|
11434
|
+
return { i, o, cw, cr };
|
|
11410
11435
|
}
|
|
11411
11436
|
function geminiModelPrice(model) {
|
|
11412
|
-
const
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
}
|
|
11416
|
-
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
11417
|
-
return null;
|
|
11437
|
+
const p = geminiPriceFor(model);
|
|
11438
|
+
if (!p) return null;
|
|
11439
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
11418
11440
|
}
|
|
11419
11441
|
function isNode9SelfOutput(text) {
|
|
11420
11442
|
let hits = 0;
|
|
@@ -12060,14 +12082,17 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12060
12082
|
if (!fs23.existsSync(chatsDir)) continue;
|
|
12061
12083
|
let chatFiles;
|
|
12062
12084
|
try {
|
|
12063
|
-
chatFiles = fs23.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
12085
|
+
chatFiles = fs23.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
12064
12086
|
} catch {
|
|
12065
12087
|
continue;
|
|
12066
12088
|
}
|
|
12089
|
+
const seenSessions = /* @__PURE__ */ new Set();
|
|
12067
12090
|
for (const chatFile of chatFiles) {
|
|
12091
|
+
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
12092
|
+
if (seenSessions.has(sessionId)) continue;
|
|
12093
|
+
seenSessions.add(sessionId);
|
|
12068
12094
|
result.filesScanned++;
|
|
12069
12095
|
onProgress?.(result.filesScanned);
|
|
12070
|
-
const sessionId = chatFile.replace(/\.json$/, "");
|
|
12071
12096
|
let raw;
|
|
12072
12097
|
try {
|
|
12073
12098
|
raw = fs23.readFileSync(path25.join(chatsDir, chatFile), "utf-8");
|
|
@@ -12077,7 +12102,18 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12077
12102
|
const sessionCalls = [];
|
|
12078
12103
|
let session;
|
|
12079
12104
|
try {
|
|
12080
|
-
|
|
12105
|
+
if (chatFile.endsWith(".jsonl")) {
|
|
12106
|
+
const messages = raw.split("\n").filter((l) => l.trim()).map((l) => {
|
|
12107
|
+
try {
|
|
12108
|
+
return JSON.parse(l);
|
|
12109
|
+
} catch {
|
|
12110
|
+
return null;
|
|
12111
|
+
}
|
|
12112
|
+
}).filter((m) => m !== null);
|
|
12113
|
+
session = { messages };
|
|
12114
|
+
} else {
|
|
12115
|
+
session = JSON.parse(raw);
|
|
12116
|
+
}
|
|
12081
12117
|
} catch {
|
|
12082
12118
|
continue;
|
|
12083
12119
|
}
|
|
@@ -12687,6 +12723,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12687
12723
|
let lastTotalInput = 0;
|
|
12688
12724
|
let lastTotalCached = 0;
|
|
12689
12725
|
let lastTotalOutput = 0;
|
|
12726
|
+
let model = "";
|
|
12690
12727
|
for (const line of lines) {
|
|
12691
12728
|
if (!line.trim()) continue;
|
|
12692
12729
|
onLine?.();
|
|
@@ -12704,6 +12741,10 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12704
12741
|
projLabel = stripTerminalEscapes(cwd.replace(os22.homedir(), "~")).slice(0, 40);
|
|
12705
12742
|
continue;
|
|
12706
12743
|
}
|
|
12744
|
+
if (entry.type === "turn_context" && typeof payload["model"] === "string") {
|
|
12745
|
+
model = payload["model"];
|
|
12746
|
+
continue;
|
|
12747
|
+
}
|
|
12707
12748
|
if (entry.type === "event_msg" && payload["type"] === "token_count") {
|
|
12708
12749
|
const info = payload["info"];
|
|
12709
12750
|
const usage = info?.["total_token_usage"] ?? {};
|
|
@@ -12847,8 +12888,11 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12847
12888
|
}
|
|
12848
12889
|
}
|
|
12849
12890
|
}
|
|
12850
|
-
|
|
12851
|
-
|
|
12891
|
+
result.totalCostUSD += codexSessionCost(model, {
|
|
12892
|
+
input: lastTotalInput,
|
|
12893
|
+
cached: lastTotalCached,
|
|
12894
|
+
output: lastTotalOutput
|
|
12895
|
+
});
|
|
12852
12896
|
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
|
|
12853
12897
|
}
|
|
12854
12898
|
return result;
|
|
@@ -13895,7 +13939,7 @@ function registerScanCommand(program2) {
|
|
|
13895
13939
|
}
|
|
13896
13940
|
);
|
|
13897
13941
|
}
|
|
13898
|
-
var
|
|
13942
|
+
var CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, classifyRuleSeverity2, narrativeRuleLabel2;
|
|
13899
13943
|
var init_scan = __esm({
|
|
13900
13944
|
"src/cli/commands/scan.ts"() {
|
|
13901
13945
|
"use strict";
|
|
@@ -13904,6 +13948,9 @@ var init_scan = __esm({
|
|
|
13904
13948
|
init_policy();
|
|
13905
13949
|
init_dist();
|
|
13906
13950
|
init_dlp();
|
|
13951
|
+
init_litellm();
|
|
13952
|
+
init_cost_gemini();
|
|
13953
|
+
init_cost_codex();
|
|
13907
13954
|
init_hook_payload();
|
|
13908
13955
|
init_dist();
|
|
13909
13956
|
init_scan_summary();
|
|
@@ -13913,26 +13960,6 @@ var init_scan = __esm({
|
|
|
13913
13960
|
init_protection();
|
|
13914
13961
|
init_scan_json();
|
|
13915
13962
|
init_scan_history();
|
|
13916
|
-
CLAUDE_PRICING = {
|
|
13917
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13918
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13919
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
13920
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13921
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13922
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13923
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13924
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13925
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
13926
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
13927
|
-
};
|
|
13928
|
-
GEMINI_PRICING = {
|
|
13929
|
-
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
13930
|
-
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
13931
|
-
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
13932
|
-
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
13933
|
-
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
13934
|
-
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
13935
|
-
};
|
|
13936
13963
|
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13937
13964
|
".ts",
|
|
13938
13965
|
".tsx",
|
|
@@ -20108,6 +20135,7 @@ import chalk13 from "chalk";
|
|
|
20108
20135
|
// src/cli/aggregate/report-audit.ts
|
|
20109
20136
|
init_costSync();
|
|
20110
20137
|
init_litellm();
|
|
20138
|
+
init_cost_codex();
|
|
20111
20139
|
import fs40 from "fs";
|
|
20112
20140
|
import os36 from "os";
|
|
20113
20141
|
import path41 from "path";
|
|
@@ -20207,24 +20235,11 @@ function isAllow(decision) {
|
|
|
20207
20235
|
function isDlp(checkedBy) {
|
|
20208
20236
|
return !!checkedBy?.includes("dlp");
|
|
20209
20237
|
}
|
|
20210
|
-
var CLAUDE_PRICING2 = {
|
|
20211
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
20212
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
20213
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
20214
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20215
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20216
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20217
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20218
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20219
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
20220
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
20221
|
-
};
|
|
20222
20238
|
function claudeModelPrice2(model) {
|
|
20223
|
-
const
|
|
20224
|
-
|
|
20225
|
-
|
|
20226
|
-
}
|
|
20227
|
-
return null;
|
|
20239
|
+
const t = pricingFor(model);
|
|
20240
|
+
if (!t) return null;
|
|
20241
|
+
const [i, o, cw, cr] = t;
|
|
20242
|
+
return { i, o, cw, cr };
|
|
20228
20243
|
}
|
|
20229
20244
|
function emptyClaudeCostAccumulator() {
|
|
20230
20245
|
return {
|
|
@@ -20339,6 +20354,7 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20339
20354
|
return;
|
|
20340
20355
|
}
|
|
20341
20356
|
let sessionStart2 = "";
|
|
20357
|
+
let model = "";
|
|
20342
20358
|
let lastTotalInput = 0;
|
|
20343
20359
|
let lastTotalCached = 0;
|
|
20344
20360
|
let lastTotalOutput = 0;
|
|
@@ -20356,6 +20372,10 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20356
20372
|
sessionStart2 = String(p["timestamp"] ?? "");
|
|
20357
20373
|
continue;
|
|
20358
20374
|
}
|
|
20375
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
20376
|
+
model = p["model"];
|
|
20377
|
+
continue;
|
|
20378
|
+
}
|
|
20359
20379
|
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
20360
20380
|
const info = p["info"] ?? {};
|
|
20361
20381
|
const usage = info["total_token_usage"] ?? {};
|
|
@@ -20370,12 +20390,17 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20370
20390
|
if (!sessionStart2) return;
|
|
20371
20391
|
const ts = new Date(sessionStart2);
|
|
20372
20392
|
if (ts < start || ts > end) return;
|
|
20373
|
-
const
|
|
20374
|
-
|
|
20393
|
+
const cost = codexSessionCost(model, {
|
|
20394
|
+
input: lastTotalInput,
|
|
20395
|
+
cached: lastTotalCached,
|
|
20396
|
+
output: lastTotalOutput
|
|
20397
|
+
});
|
|
20375
20398
|
acc.total += cost;
|
|
20376
20399
|
acc.toolCalls += sessionToolCalls;
|
|
20377
20400
|
const dateKey = sessionStart2.slice(0, 10);
|
|
20378
20401
|
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
20402
|
+
const normModel = normalizeModel(model || "gpt-5");
|
|
20403
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
20379
20404
|
}
|
|
20380
20405
|
function listCodexSessionFiles2(sessionsBase) {
|
|
20381
20406
|
const jsonlFiles = [];
|
|
@@ -20413,13 +20438,25 @@ function listCodexSessionFiles2(sessionsBase) {
|
|
|
20413
20438
|
}
|
|
20414
20439
|
return jsonlFiles;
|
|
20415
20440
|
}
|
|
20441
|
+
function mergeByModel(...maps) {
|
|
20442
|
+
const out = /* @__PURE__ */ new Map();
|
|
20443
|
+
for (const m of maps) {
|
|
20444
|
+
for (const [k, v] of m) out.set(k, (out.get(k) ?? 0) + v);
|
|
20445
|
+
}
|
|
20446
|
+
return out;
|
|
20447
|
+
}
|
|
20416
20448
|
function loadCodexCost(start, end, sessionsBase) {
|
|
20417
|
-
const acc = {
|
|
20449
|
+
const acc = {
|
|
20450
|
+
total: 0,
|
|
20451
|
+
toolCalls: 0,
|
|
20452
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
20453
|
+
byModel: /* @__PURE__ */ new Map()
|
|
20454
|
+
};
|
|
20418
20455
|
const files = listCodexSessionFiles2(sessionsBase);
|
|
20419
20456
|
for (const filePath of files) {
|
|
20420
20457
|
processCodexCostFile(filePath, start, end, acc);
|
|
20421
20458
|
}
|
|
20422
|
-
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
20459
|
+
return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
|
|
20423
20460
|
}
|
|
20424
20461
|
var GEMINI_FALLBACK_MODELS2 = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
20425
20462
|
function geminiPriceFor2(model) {
|
|
@@ -20708,7 +20745,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
20708
20745
|
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
20709
20746
|
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
20710
20747
|
byDay: claudeCost.byDay,
|
|
20711
|
-
byModel: claudeCost.byModel,
|
|
20748
|
+
byModel: mergeByModel(claudeCost.byModel, codexCost.byModel),
|
|
20712
20749
|
byProject: claudeCost.byProject
|
|
20713
20750
|
},
|
|
20714
20751
|
toolMap,
|
|
@@ -23477,44 +23514,23 @@ init_scan();
|
|
|
23477
23514
|
|
|
23478
23515
|
// src/cli/commands/sessions.ts
|
|
23479
23516
|
init_scan_summary();
|
|
23517
|
+
init_litellm();
|
|
23518
|
+
init_cost_gemini();
|
|
23519
|
+
init_cost_codex();
|
|
23480
23520
|
import chalk24 from "chalk";
|
|
23481
23521
|
import fs45 from "fs";
|
|
23482
23522
|
import path46 from "path";
|
|
23483
23523
|
import os40 from "os";
|
|
23484
|
-
var CLAUDE_PRICING3 = {
|
|
23485
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
23486
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
23487
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
23488
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23489
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23490
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23491
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23492
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23493
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
23494
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
23495
|
-
};
|
|
23496
23524
|
function modelPrice(model) {
|
|
23497
|
-
const
|
|
23498
|
-
|
|
23499
|
-
|
|
23500
|
-
}
|
|
23501
|
-
return null;
|
|
23525
|
+
const t = pricingFor(model);
|
|
23526
|
+
if (!t) return null;
|
|
23527
|
+
const [i, o, cw, cr] = t;
|
|
23528
|
+
return { i, o, cw, cr };
|
|
23502
23529
|
}
|
|
23503
|
-
var GEMINI_PRICING2 = {
|
|
23504
|
-
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
23505
|
-
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
23506
|
-
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
23507
|
-
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
23508
|
-
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
23509
|
-
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
23510
|
-
};
|
|
23511
23530
|
function geminiModelPrice2(model) {
|
|
23512
|
-
const
|
|
23513
|
-
|
|
23514
|
-
|
|
23515
|
-
}
|
|
23516
|
-
if (base.includes("flash")) return GEMINI_PRICING2["gemini-2.0-flash"];
|
|
23517
|
-
return null;
|
|
23531
|
+
const p = geminiPriceFor(model);
|
|
23532
|
+
if (!p) return null;
|
|
23533
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
23518
23534
|
}
|
|
23519
23535
|
function encodeProjectPath(projectPath) {
|
|
23520
23536
|
return projectPath.replace(/\//g, "-");
|
|
@@ -23807,6 +23823,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23807
23823
|
let lastTotalInput = 0;
|
|
23808
23824
|
let lastTotalCached = 0;
|
|
23809
23825
|
let lastTotalOutput = 0;
|
|
23826
|
+
let model = "";
|
|
23810
23827
|
for (const line of lines) {
|
|
23811
23828
|
if (!line.trim()) continue;
|
|
23812
23829
|
let entry;
|
|
@@ -23822,6 +23839,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23822
23839
|
cwd = String(p["cwd"] ?? "");
|
|
23823
23840
|
continue;
|
|
23824
23841
|
}
|
|
23842
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
23843
|
+
model = p["model"];
|
|
23844
|
+
continue;
|
|
23845
|
+
}
|
|
23825
23846
|
if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
|
|
23826
23847
|
firstPrompt = String(p["message"] ?? "");
|
|
23827
23848
|
continue;
|
|
@@ -23848,8 +23869,11 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23848
23869
|
}
|
|
23849
23870
|
if (!sessionId || !startTime) continue;
|
|
23850
23871
|
if (cutoff && new Date(startTime) < cutoff) continue;
|
|
23851
|
-
const
|
|
23852
|
-
|
|
23872
|
+
const costUSD = codexSessionCost(model, {
|
|
23873
|
+
input: lastTotalInput,
|
|
23874
|
+
cached: lastTotalCached,
|
|
23875
|
+
output: lastTotalOutput
|
|
23876
|
+
});
|
|
23853
23877
|
const windowEnd = new Date(
|
|
23854
23878
|
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
23855
23879
|
).toISOString();
|
|
@@ -23873,11 +23897,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23873
23897
|
}
|
|
23874
23898
|
function buildSessions(days, historyPath) {
|
|
23875
23899
|
const hPath = historyPath ?? path46.join(os40.homedir(), ".claude", "history.jsonl");
|
|
23876
|
-
let historyRaw;
|
|
23900
|
+
let historyRaw = "";
|
|
23877
23901
|
try {
|
|
23878
23902
|
historyRaw = fs45.readFileSync(hPath, "utf-8");
|
|
23879
23903
|
} catch {
|
|
23880
|
-
return [];
|
|
23881
23904
|
}
|
|
23882
23905
|
const cutoff = days !== null ? (() => {
|
|
23883
23906
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -24168,12 +24191,6 @@ function registerSessionsCommand(program2) {
|
|
|
24168
24191
|
console.log("");
|
|
24169
24192
|
console.log(chalk24.cyan.bold("\u{1F4CB} node9 sessions") + chalk24.dim(" \u2014 what your AI agent did"));
|
|
24170
24193
|
console.log("");
|
|
24171
|
-
const historyPath = path46.join(os40.homedir(), ".claude", "history.jsonl");
|
|
24172
|
-
if (!fs45.existsSync(historyPath)) {
|
|
24173
|
-
console.log(chalk24.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
24174
|
-
console.log(chalk24.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
24175
|
-
return;
|
|
24176
|
-
}
|
|
24177
24194
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
24178
24195
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
24179
24196
|
console.log(chalk24.dim(" " + rangeLabel));
|