@node9/proxy 1.33.0 → 1.35.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 +1257 -1097
- package/dist/cli.mjs +1255 -1094
- package/dist/dashboard.mjs +335 -174
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5889,6 +5889,57 @@ function validateApiUrl(raw) {
|
|
|
5889
5889
|
}
|
|
5890
5890
|
return null;
|
|
5891
5891
|
}
|
|
5892
|
+
function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false, riskMetadata) {
|
|
5893
|
+
const validated = validateApiUrl(creds.apiUrl);
|
|
5894
|
+
if (!validated) {
|
|
5895
|
+
try {
|
|
5896
|
+
import_fs10.default.appendFileSync(
|
|
5897
|
+
HOOK_DEBUG_LOG,
|
|
5898
|
+
`[audit] refused to send: invalid apiUrl scheme/host (got "${String(creds.apiUrl).slice(0, 200)}")
|
|
5899
|
+
`
|
|
5900
|
+
);
|
|
5901
|
+
} catch {
|
|
5902
|
+
}
|
|
5903
|
+
return Promise.resolve();
|
|
5904
|
+
}
|
|
5905
|
+
const safeArgs = containsSensitiveArgs ? { tool: toolName, redacted: true } : args;
|
|
5906
|
+
const dlpSample = dlpInfo && typeof dlpInfo.redactedSample === "string" ? dlpInfo.redactedSample.slice(0, DLP_SAMPLE_MAX_LEN) : void 0;
|
|
5907
|
+
const dlpPattern = dlpInfo && typeof dlpInfo.pattern === "string" ? dlpInfo.pattern.slice(0, DLP_PATTERN_MAX_LEN) : void 0;
|
|
5908
|
+
const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
|
|
5909
|
+
const cleanedRiskMetadata = riskMetadata ? Object.fromEntries(
|
|
5910
|
+
Object.entries(riskMetadata).filter(
|
|
5911
|
+
([, v]) => typeof v === "string" && v.length > 0 || typeof v === "number" && Number.isFinite(v)
|
|
5912
|
+
)
|
|
5913
|
+
) : void 0;
|
|
5914
|
+
const hasRiskMetadata = cleanedRiskMetadata && Object.keys(cleanedRiskMetadata).length > 0;
|
|
5915
|
+
return fetch(`${validated.toString().replace(/\/$/, "")}/audit`, {
|
|
5916
|
+
method: "POST",
|
|
5917
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
|
|
5918
|
+
body: JSON.stringify({
|
|
5919
|
+
toolName,
|
|
5920
|
+
args: safeArgs,
|
|
5921
|
+
checkedBy: safeCheckedBy,
|
|
5922
|
+
...dlpInfo && { dlpPattern, dlpSample },
|
|
5923
|
+
...hasRiskMetadata && { riskMetadata: cleanedRiskMetadata },
|
|
5924
|
+
// session_id (Claude Code + Gemini CLI) groups all audit rows from one
|
|
5925
|
+
// agent run; transcript_path is the authoritative pointer to the
|
|
5926
|
+
// session log (survives Gemini resume drift). Both optional —
|
|
5927
|
+
// unsupported agents (MCP-mediated) leave them undefined.
|
|
5928
|
+
...meta?.sessionId && { runId: meta.sessionId },
|
|
5929
|
+
...meta?.transcriptPath && { transcriptPath: meta.transcriptPath },
|
|
5930
|
+
context: {
|
|
5931
|
+
agent: meta?.agent,
|
|
5932
|
+
mcpServer: meta?.mcpServer,
|
|
5933
|
+
hostname: import_os9.default.hostname(),
|
|
5934
|
+
cwd: process.cwd(),
|
|
5935
|
+
platform: import_os9.default.platform()
|
|
5936
|
+
}
|
|
5937
|
+
}),
|
|
5938
|
+
signal: AbortSignal.timeout(5e3)
|
|
5939
|
+
}).then(() => {
|
|
5940
|
+
}).catch(() => {
|
|
5941
|
+
});
|
|
5942
|
+
}
|
|
5892
5943
|
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
5893
5944
|
const controller = new AbortController();
|
|
5894
5945
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -6012,7 +6063,7 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
6012
6063
|
);
|
|
6013
6064
|
}
|
|
6014
6065
|
}
|
|
6015
|
-
var import_fs10, import_os9, import_path12;
|
|
6066
|
+
var import_fs10, import_os9, import_path12, DLP_SAMPLE_MAX_LEN, DLP_PATTERN_MAX_LEN, KNOWN_CHECKED_BY;
|
|
6016
6067
|
var init_cloud = __esm({
|
|
6017
6068
|
"src/auth/cloud.ts"() {
|
|
6018
6069
|
"use strict";
|
|
@@ -6020,6 +6071,39 @@ var init_cloud = __esm({
|
|
|
6020
6071
|
import_os9 = __toESM(require("os"));
|
|
6021
6072
|
import_path12 = __toESM(require("path"));
|
|
6022
6073
|
init_audit();
|
|
6074
|
+
DLP_SAMPLE_MAX_LEN = 200;
|
|
6075
|
+
DLP_PATTERN_MAX_LEN = 100;
|
|
6076
|
+
KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
|
|
6077
|
+
"dlp-block",
|
|
6078
|
+
"observe-mode-dlp-would-block",
|
|
6079
|
+
"dlp-review-flagged",
|
|
6080
|
+
"loop-detected",
|
|
6081
|
+
"audit-mode",
|
|
6082
|
+
"local-policy",
|
|
6083
|
+
"smart-rule-block",
|
|
6084
|
+
// Smart-rule block was downgraded to review because the daemon was
|
|
6085
|
+
// running and we're not in CI. The block attempt is still recorded;
|
|
6086
|
+
// the user got a popup. Distinct from 'smart-rule-block' so the
|
|
6087
|
+
// dashboard can show "block rule overridden" separately from a hard
|
|
6088
|
+
// block that fired with no human in the loop.
|
|
6089
|
+
"smart-rule-block-override",
|
|
6090
|
+
"persistent",
|
|
6091
|
+
"trust",
|
|
6092
|
+
"observe-mode",
|
|
6093
|
+
"observe-mode-would-block",
|
|
6094
|
+
// MCP supply-chain: the gateway pinned a server's tool definitions and they
|
|
6095
|
+
// changed since (possible tool poisoning / rug pull). Emitted as a synthetic
|
|
6096
|
+
// audit row so the SaaS surfaces it as a blocked event. The firewall maps
|
|
6097
|
+
// this checkedBy to AUTO_BLOCKED. See doc/roadmap/active/saas-value-first.md
|
|
6098
|
+
// (workstream B-Tier2).
|
|
6099
|
+
"mcp-pin-mismatch",
|
|
6100
|
+
// MCP visibility (B-Tier2, informational — NOT blocks): the gateway
|
|
6101
|
+
// discovered a server's tool inventory (mcp-discovered) or saw an oversized
|
|
6102
|
+
// tool response that bloats the context window (mcp-large-response). Stored
|
|
6103
|
+
// AUTO_ALLOWED; carries mcpToolCount / mcpResponseBytes in riskMetadata.
|
|
6104
|
+
"mcp-discovered",
|
|
6105
|
+
"mcp-large-response"
|
|
6106
|
+
]);
|
|
6023
6107
|
}
|
|
6024
6108
|
});
|
|
6025
6109
|
|
|
@@ -9278,1114 +9362,1140 @@ var init_setup = __esm({
|
|
|
9278
9362
|
}
|
|
9279
9363
|
});
|
|
9280
9364
|
|
|
9281
|
-
// src/
|
|
9282
|
-
function
|
|
9283
|
-
return
|
|
9365
|
+
// src/pricing/litellm.ts
|
|
9366
|
+
function normalizeModel(raw) {
|
|
9367
|
+
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
9284
9368
|
}
|
|
9285
|
-
function
|
|
9286
|
-
|
|
9369
|
+
function readCache() {
|
|
9370
|
+
try {
|
|
9371
|
+
const raw = JSON.parse(import_fs14.default.readFileSync(CACHE_FILE(), "utf-8"));
|
|
9372
|
+
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
9373
|
+
return null;
|
|
9374
|
+
}
|
|
9375
|
+
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
9376
|
+
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
9377
|
+
return raw.prices;
|
|
9378
|
+
} catch {
|
|
9379
|
+
return null;
|
|
9380
|
+
}
|
|
9287
9381
|
}
|
|
9288
|
-
function
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
|
|
9382
|
+
function writeCache(prices) {
|
|
9383
|
+
try {
|
|
9384
|
+
const target = CACHE_FILE();
|
|
9385
|
+
const dir = import_path16.default.dirname(target);
|
|
9386
|
+
if (!import_fs14.default.existsSync(dir)) import_fs14.default.mkdirSync(dir, { recursive: true });
|
|
9387
|
+
const tmp = target + ".tmp";
|
|
9388
|
+
const body = {
|
|
9389
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9390
|
+
prices
|
|
9391
|
+
};
|
|
9392
|
+
import_fs14.default.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
9393
|
+
import_fs14.default.renameSync(tmp, target);
|
|
9394
|
+
} catch (err2) {
|
|
9395
|
+
try {
|
|
9396
|
+
import_fs14.default.appendFileSync(
|
|
9397
|
+
HOOK_DEBUG_LOG,
|
|
9398
|
+
`[pricing] cache write failed: ${err2.message}
|
|
9399
|
+
`
|
|
9400
|
+
);
|
|
9401
|
+
} catch {
|
|
9402
|
+
}
|
|
9306
9403
|
}
|
|
9307
9404
|
}
|
|
9308
|
-
function
|
|
9309
|
-
if (typeof
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9315
|
-
|
|
9316
|
-
|
|
9317
|
-
|
|
9405
|
+
function tupleFromLiteLLM(entry) {
|
|
9406
|
+
if (!entry || typeof entry !== "object") return null;
|
|
9407
|
+
const e = entry;
|
|
9408
|
+
const num3 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
9409
|
+
const inCost = num3(e.input_cost_per_token);
|
|
9410
|
+
const outCost = num3(e.output_cost_per_token);
|
|
9411
|
+
if (inCost === 0 && outCost === 0) return null;
|
|
9412
|
+
return [
|
|
9413
|
+
inCost,
|
|
9414
|
+
outCost,
|
|
9415
|
+
num3(e.cache_creation_input_token_cost),
|
|
9416
|
+
num3(e.cache_read_input_token_cost)
|
|
9417
|
+
];
|
|
9418
|
+
}
|
|
9419
|
+
async function fetchLiteLLMPricing() {
|
|
9420
|
+
try {
|
|
9421
|
+
const res = await fetch(LITELLM_URL, {
|
|
9422
|
+
signal: AbortSignal.timeout(15e3)
|
|
9423
|
+
});
|
|
9424
|
+
if (!res.ok) return null;
|
|
9425
|
+
const json = await res.json();
|
|
9426
|
+
if (!json || typeof json !== "object") return null;
|
|
9427
|
+
const out = {};
|
|
9428
|
+
for (const [key, value] of Object.entries(json)) {
|
|
9429
|
+
const tuple = tupleFromLiteLLM(value);
|
|
9430
|
+
if (tuple) out[key.toLowerCase()] = tuple;
|
|
9431
|
+
}
|
|
9432
|
+
if (Object.keys(out).length < 10) {
|
|
9433
|
+
return null;
|
|
9434
|
+
}
|
|
9435
|
+
return out;
|
|
9436
|
+
} catch {
|
|
9437
|
+
return null;
|
|
9318
9438
|
}
|
|
9319
9439
|
}
|
|
9320
|
-
function
|
|
9321
|
-
if (
|
|
9322
|
-
|
|
9323
|
-
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9440
|
+
async function ensurePricingLoaded() {
|
|
9441
|
+
if (memCache !== null && Date.now() - memCacheAt < TTL_MS) return;
|
|
9442
|
+
const fromDisk = readCache();
|
|
9443
|
+
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
9444
|
+
memCache = fromDisk;
|
|
9445
|
+
memCacheAt = Date.now();
|
|
9446
|
+
lookupCache.clear();
|
|
9447
|
+
return;
|
|
9448
|
+
}
|
|
9449
|
+
const fetched = await fetchLiteLLMPricing();
|
|
9450
|
+
if (fetched && Object.keys(fetched).length > 0) {
|
|
9451
|
+
memCache = fetched;
|
|
9452
|
+
memCacheAt = Date.now();
|
|
9453
|
+
writeCache(fetched);
|
|
9454
|
+
lookupCache.clear();
|
|
9455
|
+
return;
|
|
9456
|
+
}
|
|
9457
|
+
memCache = { ...BUNDLED_PRICING };
|
|
9458
|
+
memCacheAt = Date.now();
|
|
9459
|
+
lookupCache.clear();
|
|
9329
9460
|
}
|
|
9330
|
-
|
|
9331
|
-
|
|
9461
|
+
function pricingFor(model) {
|
|
9462
|
+
const norm = normalizeModel(model);
|
|
9463
|
+
const cached = lookupCache.get(norm);
|
|
9464
|
+
if (cached !== void 0) return cached;
|
|
9465
|
+
if (memCache === null && !diskChecked) {
|
|
9466
|
+
diskChecked = true;
|
|
9467
|
+
const disk = readCache();
|
|
9468
|
+
if (disk && Object.keys(disk).length > 0) {
|
|
9469
|
+
memCache = disk;
|
|
9470
|
+
memCacheAt = Date.now();
|
|
9471
|
+
}
|
|
9472
|
+
}
|
|
9473
|
+
const sources = [];
|
|
9474
|
+
if (memCache) sources.push(memCache);
|
|
9475
|
+
sources.push(BUNDLED_PRICING);
|
|
9476
|
+
let resolved = null;
|
|
9477
|
+
for (const source of sources) {
|
|
9478
|
+
const exact = source[norm];
|
|
9479
|
+
if (exact) {
|
|
9480
|
+
resolved = exact;
|
|
9481
|
+
break;
|
|
9482
|
+
}
|
|
9483
|
+
let best = null;
|
|
9484
|
+
for (const key of Object.keys(source)) {
|
|
9485
|
+
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
9486
|
+
best = key;
|
|
9487
|
+
}
|
|
9488
|
+
}
|
|
9489
|
+
if (best) {
|
|
9490
|
+
resolved = source[best];
|
|
9491
|
+
break;
|
|
9492
|
+
}
|
|
9493
|
+
}
|
|
9494
|
+
lookupCache.set(norm, resolved);
|
|
9495
|
+
return resolved;
|
|
9496
|
+
}
|
|
9497
|
+
var import_fs14, import_path16, import_os13, LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
|
|
9498
|
+
var init_litellm = __esm({
|
|
9499
|
+
"src/pricing/litellm.ts"() {
|
|
9332
9500
|
"use strict";
|
|
9501
|
+
import_fs14 = __toESM(require("fs"));
|
|
9502
|
+
import_path16 = __toESM(require("path"));
|
|
9503
|
+
import_os13 = __toESM(require("os"));
|
|
9504
|
+
init_audit();
|
|
9505
|
+
LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
9506
|
+
BUNDLED_PRICING = {
|
|
9507
|
+
// Anthropic
|
|
9508
|
+
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9509
|
+
"claude-opus-4-1": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9510
|
+
"claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9511
|
+
"claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9512
|
+
"claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9513
|
+
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9514
|
+
"claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9515
|
+
"claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9516
|
+
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9517
|
+
"claude-haiku-4-5": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9518
|
+
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9519
|
+
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9520
|
+
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9521
|
+
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
9522
|
+
// OpenAI. gpt-5 family + o-series copied from the live LiteLLM table
|
|
9523
|
+
// (verified 2026-06-14) — the bundled gpt-5 was stale at $10/$30 vs the real
|
|
9524
|
+
// $1.25/$10, and Codex models (gpt-5-codex etc.) were absent, so the offline
|
|
9525
|
+
// fallback mispriced every Codex session. See cost-codex.codexPriceFor.
|
|
9526
|
+
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
9527
|
+
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
9528
|
+
"gpt-5": [125e-8, 1e-5, 0, 125e-9],
|
|
9529
|
+
"gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
9530
|
+
"gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
9531
|
+
o3: [2e-6, 8e-6, 0, 5e-7],
|
|
9532
|
+
"o4-mini": [11e-7, 44e-7, 0, 275e-9],
|
|
9533
|
+
// Google. Values copied from the live LiteLLM table (verified 2026-06-14)
|
|
9534
|
+
// so the bundled fallback prices the current Gemini tiers correctly offline
|
|
9535
|
+
// — the local cost readers were carrying a stale hardcoded copy where
|
|
9536
|
+
// gemini-2.5-flash read $0.15/$0.60 vs the real $0.30/$2.50 (~4× under on
|
|
9537
|
+
// output). See cost-gemini.geminiPriceFor (the single Gemini price source).
|
|
9538
|
+
"gemini-2.5-pro": [125e-8, 1e-5, 0, 125e-9],
|
|
9539
|
+
"gemini-2.5-flash": [3e-7, 25e-7, 0, 3e-8],
|
|
9540
|
+
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
9541
|
+
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
9542
|
+
};
|
|
9543
|
+
CACHE_FILE = () => import_path16.default.join(import_os13.default.homedir(), ".node9", "model-pricing.json");
|
|
9544
|
+
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9545
|
+
memCache = null;
|
|
9546
|
+
memCacheAt = 0;
|
|
9547
|
+
diskChecked = false;
|
|
9548
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
9333
9549
|
}
|
|
9334
9550
|
});
|
|
9335
9551
|
|
|
9336
|
-
// src/
|
|
9337
|
-
function
|
|
9338
|
-
return
|
|
9552
|
+
// src/cost-gemini.ts
|
|
9553
|
+
function geminiTmpDir() {
|
|
9554
|
+
return import_path17.default.join(import_os14.default.homedir(), ".gemini", "tmp");
|
|
9339
9555
|
}
|
|
9340
|
-
function
|
|
9341
|
-
|
|
9556
|
+
function geminiPriceFor(model) {
|
|
9557
|
+
let tuple = pricingFor(model);
|
|
9558
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
9559
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
9560
|
+
tuple = pricingFor(proxy);
|
|
9561
|
+
if (tuple) break;
|
|
9562
|
+
}
|
|
9563
|
+
}
|
|
9564
|
+
if (!tuple) return null;
|
|
9565
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
9342
9566
|
}
|
|
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";
|
|
9567
|
+
function safeReaddir(dir) {
|
|
9568
|
+
try {
|
|
9569
|
+
return import_fs15.default.readdirSync(dir);
|
|
9570
|
+
} catch {
|
|
9571
|
+
return [];
|
|
9357
9572
|
}
|
|
9358
9573
|
}
|
|
9359
|
-
function
|
|
9360
|
-
const stats = {
|
|
9361
|
-
sessions: 0,
|
|
9362
|
-
totalToolCalls: 0,
|
|
9363
|
-
bashCalls: 0,
|
|
9364
|
-
totalCostUSD: 0,
|
|
9365
|
-
firstDate: null,
|
|
9366
|
-
lastDate: null
|
|
9367
|
-
};
|
|
9368
|
-
for (const a of agents) {
|
|
9369
|
-
stats.sessions += a.scan.sessions;
|
|
9370
|
-
stats.totalToolCalls += a.scan.totalToolCalls;
|
|
9371
|
-
stats.bashCalls += a.scan.bashCalls;
|
|
9372
|
-
stats.totalCostUSD += a.scan.totalCostUSD;
|
|
9373
|
-
if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
|
|
9374
|
-
stats.firstDate = a.scan.firstDate;
|
|
9375
|
-
}
|
|
9376
|
-
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9377
|
-
stats.lastDate = a.scan.lastDate;
|
|
9378
|
-
}
|
|
9379
|
-
}
|
|
9380
|
-
const allFindings = agents.flatMap((a) => a.scan.findings);
|
|
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
|
-
};
|
|
9430
|
-
}
|
|
9431
|
-
function buildSections(findings) {
|
|
9432
|
-
const sectionMap = /* @__PURE__ */ new Map();
|
|
9433
|
-
function ensureSection(id, label, subtitle, sourceType, shieldKey) {
|
|
9434
|
-
let s = sectionMap.get(id);
|
|
9435
|
-
if (!s) {
|
|
9436
|
-
s = {
|
|
9437
|
-
id,
|
|
9438
|
-
label,
|
|
9439
|
-
subtitle,
|
|
9440
|
-
sourceType,
|
|
9441
|
-
shieldKey,
|
|
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";
|
|
9477
|
-
}
|
|
9478
|
-
const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
|
|
9479
|
-
const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
|
|
9480
|
-
const ruleKey = sectionId + "::" + ruleDisplayName;
|
|
9481
|
-
let rule = ruleMap.get(ruleKey);
|
|
9482
|
-
if (!rule) {
|
|
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);
|
|
9491
|
-
}
|
|
9492
|
-
const cmdPreview = previewCommand(f.input, 120);
|
|
9493
|
-
const fullCmd = fullCommandOf(f.input);
|
|
9494
|
-
const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
|
|
9495
|
-
if (!isDupe) {
|
|
9496
|
-
rule.findings.push({
|
|
9497
|
-
timestamp: f.timestamp ?? "",
|
|
9498
|
-
command: cmdPreview,
|
|
9499
|
-
fullCommand: fullCmd,
|
|
9500
|
-
project: f.project,
|
|
9501
|
-
sessionId: f.sessionId,
|
|
9502
|
-
agent: f.agent,
|
|
9503
|
-
toolName: f.toolName
|
|
9504
|
-
});
|
|
9505
|
-
}
|
|
9506
|
-
if (verdict === "block") section.blockedCount++;
|
|
9507
|
-
else section.reviewCount++;
|
|
9508
|
-
}
|
|
9509
|
-
const sections = [...sectionMap.values()];
|
|
9510
|
-
sections.sort((a, b) => {
|
|
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();
|
|
9534
|
-
}
|
|
9535
|
-
var AGENT_SHORT, AGENT_LONG;
|
|
9536
|
-
var init_scan_summary = __esm({
|
|
9537
|
-
"src/scan-summary.ts"() {
|
|
9538
|
-
"use strict";
|
|
9539
|
-
init_shields();
|
|
9540
|
-
init_dist();
|
|
9541
|
-
init_dist();
|
|
9542
|
-
AGENT_SHORT = {
|
|
9543
|
-
claude: "Claude",
|
|
9544
|
-
gemini: "Gemini",
|
|
9545
|
-
codex: "Codex",
|
|
9546
|
-
antigravity: "Agy",
|
|
9547
|
-
copilot: "Copilot",
|
|
9548
|
-
shell: "Shell"
|
|
9549
|
-
};
|
|
9550
|
-
AGENT_LONG = {
|
|
9551
|
-
claude: "Claude Code",
|
|
9552
|
-
gemini: "Gemini CLI",
|
|
9553
|
-
codex: "Codex",
|
|
9554
|
-
antigravity: "Antigravity",
|
|
9555
|
-
copilot: "GitHub Copilot",
|
|
9556
|
-
shell: "Shell"
|
|
9557
|
-
};
|
|
9558
|
-
}
|
|
9559
|
-
});
|
|
9560
|
-
|
|
9561
|
-
// src/cli/commands/blast.ts
|
|
9562
|
-
function buildSensitivePaths(home, cwd) {
|
|
9563
|
-
return [
|
|
9564
|
-
{
|
|
9565
|
-
full: import_path16.default.join(home, ".ssh", "id_rsa"),
|
|
9566
|
-
label: "~/.ssh/id_rsa",
|
|
9567
|
-
description: "RSA private key \u2014 grants SSH access to your servers",
|
|
9568
|
-
score: 20
|
|
9569
|
-
},
|
|
9570
|
-
{
|
|
9571
|
-
full: import_path16.default.join(home, ".ssh", "id_ed25519"),
|
|
9572
|
-
label: "~/.ssh/id_ed25519",
|
|
9573
|
-
description: "Ed25519 private key \u2014 grants SSH access to your servers",
|
|
9574
|
-
score: 20
|
|
9575
|
-
},
|
|
9576
|
-
{
|
|
9577
|
-
full: import_path16.default.join(home, ".ssh", "id_ecdsa"),
|
|
9578
|
-
label: "~/.ssh/id_ecdsa",
|
|
9579
|
-
description: "ECDSA private key \u2014 grants SSH access to your servers",
|
|
9580
|
-
score: 20
|
|
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) {
|
|
9574
|
+
function isDir(p) {
|
|
9645
9575
|
try {
|
|
9646
|
-
|
|
9647
|
-
return true;
|
|
9576
|
+
return import_fs15.default.statSync(p).isDirectory();
|
|
9648
9577
|
} catch {
|
|
9649
9578
|
return false;
|
|
9650
9579
|
}
|
|
9651
9580
|
}
|
|
9652
|
-
function
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9657
|
-
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
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;
|
|
9581
|
+
function listGeminiSessionFiles(base) {
|
|
9582
|
+
const out = [];
|
|
9583
|
+
for (const project of safeReaddir(base)) {
|
|
9584
|
+
const chats = import_path17.default.join(base, project, "chats");
|
|
9585
|
+
if (!isDir(chats)) continue;
|
|
9586
|
+
for (const f of safeReaddir(chats)) {
|
|
9587
|
+
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
9588
|
+
out.push({ file: import_path17.default.join(chats, f), project });
|
|
9589
|
+
}
|
|
9677
9590
|
}
|
|
9678
9591
|
}
|
|
9679
|
-
return
|
|
9592
|
+
return out;
|
|
9680
9593
|
}
|
|
9681
|
-
function
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9688
|
-
|
|
9689
|
-
|
|
9690
|
-
|
|
9691
|
-
|
|
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("");
|
|
9594
|
+
function parseGeminiSession(lines, project) {
|
|
9595
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
9596
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9597
|
+
let runId = "";
|
|
9598
|
+
for (const raw of lines) {
|
|
9599
|
+
if (!raw.trim()) continue;
|
|
9600
|
+
let obj;
|
|
9601
|
+
try {
|
|
9602
|
+
obj = JSON.parse(raw);
|
|
9603
|
+
} catch {
|
|
9604
|
+
continue;
|
|
9700
9605
|
}
|
|
9701
|
-
if (
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
);
|
|
9707
|
-
}
|
|
9708
|
-
console.log("");
|
|
9606
|
+
if (typeof obj.sessionId === "string" && !runId) runId = obj.sessionId;
|
|
9607
|
+
if (!obj.tokens || !obj.model || !obj.timestamp) continue;
|
|
9608
|
+
if (obj.id) {
|
|
9609
|
+
if (seenIds.has(obj.id)) continue;
|
|
9610
|
+
seenIds.add(obj.id);
|
|
9709
9611
|
}
|
|
9710
|
-
|
|
9711
|
-
if (
|
|
9712
|
-
|
|
9713
|
-
|
|
9612
|
+
const price = geminiPriceFor(obj.model);
|
|
9613
|
+
if (!price) continue;
|
|
9614
|
+
const inp = obj.tokens.input ?? 0;
|
|
9615
|
+
const out = obj.tokens.output ?? 0;
|
|
9616
|
+
const cached = Math.min(obj.tokens.cached ?? 0, inp);
|
|
9617
|
+
const fresh = Math.max(0, inp - cached);
|
|
9618
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
9619
|
+
const date = obj.timestamp.slice(0, 10);
|
|
9620
|
+
const model = normalizeModel(obj.model);
|
|
9621
|
+
const key = `${date}::${model}`;
|
|
9622
|
+
const prev = byKey.get(key);
|
|
9623
|
+
if (prev) {
|
|
9624
|
+
prev.costUSD += cost;
|
|
9625
|
+
prev.inputTokens += fresh;
|
|
9626
|
+
prev.outputTokens += out;
|
|
9627
|
+
prev.cacheReadTokens += cached;
|
|
9714
9628
|
} else {
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
);
|
|
9629
|
+
byKey.set(key, {
|
|
9630
|
+
date,
|
|
9631
|
+
model,
|
|
9632
|
+
workingDir: project,
|
|
9633
|
+
runId,
|
|
9634
|
+
costUSD: cost,
|
|
9635
|
+
inputTokens: fresh,
|
|
9636
|
+
outputTokens: out,
|
|
9637
|
+
cacheReadTokens: cached,
|
|
9638
|
+
cacheWriteTokens: 0
|
|
9639
|
+
});
|
|
9726
9640
|
}
|
|
9727
|
-
|
|
9728
|
-
|
|
9641
|
+
}
|
|
9642
|
+
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
9643
|
+
return [...byKey.values()];
|
|
9729
9644
|
}
|
|
9730
|
-
var
|
|
9731
|
-
var
|
|
9732
|
-
"src/
|
|
9645
|
+
var import_fs15, import_os14, import_path17, GEMINI_FALLBACK_MODELS, geminiSource;
|
|
9646
|
+
var init_cost_gemini = __esm({
|
|
9647
|
+
"src/cost-gemini.ts"() {
|
|
9733
9648
|
"use strict";
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9649
|
+
import_fs15 = __toESM(require("fs"));
|
|
9650
|
+
import_os14 = __toESM(require("os"));
|
|
9651
|
+
import_path17 = __toESM(require("path"));
|
|
9652
|
+
init_litellm();
|
|
9653
|
+
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
9654
|
+
geminiSource = {
|
|
9655
|
+
id: "gemini",
|
|
9656
|
+
available() {
|
|
9657
|
+
try {
|
|
9658
|
+
return import_fs15.default.existsSync(geminiTmpDir());
|
|
9659
|
+
} catch {
|
|
9660
|
+
return false;
|
|
9661
|
+
}
|
|
9662
|
+
},
|
|
9663
|
+
collect(sinceMs) {
|
|
9664
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9665
|
+
for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
9666
|
+
try {
|
|
9667
|
+
if (sinceMs !== void 0 && import_fs15.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
9668
|
+
} catch {
|
|
9669
|
+
continue;
|
|
9670
|
+
}
|
|
9671
|
+
let content;
|
|
9672
|
+
try {
|
|
9673
|
+
content = import_fs15.default.readFileSync(file, "utf8");
|
|
9674
|
+
} catch {
|
|
9675
|
+
continue;
|
|
9676
|
+
}
|
|
9677
|
+
for (const e of parseGeminiSession(content.split("\n"), project)) {
|
|
9678
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9679
|
+
const prev = combined.get(key);
|
|
9680
|
+
if (prev) {
|
|
9681
|
+
prev.costUSD += e.costUSD;
|
|
9682
|
+
prev.inputTokens += e.inputTokens;
|
|
9683
|
+
prev.outputTokens += e.outputTokens;
|
|
9684
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9685
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9686
|
+
} else {
|
|
9687
|
+
combined.set(key, { ...e });
|
|
9688
|
+
}
|
|
9689
|
+
}
|
|
9690
|
+
}
|
|
9691
|
+
return [...combined.values()];
|
|
9692
|
+
}
|
|
9693
|
+
};
|
|
9739
9694
|
}
|
|
9740
9695
|
});
|
|
9741
9696
|
|
|
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 };
|
|
9747
|
-
}
|
|
9748
|
-
function topDlpPatterns(findings, n) {
|
|
9749
|
-
const counts = /* @__PURE__ */ new Map();
|
|
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 }));
|
|
9697
|
+
// src/cost-codex.ts
|
|
9698
|
+
function codexSessionsDir() {
|
|
9699
|
+
return import_path18.default.join(import_os15.default.homedir(), ".codex", "sessions");
|
|
9754
9700
|
}
|
|
9755
|
-
function
|
|
9756
|
-
|
|
9757
|
-
for (const section of sections) {
|
|
9758
|
-
for (const rule of section.rules) {
|
|
9759
|
-
const matches = verdict === "block" ? rule.verdict === "block" : rule.verdict !== "block";
|
|
9760
|
-
if (matches) matched.push({ name: rule.name, count: rule.findings.length });
|
|
9761
|
-
}
|
|
9762
|
-
}
|
|
9763
|
-
return matched.sort((a, b) => b.count - a.count).slice(0, n);
|
|
9701
|
+
function codexPriceFor(model) {
|
|
9702
|
+
return pricingFor(model) ?? CODEX_FALLBACK;
|
|
9764
9703
|
}
|
|
9765
|
-
function
|
|
9766
|
-
const
|
|
9767
|
-
const
|
|
9768
|
-
return
|
|
9704
|
+
function codexSessionCost(model, tokens) {
|
|
9705
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
9706
|
+
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
9707
|
+
return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
|
|
9769
9708
|
}
|
|
9770
|
-
function
|
|
9709
|
+
function listCodexSessionFiles(base) {
|
|
9771
9710
|
const out = [];
|
|
9772
|
-
for (const
|
|
9773
|
-
|
|
9774
|
-
if (!
|
|
9775
|
-
const
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9711
|
+
for (const y of safeReaddir2(base)) {
|
|
9712
|
+
const yp = import_path18.default.join(base, y);
|
|
9713
|
+
if (!isDir2(yp)) continue;
|
|
9714
|
+
for (const m of safeReaddir2(yp)) {
|
|
9715
|
+
const mp = import_path18.default.join(yp, m);
|
|
9716
|
+
if (!isDir2(mp)) continue;
|
|
9717
|
+
for (const d of safeReaddir2(mp)) {
|
|
9718
|
+
const dp = import_path18.default.join(mp, d);
|
|
9719
|
+
if (!isDir2(dp)) continue;
|
|
9720
|
+
for (const f of safeReaddir2(dp)) {
|
|
9721
|
+
if (f.endsWith(".jsonl")) out.push(import_path18.default.join(dp, f));
|
|
9722
|
+
}
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9784
9725
|
}
|
|
9785
|
-
return out
|
|
9726
|
+
return out;
|
|
9786
9727
|
}
|
|
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"));
|
|
9728
|
+
function safeReaddir2(dir) {
|
|
9729
|
+
try {
|
|
9730
|
+
return import_fs16.default.readdirSync(dir);
|
|
9731
|
+
} catch {
|
|
9732
|
+
return [];
|
|
9798
9733
|
}
|
|
9799
|
-
out.push(import_chalk3.default.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
9800
|
-
return out;
|
|
9801
9734
|
}
|
|
9802
|
-
function
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
return `${days}d`;
|
|
9735
|
+
function isDir2(p) {
|
|
9736
|
+
try {
|
|
9737
|
+
return import_fs16.default.statSync(p).isDirectory();
|
|
9738
|
+
} catch {
|
|
9739
|
+
return false;
|
|
9740
|
+
}
|
|
9809
9741
|
}
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
"
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9742
|
+
function parseCodexSession(lines) {
|
|
9743
|
+
let sessionStart2 = "";
|
|
9744
|
+
let runId = "";
|
|
9745
|
+
let cwd = "";
|
|
9746
|
+
let model = "";
|
|
9747
|
+
let input = 0;
|
|
9748
|
+
let cached = 0;
|
|
9749
|
+
let output = 0;
|
|
9750
|
+
let sawUsage = false;
|
|
9751
|
+
for (const raw of lines) {
|
|
9752
|
+
if (!raw.trim()) continue;
|
|
9753
|
+
let entry;
|
|
9754
|
+
try {
|
|
9755
|
+
entry = JSON.parse(raw);
|
|
9756
|
+
} catch {
|
|
9757
|
+
continue;
|
|
9758
|
+
}
|
|
9759
|
+
const p = entry.payload ?? {};
|
|
9760
|
+
if (entry.type === "session_meta") {
|
|
9761
|
+
if (!sessionStart2 && typeof p["timestamp"] === "string") sessionStart2 = p["timestamp"];
|
|
9762
|
+
if (!runId && typeof p["id"] === "string") runId = p["id"];
|
|
9763
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9764
|
+
continue;
|
|
9765
|
+
}
|
|
9766
|
+
if (entry.type === "turn_context") {
|
|
9767
|
+
if (typeof p["model"] === "string") model = p["model"];
|
|
9768
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9769
|
+
continue;
|
|
9770
|
+
}
|
|
9771
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
9772
|
+
const info = p["info"] ?? {};
|
|
9773
|
+
const usage = info["total_token_usage"] ?? {};
|
|
9774
|
+
if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
|
|
9775
|
+
if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
|
|
9776
|
+
if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
|
|
9777
|
+
sawUsage = true;
|
|
9778
|
+
}
|
|
9817
9779
|
}
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9780
|
+
if (!sessionStart2 || !sawUsage) return null;
|
|
9781
|
+
const nonCached = Math.max(0, input - cached);
|
|
9782
|
+
if (nonCached === 0 && output === 0 && cached === 0) return null;
|
|
9783
|
+
const norm = normalizeModel(model || "gpt-5");
|
|
9784
|
+
const costUSD = codexSessionCost(model, { input, cached, output });
|
|
9785
|
+
return {
|
|
9786
|
+
date: sessionStart2.slice(0, 10),
|
|
9787
|
+
model: norm,
|
|
9788
|
+
workingDir: cwd,
|
|
9789
|
+
runId,
|
|
9790
|
+
costUSD,
|
|
9791
|
+
inputTokens: nonCached,
|
|
9792
|
+
outputTokens: output,
|
|
9793
|
+
cacheReadTokens: cached,
|
|
9794
|
+
cacheWriteTokens: 0
|
|
9795
|
+
};
|
|
9796
|
+
}
|
|
9797
|
+
var import_fs16, import_os15, import_path18, CODEX_FALLBACK, codexSource;
|
|
9798
|
+
var init_cost_codex = __esm({
|
|
9799
|
+
"src/cost-codex.ts"() {
|
|
9824
9800
|
"use strict";
|
|
9825
|
-
|
|
9826
|
-
|
|
9801
|
+
import_fs16 = __toESM(require("fs"));
|
|
9802
|
+
import_os15 = __toESM(require("os"));
|
|
9803
|
+
import_path18 = __toESM(require("path"));
|
|
9804
|
+
init_litellm();
|
|
9805
|
+
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
9806
|
+
codexSource = {
|
|
9807
|
+
id: "codex",
|
|
9808
|
+
available() {
|
|
9809
|
+
try {
|
|
9810
|
+
return import_fs16.default.existsSync(codexSessionsDir());
|
|
9811
|
+
} catch {
|
|
9812
|
+
return false;
|
|
9813
|
+
}
|
|
9814
|
+
},
|
|
9815
|
+
collect(sinceMs) {
|
|
9816
|
+
const base = codexSessionsDir();
|
|
9817
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9818
|
+
for (const file of listCodexSessionFiles(base)) {
|
|
9819
|
+
try {
|
|
9820
|
+
if (sinceMs !== void 0 && import_fs16.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
9821
|
+
} catch {
|
|
9822
|
+
continue;
|
|
9823
|
+
}
|
|
9824
|
+
let content;
|
|
9825
|
+
try {
|
|
9826
|
+
content = import_fs16.default.readFileSync(file, "utf8");
|
|
9827
|
+
} catch {
|
|
9828
|
+
continue;
|
|
9829
|
+
}
|
|
9830
|
+
const e = parseCodexSession(content.split("\n"));
|
|
9831
|
+
if (!e) continue;
|
|
9832
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9833
|
+
const prev = combined.get(key);
|
|
9834
|
+
if (prev) {
|
|
9835
|
+
prev.costUSD += e.costUSD;
|
|
9836
|
+
prev.inputTokens += e.inputTokens;
|
|
9837
|
+
prev.outputTokens += e.outputTokens;
|
|
9838
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9839
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9840
|
+
} else {
|
|
9841
|
+
combined.set(key, { ...e });
|
|
9842
|
+
}
|
|
9843
|
+
}
|
|
9844
|
+
return [...combined.values()];
|
|
9845
|
+
}
|
|
9827
9846
|
};
|
|
9828
9847
|
}
|
|
9829
9848
|
});
|
|
9830
9849
|
|
|
9831
|
-
// src/
|
|
9832
|
-
function
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9850
|
+
// src/utils/hook-payload.ts
|
|
9851
|
+
function extractToolName(payload, defaultValue = "") {
|
|
9852
|
+
return payload.tool_name ?? payload.name ?? payload.toolCall?.name ?? defaultValue;
|
|
9853
|
+
}
|
|
9854
|
+
function extractToolInput(payload) {
|
|
9855
|
+
return payload.tool_input ?? payload.args ?? payload.toolCall?.args ?? {};
|
|
9856
|
+
}
|
|
9857
|
+
function canonicalToolName(name) {
|
|
9858
|
+
switch (name) {
|
|
9859
|
+
// Hermes Agent
|
|
9860
|
+
case "terminal":
|
|
9861
|
+
return "Bash";
|
|
9862
|
+
case "write_file":
|
|
9863
|
+
return "Write";
|
|
9864
|
+
case "patch":
|
|
9865
|
+
return "Edit";
|
|
9866
|
+
case "read_file":
|
|
9867
|
+
return "Read";
|
|
9868
|
+
case "search_files":
|
|
9869
|
+
return "Grep";
|
|
9870
|
+
// Antigravity (agy) — shell tool renamed from Gemini's run_shell_command
|
|
9871
|
+
case "run_command":
|
|
9872
|
+
return "Bash";
|
|
9873
|
+
default:
|
|
9874
|
+
return name;
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
function agentLabelFromFlag(flag) {
|
|
9878
|
+
if (typeof flag !== "string") return void 0;
|
|
9879
|
+
switch (flag.toLowerCase()) {
|
|
9880
|
+
case "antigravity":
|
|
9881
|
+
case "agy":
|
|
9882
|
+
return "Antigravity";
|
|
9883
|
+
case "copilot":
|
|
9884
|
+
return "GitHub Copilot";
|
|
9885
|
+
default:
|
|
9886
|
+
return void 0;
|
|
9887
|
+
}
|
|
9855
9888
|
}
|
|
9856
|
-
|
|
9857
|
-
"
|
|
9889
|
+
function canonicalToolInput(rawToolName, input) {
|
|
9890
|
+
if (rawToolName !== "run_command") return input;
|
|
9891
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
|
|
9892
|
+
const args = input;
|
|
9893
|
+
if (typeof args.CommandLine !== "string") return input;
|
|
9894
|
+
const { CommandLine, Cwd, ...rest } = args;
|
|
9895
|
+
const canonical = { ...rest, command: CommandLine };
|
|
9896
|
+
if (typeof Cwd === "string" && Cwd.length > 0) canonical.cwd = Cwd;
|
|
9897
|
+
return canonical;
|
|
9898
|
+
}
|
|
9899
|
+
var init_hook_payload = __esm({
|
|
9900
|
+
"src/utils/hook-payload.ts"() {
|
|
9858
9901
|
"use strict";
|
|
9859
|
-
init_scan_derive();
|
|
9860
9902
|
}
|
|
9861
9903
|
});
|
|
9862
9904
|
|
|
9863
|
-
// src/
|
|
9864
|
-
function
|
|
9865
|
-
return
|
|
9905
|
+
// src/scan-summary.ts
|
|
9906
|
+
function agentDisplayName(agent) {
|
|
9907
|
+
return AGENT_LONG[agent] ?? "Claude Code";
|
|
9866
9908
|
}
|
|
9867
|
-
function
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9909
|
+
function agentBadgeText(agent, width = 10) {
|
|
9910
|
+
return `[${AGENT_SHORT[agent] ?? "Claude"}]`.padEnd(width);
|
|
9911
|
+
}
|
|
9912
|
+
function agentColorName(agent) {
|
|
9913
|
+
switch (agent) {
|
|
9914
|
+
case "gemini":
|
|
9915
|
+
return "blue";
|
|
9916
|
+
case "codex":
|
|
9917
|
+
return "magenta";
|
|
9918
|
+
case "antigravity":
|
|
9919
|
+
return "yellow";
|
|
9920
|
+
case "copilot":
|
|
9921
|
+
return "green";
|
|
9922
|
+
case "shell":
|
|
9923
|
+
return "yellow";
|
|
9924
|
+
default:
|
|
9925
|
+
return "cyan";
|
|
9879
9926
|
}
|
|
9880
9927
|
}
|
|
9881
|
-
function
|
|
9882
|
-
const
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
9928
|
+
function buildScanSummary(agents) {
|
|
9929
|
+
const stats = {
|
|
9930
|
+
sessions: 0,
|
|
9931
|
+
totalToolCalls: 0,
|
|
9932
|
+
bashCalls: 0,
|
|
9933
|
+
totalCostUSD: 0,
|
|
9934
|
+
firstDate: null,
|
|
9935
|
+
lastDate: null
|
|
9936
|
+
};
|
|
9937
|
+
for (const a of agents) {
|
|
9938
|
+
stats.sessions += a.scan.sessions;
|
|
9939
|
+
stats.totalToolCalls += a.scan.totalToolCalls;
|
|
9940
|
+
stats.bashCalls += a.scan.bashCalls;
|
|
9941
|
+
stats.totalCostUSD += a.scan.totalCostUSD;
|
|
9942
|
+
if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
|
|
9943
|
+
stats.firstDate = a.scan.firstDate;
|
|
9895
9944
|
}
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
history = history.slice(history.length - cap);
|
|
9945
|
+
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9946
|
+
stats.lastDate = a.scan.lastDate;
|
|
9899
9947
|
}
|
|
9900
|
-
import_fs15.default.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
9901
|
-
} catch (err2) {
|
|
9902
|
-
process.stderr.write(
|
|
9903
|
-
`[node9] Warning: could not write scan-history.json: ${err2.message}
|
|
9904
|
-
`
|
|
9905
|
-
);
|
|
9906
9948
|
}
|
|
9949
|
+
const allFindings = agents.flatMap((a) => a.scan.findings);
|
|
9950
|
+
const allLeaks = agents.flatMap(
|
|
9951
|
+
(a) => a.scan.dlpFindings.map((f) => ({
|
|
9952
|
+
patternName: f.patternName,
|
|
9953
|
+
redactedSample: f.redactedSample,
|
|
9954
|
+
toolName: f.toolName,
|
|
9955
|
+
timestamp: f.timestamp,
|
|
9956
|
+
project: f.project,
|
|
9957
|
+
sessionId: f.sessionId,
|
|
9958
|
+
agent: f.agent
|
|
9959
|
+
}))
|
|
9960
|
+
);
|
|
9961
|
+
const allLoops = agents.flatMap(
|
|
9962
|
+
(a) => a.scan.loopFindings.map((f) => ({
|
|
9963
|
+
toolName: f.toolName,
|
|
9964
|
+
commandPreview: f.commandPreview,
|
|
9965
|
+
count: f.count,
|
|
9966
|
+
timestamp: f.timestamp,
|
|
9967
|
+
project: f.project,
|
|
9968
|
+
sessionId: f.sessionId,
|
|
9969
|
+
agent: f.agent,
|
|
9970
|
+
kind: f.kind
|
|
9971
|
+
}))
|
|
9972
|
+
);
|
|
9973
|
+
const byVerdict = {
|
|
9974
|
+
blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
|
|
9975
|
+
supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
|
|
9976
|
+
leaks: allLeaks.length,
|
|
9977
|
+
loops: allLoops.length
|
|
9978
|
+
};
|
|
9979
|
+
const byAgent = agents.map((a) => ({
|
|
9980
|
+
id: a.id,
|
|
9981
|
+
label: a.label,
|
|
9982
|
+
icon: a.icon,
|
|
9983
|
+
sessions: a.scan.sessions,
|
|
9984
|
+
findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
|
|
9985
|
+
costUSD: a.scan.totalCostUSD
|
|
9986
|
+
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
9987
|
+
const sections = buildSections(allFindings);
|
|
9988
|
+
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
9989
|
+
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
9990
|
+
return {
|
|
9991
|
+
stats,
|
|
9992
|
+
byVerdict,
|
|
9993
|
+
byAgent,
|
|
9994
|
+
sections,
|
|
9995
|
+
leaks: allLeaks,
|
|
9996
|
+
loops: allLoops,
|
|
9997
|
+
loopWastedUSD
|
|
9998
|
+
};
|
|
9907
9999
|
}
|
|
9908
|
-
function
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
10000
|
+
function buildSections(findings) {
|
|
10001
|
+
const sectionMap = /* @__PURE__ */ new Map();
|
|
10002
|
+
function ensureSection(id, label, subtitle, sourceType, shieldKey) {
|
|
10003
|
+
let s = sectionMap.get(id);
|
|
10004
|
+
if (!s) {
|
|
10005
|
+
s = {
|
|
10006
|
+
id,
|
|
10007
|
+
label,
|
|
10008
|
+
subtitle,
|
|
10009
|
+
sourceType,
|
|
10010
|
+
shieldKey,
|
|
10011
|
+
blockedCount: 0,
|
|
10012
|
+
reviewCount: 0,
|
|
10013
|
+
rules: []
|
|
10014
|
+
};
|
|
10015
|
+
sectionMap.set(id, s);
|
|
10016
|
+
}
|
|
10017
|
+
return s;
|
|
10018
|
+
}
|
|
10019
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
10020
|
+
for (const f of findings) {
|
|
10021
|
+
const src = f.source;
|
|
10022
|
+
const sourceType = src.sourceType;
|
|
10023
|
+
const shieldName = src.shieldName;
|
|
10024
|
+
const verdict = src.rule.verdict === "block" ? "block" : "review";
|
|
10025
|
+
let sectionId;
|
|
10026
|
+
let sectionLabel;
|
|
10027
|
+
let sectionSubtitle;
|
|
10028
|
+
let shieldKey;
|
|
10029
|
+
if (sourceType === "default") {
|
|
10030
|
+
sectionId = "default";
|
|
10031
|
+
sectionLabel = "Default Rules";
|
|
10032
|
+
sectionSubtitle = "built-in, always on";
|
|
10033
|
+
} else if (sourceType === "shield") {
|
|
10034
|
+
sectionId = `shield:${shieldName}`;
|
|
10035
|
+
sectionLabel = shieldName;
|
|
10036
|
+
sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
|
|
10037
|
+
shieldKey = shieldName;
|
|
10038
|
+
} else if (shieldName === "cloud") {
|
|
10039
|
+
sectionId = "cloud";
|
|
10040
|
+
sectionLabel = "Cloud Policy";
|
|
10041
|
+
sectionSubtitle = "synced from node9 cloud";
|
|
10042
|
+
} else {
|
|
10043
|
+
sectionId = "user";
|
|
10044
|
+
sectionLabel = "Your Rules";
|
|
10045
|
+
sectionSubtitle = "added in node9.config.json";
|
|
10046
|
+
}
|
|
10047
|
+
const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
|
|
10048
|
+
const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
|
|
10049
|
+
const ruleKey = sectionId + "::" + ruleDisplayName;
|
|
10050
|
+
let rule = ruleMap.get(ruleKey);
|
|
10051
|
+
if (!rule) {
|
|
10052
|
+
rule = {
|
|
10053
|
+
name: ruleDisplayName,
|
|
10054
|
+
verdict,
|
|
10055
|
+
reason: src.rule.reason ?? "",
|
|
10056
|
+
findings: []
|
|
10057
|
+
};
|
|
10058
|
+
ruleMap.set(ruleKey, rule);
|
|
10059
|
+
section.rules.push(rule);
|
|
10060
|
+
}
|
|
10061
|
+
const cmdPreview = previewCommand(f.input, 120);
|
|
10062
|
+
const fullCmd = fullCommandOf(f.input);
|
|
10063
|
+
const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
|
|
10064
|
+
if (!isDupe) {
|
|
10065
|
+
rule.findings.push({
|
|
10066
|
+
timestamp: f.timestamp ?? "",
|
|
10067
|
+
command: cmdPreview,
|
|
10068
|
+
fullCommand: fullCmd,
|
|
10069
|
+
project: f.project,
|
|
10070
|
+
sessionId: f.sessionId,
|
|
10071
|
+
agent: f.agent,
|
|
10072
|
+
toolName: f.toolName
|
|
10073
|
+
});
|
|
10074
|
+
}
|
|
10075
|
+
if (verdict === "block") section.blockedCount++;
|
|
10076
|
+
else section.reviewCount++;
|
|
10077
|
+
}
|
|
10078
|
+
const sections = [...sectionMap.values()];
|
|
10079
|
+
sections.sort((a, b) => {
|
|
10080
|
+
const aTotal = a.blockedCount + a.reviewCount;
|
|
10081
|
+
const bTotal = b.blockedCount + b.reviewCount;
|
|
10082
|
+
if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
|
|
10083
|
+
return bTotal - aTotal;
|
|
10084
|
+
});
|
|
10085
|
+
for (const s of sections) {
|
|
10086
|
+
s.rules.sort((a, b) => {
|
|
10087
|
+
const aBlock = a.verdict === "block" ? 1 : 0;
|
|
10088
|
+
const bBlock = b.verdict === "block" ? 1 : 0;
|
|
10089
|
+
if (bBlock !== aBlock) return bBlock - aBlock;
|
|
10090
|
+
return b.findings.length - a.findings.length;
|
|
10091
|
+
});
|
|
9930
10092
|
}
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
// src/pricing/litellm.ts
|
|
9934
|
-
function normalizeModel(raw) {
|
|
9935
|
-
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
10093
|
+
return sections;
|
|
9936
10094
|
}
|
|
9937
|
-
function
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
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
|
-
}
|
|
10095
|
+
function previewCommand(input, max) {
|
|
10096
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
10097
|
+
const s = String(raw).replace(/\s+/g, " ").trim();
|
|
10098
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9949
10099
|
}
|
|
9950
|
-
function
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
10100
|
+
function fullCommandOf(input) {
|
|
10101
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
10102
|
+
return String(raw).replace(/\s+/g, " ").trim();
|
|
10103
|
+
}
|
|
10104
|
+
var AGENT_SHORT, AGENT_LONG;
|
|
10105
|
+
var init_scan_summary = __esm({
|
|
10106
|
+
"src/scan-summary.ts"() {
|
|
10107
|
+
"use strict";
|
|
10108
|
+
init_shields();
|
|
10109
|
+
init_dist();
|
|
10110
|
+
init_dist();
|
|
10111
|
+
AGENT_SHORT = {
|
|
10112
|
+
claude: "Claude",
|
|
10113
|
+
gemini: "Gemini",
|
|
10114
|
+
codex: "Codex",
|
|
10115
|
+
antigravity: "Agy",
|
|
10116
|
+
copilot: "Copilot",
|
|
10117
|
+
shell: "Shell"
|
|
10118
|
+
};
|
|
10119
|
+
AGENT_LONG = {
|
|
10120
|
+
claude: "Claude Code",
|
|
10121
|
+
gemini: "Gemini CLI",
|
|
10122
|
+
codex: "Codex",
|
|
10123
|
+
antigravity: "Antigravity",
|
|
10124
|
+
copilot: "GitHub Copilot",
|
|
10125
|
+
shell: "Shell"
|
|
9959
10126
|
};
|
|
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
10127
|
}
|
|
9972
|
-
}
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
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;
|
|
10128
|
+
});
|
|
10129
|
+
|
|
10130
|
+
// src/cli/commands/blast.ts
|
|
10131
|
+
function buildSensitivePaths(home, cwd) {
|
|
9980
10132
|
return [
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
10133
|
+
{
|
|
10134
|
+
full: import_path19.default.join(home, ".ssh", "id_rsa"),
|
|
10135
|
+
label: "~/.ssh/id_rsa",
|
|
10136
|
+
description: "RSA private key \u2014 grants SSH access to your servers",
|
|
10137
|
+
score: 20
|
|
10138
|
+
},
|
|
10139
|
+
{
|
|
10140
|
+
full: import_path19.default.join(home, ".ssh", "id_ed25519"),
|
|
10141
|
+
label: "~/.ssh/id_ed25519",
|
|
10142
|
+
description: "Ed25519 private key \u2014 grants SSH access to your servers",
|
|
10143
|
+
score: 20
|
|
10144
|
+
},
|
|
10145
|
+
{
|
|
10146
|
+
full: import_path19.default.join(home, ".ssh", "id_ecdsa"),
|
|
10147
|
+
label: "~/.ssh/id_ecdsa",
|
|
10148
|
+
description: "ECDSA private key \u2014 grants SSH access to your servers",
|
|
10149
|
+
score: 20
|
|
10150
|
+
},
|
|
10151
|
+
{
|
|
10152
|
+
full: import_path19.default.join(home, ".aws", "credentials"),
|
|
10153
|
+
label: "~/.aws/credentials",
|
|
10154
|
+
description: "AWS access keys \u2014 full cloud account access",
|
|
10155
|
+
score: 20
|
|
10156
|
+
},
|
|
10157
|
+
{
|
|
10158
|
+
full: import_path19.default.join(home, ".aws", "config"),
|
|
10159
|
+
label: "~/.aws/config",
|
|
10160
|
+
description: "AWS configuration \u2014 account and region settings",
|
|
10161
|
+
score: 5
|
|
10162
|
+
},
|
|
10163
|
+
{
|
|
10164
|
+
full: import_path19.default.join(home, ".config", "gcloud", "credentials.db"),
|
|
10165
|
+
label: "~/.config/gcloud/credentials.db",
|
|
10166
|
+
description: "Google Cloud credentials",
|
|
10167
|
+
score: 15
|
|
10168
|
+
},
|
|
10169
|
+
{
|
|
10170
|
+
full: import_path19.default.join(home, ".docker", "config.json"),
|
|
10171
|
+
label: "~/.docker/config.json",
|
|
10172
|
+
description: "Docker registry auth tokens",
|
|
10173
|
+
score: 10
|
|
10174
|
+
},
|
|
10175
|
+
{
|
|
10176
|
+
full: import_path19.default.join(home, ".netrc"),
|
|
10177
|
+
label: "~/.netrc",
|
|
10178
|
+
description: "FTP/HTTP credentials in plain text",
|
|
10179
|
+
score: 15
|
|
10180
|
+
},
|
|
10181
|
+
{
|
|
10182
|
+
full: import_path19.default.join(home, ".npmrc"),
|
|
10183
|
+
label: "~/.npmrc",
|
|
10184
|
+
description: "npm auth token \u2014 can publish packages as you",
|
|
10185
|
+
score: 10
|
|
10186
|
+
},
|
|
10187
|
+
{
|
|
10188
|
+
full: import_path19.default.join(home, ".node9", "credentials.json"),
|
|
10189
|
+
label: "~/.node9/credentials.json",
|
|
10190
|
+
description: "Node9 cloud API key",
|
|
10191
|
+
score: 10
|
|
10192
|
+
},
|
|
10193
|
+
{
|
|
10194
|
+
full: import_path19.default.join(cwd, ".env"),
|
|
10195
|
+
label: ".env (current folder)",
|
|
10196
|
+
description: "App secrets \u2014 database passwords, API keys",
|
|
10197
|
+
score: 20
|
|
10198
|
+
},
|
|
10199
|
+
{
|
|
10200
|
+
full: import_path19.default.join(cwd, ".env.local"),
|
|
10201
|
+
label: ".env.local (current folder)",
|
|
10202
|
+
description: "Local overrides \u2014 often contains real credentials",
|
|
10203
|
+
score: 15
|
|
10204
|
+
},
|
|
10205
|
+
{
|
|
10206
|
+
full: import_path19.default.join(cwd, ".env.production"),
|
|
10207
|
+
label: ".env.production (current folder)",
|
|
10208
|
+
description: "Production secrets",
|
|
10209
|
+
score: 20
|
|
10210
|
+
}
|
|
9985
10211
|
];
|
|
9986
10212
|
}
|
|
9987
|
-
|
|
10213
|
+
function isReadable(filePath) {
|
|
9988
10214
|
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;
|
|
10215
|
+
import_fs17.default.accessSync(filePath, import_fs17.default.constants.R_OK);
|
|
10216
|
+
return true;
|
|
10004
10217
|
} catch {
|
|
10005
|
-
return
|
|
10218
|
+
return false;
|
|
10006
10219
|
}
|
|
10007
10220
|
}
|
|
10008
|
-
|
|
10009
|
-
if (
|
|
10010
|
-
|
|
10011
|
-
if (
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10221
|
+
function scoreLabel(score) {
|
|
10222
|
+
if (score >= 80) return import_chalk2.default.green(`${score}/100 Good`);
|
|
10223
|
+
if (score >= 50) return import_chalk2.default.yellow(`${score}/100 Moderate risk`);
|
|
10224
|
+
if (score >= 25) return import_chalk2.default.red(`${score}/100 High risk`);
|
|
10225
|
+
return import_chalk2.default.red.bold(`${score}/100 Critical`);
|
|
10226
|
+
}
|
|
10227
|
+
function runBlast() {
|
|
10228
|
+
const home = import_os16.default.homedir();
|
|
10229
|
+
const cwd = process.cwd();
|
|
10230
|
+
const paths = buildSensitivePaths(home, cwd);
|
|
10231
|
+
let scoreDeduction = 0;
|
|
10232
|
+
const reachable = [];
|
|
10233
|
+
for (const p of paths) {
|
|
10234
|
+
if (import_fs17.default.existsSync(p.full) && isReadable(p.full)) {
|
|
10235
|
+
reachable.push(p);
|
|
10236
|
+
scoreDeduction += p.score;
|
|
10237
|
+
}
|
|
10016
10238
|
}
|
|
10017
|
-
const
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10239
|
+
const envFindings = [];
|
|
10240
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
10241
|
+
if (!value) continue;
|
|
10242
|
+
const match = scanArgs({ [key]: value });
|
|
10243
|
+
if (match) {
|
|
10244
|
+
envFindings.push({ key, patternName: match.patternName });
|
|
10245
|
+
scoreDeduction += 10;
|
|
10246
|
+
}
|
|
10024
10247
|
}
|
|
10025
|
-
|
|
10026
|
-
memCacheAt = Date.now();
|
|
10027
|
-
lookupCache.clear();
|
|
10248
|
+
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
10028
10249
|
}
|
|
10029
|
-
function
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10250
|
+
function registerBlastCommand(program2) {
|
|
10251
|
+
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
10252
|
+
const home = import_os16.default.homedir();
|
|
10253
|
+
const cwd = process.cwd();
|
|
10254
|
+
const { reachable, envFindings, score } = runBlast();
|
|
10255
|
+
console.log("");
|
|
10256
|
+
console.log(
|
|
10257
|
+
import_chalk2.default.bold(" \u{1F52D} Node9 Blast Radius") + import_chalk2.default.dim(" \xB7 what an AI agent can reach from here")
|
|
10258
|
+
);
|
|
10259
|
+
console.log(import_chalk2.default.dim(" Running in: ") + import_chalk2.default.white(cwd.replace(home, "~")));
|
|
10260
|
+
console.log("");
|
|
10261
|
+
if (reachable.length > 0) {
|
|
10262
|
+
console.log(" " + import_chalk2.default.red.bold("Sensitive files reachable:"));
|
|
10263
|
+
for (const p of reachable) {
|
|
10264
|
+
console.log(
|
|
10265
|
+
" " + import_chalk2.default.red("\u2717 ") + import_chalk2.default.yellow(p.label.padEnd(38)) + import_chalk2.default.dim(p.description)
|
|
10266
|
+
);
|
|
10267
|
+
}
|
|
10268
|
+
console.log("");
|
|
10042
10269
|
}
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10270
|
+
if (envFindings.length > 0) {
|
|
10271
|
+
console.log(" " + import_chalk2.default.red.bold("Secrets in active environment:"));
|
|
10272
|
+
for (const f of envFindings) {
|
|
10273
|
+
console.log(
|
|
10274
|
+
" " + import_chalk2.default.red("\u2717 ") + import_chalk2.default.yellow(f.key.padEnd(38)) + import_chalk2.default.dim(f.patternName)
|
|
10275
|
+
);
|
|
10047
10276
|
}
|
|
10277
|
+
console.log("");
|
|
10048
10278
|
}
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10279
|
+
console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
|
|
10280
|
+
if (reachable.length === 0 && envFindings.length === 0) {
|
|
10281
|
+
console.log(" " + import_chalk2.default.green("\u2705 No sensitive files or environment secrets found."));
|
|
10282
|
+
console.log(" Security Score: " + scoreLabel(score));
|
|
10283
|
+
} else {
|
|
10284
|
+
console.log(
|
|
10285
|
+
" Security Score: " + scoreLabel(score) + import_chalk2.default.dim(
|
|
10286
|
+
` (${reachable.length} file${reachable.length !== 1 ? "s" : ""}, ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""})`
|
|
10287
|
+
)
|
|
10288
|
+
);
|
|
10289
|
+
console.log("");
|
|
10290
|
+
console.log(
|
|
10291
|
+
import_chalk2.default.dim(
|
|
10292
|
+
" 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."
|
|
10293
|
+
)
|
|
10294
|
+
);
|
|
10052
10295
|
}
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
return resolved;
|
|
10296
|
+
console.log("");
|
|
10297
|
+
});
|
|
10056
10298
|
}
|
|
10057
|
-
var
|
|
10058
|
-
var
|
|
10059
|
-
"src/
|
|
10299
|
+
var import_chalk2, import_fs17, import_path19, import_os16;
|
|
10300
|
+
var init_blast = __esm({
|
|
10301
|
+
"src/cli/commands/blast.ts"() {
|
|
10060
10302
|
"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();
|
|
10303
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
10304
|
+
import_fs17 = __toESM(require("fs"));
|
|
10305
|
+
import_path19 = __toESM(require("path"));
|
|
10306
|
+
import_os16 = __toESM(require("os"));
|
|
10307
|
+
init_dlp();
|
|
10095
10308
|
}
|
|
10096
10309
|
});
|
|
10097
10310
|
|
|
10098
|
-
// src/
|
|
10099
|
-
function
|
|
10100
|
-
|
|
10311
|
+
// src/cli/render/scan-derive.ts
|
|
10312
|
+
function classifyScore(score) {
|
|
10313
|
+
if (score >= 80) return { band: "good", label: "Good", color: import_chalk3.default.green };
|
|
10314
|
+
if (score >= 50) return { band: "at-risk", label: "At Risk", color: import_chalk3.default.yellow };
|
|
10315
|
+
return { band: "critical", label: "Critical", color: import_chalk3.default.red };
|
|
10101
10316
|
}
|
|
10102
|
-
function
|
|
10103
|
-
|
|
10317
|
+
function topDlpPatterns(findings, n) {
|
|
10318
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10319
|
+
for (const f of findings) {
|
|
10320
|
+
counts.set(f.patternName, (counts.get(f.patternName) ?? 0) + 1);
|
|
10321
|
+
}
|
|
10322
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name, count]) => ({ name, count }));
|
|
10104
10323
|
}
|
|
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
|
-
}
|
|
10324
|
+
function topRulesByVerdict(sections, verdict, n) {
|
|
10325
|
+
const matched = [];
|
|
10326
|
+
for (const section of sections) {
|
|
10327
|
+
for (const rule of section.rules) {
|
|
10328
|
+
const matches = verdict === "block" ? rule.verdict === "block" : rule.verdict !== "block";
|
|
10329
|
+
if (matches) matched.push({ name: rule.name, count: rule.findings.length });
|
|
10120
10330
|
}
|
|
10121
10331
|
}
|
|
10122
|
-
return
|
|
10332
|
+
return matched.sort((a, b) => b.count - a.count).slice(0, n);
|
|
10123
10333
|
}
|
|
10124
|
-
function
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
return [];
|
|
10129
|
-
}
|
|
10334
|
+
function computeLoopWaste(loops, totalToolCalls) {
|
|
10335
|
+
const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
10336
|
+
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
10337
|
+
return { wastedCalls, wastePct };
|
|
10130
10338
|
}
|
|
10131
|
-
function
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10339
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
10340
|
+
const out = [];
|
|
10341
|
+
for (const section of sections) {
|
|
10342
|
+
if (section.sourceType !== "shield") continue;
|
|
10343
|
+
if (!section.shieldKey) continue;
|
|
10344
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
10345
|
+
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);
|
|
10346
|
+
out.push({
|
|
10347
|
+
shieldName: section.shieldKey,
|
|
10348
|
+
totalCatches,
|
|
10349
|
+
blockCatches: section.blockedCount,
|
|
10350
|
+
reviewCatches: section.reviewCount,
|
|
10351
|
+
topRuleLabels
|
|
10352
|
+
});
|
|
10136
10353
|
}
|
|
10354
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
10137
10355
|
}
|
|
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
|
-
}
|
|
10356
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
10357
|
+
const inner = width - 4;
|
|
10358
|
+
const out = [];
|
|
10359
|
+
const titlePad = ` ${title} `;
|
|
10360
|
+
const titleWidth = (0, import_string_width.default)(titlePad);
|
|
10361
|
+
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
10362
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - (0, import_string_width.default)(titleSegment)));
|
|
10363
|
+
out.push(import_chalk3.default.dim("\u256D\u2500") + import_chalk3.default.bold(titleSegment) + import_chalk3.default.dim(`${dashFill}\u2500\u256E`));
|
|
10364
|
+
for (const line of bodyLines) {
|
|
10365
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
10366
|
+
out.push(import_chalk3.default.dim("\u2502 ") + line.rendered + padding + import_chalk3.default.dim(" \u2502"));
|
|
10175
10367
|
}
|
|
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
|
-
};
|
|
10368
|
+
out.push(import_chalk3.default.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
10369
|
+
return out;
|
|
10193
10370
|
}
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10371
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
10372
|
+
const t = new Date(timestamp).getTime();
|
|
10373
|
+
if (Number.isNaN(t)) return "?";
|
|
10374
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
10375
|
+
if (days < 1) return "today";
|
|
10376
|
+
if (days > 90) return "90d+";
|
|
10377
|
+
return `${days}d`;
|
|
10378
|
+
}
|
|
10379
|
+
var import_chalk3, import_string_width, PANEL_WIDTH;
|
|
10380
|
+
var init_scan_derive = __esm({
|
|
10381
|
+
"src/cli/render/scan-derive.ts"() {
|
|
10197
10382
|
"use strict";
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10206
|
-
|
|
10207
|
-
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
},
|
|
10212
|
-
collect(sinceMs) {
|
|
10213
|
-
const base = codexSessionsDir();
|
|
10214
|
-
const combined = /* @__PURE__ */ new Map();
|
|
10215
|
-
for (const file of listCodexSessionFiles(base)) {
|
|
10216
|
-
try {
|
|
10217
|
-
if (sinceMs !== void 0 && import_fs17.default.statSync(file).mtimeMs < sinceMs) continue;
|
|
10218
|
-
} catch {
|
|
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
|
-
}
|
|
10383
|
+
import_chalk3 = __toESM(require("chalk"));
|
|
10384
|
+
import_string_width = __toESM(require("string-width"));
|
|
10385
|
+
PANEL_WIDTH = 76;
|
|
10386
|
+
}
|
|
10387
|
+
});
|
|
10388
|
+
|
|
10389
|
+
// src/protection.ts
|
|
10390
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
10391
|
+
var init_protection = __esm({
|
|
10392
|
+
"src/protection.ts"() {
|
|
10393
|
+
"use strict";
|
|
10394
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
10395
|
+
"project-jail": 0.7
|
|
10243
10396
|
};
|
|
10244
10397
|
}
|
|
10245
10398
|
});
|
|
10246
10399
|
|
|
10247
|
-
// src/
|
|
10248
|
-
function
|
|
10249
|
-
|
|
10250
|
-
}
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10400
|
+
// src/cli/render/scan-json.ts
|
|
10401
|
+
function buildScanJson(input) {
|
|
10402
|
+
const { summary, blast, isWired, generatedAt } = input;
|
|
10403
|
+
const { band } = classifyScore(blast.score);
|
|
10404
|
+
return {
|
|
10405
|
+
schemaVersion: 1,
|
|
10406
|
+
generatedAt,
|
|
10407
|
+
isWired,
|
|
10408
|
+
score: blast.score,
|
|
10409
|
+
band,
|
|
10410
|
+
totals: {
|
|
10411
|
+
blocked: summary.byVerdict.blocked,
|
|
10412
|
+
review: summary.byVerdict.supervised,
|
|
10413
|
+
leaks: summary.byVerdict.leaks,
|
|
10414
|
+
loops: summary.byVerdict.loops,
|
|
10415
|
+
blastExposures: blast.reachable.length + blast.envFindings.length
|
|
10416
|
+
},
|
|
10417
|
+
summary,
|
|
10418
|
+
blast: {
|
|
10419
|
+
score: blast.score,
|
|
10420
|
+
reachable: blast.reachable,
|
|
10421
|
+
envFindings: blast.envFindings
|
|
10422
|
+
}
|
|
10423
|
+
};
|
|
10424
|
+
}
|
|
10425
|
+
var init_scan_json = __esm({
|
|
10426
|
+
"src/cli/render/scan-json.ts"() {
|
|
10427
|
+
"use strict";
|
|
10428
|
+
init_scan_derive();
|
|
10429
|
+
}
|
|
10430
|
+
});
|
|
10431
|
+
|
|
10432
|
+
// src/cli/render/scan-history.ts
|
|
10433
|
+
function defaultHistoryPath() {
|
|
10434
|
+
return import_path20.default.join(import_os17.default.homedir(), ".node9", "scan-history.json");
|
|
10261
10435
|
}
|
|
10262
|
-
function
|
|
10436
|
+
function readPreviousScan(opts = {}) {
|
|
10437
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10263
10438
|
try {
|
|
10264
|
-
|
|
10439
|
+
if (!import_fs18.default.existsSync(filePath)) return null;
|
|
10440
|
+
const raw = import_fs18.default.readFileSync(filePath, "utf8");
|
|
10441
|
+
const parsed = JSON.parse(raw);
|
|
10442
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
10443
|
+
const last = parsed[parsed.length - 1];
|
|
10444
|
+
if (!isValidRecord(last)) return null;
|
|
10445
|
+
return last;
|
|
10265
10446
|
} catch {
|
|
10266
|
-
return
|
|
10447
|
+
return null;
|
|
10267
10448
|
}
|
|
10268
10449
|
}
|
|
10269
|
-
function
|
|
10450
|
+
function appendScanHistory(record, opts = {}) {
|
|
10451
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10452
|
+
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
10270
10453
|
try {
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
if (!isDir2(chats)) continue;
|
|
10281
|
-
for (const f of safeReaddir2(chats)) {
|
|
10282
|
-
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
10283
|
-
out.push({ file: import_path20.default.join(chats, f), project });
|
|
10454
|
+
import_fs18.default.mkdirSync(import_path20.default.dirname(filePath), { recursive: true });
|
|
10455
|
+
let history = [];
|
|
10456
|
+
if (import_fs18.default.existsSync(filePath)) {
|
|
10457
|
+
try {
|
|
10458
|
+
const parsed = JSON.parse(import_fs18.default.readFileSync(filePath, "utf8"));
|
|
10459
|
+
if (Array.isArray(parsed)) {
|
|
10460
|
+
history = parsed.filter(isValidRecord);
|
|
10461
|
+
}
|
|
10462
|
+
} catch {
|
|
10284
10463
|
}
|
|
10285
10464
|
}
|
|
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
|
-
});
|
|
10465
|
+
history.push(record);
|
|
10466
|
+
if (history.length > cap) {
|
|
10467
|
+
history = history.slice(history.length - cap);
|
|
10335
10468
|
}
|
|
10469
|
+
import_fs18.default.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
10470
|
+
} catch (err2) {
|
|
10471
|
+
process.stderr.write(
|
|
10472
|
+
`[node9] Warning: could not write scan-history.json: ${err2.message}
|
|
10473
|
+
`
|
|
10474
|
+
);
|
|
10336
10475
|
}
|
|
10337
|
-
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
10338
|
-
return [...byKey.values()];
|
|
10339
10476
|
}
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10477
|
+
function computeScanDelta(current, previous, now = Date.now()) {
|
|
10478
|
+
if (!previous) return null;
|
|
10479
|
+
const prevMs = Date.parse(previous.timestamp);
|
|
10480
|
+
if (Number.isNaN(prevMs)) return null;
|
|
10481
|
+
const scoreDelta = current.score - previous.score;
|
|
10482
|
+
const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
|
|
10483
|
+
if (scoreDelta === 0 && daysAgo === 0) return null;
|
|
10484
|
+
return { scoreDelta, daysAgo };
|
|
10485
|
+
}
|
|
10486
|
+
function isValidRecord(x) {
|
|
10487
|
+
if (typeof x !== "object" || x === null) return false;
|
|
10488
|
+
const r = x;
|
|
10489
|
+
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";
|
|
10490
|
+
}
|
|
10491
|
+
var import_fs18, import_path20, import_os17, SCAN_HISTORY_CAP;
|
|
10492
|
+
var init_scan_history = __esm({
|
|
10493
|
+
"src/cli/render/scan-history.ts"() {
|
|
10343
10494
|
"use strict";
|
|
10344
10495
|
import_fs18 = __toESM(require("fs"));
|
|
10345
|
-
import_os17 = __toESM(require("os"));
|
|
10346
10496
|
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
|
-
};
|
|
10497
|
+
import_os17 = __toESM(require("os"));
|
|
10498
|
+
SCAN_HISTORY_CAP = 30;
|
|
10389
10499
|
}
|
|
10390
10500
|
});
|
|
10391
10501
|
|
|
@@ -11424,19 +11534,15 @@ var init_scan_upload_history = __esm({
|
|
|
11424
11534
|
|
|
11425
11535
|
// src/cli/commands/scan.ts
|
|
11426
11536
|
function claudeModelPrice(model) {
|
|
11427
|
-
const
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
}
|
|
11431
|
-
return null;
|
|
11537
|
+
const t = pricingFor(model);
|
|
11538
|
+
if (!t) return null;
|
|
11539
|
+
const [i, o, cw, cr] = t;
|
|
11540
|
+
return { i, o, cw, cr };
|
|
11432
11541
|
}
|
|
11433
11542
|
function geminiModelPrice(model) {
|
|
11434
|
-
const
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
}
|
|
11438
|
-
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
11439
|
-
return null;
|
|
11543
|
+
const p = geminiPriceFor(model);
|
|
11544
|
+
if (!p) return null;
|
|
11545
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
11440
11546
|
}
|
|
11441
11547
|
function isNode9SelfOutput(text) {
|
|
11442
11548
|
let hits = 0;
|
|
@@ -12082,14 +12188,17 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12082
12188
|
if (!import_fs23.default.existsSync(chatsDir)) continue;
|
|
12083
12189
|
let chatFiles;
|
|
12084
12190
|
try {
|
|
12085
|
-
chatFiles = import_fs23.default.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
12191
|
+
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
12192
|
} catch {
|
|
12087
12193
|
continue;
|
|
12088
12194
|
}
|
|
12195
|
+
const seenSessions = /* @__PURE__ */ new Set();
|
|
12089
12196
|
for (const chatFile of chatFiles) {
|
|
12197
|
+
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
12198
|
+
if (seenSessions.has(sessionId)) continue;
|
|
12199
|
+
seenSessions.add(sessionId);
|
|
12090
12200
|
result.filesScanned++;
|
|
12091
12201
|
onProgress?.(result.filesScanned);
|
|
12092
|
-
const sessionId = chatFile.replace(/\.json$/, "");
|
|
12093
12202
|
let raw;
|
|
12094
12203
|
try {
|
|
12095
12204
|
raw = import_fs23.default.readFileSync(import_path25.default.join(chatsDir, chatFile), "utf-8");
|
|
@@ -12099,7 +12208,18 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12099
12208
|
const sessionCalls = [];
|
|
12100
12209
|
let session;
|
|
12101
12210
|
try {
|
|
12102
|
-
|
|
12211
|
+
if (chatFile.endsWith(".jsonl")) {
|
|
12212
|
+
const messages = raw.split("\n").filter((l) => l.trim()).map((l) => {
|
|
12213
|
+
try {
|
|
12214
|
+
return JSON.parse(l);
|
|
12215
|
+
} catch {
|
|
12216
|
+
return null;
|
|
12217
|
+
}
|
|
12218
|
+
}).filter((m) => m !== null);
|
|
12219
|
+
session = { messages };
|
|
12220
|
+
} else {
|
|
12221
|
+
session = JSON.parse(raw);
|
|
12222
|
+
}
|
|
12103
12223
|
} catch {
|
|
12104
12224
|
continue;
|
|
12105
12225
|
}
|
|
@@ -12709,6 +12829,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12709
12829
|
let lastTotalInput = 0;
|
|
12710
12830
|
let lastTotalCached = 0;
|
|
12711
12831
|
let lastTotalOutput = 0;
|
|
12832
|
+
let model = "";
|
|
12712
12833
|
for (const line of lines) {
|
|
12713
12834
|
if (!line.trim()) continue;
|
|
12714
12835
|
onLine?.();
|
|
@@ -12726,6 +12847,10 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12726
12847
|
projLabel = stripTerminalEscapes(cwd.replace(import_os22.default.homedir(), "~")).slice(0, 40);
|
|
12727
12848
|
continue;
|
|
12728
12849
|
}
|
|
12850
|
+
if (entry.type === "turn_context" && typeof payload["model"] === "string") {
|
|
12851
|
+
model = payload["model"];
|
|
12852
|
+
continue;
|
|
12853
|
+
}
|
|
12729
12854
|
if (entry.type === "event_msg" && payload["type"] === "token_count") {
|
|
12730
12855
|
const info = payload["info"];
|
|
12731
12856
|
const usage = info?.["total_token_usage"] ?? {};
|
|
@@ -12869,8 +12994,11 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12869
12994
|
}
|
|
12870
12995
|
}
|
|
12871
12996
|
}
|
|
12872
|
-
|
|
12873
|
-
|
|
12997
|
+
result.totalCostUSD += codexSessionCost(model, {
|
|
12998
|
+
input: lastTotalInput,
|
|
12999
|
+
cached: lastTotalCached,
|
|
13000
|
+
output: lastTotalOutput
|
|
13001
|
+
});
|
|
12874
13002
|
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
|
|
12875
13003
|
}
|
|
12876
13004
|
return result;
|
|
@@ -13917,7 +14045,7 @@ function registerScanCommand(program2) {
|
|
|
13917
14045
|
}
|
|
13918
14046
|
);
|
|
13919
14047
|
}
|
|
13920
|
-
var import_chalk5, import_fs23, import_path25, import_os22, import_string_width2,
|
|
14048
|
+
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
14049
|
var init_scan = __esm({
|
|
13922
14050
|
"src/cli/commands/scan.ts"() {
|
|
13923
14051
|
"use strict";
|
|
@@ -13930,6 +14058,9 @@ var init_scan = __esm({
|
|
|
13930
14058
|
init_policy();
|
|
13931
14059
|
init_dist();
|
|
13932
14060
|
init_dlp();
|
|
14061
|
+
init_litellm();
|
|
14062
|
+
init_cost_gemini();
|
|
14063
|
+
init_cost_codex();
|
|
13933
14064
|
init_hook_payload();
|
|
13934
14065
|
init_dist();
|
|
13935
14066
|
init_scan_summary();
|
|
@@ -13940,26 +14071,6 @@ var init_scan = __esm({
|
|
|
13940
14071
|
import_string_width2 = __toESM(require("string-width"));
|
|
13941
14072
|
init_scan_json();
|
|
13942
14073
|
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
14074
|
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13964
14075
|
".ts",
|
|
13965
14076
|
".tsx",
|
|
@@ -20140,6 +20251,7 @@ var import_os36 = __toESM(require("os"));
|
|
|
20140
20251
|
var import_path41 = __toESM(require("path"));
|
|
20141
20252
|
init_costSync();
|
|
20142
20253
|
init_litellm();
|
|
20254
|
+
init_cost_codex();
|
|
20143
20255
|
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
20256
|
function buildTestTimestamps(allEntries) {
|
|
20145
20257
|
const testTs = /* @__PURE__ */ new Set();
|
|
@@ -20236,24 +20348,11 @@ function isAllow(decision) {
|
|
|
20236
20348
|
function isDlp(checkedBy) {
|
|
20237
20349
|
return !!checkedBy?.includes("dlp");
|
|
20238
20350
|
}
|
|
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
20351
|
function claudeModelPrice2(model) {
|
|
20252
|
-
const
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
}
|
|
20256
|
-
return null;
|
|
20352
|
+
const t = pricingFor(model);
|
|
20353
|
+
if (!t) return null;
|
|
20354
|
+
const [i, o, cw, cr] = t;
|
|
20355
|
+
return { i, o, cw, cr };
|
|
20257
20356
|
}
|
|
20258
20357
|
function emptyClaudeCostAccumulator() {
|
|
20259
20358
|
return {
|
|
@@ -20368,6 +20467,7 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20368
20467
|
return;
|
|
20369
20468
|
}
|
|
20370
20469
|
let sessionStart2 = "";
|
|
20470
|
+
let model = "";
|
|
20371
20471
|
let lastTotalInput = 0;
|
|
20372
20472
|
let lastTotalCached = 0;
|
|
20373
20473
|
let lastTotalOutput = 0;
|
|
@@ -20385,6 +20485,10 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20385
20485
|
sessionStart2 = String(p["timestamp"] ?? "");
|
|
20386
20486
|
continue;
|
|
20387
20487
|
}
|
|
20488
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
20489
|
+
model = p["model"];
|
|
20490
|
+
continue;
|
|
20491
|
+
}
|
|
20388
20492
|
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
20389
20493
|
const info = p["info"] ?? {};
|
|
20390
20494
|
const usage = info["total_token_usage"] ?? {};
|
|
@@ -20399,12 +20503,17 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20399
20503
|
if (!sessionStart2) return;
|
|
20400
20504
|
const ts = new Date(sessionStart2);
|
|
20401
20505
|
if (ts < start || ts > end) return;
|
|
20402
|
-
const
|
|
20403
|
-
|
|
20506
|
+
const cost = codexSessionCost(model, {
|
|
20507
|
+
input: lastTotalInput,
|
|
20508
|
+
cached: lastTotalCached,
|
|
20509
|
+
output: lastTotalOutput
|
|
20510
|
+
});
|
|
20404
20511
|
acc.total += cost;
|
|
20405
20512
|
acc.toolCalls += sessionToolCalls;
|
|
20406
20513
|
const dateKey = sessionStart2.slice(0, 10);
|
|
20407
20514
|
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
20515
|
+
const normModel = normalizeModel(model || "gpt-5");
|
|
20516
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
20408
20517
|
}
|
|
20409
20518
|
function listCodexSessionFiles2(sessionsBase) {
|
|
20410
20519
|
const jsonlFiles = [];
|
|
@@ -20442,13 +20551,25 @@ function listCodexSessionFiles2(sessionsBase) {
|
|
|
20442
20551
|
}
|
|
20443
20552
|
return jsonlFiles;
|
|
20444
20553
|
}
|
|
20554
|
+
function mergeByModel(...maps) {
|
|
20555
|
+
const out = /* @__PURE__ */ new Map();
|
|
20556
|
+
for (const m of maps) {
|
|
20557
|
+
for (const [k, v] of m) out.set(k, (out.get(k) ?? 0) + v);
|
|
20558
|
+
}
|
|
20559
|
+
return out;
|
|
20560
|
+
}
|
|
20445
20561
|
function loadCodexCost(start, end, sessionsBase) {
|
|
20446
|
-
const acc = {
|
|
20562
|
+
const acc = {
|
|
20563
|
+
total: 0,
|
|
20564
|
+
toolCalls: 0,
|
|
20565
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
20566
|
+
byModel: /* @__PURE__ */ new Map()
|
|
20567
|
+
};
|
|
20447
20568
|
const files = listCodexSessionFiles2(sessionsBase);
|
|
20448
20569
|
for (const filePath of files) {
|
|
20449
20570
|
processCodexCostFile(filePath, start, end, acc);
|
|
20450
20571
|
}
|
|
20451
|
-
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
20572
|
+
return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
|
|
20452
20573
|
}
|
|
20453
20574
|
var GEMINI_FALLBACK_MODELS2 = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
20454
20575
|
function geminiPriceFor2(model) {
|
|
@@ -20737,7 +20858,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
20737
20858
|
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
20738
20859
|
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
20739
20860
|
byDay: claudeCost.byDay,
|
|
20740
|
-
byModel: claudeCost.byModel,
|
|
20861
|
+
byModel: mergeByModel(claudeCost.byModel, codexCost.byModel),
|
|
20741
20862
|
byProject: claudeCost.byProject
|
|
20742
20863
|
},
|
|
20743
20864
|
toolMap,
|
|
@@ -22010,6 +22131,8 @@ var import_chalk19 = __toESM(require("chalk"));
|
|
|
22010
22131
|
var import_child_process10 = require("child_process");
|
|
22011
22132
|
var import_execa3 = require("execa");
|
|
22012
22133
|
init_orchestrator();
|
|
22134
|
+
init_cloud();
|
|
22135
|
+
init_config();
|
|
22013
22136
|
init_provenance();
|
|
22014
22137
|
init_mcp_pin();
|
|
22015
22138
|
init_mcp_tools();
|
|
@@ -22038,6 +22161,60 @@ function normalizeClientName(name) {
|
|
|
22038
22161
|
const sanitized = sanitize4(name).slice(0, 40);
|
|
22039
22162
|
return sanitized.length > 0 ? sanitized : void 0;
|
|
22040
22163
|
}
|
|
22164
|
+
function reportPinMismatchToCloud(serverKey, agent) {
|
|
22165
|
+
try {
|
|
22166
|
+
const creds = getCredentials();
|
|
22167
|
+
if (!creds) return;
|
|
22168
|
+
void auditLocalAllow(
|
|
22169
|
+
`mcp-server:${serverKey}`,
|
|
22170
|
+
{ serverKey, reason: "tool-pin-mismatch" },
|
|
22171
|
+
"mcp-pin-mismatch",
|
|
22172
|
+
creds,
|
|
22173
|
+
{ mcpServer: serverKey, agent },
|
|
22174
|
+
void 0,
|
|
22175
|
+
false,
|
|
22176
|
+
{
|
|
22177
|
+
ruleName: "MCP tool definitions changed (possible rug pull)",
|
|
22178
|
+
ruleDescription: `The MCP server "${serverKey}" changed its tool definitions since they were pinned. This can indicate a supply-chain attack (tool poisoning). The session was quarantined. Review with: node9 mcp pin update ${serverKey}`
|
|
22179
|
+
}
|
|
22180
|
+
);
|
|
22181
|
+
} catch {
|
|
22182
|
+
}
|
|
22183
|
+
}
|
|
22184
|
+
function reportInventoryToCloud(serverKey, toolCount, agent) {
|
|
22185
|
+
try {
|
|
22186
|
+
const creds = getCredentials();
|
|
22187
|
+
if (!creds) return;
|
|
22188
|
+
void auditLocalAllow(
|
|
22189
|
+
`mcp-server:${serverKey}`,
|
|
22190
|
+
{ serverKey, toolCount },
|
|
22191
|
+
"mcp-discovered",
|
|
22192
|
+
creds,
|
|
22193
|
+
{ mcpServer: serverKey, agent },
|
|
22194
|
+
void 0,
|
|
22195
|
+
false,
|
|
22196
|
+
{ mcpToolCount: toolCount }
|
|
22197
|
+
);
|
|
22198
|
+
} catch {
|
|
22199
|
+
}
|
|
22200
|
+
}
|
|
22201
|
+
function reportLargeResponseToCloud(serverKey, responseBytes, agent) {
|
|
22202
|
+
try {
|
|
22203
|
+
const creds = getCredentials();
|
|
22204
|
+
if (!creds) return;
|
|
22205
|
+
void auditLocalAllow(
|
|
22206
|
+
`mcp-server:${serverKey}`,
|
|
22207
|
+
{ serverKey, responseBytes },
|
|
22208
|
+
"mcp-large-response",
|
|
22209
|
+
creds,
|
|
22210
|
+
{ mcpServer: serverKey, agent },
|
|
22211
|
+
void 0,
|
|
22212
|
+
false,
|
|
22213
|
+
{ mcpResponseBytes: responseBytes }
|
|
22214
|
+
);
|
|
22215
|
+
} catch {
|
|
22216
|
+
}
|
|
22217
|
+
}
|
|
22041
22218
|
function tokenize4(cmd) {
|
|
22042
22219
|
const tokens = [];
|
|
22043
22220
|
let current = "";
|
|
@@ -22298,6 +22475,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
22298
22475
|
const currentHash = hashToolDefinitions(tools);
|
|
22299
22476
|
const pinStatus = checkPin(serverKey, currentHash, gatewayCwd);
|
|
22300
22477
|
const token = getInternalToken();
|
|
22478
|
+
reportInventoryToCloud(serverKey, tools.length, clientName);
|
|
22301
22479
|
if (isDaemonRunning() && process.env.NODE9_TESTING !== "1") {
|
|
22302
22480
|
const toolSummary = tools.map((t) => ({ name: t.name, description: t.description }));
|
|
22303
22481
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/mcp/discovered`, {
|
|
@@ -22362,6 +22540,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
22362
22540
|
console.error(import_chalk19.default.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
22363
22541
|
console.error(import_chalk19.default.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
22364
22542
|
`));
|
|
22543
|
+
reportPinMismatchToCloud(serverKey, clientName);
|
|
22365
22544
|
const errorResponse = {
|
|
22366
22545
|
jsonrpc: "2.0",
|
|
22367
22546
|
id: parsed.id,
|
|
@@ -22407,6 +22586,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
22407
22586
|
`\u26A1 Node9: Large MCP response from '${toolName}' (${(line.length / 1024).toFixed(0)}KB) \u2014 context window enlarged`
|
|
22408
22587
|
)
|
|
22409
22588
|
);
|
|
22589
|
+
reportLargeResponseToCloud(serverKey, line.length, clientName);
|
|
22410
22590
|
if (isDaemonRunning() && process.env.NODE9_TESTING !== "1") {
|
|
22411
22591
|
const token = getInternalToken();
|
|
22412
22592
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/mcp/large-response`, {
|
|
@@ -23510,40 +23690,19 @@ var import_fs45 = __toESM(require("fs"));
|
|
|
23510
23690
|
var import_path46 = __toESM(require("path"));
|
|
23511
23691
|
var import_os40 = __toESM(require("os"));
|
|
23512
23692
|
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
|
-
};
|
|
23693
|
+
init_litellm();
|
|
23694
|
+
init_cost_gemini();
|
|
23695
|
+
init_cost_codex();
|
|
23525
23696
|
function modelPrice(model) {
|
|
23526
|
-
const
|
|
23527
|
-
|
|
23528
|
-
|
|
23529
|
-
}
|
|
23530
|
-
return null;
|
|
23697
|
+
const t = pricingFor(model);
|
|
23698
|
+
if (!t) return null;
|
|
23699
|
+
const [i, o, cw, cr] = t;
|
|
23700
|
+
return { i, o, cw, cr };
|
|
23531
23701
|
}
|
|
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
23702
|
function geminiModelPrice2(model) {
|
|
23541
|
-
const
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
}
|
|
23545
|
-
if (base.includes("flash")) return GEMINI_PRICING2["gemini-2.0-flash"];
|
|
23546
|
-
return null;
|
|
23703
|
+
const p = geminiPriceFor(model);
|
|
23704
|
+
if (!p) return null;
|
|
23705
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
23547
23706
|
}
|
|
23548
23707
|
function encodeProjectPath(projectPath) {
|
|
23549
23708
|
return projectPath.replace(/\//g, "-");
|
|
@@ -23836,6 +23995,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23836
23995
|
let lastTotalInput = 0;
|
|
23837
23996
|
let lastTotalCached = 0;
|
|
23838
23997
|
let lastTotalOutput = 0;
|
|
23998
|
+
let model = "";
|
|
23839
23999
|
for (const line of lines) {
|
|
23840
24000
|
if (!line.trim()) continue;
|
|
23841
24001
|
let entry;
|
|
@@ -23851,6 +24011,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23851
24011
|
cwd = String(p["cwd"] ?? "");
|
|
23852
24012
|
continue;
|
|
23853
24013
|
}
|
|
24014
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
24015
|
+
model = p["model"];
|
|
24016
|
+
continue;
|
|
24017
|
+
}
|
|
23854
24018
|
if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
|
|
23855
24019
|
firstPrompt = String(p["message"] ?? "");
|
|
23856
24020
|
continue;
|
|
@@ -23877,8 +24041,11 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23877
24041
|
}
|
|
23878
24042
|
if (!sessionId || !startTime) continue;
|
|
23879
24043
|
if (cutoff && new Date(startTime) < cutoff) continue;
|
|
23880
|
-
const
|
|
23881
|
-
|
|
24044
|
+
const costUSD = codexSessionCost(model, {
|
|
24045
|
+
input: lastTotalInput,
|
|
24046
|
+
cached: lastTotalCached,
|
|
24047
|
+
output: lastTotalOutput
|
|
24048
|
+
});
|
|
23882
24049
|
const windowEnd = new Date(
|
|
23883
24050
|
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
23884
24051
|
).toISOString();
|
|
@@ -23902,11 +24069,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23902
24069
|
}
|
|
23903
24070
|
function buildSessions(days, historyPath) {
|
|
23904
24071
|
const hPath = historyPath ?? import_path46.default.join(import_os40.default.homedir(), ".claude", "history.jsonl");
|
|
23905
|
-
let historyRaw;
|
|
24072
|
+
let historyRaw = "";
|
|
23906
24073
|
try {
|
|
23907
24074
|
historyRaw = import_fs45.default.readFileSync(hPath, "utf-8");
|
|
23908
24075
|
} catch {
|
|
23909
|
-
return [];
|
|
23910
24076
|
}
|
|
23911
24077
|
const cutoff = days !== null ? (() => {
|
|
23912
24078
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -24197,12 +24363,6 @@ function registerSessionsCommand(program2) {
|
|
|
24197
24363
|
console.log("");
|
|
24198
24364
|
console.log(import_chalk24.default.cyan.bold("\u{1F4CB} node9 sessions") + import_chalk24.default.dim(" \u2014 what your AI agent did"));
|
|
24199
24365
|
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
24366
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
24207
24367
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
24208
24368
|
console.log(import_chalk24.default.dim(" " + rangeLabel));
|