@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.mjs
CHANGED
|
@@ -5869,6 +5869,57 @@ function validateApiUrl(raw) {
|
|
|
5869
5869
|
}
|
|
5870
5870
|
return null;
|
|
5871
5871
|
}
|
|
5872
|
+
function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false, riskMetadata) {
|
|
5873
|
+
const validated = validateApiUrl(creds.apiUrl);
|
|
5874
|
+
if (!validated) {
|
|
5875
|
+
try {
|
|
5876
|
+
fs10.appendFileSync(
|
|
5877
|
+
HOOK_DEBUG_LOG,
|
|
5878
|
+
`[audit] refused to send: invalid apiUrl scheme/host (got "${String(creds.apiUrl).slice(0, 200)}")
|
|
5879
|
+
`
|
|
5880
|
+
);
|
|
5881
|
+
} catch {
|
|
5882
|
+
}
|
|
5883
|
+
return Promise.resolve();
|
|
5884
|
+
}
|
|
5885
|
+
const safeArgs = containsSensitiveArgs ? { tool: toolName, redacted: true } : args;
|
|
5886
|
+
const dlpSample = dlpInfo && typeof dlpInfo.redactedSample === "string" ? dlpInfo.redactedSample.slice(0, DLP_SAMPLE_MAX_LEN) : void 0;
|
|
5887
|
+
const dlpPattern = dlpInfo && typeof dlpInfo.pattern === "string" ? dlpInfo.pattern.slice(0, DLP_PATTERN_MAX_LEN) : void 0;
|
|
5888
|
+
const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
|
|
5889
|
+
const cleanedRiskMetadata = riskMetadata ? Object.fromEntries(
|
|
5890
|
+
Object.entries(riskMetadata).filter(
|
|
5891
|
+
([, v]) => typeof v === "string" && v.length > 0 || typeof v === "number" && Number.isFinite(v)
|
|
5892
|
+
)
|
|
5893
|
+
) : void 0;
|
|
5894
|
+
const hasRiskMetadata = cleanedRiskMetadata && Object.keys(cleanedRiskMetadata).length > 0;
|
|
5895
|
+
return fetch(`${validated.toString().replace(/\/$/, "")}/audit`, {
|
|
5896
|
+
method: "POST",
|
|
5897
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
|
|
5898
|
+
body: JSON.stringify({
|
|
5899
|
+
toolName,
|
|
5900
|
+
args: safeArgs,
|
|
5901
|
+
checkedBy: safeCheckedBy,
|
|
5902
|
+
...dlpInfo && { dlpPattern, dlpSample },
|
|
5903
|
+
...hasRiskMetadata && { riskMetadata: cleanedRiskMetadata },
|
|
5904
|
+
// session_id (Claude Code + Gemini CLI) groups all audit rows from one
|
|
5905
|
+
// agent run; transcript_path is the authoritative pointer to the
|
|
5906
|
+
// session log (survives Gemini resume drift). Both optional —
|
|
5907
|
+
// unsupported agents (MCP-mediated) leave them undefined.
|
|
5908
|
+
...meta?.sessionId && { runId: meta.sessionId },
|
|
5909
|
+
...meta?.transcriptPath && { transcriptPath: meta.transcriptPath },
|
|
5910
|
+
context: {
|
|
5911
|
+
agent: meta?.agent,
|
|
5912
|
+
mcpServer: meta?.mcpServer,
|
|
5913
|
+
hostname: os9.hostname(),
|
|
5914
|
+
cwd: process.cwd(),
|
|
5915
|
+
platform: os9.platform()
|
|
5916
|
+
}
|
|
5917
|
+
}),
|
|
5918
|
+
signal: AbortSignal.timeout(5e3)
|
|
5919
|
+
}).then(() => {
|
|
5920
|
+
}).catch(() => {
|
|
5921
|
+
});
|
|
5922
|
+
}
|
|
5872
5923
|
async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
|
|
5873
5924
|
const controller = new AbortController();
|
|
5874
5925
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -5992,10 +6043,44 @@ async function resolveNode9SaaS(requestId, creds, approved, decidedBy) {
|
|
|
5992
6043
|
);
|
|
5993
6044
|
}
|
|
5994
6045
|
}
|
|
6046
|
+
var DLP_SAMPLE_MAX_LEN, DLP_PATTERN_MAX_LEN, KNOWN_CHECKED_BY;
|
|
5995
6047
|
var init_cloud = __esm({
|
|
5996
6048
|
"src/auth/cloud.ts"() {
|
|
5997
6049
|
"use strict";
|
|
5998
6050
|
init_audit();
|
|
6051
|
+
DLP_SAMPLE_MAX_LEN = 200;
|
|
6052
|
+
DLP_PATTERN_MAX_LEN = 100;
|
|
6053
|
+
KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
|
|
6054
|
+
"dlp-block",
|
|
6055
|
+
"observe-mode-dlp-would-block",
|
|
6056
|
+
"dlp-review-flagged",
|
|
6057
|
+
"loop-detected",
|
|
6058
|
+
"audit-mode",
|
|
6059
|
+
"local-policy",
|
|
6060
|
+
"smart-rule-block",
|
|
6061
|
+
// Smart-rule block was downgraded to review because the daemon was
|
|
6062
|
+
// running and we're not in CI. The block attempt is still recorded;
|
|
6063
|
+
// the user got a popup. Distinct from 'smart-rule-block' so the
|
|
6064
|
+
// dashboard can show "block rule overridden" separately from a hard
|
|
6065
|
+
// block that fired with no human in the loop.
|
|
6066
|
+
"smart-rule-block-override",
|
|
6067
|
+
"persistent",
|
|
6068
|
+
"trust",
|
|
6069
|
+
"observe-mode",
|
|
6070
|
+
"observe-mode-would-block",
|
|
6071
|
+
// MCP supply-chain: the gateway pinned a server's tool definitions and they
|
|
6072
|
+
// changed since (possible tool poisoning / rug pull). Emitted as a synthetic
|
|
6073
|
+
// audit row so the SaaS surfaces it as a blocked event. The firewall maps
|
|
6074
|
+
// this checkedBy to AUTO_BLOCKED. See doc/roadmap/active/saas-value-first.md
|
|
6075
|
+
// (workstream B-Tier2).
|
|
6076
|
+
"mcp-pin-mismatch",
|
|
6077
|
+
// MCP visibility (B-Tier2, informational — NOT blocks): the gateway
|
|
6078
|
+
// discovered a server's tool inventory (mcp-discovered) or saw an oversized
|
|
6079
|
+
// tool response that bloats the context window (mcp-large-response). Stored
|
|
6080
|
+
// AUTO_ALLOWED; carries mcpToolCount / mcpResponseBytes in riskMetadata.
|
|
6081
|
+
"mcp-discovered",
|
|
6082
|
+
"mcp-large-response"
|
|
6083
|
+
]);
|
|
5999
6084
|
}
|
|
6000
6085
|
});
|
|
6001
6086
|
|
|
@@ -9252,1113 +9337,1139 @@ var init_setup = __esm({
|
|
|
9252
9337
|
}
|
|
9253
9338
|
});
|
|
9254
9339
|
|
|
9255
|
-
// src/
|
|
9256
|
-
|
|
9257
|
-
|
|
9340
|
+
// src/pricing/litellm.ts
|
|
9341
|
+
import fs14 from "fs";
|
|
9342
|
+
import path16 from "path";
|
|
9343
|
+
import os13 from "os";
|
|
9344
|
+
function normalizeModel(raw) {
|
|
9345
|
+
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
9258
9346
|
}
|
|
9259
|
-
function
|
|
9260
|
-
|
|
9347
|
+
function readCache() {
|
|
9348
|
+
try {
|
|
9349
|
+
const raw = JSON.parse(fs14.readFileSync(CACHE_FILE(), "utf-8"));
|
|
9350
|
+
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
9351
|
+
return null;
|
|
9352
|
+
}
|
|
9353
|
+
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
9354
|
+
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
9355
|
+
return raw.prices;
|
|
9356
|
+
} catch {
|
|
9357
|
+
return null;
|
|
9358
|
+
}
|
|
9261
9359
|
}
|
|
9262
|
-
function
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
|
|
9271
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9360
|
+
function writeCache(prices) {
|
|
9361
|
+
try {
|
|
9362
|
+
const target = CACHE_FILE();
|
|
9363
|
+
const dir = path16.dirname(target);
|
|
9364
|
+
if (!fs14.existsSync(dir)) fs14.mkdirSync(dir, { recursive: true });
|
|
9365
|
+
const tmp = target + ".tmp";
|
|
9366
|
+
const body = {
|
|
9367
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9368
|
+
prices
|
|
9369
|
+
};
|
|
9370
|
+
fs14.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
9371
|
+
fs14.renameSync(tmp, target);
|
|
9372
|
+
} catch (err2) {
|
|
9373
|
+
try {
|
|
9374
|
+
fs14.appendFileSync(
|
|
9375
|
+
HOOK_DEBUG_LOG,
|
|
9376
|
+
`[pricing] cache write failed: ${err2.message}
|
|
9377
|
+
`
|
|
9378
|
+
);
|
|
9379
|
+
} catch {
|
|
9380
|
+
}
|
|
9280
9381
|
}
|
|
9281
9382
|
}
|
|
9282
|
-
function
|
|
9283
|
-
if (typeof
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9383
|
+
function tupleFromLiteLLM(entry) {
|
|
9384
|
+
if (!entry || typeof entry !== "object") return null;
|
|
9385
|
+
const e = entry;
|
|
9386
|
+
const num3 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
9387
|
+
const inCost = num3(e.input_cost_per_token);
|
|
9388
|
+
const outCost = num3(e.output_cost_per_token);
|
|
9389
|
+
if (inCost === 0 && outCost === 0) return null;
|
|
9390
|
+
return [
|
|
9391
|
+
inCost,
|
|
9392
|
+
outCost,
|
|
9393
|
+
num3(e.cache_creation_input_token_cost),
|
|
9394
|
+
num3(e.cache_read_input_token_cost)
|
|
9395
|
+
];
|
|
9396
|
+
}
|
|
9397
|
+
async function fetchLiteLLMPricing() {
|
|
9398
|
+
try {
|
|
9399
|
+
const res = await fetch(LITELLM_URL, {
|
|
9400
|
+
signal: AbortSignal.timeout(15e3)
|
|
9401
|
+
});
|
|
9402
|
+
if (!res.ok) return null;
|
|
9403
|
+
const json = await res.json();
|
|
9404
|
+
if (!json || typeof json !== "object") return null;
|
|
9405
|
+
const out = {};
|
|
9406
|
+
for (const [key, value] of Object.entries(json)) {
|
|
9407
|
+
const tuple = tupleFromLiteLLM(value);
|
|
9408
|
+
if (tuple) out[key.toLowerCase()] = tuple;
|
|
9409
|
+
}
|
|
9410
|
+
if (Object.keys(out).length < 10) {
|
|
9411
|
+
return null;
|
|
9412
|
+
}
|
|
9413
|
+
return out;
|
|
9414
|
+
} catch {
|
|
9415
|
+
return null;
|
|
9292
9416
|
}
|
|
9293
9417
|
}
|
|
9294
|
-
function
|
|
9295
|
-
if (
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
|
|
9418
|
+
async function ensurePricingLoaded() {
|
|
9419
|
+
if (memCache !== null && Date.now() - memCacheAt < TTL_MS) return;
|
|
9420
|
+
const fromDisk = readCache();
|
|
9421
|
+
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
9422
|
+
memCache = fromDisk;
|
|
9423
|
+
memCacheAt = Date.now();
|
|
9424
|
+
lookupCache.clear();
|
|
9425
|
+
return;
|
|
9426
|
+
}
|
|
9427
|
+
const fetched = await fetchLiteLLMPricing();
|
|
9428
|
+
if (fetched && Object.keys(fetched).length > 0) {
|
|
9429
|
+
memCache = fetched;
|
|
9430
|
+
memCacheAt = Date.now();
|
|
9431
|
+
writeCache(fetched);
|
|
9432
|
+
lookupCache.clear();
|
|
9433
|
+
return;
|
|
9434
|
+
}
|
|
9435
|
+
memCache = { ...BUNDLED_PRICING };
|
|
9436
|
+
memCacheAt = Date.now();
|
|
9437
|
+
lookupCache.clear();
|
|
9303
9438
|
}
|
|
9304
|
-
|
|
9305
|
-
|
|
9439
|
+
function pricingFor(model) {
|
|
9440
|
+
const norm = normalizeModel(model);
|
|
9441
|
+
const cached = lookupCache.get(norm);
|
|
9442
|
+
if (cached !== void 0) return cached;
|
|
9443
|
+
if (memCache === null && !diskChecked) {
|
|
9444
|
+
diskChecked = true;
|
|
9445
|
+
const disk = readCache();
|
|
9446
|
+
if (disk && Object.keys(disk).length > 0) {
|
|
9447
|
+
memCache = disk;
|
|
9448
|
+
memCacheAt = Date.now();
|
|
9449
|
+
}
|
|
9450
|
+
}
|
|
9451
|
+
const sources = [];
|
|
9452
|
+
if (memCache) sources.push(memCache);
|
|
9453
|
+
sources.push(BUNDLED_PRICING);
|
|
9454
|
+
let resolved = null;
|
|
9455
|
+
for (const source of sources) {
|
|
9456
|
+
const exact = source[norm];
|
|
9457
|
+
if (exact) {
|
|
9458
|
+
resolved = exact;
|
|
9459
|
+
break;
|
|
9460
|
+
}
|
|
9461
|
+
let best = null;
|
|
9462
|
+
for (const key of Object.keys(source)) {
|
|
9463
|
+
if (norm.startsWith(key.toLowerCase()) && (best === null || key.length > best.length)) {
|
|
9464
|
+
best = key;
|
|
9465
|
+
}
|
|
9466
|
+
}
|
|
9467
|
+
if (best) {
|
|
9468
|
+
resolved = source[best];
|
|
9469
|
+
break;
|
|
9470
|
+
}
|
|
9471
|
+
}
|
|
9472
|
+
lookupCache.set(norm, resolved);
|
|
9473
|
+
return resolved;
|
|
9474
|
+
}
|
|
9475
|
+
var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
|
|
9476
|
+
var init_litellm = __esm({
|
|
9477
|
+
"src/pricing/litellm.ts"() {
|
|
9306
9478
|
"use strict";
|
|
9479
|
+
init_audit();
|
|
9480
|
+
LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
9481
|
+
BUNDLED_PRICING = {
|
|
9482
|
+
// Anthropic
|
|
9483
|
+
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9484
|
+
"claude-opus-4-1": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9485
|
+
"claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9486
|
+
"claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9487
|
+
"claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
9488
|
+
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9489
|
+
"claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9490
|
+
"claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9491
|
+
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9492
|
+
"claude-haiku-4-5": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9493
|
+
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9494
|
+
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
9495
|
+
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
9496
|
+
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
9497
|
+
// OpenAI. gpt-5 family + o-series copied from the live LiteLLM table
|
|
9498
|
+
// (verified 2026-06-14) — the bundled gpt-5 was stale at $10/$30 vs the real
|
|
9499
|
+
// $1.25/$10, and Codex models (gpt-5-codex etc.) were absent, so the offline
|
|
9500
|
+
// fallback mispriced every Codex session. See cost-codex.codexPriceFor.
|
|
9501
|
+
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
9502
|
+
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
9503
|
+
"gpt-5": [125e-8, 1e-5, 0, 125e-9],
|
|
9504
|
+
"gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
9505
|
+
"gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
9506
|
+
o3: [2e-6, 8e-6, 0, 5e-7],
|
|
9507
|
+
"o4-mini": [11e-7, 44e-7, 0, 275e-9],
|
|
9508
|
+
// Google. Values copied from the live LiteLLM table (verified 2026-06-14)
|
|
9509
|
+
// so the bundled fallback prices the current Gemini tiers correctly offline
|
|
9510
|
+
// — the local cost readers were carrying a stale hardcoded copy where
|
|
9511
|
+
// gemini-2.5-flash read $0.15/$0.60 vs the real $0.30/$2.50 (~4× under on
|
|
9512
|
+
// output). See cost-gemini.geminiPriceFor (the single Gemini price source).
|
|
9513
|
+
"gemini-2.5-pro": [125e-8, 1e-5, 0, 125e-9],
|
|
9514
|
+
"gemini-2.5-flash": [3e-7, 25e-7, 0, 3e-8],
|
|
9515
|
+
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
9516
|
+
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
9517
|
+
};
|
|
9518
|
+
CACHE_FILE = () => path16.join(os13.homedir(), ".node9", "model-pricing.json");
|
|
9519
|
+
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9520
|
+
memCache = null;
|
|
9521
|
+
memCacheAt = 0;
|
|
9522
|
+
diskChecked = false;
|
|
9523
|
+
lookupCache = /* @__PURE__ */ new Map();
|
|
9307
9524
|
}
|
|
9308
9525
|
});
|
|
9309
9526
|
|
|
9310
|
-
// src/
|
|
9311
|
-
|
|
9312
|
-
|
|
9527
|
+
// src/cost-gemini.ts
|
|
9528
|
+
import fs15 from "fs";
|
|
9529
|
+
import os14 from "os";
|
|
9530
|
+
import path17 from "path";
|
|
9531
|
+
function geminiTmpDir() {
|
|
9532
|
+
return path17.join(os14.homedir(), ".gemini", "tmp");
|
|
9313
9533
|
}
|
|
9314
|
-
function
|
|
9315
|
-
|
|
9534
|
+
function geminiPriceFor(model) {
|
|
9535
|
+
let tuple = pricingFor(model);
|
|
9536
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
9537
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
9538
|
+
tuple = pricingFor(proxy);
|
|
9539
|
+
if (tuple) break;
|
|
9540
|
+
}
|
|
9541
|
+
}
|
|
9542
|
+
if (!tuple) return null;
|
|
9543
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
9316
9544
|
}
|
|
9317
|
-
function
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
return "magenta";
|
|
9323
|
-
case "antigravity":
|
|
9324
|
-
return "yellow";
|
|
9325
|
-
case "copilot":
|
|
9326
|
-
return "green";
|
|
9327
|
-
case "shell":
|
|
9328
|
-
return "yellow";
|
|
9329
|
-
default:
|
|
9330
|
-
return "cyan";
|
|
9545
|
+
function safeReaddir(dir) {
|
|
9546
|
+
try {
|
|
9547
|
+
return fs15.readdirSync(dir);
|
|
9548
|
+
} catch {
|
|
9549
|
+
return [];
|
|
9331
9550
|
}
|
|
9332
9551
|
}
|
|
9333
|
-
function
|
|
9334
|
-
const stats = {
|
|
9335
|
-
sessions: 0,
|
|
9336
|
-
totalToolCalls: 0,
|
|
9337
|
-
bashCalls: 0,
|
|
9338
|
-
totalCostUSD: 0,
|
|
9339
|
-
firstDate: null,
|
|
9340
|
-
lastDate: null
|
|
9341
|
-
};
|
|
9342
|
-
for (const a of agents) {
|
|
9343
|
-
stats.sessions += a.scan.sessions;
|
|
9344
|
-
stats.totalToolCalls += a.scan.totalToolCalls;
|
|
9345
|
-
stats.bashCalls += a.scan.bashCalls;
|
|
9346
|
-
stats.totalCostUSD += a.scan.totalCostUSD;
|
|
9347
|
-
if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
|
|
9348
|
-
stats.firstDate = a.scan.firstDate;
|
|
9349
|
-
}
|
|
9350
|
-
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9351
|
-
stats.lastDate = a.scan.lastDate;
|
|
9352
|
-
}
|
|
9353
|
-
}
|
|
9354
|
-
const allFindings = agents.flatMap((a) => a.scan.findings);
|
|
9355
|
-
const allLeaks = agents.flatMap(
|
|
9356
|
-
(a) => a.scan.dlpFindings.map((f) => ({
|
|
9357
|
-
patternName: f.patternName,
|
|
9358
|
-
redactedSample: f.redactedSample,
|
|
9359
|
-
toolName: f.toolName,
|
|
9360
|
-
timestamp: f.timestamp,
|
|
9361
|
-
project: f.project,
|
|
9362
|
-
sessionId: f.sessionId,
|
|
9363
|
-
agent: f.agent
|
|
9364
|
-
}))
|
|
9365
|
-
);
|
|
9366
|
-
const allLoops = agents.flatMap(
|
|
9367
|
-
(a) => a.scan.loopFindings.map((f) => ({
|
|
9368
|
-
toolName: f.toolName,
|
|
9369
|
-
commandPreview: f.commandPreview,
|
|
9370
|
-
count: f.count,
|
|
9371
|
-
timestamp: f.timestamp,
|
|
9372
|
-
project: f.project,
|
|
9373
|
-
sessionId: f.sessionId,
|
|
9374
|
-
agent: f.agent,
|
|
9375
|
-
kind: f.kind
|
|
9376
|
-
}))
|
|
9377
|
-
);
|
|
9378
|
-
const byVerdict = {
|
|
9379
|
-
blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
|
|
9380
|
-
supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
|
|
9381
|
-
leaks: allLeaks.length,
|
|
9382
|
-
loops: allLoops.length
|
|
9383
|
-
};
|
|
9384
|
-
const byAgent = agents.map((a) => ({
|
|
9385
|
-
id: a.id,
|
|
9386
|
-
label: a.label,
|
|
9387
|
-
icon: a.icon,
|
|
9388
|
-
sessions: a.scan.sessions,
|
|
9389
|
-
findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
|
|
9390
|
-
costUSD: a.scan.totalCostUSD
|
|
9391
|
-
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
9392
|
-
const sections = buildSections(allFindings);
|
|
9393
|
-
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
9394
|
-
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
9395
|
-
return {
|
|
9396
|
-
stats,
|
|
9397
|
-
byVerdict,
|
|
9398
|
-
byAgent,
|
|
9399
|
-
sections,
|
|
9400
|
-
leaks: allLeaks,
|
|
9401
|
-
loops: allLoops,
|
|
9402
|
-
loopWastedUSD
|
|
9403
|
-
};
|
|
9404
|
-
}
|
|
9405
|
-
function buildSections(findings) {
|
|
9406
|
-
const sectionMap = /* @__PURE__ */ new Map();
|
|
9407
|
-
function ensureSection(id, label, subtitle, sourceType, shieldKey) {
|
|
9408
|
-
let s = sectionMap.get(id);
|
|
9409
|
-
if (!s) {
|
|
9410
|
-
s = {
|
|
9411
|
-
id,
|
|
9412
|
-
label,
|
|
9413
|
-
subtitle,
|
|
9414
|
-
sourceType,
|
|
9415
|
-
shieldKey,
|
|
9416
|
-
blockedCount: 0,
|
|
9417
|
-
reviewCount: 0,
|
|
9418
|
-
rules: []
|
|
9419
|
-
};
|
|
9420
|
-
sectionMap.set(id, s);
|
|
9421
|
-
}
|
|
9422
|
-
return s;
|
|
9423
|
-
}
|
|
9424
|
-
const ruleMap = /* @__PURE__ */ new Map();
|
|
9425
|
-
for (const f of findings) {
|
|
9426
|
-
const src = f.source;
|
|
9427
|
-
const sourceType = src.sourceType;
|
|
9428
|
-
const shieldName = src.shieldName;
|
|
9429
|
-
const verdict = src.rule.verdict === "block" ? "block" : "review";
|
|
9430
|
-
let sectionId;
|
|
9431
|
-
let sectionLabel;
|
|
9432
|
-
let sectionSubtitle;
|
|
9433
|
-
let shieldKey;
|
|
9434
|
-
if (sourceType === "default") {
|
|
9435
|
-
sectionId = "default";
|
|
9436
|
-
sectionLabel = "Default Rules";
|
|
9437
|
-
sectionSubtitle = "built-in, always on";
|
|
9438
|
-
} else if (sourceType === "shield") {
|
|
9439
|
-
sectionId = `shield:${shieldName}`;
|
|
9440
|
-
sectionLabel = shieldName;
|
|
9441
|
-
sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
|
|
9442
|
-
shieldKey = shieldName;
|
|
9443
|
-
} else if (shieldName === "cloud") {
|
|
9444
|
-
sectionId = "cloud";
|
|
9445
|
-
sectionLabel = "Cloud Policy";
|
|
9446
|
-
sectionSubtitle = "synced from node9 cloud";
|
|
9447
|
-
} else {
|
|
9448
|
-
sectionId = "user";
|
|
9449
|
-
sectionLabel = "Your Rules";
|
|
9450
|
-
sectionSubtitle = "added in node9.config.json";
|
|
9451
|
-
}
|
|
9452
|
-
const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
|
|
9453
|
-
const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
|
|
9454
|
-
const ruleKey = sectionId + "::" + ruleDisplayName;
|
|
9455
|
-
let rule = ruleMap.get(ruleKey);
|
|
9456
|
-
if (!rule) {
|
|
9457
|
-
rule = {
|
|
9458
|
-
name: ruleDisplayName,
|
|
9459
|
-
verdict,
|
|
9460
|
-
reason: src.rule.reason ?? "",
|
|
9461
|
-
findings: []
|
|
9462
|
-
};
|
|
9463
|
-
ruleMap.set(ruleKey, rule);
|
|
9464
|
-
section.rules.push(rule);
|
|
9465
|
-
}
|
|
9466
|
-
const cmdPreview = previewCommand(f.input, 120);
|
|
9467
|
-
const fullCmd = fullCommandOf(f.input);
|
|
9468
|
-
const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
|
|
9469
|
-
if (!isDupe) {
|
|
9470
|
-
rule.findings.push({
|
|
9471
|
-
timestamp: f.timestamp ?? "",
|
|
9472
|
-
command: cmdPreview,
|
|
9473
|
-
fullCommand: fullCmd,
|
|
9474
|
-
project: f.project,
|
|
9475
|
-
sessionId: f.sessionId,
|
|
9476
|
-
agent: f.agent,
|
|
9477
|
-
toolName: f.toolName
|
|
9478
|
-
});
|
|
9479
|
-
}
|
|
9480
|
-
if (verdict === "block") section.blockedCount++;
|
|
9481
|
-
else section.reviewCount++;
|
|
9482
|
-
}
|
|
9483
|
-
const sections = [...sectionMap.values()];
|
|
9484
|
-
sections.sort((a, b) => {
|
|
9485
|
-
const aTotal = a.blockedCount + a.reviewCount;
|
|
9486
|
-
const bTotal = b.blockedCount + b.reviewCount;
|
|
9487
|
-
if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
|
|
9488
|
-
return bTotal - aTotal;
|
|
9489
|
-
});
|
|
9490
|
-
for (const s of sections) {
|
|
9491
|
-
s.rules.sort((a, b) => {
|
|
9492
|
-
const aBlock = a.verdict === "block" ? 1 : 0;
|
|
9493
|
-
const bBlock = b.verdict === "block" ? 1 : 0;
|
|
9494
|
-
if (bBlock !== aBlock) return bBlock - aBlock;
|
|
9495
|
-
return b.findings.length - a.findings.length;
|
|
9496
|
-
});
|
|
9497
|
-
}
|
|
9498
|
-
return sections;
|
|
9499
|
-
}
|
|
9500
|
-
function previewCommand(input, max) {
|
|
9501
|
-
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9502
|
-
const s = String(raw).replace(/\s+/g, " ").trim();
|
|
9503
|
-
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9504
|
-
}
|
|
9505
|
-
function fullCommandOf(input) {
|
|
9506
|
-
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
9507
|
-
return String(raw).replace(/\s+/g, " ").trim();
|
|
9508
|
-
}
|
|
9509
|
-
var AGENT_SHORT, AGENT_LONG;
|
|
9510
|
-
var init_scan_summary = __esm({
|
|
9511
|
-
"src/scan-summary.ts"() {
|
|
9512
|
-
"use strict";
|
|
9513
|
-
init_shields();
|
|
9514
|
-
init_dist();
|
|
9515
|
-
init_dist();
|
|
9516
|
-
AGENT_SHORT = {
|
|
9517
|
-
claude: "Claude",
|
|
9518
|
-
gemini: "Gemini",
|
|
9519
|
-
codex: "Codex",
|
|
9520
|
-
antigravity: "Agy",
|
|
9521
|
-
copilot: "Copilot",
|
|
9522
|
-
shell: "Shell"
|
|
9523
|
-
};
|
|
9524
|
-
AGENT_LONG = {
|
|
9525
|
-
claude: "Claude Code",
|
|
9526
|
-
gemini: "Gemini CLI",
|
|
9527
|
-
codex: "Codex",
|
|
9528
|
-
antigravity: "Antigravity",
|
|
9529
|
-
copilot: "GitHub Copilot",
|
|
9530
|
-
shell: "Shell"
|
|
9531
|
-
};
|
|
9532
|
-
}
|
|
9533
|
-
});
|
|
9534
|
-
|
|
9535
|
-
// src/cli/commands/blast.ts
|
|
9536
|
-
import chalk2 from "chalk";
|
|
9537
|
-
import fs14 from "fs";
|
|
9538
|
-
import path16 from "path";
|
|
9539
|
-
import os13 from "os";
|
|
9540
|
-
function buildSensitivePaths(home, cwd) {
|
|
9541
|
-
return [
|
|
9542
|
-
{
|
|
9543
|
-
full: path16.join(home, ".ssh", "id_rsa"),
|
|
9544
|
-
label: "~/.ssh/id_rsa",
|
|
9545
|
-
description: "RSA private key \u2014 grants SSH access to your servers",
|
|
9546
|
-
score: 20
|
|
9547
|
-
},
|
|
9548
|
-
{
|
|
9549
|
-
full: path16.join(home, ".ssh", "id_ed25519"),
|
|
9550
|
-
label: "~/.ssh/id_ed25519",
|
|
9551
|
-
description: "Ed25519 private key \u2014 grants SSH access to your servers",
|
|
9552
|
-
score: 20
|
|
9553
|
-
},
|
|
9554
|
-
{
|
|
9555
|
-
full: path16.join(home, ".ssh", "id_ecdsa"),
|
|
9556
|
-
label: "~/.ssh/id_ecdsa",
|
|
9557
|
-
description: "ECDSA private key \u2014 grants SSH access to your servers",
|
|
9558
|
-
score: 20
|
|
9559
|
-
},
|
|
9560
|
-
{
|
|
9561
|
-
full: path16.join(home, ".aws", "credentials"),
|
|
9562
|
-
label: "~/.aws/credentials",
|
|
9563
|
-
description: "AWS access keys \u2014 full cloud account access",
|
|
9564
|
-
score: 20
|
|
9565
|
-
},
|
|
9566
|
-
{
|
|
9567
|
-
full: path16.join(home, ".aws", "config"),
|
|
9568
|
-
label: "~/.aws/config",
|
|
9569
|
-
description: "AWS configuration \u2014 account and region settings",
|
|
9570
|
-
score: 5
|
|
9571
|
-
},
|
|
9572
|
-
{
|
|
9573
|
-
full: path16.join(home, ".config", "gcloud", "credentials.db"),
|
|
9574
|
-
label: "~/.config/gcloud/credentials.db",
|
|
9575
|
-
description: "Google Cloud credentials",
|
|
9576
|
-
score: 15
|
|
9577
|
-
},
|
|
9578
|
-
{
|
|
9579
|
-
full: path16.join(home, ".docker", "config.json"),
|
|
9580
|
-
label: "~/.docker/config.json",
|
|
9581
|
-
description: "Docker registry auth tokens",
|
|
9582
|
-
score: 10
|
|
9583
|
-
},
|
|
9584
|
-
{
|
|
9585
|
-
full: path16.join(home, ".netrc"),
|
|
9586
|
-
label: "~/.netrc",
|
|
9587
|
-
description: "FTP/HTTP credentials in plain text",
|
|
9588
|
-
score: 15
|
|
9589
|
-
},
|
|
9590
|
-
{
|
|
9591
|
-
full: path16.join(home, ".npmrc"),
|
|
9592
|
-
label: "~/.npmrc",
|
|
9593
|
-
description: "npm auth token \u2014 can publish packages as you",
|
|
9594
|
-
score: 10
|
|
9595
|
-
},
|
|
9596
|
-
{
|
|
9597
|
-
full: path16.join(home, ".node9", "credentials.json"),
|
|
9598
|
-
label: "~/.node9/credentials.json",
|
|
9599
|
-
description: "Node9 cloud API key",
|
|
9600
|
-
score: 10
|
|
9601
|
-
},
|
|
9602
|
-
{
|
|
9603
|
-
full: path16.join(cwd, ".env"),
|
|
9604
|
-
label: ".env (current folder)",
|
|
9605
|
-
description: "App secrets \u2014 database passwords, API keys",
|
|
9606
|
-
score: 20
|
|
9607
|
-
},
|
|
9608
|
-
{
|
|
9609
|
-
full: path16.join(cwd, ".env.local"),
|
|
9610
|
-
label: ".env.local (current folder)",
|
|
9611
|
-
description: "Local overrides \u2014 often contains real credentials",
|
|
9612
|
-
score: 15
|
|
9613
|
-
},
|
|
9614
|
-
{
|
|
9615
|
-
full: path16.join(cwd, ".env.production"),
|
|
9616
|
-
label: ".env.production (current folder)",
|
|
9617
|
-
description: "Production secrets",
|
|
9618
|
-
score: 20
|
|
9619
|
-
}
|
|
9620
|
-
];
|
|
9621
|
-
}
|
|
9622
|
-
function isReadable(filePath) {
|
|
9552
|
+
function isDir(p) {
|
|
9623
9553
|
try {
|
|
9624
|
-
|
|
9625
|
-
return true;
|
|
9554
|
+
return fs15.statSync(p).isDirectory();
|
|
9626
9555
|
} catch {
|
|
9627
9556
|
return false;
|
|
9628
9557
|
}
|
|
9629
9558
|
}
|
|
9630
|
-
function
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
const paths = buildSensitivePaths(home, cwd);
|
|
9640
|
-
let scoreDeduction = 0;
|
|
9641
|
-
const reachable = [];
|
|
9642
|
-
for (const p of paths) {
|
|
9643
|
-
if (fs14.existsSync(p.full) && isReadable(p.full)) {
|
|
9644
|
-
reachable.push(p);
|
|
9645
|
-
scoreDeduction += p.score;
|
|
9646
|
-
}
|
|
9647
|
-
}
|
|
9648
|
-
const envFindings = [];
|
|
9649
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
9650
|
-
if (!value) continue;
|
|
9651
|
-
const match = scanArgs({ [key]: value });
|
|
9652
|
-
if (match) {
|
|
9653
|
-
envFindings.push({ key, patternName: match.patternName });
|
|
9654
|
-
scoreDeduction += 10;
|
|
9559
|
+
function listGeminiSessionFiles(base) {
|
|
9560
|
+
const out = [];
|
|
9561
|
+
for (const project of safeReaddir(base)) {
|
|
9562
|
+
const chats = path17.join(base, project, "chats");
|
|
9563
|
+
if (!isDir(chats)) continue;
|
|
9564
|
+
for (const f of safeReaddir(chats)) {
|
|
9565
|
+
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
9566
|
+
out.push({ file: path17.join(chats, f), project });
|
|
9567
|
+
}
|
|
9655
9568
|
}
|
|
9656
9569
|
}
|
|
9657
|
-
return
|
|
9570
|
+
return out;
|
|
9658
9571
|
}
|
|
9659
|
-
function
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
if (reachable.length > 0) {
|
|
9671
|
-
console.log(" " + chalk2.red.bold("Sensitive files reachable:"));
|
|
9672
|
-
for (const p of reachable) {
|
|
9673
|
-
console.log(
|
|
9674
|
-
" " + chalk2.red("\u2717 ") + chalk2.yellow(p.label.padEnd(38)) + chalk2.dim(p.description)
|
|
9675
|
-
);
|
|
9676
|
-
}
|
|
9677
|
-
console.log("");
|
|
9572
|
+
function parseGeminiSession(lines, project) {
|
|
9573
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
9574
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
9575
|
+
let runId = "";
|
|
9576
|
+
for (const raw of lines) {
|
|
9577
|
+
if (!raw.trim()) continue;
|
|
9578
|
+
let obj;
|
|
9579
|
+
try {
|
|
9580
|
+
obj = JSON.parse(raw);
|
|
9581
|
+
} catch {
|
|
9582
|
+
continue;
|
|
9678
9583
|
}
|
|
9679
|
-
if (
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
);
|
|
9685
|
-
}
|
|
9686
|
-
console.log("");
|
|
9584
|
+
if (typeof obj.sessionId === "string" && !runId) runId = obj.sessionId;
|
|
9585
|
+
if (!obj.tokens || !obj.model || !obj.timestamp) continue;
|
|
9586
|
+
if (obj.id) {
|
|
9587
|
+
if (seenIds.has(obj.id)) continue;
|
|
9588
|
+
seenIds.add(obj.id);
|
|
9687
9589
|
}
|
|
9688
|
-
|
|
9689
|
-
if (
|
|
9690
|
-
|
|
9691
|
-
|
|
9590
|
+
const price = geminiPriceFor(obj.model);
|
|
9591
|
+
if (!price) continue;
|
|
9592
|
+
const inp = obj.tokens.input ?? 0;
|
|
9593
|
+
const out = obj.tokens.output ?? 0;
|
|
9594
|
+
const cached = Math.min(obj.tokens.cached ?? 0, inp);
|
|
9595
|
+
const fresh = Math.max(0, inp - cached);
|
|
9596
|
+
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
9597
|
+
const date = obj.timestamp.slice(0, 10);
|
|
9598
|
+
const model = normalizeModel(obj.model);
|
|
9599
|
+
const key = `${date}::${model}`;
|
|
9600
|
+
const prev = byKey.get(key);
|
|
9601
|
+
if (prev) {
|
|
9602
|
+
prev.costUSD += cost;
|
|
9603
|
+
prev.inputTokens += fresh;
|
|
9604
|
+
prev.outputTokens += out;
|
|
9605
|
+
prev.cacheReadTokens += cached;
|
|
9692
9606
|
} else {
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
);
|
|
9607
|
+
byKey.set(key, {
|
|
9608
|
+
date,
|
|
9609
|
+
model,
|
|
9610
|
+
workingDir: project,
|
|
9611
|
+
runId,
|
|
9612
|
+
costUSD: cost,
|
|
9613
|
+
inputTokens: fresh,
|
|
9614
|
+
outputTokens: out,
|
|
9615
|
+
cacheReadTokens: cached,
|
|
9616
|
+
cacheWriteTokens: 0
|
|
9617
|
+
});
|
|
9704
9618
|
}
|
|
9705
|
-
|
|
9706
|
-
|
|
9619
|
+
}
|
|
9620
|
+
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
9621
|
+
return [...byKey.values()];
|
|
9707
9622
|
}
|
|
9708
|
-
var
|
|
9709
|
-
|
|
9623
|
+
var GEMINI_FALLBACK_MODELS, geminiSource;
|
|
9624
|
+
var init_cost_gemini = __esm({
|
|
9625
|
+
"src/cost-gemini.ts"() {
|
|
9710
9626
|
"use strict";
|
|
9711
|
-
|
|
9627
|
+
init_litellm();
|
|
9628
|
+
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
9629
|
+
geminiSource = {
|
|
9630
|
+
id: "gemini",
|
|
9631
|
+
available() {
|
|
9632
|
+
try {
|
|
9633
|
+
return fs15.existsSync(geminiTmpDir());
|
|
9634
|
+
} catch {
|
|
9635
|
+
return false;
|
|
9636
|
+
}
|
|
9637
|
+
},
|
|
9638
|
+
collect(sinceMs) {
|
|
9639
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9640
|
+
for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
9641
|
+
try {
|
|
9642
|
+
if (sinceMs !== void 0 && fs15.statSync(file).mtimeMs < sinceMs) continue;
|
|
9643
|
+
} catch {
|
|
9644
|
+
continue;
|
|
9645
|
+
}
|
|
9646
|
+
let content;
|
|
9647
|
+
try {
|
|
9648
|
+
content = fs15.readFileSync(file, "utf8");
|
|
9649
|
+
} catch {
|
|
9650
|
+
continue;
|
|
9651
|
+
}
|
|
9652
|
+
for (const e of parseGeminiSession(content.split("\n"), project)) {
|
|
9653
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9654
|
+
const prev = combined.get(key);
|
|
9655
|
+
if (prev) {
|
|
9656
|
+
prev.costUSD += e.costUSD;
|
|
9657
|
+
prev.inputTokens += e.inputTokens;
|
|
9658
|
+
prev.outputTokens += e.outputTokens;
|
|
9659
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9660
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9661
|
+
} else {
|
|
9662
|
+
combined.set(key, { ...e });
|
|
9663
|
+
}
|
|
9664
|
+
}
|
|
9665
|
+
}
|
|
9666
|
+
return [...combined.values()];
|
|
9667
|
+
}
|
|
9668
|
+
};
|
|
9712
9669
|
}
|
|
9713
9670
|
});
|
|
9714
9671
|
|
|
9715
|
-
// src/
|
|
9716
|
-
import
|
|
9717
|
-
import
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
return { band: "critical", label: "Critical", color: chalk3.red };
|
|
9722
|
-
}
|
|
9723
|
-
function topDlpPatterns(findings, n) {
|
|
9724
|
-
const counts = /* @__PURE__ */ new Map();
|
|
9725
|
-
for (const f of findings) {
|
|
9726
|
-
counts.set(f.patternName, (counts.get(f.patternName) ?? 0) + 1);
|
|
9727
|
-
}
|
|
9728
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name, count]) => ({ name, count }));
|
|
9672
|
+
// src/cost-codex.ts
|
|
9673
|
+
import fs16 from "fs";
|
|
9674
|
+
import os15 from "os";
|
|
9675
|
+
import path18 from "path";
|
|
9676
|
+
function codexSessionsDir() {
|
|
9677
|
+
return path18.join(os15.homedir(), ".codex", "sessions");
|
|
9729
9678
|
}
|
|
9730
|
-
function
|
|
9731
|
-
|
|
9732
|
-
for (const section of sections) {
|
|
9733
|
-
for (const rule of section.rules) {
|
|
9734
|
-
const matches = verdict === "block" ? rule.verdict === "block" : rule.verdict !== "block";
|
|
9735
|
-
if (matches) matched.push({ name: rule.name, count: rule.findings.length });
|
|
9736
|
-
}
|
|
9737
|
-
}
|
|
9738
|
-
return matched.sort((a, b) => b.count - a.count).slice(0, n);
|
|
9679
|
+
function codexPriceFor(model) {
|
|
9680
|
+
return pricingFor(model) ?? CODEX_FALLBACK;
|
|
9739
9681
|
}
|
|
9740
|
-
function
|
|
9741
|
-
const
|
|
9742
|
-
const
|
|
9743
|
-
return
|
|
9682
|
+
function codexSessionCost(model, tokens) {
|
|
9683
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
9684
|
+
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
9685
|
+
return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
|
|
9744
9686
|
}
|
|
9745
|
-
function
|
|
9687
|
+
function listCodexSessionFiles(base) {
|
|
9746
9688
|
const out = [];
|
|
9747
|
-
for (const
|
|
9748
|
-
|
|
9749
|
-
if (!
|
|
9750
|
-
const
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9689
|
+
for (const y of safeReaddir2(base)) {
|
|
9690
|
+
const yp = path18.join(base, y);
|
|
9691
|
+
if (!isDir2(yp)) continue;
|
|
9692
|
+
for (const m of safeReaddir2(yp)) {
|
|
9693
|
+
const mp = path18.join(yp, m);
|
|
9694
|
+
if (!isDir2(mp)) continue;
|
|
9695
|
+
for (const d of safeReaddir2(mp)) {
|
|
9696
|
+
const dp = path18.join(mp, d);
|
|
9697
|
+
if (!isDir2(dp)) continue;
|
|
9698
|
+
for (const f of safeReaddir2(dp)) {
|
|
9699
|
+
if (f.endsWith(".jsonl")) out.push(path18.join(dp, f));
|
|
9700
|
+
}
|
|
9701
|
+
}
|
|
9702
|
+
}
|
|
9759
9703
|
}
|
|
9760
|
-
return out
|
|
9704
|
+
return out;
|
|
9761
9705
|
}
|
|
9762
|
-
function
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
9768
|
-
const dashFill = "\u2500".repeat(Math.max(0, inner - stringWidth(titleSegment)));
|
|
9769
|
-
out.push(chalk3.dim("\u256D\u2500") + chalk3.bold(titleSegment) + chalk3.dim(`${dashFill}\u2500\u256E`));
|
|
9770
|
-
for (const line of bodyLines) {
|
|
9771
|
-
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
9772
|
-
out.push(chalk3.dim("\u2502 ") + line.rendered + padding + chalk3.dim(" \u2502"));
|
|
9706
|
+
function safeReaddir2(dir) {
|
|
9707
|
+
try {
|
|
9708
|
+
return fs16.readdirSync(dir);
|
|
9709
|
+
} catch {
|
|
9710
|
+
return [];
|
|
9773
9711
|
}
|
|
9774
|
-
out.push(chalk3.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
9775
|
-
return out;
|
|
9776
9712
|
}
|
|
9777
|
-
function
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
return `${days}d`;
|
|
9713
|
+
function isDir2(p) {
|
|
9714
|
+
try {
|
|
9715
|
+
return fs16.statSync(p).isDirectory();
|
|
9716
|
+
} catch {
|
|
9717
|
+
return false;
|
|
9718
|
+
}
|
|
9784
9719
|
}
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
"
|
|
9788
|
-
|
|
9789
|
-
|
|
9720
|
+
function parseCodexSession(lines) {
|
|
9721
|
+
let sessionStart2 = "";
|
|
9722
|
+
let runId = "";
|
|
9723
|
+
let cwd = "";
|
|
9724
|
+
let model = "";
|
|
9725
|
+
let input = 0;
|
|
9726
|
+
let cached = 0;
|
|
9727
|
+
let output = 0;
|
|
9728
|
+
let sawUsage = false;
|
|
9729
|
+
for (const raw of lines) {
|
|
9730
|
+
if (!raw.trim()) continue;
|
|
9731
|
+
let entry;
|
|
9732
|
+
try {
|
|
9733
|
+
entry = JSON.parse(raw);
|
|
9734
|
+
} catch {
|
|
9735
|
+
continue;
|
|
9736
|
+
}
|
|
9737
|
+
const p = entry.payload ?? {};
|
|
9738
|
+
if (entry.type === "session_meta") {
|
|
9739
|
+
if (!sessionStart2 && typeof p["timestamp"] === "string") sessionStart2 = p["timestamp"];
|
|
9740
|
+
if (!runId && typeof p["id"] === "string") runId = p["id"];
|
|
9741
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9742
|
+
continue;
|
|
9743
|
+
}
|
|
9744
|
+
if (entry.type === "turn_context") {
|
|
9745
|
+
if (typeof p["model"] === "string") model = p["model"];
|
|
9746
|
+
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
9747
|
+
continue;
|
|
9748
|
+
}
|
|
9749
|
+
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
9750
|
+
const info = p["info"] ?? {};
|
|
9751
|
+
const usage = info["total_token_usage"] ?? {};
|
|
9752
|
+
if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
|
|
9753
|
+
if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
|
|
9754
|
+
if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
|
|
9755
|
+
sawUsage = true;
|
|
9756
|
+
}
|
|
9790
9757
|
}
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9758
|
+
if (!sessionStart2 || !sawUsage) return null;
|
|
9759
|
+
const nonCached = Math.max(0, input - cached);
|
|
9760
|
+
if (nonCached === 0 && output === 0 && cached === 0) return null;
|
|
9761
|
+
const norm = normalizeModel(model || "gpt-5");
|
|
9762
|
+
const costUSD = codexSessionCost(model, { input, cached, output });
|
|
9763
|
+
return {
|
|
9764
|
+
date: sessionStart2.slice(0, 10),
|
|
9765
|
+
model: norm,
|
|
9766
|
+
workingDir: cwd,
|
|
9767
|
+
runId,
|
|
9768
|
+
costUSD,
|
|
9769
|
+
inputTokens: nonCached,
|
|
9770
|
+
outputTokens: output,
|
|
9771
|
+
cacheReadTokens: cached,
|
|
9772
|
+
cacheWriteTokens: 0
|
|
9773
|
+
};
|
|
9774
|
+
}
|
|
9775
|
+
var CODEX_FALLBACK, codexSource;
|
|
9776
|
+
var init_cost_codex = __esm({
|
|
9777
|
+
"src/cost-codex.ts"() {
|
|
9797
9778
|
"use strict";
|
|
9798
|
-
|
|
9799
|
-
|
|
9779
|
+
init_litellm();
|
|
9780
|
+
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
9781
|
+
codexSource = {
|
|
9782
|
+
id: "codex",
|
|
9783
|
+
available() {
|
|
9784
|
+
try {
|
|
9785
|
+
return fs16.existsSync(codexSessionsDir());
|
|
9786
|
+
} catch {
|
|
9787
|
+
return false;
|
|
9788
|
+
}
|
|
9789
|
+
},
|
|
9790
|
+
collect(sinceMs) {
|
|
9791
|
+
const base = codexSessionsDir();
|
|
9792
|
+
const combined = /* @__PURE__ */ new Map();
|
|
9793
|
+
for (const file of listCodexSessionFiles(base)) {
|
|
9794
|
+
try {
|
|
9795
|
+
if (sinceMs !== void 0 && fs16.statSync(file).mtimeMs < sinceMs) continue;
|
|
9796
|
+
} catch {
|
|
9797
|
+
continue;
|
|
9798
|
+
}
|
|
9799
|
+
let content;
|
|
9800
|
+
try {
|
|
9801
|
+
content = fs16.readFileSync(file, "utf8");
|
|
9802
|
+
} catch {
|
|
9803
|
+
continue;
|
|
9804
|
+
}
|
|
9805
|
+
const e = parseCodexSession(content.split("\n"));
|
|
9806
|
+
if (!e) continue;
|
|
9807
|
+
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
9808
|
+
const prev = combined.get(key);
|
|
9809
|
+
if (prev) {
|
|
9810
|
+
prev.costUSD += e.costUSD;
|
|
9811
|
+
prev.inputTokens += e.inputTokens;
|
|
9812
|
+
prev.outputTokens += e.outputTokens;
|
|
9813
|
+
prev.cacheReadTokens += e.cacheReadTokens;
|
|
9814
|
+
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
9815
|
+
} else {
|
|
9816
|
+
combined.set(key, { ...e });
|
|
9817
|
+
}
|
|
9818
|
+
}
|
|
9819
|
+
return [...combined.values()];
|
|
9820
|
+
}
|
|
9800
9821
|
};
|
|
9801
9822
|
}
|
|
9802
9823
|
});
|
|
9803
9824
|
|
|
9804
|
-
// src/
|
|
9805
|
-
function
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9825
|
+
// src/utils/hook-payload.ts
|
|
9826
|
+
function extractToolName(payload, defaultValue = "") {
|
|
9827
|
+
return payload.tool_name ?? payload.name ?? payload.toolCall?.name ?? defaultValue;
|
|
9828
|
+
}
|
|
9829
|
+
function extractToolInput(payload) {
|
|
9830
|
+
return payload.tool_input ?? payload.args ?? payload.toolCall?.args ?? {};
|
|
9831
|
+
}
|
|
9832
|
+
function canonicalToolName(name) {
|
|
9833
|
+
switch (name) {
|
|
9834
|
+
// Hermes Agent
|
|
9835
|
+
case "terminal":
|
|
9836
|
+
return "Bash";
|
|
9837
|
+
case "write_file":
|
|
9838
|
+
return "Write";
|
|
9839
|
+
case "patch":
|
|
9840
|
+
return "Edit";
|
|
9841
|
+
case "read_file":
|
|
9842
|
+
return "Read";
|
|
9843
|
+
case "search_files":
|
|
9844
|
+
return "Grep";
|
|
9845
|
+
// Antigravity (agy) — shell tool renamed from Gemini's run_shell_command
|
|
9846
|
+
case "run_command":
|
|
9847
|
+
return "Bash";
|
|
9848
|
+
default:
|
|
9849
|
+
return name;
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
function agentLabelFromFlag(flag) {
|
|
9853
|
+
if (typeof flag !== "string") return void 0;
|
|
9854
|
+
switch (flag.toLowerCase()) {
|
|
9855
|
+
case "antigravity":
|
|
9856
|
+
case "agy":
|
|
9857
|
+
return "Antigravity";
|
|
9858
|
+
case "copilot":
|
|
9859
|
+
return "GitHub Copilot";
|
|
9860
|
+
default:
|
|
9861
|
+
return void 0;
|
|
9862
|
+
}
|
|
9828
9863
|
}
|
|
9829
|
-
|
|
9830
|
-
"
|
|
9864
|
+
function canonicalToolInput(rawToolName, input) {
|
|
9865
|
+
if (rawToolName !== "run_command") return input;
|
|
9866
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return input;
|
|
9867
|
+
const args = input;
|
|
9868
|
+
if (typeof args.CommandLine !== "string") return input;
|
|
9869
|
+
const { CommandLine, Cwd, ...rest } = args;
|
|
9870
|
+
const canonical = { ...rest, command: CommandLine };
|
|
9871
|
+
if (typeof Cwd === "string" && Cwd.length > 0) canonical.cwd = Cwd;
|
|
9872
|
+
return canonical;
|
|
9873
|
+
}
|
|
9874
|
+
var init_hook_payload = __esm({
|
|
9875
|
+
"src/utils/hook-payload.ts"() {
|
|
9831
9876
|
"use strict";
|
|
9832
|
-
init_scan_derive();
|
|
9833
9877
|
}
|
|
9834
9878
|
});
|
|
9835
9879
|
|
|
9836
|
-
// src/
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
import os14 from "os";
|
|
9840
|
-
function defaultHistoryPath() {
|
|
9841
|
-
return path17.join(os14.homedir(), ".node9", "scan-history.json");
|
|
9880
|
+
// src/scan-summary.ts
|
|
9881
|
+
function agentDisplayName(agent) {
|
|
9882
|
+
return AGENT_LONG[agent] ?? "Claude Code";
|
|
9842
9883
|
}
|
|
9843
|
-
function
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9884
|
+
function agentBadgeText(agent, width = 10) {
|
|
9885
|
+
return `[${AGENT_SHORT[agent] ?? "Claude"}]`.padEnd(width);
|
|
9886
|
+
}
|
|
9887
|
+
function agentColorName(agent) {
|
|
9888
|
+
switch (agent) {
|
|
9889
|
+
case "gemini":
|
|
9890
|
+
return "blue";
|
|
9891
|
+
case "codex":
|
|
9892
|
+
return "magenta";
|
|
9893
|
+
case "antigravity":
|
|
9894
|
+
return "yellow";
|
|
9895
|
+
case "copilot":
|
|
9896
|
+
return "green";
|
|
9897
|
+
case "shell":
|
|
9898
|
+
return "yellow";
|
|
9899
|
+
default:
|
|
9900
|
+
return "cyan";
|
|
9855
9901
|
}
|
|
9856
9902
|
}
|
|
9857
|
-
function
|
|
9858
|
-
const
|
|
9859
|
-
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9903
|
+
function buildScanSummary(agents) {
|
|
9904
|
+
const stats = {
|
|
9905
|
+
sessions: 0,
|
|
9906
|
+
totalToolCalls: 0,
|
|
9907
|
+
bashCalls: 0,
|
|
9908
|
+
totalCostUSD: 0,
|
|
9909
|
+
firstDate: null,
|
|
9910
|
+
lastDate: null
|
|
9911
|
+
};
|
|
9912
|
+
for (const a of agents) {
|
|
9913
|
+
stats.sessions += a.scan.sessions;
|
|
9914
|
+
stats.totalToolCalls += a.scan.totalToolCalls;
|
|
9915
|
+
stats.bashCalls += a.scan.bashCalls;
|
|
9916
|
+
stats.totalCostUSD += a.scan.totalCostUSD;
|
|
9917
|
+
if (a.scan.firstDate && (!stats.firstDate || a.scan.firstDate < stats.firstDate)) {
|
|
9918
|
+
stats.firstDate = a.scan.firstDate;
|
|
9871
9919
|
}
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
history = history.slice(history.length - cap);
|
|
9920
|
+
if (a.scan.lastDate && (!stats.lastDate || a.scan.lastDate > stats.lastDate)) {
|
|
9921
|
+
stats.lastDate = a.scan.lastDate;
|
|
9875
9922
|
}
|
|
9876
|
-
fs15.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
9877
|
-
} catch (err2) {
|
|
9878
|
-
process.stderr.write(
|
|
9879
|
-
`[node9] Warning: could not write scan-history.json: ${err2.message}
|
|
9880
|
-
`
|
|
9881
|
-
);
|
|
9882
9923
|
}
|
|
9924
|
+
const allFindings = agents.flatMap((a) => a.scan.findings);
|
|
9925
|
+
const allLeaks = agents.flatMap(
|
|
9926
|
+
(a) => a.scan.dlpFindings.map((f) => ({
|
|
9927
|
+
patternName: f.patternName,
|
|
9928
|
+
redactedSample: f.redactedSample,
|
|
9929
|
+
toolName: f.toolName,
|
|
9930
|
+
timestamp: f.timestamp,
|
|
9931
|
+
project: f.project,
|
|
9932
|
+
sessionId: f.sessionId,
|
|
9933
|
+
agent: f.agent
|
|
9934
|
+
}))
|
|
9935
|
+
);
|
|
9936
|
+
const allLoops = agents.flatMap(
|
|
9937
|
+
(a) => a.scan.loopFindings.map((f) => ({
|
|
9938
|
+
toolName: f.toolName,
|
|
9939
|
+
commandPreview: f.commandPreview,
|
|
9940
|
+
count: f.count,
|
|
9941
|
+
timestamp: f.timestamp,
|
|
9942
|
+
project: f.project,
|
|
9943
|
+
sessionId: f.sessionId,
|
|
9944
|
+
agent: f.agent,
|
|
9945
|
+
kind: f.kind
|
|
9946
|
+
}))
|
|
9947
|
+
);
|
|
9948
|
+
const byVerdict = {
|
|
9949
|
+
blocked: allFindings.filter((f) => f.source.rule.verdict === "block").length,
|
|
9950
|
+
supervised: allFindings.filter((f) => f.source.rule.verdict === "review").length,
|
|
9951
|
+
leaks: allLeaks.length,
|
|
9952
|
+
loops: allLoops.length
|
|
9953
|
+
};
|
|
9954
|
+
const byAgent = agents.map((a) => ({
|
|
9955
|
+
id: a.id,
|
|
9956
|
+
label: a.label,
|
|
9957
|
+
icon: a.icon,
|
|
9958
|
+
sessions: a.scan.sessions,
|
|
9959
|
+
findings: a.scan.findings.length + a.scan.dlpFindings.length + a.scan.loopFindings.length,
|
|
9960
|
+
costUSD: a.scan.totalCostUSD
|
|
9961
|
+
})).filter((s) => s.sessions > 0 || s.findings > 0);
|
|
9962
|
+
const sections = buildSections(allFindings);
|
|
9963
|
+
const wastedIters = allLoops.filter((l) => l.kind !== "long-iteration").reduce((sum, l) => sum + Math.max(0, l.count - LOOP_THRESHOLD_FOR_WASTE), 0);
|
|
9964
|
+
const loopWastedUSD = wastedIters * COST_PER_LOOP_ITER_USD;
|
|
9965
|
+
return {
|
|
9966
|
+
stats,
|
|
9967
|
+
byVerdict,
|
|
9968
|
+
byAgent,
|
|
9969
|
+
sections,
|
|
9970
|
+
leaks: allLeaks,
|
|
9971
|
+
loops: allLoops,
|
|
9972
|
+
loopWastedUSD
|
|
9973
|
+
};
|
|
9883
9974
|
}
|
|
9884
|
-
function
|
|
9885
|
-
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9975
|
+
function buildSections(findings) {
|
|
9976
|
+
const sectionMap = /* @__PURE__ */ new Map();
|
|
9977
|
+
function ensureSection(id, label, subtitle, sourceType, shieldKey) {
|
|
9978
|
+
let s = sectionMap.get(id);
|
|
9979
|
+
if (!s) {
|
|
9980
|
+
s = {
|
|
9981
|
+
id,
|
|
9982
|
+
label,
|
|
9983
|
+
subtitle,
|
|
9984
|
+
sourceType,
|
|
9985
|
+
shieldKey,
|
|
9986
|
+
blockedCount: 0,
|
|
9987
|
+
reviewCount: 0,
|
|
9988
|
+
rules: []
|
|
9989
|
+
};
|
|
9990
|
+
sectionMap.set(id, s);
|
|
9991
|
+
}
|
|
9992
|
+
return s;
|
|
9993
|
+
}
|
|
9994
|
+
const ruleMap = /* @__PURE__ */ new Map();
|
|
9995
|
+
for (const f of findings) {
|
|
9996
|
+
const src = f.source;
|
|
9997
|
+
const sourceType = src.sourceType;
|
|
9998
|
+
const shieldName = src.shieldName;
|
|
9999
|
+
const verdict = src.rule.verdict === "block" ? "block" : "review";
|
|
10000
|
+
let sectionId;
|
|
10001
|
+
let sectionLabel;
|
|
10002
|
+
let sectionSubtitle;
|
|
10003
|
+
let shieldKey;
|
|
10004
|
+
if (sourceType === "default") {
|
|
10005
|
+
sectionId = "default";
|
|
10006
|
+
sectionLabel = "Default Rules";
|
|
10007
|
+
sectionSubtitle = "built-in, always on";
|
|
10008
|
+
} else if (sourceType === "shield") {
|
|
10009
|
+
sectionId = `shield:${shieldName}`;
|
|
10010
|
+
sectionLabel = shieldName;
|
|
10011
|
+
sectionSubtitle = SHIELDS[shieldName]?.description ?? "";
|
|
10012
|
+
shieldKey = shieldName;
|
|
10013
|
+
} else if (shieldName === "cloud") {
|
|
10014
|
+
sectionId = "cloud";
|
|
10015
|
+
sectionLabel = "Cloud Policy";
|
|
10016
|
+
sectionSubtitle = "synced from node9 cloud";
|
|
10017
|
+
} else {
|
|
10018
|
+
sectionId = "user";
|
|
10019
|
+
sectionLabel = "Your Rules";
|
|
10020
|
+
sectionSubtitle = "added in node9.config.json";
|
|
10021
|
+
}
|
|
10022
|
+
const section = ensureSection(sectionId, sectionLabel, sectionSubtitle, sourceType, shieldKey);
|
|
10023
|
+
const ruleDisplayName = (src.rule.name ?? "unnamed").replace(/^shield:[^:]+:/, "");
|
|
10024
|
+
const ruleKey = sectionId + "::" + ruleDisplayName;
|
|
10025
|
+
let rule = ruleMap.get(ruleKey);
|
|
10026
|
+
if (!rule) {
|
|
10027
|
+
rule = {
|
|
10028
|
+
name: ruleDisplayName,
|
|
10029
|
+
verdict,
|
|
10030
|
+
reason: src.rule.reason ?? "",
|
|
10031
|
+
findings: []
|
|
10032
|
+
};
|
|
10033
|
+
ruleMap.set(ruleKey, rule);
|
|
10034
|
+
section.rules.push(rule);
|
|
10035
|
+
}
|
|
10036
|
+
const cmdPreview = previewCommand(f.input, 120);
|
|
10037
|
+
const fullCmd = fullCommandOf(f.input);
|
|
10038
|
+
const isDupe = rule.findings.some((x) => x.project === f.project && x.command === cmdPreview);
|
|
10039
|
+
if (!isDupe) {
|
|
10040
|
+
rule.findings.push({
|
|
10041
|
+
timestamp: f.timestamp ?? "",
|
|
10042
|
+
command: cmdPreview,
|
|
10043
|
+
fullCommand: fullCmd,
|
|
10044
|
+
project: f.project,
|
|
10045
|
+
sessionId: f.sessionId,
|
|
10046
|
+
agent: f.agent,
|
|
10047
|
+
toolName: f.toolName
|
|
10048
|
+
});
|
|
10049
|
+
}
|
|
10050
|
+
if (verdict === "block") section.blockedCount++;
|
|
10051
|
+
else section.reviewCount++;
|
|
10052
|
+
}
|
|
10053
|
+
const sections = [...sectionMap.values()];
|
|
10054
|
+
sections.sort((a, b) => {
|
|
10055
|
+
const aTotal = a.blockedCount + a.reviewCount;
|
|
10056
|
+
const bTotal = b.blockedCount + b.reviewCount;
|
|
10057
|
+
if (b.blockedCount !== a.blockedCount) return b.blockedCount - a.blockedCount;
|
|
10058
|
+
return bTotal - aTotal;
|
|
10059
|
+
});
|
|
10060
|
+
for (const s of sections) {
|
|
10061
|
+
s.rules.sort((a, b) => {
|
|
10062
|
+
const aBlock = a.verdict === "block" ? 1 : 0;
|
|
10063
|
+
const bBlock = b.verdict === "block" ? 1 : 0;
|
|
10064
|
+
if (bBlock !== aBlock) return bBlock - aBlock;
|
|
10065
|
+
return b.findings.length - a.findings.length;
|
|
10066
|
+
});
|
|
10067
|
+
}
|
|
10068
|
+
return sections;
|
|
9892
10069
|
}
|
|
9893
|
-
function
|
|
9894
|
-
|
|
9895
|
-
const
|
|
9896
|
-
return
|
|
10070
|
+
function previewCommand(input, max) {
|
|
10071
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
10072
|
+
const s = String(raw).replace(/\s+/g, " ").trim();
|
|
10073
|
+
return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
|
|
9897
10074
|
}
|
|
9898
|
-
|
|
9899
|
-
|
|
9900
|
-
"
|
|
10075
|
+
function fullCommandOf(input) {
|
|
10076
|
+
const raw = input.command ?? input.query ?? input.file_path ?? JSON.stringify(input);
|
|
10077
|
+
return String(raw).replace(/\s+/g, " ").trim();
|
|
10078
|
+
}
|
|
10079
|
+
var AGENT_SHORT, AGENT_LONG;
|
|
10080
|
+
var init_scan_summary = __esm({
|
|
10081
|
+
"src/scan-summary.ts"() {
|
|
9901
10082
|
"use strict";
|
|
9902
|
-
|
|
10083
|
+
init_shields();
|
|
10084
|
+
init_dist();
|
|
10085
|
+
init_dist();
|
|
10086
|
+
AGENT_SHORT = {
|
|
10087
|
+
claude: "Claude",
|
|
10088
|
+
gemini: "Gemini",
|
|
10089
|
+
codex: "Codex",
|
|
10090
|
+
antigravity: "Agy",
|
|
10091
|
+
copilot: "Copilot",
|
|
10092
|
+
shell: "Shell"
|
|
10093
|
+
};
|
|
10094
|
+
AGENT_LONG = {
|
|
10095
|
+
claude: "Claude Code",
|
|
10096
|
+
gemini: "Gemini CLI",
|
|
10097
|
+
codex: "Codex",
|
|
10098
|
+
antigravity: "Antigravity",
|
|
10099
|
+
copilot: "GitHub Copilot",
|
|
10100
|
+
shell: "Shell"
|
|
10101
|
+
};
|
|
9903
10102
|
}
|
|
9904
10103
|
});
|
|
9905
10104
|
|
|
9906
|
-
// src/
|
|
9907
|
-
import
|
|
9908
|
-
import
|
|
9909
|
-
import
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
}
|
|
9913
|
-
function readCache() {
|
|
9914
|
-
try {
|
|
9915
|
-
const raw = JSON.parse(fs16.readFileSync(CACHE_FILE(), "utf-8"));
|
|
9916
|
-
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
9917
|
-
return null;
|
|
9918
|
-
}
|
|
9919
|
-
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
9920
|
-
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
9921
|
-
return raw.prices;
|
|
9922
|
-
} catch {
|
|
9923
|
-
return null;
|
|
9924
|
-
}
|
|
9925
|
-
}
|
|
9926
|
-
function writeCache(prices) {
|
|
9927
|
-
try {
|
|
9928
|
-
const target = CACHE_FILE();
|
|
9929
|
-
const dir = path18.dirname(target);
|
|
9930
|
-
if (!fs16.existsSync(dir)) fs16.mkdirSync(dir, { recursive: true });
|
|
9931
|
-
const tmp = target + ".tmp";
|
|
9932
|
-
const body = {
|
|
9933
|
-
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9934
|
-
prices
|
|
9935
|
-
};
|
|
9936
|
-
fs16.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
9937
|
-
fs16.renameSync(tmp, target);
|
|
9938
|
-
} catch (err2) {
|
|
9939
|
-
try {
|
|
9940
|
-
fs16.appendFileSync(
|
|
9941
|
-
HOOK_DEBUG_LOG,
|
|
9942
|
-
`[pricing] cache write failed: ${err2.message}
|
|
9943
|
-
`
|
|
9944
|
-
);
|
|
9945
|
-
} catch {
|
|
9946
|
-
}
|
|
9947
|
-
}
|
|
9948
|
-
}
|
|
9949
|
-
function tupleFromLiteLLM(entry) {
|
|
9950
|
-
if (!entry || typeof entry !== "object") return null;
|
|
9951
|
-
const e = entry;
|
|
9952
|
-
const num3 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
9953
|
-
const inCost = num3(e.input_cost_per_token);
|
|
9954
|
-
const outCost = num3(e.output_cost_per_token);
|
|
9955
|
-
if (inCost === 0 && outCost === 0) return null;
|
|
10105
|
+
// src/cli/commands/blast.ts
|
|
10106
|
+
import chalk2 from "chalk";
|
|
10107
|
+
import fs17 from "fs";
|
|
10108
|
+
import path19 from "path";
|
|
10109
|
+
import os16 from "os";
|
|
10110
|
+
function buildSensitivePaths(home, cwd) {
|
|
9956
10111
|
return [
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
|
|
10112
|
+
{
|
|
10113
|
+
full: path19.join(home, ".ssh", "id_rsa"),
|
|
10114
|
+
label: "~/.ssh/id_rsa",
|
|
10115
|
+
description: "RSA private key \u2014 grants SSH access to your servers",
|
|
10116
|
+
score: 20
|
|
10117
|
+
},
|
|
10118
|
+
{
|
|
10119
|
+
full: path19.join(home, ".ssh", "id_ed25519"),
|
|
10120
|
+
label: "~/.ssh/id_ed25519",
|
|
10121
|
+
description: "Ed25519 private key \u2014 grants SSH access to your servers",
|
|
10122
|
+
score: 20
|
|
10123
|
+
},
|
|
10124
|
+
{
|
|
10125
|
+
full: path19.join(home, ".ssh", "id_ecdsa"),
|
|
10126
|
+
label: "~/.ssh/id_ecdsa",
|
|
10127
|
+
description: "ECDSA private key \u2014 grants SSH access to your servers",
|
|
10128
|
+
score: 20
|
|
10129
|
+
},
|
|
10130
|
+
{
|
|
10131
|
+
full: path19.join(home, ".aws", "credentials"),
|
|
10132
|
+
label: "~/.aws/credentials",
|
|
10133
|
+
description: "AWS access keys \u2014 full cloud account access",
|
|
10134
|
+
score: 20
|
|
10135
|
+
},
|
|
10136
|
+
{
|
|
10137
|
+
full: path19.join(home, ".aws", "config"),
|
|
10138
|
+
label: "~/.aws/config",
|
|
10139
|
+
description: "AWS configuration \u2014 account and region settings",
|
|
10140
|
+
score: 5
|
|
10141
|
+
},
|
|
10142
|
+
{
|
|
10143
|
+
full: path19.join(home, ".config", "gcloud", "credentials.db"),
|
|
10144
|
+
label: "~/.config/gcloud/credentials.db",
|
|
10145
|
+
description: "Google Cloud credentials",
|
|
10146
|
+
score: 15
|
|
10147
|
+
},
|
|
10148
|
+
{
|
|
10149
|
+
full: path19.join(home, ".docker", "config.json"),
|
|
10150
|
+
label: "~/.docker/config.json",
|
|
10151
|
+
description: "Docker registry auth tokens",
|
|
10152
|
+
score: 10
|
|
10153
|
+
},
|
|
10154
|
+
{
|
|
10155
|
+
full: path19.join(home, ".netrc"),
|
|
10156
|
+
label: "~/.netrc",
|
|
10157
|
+
description: "FTP/HTTP credentials in plain text",
|
|
10158
|
+
score: 15
|
|
10159
|
+
},
|
|
10160
|
+
{
|
|
10161
|
+
full: path19.join(home, ".npmrc"),
|
|
10162
|
+
label: "~/.npmrc",
|
|
10163
|
+
description: "npm auth token \u2014 can publish packages as you",
|
|
10164
|
+
score: 10
|
|
10165
|
+
},
|
|
10166
|
+
{
|
|
10167
|
+
full: path19.join(home, ".node9", "credentials.json"),
|
|
10168
|
+
label: "~/.node9/credentials.json",
|
|
10169
|
+
description: "Node9 cloud API key",
|
|
10170
|
+
score: 10
|
|
10171
|
+
},
|
|
10172
|
+
{
|
|
10173
|
+
full: path19.join(cwd, ".env"),
|
|
10174
|
+
label: ".env (current folder)",
|
|
10175
|
+
description: "App secrets \u2014 database passwords, API keys",
|
|
10176
|
+
score: 20
|
|
10177
|
+
},
|
|
10178
|
+
{
|
|
10179
|
+
full: path19.join(cwd, ".env.local"),
|
|
10180
|
+
label: ".env.local (current folder)",
|
|
10181
|
+
description: "Local overrides \u2014 often contains real credentials",
|
|
10182
|
+
score: 15
|
|
10183
|
+
},
|
|
10184
|
+
{
|
|
10185
|
+
full: path19.join(cwd, ".env.production"),
|
|
10186
|
+
label: ".env.production (current folder)",
|
|
10187
|
+
description: "Production secrets",
|
|
10188
|
+
score: 20
|
|
10189
|
+
}
|
|
9961
10190
|
];
|
|
9962
10191
|
}
|
|
9963
|
-
|
|
10192
|
+
function isReadable(filePath) {
|
|
9964
10193
|
try {
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
});
|
|
9968
|
-
if (!res.ok) return null;
|
|
9969
|
-
const json = await res.json();
|
|
9970
|
-
if (!json || typeof json !== "object") return null;
|
|
9971
|
-
const out = {};
|
|
9972
|
-
for (const [key, value] of Object.entries(json)) {
|
|
9973
|
-
const tuple = tupleFromLiteLLM(value);
|
|
9974
|
-
if (tuple) out[key.toLowerCase()] = tuple;
|
|
9975
|
-
}
|
|
9976
|
-
if (Object.keys(out).length < 10) {
|
|
9977
|
-
return null;
|
|
9978
|
-
}
|
|
9979
|
-
return out;
|
|
10194
|
+
fs17.accessSync(filePath, fs17.constants.R_OK);
|
|
10195
|
+
return true;
|
|
9980
10196
|
} catch {
|
|
9981
|
-
return
|
|
10197
|
+
return false;
|
|
9982
10198
|
}
|
|
9983
10199
|
}
|
|
9984
|
-
|
|
9985
|
-
if (
|
|
9986
|
-
|
|
9987
|
-
if (
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
10200
|
+
function scoreLabel(score) {
|
|
10201
|
+
if (score >= 80) return chalk2.green(`${score}/100 Good`);
|
|
10202
|
+
if (score >= 50) return chalk2.yellow(`${score}/100 Moderate risk`);
|
|
10203
|
+
if (score >= 25) return chalk2.red(`${score}/100 High risk`);
|
|
10204
|
+
return chalk2.red.bold(`${score}/100 Critical`);
|
|
10205
|
+
}
|
|
10206
|
+
function runBlast() {
|
|
10207
|
+
const home = os16.homedir();
|
|
10208
|
+
const cwd = process.cwd();
|
|
10209
|
+
const paths = buildSensitivePaths(home, cwd);
|
|
10210
|
+
let scoreDeduction = 0;
|
|
10211
|
+
const reachable = [];
|
|
10212
|
+
for (const p of paths) {
|
|
10213
|
+
if (fs17.existsSync(p.full) && isReadable(p.full)) {
|
|
10214
|
+
reachable.push(p);
|
|
10215
|
+
scoreDeduction += p.score;
|
|
10216
|
+
}
|
|
9992
10217
|
}
|
|
9993
|
-
const
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
|
|
10218
|
+
const envFindings = [];
|
|
10219
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
10220
|
+
if (!value) continue;
|
|
10221
|
+
const match = scanArgs({ [key]: value });
|
|
10222
|
+
if (match) {
|
|
10223
|
+
envFindings.push({ key, patternName: match.patternName });
|
|
10224
|
+
scoreDeduction += 10;
|
|
10225
|
+
}
|
|
10000
10226
|
}
|
|
10001
|
-
|
|
10002
|
-
memCacheAt = Date.now();
|
|
10003
|
-
lookupCache.clear();
|
|
10227
|
+
return { reachable, envFindings, score: Math.max(0, 100 - scoreDeduction) };
|
|
10004
10228
|
}
|
|
10005
|
-
function
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10229
|
+
function registerBlastCommand(program2) {
|
|
10230
|
+
program2.command("blast").description("Map what an AI agent can currently reach on this machine").action(() => {
|
|
10231
|
+
const home = os16.homedir();
|
|
10232
|
+
const cwd = process.cwd();
|
|
10233
|
+
const { reachable, envFindings, score } = runBlast();
|
|
10234
|
+
console.log("");
|
|
10235
|
+
console.log(
|
|
10236
|
+
chalk2.bold(" \u{1F52D} Node9 Blast Radius") + chalk2.dim(" \xB7 what an AI agent can reach from here")
|
|
10237
|
+
);
|
|
10238
|
+
console.log(chalk2.dim(" Running in: ") + chalk2.white(cwd.replace(home, "~")));
|
|
10239
|
+
console.log("");
|
|
10240
|
+
if (reachable.length > 0) {
|
|
10241
|
+
console.log(" " + chalk2.red.bold("Sensitive files reachable:"));
|
|
10242
|
+
for (const p of reachable) {
|
|
10243
|
+
console.log(
|
|
10244
|
+
" " + chalk2.red("\u2717 ") + chalk2.yellow(p.label.padEnd(38)) + chalk2.dim(p.description)
|
|
10245
|
+
);
|
|
10246
|
+
}
|
|
10247
|
+
console.log("");
|
|
10018
10248
|
}
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10249
|
+
if (envFindings.length > 0) {
|
|
10250
|
+
console.log(" " + chalk2.red.bold("Secrets in active environment:"));
|
|
10251
|
+
for (const f of envFindings) {
|
|
10252
|
+
console.log(
|
|
10253
|
+
" " + chalk2.red("\u2717 ") + chalk2.yellow(f.key.padEnd(38)) + chalk2.dim(f.patternName)
|
|
10254
|
+
);
|
|
10023
10255
|
}
|
|
10256
|
+
console.log("");
|
|
10024
10257
|
}
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10258
|
+
console.log(" " + chalk2.dim("\u2500".repeat(70)));
|
|
10259
|
+
if (reachable.length === 0 && envFindings.length === 0) {
|
|
10260
|
+
console.log(" " + chalk2.green("\u2705 No sensitive files or environment secrets found."));
|
|
10261
|
+
console.log(" Security Score: " + scoreLabel(score));
|
|
10262
|
+
} else {
|
|
10263
|
+
console.log(
|
|
10264
|
+
" Security Score: " + scoreLabel(score) + chalk2.dim(
|
|
10265
|
+
` (${reachable.length} file${reachable.length !== 1 ? "s" : ""}, ${envFindings.length} env var${envFindings.length !== 1 ? "s" : ""})`
|
|
10266
|
+
)
|
|
10267
|
+
);
|
|
10268
|
+
console.log("");
|
|
10269
|
+
console.log(
|
|
10270
|
+
chalk2.dim(
|
|
10271
|
+
" 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."
|
|
10272
|
+
)
|
|
10273
|
+
);
|
|
10028
10274
|
}
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
return resolved;
|
|
10275
|
+
console.log("");
|
|
10276
|
+
});
|
|
10032
10277
|
}
|
|
10033
|
-
var
|
|
10034
|
-
|
|
10035
|
-
"src/pricing/litellm.ts"() {
|
|
10278
|
+
var init_blast = __esm({
|
|
10279
|
+
"src/cli/commands/blast.ts"() {
|
|
10036
10280
|
"use strict";
|
|
10037
|
-
|
|
10038
|
-
LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
10039
|
-
BUNDLED_PRICING = {
|
|
10040
|
-
// Anthropic
|
|
10041
|
-
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10042
|
-
"claude-opus-4-1": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10043
|
-
"claude-opus-4-5": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10044
|
-
"claude-opus-4-6": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10045
|
-
"claude-opus-4-7": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
10046
|
-
"claude-sonnet-4": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10047
|
-
"claude-sonnet-4-5": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10048
|
-
"claude-sonnet-4-6": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10049
|
-
"claude-haiku-4": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10050
|
-
"claude-haiku-4-5": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10051
|
-
"claude-3-7-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10052
|
-
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
10053
|
-
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
10054
|
-
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
10055
|
-
// OpenAI
|
|
10056
|
-
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
10057
|
-
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
10058
|
-
"gpt-5": [1e-5, 3e-5, 0, 5e-6],
|
|
10059
|
-
// Google
|
|
10060
|
-
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
10061
|
-
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
10062
|
-
};
|
|
10063
|
-
CACHE_FILE = () => path18.join(os15.homedir(), ".node9", "model-pricing.json");
|
|
10064
|
-
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10065
|
-
memCache = null;
|
|
10066
|
-
memCacheAt = 0;
|
|
10067
|
-
lookupCache = /* @__PURE__ */ new Map();
|
|
10281
|
+
init_dlp();
|
|
10068
10282
|
}
|
|
10069
10283
|
});
|
|
10070
10284
|
|
|
10071
|
-
// src/
|
|
10072
|
-
import
|
|
10073
|
-
import
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10285
|
+
// src/cli/render/scan-derive.ts
|
|
10286
|
+
import chalk3 from "chalk";
|
|
10287
|
+
import stringWidth from "string-width";
|
|
10288
|
+
function classifyScore(score) {
|
|
10289
|
+
if (score >= 80) return { band: "good", label: "Good", color: chalk3.green };
|
|
10290
|
+
if (score >= 50) return { band: "at-risk", label: "At Risk", color: chalk3.yellow };
|
|
10291
|
+
return { band: "critical", label: "Critical", color: chalk3.red };
|
|
10077
10292
|
}
|
|
10078
|
-
function
|
|
10079
|
-
|
|
10293
|
+
function topDlpPatterns(findings, n) {
|
|
10294
|
+
const counts = /* @__PURE__ */ new Map();
|
|
10295
|
+
for (const f of findings) {
|
|
10296
|
+
counts.set(f.patternName, (counts.get(f.patternName) ?? 0) + 1);
|
|
10297
|
+
}
|
|
10298
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([name, count]) => ({ name, count }));
|
|
10080
10299
|
}
|
|
10081
|
-
function
|
|
10082
|
-
const
|
|
10083
|
-
for (const
|
|
10084
|
-
const
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
const mp = path19.join(yp, m);
|
|
10088
|
-
if (!isDir(mp)) continue;
|
|
10089
|
-
for (const d of safeReaddir(mp)) {
|
|
10090
|
-
const dp = path19.join(mp, d);
|
|
10091
|
-
if (!isDir(dp)) continue;
|
|
10092
|
-
for (const f of safeReaddir(dp)) {
|
|
10093
|
-
if (f.endsWith(".jsonl")) out.push(path19.join(dp, f));
|
|
10094
|
-
}
|
|
10095
|
-
}
|
|
10300
|
+
function topRulesByVerdict(sections, verdict, n) {
|
|
10301
|
+
const matched = [];
|
|
10302
|
+
for (const section of sections) {
|
|
10303
|
+
for (const rule of section.rules) {
|
|
10304
|
+
const matches = verdict === "block" ? rule.verdict === "block" : rule.verdict !== "block";
|
|
10305
|
+
if (matches) matched.push({ name: rule.name, count: rule.findings.length });
|
|
10096
10306
|
}
|
|
10097
10307
|
}
|
|
10098
|
-
return
|
|
10308
|
+
return matched.sort((a, b) => b.count - a.count).slice(0, n);
|
|
10099
10309
|
}
|
|
10100
|
-
function
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
return [];
|
|
10105
|
-
}
|
|
10310
|
+
function computeLoopWaste(loops, totalToolCalls) {
|
|
10311
|
+
const wastedCalls = loops.reduce((s, l) => s + Math.max(0, l.count - 1), 0);
|
|
10312
|
+
const wastePct = totalToolCalls > 0 ? Math.round(wastedCalls / totalToolCalls * 100) : 0;
|
|
10313
|
+
return { wastedCalls, wastePct };
|
|
10106
10314
|
}
|
|
10107
|
-
function
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10315
|
+
function rollupByShield(sections, topRulesPerShield = 3) {
|
|
10316
|
+
const out = [];
|
|
10317
|
+
for (const section of sections) {
|
|
10318
|
+
if (section.sourceType !== "shield") continue;
|
|
10319
|
+
if (!section.shieldKey) continue;
|
|
10320
|
+
const totalCatches = section.blockedCount + section.reviewCount;
|
|
10321
|
+
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);
|
|
10322
|
+
out.push({
|
|
10323
|
+
shieldName: section.shieldKey,
|
|
10324
|
+
totalCatches,
|
|
10325
|
+
blockCatches: section.blockedCount,
|
|
10326
|
+
reviewCatches: section.reviewCount,
|
|
10327
|
+
topRuleLabels
|
|
10328
|
+
});
|
|
10112
10329
|
}
|
|
10330
|
+
return out.sort((a, b) => b.totalCatches - a.totalCatches);
|
|
10113
10331
|
}
|
|
10114
|
-
function
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
let entry;
|
|
10126
|
-
try {
|
|
10127
|
-
entry = JSON.parse(raw);
|
|
10128
|
-
} catch {
|
|
10129
|
-
continue;
|
|
10130
|
-
}
|
|
10131
|
-
const p = entry.payload ?? {};
|
|
10132
|
-
if (entry.type === "session_meta") {
|
|
10133
|
-
if (!sessionStart2 && typeof p["timestamp"] === "string") sessionStart2 = p["timestamp"];
|
|
10134
|
-
if (!runId && typeof p["id"] === "string") runId = p["id"];
|
|
10135
|
-
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
10136
|
-
continue;
|
|
10137
|
-
}
|
|
10138
|
-
if (entry.type === "turn_context") {
|
|
10139
|
-
if (typeof p["model"] === "string") model = p["model"];
|
|
10140
|
-
if (!cwd && typeof p["cwd"] === "string") cwd = p["cwd"];
|
|
10141
|
-
continue;
|
|
10142
|
-
}
|
|
10143
|
-
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
10144
|
-
const info = p["info"] ?? {};
|
|
10145
|
-
const usage = info["total_token_usage"] ?? {};
|
|
10146
|
-
if (typeof usage["input_tokens"] === "number") input = usage["input_tokens"];
|
|
10147
|
-
if (typeof usage["cached_input_tokens"] === "number") cached = usage["cached_input_tokens"];
|
|
10148
|
-
if (typeof usage["output_tokens"] === "number") output = usage["output_tokens"];
|
|
10149
|
-
sawUsage = true;
|
|
10150
|
-
}
|
|
10332
|
+
function boxPanel(title, bodyLines, width = PANEL_WIDTH) {
|
|
10333
|
+
const inner = width - 4;
|
|
10334
|
+
const out = [];
|
|
10335
|
+
const titlePad = ` ${title} `;
|
|
10336
|
+
const titleWidth = stringWidth(titlePad);
|
|
10337
|
+
const titleSegment = titleWidth <= inner ? titlePad : titlePad.slice(0, inner);
|
|
10338
|
+
const dashFill = "\u2500".repeat(Math.max(0, inner - stringWidth(titleSegment)));
|
|
10339
|
+
out.push(chalk3.dim("\u256D\u2500") + chalk3.bold(titleSegment) + chalk3.dim(`${dashFill}\u2500\u256E`));
|
|
10340
|
+
for (const line of bodyLines) {
|
|
10341
|
+
const padding = " ".repeat(Math.max(0, inner - line.width));
|
|
10342
|
+
out.push(chalk3.dim("\u2502 ") + line.rendered + padding + chalk3.dim(" \u2502"));
|
|
10151
10343
|
}
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
if (nonCached === 0 && output === 0 && cached === 0) return null;
|
|
10155
|
-
const norm = normalizeModel(model || "gpt-5");
|
|
10156
|
-
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
10157
|
-
const costUSD = nonCached * pin + output * pout + cached * pcr;
|
|
10158
|
-
return {
|
|
10159
|
-
date: sessionStart2.slice(0, 10),
|
|
10160
|
-
model: norm,
|
|
10161
|
-
workingDir: cwd,
|
|
10162
|
-
runId,
|
|
10163
|
-
costUSD,
|
|
10164
|
-
inputTokens: nonCached,
|
|
10165
|
-
outputTokens: output,
|
|
10166
|
-
cacheReadTokens: cached,
|
|
10167
|
-
cacheWriteTokens: 0
|
|
10168
|
-
};
|
|
10344
|
+
out.push(chalk3.dim("\u2570" + "\u2500".repeat(inner + 2) + "\u256F"));
|
|
10345
|
+
return out;
|
|
10169
10346
|
}
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10347
|
+
function relativeDate(timestamp, now = /* @__PURE__ */ new Date()) {
|
|
10348
|
+
const t = new Date(timestamp).getTime();
|
|
10349
|
+
if (Number.isNaN(t)) return "?";
|
|
10350
|
+
const days = Math.floor((now.getTime() - t) / 864e5);
|
|
10351
|
+
if (days < 1) return "today";
|
|
10352
|
+
if (days > 90) return "90d+";
|
|
10353
|
+
return `${days}d`;
|
|
10354
|
+
}
|
|
10355
|
+
var PANEL_WIDTH;
|
|
10356
|
+
var init_scan_derive = __esm({
|
|
10357
|
+
"src/cli/render/scan-derive.ts"() {
|
|
10173
10358
|
"use strict";
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
collect(sinceMs) {
|
|
10186
|
-
const base = codexSessionsDir();
|
|
10187
|
-
const combined = /* @__PURE__ */ new Map();
|
|
10188
|
-
for (const file of listCodexSessionFiles(base)) {
|
|
10189
|
-
try {
|
|
10190
|
-
if (sinceMs !== void 0 && fs17.statSync(file).mtimeMs < sinceMs) continue;
|
|
10191
|
-
} catch {
|
|
10192
|
-
continue;
|
|
10193
|
-
}
|
|
10194
|
-
let content;
|
|
10195
|
-
try {
|
|
10196
|
-
content = fs17.readFileSync(file, "utf8");
|
|
10197
|
-
} catch {
|
|
10198
|
-
continue;
|
|
10199
|
-
}
|
|
10200
|
-
const e = parseCodexSession(content.split("\n"));
|
|
10201
|
-
if (!e) continue;
|
|
10202
|
-
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
10203
|
-
const prev = combined.get(key);
|
|
10204
|
-
if (prev) {
|
|
10205
|
-
prev.costUSD += e.costUSD;
|
|
10206
|
-
prev.inputTokens += e.inputTokens;
|
|
10207
|
-
prev.outputTokens += e.outputTokens;
|
|
10208
|
-
prev.cacheReadTokens += e.cacheReadTokens;
|
|
10209
|
-
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
10210
|
-
} else {
|
|
10211
|
-
combined.set(key, { ...e });
|
|
10212
|
-
}
|
|
10213
|
-
}
|
|
10214
|
-
return [...combined.values()];
|
|
10215
|
-
}
|
|
10359
|
+
PANEL_WIDTH = 76;
|
|
10360
|
+
}
|
|
10361
|
+
});
|
|
10362
|
+
|
|
10363
|
+
// src/protection.ts
|
|
10364
|
+
var PROTECTIVE_SHIELD_DISCOUNTS;
|
|
10365
|
+
var init_protection = __esm({
|
|
10366
|
+
"src/protection.ts"() {
|
|
10367
|
+
"use strict";
|
|
10368
|
+
PROTECTIVE_SHIELD_DISCOUNTS = {
|
|
10369
|
+
"project-jail": 0.7
|
|
10216
10370
|
};
|
|
10217
10371
|
}
|
|
10218
10372
|
});
|
|
10219
10373
|
|
|
10220
|
-
// src/
|
|
10374
|
+
// src/cli/render/scan-json.ts
|
|
10375
|
+
function buildScanJson(input) {
|
|
10376
|
+
const { summary, blast, isWired, generatedAt } = input;
|
|
10377
|
+
const { band } = classifyScore(blast.score);
|
|
10378
|
+
return {
|
|
10379
|
+
schemaVersion: 1,
|
|
10380
|
+
generatedAt,
|
|
10381
|
+
isWired,
|
|
10382
|
+
score: blast.score,
|
|
10383
|
+
band,
|
|
10384
|
+
totals: {
|
|
10385
|
+
blocked: summary.byVerdict.blocked,
|
|
10386
|
+
review: summary.byVerdict.supervised,
|
|
10387
|
+
leaks: summary.byVerdict.leaks,
|
|
10388
|
+
loops: summary.byVerdict.loops,
|
|
10389
|
+
blastExposures: blast.reachable.length + blast.envFindings.length
|
|
10390
|
+
},
|
|
10391
|
+
summary,
|
|
10392
|
+
blast: {
|
|
10393
|
+
score: blast.score,
|
|
10394
|
+
reachable: blast.reachable,
|
|
10395
|
+
envFindings: blast.envFindings
|
|
10396
|
+
}
|
|
10397
|
+
};
|
|
10398
|
+
}
|
|
10399
|
+
var init_scan_json = __esm({
|
|
10400
|
+
"src/cli/render/scan-json.ts"() {
|
|
10401
|
+
"use strict";
|
|
10402
|
+
init_scan_derive();
|
|
10403
|
+
}
|
|
10404
|
+
});
|
|
10405
|
+
|
|
10406
|
+
// src/cli/render/scan-history.ts
|
|
10221
10407
|
import fs18 from "fs";
|
|
10222
|
-
import os17 from "os";
|
|
10223
10408
|
import path20 from "path";
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
function geminiPriceFor(model) {
|
|
10228
|
-
let tuple = pricingFor(model);
|
|
10229
|
-
if (!tuple && /^gemini-/i.test(model)) {
|
|
10230
|
-
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
10231
|
-
tuple = pricingFor(proxy);
|
|
10232
|
-
if (tuple) break;
|
|
10233
|
-
}
|
|
10234
|
-
}
|
|
10235
|
-
if (!tuple) return null;
|
|
10236
|
-
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
10409
|
+
import os17 from "os";
|
|
10410
|
+
function defaultHistoryPath() {
|
|
10411
|
+
return path20.join(os17.homedir(), ".node9", "scan-history.json");
|
|
10237
10412
|
}
|
|
10238
|
-
function
|
|
10413
|
+
function readPreviousScan(opts = {}) {
|
|
10414
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10239
10415
|
try {
|
|
10240
|
-
|
|
10416
|
+
if (!fs18.existsSync(filePath)) return null;
|
|
10417
|
+
const raw = fs18.readFileSync(filePath, "utf8");
|
|
10418
|
+
const parsed = JSON.parse(raw);
|
|
10419
|
+
if (!Array.isArray(parsed) || parsed.length === 0) return null;
|
|
10420
|
+
const last = parsed[parsed.length - 1];
|
|
10421
|
+
if (!isValidRecord(last)) return null;
|
|
10422
|
+
return last;
|
|
10241
10423
|
} catch {
|
|
10242
|
-
return
|
|
10424
|
+
return null;
|
|
10243
10425
|
}
|
|
10244
10426
|
}
|
|
10245
|
-
function
|
|
10427
|
+
function appendScanHistory(record, opts = {}) {
|
|
10428
|
+
const filePath = opts.path ?? defaultHistoryPath();
|
|
10429
|
+
const cap = opts.cap ?? SCAN_HISTORY_CAP;
|
|
10246
10430
|
try {
|
|
10247
|
-
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
if (!isDir2(chats)) continue;
|
|
10257
|
-
for (const f of safeReaddir2(chats)) {
|
|
10258
|
-
if (f.startsWith("session-") && f.endsWith(".jsonl")) {
|
|
10259
|
-
out.push({ file: path20.join(chats, f), project });
|
|
10431
|
+
fs18.mkdirSync(path20.dirname(filePath), { recursive: true });
|
|
10432
|
+
let history = [];
|
|
10433
|
+
if (fs18.existsSync(filePath)) {
|
|
10434
|
+
try {
|
|
10435
|
+
const parsed = JSON.parse(fs18.readFileSync(filePath, "utf8"));
|
|
10436
|
+
if (Array.isArray(parsed)) {
|
|
10437
|
+
history = parsed.filter(isValidRecord);
|
|
10438
|
+
}
|
|
10439
|
+
} catch {
|
|
10260
10440
|
}
|
|
10261
10441
|
}
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
function parseGeminiSession(lines, project) {
|
|
10266
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
10267
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
10268
|
-
let runId = "";
|
|
10269
|
-
for (const raw of lines) {
|
|
10270
|
-
if (!raw.trim()) continue;
|
|
10271
|
-
let obj;
|
|
10272
|
-
try {
|
|
10273
|
-
obj = JSON.parse(raw);
|
|
10274
|
-
} catch {
|
|
10275
|
-
continue;
|
|
10276
|
-
}
|
|
10277
|
-
if (typeof obj.sessionId === "string" && !runId) runId = obj.sessionId;
|
|
10278
|
-
if (!obj.tokens || !obj.model || !obj.timestamp) continue;
|
|
10279
|
-
if (obj.id) {
|
|
10280
|
-
if (seenIds.has(obj.id)) continue;
|
|
10281
|
-
seenIds.add(obj.id);
|
|
10282
|
-
}
|
|
10283
|
-
const price = geminiPriceFor(obj.model);
|
|
10284
|
-
if (!price) continue;
|
|
10285
|
-
const inp = obj.tokens.input ?? 0;
|
|
10286
|
-
const out = obj.tokens.output ?? 0;
|
|
10287
|
-
const cached = Math.min(obj.tokens.cached ?? 0, inp);
|
|
10288
|
-
const fresh = Math.max(0, inp - cached);
|
|
10289
|
-
const cost = fresh * price.input + cached * price.cacheRead + out * price.output;
|
|
10290
|
-
const date = obj.timestamp.slice(0, 10);
|
|
10291
|
-
const model = normalizeModel(obj.model);
|
|
10292
|
-
const key = `${date}::${model}`;
|
|
10293
|
-
const prev = byKey.get(key);
|
|
10294
|
-
if (prev) {
|
|
10295
|
-
prev.costUSD += cost;
|
|
10296
|
-
prev.inputTokens += fresh;
|
|
10297
|
-
prev.outputTokens += out;
|
|
10298
|
-
prev.cacheReadTokens += cached;
|
|
10299
|
-
} else {
|
|
10300
|
-
byKey.set(key, {
|
|
10301
|
-
date,
|
|
10302
|
-
model,
|
|
10303
|
-
workingDir: project,
|
|
10304
|
-
runId,
|
|
10305
|
-
costUSD: cost,
|
|
10306
|
-
inputTokens: fresh,
|
|
10307
|
-
outputTokens: out,
|
|
10308
|
-
cacheReadTokens: cached,
|
|
10309
|
-
cacheWriteTokens: 0
|
|
10310
|
-
});
|
|
10442
|
+
history.push(record);
|
|
10443
|
+
if (history.length > cap) {
|
|
10444
|
+
history = history.slice(history.length - cap);
|
|
10311
10445
|
}
|
|
10446
|
+
fs18.writeFileSync(filePath, JSON.stringify(history, null, 2));
|
|
10447
|
+
} catch (err2) {
|
|
10448
|
+
process.stderr.write(
|
|
10449
|
+
`[node9] Warning: could not write scan-history.json: ${err2.message}
|
|
10450
|
+
`
|
|
10451
|
+
);
|
|
10312
10452
|
}
|
|
10313
|
-
if (runId) for (const e of byKey.values()) e.runId = runId;
|
|
10314
|
-
return [...byKey.values()];
|
|
10315
10453
|
}
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10454
|
+
function computeScanDelta(current, previous, now = Date.now()) {
|
|
10455
|
+
if (!previous) return null;
|
|
10456
|
+
const prevMs = Date.parse(previous.timestamp);
|
|
10457
|
+
if (Number.isNaN(prevMs)) return null;
|
|
10458
|
+
const scoreDelta = current.score - previous.score;
|
|
10459
|
+
const daysAgo = Math.max(0, Math.floor((now - prevMs) / 864e5));
|
|
10460
|
+
if (scoreDelta === 0 && daysAgo === 0) return null;
|
|
10461
|
+
return { scoreDelta, daysAgo };
|
|
10462
|
+
}
|
|
10463
|
+
function isValidRecord(x) {
|
|
10464
|
+
if (typeof x !== "object" || x === null) return false;
|
|
10465
|
+
const r = x;
|
|
10466
|
+
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";
|
|
10467
|
+
}
|
|
10468
|
+
var SCAN_HISTORY_CAP;
|
|
10469
|
+
var init_scan_history = __esm({
|
|
10470
|
+
"src/cli/render/scan-history.ts"() {
|
|
10319
10471
|
"use strict";
|
|
10320
|
-
|
|
10321
|
-
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
10322
|
-
geminiSource = {
|
|
10323
|
-
id: "gemini",
|
|
10324
|
-
available() {
|
|
10325
|
-
try {
|
|
10326
|
-
return fs18.existsSync(geminiTmpDir());
|
|
10327
|
-
} catch {
|
|
10328
|
-
return false;
|
|
10329
|
-
}
|
|
10330
|
-
},
|
|
10331
|
-
collect(sinceMs) {
|
|
10332
|
-
const combined = /* @__PURE__ */ new Map();
|
|
10333
|
-
for (const { file, project } of listGeminiSessionFiles(geminiTmpDir())) {
|
|
10334
|
-
try {
|
|
10335
|
-
if (sinceMs !== void 0 && fs18.statSync(file).mtimeMs < sinceMs) continue;
|
|
10336
|
-
} catch {
|
|
10337
|
-
continue;
|
|
10338
|
-
}
|
|
10339
|
-
let content;
|
|
10340
|
-
try {
|
|
10341
|
-
content = fs18.readFileSync(file, "utf8");
|
|
10342
|
-
} catch {
|
|
10343
|
-
continue;
|
|
10344
|
-
}
|
|
10345
|
-
for (const e of parseGeminiSession(content.split("\n"), project)) {
|
|
10346
|
-
const key = `${e.date}::${e.model}::${e.workingDir ?? ""}::${e.runId ?? ""}`;
|
|
10347
|
-
const prev = combined.get(key);
|
|
10348
|
-
if (prev) {
|
|
10349
|
-
prev.costUSD += e.costUSD;
|
|
10350
|
-
prev.inputTokens += e.inputTokens;
|
|
10351
|
-
prev.outputTokens += e.outputTokens;
|
|
10352
|
-
prev.cacheReadTokens += e.cacheReadTokens;
|
|
10353
|
-
prev.cacheWriteTokens += e.cacheWriteTokens;
|
|
10354
|
-
} else {
|
|
10355
|
-
combined.set(key, { ...e });
|
|
10356
|
-
}
|
|
10357
|
-
}
|
|
10358
|
-
}
|
|
10359
|
-
return [...combined.values()];
|
|
10360
|
-
}
|
|
10361
|
-
};
|
|
10472
|
+
SCAN_HISTORY_CAP = 30;
|
|
10362
10473
|
}
|
|
10363
10474
|
});
|
|
10364
10475
|
|
|
@@ -11402,19 +11513,15 @@ import path25 from "path";
|
|
|
11402
11513
|
import os22 from "os";
|
|
11403
11514
|
import stringWidth2 from "string-width";
|
|
11404
11515
|
function claudeModelPrice(model) {
|
|
11405
|
-
const
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
}
|
|
11409
|
-
return null;
|
|
11516
|
+
const t = pricingFor(model);
|
|
11517
|
+
if (!t) return null;
|
|
11518
|
+
const [i, o, cw, cr] = t;
|
|
11519
|
+
return { i, o, cw, cr };
|
|
11410
11520
|
}
|
|
11411
11521
|
function geminiModelPrice(model) {
|
|
11412
|
-
const
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
}
|
|
11416
|
-
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
11417
|
-
return null;
|
|
11522
|
+
const p = geminiPriceFor(model);
|
|
11523
|
+
if (!p) return null;
|
|
11524
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
11418
11525
|
}
|
|
11419
11526
|
function isNode9SelfOutput(text) {
|
|
11420
11527
|
let hits = 0;
|
|
@@ -12060,14 +12167,17 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12060
12167
|
if (!fs23.existsSync(chatsDir)) continue;
|
|
12061
12168
|
let chatFiles;
|
|
12062
12169
|
try {
|
|
12063
|
-
chatFiles = fs23.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
12170
|
+
chatFiles = fs23.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
12064
12171
|
} catch {
|
|
12065
12172
|
continue;
|
|
12066
12173
|
}
|
|
12174
|
+
const seenSessions = /* @__PURE__ */ new Set();
|
|
12067
12175
|
for (const chatFile of chatFiles) {
|
|
12176
|
+
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
12177
|
+
if (seenSessions.has(sessionId)) continue;
|
|
12178
|
+
seenSessions.add(sessionId);
|
|
12068
12179
|
result.filesScanned++;
|
|
12069
12180
|
onProgress?.(result.filesScanned);
|
|
12070
|
-
const sessionId = chatFile.replace(/\.json$/, "");
|
|
12071
12181
|
let raw;
|
|
12072
12182
|
try {
|
|
12073
12183
|
raw = fs23.readFileSync(path25.join(chatsDir, chatFile), "utf-8");
|
|
@@ -12077,7 +12187,18 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
12077
12187
|
const sessionCalls = [];
|
|
12078
12188
|
let session;
|
|
12079
12189
|
try {
|
|
12080
|
-
|
|
12190
|
+
if (chatFile.endsWith(".jsonl")) {
|
|
12191
|
+
const messages = raw.split("\n").filter((l) => l.trim()).map((l) => {
|
|
12192
|
+
try {
|
|
12193
|
+
return JSON.parse(l);
|
|
12194
|
+
} catch {
|
|
12195
|
+
return null;
|
|
12196
|
+
}
|
|
12197
|
+
}).filter((m) => m !== null);
|
|
12198
|
+
session = { messages };
|
|
12199
|
+
} else {
|
|
12200
|
+
session = JSON.parse(raw);
|
|
12201
|
+
}
|
|
12081
12202
|
} catch {
|
|
12082
12203
|
continue;
|
|
12083
12204
|
}
|
|
@@ -12687,6 +12808,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12687
12808
|
let lastTotalInput = 0;
|
|
12688
12809
|
let lastTotalCached = 0;
|
|
12689
12810
|
let lastTotalOutput = 0;
|
|
12811
|
+
let model = "";
|
|
12690
12812
|
for (const line of lines) {
|
|
12691
12813
|
if (!line.trim()) continue;
|
|
12692
12814
|
onLine?.();
|
|
@@ -12704,6 +12826,10 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12704
12826
|
projLabel = stripTerminalEscapes(cwd.replace(os22.homedir(), "~")).slice(0, 40);
|
|
12705
12827
|
continue;
|
|
12706
12828
|
}
|
|
12829
|
+
if (entry.type === "turn_context" && typeof payload["model"] === "string") {
|
|
12830
|
+
model = payload["model"];
|
|
12831
|
+
continue;
|
|
12832
|
+
}
|
|
12707
12833
|
if (entry.type === "event_msg" && payload["type"] === "token_count") {
|
|
12708
12834
|
const info = payload["info"];
|
|
12709
12835
|
const usage = info?.["total_token_usage"] ?? {};
|
|
@@ -12847,8 +12973,11 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
12847
12973
|
}
|
|
12848
12974
|
}
|
|
12849
12975
|
}
|
|
12850
|
-
|
|
12851
|
-
|
|
12976
|
+
result.totalCostUSD += codexSessionCost(model, {
|
|
12977
|
+
input: lastTotalInput,
|
|
12978
|
+
cached: lastTotalCached,
|
|
12979
|
+
output: lastTotalOutput
|
|
12980
|
+
});
|
|
12852
12981
|
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
|
|
12853
12982
|
}
|
|
12854
12983
|
return result;
|
|
@@ -13895,7 +14024,7 @@ function registerScanCommand(program2) {
|
|
|
13895
14024
|
}
|
|
13896
14025
|
);
|
|
13897
14026
|
}
|
|
13898
|
-
var
|
|
14027
|
+
var CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE2, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS, STUCK_TOOLS_MIN_WASTE, STUCK_TOOLS_LIMIT, RECURRING_SESSION_THRESHOLD, STALE_AGE_DAYS, classifyRuleSeverity2, narrativeRuleLabel2;
|
|
13899
14028
|
var init_scan = __esm({
|
|
13900
14029
|
"src/cli/commands/scan.ts"() {
|
|
13901
14030
|
"use strict";
|
|
@@ -13904,6 +14033,9 @@ var init_scan = __esm({
|
|
|
13904
14033
|
init_policy();
|
|
13905
14034
|
init_dist();
|
|
13906
14035
|
init_dlp();
|
|
14036
|
+
init_litellm();
|
|
14037
|
+
init_cost_gemini();
|
|
14038
|
+
init_cost_codex();
|
|
13907
14039
|
init_hook_payload();
|
|
13908
14040
|
init_dist();
|
|
13909
14041
|
init_scan_summary();
|
|
@@ -13913,26 +14045,6 @@ var init_scan = __esm({
|
|
|
13913
14045
|
init_protection();
|
|
13914
14046
|
init_scan_json();
|
|
13915
14047
|
init_scan_history();
|
|
13916
|
-
CLAUDE_PRICING = {
|
|
13917
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13918
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
13919
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
13920
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13921
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13922
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13923
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13924
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
13925
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
13926
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
13927
|
-
};
|
|
13928
|
-
GEMINI_PRICING = {
|
|
13929
|
-
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
13930
|
-
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
13931
|
-
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
13932
|
-
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
13933
|
-
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
13934
|
-
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
13935
|
-
};
|
|
13936
14048
|
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
13937
14049
|
".ts",
|
|
13938
14050
|
".tsx",
|
|
@@ -20108,6 +20220,7 @@ import chalk13 from "chalk";
|
|
|
20108
20220
|
// src/cli/aggregate/report-audit.ts
|
|
20109
20221
|
init_costSync();
|
|
20110
20222
|
init_litellm();
|
|
20223
|
+
init_cost_codex();
|
|
20111
20224
|
import fs40 from "fs";
|
|
20112
20225
|
import os36 from "os";
|
|
20113
20226
|
import path41 from "path";
|
|
@@ -20207,24 +20320,11 @@ function isAllow(decision) {
|
|
|
20207
20320
|
function isDlp(checkedBy) {
|
|
20208
20321
|
return !!checkedBy?.includes("dlp");
|
|
20209
20322
|
}
|
|
20210
|
-
var CLAUDE_PRICING2 = {
|
|
20211
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
20212
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
20213
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
20214
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20215
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20216
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20217
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20218
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
20219
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
20220
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
20221
|
-
};
|
|
20222
20323
|
function claudeModelPrice2(model) {
|
|
20223
|
-
const
|
|
20224
|
-
|
|
20225
|
-
|
|
20226
|
-
}
|
|
20227
|
-
return null;
|
|
20324
|
+
const t = pricingFor(model);
|
|
20325
|
+
if (!t) return null;
|
|
20326
|
+
const [i, o, cw, cr] = t;
|
|
20327
|
+
return { i, o, cw, cr };
|
|
20228
20328
|
}
|
|
20229
20329
|
function emptyClaudeCostAccumulator() {
|
|
20230
20330
|
return {
|
|
@@ -20339,6 +20439,7 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20339
20439
|
return;
|
|
20340
20440
|
}
|
|
20341
20441
|
let sessionStart2 = "";
|
|
20442
|
+
let model = "";
|
|
20342
20443
|
let lastTotalInput = 0;
|
|
20343
20444
|
let lastTotalCached = 0;
|
|
20344
20445
|
let lastTotalOutput = 0;
|
|
@@ -20356,6 +20457,10 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20356
20457
|
sessionStart2 = String(p["timestamp"] ?? "");
|
|
20357
20458
|
continue;
|
|
20358
20459
|
}
|
|
20460
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
20461
|
+
model = p["model"];
|
|
20462
|
+
continue;
|
|
20463
|
+
}
|
|
20359
20464
|
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
20360
20465
|
const info = p["info"] ?? {};
|
|
20361
20466
|
const usage = info["total_token_usage"] ?? {};
|
|
@@ -20370,12 +20475,17 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
20370
20475
|
if (!sessionStart2) return;
|
|
20371
20476
|
const ts = new Date(sessionStart2);
|
|
20372
20477
|
if (ts < start || ts > end) return;
|
|
20373
|
-
const
|
|
20374
|
-
|
|
20478
|
+
const cost = codexSessionCost(model, {
|
|
20479
|
+
input: lastTotalInput,
|
|
20480
|
+
cached: lastTotalCached,
|
|
20481
|
+
output: lastTotalOutput
|
|
20482
|
+
});
|
|
20375
20483
|
acc.total += cost;
|
|
20376
20484
|
acc.toolCalls += sessionToolCalls;
|
|
20377
20485
|
const dateKey = sessionStart2.slice(0, 10);
|
|
20378
20486
|
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
20487
|
+
const normModel = normalizeModel(model || "gpt-5");
|
|
20488
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
20379
20489
|
}
|
|
20380
20490
|
function listCodexSessionFiles2(sessionsBase) {
|
|
20381
20491
|
const jsonlFiles = [];
|
|
@@ -20413,13 +20523,25 @@ function listCodexSessionFiles2(sessionsBase) {
|
|
|
20413
20523
|
}
|
|
20414
20524
|
return jsonlFiles;
|
|
20415
20525
|
}
|
|
20526
|
+
function mergeByModel(...maps) {
|
|
20527
|
+
const out = /* @__PURE__ */ new Map();
|
|
20528
|
+
for (const m of maps) {
|
|
20529
|
+
for (const [k, v] of m) out.set(k, (out.get(k) ?? 0) + v);
|
|
20530
|
+
}
|
|
20531
|
+
return out;
|
|
20532
|
+
}
|
|
20416
20533
|
function loadCodexCost(start, end, sessionsBase) {
|
|
20417
|
-
const acc = {
|
|
20534
|
+
const acc = {
|
|
20535
|
+
total: 0,
|
|
20536
|
+
toolCalls: 0,
|
|
20537
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
20538
|
+
byModel: /* @__PURE__ */ new Map()
|
|
20539
|
+
};
|
|
20418
20540
|
const files = listCodexSessionFiles2(sessionsBase);
|
|
20419
20541
|
for (const filePath of files) {
|
|
20420
20542
|
processCodexCostFile(filePath, start, end, acc);
|
|
20421
20543
|
}
|
|
20422
|
-
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
20544
|
+
return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
|
|
20423
20545
|
}
|
|
20424
20546
|
var GEMINI_FALLBACK_MODELS2 = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
20425
20547
|
function geminiPriceFor2(model) {
|
|
@@ -20708,7 +20830,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
20708
20830
|
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
20709
20831
|
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
20710
20832
|
byDay: claudeCost.byDay,
|
|
20711
|
-
byModel: claudeCost.byModel,
|
|
20833
|
+
byModel: mergeByModel(claudeCost.byModel, codexCost.byModel),
|
|
20712
20834
|
byProject: claudeCost.byProject
|
|
20713
20835
|
},
|
|
20714
20836
|
toolMap,
|
|
@@ -21977,6 +22099,8 @@ function registerUndoCommand(program2) {
|
|
|
21977
22099
|
|
|
21978
22100
|
// src/mcp-gateway/index.ts
|
|
21979
22101
|
init_orchestrator();
|
|
22102
|
+
init_cloud();
|
|
22103
|
+
init_config();
|
|
21980
22104
|
import readline4 from "readline";
|
|
21981
22105
|
import chalk19 from "chalk";
|
|
21982
22106
|
import { spawn as spawn7 } from "child_process";
|
|
@@ -22009,6 +22133,60 @@ function normalizeClientName(name) {
|
|
|
22009
22133
|
const sanitized = sanitize4(name).slice(0, 40);
|
|
22010
22134
|
return sanitized.length > 0 ? sanitized : void 0;
|
|
22011
22135
|
}
|
|
22136
|
+
function reportPinMismatchToCloud(serverKey, agent) {
|
|
22137
|
+
try {
|
|
22138
|
+
const creds = getCredentials();
|
|
22139
|
+
if (!creds) return;
|
|
22140
|
+
void auditLocalAllow(
|
|
22141
|
+
`mcp-server:${serverKey}`,
|
|
22142
|
+
{ serverKey, reason: "tool-pin-mismatch" },
|
|
22143
|
+
"mcp-pin-mismatch",
|
|
22144
|
+
creds,
|
|
22145
|
+
{ mcpServer: serverKey, agent },
|
|
22146
|
+
void 0,
|
|
22147
|
+
false,
|
|
22148
|
+
{
|
|
22149
|
+
ruleName: "MCP tool definitions changed (possible rug pull)",
|
|
22150
|
+
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}`
|
|
22151
|
+
}
|
|
22152
|
+
);
|
|
22153
|
+
} catch {
|
|
22154
|
+
}
|
|
22155
|
+
}
|
|
22156
|
+
function reportInventoryToCloud(serverKey, toolCount, agent) {
|
|
22157
|
+
try {
|
|
22158
|
+
const creds = getCredentials();
|
|
22159
|
+
if (!creds) return;
|
|
22160
|
+
void auditLocalAllow(
|
|
22161
|
+
`mcp-server:${serverKey}`,
|
|
22162
|
+
{ serverKey, toolCount },
|
|
22163
|
+
"mcp-discovered",
|
|
22164
|
+
creds,
|
|
22165
|
+
{ mcpServer: serverKey, agent },
|
|
22166
|
+
void 0,
|
|
22167
|
+
false,
|
|
22168
|
+
{ mcpToolCount: toolCount }
|
|
22169
|
+
);
|
|
22170
|
+
} catch {
|
|
22171
|
+
}
|
|
22172
|
+
}
|
|
22173
|
+
function reportLargeResponseToCloud(serverKey, responseBytes, agent) {
|
|
22174
|
+
try {
|
|
22175
|
+
const creds = getCredentials();
|
|
22176
|
+
if (!creds) return;
|
|
22177
|
+
void auditLocalAllow(
|
|
22178
|
+
`mcp-server:${serverKey}`,
|
|
22179
|
+
{ serverKey, responseBytes },
|
|
22180
|
+
"mcp-large-response",
|
|
22181
|
+
creds,
|
|
22182
|
+
{ mcpServer: serverKey, agent },
|
|
22183
|
+
void 0,
|
|
22184
|
+
false,
|
|
22185
|
+
{ mcpResponseBytes: responseBytes }
|
|
22186
|
+
);
|
|
22187
|
+
} catch {
|
|
22188
|
+
}
|
|
22189
|
+
}
|
|
22012
22190
|
function tokenize4(cmd) {
|
|
22013
22191
|
const tokens = [];
|
|
22014
22192
|
let current = "";
|
|
@@ -22269,6 +22447,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
22269
22447
|
const currentHash = hashToolDefinitions(tools);
|
|
22270
22448
|
const pinStatus = checkPin(serverKey, currentHash, gatewayCwd);
|
|
22271
22449
|
const token = getInternalToken();
|
|
22450
|
+
reportInventoryToCloud(serverKey, tools.length, clientName);
|
|
22272
22451
|
if (isDaemonRunning() && process.env.NODE9_TESTING !== "1") {
|
|
22273
22452
|
const toolSummary = tools.map((t) => ({ name: t.name, description: t.description }));
|
|
22274
22453
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/mcp/discovered`, {
|
|
@@ -22333,6 +22512,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
22333
22512
|
console.error(chalk19.red(" Session quarantined \u2014 all tool calls blocked."));
|
|
22334
22513
|
console.error(chalk19.yellow(` Run: node9 mcp pin update ${serverKey}
|
|
22335
22514
|
`));
|
|
22515
|
+
reportPinMismatchToCloud(serverKey, clientName);
|
|
22336
22516
|
const errorResponse = {
|
|
22337
22517
|
jsonrpc: "2.0",
|
|
22338
22518
|
id: parsed.id,
|
|
@@ -22378,6 +22558,7 @@ async function runMcpGateway(upstreamCommand) {
|
|
|
22378
22558
|
`\u26A1 Node9: Large MCP response from '${toolName}' (${(line.length / 1024).toFixed(0)}KB) \u2014 context window enlarged`
|
|
22379
22559
|
)
|
|
22380
22560
|
);
|
|
22561
|
+
reportLargeResponseToCloud(serverKey, line.length, clientName);
|
|
22381
22562
|
if (isDaemonRunning() && process.env.NODE9_TESTING !== "1") {
|
|
22382
22563
|
const token = getInternalToken();
|
|
22383
22564
|
fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/mcp/large-response`, {
|
|
@@ -23477,44 +23658,23 @@ init_scan();
|
|
|
23477
23658
|
|
|
23478
23659
|
// src/cli/commands/sessions.ts
|
|
23479
23660
|
init_scan_summary();
|
|
23661
|
+
init_litellm();
|
|
23662
|
+
init_cost_gemini();
|
|
23663
|
+
init_cost_codex();
|
|
23480
23664
|
import chalk24 from "chalk";
|
|
23481
23665
|
import fs45 from "fs";
|
|
23482
23666
|
import path46 from "path";
|
|
23483
23667
|
import os40 from "os";
|
|
23484
|
-
var CLAUDE_PRICING3 = {
|
|
23485
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
23486
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
23487
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
23488
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23489
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23490
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23491
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23492
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
23493
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
23494
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
23495
|
-
};
|
|
23496
23668
|
function modelPrice(model) {
|
|
23497
|
-
const
|
|
23498
|
-
|
|
23499
|
-
|
|
23500
|
-
}
|
|
23501
|
-
return null;
|
|
23669
|
+
const t = pricingFor(model);
|
|
23670
|
+
if (!t) return null;
|
|
23671
|
+
const [i, o, cw, cr] = t;
|
|
23672
|
+
return { i, o, cw, cr };
|
|
23502
23673
|
}
|
|
23503
|
-
var GEMINI_PRICING2 = {
|
|
23504
|
-
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
23505
|
-
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
23506
|
-
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
23507
|
-
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
23508
|
-
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
23509
|
-
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
23510
|
-
};
|
|
23511
23674
|
function geminiModelPrice2(model) {
|
|
23512
|
-
const
|
|
23513
|
-
|
|
23514
|
-
|
|
23515
|
-
}
|
|
23516
|
-
if (base.includes("flash")) return GEMINI_PRICING2["gemini-2.0-flash"];
|
|
23517
|
-
return null;
|
|
23675
|
+
const p = geminiPriceFor(model);
|
|
23676
|
+
if (!p) return null;
|
|
23677
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
23518
23678
|
}
|
|
23519
23679
|
function encodeProjectPath(projectPath) {
|
|
23520
23680
|
return projectPath.replace(/\//g, "-");
|
|
@@ -23807,6 +23967,7 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23807
23967
|
let lastTotalInput = 0;
|
|
23808
23968
|
let lastTotalCached = 0;
|
|
23809
23969
|
let lastTotalOutput = 0;
|
|
23970
|
+
let model = "";
|
|
23810
23971
|
for (const line of lines) {
|
|
23811
23972
|
if (!line.trim()) continue;
|
|
23812
23973
|
let entry;
|
|
@@ -23822,6 +23983,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23822
23983
|
cwd = String(p["cwd"] ?? "");
|
|
23823
23984
|
continue;
|
|
23824
23985
|
}
|
|
23986
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
23987
|
+
model = p["model"];
|
|
23988
|
+
continue;
|
|
23989
|
+
}
|
|
23825
23990
|
if (entry.type === "event_msg" && p["type"] === "user_message" && !firstPrompt) {
|
|
23826
23991
|
firstPrompt = String(p["message"] ?? "");
|
|
23827
23992
|
continue;
|
|
@@ -23848,8 +24013,11 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23848
24013
|
}
|
|
23849
24014
|
if (!sessionId || !startTime) continue;
|
|
23850
24015
|
if (cutoff && new Date(startTime) < cutoff) continue;
|
|
23851
|
-
const
|
|
23852
|
-
|
|
24016
|
+
const costUSD = codexSessionCost(model, {
|
|
24017
|
+
input: lastTotalInput,
|
|
24018
|
+
cached: lastTotalCached,
|
|
24019
|
+
output: lastTotalOutput
|
|
24020
|
+
});
|
|
23853
24021
|
const windowEnd = new Date(
|
|
23854
24022
|
Math.max(new Date(startTime).getTime(), lastToolTs ? new Date(lastToolTs).getTime() : 0) + 5 * 60 * 1e3
|
|
23855
24023
|
).toISOString();
|
|
@@ -23873,11 +24041,10 @@ function buildCodexSessions(days, allAuditEntries) {
|
|
|
23873
24041
|
}
|
|
23874
24042
|
function buildSessions(days, historyPath) {
|
|
23875
24043
|
const hPath = historyPath ?? path46.join(os40.homedir(), ".claude", "history.jsonl");
|
|
23876
|
-
let historyRaw;
|
|
24044
|
+
let historyRaw = "";
|
|
23877
24045
|
try {
|
|
23878
24046
|
historyRaw = fs45.readFileSync(hPath, "utf-8");
|
|
23879
24047
|
} catch {
|
|
23880
|
-
return [];
|
|
23881
24048
|
}
|
|
23882
24049
|
const cutoff = days !== null ? (() => {
|
|
23883
24050
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -24168,12 +24335,6 @@ function registerSessionsCommand(program2) {
|
|
|
24168
24335
|
console.log("");
|
|
24169
24336
|
console.log(chalk24.cyan.bold("\u{1F4CB} node9 sessions") + chalk24.dim(" \u2014 what your AI agent did"));
|
|
24170
24337
|
console.log("");
|
|
24171
|
-
const historyPath = path46.join(os40.homedir(), ".claude", "history.jsonl");
|
|
24172
|
-
if (!fs45.existsSync(historyPath)) {
|
|
24173
|
-
console.log(chalk24.yellow(" No Claude session history found at ~/.claude/history.jsonl"));
|
|
24174
|
-
console.log(chalk24.gray(" Install Claude Code, run a few sessions, then try again.\n"));
|
|
24175
|
-
return;
|
|
24176
|
-
}
|
|
24177
24338
|
const days = options.detail || options.all ? null : Math.max(1, parseInt(options.days, 10) || 7);
|
|
24178
24339
|
const rangeLabel = options.detail ? "all time" : options.all ? "all time" : `last ${String(days)} days`;
|
|
24179
24340
|
console.log(chalk24.dim(" " + rangeLabel));
|