@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.js
CHANGED
|
@@ -9278,1114 +9278,1140 @@ var init_setup = __esm({
|
|
|
9278
9278
|
}
|
|
9279
9279
|
});
|
|
9280
9280
|
|
|
9281
|
-
// src/
|
|
9282
|
-
function
|
|
9283
|
-
return
|
|
9281
|
+
// src/pricing/litellm.ts
|
|
9282
|
+
function normalizeModel(raw) {
|
|
9283
|
+
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
9284
9284
|
}
|
|
9285
|
-
function
|
|
9286
|
-
|
|
9285
|
+
function readCache() {
|
|
9286
|
+
try {
|
|
9287
|
+
const raw = JSON.parse(import_fs14.default.readFileSync(CACHE_FILE(), "utf-8"));
|
|
9288
|
+
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
9289
|
+
return null;
|
|
9290
|
+
}
|
|
9291
|
+
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
9292
|
+
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
9293
|
+
return raw.prices;
|
|
9294
|
+
} catch {
|
|
9295
|
+
return null;
|
|
9296
|
+
}
|
|
9287
9297
|
}
|
|
9288
|
-
function
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9298
|
+
function writeCache(prices) {
|
|
9299
|
+
try {
|
|
9300
|
+
const target = CACHE_FILE();
|
|
9301
|
+
const dir = import_path16.default.dirname(target);
|
|
9302
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
9303
|
+
const tmp = target + ".tmp";
|
|
9304
|
+
const body = {
|
|
9305
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9306
|
+
prices
|
|
9307
|
+
};
|
|
9308
|
+
import_fs14.default.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
9309
|
+
import_fs14.default.renameSync(tmp, target);
|
|
9310
|
+
} catch (err2) {
|
|
9311
|
+
try {
|
|
9312
|
+
import_fs14.default.appendFileSync(
|
|
9313
|
+
HOOK_DEBUG_LOG,
|
|
9314
|
+
`[pricing] cache write failed: ${err2.message}
|
|
9315
|
+
`
|
|
9316
|
+
);
|
|
9317
|
+
} catch {
|
|
9318
|
+
}
|
|
9306
9319
|
}
|
|
9307
9320
|
}
|
|
9308
|
-
function
|
|
9309
|
-
if (typeof
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9321
|
+
function tupleFromLiteLLM(entry) {
|
|
9322
|
+
if (!entry || typeof entry !== "object") return null;
|
|
9323
|
+
const e = entry;
|
|
9324
|
+
const num3 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
9325
|
+
const inCost = num3(e.input_cost_per_token);
|
|
9326
|
+
const outCost = num3(e.output_cost_per_token);
|
|
9327
|
+
if (inCost === 0 && outCost === 0) return null;
|
|
9328
|
+
return [
|
|
9329
|
+
inCost,
|
|
9330
|
+
outCost,
|
|
9331
|
+
num3(e.cache_creation_input_token_cost),
|
|
9332
|
+
num3(e.cache_read_input_token_cost)
|
|
9333
|
+
];
|
|
9334
|
+
}
|
|
9335
|
+
async function fetchLiteLLMPricing() {
|
|
9336
|
+
try {
|
|
9337
|
+
const res = await fetch(LITELLM_URL, {
|
|
9338
|
+
signal: AbortSignal.timeout(15e3)
|
|
9339
|
+
});
|
|
9340
|
+
if (!res.ok) return null;
|
|
9341
|
+
const json = await res.json();
|
|
9342
|
+
if (!json || typeof json !== "object") return null;
|
|
9343
|
+
const out = {};
|
|
9344
|
+
for (const [key, value] of Object.entries(json)) {
|
|
9345
|
+
const tuple = tupleFromLiteLLM(value);
|
|
9346
|
+
if (tuple) out[key.toLowerCase()] = tuple;
|
|
9347
|
+
}
|
|
9348
|
+
if (Object.keys(out).length < 10) {
|
|
9349
|
+
return null;
|
|
9350
|
+
}
|
|
9351
|
+
return out;
|
|
9352
|
+
} catch {
|
|
9353
|
+
return null;
|
|
9318
9354
|
}
|
|
9319
9355
|
}
|
|
9320
|
-
function
|
|
9321
|
-
if (
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9356
|
+
async function ensurePricingLoaded() {
|
|
9357
|
+
if (memCache !== null && Date.now() - memCacheAt < TTL_MS) return;
|
|
9358
|
+
const fromDisk = readCache();
|
|
9359
|
+
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
9360
|
+
memCache = fromDisk;
|
|
9361
|
+
memCacheAt = Date.now();
|
|
9362
|
+
lookupCache.clear();
|
|
9363
|
+
return;
|
|
9364
|
+
}
|
|
9365
|
+
const fetched = await fetchLiteLLMPricing();
|
|
9366
|
+
if (fetched && Object.keys(fetched).length > 0) {
|
|
9367
|
+
memCache = fetched;
|
|
9368
|
+
memCacheAt = Date.now();
|
|
9369
|
+
writeCache(fetched);
|
|
9370
|
+
lookupCache.clear();
|
|
9371
|
+
return;
|
|
9372
|
+
}
|
|
9373
|
+
memCache = { ...BUNDLED_PRICING };
|
|
9374
|
+
memCacheAt = Date.now();
|
|
9375
|
+
lookupCache.clear();
|
|
9329
9376
|
}
|
|
9330
|
-
|
|
9331
|
-
|
|
9377
|
+
function pricingFor(model) {
|
|
9378
|
+
const norm = normalizeModel(model);
|
|
9379
|
+
const cached = lookupCache.get(norm);
|
|
9380
|
+
if (cached !== void 0) return cached;
|
|
9381
|
+
if (memCache === null && !diskChecked) {
|
|
9382
|
+
diskChecked = true;
|
|
9383
|
+
const disk = readCache();
|
|
9384
|
+
if (disk && Object.keys(disk).length > 0) {
|
|
9385
|
+
memCache = disk;
|
|
9386
|
+
memCacheAt = Date.now();
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
const sources = [];
|
|
9390
|
+
if (memCache) sources.push(memCache);
|
|
9391
|
+
sources.push(BUNDLED_PRICING);
|
|
9392
|
+
let resolved = null;
|
|
9393
|
+
for (const source of sources) {
|
|
9394
|
+
const exact = source[norm];
|
|
9395
|
+
if (exact) {
|
|
9396
|
+
resolved = exact;
|
|
9397
|
+
break;
|
|
9398
|
+
}
|
|
9399
|
+
let best = null;
|
|
9400
|
+
for (const key of Object.keys(source)) {
|
|
9401
|
+
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
9402
|
+
best = key;
|
|
9403
|
+
}
|
|
9404
|
+
}
|
|
9405
|
+
if (best) {
|
|
9406
|
+
resolved = source[best];
|
|
9407
|
+
break;
|
|
9408
|
+
}
|
|
9409
|
+
}
|
|
9410
|
+
lookupCache.set(norm, resolved);
|
|
9411
|
+
return resolved;
|
|
9412
|
+
}
|
|
9413
|
+
var import_fs14, import_path16, import_os13, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
|
|
9414
|
+
var init_litellm = __esm({
|
|
9415
|
+
"src/pricing/litellm.ts"() {
|
|
9332
9416
|
"use strict";
|
|
9417
|
+
import_fs14 = __toESM(require("fs"));
|
|
9418
|
+
import_path16 = __toESM(require("path"));
|
|
9419
|
+
import_os13 = __toESM(require("os"));
|
|
9420
|
+
init_audit();
|
|
9421
|
+
LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
9422
|
+
BUNDLED_PRICING = {
|
|
9423
|
+
// Anthropic
|
|
9424
|
+
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9425
|
+
"claude-opus-4-1": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9426
|
+
"claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9427
|
+
"claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9428
|
+
"claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9429
|
+
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9430
|
+
"claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9431
|
+
"claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9432
|
+
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9433
|
+
"claude-haiku-4-5": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9434
|
+
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9435
|
+
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9436
|
+
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9437
|
+
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
9438
|
+
// OpenAI. gpt-5 family + o-series copied from the live LiteLLM table
|
|
9439
|
+
// (verified 2026-06-14) — the bundled gpt-5 was stale at $10/$30 vs the real
|
|
9440
|
+
// $1.25/$10, and Codex models (gpt-5-codex etc.) were absent, so the offline
|
|
9441
|
+
// fallback mispriced every Codex session. See cost-codex.codexPriceFor.
|
|
9442
|
+
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
9443
|
+
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
9444
|
+
"gpt-5": [125e-8, 1e-5, 0, 125e-9],
|
|
9445
|
+
"gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
9446
|
+
"gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
9447
|
+
o3: [2e-6, 8e-6, 0, 5e-7],
|
|
9448
|
+
"o4-mini": [11e-7, 44e-7, 0, 275e-9],
|
|
9449
|
+
// Google. Values copied from the live LiteLLM table (verified 2026-06-14)
|
|
9450
|
+
// so the bundled fallback prices the current Gemini tiers correctly offline
|
|
9451
|
+
// — the local cost readers were carrying a stale hardcoded copy where
|
|
9452
|
+
// gemini-2.5-flash read $0.15/$0.60 vs the real $0.30/$2.50 (~4× under on
|
|
9453
|
+
// output). See cost-gemini.geminiPriceFor (the single Gemini price source).
|
|
9454
|
+
"gemini-2.5-pro": [125e-8, 1e-5, 0, 125e-9],
|
|
9455
|
+
"gemini-2.5-flash": [3e-7, 25e-7, 0, 3e-8],
|
|
9456
|
+
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
9457
|
+
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
9458
|
+
};
|
|
9459
|
+
CACHE_FILE = () => import_path16.default.join(import_os13.default.homedir(), ".node9", "model-pricing.json");
|
|
9460
|
+
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9461
|
+
memCache = null;
|
|
9462
|
+
memCacheAt = 0;
|
|
9463
|
+
diskChecked = false;
|
|
9464
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
9333
9465
|
}
|
|
9334
9466
|
});
|
|
9335
9467
|
|
|
9336
|
-
// src/
|
|
9337
|
-
function
|
|
9338
|
-
return
|
|
9468
|
+
// src/cost-gemini.ts
|
|
9469
|
+
function geminiTmpDir() {
|
|
9470
|
+
return import_path17.default.join(import_os14.default.homedir(), ".gemini", "tmp");
|
|
9339
9471
|
}
|
|
9340
|
-
function
|
|
9341
|
-
|
|
9472
|
+
function geminiPriceFor(model) {
|
|
9473
|
+
let tuple = pricingFor(model);
|
|
9474
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
9475
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
9476
|
+
tuple = pricingFor(proxy);
|
|
9477
|
+
if (tuple) break;
|
|
9478
|
+
}
|
|
9479
|
+
}
|
|
9480
|
+
if (!tuple) return null;
|
|
9481
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
9342
9482
|
}
|
|
9343
|
-
function
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
return "magenta";
|
|
9349
|
-
case "antigravity":
|
|
9350
|
-
return "yellow";
|
|
9351
|
-
case "copilot":
|
|
9352
|
-
return "green";
|
|
9353
|
-
case "shell":
|
|
9354
|
-
return "yellow";
|
|
9355
|
-
default:
|
|
9356
|
-
return "cyan";
|
|
9483
|
+
function safeReaddir(dir) {
|
|
9484
|
+
try {
|
|
9485
|
+
return import_fs15.default.readdirSync(dir);
|
|
9486
|
+
} catch {
|
|
9487
|
+
return [];
|
|
9357
9488
|
}
|
|
9358
9489
|
}
|
|
9359
|
-
function
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
for (const
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
}
|
|
9376
|
-
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9377
|
-
stats.lastDate = a.scan.lastDate;
|
|
9490
|
+
function isDir(p) {
|
|
9491
|
+
try {
|
|
9492
|
+
return import_fs15.default.statSync(p).isDirectory();
|
|
9493
|
+
} catch {
|
|
9494
|
+
return false;
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
function listGeminiSessionFiles(base) {
|
|
9498
|
+
const out = [];
|
|
9499
|
+
for (const project of safeReaddir(base)) {
|
|
9500
|
+
const chats = import_path17.default.join(base, project, "chats");
|
|
9501
|
+
if (!isDir(chats)) continue;
|
|
9502
|
+
for (const f of safeReaddir(chats)) {
|
|
9503
|
+
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
9504
|
+
out.push({ file: import_path17.default.join(chats, f), project });
|
|
9505
|
+
}
|
|
9378
9506
|
}
|
|
9379
9507
|
}
|
|
9380
|
-
|
|
9381
|
-
const allLeaks = agents.flatMap(
|
|
9382
|
-
(a) => a.scan.dlpFindings.map((f) => ({
|
|
9383
|
-
patternName: f.patternName,
|
|
9384
|
-
redactedSample: f.redactedSample,
|
|
9385
|
-
toolName: f.toolName,
|
|
9386
|
-
timestamp: f.timestamp,
|
|
9387
|
-
project: f.project,
|
|
9388
|
-
sessionId: f.sessionId,
|
|
9389
|
-
agent: f.agent
|
|
9390
|
-
}))
|
|
9391
|
-
);
|
|
9392
|
-
const allLoops = agents.flatMap(
|
|
9393
|
-
(a) => a.scan.loopFindings.map((f) => ({
|
|
9394
|
-
toolName: f.toolName,
|
|
9395
|
-
commandPreview: f.commandPreview,
|
|
9396
|
-
count: f.count,
|
|
9397
|
-
timestamp: f.timestamp,
|
|
9398
|
-
project: f.project,
|
|
9399
|
-
sessionId: f.sessionId,
|
|
9400
|
-
agent: f.agent,
|
|
9401
|
-
kind: f.kind
|
|
9402
|
-
}))
|
|
9403
|
-
);
|
|
9404
|
-
const byVerdict = {
|
|
9405
|
-
blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
|
|
9406
|
-
supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
|
|
9407
|
-
leaks: allLeaks.length,
|
|
9408
|
-
loops: allLoops.length
|
|
9409
|
-
};
|
|
9410
|
-
const byAgent = agents.map((a) => ({
|
|
9411
|
-
id: a.id,
|
|
9412
|
-
label: a.label,
|
|
9413
|
-
icon: a.icon,
|
|
9414
|
-
sessions: a.scan.sessions,
|
|
9415
|
-
findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
|
|
9416
|
-
costUSD: a.scan.totalCostUSD
|
|
9417
|
-
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
9418
|
-
const sections = buildSections(allFindings);
|
|
9419
|
-
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
9420
|
-
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
9421
|
-
return {
|
|
9422
|
-
stats,
|
|
9423
|
-
byVerdict,
|
|
9424
|
-
byAgent,
|
|
9425
|
-
sections,
|
|
9426
|
-
leaks: allLeaks,
|
|
9427
|
-
loops: allLoops,
|
|
9428
|
-
loopWastedUSD
|
|
9429
|
-
};
|
|
9508
|
+
return out;
|
|
9430
9509
|
}
|
|
9431
|
-
function
|
|
9432
|
-
const
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
blockedCount: 0,
|
|
9443
|
-
reviewCount: 0,
|
|
9444
|
-
rules: []
|
|
9445
|
-
};
|
|
9446
|
-
sectionMap.set(id, s);
|
|
9447
|
-
}
|
|
9448
|
-
return s;
|
|
9449
|
-
}
|
|
9450
|
-
const ruleMap = /* @__PURE__ */ new Map();
|
|
9451
|
-
for (const f of findings) {
|
|
9452
|
-
const src = f.source;
|
|
9453
|
-
const sourceType = src.sourceType;
|
|
9454
|
-
const shieldName = src.shieldName;
|
|
9455
|
-
const verdict = src.rule.verdict === "block" ? "block" : "review";
|
|
9456
|
-
let sectionId;
|
|
9457
|
-
let sectionLabel;
|
|
9458
|
-
let sectionSubtitle;
|
|
9459
|
-
let shieldKey;
|
|
9460
|
-
if (sourceType === "default") {
|
|
9461
|
-
sectionId = "default";
|
|
9462
|
-
sectionLabel = "Default Rules";
|
|
9463
|
-
sectionSubtitle = "built-in, always on";
|
|
9464
|
-
} else if (sourceType === "shield") {
|
|
9465
|
-
sectionId = `shield:${shieldName}`;
|
|
9466
|
-
sectionLabel = shieldName;
|
|
9467
|
-
sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
|
|
9468
|
-
shieldKey = shieldName;
|
|
9469
|
-
} else if (shieldName === "cloud") {
|
|
9470
|
-
sectionId = "cloud";
|
|
9471
|
-
sectionLabel = "Cloud Policy";
|
|
9472
|
-
sectionSubtitle = "synced from node9 cloud";
|
|
9473
|
-
} else {
|
|
9474
|
-
sectionId = "user";
|
|
9475
|
-
sectionLabel = "Your Rules";
|
|
9476
|
-
sectionSubtitle = "added in node9.config.json";
|
|
9510
|
+
function parseGeminiSession(lines, project) {
|
|
9511
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
9512
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9513
|
+
let runId = "";
|
|
9514
|
+
for (const raw of lines) {
|
|
9515
|
+
if (!raw.trim()) continue;
|
|
9516
|
+
let obj;
|
|
9517
|
+
try {
|
|
9518
|
+
obj = JSON.parse(raw);
|
|
9519
|
+
} catch {
|
|
9520
|
+
continue;
|
|
9477
9521
|
}
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
rule = {
|
|
9484
|
-
name: ruleDisplayName,
|
|
9485
|
-
verdict,
|
|
9486
|
-
reason: src.rule.reason ?? "",
|
|
9487
|
-
findings: []
|
|
9488
|
-
};
|
|
9489
|
-
ruleMap.set(ruleKey, rule);
|
|
9490
|
-
section.rules.push(rule);
|
|
9522
|
+
if (typeof obj.sessionId === "string" && !runId) runId = obj.sessionId;
|
|
9523
|
+
if (!obj.tokens || !obj.model || !obj.timestamp) continue;
|
|
9524
|
+
if (obj.id) {
|
|
9525
|
+
if (seenIds.has(obj.id)) continue;
|
|
9526
|
+
seenIds.add(obj.id);
|
|
9491
9527
|
}
|
|
9492
|
-
const
|
|
9493
|
-
|
|
9494
|
-
const
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9528
|
+
const price = geminiPriceFor(obj.model);
|
|
9529
|
+
if (!price) continue;
|
|
9530
|
+
const inp = obj.tokens.input ?? 0;
|
|
9531
|
+
const out = obj.tokens.output ?? 0;
|
|
9532
|
+
const cached = Math.min(obj.tokens.cached ?? 0, inp);
|
|
9533
|
+
const fresh = Math.max(0, inp - cached);
|
|
9534
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
9535
|
+
const date = obj.timestamp.slice(0, 10);
|
|
9536
|
+
const model = normalizeModel(obj.model);
|
|
9537
|
+
const key = `${date}::${model}`;
|
|
9538
|
+
const prev = byKey.get(key);
|
|
9539
|
+
if (prev) {
|
|
9540
|
+
prev.costUSD += cost;
|
|
9541
|
+
prev.inputTokens += fresh;
|
|
9542
|
+
prev.outputTokens += out;
|
|
9543
|
+
prev.cacheReadTokens += cached;
|
|
9544
|
+
} else {
|
|
9545
|
+
byKey.set(key, {
|
|
9546
|
+
date,
|
|
9547
|
+
model,
|
|
9548
|
+
workingDir: project,
|
|
9549
|
+
runId,
|
|
9550
|
+
costUSD: cost,
|
|
9551
|
+
inputTokens: fresh,
|
|
9552
|
+
outputTokens: out,
|
|
9553
|
+
cacheReadTokens: cached,
|
|
9554
|
+
cacheWriteTokens: 0
|
|
9504
9555
|
});
|
|
9505
9556
|
}
|
|
9506
|
-
if (verdict === "block") section.blockedCount++;
|
|
9507
|
-
else section.reviewCount++;
|
|
9508
9557
|
}
|
|
9509
|
-
const
|
|
9510
|
-
|
|
9511
|
-
const aTotal = a.blockedCount + a.reviewCount;
|
|
9512
|
-
const bTotal = b.blockedCount + b.reviewCount;
|
|
9513
|
-
if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
|
|
9514
|
-
return bTotal - aTotal;
|
|
9515
|
-
});
|
|
9516
|
-
for (const s of sections) {
|
|
9517
|
-
s.rules.sort((a, b) => {
|
|
9518
|
-
const aBlock = a.verdict === "block" ? 1 : 0;
|
|
9519
|
-
const bBlock = b.verdict === "block" ? 1 : 0;
|
|
9520
|
-
if (bBlock !== aBlock) return bBlock - aBlock;
|
|
9521
|
-
return b.findings.length - a.findings.length;
|
|
9522
|
-
});
|
|
9523
|
-
}
|
|
9524
|
-
return sections;
|
|
9525
|
-
}
|
|
9526
|
-
function previewCommand(input, max) {
|
|
9527
|
-
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9528
|
-
const s = String(raw).replace(/\s+/g, " ").trim();
|
|
9529
|
-
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9530
|
-
}
|
|
9531
|
-
function fullCommandOf(input) {
|
|
9532
|
-
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9533
|
-
return String(raw).replace(/\s+/g, " ").trim();
|
|
9558
|
+
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
9559
|
+
return [...byKey.values()];
|
|
9534
9560
|
}
|
|
9535
|
-
var
|
|
9536
|
-
var
|
|
9537
|
-
"src/
|
|
9561
|
+
var import_fs15, import_os14, import_path17, GEMINI_FALLBACK_MODELS, geminiSource;
|
|
9562
|
+
var init_cost_gemini = __esm({
|
|
9563
|
+
"src/cost-gemini.ts"() {
|
|
9538
9564
|
"use strict";
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
{
|
|
9583
|
-
full: import_path16.default.join(home, ".aws", "credentials"),
|
|
9584
|
-
label: "~/.aws/credentials",
|
|
9585
|
-
description: "AWS access keys \u2014 full cloud account access",
|
|
9586
|
-
score: 20
|
|
9587
|
-
},
|
|
9588
|
-
{
|
|
9589
|
-
full: import_path16.default.join(home, ".aws", "config"),
|
|
9590
|
-
label: "~/.aws/config",
|
|
9591
|
-
description: "AWS configuration \u2014 account and region settings",
|
|
9592
|
-
score: 5
|
|
9593
|
-
},
|
|
9594
|
-
{
|
|
9595
|
-
full: import_path16.default.join(home, ".config", "gcloud", "credentials.db"),
|
|
9596
|
-
label: "~/.config/gcloud/credentials.db",
|
|
9597
|
-
description: "Google Cloud credentials",
|
|
9598
|
-
score: 15
|
|
9599
|
-
},
|
|
9600
|
-
{
|
|
9601
|
-
full: import_path16.default.join(home, ".docker", "config.json"),
|
|
9602
|
-
label: "~/.docker/config.json",
|
|
9603
|
-
description: "Docker registry auth tokens",
|
|
9604
|
-
score: 10
|
|
9605
|
-
},
|
|
9606
|
-
{
|
|
9607
|
-
full: import_path16.default.join(home, ".netrc"),
|
|
9608
|
-
label: "~/.netrc",
|
|
9609
|
-
description: "FTP/HTTP credentials in plain text",
|
|
9610
|
-
score: 15
|
|
9611
|
-
},
|
|
9612
|
-
{
|
|
9613
|
-
full: import_path16.default.join(home, ".npmrc"),
|
|
9614
|
-
label: "~/.npmrc",
|
|
9615
|
-
description: "npm auth token \u2014 can publish packages as you",
|
|
9616
|
-
score: 10
|
|
9617
|
-
},
|
|
9618
|
-
{
|
|
9619
|
-
full: import_path16.default.join(home, ".node9", "credentials.json"),
|
|
9620
|
-
label: "~/.node9/credentials.json",
|
|
9621
|
-
description: "Node9 cloud API key",
|
|
9622
|
-
score: 10
|
|
9623
|
-
},
|
|
9624
|
-
{
|
|
9625
|
-
full: import_path16.default.join(cwd, ".env"),
|
|
9626
|
-
label: ".env (current folder)",
|
|
9627
|
-
description: "App secrets \u2014 database passwords, API keys",
|
|
9628
|
-
score: 20
|
|
9629
|
-
},
|
|
9630
|
-
{
|
|
9631
|
-
full: import_path16.default.join(cwd, ".env.local"),
|
|
9632
|
-
label: ".env.local (current folder)",
|
|
9633
|
-
description: "Local overrides \u2014 often contains real credentials",
|
|
9634
|
-
score: 15
|
|
9635
|
-
},
|
|
9636
|
-
{
|
|
9637
|
-
full: import_path16.default.join(cwd, ".env.production"),
|
|
9638
|
-
label: ".env.production (current folder)",
|
|
9639
|
-
description: "Production secrets",
|
|
9640
|
-
score: 20
|
|
9641
|
-
}
|
|
9642
|
-
];
|
|
9643
|
-
}
|
|
9644
|
-
function isReadable(filePath) {
|
|
9645
|
-
try {
|
|
9646
|
-
import_fs14.default.accessSync(filePath, import_fs14.default.constants.R_OK);
|
|
9647
|
-
return true;
|
|
9648
|
-
} catch {
|
|
9649
|
-
return false;
|
|
9650
|
-
}
|
|
9651
|
-
}
|
|
9652
|
-
function scoreLabel(score) {
|
|
9653
|
-
if (score >= 80) return import_chalk2.default.green(`${score}/100 Good`);
|
|
9654
|
-
if (score >= 50) return import_chalk2.default.yellow(`${score}/100 Moderate risk`);
|
|
9655
|
-
if (score >= 25) return import_chalk2.default.red(`${score}/100 High risk`);
|
|
9656
|
-
return import_chalk2.default.red.bold(`${score}/100 Critical`);
|
|
9657
|
-
}
|
|
9658
|
-
function runBlast() {
|
|
9659
|
-
const home = import_os13.default.homedir();
|
|
9660
|
-
const cwd = process.cwd();
|
|
9661
|
-
const paths = buildSensitivePaths(home, cwd);
|
|
9662
|
-
let scoreDeduction = 0;
|
|
9663
|
-
const reachable = [];
|
|
9664
|
-
for (const p of paths) {
|
|
9665
|
-
if (import_fs14.default.existsSync(p.full) && isReadable(p.full)) {
|
|
9666
|
-
reachable.push(p);
|
|
9667
|
-
scoreDeduction += p.score;
|
|
9668
|
-
}
|
|
9669
|
-
}
|
|
9670
|
-
const envFindings = [];
|
|
9671
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
9672
|
-
if (!value) continue;
|
|
9673
|
-
const match = scanArgs({ [key]: value });
|
|
9674
|
-
if (match) {
|
|
9675
|
-
envFindings.push({ key, patternName: match.patternName });
|
|
9676
|
-
scoreDeduction += 10;
|
|
9677
|
-
}
|
|
9678
|
-
}
|
|
9679
|
-
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
9680
|
-
}
|
|
9681
|
-
function registerBlastCommand(program2) {
|
|
9682
|
-
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
9683
|
-
const home = import_os13.default.homedir();
|
|
9684
|
-
const cwd = process.cwd();
|
|
9685
|
-
const { reachable, envFindings, score } = runBlast();
|
|
9686
|
-
console.log("");
|
|
9687
|
-
console.log(
|
|
9688
|
-
import_chalk2.default.bold(" \u{1F52D} Node9 Blast Radius") + import_chalk2.default.dim(" \xB7 what an AI agent can reach from here")
|
|
9689
|
-
);
|
|
9690
|
-
console.log(import_chalk2.default.dim(" Running in: ") + import_chalk2.default.white(cwd.replace(home, "~")));
|
|
9691
|
-
console.log("");
|
|
9692
|
-
if (reachable.length > 0) {
|
|
9693
|
-
console.log(" " + import_chalk2.default.red.bold("Sensitive files reachable:"));
|
|
9694
|
-
for (const p of reachable) {
|
|
9695
|
-
console.log(
|
|
9696
|
-
" " + import_chalk2.default.red("\u2717 ") + import_chalk2.default.yellow(p.label.padEnd(38)) + import_chalk2.default.dim(p.description)
|
|
9697
|
-
);
|
|
9698
|
-
}
|
|
9699
|
-
console.log("");
|
|
9700
|
-
}
|
|
9701
|
-
if (envFindings.length > 0) {
|
|
9702
|
-
console.log(" " + import_chalk2.default.red.bold("Secrets in active environment:"));
|
|
9703
|
-
for (const f of envFindings) {
|
|
9704
|
-
console.log(
|
|
9705
|
-
" " + import_chalk2.default.red("\u2717 ") + import_chalk2.default.yellow(f.key.padEnd(38)) + import_chalk2.default.dim(f.patternName)
|
|
9706
|
-
);
|
|
9565
|
+
import_fs15 = __toESM(require("fs"));
|
|
9566
|
+
import_os14 = __toESM(require("os"));
|
|
9567
|
+
import_path17 = __toESM(require("path"));
|
|
9568
|
+
init_litellm();
|
|
9569
|
+
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
9570
|
+
geminiSource = {
|
|
9571
|
+
id: "gemini",
|
|
9572
|
+
available() {
|
|
9573
|
+
try {
|
|
9574
|
+
return import_fs15.default.existsSync(geminiTmpDir());
|
|
9575
|
+
} catch {
|
|
9576
|
+
return false;
|
|
9577
|
+
}
|
|
9578
|
+
},
|
|
9579
|
+
collect(sinceMs) {
|
|
9580
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9581
|
+
for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
9582
|
+
try {
|
|
9583
|
+
if (sinceMs !== void 0 && import_fs15.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
9584
|
+
} catch {
|
|
9585
|
+
continue;
|
|
9586
|
+
}
|
|
9587
|
+
let content;
|
|
9588
|
+
try {
|
|
9589
|
+
content = import_fs15.default.readFileSync(file, "utf8");
|
|
9590
|
+
} catch {
|
|
9591
|
+
continue;
|
|
9592
|
+
}
|
|
9593
|
+
for (const e of parseGeminiSession(content.split("\n"), project)) {
|
|
9594
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9595
|
+
const prev = combined.get(key);
|
|
9596
|
+
if (prev) {
|
|
9597
|
+
prev.costUSD += e.costUSD;
|
|
9598
|
+
prev.inputTokens += e.inputTokens;
|
|
9599
|
+
prev.outputTokens += e.outputTokens;
|
|
9600
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9601
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9602
|
+
} else {
|
|
9603
|
+
combined.set(key, { ...e });
|
|
9604
|
+
}
|
|
9605
|
+
}
|
|
9606
|
+
}
|
|
9607
|
+
return [...combined.values()];
|
|
9707
9608
|
}
|
|
9708
|
-
|
|
9709
|
-
}
|
|
9710
|
-
console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
|
|
9711
|
-
if (reachable.length === 0 && envFindings.length === 0) {
|
|
9712
|
-
console.log(" " + import_chalk2.default.green("\u2705 No sensitive files or environment secrets found."));
|
|
9713
|
-
console.log(" Security Score: " + scoreLabel(score));
|
|
9714
|
-
} else {
|
|
9715
|
-
console.log(
|
|
9716
|
-
" Security Score: " + scoreLabel(score) + import_chalk2.default.dim(
|
|
9717
|
-
` (${reachable.length} file${reachable.length !== 1 ? "s" : ""}, ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""})`
|
|
9718
|
-
)
|
|
9719
|
-
);
|
|
9720
|
-
console.log("");
|
|
9721
|
-
console.log(
|
|
9722
|
-
import_chalk2.default.dim(
|
|
9723
|
-
" 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."
|
|
9724
|
-
)
|
|
9725
|
-
);
|
|
9726
|
-
}
|
|
9727
|
-
console.log("");
|
|
9728
|
-
});
|
|
9729
|
-
}
|
|
9730
|
-
var import_chalk2, import_fs14, import_path16, import_os13;
|
|
9731
|
-
var init_blast = __esm({
|
|
9732
|
-
"src/cli/commands/blast.ts"() {
|
|
9733
|
-
"use strict";
|
|
9734
|
-
import_chalk2 = __toESM(require("chalk"));
|
|
9735
|
-
import_fs14 = __toESM(require("fs"));
|
|
9736
|
-
import_path16 = __toESM(require("path"));
|
|
9737
|
-
import_os13 = __toESM(require("os"));
|
|
9738
|
-
init_dlp();
|
|
9609
|
+
};
|
|
9739
9610
|
}
|
|
9740
9611
|
});
|
|
9741
9612
|
|
|
9742
|
-
// src/
|
|
9743
|
-
function
|
|
9744
|
-
|
|
9745
|
-
if (score >= 50) return { band: "at-risk", label: "At Risk", color: import_chalk3.default.yellow };
|
|
9746
|
-
return { band: "critical", label: "Critical", color: import_chalk3.default.red };
|
|
9613
|
+
// src/cost-codex.ts
|
|
9614
|
+
function codexSessionsDir() {
|
|
9615
|
+
return import_path18.default.join(import_os15.default.homedir(), ".codex", "sessions");
|
|
9747
9616
|
}
|
|
9748
|
-
function
|
|
9749
|
-
|
|
9750
|
-
for (const f of findings) {
|
|
9751
|
-
counts.set(f.patternName, (counts.get(f.patternName) ?? 0) + 1);
|
|
9752
|
-
}
|
|
9753
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name, count]) => ({ name, count }));
|
|
9617
|
+
function codexPriceFor(model) {
|
|
9618
|
+
return pricingFor(model) ?? CODEX_FALLBACK;
|
|
9754
9619
|
}
|
|
9755
|
-
function
|
|
9756
|
-
const
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9620
|
+
function codexSessionCost(model, tokens) {
|
|
9621
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
9622
|
+
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
9623
|
+
return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
|
|
9624
|
+
}
|
|
9625
|
+
function listCodexSessionFiles(base) {
|
|
9626
|
+
const out = [];
|
|
9627
|
+
for (const y of safeReaddir2(base)) {
|
|
9628
|
+
const yp = import_path18.default.join(base, y);
|
|
9629
|
+
if (!isDir2(yp)) continue;
|
|
9630
|
+
for (const m of safeReaddir2(yp)) {
|
|
9631
|
+
const mp = import_path18.default.join(yp, m);
|
|
9632
|
+
if (!isDir2(mp)) continue;
|
|
9633
|
+
for (const d of safeReaddir2(mp)) {
|
|
9634
|
+
const dp = import_path18.default.join(mp, d);
|
|
9635
|
+
if (!isDir2(dp)) continue;
|
|
9636
|
+
for (const f of safeReaddir2(dp)) {
|
|
9637
|
+
if (f.endsWith(".jsonl")) out.push(import_path18.default.join(dp, f));
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9761
9640
|
}
|
|
9762
9641
|
}
|
|
9763
|
-
return
|
|
9764
|
-
}
|
|
9765
|
-
function computeLoopWaste(loops, totalToolCalls) {
|
|
9766
|
-
const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
9767
|
-
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
9768
|
-
return { wastedCalls, wastePct };
|
|
9642
|
+
return out;
|
|
9769
9643
|
}
|
|
9770
|
-
function
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
const totalCatches = section.blockedCount + section.reviewCount;
|
|
9776
|
-
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);
|
|
9777
|
-
out.push({
|
|
9778
|
-
shieldName: section.shieldKey,
|
|
9779
|
-
totalCatches,
|
|
9780
|
-
blockCatches: section.blockedCount,
|
|
9781
|
-
reviewCatches: section.reviewCount,
|
|
9782
|
-
topRuleLabels
|
|
9783
|
-
});
|
|
9644
|
+
function safeReaddir2(dir) {
|
|
9645
|
+
try {
|
|
9646
|
+
return import_fs16.default.readdirSync(dir);
|
|
9647
|
+
} catch {
|
|
9648
|
+
return [];
|
|
9784
9649
|
}
|
|
9785
|
-
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
9786
9650
|
}
|
|
9787
|
-
function
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
9793
|
-
const dashFill = "\u2500".repeat(Math.max(0, inner - (0, import_string_width.default)(titleSegment)));
|
|
9794
|
-
out.push(import_chalk3.default.dim("\u256D\u2500") + import_chalk3.default.bold(titleSegment) + import_chalk3.default.dim(`${dashFill}\u2500\u256E`));
|
|
9795
|
-
for (const line of bodyLines) {
|
|
9796
|
-
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
9797
|
-
out.push(import_chalk3.default.dim("\u2502 ") + line.rendered + padding + import_chalk3.default.dim(" \u2502"));
|
|
9651
|
+
function isDir2(p) {
|
|
9652
|
+
try {
|
|
9653
|
+
return import_fs16.default.statSync(p).isDirectory();
|
|
9654
|
+
} catch {
|
|
9655
|
+
return false;
|
|
9798
9656
|
}
|
|
9799
|
-
out.push(import_chalk3.default.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
9800
|
-
return out;
|
|
9801
9657
|
}
|
|
9802
|
-
function
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9658
|
+
function parseCodexSession(lines) {
|
|
9659
|
+
let sessionStart2 = "";
|
|
9660
|
+
let runId = "";
|
|
9661
|
+
let cwd = "";
|
|
9662
|
+
let model = "";
|
|
9663
|
+
let input = 0;
|
|
9664
|
+
let cached = 0;
|
|
9665
|
+
let output = 0;
|
|
9666
|
+
let sawUsage = false;
|
|
9667
|
+
for (const raw of lines) {
|
|
9668
|
+
if (!raw.trim()) continue;
|
|
9669
|
+
let entry;
|
|
9670
|
+
try {
|
|
9671
|
+
entry = JSON.parse(raw);
|
|
9672
|
+
} catch {
|
|
9673
|
+
continue;
|
|
9674
|
+
}
|
|
9675
|
+
const p = entry.payload ?? {};
|
|
9676
|
+
if (entry.type === "session_meta") {
|
|
9677
|
+
if (!sessionStart2 && typeof p["timestamp"] === "string") sessionStart2 = p["timestamp"];
|
|
9678
|
+
if (!runId && typeof p["id"] === "string") runId = p["id"];
|
|
9679
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9680
|
+
continue;
|
|
9681
|
+
}
|
|
9682
|
+
if (entry.type === "turn_context") {
|
|
9683
|
+
if (typeof p["model"] === "string") model = p["model"];
|
|
9684
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9685
|
+
continue;
|
|
9686
|
+
}
|
|
9687
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
9688
|
+
const info = p["info"] ?? {};
|
|
9689
|
+
const usage = info["total_token_usage"] ?? {};
|
|
9690
|
+
if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
|
|
9691
|
+
if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
|
|
9692
|
+
if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
|
|
9693
|
+
sawUsage = true;
|
|
9694
|
+
}
|
|
9695
|
+
}
|
|
9696
|
+
if (!sessionStart2 || !sawUsage) return null;
|
|
9697
|
+
const nonCached = Math.max(0, input - cached);
|
|
9698
|
+
if (nonCached === 0 && output === 0 && cached === 0) return null;
|
|
9699
|
+
const norm = normalizeModel(model || "gpt-5");
|
|
9700
|
+
const costUSD = codexSessionCost(model, { input, cached, output });
|
|
9701
|
+
return {
|
|
9702
|
+
date: sessionStart2.slice(0, 10),
|
|
9703
|
+
model: norm,
|
|
9704
|
+
workingDir: cwd,
|
|
9705
|
+
runId,
|
|
9706
|
+
costUSD,
|
|
9707
|
+
inputTokens: nonCached,
|
|
9708
|
+
outputTokens: output,
|
|
9709
|
+
cacheReadTokens: cached,
|
|
9710
|
+
cacheWriteTokens: 0
|
|
9711
|
+
};
|
|
9809
9712
|
}
|
|
9810
|
-
var
|
|
9811
|
-
var
|
|
9812
|
-
"src/
|
|
9713
|
+
var import_fs16, import_os15, import_path18, CODEX_FALLBACK, codexSource;
|
|
9714
|
+
var init_cost_codex = __esm({
|
|
9715
|
+
"src/cost-codex.ts"() {
|
|
9813
9716
|
"use strict";
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9717
|
+
import_fs16 = __toESM(require("fs"));
|
|
9718
|
+
import_os15 = __toESM(require("os"));
|
|
9719
|
+
import_path18 = __toESM(require("path"));
|
|
9720
|
+
init_litellm();
|
|
9721
|
+
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
9722
|
+
codexSource = {
|
|
9723
|
+
id: "codex",
|
|
9724
|
+
available() {
|
|
9725
|
+
try {
|
|
9726
|
+
return import_fs16.default.existsSync(codexSessionsDir());
|
|
9727
|
+
} catch {
|
|
9728
|
+
return false;
|
|
9729
|
+
}
|
|
9730
|
+
},
|
|
9731
|
+
collect(sinceMs) {
|
|
9732
|
+
const base = codexSessionsDir();
|
|
9733
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9734
|
+
for (const file of listCodexSessionFiles(base)) {
|
|
9735
|
+
try {
|
|
9736
|
+
if (sinceMs !== void 0 && import_fs16.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
9737
|
+
} catch {
|
|
9738
|
+
continue;
|
|
9739
|
+
}
|
|
9740
|
+
let content;
|
|
9741
|
+
try {
|
|
9742
|
+
content = import_fs16.default.readFileSync(file, "utf8");
|
|
9743
|
+
} catch {
|
|
9744
|
+
continue;
|
|
9745
|
+
}
|
|
9746
|
+
const e = parseCodexSession(content.split("\n"));
|
|
9747
|
+
if (!e) continue;
|
|
9748
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9749
|
+
const prev = combined.get(key);
|
|
9750
|
+
if (prev) {
|
|
9751
|
+
prev.costUSD += e.costUSD;
|
|
9752
|
+
prev.inputTokens += e.inputTokens;
|
|
9753
|
+
prev.outputTokens += e.outputTokens;
|
|
9754
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9755
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9756
|
+
} else {
|
|
9757
|
+
combined.set(key, { ...e });
|
|
9758
|
+
}
|
|
9759
|
+
}
|
|
9760
|
+
return [...combined.values()];
|
|
9761
|
+
}
|
|
9762
|
+
};
|
|
9763
|
+
}
|
|
9764
|
+
});
|
|
9765
|
+
|
|
9766
|
+
// src/utils/hook-payload.ts
|
|
9767
|
+
function extractToolName(payload, defaultValue = "") {
|
|
9768
|
+
return payload.tool_name ?? payload.name ?? payload.toolCall?.name ?? defaultValue;
|
|
9769
|
+
}
|
|
9770
|
+
function extractToolInput(payload) {
|
|
9771
|
+
return payload.tool_input ?? payload.args ?? payload.toolCall?.args ?? {};
|
|
9772
|
+
}
|
|
9773
|
+
function canonicalToolName(name) {
|
|
9774
|
+
switch (name) {
|
|
9775
|
+
// Hermes Agent
|
|
9776
|
+
case "terminal":
|
|
9777
|
+
return "Bash";
|
|
9778
|
+
case "write_file":
|
|
9779
|
+
return "Write";
|
|
9780
|
+
case "patch":
|
|
9781
|
+
return "Edit";
|
|
9782
|
+
case "read_file":
|
|
9783
|
+
return "Read";
|
|
9784
|
+
case "search_files":
|
|
9785
|
+
return "Grep";
|
|
9786
|
+
// Antigravity (agy) — shell tool renamed from Gemini's run_shell_command
|
|
9787
|
+
case "run_command":
|
|
9788
|
+
return "Bash";
|
|
9789
|
+
default:
|
|
9790
|
+
return name;
|
|
9791
|
+
}
|
|
9792
|
+
}
|
|
9793
|
+
function agentLabelFromFlag(flag) {
|
|
9794
|
+
if (typeof flag !== "string") return void 0;
|
|
9795
|
+
switch (flag.toLowerCase()) {
|
|
9796
|
+
case "antigravity":
|
|
9797
|
+
case "agy":
|
|
9798
|
+
return "Antigravity";
|
|
9799
|
+
case "copilot":
|
|
9800
|
+
return "GitHub Copilot";
|
|
9801
|
+
default:
|
|
9802
|
+
return void 0;
|
|
9817
9803
|
}
|
|
9818
|
-
}
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9804
|
+
}
|
|
9805
|
+
function canonicalToolInput(rawToolName, input) {
|
|
9806
|
+
if (rawToolName !== "run_command") return input;
|
|
9807
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
|
|
9808
|
+
const args = input;
|
|
9809
|
+
if (typeof args.CommandLine !== "string") return input;
|
|
9810
|
+
const { CommandLine, Cwd, ...rest } = args;
|
|
9811
|
+
const canonical = { ...rest, command: CommandLine };
|
|
9812
|
+
if (typeof Cwd === "string" && Cwd.length > 0) canonical.cwd = Cwd;
|
|
9813
|
+
return canonical;
|
|
9814
|
+
}
|
|
9815
|
+
var init_hook_payload = __esm({
|
|
9816
|
+
"src/utils/hook-payload.ts"() {
|
|
9824
9817
|
"use strict";
|
|
9825
|
-
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
9826
|
-
"project-jail": 0.7
|
|
9827
|
-
};
|
|
9828
9818
|
}
|
|
9829
9819
|
});
|
|
9830
9820
|
|
|
9831
|
-
// src/
|
|
9832
|
-
function
|
|
9833
|
-
|
|
9834
|
-
const { band } = classifyScore(blast.score);
|
|
9835
|
-
return {
|
|
9836
|
-
schemaVersion: 1,
|
|
9837
|
-
generatedAt,
|
|
9838
|
-
isWired,
|
|
9839
|
-
score: blast.score,
|
|
9840
|
-
band,
|
|
9841
|
-
totals: {
|
|
9842
|
-
blocked: summary.byVerdict.blocked,
|
|
9843
|
-
review: summary.byVerdict.supervised,
|
|
9844
|
-
leaks: summary.byVerdict.leaks,
|
|
9845
|
-
loops: summary.byVerdict.loops,
|
|
9846
|
-
blastExposures: blast.reachable.length + blast.envFindings.length
|
|
9847
|
-
},
|
|
9848
|
-
summary,
|
|
9849
|
-
blast: {
|
|
9850
|
-
score: blast.score,
|
|
9851
|
-
reachable: blast.reachable,
|
|
9852
|
-
envFindings: blast.envFindings
|
|
9853
|
-
}
|
|
9854
|
-
};
|
|
9821
|
+
// src/scan-summary.ts
|
|
9822
|
+
function agentDisplayName(agent) {
|
|
9823
|
+
return AGENT_LONG[agent] ?? "Claude Code";
|
|
9855
9824
|
}
|
|
9856
|
-
|
|
9857
|
-
"
|
|
9858
|
-
|
|
9859
|
-
|
|
9825
|
+
function agentBadgeText(agent, width = 10) {
|
|
9826
|
+
return `[${AGENT_SHORT[agent] ?? "Claude"}]`.padEnd(width);
|
|
9827
|
+
}
|
|
9828
|
+
function agentColorName(agent) {
|
|
9829
|
+
switch (agent) {
|
|
9830
|
+
case "gemini":
|
|
9831
|
+
return "blue";
|
|
9832
|
+
case "codex":
|
|
9833
|
+
return "magenta";
|
|
9834
|
+
case "antigravity":
|
|
9835
|
+
return "yellow";
|
|
9836
|
+
case "copilot":
|
|
9837
|
+
return "green";
|
|
9838
|
+
case "shell":
|
|
9839
|
+
return "yellow";
|
|
9840
|
+
default:
|
|
9841
|
+
return "cyan";
|
|
9860
9842
|
}
|
|
9861
|
-
});
|
|
9862
|
-
|
|
9863
|
-
// src/cli/render/scan-history.ts
|
|
9864
|
-
function defaultHistoryPath() {
|
|
9865
|
-
return import_path17.default.join(import_os14.default.homedir(), ".node9", "scan-history.json");
|
|
9866
9843
|
}
|
|
9867
|
-
function
|
|
9868
|
-
const
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9844
|
+
function buildScanSummary(agents) {
|
|
9845
|
+
const stats = {
|
|
9846
|
+
sessions: 0,
|
|
9847
|
+
totalToolCalls: 0,
|
|
9848
|
+
bashCalls: 0,
|
|
9849
|
+
totalCostUSD: 0,
|
|
9850
|
+
firstDate: null,
|
|
9851
|
+
lastDate: null
|
|
9852
|
+
};
|
|
9853
|
+
for (const a of agents) {
|
|
9854
|
+
stats.sessions += a.scan.sessions;
|
|
9855
|
+
stats.totalToolCalls += a.scan.totalToolCalls;
|
|
9856
|
+
stats.bashCalls += a.scan.bashCalls;
|
|
9857
|
+
stats.totalCostUSD += a.scan.totalCostUSD;
|
|
9858
|
+
if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
|
|
9859
|
+
stats.firstDate = a.scan.firstDate;
|
|
9860
|
+
}
|
|
9861
|
+
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9862
|
+
stats.lastDate = a.scan.lastDate;
|
|
9863
|
+
}
|
|
9879
9864
|
}
|
|
9865
|
+
const allFindings = agents.flatMap((a) => a.scan.findings);
|
|
9866
|
+
const allLeaks = agents.flatMap(
|
|
9867
|
+
(a) => a.scan.dlpFindings.map((f) => ({
|
|
9868
|
+
patternName: f.patternName,
|
|
9869
|
+
redactedSample: f.redactedSample,
|
|
9870
|
+
toolName: f.toolName,
|
|
9871
|
+
timestamp: f.timestamp,
|
|
9872
|
+
project: f.project,
|
|
9873
|
+
sessionId: f.sessionId,
|
|
9874
|
+
agent: f.agent
|
|
9875
|
+
}))
|
|
9876
|
+
);
|
|
9877
|
+
const allLoops = agents.flatMap(
|
|
9878
|
+
(a) => a.scan.loopFindings.map((f) => ({
|
|
9879
|
+
toolName: f.toolName,
|
|
9880
|
+
commandPreview: f.commandPreview,
|
|
9881
|
+
count: f.count,
|
|
9882
|
+
timestamp: f.timestamp,
|
|
9883
|
+
project: f.project,
|
|
9884
|
+
sessionId: f.sessionId,
|
|
9885
|
+
agent: f.agent,
|
|
9886
|
+
kind: f.kind
|
|
9887
|
+
}))
|
|
9888
|
+
);
|
|
9889
|
+
const byVerdict = {
|
|
9890
|
+
blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
|
|
9891
|
+
supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
|
|
9892
|
+
leaks: allLeaks.length,
|
|
9893
|
+
loops: allLoops.length
|
|
9894
|
+
};
|
|
9895
|
+
const byAgent = agents.map((a) => ({
|
|
9896
|
+
id: a.id,
|
|
9897
|
+
label: a.label,
|
|
9898
|
+
icon: a.icon,
|
|
9899
|
+
sessions: a.scan.sessions,
|
|
9900
|
+
findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
|
|
9901
|
+
costUSD: a.scan.totalCostUSD
|
|
9902
|
+
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
9903
|
+
const sections = buildSections(allFindings);
|
|
9904
|
+
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
9905
|
+
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
9906
|
+
return {
|
|
9907
|
+
stats,
|
|
9908
|
+
byVerdict,
|
|
9909
|
+
byAgent,
|
|
9910
|
+
sections,
|
|
9911
|
+
leaks: allLeaks,
|
|
9912
|
+
loops: allLoops,
|
|
9913
|
+
loopWastedUSD
|
|
9914
|
+
};
|
|
9880
9915
|
}
|
|
9881
|
-
function
|
|
9882
|
-
const
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9916
|
+
function buildSections(findings) {
|
|
9917
|
+
const sectionMap = /* @__PURE__ */ new Map();
|
|
9918
|
+
function ensureSection(id, label, subtitle, sourceType, shieldKey) {
|
|
9919
|
+
let s = sectionMap.get(id);
|
|
9920
|
+
if (!s) {
|
|
9921
|
+
s = {
|
|
9922
|
+
id,
|
|
9923
|
+
label,
|
|
9924
|
+
subtitle,
|
|
9925
|
+
sourceType,
|
|
9926
|
+
shieldKey,
|
|
9927
|
+
blockedCount: 0,
|
|
9928
|
+
reviewCount: 0,
|
|
9929
|
+
rules: []
|
|
9930
|
+
};
|
|
9931
|
+
sectionMap.set(id, s);
|
|
9895
9932
|
}
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
9933
|
+
return s;
|
|
9934
|
+
}
|
|
9935
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
9936
|
+
for (const f of findings) {
|
|
9937
|
+
const src = f.source;
|
|
9938
|
+
const sourceType = src.sourceType;
|
|
9939
|
+
const shieldName = src.shieldName;
|
|
9940
|
+
const verdict = src.rule.verdict === "block" ? "block" : "review";
|
|
9941
|
+
let sectionId;
|
|
9942
|
+
let sectionLabel;
|
|
9943
|
+
let sectionSubtitle;
|
|
9944
|
+
let shieldKey;
|
|
9945
|
+
if (sourceType === "default") {
|
|
9946
|
+
sectionId = "default";
|
|
9947
|
+
sectionLabel = "Default Rules";
|
|
9948
|
+
sectionSubtitle = "built-in, always on";
|
|
9949
|
+
} else if (sourceType === "shield") {
|
|
9950
|
+
sectionId = `shield:${shieldName}`;
|
|
9951
|
+
sectionLabel = shieldName;
|
|
9952
|
+
sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
|
|
9953
|
+
shieldKey = shieldName;
|
|
9954
|
+
} else if (shieldName === "cloud") {
|
|
9955
|
+
sectionId = "cloud";
|
|
9956
|
+
sectionLabel = "Cloud Policy";
|
|
9957
|
+
sectionSubtitle = "synced from node9 cloud";
|
|
9958
|
+
} else {
|
|
9959
|
+
sectionId = "user";
|
|
9960
|
+
sectionLabel = "Your Rules";
|
|
9961
|
+
sectionSubtitle = "added in node9.config.json";
|
|
9899
9962
|
}
|
|
9900
|
-
|
|
9901
|
-
|
|
9902
|
-
|
|
9903
|
-
|
|
9904
|
-
|
|
9905
|
-
|
|
9963
|
+
const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
|
|
9964
|
+
const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
|
|
9965
|
+
const ruleKey = sectionId + "::" + ruleDisplayName;
|
|
9966
|
+
let rule = ruleMap.get(ruleKey);
|
|
9967
|
+
if (!rule) {
|
|
9968
|
+
rule = {
|
|
9969
|
+
name: ruleDisplayName,
|
|
9970
|
+
verdict,
|
|
9971
|
+
reason: src.rule.reason ?? "",
|
|
9972
|
+
findings: []
|
|
9973
|
+
};
|
|
9974
|
+
ruleMap.set(ruleKey, rule);
|
|
9975
|
+
section.rules.push(rule);
|
|
9976
|
+
}
|
|
9977
|
+
const cmdPreview = previewCommand(f.input, 120);
|
|
9978
|
+
const fullCmd = fullCommandOf(f.input);
|
|
9979
|
+
const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
|
|
9980
|
+
if (!isDupe) {
|
|
9981
|
+
rule.findings.push({
|
|
9982
|
+
timestamp: f.timestamp ?? "",
|
|
9983
|
+
command: cmdPreview,
|
|
9984
|
+
fullCommand: fullCmd,
|
|
9985
|
+
project: f.project,
|
|
9986
|
+
sessionId: f.sessionId,
|
|
9987
|
+
agent: f.agent,
|
|
9988
|
+
toolName: f.toolName
|
|
9989
|
+
});
|
|
9990
|
+
}
|
|
9991
|
+
if (verdict === "block") section.blockedCount++;
|
|
9992
|
+
else section.reviewCount++;
|
|
9993
|
+
}
|
|
9994
|
+
const sections = [...sectionMap.values()];
|
|
9995
|
+
sections.sort((a, b) => {
|
|
9996
|
+
const aTotal = a.blockedCount + a.reviewCount;
|
|
9997
|
+
const bTotal = b.blockedCount + b.reviewCount;
|
|
9998
|
+
if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
|
|
9999
|
+
return bTotal - aTotal;
|
|
10000
|
+
});
|
|
10001
|
+
for (const s of sections) {
|
|
10002
|
+
s.rules.sort((a, b) => {
|
|
10003
|
+
const aBlock = a.verdict === "block" ? 1 : 0;
|
|
10004
|
+
const bBlock = b.verdict === "block" ? 1 : 0;
|
|
10005
|
+
if (bBlock !== aBlock) return bBlock - aBlock;
|
|
10006
|
+
return b.findings.length - a.findings.length;
|
|
10007
|
+
});
|
|
9906
10008
|
}
|
|
10009
|
+
return sections;
|
|
9907
10010
|
}
|
|
9908
|
-
function
|
|
9909
|
-
|
|
9910
|
-
const
|
|
9911
|
-
|
|
9912
|
-
const scoreDelta = current.score - previous.score;
|
|
9913
|
-
const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
|
|
9914
|
-
if (scoreDelta === 0 && daysAgo === 0) return null;
|
|
9915
|
-
return { scoreDelta, daysAgo };
|
|
10011
|
+
function previewCommand(input, max) {
|
|
10012
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
10013
|
+
const s = String(raw).replace(/\s+/g, " ").trim();
|
|
10014
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9916
10015
|
}
|
|
9917
|
-
function
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
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";
|
|
10016
|
+
function fullCommandOf(input) {
|
|
10017
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
10018
|
+
return String(raw).replace(/\s+/g, " ").trim();
|
|
9921
10019
|
}
|
|
9922
|
-
var
|
|
9923
|
-
var
|
|
9924
|
-
"src/
|
|
10020
|
+
var AGENT_SHORT, AGENT_LONG;
|
|
10021
|
+
var init_scan_summary = __esm({
|
|
10022
|
+
"src/scan-summary.ts"() {
|
|
9925
10023
|
"use strict";
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
10024
|
+
init_shields();
|
|
10025
|
+
init_dist();
|
|
10026
|
+
init_dist();
|
|
10027
|
+
AGENT_SHORT = {
|
|
10028
|
+
claude: "Claude",
|
|
10029
|
+
gemini: "Gemini",
|
|
10030
|
+
codex: "Codex",
|
|
10031
|
+
antigravity: "Agy",
|
|
10032
|
+
copilot: "Copilot",
|
|
10033
|
+
shell: "Shell"
|
|
10034
|
+
};
|
|
10035
|
+
AGENT_LONG = {
|
|
10036
|
+
claude: "Claude Code",
|
|
10037
|
+
gemini: "Gemini CLI",
|
|
10038
|
+
codex: "Codex",
|
|
10039
|
+
antigravity: "Antigravity",
|
|
10040
|
+
copilot: "GitHub Copilot",
|
|
10041
|
+
shell: "Shell"
|
|
10042
|
+
};
|
|
9930
10043
|
}
|
|
9931
10044
|
});
|
|
9932
10045
|
|
|
9933
|
-
// src/
|
|
9934
|
-
function
|
|
9935
|
-
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
9936
|
-
}
|
|
9937
|
-
function readCache() {
|
|
9938
|
-
try {
|
|
9939
|
-
const raw = JSON.parse(import_fs16.default.readFileSync(CACHE_FILE(), "utf-8"));
|
|
9940
|
-
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
9941
|
-
return null;
|
|
9942
|
-
}
|
|
9943
|
-
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
9944
|
-
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
9945
|
-
return raw.prices;
|
|
9946
|
-
} catch {
|
|
9947
|
-
return null;
|
|
9948
|
-
}
|
|
9949
|
-
}
|
|
9950
|
-
function writeCache(prices) {
|
|
9951
|
-
try {
|
|
9952
|
-
const target = CACHE_FILE();
|
|
9953
|
-
const dir = import_path18.default.dirname(target);
|
|
9954
|
-
if (!import_fs16.default.existsSync(dir)) import_fs16.default.mkdirSync(dir, { recursive: true });
|
|
9955
|
-
const tmp = target + ".tmp";
|
|
9956
|
-
const body = {
|
|
9957
|
-
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9958
|
-
prices
|
|
9959
|
-
};
|
|
9960
|
-
import_fs16.default.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
9961
|
-
import_fs16.default.renameSync(tmp, target);
|
|
9962
|
-
} catch (err2) {
|
|
9963
|
-
try {
|
|
9964
|
-
import_fs16.default.appendFileSync(
|
|
9965
|
-
HOOK_DEBUG_LOG,
|
|
9966
|
-
`[pricing] cache write failed: ${err2.message}
|
|
9967
|
-
`
|
|
9968
|
-
);
|
|
9969
|
-
} catch {
|
|
9970
|
-
}
|
|
9971
|
-
}
|
|
9972
|
-
}
|
|
9973
|
-
function tupleFromLiteLLM(entry) {
|
|
9974
|
-
if (!entry || typeof entry !== "object") return null;
|
|
9975
|
-
const e = entry;
|
|
9976
|
-
const num3 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
9977
|
-
const inCost = num3(e.input_cost_per_token);
|
|
9978
|
-
const outCost = num3(e.output_cost_per_token);
|
|
9979
|
-
if (inCost === 0 && outCost === 0) return null;
|
|
10046
|
+
// src/cli/commands/blast.ts
|
|
10047
|
+
function buildSensitivePaths(home, cwd) {
|
|
9980
10048
|
return [
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
10049
|
+
{
|
|
10050
|
+
full: import_path19.default.join(home, ".ssh", "id_rsa"),
|
|
10051
|
+
label: "~/.ssh/id_rsa",
|
|
10052
|
+
description: "RSA private key \u2014 grants SSH access to your servers",
|
|
10053
|
+
score: 20
|
|
10054
|
+
},
|
|
10055
|
+
{
|
|
10056
|
+
full: import_path19.default.join(home, ".ssh", "id_ed25519"),
|
|
10057
|
+
label: "~/.ssh/id_ed25519",
|
|
10058
|
+
description: "Ed25519 private key \u2014 grants SSH access to your servers",
|
|
10059
|
+
score: 20
|
|
10060
|
+
},
|
|
10061
|
+
{
|
|
10062
|
+
full: import_path19.default.join(home, ".ssh", "id_ecdsa"),
|
|
10063
|
+
label: "~/.ssh/id_ecdsa",
|
|
10064
|
+
description: "ECDSA private key \u2014 grants SSH access to your servers",
|
|
10065
|
+
score: 20
|
|
10066
|
+
},
|
|
10067
|
+
{
|
|
10068
|
+
full: import_path19.default.join(home, ".aws", "credentials"),
|
|
10069
|
+
label: "~/.aws/credentials",
|
|
10070
|
+
description: "AWS access keys \u2014 full cloud account access",
|
|
10071
|
+
score: 20
|
|
10072
|
+
},
|
|
10073
|
+
{
|
|
10074
|
+
full: import_path19.default.join(home, ".aws", "config"),
|
|
10075
|
+
label: "~/.aws/config",
|
|
10076
|
+
description: "AWS configuration \u2014 account and region settings",
|
|
10077
|
+
score: 5
|
|
10078
|
+
},
|
|
10079
|
+
{
|
|
10080
|
+
full: import_path19.default.join(home, ".config", "gcloud", "credentials.db"),
|
|
10081
|
+
label: "~/.config/gcloud/credentials.db",
|
|
10082
|
+
description: "Google Cloud credentials",
|
|
10083
|
+
score: 15
|
|
10084
|
+
},
|
|
10085
|
+
{
|
|
10086
|
+
full: import_path19.default.join(home, ".docker", "config.json"),
|
|
10087
|
+
label: "~/.docker/config.json",
|
|
10088
|
+
description: "Docker registry auth tokens",
|
|
10089
|
+
score: 10
|
|
10090
|
+
},
|
|
10091
|
+
{
|
|
10092
|
+
full: import_path19.default.join(home, ".netrc"),
|
|
10093
|
+
label: "~/.netrc",
|
|
10094
|
+
description: "FTP/HTTP credentials in plain text",
|
|
10095
|
+
score: 15
|
|
10096
|
+
},
|
|
10097
|
+
{
|
|
10098
|
+
full: import_path19.default.join(home, ".npmrc"),
|
|
10099
|
+
label: "~/.npmrc",
|
|
10100
|
+
description: "npm auth token \u2014 can publish packages as you",
|
|
10101
|
+
score: 10
|
|
10102
|
+
},
|
|
10103
|
+
{
|
|
10104
|
+
full: import_path19.default.join(home, ".node9", "credentials.json"),
|
|
10105
|
+
label: "~/.node9/credentials.json",
|
|
10106
|
+
description: "Node9 cloud API key",
|
|
10107
|
+
score: 10
|
|
10108
|
+
},
|
|
10109
|
+
{
|
|
10110
|
+
full: import_path19.default.join(cwd, ".env"),
|
|
10111
|
+
label: ".env (current folder)",
|
|
10112
|
+
description: "App secrets \u2014 database passwords, API keys",
|
|
10113
|
+
score: 20
|
|
10114
|
+
},
|
|
10115
|
+
{
|
|
10116
|
+
full: import_path19.default.join(cwd, ".env.local"),
|
|
10117
|
+
label: ".env.local (current folder)",
|
|
10118
|
+
description: "Local overrides \u2014 often contains real credentials",
|
|
10119
|
+
score: 15
|
|
10120
|
+
},
|
|
10121
|
+
{
|
|
10122
|
+
full: import_path19.default.join(cwd, ".env.production"),
|
|
10123
|
+
label: ".env.production (current folder)",
|
|
10124
|
+
description: "Production secrets",
|
|
10125
|
+
score: 20
|
|
10126
|
+
}
|
|
9985
10127
|
];
|
|
9986
10128
|
}
|
|
9987
|
-
|
|
10129
|
+
function isReadable(filePath) {
|
|
9988
10130
|
try {
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
});
|
|
9992
|
-
if (!res.ok) return null;
|
|
9993
|
-
const json = await res.json();
|
|
9994
|
-
if (!json || typeof json !== "object") return null;
|
|
9995
|
-
const out = {};
|
|
9996
|
-
for (const [key, value] of Object.entries(json)) {
|
|
9997
|
-
const tuple = tupleFromLiteLLM(value);
|
|
9998
|
-
if (tuple) out[key.toLowerCase()] = tuple;
|
|
9999
|
-
}
|
|
10000
|
-
if (Object.keys(out).length < 10) {
|
|
10001
|
-
return null;
|
|
10002
|
-
}
|
|
10003
|
-
return out;
|
|
10131
|
+
import_fs17.default.accessSync(filePath, import_fs17.default.constants.R_OK);
|
|
10132
|
+
return true;
|
|
10004
10133
|
} catch {
|
|
10005
|
-
return
|
|
10134
|
+
return false;
|
|
10006
10135
|
}
|
|
10007
10136
|
}
|
|
10008
|
-
|
|
10009
|
-
if (
|
|
10010
|
-
|
|
10011
|
-
if (
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10137
|
+
function scoreLabel(score) {
|
|
10138
|
+
if (score >= 80) return import_chalk2.default.green(`${score}/100 Good`);
|
|
10139
|
+
if (score >= 50) return import_chalk2.default.yellow(`${score}/100 Moderate risk`);
|
|
10140
|
+
if (score >= 25) return import_chalk2.default.red(`${score}/100 High risk`);
|
|
10141
|
+
return import_chalk2.default.red.bold(`${score}/100 Critical`);
|
|
10142
|
+
}
|
|
10143
|
+
function runBlast() {
|
|
10144
|
+
const home = import_os16.default.homedir();
|
|
10145
|
+
const cwd = process.cwd();
|
|
10146
|
+
const paths = buildSensitivePaths(home, cwd);
|
|
10147
|
+
let scoreDeduction = 0;
|
|
10148
|
+
const reachable = [];
|
|
10149
|
+
for (const p of paths) {
|
|
10150
|
+
if (import_fs17.default.existsSync(p.full) && isReadable(p.full)) {
|
|
10151
|
+
reachable.push(p);
|
|
10152
|
+
scoreDeduction += p.score;
|
|
10153
|
+
}
|
|
10016
10154
|
}
|
|
10017
|
-
const
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10155
|
+
const envFindings = [];
|
|
10156
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
10157
|
+
if (!value) continue;
|
|
10158
|
+
const match = scanArgs({ [key]: value });
|
|
10159
|
+
if (match) {
|
|
10160
|
+
envFindings.push({ key, patternName: match.patternName });
|
|
10161
|
+
scoreDeduction += 10;
|
|
10162
|
+
}
|
|
10024
10163
|
}
|
|
10025
|
-
|
|
10026
|
-
memCacheAt = Date.now();
|
|
10027
|
-
lookupCache.clear();
|
|
10164
|
+
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
10028
10165
|
}
|
|
10029
|
-
function
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10166
|
+
function registerBlastCommand(program2) {
|
|
10167
|
+
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
10168
|
+
const home = import_os16.default.homedir();
|
|
10169
|
+
const cwd = process.cwd();
|
|
10170
|
+
const { reachable, envFindings, score } = runBlast();
|
|
10171
|
+
console.log("");
|
|
10172
|
+
console.log(
|
|
10173
|
+
import_chalk2.default.bold(" \u{1F52D} Node9 Blast Radius") + import_chalk2.default.dim(" \xB7 what an AI agent can reach from here")
|
|
10174
|
+
);
|
|
10175
|
+
console.log(import_chalk2.default.dim(" Running in: ") + import_chalk2.default.white(cwd.replace(home, "~")));
|
|
10176
|
+
console.log("");
|
|
10177
|
+
if (reachable.length > 0) {
|
|
10178
|
+
console.log(" " + import_chalk2.default.red.bold("Sensitive files reachable:"));
|
|
10179
|
+
for (const p of reachable) {
|
|
10180
|
+
console.log(
|
|
10181
|
+
" " + import_chalk2.default.red("\u2717 ") + import_chalk2.default.yellow(p.label.padEnd(38)) + import_chalk2.default.dim(p.description)
|
|
10182
|
+
);
|
|
10183
|
+
}
|
|
10184
|
+
console.log("");
|
|
10042
10185
|
}
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10186
|
+
if (envFindings.length > 0) {
|
|
10187
|
+
console.log(" " + import_chalk2.default.red.bold("Secrets in active environment:"));
|
|
10188
|
+
for (const f of envFindings) {
|
|
10189
|
+
console.log(
|
|
10190
|
+
" " + import_chalk2.default.red("\u2717 ") + import_chalk2.default.yellow(f.key.padEnd(38)) + import_chalk2.default.dim(f.patternName)
|
|
10191
|
+
);
|
|
10047
10192
|
}
|
|
10193
|
+
console.log("");
|
|
10048
10194
|
}
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10195
|
+
console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
|
|
10196
|
+
if (reachable.length === 0 && envFindings.length === 0) {
|
|
10197
|
+
console.log(" " + import_chalk2.default.green("\u2705 No sensitive files or environment secrets found."));
|
|
10198
|
+
console.log(" Security Score: " + scoreLabel(score));
|
|
10199
|
+
} else {
|
|
10200
|
+
console.log(
|
|
10201
|
+
" Security Score: " + scoreLabel(score) + import_chalk2.default.dim(
|
|
10202
|
+
` (${reachable.length} file${reachable.length !== 1 ? "s" : ""}, ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""})`
|
|
10203
|
+
)
|
|
10204
|
+
);
|
|
10205
|
+
console.log("");
|
|
10206
|
+
console.log(
|
|
10207
|
+
import_chalk2.default.dim(
|
|
10208
|
+
" 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."
|
|
10209
|
+
)
|
|
10210
|
+
);
|
|
10052
10211
|
}
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
return resolved;
|
|
10212
|
+
console.log("");
|
|
10213
|
+
});
|
|
10056
10214
|
}
|
|
10057
|
-
var
|
|
10058
|
-
var
|
|
10059
|
-
"src/
|
|
10215
|
+
var import_chalk2, import_fs17, import_path19, import_os16;
|
|
10216
|
+
var init_blast = __esm({
|
|
10217
|
+
"src/cli/commands/blast.ts"() {
|
|
10060
10218
|
"use strict";
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
BUNDLED_PRICING = {
|
|
10067
|
-
// Anthropic
|
|
10068
|
-
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10069
|
-
"claude-opus-4-1": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10070
|
-
"claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10071
|
-
"claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10072
|
-
"claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10073
|
-
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10074
|
-
"claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10075
|
-
"claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10076
|
-
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10077
|
-
"claude-haiku-4-5": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10078
|
-
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10079
|
-
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10080
|
-
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10081
|
-
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
10082
|
-
// OpenAI
|
|
10083
|
-
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
10084
|
-
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
10085
|
-
"gpt-5": [1e-5, 3e-5, 0, 5e-6],
|
|
10086
|
-
// Google
|
|
10087
|
-
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
10088
|
-
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
10089
|
-
};
|
|
10090
|
-
CACHE_FILE = () => import_path18.default.join(import_os15.default.homedir(), ".node9", "model-pricing.json");
|
|
10091
|
-
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10092
|
-
memCache = null;
|
|
10093
|
-
memCacheAt = 0;
|
|
10094
|
-
lookupCache = /* @__PURE__ */ new Map();
|
|
10219
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
10220
|
+
import_fs17 = __toESM(require("fs"));
|
|
10221
|
+
import_path19 = __toESM(require("path"));
|
|
10222
|
+
import_os16 = __toESM(require("os"));
|
|
10223
|
+
init_dlp();
|
|
10095
10224
|
}
|
|
10096
10225
|
});
|
|
10097
10226
|
|
|
10098
|
-
// src/
|
|
10099
|
-
function
|
|
10100
|
-
|
|
10227
|
+
// src/cli/render/scan-derive.ts
|
|
10228
|
+
function classifyScore(score) {
|
|
10229
|
+
if (score >= 80) return { band: "good", label: "Good", color: import_chalk3.default.green };
|
|
10230
|
+
if (score >= 50) return { band: "at-risk", label: "At Risk", color: import_chalk3.default.yellow };
|
|
10231
|
+
return { band: "critical", label: "Critical", color: import_chalk3.default.red };
|
|
10101
10232
|
}
|
|
10102
|
-
function
|
|
10103
|
-
|
|
10233
|
+
function topDlpPatterns(findings, n) {
|
|
10234
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10235
|
+
for (const f of findings) {
|
|
10236
|
+
counts.set(f.patternName, (counts.get(f.patternName) ?? 0) + 1);
|
|
10237
|
+
}
|
|
10238
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name, count]) => ({ name, count }));
|
|
10104
10239
|
}
|
|
10105
|
-
function
|
|
10106
|
-
const
|
|
10107
|
-
for (const
|
|
10108
|
-
const
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
const mp = import_path19.default.join(yp, m);
|
|
10112
|
-
if (!isDir(mp)) continue;
|
|
10113
|
-
for (const d of safeReaddir(mp)) {
|
|
10114
|
-
const dp = import_path19.default.join(mp, d);
|
|
10115
|
-
if (!isDir(dp)) continue;
|
|
10116
|
-
for (const f of safeReaddir(dp)) {
|
|
10117
|
-
if (f.endsWith(".jsonl")) out.push(import_path19.default.join(dp, f));
|
|
10118
|
-
}
|
|
10119
|
-
}
|
|
10240
|
+
function topRulesByVerdict(sections, verdict, n) {
|
|
10241
|
+
const matched = [];
|
|
10242
|
+
for (const section of sections) {
|
|
10243
|
+
for (const rule of section.rules) {
|
|
10244
|
+
const matches = verdict === "block" ? rule.verdict === "block" : rule.verdict !== "block";
|
|
10245
|
+
if (matches) matched.push({ name: rule.name, count: rule.findings.length });
|
|
10120
10246
|
}
|
|
10121
10247
|
}
|
|
10122
|
-
return
|
|
10248
|
+
return matched.sort((a, b) => b.count - a.count).slice(0, n);
|
|
10123
10249
|
}
|
|
10124
|
-
function
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
return [];
|
|
10129
|
-
}
|
|
10250
|
+
function computeLoopWaste(loops, totalToolCalls) {
|
|
10251
|
+
const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
10252
|
+
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
10253
|
+
return { wastedCalls, wastePct };
|
|
10130
10254
|
}
|
|
10131
|
-
function
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10255
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
10256
|
+
const out = [];
|
|
10257
|
+
for (const section of sections) {
|
|
10258
|
+
if (section.sourceType !== "shield") continue;
|
|
10259
|
+
if (!section.shieldKey) continue;
|
|
10260
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
10261
|
+
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);
|
|
10262
|
+
out.push({
|
|
10263
|
+
shieldName: section.shieldKey,
|
|
10264
|
+
totalCatches,
|
|
10265
|
+
blockCatches: section.blockedCount,
|
|
10266
|
+
reviewCatches: section.reviewCount,
|
|
10267
|
+
topRuleLabels
|
|
10268
|
+
});
|
|
10136
10269
|
}
|
|
10270
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
10137
10271
|
}
|
|
10138
|
-
function
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
let entry;
|
|
10150
|
-
try {
|
|
10151
|
-
entry = JSON.parse(raw);
|
|
10152
|
-
} catch {
|
|
10153
|
-
continue;
|
|
10154
|
-
}
|
|
10155
|
-
const p = entry.payload ?? {};
|
|
10156
|
-
if (entry.type === "session_meta") {
|
|
10157
|
-
if (!sessionStart2 && typeof p["timestamp"] === "string") sessionStart2 = p["timestamp"];
|
|
10158
|
-
if (!runId && typeof p["id"] === "string") runId = p["id"];
|
|
10159
|
-
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
10160
|
-
continue;
|
|
10161
|
-
}
|
|
10162
|
-
if (entry.type === "turn_context") {
|
|
10163
|
-
if (typeof p["model"] === "string") model = p["model"];
|
|
10164
|
-
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
10165
|
-
continue;
|
|
10166
|
-
}
|
|
10167
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
10168
|
-
const info = p["info"] ?? {};
|
|
10169
|
-
const usage = info["total_token_usage"] ?? {};
|
|
10170
|
-
if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
|
|
10171
|
-
if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
|
|
10172
|
-
if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
|
|
10173
|
-
sawUsage = true;
|
|
10174
|
-
}
|
|
10272
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
10273
|
+
const inner = width - 4;
|
|
10274
|
+
const out = [];
|
|
10275
|
+
const titlePad = ` ${title} `;
|
|
10276
|
+
const titleWidth = (0, import_string_width.default)(titlePad);
|
|
10277
|
+
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
10278
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - (0, import_string_width.default)(titleSegment)));
|
|
10279
|
+
out.push(import_chalk3.default.dim("\u256D\u2500") + import_chalk3.default.bold(titleSegment) + import_chalk3.default.dim(`${dashFill}\u2500\u256E`));
|
|
10280
|
+
for (const line of bodyLines) {
|
|
10281
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
10282
|
+
out.push(import_chalk3.default.dim("\u2502 ") + line.rendered + padding + import_chalk3.default.dim(" \u2502"));
|
|
10175
10283
|
}
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
if (nonCached === 0 && output === 0 && cached === 0) return null;
|
|
10179
|
-
const norm = normalizeModel(model || "gpt-5");
|
|
10180
|
-
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
10181
|
-
const costUSD = nonCached * pin + output * pout + cached * pcr;
|
|
10182
|
-
return {
|
|
10183
|
-
date: sessionStart2.slice(0, 10),
|
|
10184
|
-
model: norm,
|
|
10185
|
-
workingDir: cwd,
|
|
10186
|
-
runId,
|
|
10187
|
-
costUSD,
|
|
10188
|
-
inputTokens: nonCached,
|
|
10189
|
-
outputTokens: output,
|
|
10190
|
-
cacheReadTokens: cached,
|
|
10191
|
-
cacheWriteTokens: 0
|
|
10192
|
-
};
|
|
10284
|
+
out.push(import_chalk3.default.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
10285
|
+
return out;
|
|
10193
10286
|
}
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
|
|
10219
|
-
continue;
|
|
10220
|
-
}
|
|
10221
|
-
let content;
|
|
10222
|
-
try {
|
|
10223
|
-
content = import_fs17.default.readFileSync(file, "utf8");
|
|
10224
|
-
} catch {
|
|
10225
|
-
continue;
|
|
10226
|
-
}
|
|
10227
|
-
const e = parseCodexSession(content.split("\n"));
|
|
10228
|
-
if (!e) continue;
|
|
10229
|
-
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
10230
|
-
const prev = combined.get(key);
|
|
10231
|
-
if (prev) {
|
|
10232
|
-
prev.costUSD += e.costUSD;
|
|
10233
|
-
prev.inputTokens += e.inputTokens;
|
|
10234
|
-
prev.outputTokens += e.outputTokens;
|
|
10235
|
-
prev.cacheReadTokens += e.cacheReadTokens;
|
|
10236
|
-
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
10237
|
-
} else {
|
|
10238
|
-
combined.set(key, { ...e });
|
|
10239
|
-
}
|
|
10240
|
-
}
|
|
10241
|
-
return [...combined.values()];
|
|
10242
|
-
}
|
|
10287
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
10288
|
+
const t = new Date(timestamp).getTime();
|
|
10289
|
+
if (Number.isNaN(t)) return "?";
|
|
10290
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
10291
|
+
if (days < 1) return "today";
|
|
10292
|
+
if (days > 90) return "90d+";
|
|
10293
|
+
return `${days}d`;
|
|
10294
|
+
}
|
|
10295
|
+
var import_chalk3, import_string_width, PANEL_WIDTH;
|
|
10296
|
+
var init_scan_derive = __esm({
|
|
10297
|
+
"src/cli/render/scan-derive.ts"() {
|
|
10298
|
+
"use strict";
|
|
10299
|
+
import_chalk3 = __toESM(require("chalk"));
|
|
10300
|
+
import_string_width = __toESM(require("string-width"));
|
|
10301
|
+
PANEL_WIDTH = 76;
|
|
10302
|
+
}
|
|
10303
|
+
});
|
|
10304
|
+
|
|
10305
|
+
// src/protection.ts
|
|
10306
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
10307
|
+
var init_protection = __esm({
|
|
10308
|
+
"src/protection.ts"() {
|
|
10309
|
+
"use strict";
|
|
10310
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
10311
|
+
"project-jail": 0.7
|
|
10243
10312
|
};
|
|
10244
10313
|
}
|
|
10245
10314
|
});
|
|
10246
10315
|
|
|
10247
|
-
// src/
|
|
10248
|
-
function
|
|
10249
|
-
|
|
10250
|
-
}
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10316
|
+
// src/cli/render/scan-json.ts
|
|
10317
|
+
function buildScanJson(input) {
|
|
10318
|
+
const { summary, blast, isWired, generatedAt } = input;
|
|
10319
|
+
const { band } = classifyScore(blast.score);
|
|
10320
|
+
return {
|
|
10321
|
+
schemaVersion: 1,
|
|
10322
|
+
generatedAt,
|
|
10323
|
+
isWired,
|
|
10324
|
+
score: blast.score,
|
|
10325
|
+
band,
|
|
10326
|
+
totals: {
|
|
10327
|
+
blocked: summary.byVerdict.blocked,
|
|
10328
|
+
review: summary.byVerdict.supervised,
|
|
10329
|
+
leaks: summary.byVerdict.leaks,
|
|
10330
|
+
loops: summary.byVerdict.loops,
|
|
10331
|
+
blastExposures: blast.reachable.length + blast.envFindings.length
|
|
10332
|
+
},
|
|
10333
|
+
summary,
|
|
10334
|
+
blast: {
|
|
10335
|
+
score: blast.score,
|
|
10336
|
+
reachable: blast.reachable,
|
|
10337
|
+
envFindings: blast.envFindings
|
|
10257
10338
|
}
|
|
10258
|
-
}
|
|
10259
|
-
if (!tuple) return null;
|
|
10260
|
-
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
10339
|
+
};
|
|
10261
10340
|
}
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
return [];
|
|
10341
|
+
var init_scan_json = __esm({
|
|
10342
|
+
"src/cli/render/scan-json.ts"() {
|
|
10343
|
+
"use strict";
|
|
10344
|
+
init_scan_derive();
|
|
10267
10345
|
}
|
|
10346
|
+
});
|
|
10347
|
+
|
|
10348
|
+
// src/cli/render/scan-history.ts
|
|
10349
|
+
function defaultHistoryPath() {
|
|
10350
|
+
return import_path20.default.join(import_os17.default.homedir(), ".node9", "scan-history.json");
|
|
10268
10351
|
}
|
|
10269
|
-
function
|
|
10352
|
+
function readPreviousScan(opts = {}) {
|
|
10353
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10270
10354
|
try {
|
|
10271
|
-
|
|
10355
|
+
if (!import_fs18.default.existsSync(filePath)) return null;
|
|
10356
|
+
const raw = import_fs18.default.readFileSync(filePath, "utf8");
|
|
10357
|
+
const parsed = JSON.parse(raw);
|
|
10358
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
10359
|
+
const last = parsed[parsed.length - 1];
|
|
10360
|
+
if (!isValidRecord(last)) return null;
|
|
10361
|
+
return last;
|
|
10272
10362
|
} catch {
|
|
10273
|
-
return
|
|
10363
|
+
return null;
|
|
10274
10364
|
}
|
|
10275
10365
|
}
|
|
10276
|
-
function
|
|
10277
|
-
const
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10366
|
+
function appendScanHistory(record, opts = {}) {
|
|
10367
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10368
|
+
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
10369
|
+
try {
|
|
10370
|
+
import_fs18.default.mkdirSync(import_path20.default.dirname(filePath), { recursive: true });
|
|
10371
|
+
let history = [];
|
|
10372
|
+
if (import_fs18.default.existsSync(filePath)) {
|
|
10373
|
+
try {
|
|
10374
|
+
const parsed = JSON.parse(import_fs18.default.readFileSync(filePath, "utf8"));
|
|
10375
|
+
if (Array.isArray(parsed)) {
|
|
10376
|
+
history = parsed.filter(isValidRecord);
|
|
10377
|
+
}
|
|
10378
|
+
} catch {
|
|
10284
10379
|
}
|
|
10285
10380
|
}
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
function parseGeminiSession(lines, project) {
|
|
10290
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
10291
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
10292
|
-
let runId = "";
|
|
10293
|
-
for (const raw of lines) {
|
|
10294
|
-
if (!raw.trim()) continue;
|
|
10295
|
-
let obj;
|
|
10296
|
-
try {
|
|
10297
|
-
obj = JSON.parse(raw);
|
|
10298
|
-
} catch {
|
|
10299
|
-
continue;
|
|
10300
|
-
}
|
|
10301
|
-
if (typeof obj.sessionId === "string" && !runId) runId = obj.sessionId;
|
|
10302
|
-
if (!obj.tokens || !obj.model || !obj.timestamp) continue;
|
|
10303
|
-
if (obj.id) {
|
|
10304
|
-
if (seenIds.has(obj.id)) continue;
|
|
10305
|
-
seenIds.add(obj.id);
|
|
10306
|
-
}
|
|
10307
|
-
const price = geminiPriceFor(obj.model);
|
|
10308
|
-
if (!price) continue;
|
|
10309
|
-
const inp = obj.tokens.input ?? 0;
|
|
10310
|
-
const out = obj.tokens.output ?? 0;
|
|
10311
|
-
const cached = Math.min(obj.tokens.cached ?? 0, inp);
|
|
10312
|
-
const fresh = Math.max(0, inp - cached);
|
|
10313
|
-
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
10314
|
-
const date = obj.timestamp.slice(0, 10);
|
|
10315
|
-
const model = normalizeModel(obj.model);
|
|
10316
|
-
const key = `${date}::${model}`;
|
|
10317
|
-
const prev = byKey.get(key);
|
|
10318
|
-
if (prev) {
|
|
10319
|
-
prev.costUSD += cost;
|
|
10320
|
-
prev.inputTokens += fresh;
|
|
10321
|
-
prev.outputTokens += out;
|
|
10322
|
-
prev.cacheReadTokens += cached;
|
|
10323
|
-
} else {
|
|
10324
|
-
byKey.set(key, {
|
|
10325
|
-
date,
|
|
10326
|
-
model,
|
|
10327
|
-
workingDir: project,
|
|
10328
|
-
runId,
|
|
10329
|
-
costUSD: cost,
|
|
10330
|
-
inputTokens: fresh,
|
|
10331
|
-
outputTokens: out,
|
|
10332
|
-
cacheReadTokens: cached,
|
|
10333
|
-
cacheWriteTokens: 0
|
|
10334
|
-
});
|
|
10381
|
+
history.push(record);
|
|
10382
|
+
if (history.length > cap) {
|
|
10383
|
+
history = history.slice(history.length - cap);
|
|
10335
10384
|
}
|
|
10385
|
+
import_fs18.default.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
10386
|
+
} catch (err2) {
|
|
10387
|
+
process.stderr.write(
|
|
10388
|
+
`[node9] Warning: could not write scan-history.json: ${err2.message}
|
|
10389
|
+
`
|
|
10390
|
+
);
|
|
10336
10391
|
}
|
|
10337
|
-
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
10338
|
-
return [...byKey.values()];
|
|
10339
10392
|
}
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10393
|
+
function computeScanDelta(current, previous, now = Date.now()) {
|
|
10394
|
+
if (!previous) return null;
|
|
10395
|
+
const prevMs = Date.parse(previous.timestamp);
|
|
10396
|
+
if (Number.isNaN(prevMs)) return null;
|
|
10397
|
+
const scoreDelta = current.score - previous.score;
|
|
10398
|
+
const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
|
|
10399
|
+
if (scoreDelta === 0 && daysAgo === 0) return null;
|
|
10400
|
+
return { scoreDelta, daysAgo };
|
|
10401
|
+
}
|
|
10402
|
+
function isValidRecord(x) {
|
|
10403
|
+
if (typeof x !== "object" || x === null) return false;
|
|
10404
|
+
const r = x;
|
|
10405
|
+
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";
|
|
10406
|
+
}
|
|
10407
|
+
var import_fs18, import_path20, import_os17, SCAN_HISTORY_CAP;
|
|
10408
|
+
var init_scan_history = __esm({
|
|
10409
|
+
"src/cli/render/scan-history.ts"() {
|
|
10343
10410
|
"use strict";
|
|
10344
10411
|
import_fs18 = __toESM(require("fs"));
|
|
10345
|
-
import_os17 = __toESM(require("os"));
|
|
10346
10412
|
import_path20 = __toESM(require("path"));
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
geminiSource = {
|
|
10350
|
-
id: "gemini",
|
|
10351
|
-
available() {
|
|
10352
|
-
try {
|
|
10353
|
-
return import_fs18.default.existsSync(geminiTmpDir());
|
|
10354
|
-
} catch {
|
|
10355
|
-
return false;
|
|
10356
|
-
}
|
|
10357
|
-
},
|
|
10358
|
-
collect(sinceMs) {
|
|
10359
|
-
const combined = /* @__PURE__ */ new Map();
|
|
10360
|
-
for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
10361
|
-
try {
|
|
10362
|
-
if (sinceMs !== void 0 && import_fs18.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
10363
|
-
} catch {
|
|
10364
|
-
continue;
|
|
10365
|
-
}
|
|
10366
|
-
let content;
|
|
10367
|
-
try {
|
|
10368
|
-
content = import_fs18.default.readFileSync(file, "utf8");
|
|
10369
|
-
} catch {
|
|
10370
|
-
continue;
|
|
10371
|
-
}
|
|
10372
|
-
for (const e of parseGeminiSession(content.split("\n"), project)) {
|
|
10373
|
-
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
10374
|
-
const prev = combined.get(key);
|
|
10375
|
-
if (prev) {
|
|
10376
|
-
prev.costUSD += e.costUSD;
|
|
10377
|
-
prev.inputTokens += e.inputTokens;
|
|
10378
|
-
prev.outputTokens += e.outputTokens;
|
|
10379
|
-
prev.cacheReadTokens += e.cacheReadTokens;
|
|
10380
|
-
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
10381
|
-
} else {
|
|
10382
|
-
combined.set(key, { ...e });
|
|
10383
|
-
}
|
|
10384
|
-
}
|
|
10385
|
-
}
|
|
10386
|
-
return [...combined.values()];
|
|
10387
|
-
}
|
|
10388
|
-
};
|
|
10413
|
+
import_os17 = __toESM(require("os"));
|
|
10414
|
+
SCAN_HISTORY_CAP = 30;
|
|
10389
10415
|
}
|
|
10390
10416
|
});
|
|
10391
10417
|
|
|
@@ -11424,19 +11450,15 @@ var init_scan_upload_history = __esm({
|
|
|
11424
11450
|
|
|
11425
11451
|
// src/cli/commands/scan.ts
|
|
11426
11452
|
function claudeModelPrice(model) {
|
|
11427
|
-
const
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
}
|
|
11431
|
-
return null;
|
|
11453
|
+
const t = pricingFor(model);
|
|
11454
|
+
if (!t) return null;
|
|
11455
|
+
const [i, o, cw, cr] = t;
|
|
11456
|
+
return { i, o, cw, cr };
|
|
11432
11457
|
}
|
|
11433
11458
|
function geminiModelPrice(model) {
|
|
11434
|
-
const
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
}
|
|
11438
|
-
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
11439
|
-
return null;
|
|
11459
|
+
const p = geminiPriceFor(model);
|
|
11460
|
+
if (!p) return null;
|
|
11461
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
11440
11462
|
}
|
|
11441
11463
|
function isNode9SelfOutput(text) {
|
|
11442
11464
|
let hits = 0;
|
|
@@ -12082,14 +12104,17 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12082
12104
|
if (!import_fs23.default.existsSync(chatsDir)) continue;
|
|
12083
12105
|
let chatFiles;
|
|
12084
12106
|
try {
|
|
12085
|
-
chatFiles = import_fs23.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
12107
|
+
chatFiles = import_fs23.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
12086
12108
|
} catch {
|
|
12087
12109
|
continue;
|
|
12088
12110
|
}
|
|
12111
|
+
const seenSessions = /* @__PURE__ */ new Set();
|
|
12089
12112
|
for (const chatFile of chatFiles) {
|
|
12113
|
+
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
12114
|
+
if (seenSessions.has(sessionId)) continue;
|
|
12115
|
+
seenSessions.add(sessionId);
|
|
12090
12116
|
result.filesScanned++;
|
|
12091
12117
|
onProgress?.(result.filesScanned);
|
|
12092
|
-
const sessionId = chatFile.replace(/\.json$/, "");
|
|
12093
12118
|
let raw;
|
|
12094
12119
|
try {
|
|
12095
12120
|
raw = import_fs23.default.readFileSync(import_path25.default.join(chatsDir, chatFile), "utf-8");
|
|
@@ -12099,7 +12124,18 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12099
12124
|
const sessionCalls = [];
|
|
12100
12125
|
let session;
|
|
12101
12126
|
try {
|
|
12102
|
-
|
|
12127
|
+
if (chatFile.endsWith(".jsonl")) {
|
|
12128
|
+
const messages = raw.split("\n").filter((l) => l.trim()).map((l) => {
|
|
12129
|
+
try {
|
|
12130
|
+
return JSON.parse(l);
|
|
12131
|
+
} catch {
|
|
12132
|
+
return null;
|
|
12133
|
+
}
|
|
12134
|
+
}).filter((m) => m !== null);
|
|
12135
|
+
session = { messages };
|
|
12136
|
+
} else {
|
|
12137
|
+
session = JSON.parse(raw);
|
|
12138
|
+
}
|
|
12103
12139
|
} catch {
|
|
12104
12140
|
continue;
|
|
12105
12141
|
}
|
|
@@ -12709,6 +12745,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12709
12745
|
let lastTotalInput = 0;
|
|
12710
12746
|
let lastTotalCached = 0;
|
|
12711
12747
|
let lastTotalOutput = 0;
|
|
12748
|
+
let model = "";
|
|
12712
12749
|
for (const line of lines) {
|
|
12713
12750
|
if (!line.trim()) continue;
|
|
12714
12751
|
onLine?.();
|
|
@@ -12726,6 +12763,10 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12726
12763
|
projLabel = stripTerminalEscapes(cwd.replace(import_os22.default.homedir(), "~")).slice(0, 40);
|
|
12727
12764
|
continue;
|
|
12728
12765
|
}
|
|
12766
|
+
if (entry.type === "turn_context" && typeof payload["model"] === "string") {
|
|
12767
|
+
model = payload["model"];
|
|
12768
|
+
continue;
|
|
12769
|
+
}
|
|
12729
12770
|
if (entry.type === "event_msg" && payload["type"] === "token_count") {
|
|
12730
12771
|
const info = payload["info"];
|
|
12731
12772
|
const usage = info?.["total_token_usage"] ?? {};
|
|
@@ -12869,8 +12910,11 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12869
12910
|
}
|
|
12870
12911
|
}
|
|
12871
12912
|
}
|
|
12872
|
-
|
|
12873
|
-
|
|
12913
|
+
result.totalCostUSD += codexSessionCost(model, {
|
|
12914
|
+
input: lastTotalInput,
|
|
12915
|
+
cached: lastTotalCached,
|
|
12916
|
+
output: lastTotalOutput
|
|
12917
|
+
});
|
|
12874
12918
|
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
|
|
12875
12919
|
}
|
|
12876
12920
|
return result;
|
|
@@ -13917,7 +13961,7 @@ function registerScanCommand(program2) {
|
|
|
13917
13961
|
}
|
|
13918
13962
|
);
|
|
13919
13963
|
}
|
|
13920
|
-
var import_chalk5, import_fs23, import_path25, import_os22, import_string_width2,
|
|
13964
|
+
var import_chalk5, import_fs23, import_path25, import_os22, import_string_width2, 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;
|
|
13921
13965
|
var init_scan = __esm({
|
|
13922
13966
|
"src/cli/commands/scan.ts"() {
|
|
13923
13967
|
"use strict";
|
|
@@ -13930,6 +13974,9 @@ var init_scan = __esm({
|
|
|
13930
13974
|
init_policy();
|
|
13931
13975
|
init_dist();
|
|
13932
13976
|
init_dlp();
|
|
13977
|
+
init_litellm();
|
|
13978
|
+
init_cost_gemini();
|
|
13979
|
+
init_cost_codex();
|
|
13933
13980
|
init_hook_payload();
|
|
13934
13981
|
init_dist();
|
|
13935
13982
|
init_scan_summary();
|
|
@@ -13940,26 +13987,6 @@ var init_scan = __esm({
|
|
|
13940
13987
|
import_string_width2 = __toESM(require("string-width"));
|
|
13941
13988
|
init_scan_json();
|
|
13942
13989
|
init_scan_history();
|
|
13943
|
-
CLAUDE_PRICING = {
|
|
13944
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13945
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13946
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
13947
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13948
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13949
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13950
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13951
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13952
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
13953
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
13954
|
-
};
|
|
13955
|
-
GEMINI_PRICING = {
|
|
13956
|
-
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
13957
|
-
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
13958
|
-
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
13959
|
-
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
13960
|
-
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
13961
|
-
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
13962
|
-
};
|
|
13963
13990
|
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13964
13991
|
".ts",
|
|
13965
13992
|
".tsx",
|
|
@@ -20140,6 +20167,7 @@ var import_os36 = __toESM(require("os"));
|
|
|
20140
20167
|
var import_path41 = __toESM(require("path"));
|
|
20141
20168
|
init_costSync();
|
|
20142
20169
|
init_litellm();
|
|
20170
|
+
init_cost_codex();
|
|
20143
20171
|
var TEST_COMMAND_RE3 = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
20144
20172
|
function buildTestTimestamps(allEntries) {
|
|
20145
20173
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -20236,24 +20264,11 @@ function isAllow(decision) {
|
|
|
20236
20264
|
function isDlp(checkedBy) {
|
|
20237
20265
|
return !!checkedBy?.includes("dlp");
|
|
20238
20266
|
}
|
|
20239
|
-
var CLAUDE_PRICING2 = {
|
|
20240
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
20241
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
20242
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
20243
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20244
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20245
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20246
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20247
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20248
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
20249
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
20250
|
-
};
|
|
20251
20267
|
function claudeModelPrice2(model) {
|
|
20252
|
-
const
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
}
|
|
20256
|
-
return null;
|
|
20268
|
+
const t = pricingFor(model);
|
|
20269
|
+
if (!t) return null;
|
|
20270
|
+
const [i, o, cw, cr] = t;
|
|
20271
|
+
return { i, o, cw, cr };
|
|
20257
20272
|
}
|
|
20258
20273
|
function emptyClaudeCostAccumulator() {
|
|
20259
20274
|
return {
|
|
@@ -20368,6 +20383,7 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20368
20383
|
return;
|
|
20369
20384
|
}
|
|
20370
20385
|
let sessionStart2 = "";
|
|
20386
|
+
let model = "";
|
|
20371
20387
|
let lastTotalInput = 0;
|
|
20372
20388
|
let lastTotalCached = 0;
|
|
20373
20389
|
let lastTotalOutput = 0;
|
|
@@ -20385,6 +20401,10 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20385
20401
|
sessionStart2 = String(p["timestamp"] ?? "");
|
|
20386
20402
|
continue;
|
|
20387
20403
|
}
|
|
20404
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
20405
|
+
model = p["model"];
|
|
20406
|
+
continue;
|
|
20407
|
+
}
|
|
20388
20408
|
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
20389
20409
|
const info = p["info"] ?? {};
|
|
20390
20410
|
const usage = info["total_token_usage"] ?? {};
|
|
@@ -20399,12 +20419,17 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20399
20419
|
if (!sessionStart2) return;
|
|
20400
20420
|
const ts = new Date(sessionStart2);
|
|
20401
20421
|
if (ts < start || ts > end) return;
|
|
20402
|
-
const
|
|
20403
|
-
|
|
20422
|
+
const cost = codexSessionCost(model, {
|
|
20423
|
+
input: lastTotalInput,
|
|
20424
|
+
cached: lastTotalCached,
|
|
20425
|
+
output: lastTotalOutput
|
|
20426
|
+
});
|
|
20404
20427
|
acc.total += cost;
|
|
20405
20428
|
acc.toolCalls += sessionToolCalls;
|
|
20406
20429
|
const dateKey = sessionStart2.slice(0, 10);
|
|
20407
20430
|
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
20431
|
+
const normModel = normalizeModel(model || "gpt-5");
|
|
20432
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
20408
20433
|
}
|
|
20409
20434
|
function listCodexSessionFiles2(sessionsBase) {
|
|
20410
20435
|
const jsonlFiles = [];
|
|
@@ -20442,13 +20467,25 @@ function listCodexSessionFiles2(sessionsBase) {
|
|
|
20442
20467
|
}
|
|
20443
20468
|
return jsonlFiles;
|
|
20444
20469
|
}
|
|
20470
|
+
function mergeByModel(...maps) {
|
|
20471
|
+
const out = /* @__PURE__ */ new Map();
|
|
20472
|
+
for (const m of maps) {
|
|
20473
|
+
for (const [k, v] of m) out.set(k, (out.get(k) ?? 0) + v);
|
|
20474
|
+
}
|
|
20475
|
+
return out;
|
|
20476
|
+
}
|
|
20445
20477
|
function loadCodexCost(start, end, sessionsBase) {
|
|
20446
|
-
const acc = {
|
|
20478
|
+
const acc = {
|
|
20479
|
+
total: 0,
|
|
20480
|
+
toolCalls: 0,
|
|
20481
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
20482
|
+
byModel: /* @__PURE__ */ new Map()
|
|
20483
|
+
};
|
|
20447
20484
|
const files = listCodexSessionFiles2(sessionsBase);
|
|
20448
20485
|
for (const filePath of files) {
|
|
20449
20486
|
processCodexCostFile(filePath, start, end, acc);
|
|
20450
20487
|
}
|
|
20451
|
-
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
20488
|
+
return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
|
|
20452
20489
|
}
|
|
20453
20490
|
var GEMINI_FALLBACK_MODELS2 = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
20454
20491
|
function geminiPriceFor2(model) {
|
|
@@ -20737,7 +20774,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
20737
20774
|
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
20738
20775
|
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
20739
20776
|
byDay: claudeCost.byDay,
|
|
20740
|
-
byModel: claudeCost.byModel,
|
|
20777
|
+
byModel: mergeByModel(claudeCost.byModel, codexCost.byModel),
|
|
20741
20778
|
byProject: claudeCost.byProject
|
|
20742
20779
|
},
|
|
20743
20780
|
toolMap,
|
|
@@ -23510,40 +23547,19 @@ var import_fs45 = __toESM(require("fs"));
|
|
|
23510
23547
|
var import_path46 = __toESM(require("path"));
|
|
23511
23548
|
var import_os40 = __toESM(require("os"));
|
|
23512
23549
|
init_scan_summary();
|
|
23513
|
-
|
|
23514
|
-
|
|
23515
|
-
|
|
23516
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
23517
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23518
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23519
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23520
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23521
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23522
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
23523
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
23524
|
-
};
|
|
23550
|
+
init_litellm();
|
|
23551
|
+
init_cost_gemini();
|
|
23552
|
+
init_cost_codex();
|
|
23525
23553
|
function modelPrice(model) {
|
|
23526
|
-
const
|
|
23527
|
-
|
|
23528
|
-
|
|
23529
|
-
}
|
|
23530
|
-
return null;
|
|
23554
|
+
const t = pricingFor(model);
|
|
23555
|
+
if (!t) return null;
|
|
23556
|
+
const [i, o, cw, cr] = t;
|
|
23557
|
+
return { i, o, cw, cr };
|
|
23531
23558
|
}
|
|
23532
|
-
var GEMINI_PRICING2 = {
|
|
23533
|
-
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
23534
|
-
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
23535
|
-
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
23536
|
-
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
23537
|
-
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
23538
|
-
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
23539
|
-
};
|
|
23540
23559
|
function geminiModelPrice2(model) {
|
|
23541
|
-
const
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
}
|
|
23545
|
-
if (base.includes("flash")) return GEMINI_PRICING2["gemini-2.0-flash"];
|
|
23546
|
-
return null;
|
|
23560
|
+
const p = geminiPriceFor(model);
|
|
23561
|
+
if (!p) return null;
|
|
23562
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
23547
23563
|
}
|
|
23548
23564
|
function encodeProjectPath(projectPath) {
|
|
23549
23565
|
return projectPath.replace(/\//g, "-");
|
|
@@ -23836,6 +23852,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23836
23852
|
let lastTotalInput = 0;
|
|
23837
23853
|
let lastTotalCached = 0;
|
|
23838
23854
|
let lastTotalOutput = 0;
|
|
23855
|
+
let model = "";
|
|
23839
23856
|
for (const line of lines) {
|
|
23840
23857
|
if (!line.trim()) continue;
|
|
23841
23858
|
let entry;
|
|
@@ -23851,6 +23868,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23851
23868
|
cwd = String(p["cwd"] ?? "");
|
|
23852
23869
|
continue;
|
|
23853
23870
|
}
|
|
23871
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
23872
|
+
model = p["model"];
|
|
23873
|
+
continue;
|
|
23874
|
+
}
|
|
23854
23875
|
if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
|
|
23855
23876
|
firstPrompt = String(p["message"] ?? "");
|
|
23856
23877
|
continue;
|
|
@@ -23877,8 +23898,11 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23877
23898
|
}
|
|
23878
23899
|
if (!sessionId || !startTime) continue;
|
|
23879
23900
|
if (cutoff && new Date(startTime) < cutoff) continue;
|
|
23880
|
-
const
|
|
23881
|
-
|
|
23901
|
+
const costUSD = codexSessionCost(model, {
|
|
23902
|
+
input: lastTotalInput,
|
|
23903
|
+
cached: lastTotalCached,
|
|
23904
|
+
output: lastTotalOutput
|
|
23905
|
+
});
|
|
23882
23906
|
const windowEnd = new Date(
|
|
23883
23907
|
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
23884
23908
|
).toISOString();
|
|
@@ -23902,11 +23926,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23902
23926
|
}
|
|
23903
23927
|
function buildSessions(days, historyPath) {
|
|
23904
23928
|
const hPath = historyPath ?? import_path46.default.join(import_os40.default.homedir(), ".claude", "history.jsonl");
|
|
23905
|
-
let historyRaw;
|
|
23929
|
+
let historyRaw = "";
|
|
23906
23930
|
try {
|
|
23907
23931
|
historyRaw = import_fs45.default.readFileSync(hPath, "utf-8");
|
|
23908
23932
|
} catch {
|
|
23909
|
-
return [];
|
|
23910
23933
|
}
|
|
23911
23934
|
const cutoff = days !== null ? (() => {
|
|
23912
23935
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -24197,12 +24220,6 @@ function registerSessionsCommand(program2) {
|
|
|
24197
24220
|
console.log("");
|
|
24198
24221
|
console.log(import_chalk24.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk24.default.dim(" \u2014 what your AI agent did"));
|
|
24199
24222
|
console.log("");
|
|
24200
|
-
const historyPath = import_path46.default.join(import_os40.default.homedir(), ".claude", "history.jsonl");
|
|
24201
|
-
if (!import_fs45.default.existsSync(historyPath)) {
|
|
24202
|
-
console.log(import_chalk24.default.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
24203
|
-
console.log(import_chalk24.default.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
24204
|
-
return;
|
|
24205
|
-
}
|
|
24206
24223
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
24207
24224
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
24208
24225
|
console.log(import_chalk24.default.dim(" " + rangeLabel));
|