@node9/proxy 1.32.0 → 1.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1672 -1241
- package/dist/cli.mjs +1654 -1223
- package/dist/dashboard.mjs +413 -189
- package/dist/index.js +86 -17
- package/dist/index.mjs +86 -17
- package/package.json +1 -1
package/dist/dashboard.mjs
CHANGED
|
@@ -244,6 +244,8 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
244
244
|
if (f === PARSE_FAIL) return command;
|
|
245
245
|
try {
|
|
246
246
|
const strips = [];
|
|
247
|
+
const rewrites = [];
|
|
248
|
+
const msgSpans = /* @__PURE__ */ new Set();
|
|
247
249
|
syntax.Walk(f, (node) => {
|
|
248
250
|
if (!node) return false;
|
|
249
251
|
const n = node;
|
|
@@ -259,25 +261,46 @@ function normalizeCommandForPolicyImpl(command) {
|
|
|
259
261
|
if (nextParts.length !== 1) continue;
|
|
260
262
|
const quotedNode = nextParts[0];
|
|
261
263
|
const nt = syntax.NodeType(quotedNode);
|
|
264
|
+
const markStrip = () => {
|
|
265
|
+
const s = next.Pos().Offset();
|
|
266
|
+
const e = next.End().Offset();
|
|
267
|
+
strips.push([s, e]);
|
|
268
|
+
msgSpans.add(`${s}:${e}`);
|
|
269
|
+
};
|
|
262
270
|
if (nt === "SglQuoted") {
|
|
263
|
-
|
|
271
|
+
markStrip();
|
|
264
272
|
} else if (nt === "DblQuoted") {
|
|
265
273
|
const innerParts = quotedNode.Parts || [];
|
|
266
274
|
const allLit = innerParts.length === 0 || innerParts.every((p) => syntax.NodeType(p) === "Lit");
|
|
267
275
|
if (allLit) {
|
|
268
|
-
|
|
276
|
+
markStrip();
|
|
269
277
|
} else if (innerParts.every((p) => isCatHeredocOrLit(p))) {
|
|
270
|
-
|
|
278
|
+
markStrip();
|
|
271
279
|
}
|
|
272
280
|
}
|
|
273
281
|
}
|
|
282
|
+
for (const arg of args) {
|
|
283
|
+
const s = arg.Pos().Offset();
|
|
284
|
+
const e = arg.End().Offset();
|
|
285
|
+
if (msgSpans.has(`${s}:${e}`)) continue;
|
|
286
|
+
const resolved = resolveWordLiteral(arg);
|
|
287
|
+
if (resolved === null) continue;
|
|
288
|
+
const source = command.slice(s, e);
|
|
289
|
+
if (resolved === source) continue;
|
|
290
|
+
if (resolved === "" || /\s/.test(resolved)) continue;
|
|
291
|
+
rewrites.push([s, e, resolved]);
|
|
292
|
+
}
|
|
274
293
|
return true;
|
|
275
294
|
});
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
const edits = [
|
|
296
|
+
...strips.map(([s, e]) => [s, e, '""']),
|
|
297
|
+
...rewrites
|
|
298
|
+
];
|
|
299
|
+
if (edits.length === 0) return command;
|
|
300
|
+
edits.sort((a, b) => b[0] - a[0]);
|
|
278
301
|
let result = command;
|
|
279
|
-
for (const [
|
|
280
|
-
result = result.slice(0,
|
|
302
|
+
for (const [s, e, rep] of edits) {
|
|
303
|
+
result = result.slice(0, s) + rep + result.slice(e);
|
|
281
304
|
}
|
|
282
305
|
return result;
|
|
283
306
|
} catch {
|
|
@@ -386,20 +409,38 @@ function extractLiteralArgs(callExpr) {
|
|
|
386
409
|
}
|
|
387
410
|
return { name, flags, paths };
|
|
388
411
|
}
|
|
412
|
+
function resolveWordLiteral(w) {
|
|
413
|
+
const parts = w?.Parts || [];
|
|
414
|
+
let s = "";
|
|
415
|
+
for (const p of parts) {
|
|
416
|
+
const t = syntax.NodeType(p);
|
|
417
|
+
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
418
|
+
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
419
|
+
else if (t === "DblQuoted") {
|
|
420
|
+
const inner = p.Parts || [];
|
|
421
|
+
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
422
|
+
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
423
|
+
} else {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return s;
|
|
428
|
+
}
|
|
389
429
|
function analyzeFsOperation(command) {
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
fsOpCache.
|
|
394
|
-
fsOpCache.
|
|
430
|
+
const normalized = normalizeCommandForPolicy(command);
|
|
431
|
+
if (!FS_OP_PRESCREEN_RE.test(normalized)) return null;
|
|
432
|
+
if (fsOpCache.has(normalized)) {
|
|
433
|
+
const hit = fsOpCache.get(normalized) ?? null;
|
|
434
|
+
fsOpCache.delete(normalized);
|
|
435
|
+
fsOpCache.set(normalized, hit);
|
|
395
436
|
return hit;
|
|
396
437
|
}
|
|
397
|
-
const computed = analyzeFsOperationImpl(
|
|
438
|
+
const computed = analyzeFsOperationImpl(normalized);
|
|
398
439
|
if (fsOpCache.size >= FS_OP_CACHE_MAX) {
|
|
399
440
|
const oldest = fsOpCache.keys().next().value;
|
|
400
441
|
if (oldest !== void 0) fsOpCache.delete(oldest);
|
|
401
442
|
}
|
|
402
|
-
fsOpCache.set(
|
|
443
|
+
fsOpCache.set(normalized, computed);
|
|
403
444
|
return computed;
|
|
404
445
|
}
|
|
405
446
|
function analyzeFsOperationImpl(command) {
|
|
@@ -506,9 +547,9 @@ function matchesPattern(text, patterns) {
|
|
|
506
547
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
507
548
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
508
549
|
}
|
|
509
|
-
function getNestedValue(obj,
|
|
550
|
+
function getNestedValue(obj, path10) {
|
|
510
551
|
if (!obj || typeof obj !== "object") return null;
|
|
511
|
-
const segments =
|
|
552
|
+
const segments = path10.split(".");
|
|
512
553
|
for (const seg of segments) {
|
|
513
554
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
514
555
|
}
|
|
@@ -1219,7 +1260,11 @@ var init_dist = __esm({
|
|
|
1219
1260
|
"shield:project-jail:block-read-ssh",
|
|
1220
1261
|
"shield:project-jail:block-read-aws",
|
|
1221
1262
|
"shield:project-jail:block-read-env",
|
|
1222
|
-
"shield:project-jail:review-read-credentials"
|
|
1263
|
+
"shield:project-jail:review-read-credentials",
|
|
1264
|
+
// SQL-DDL is now owned by the AST detector (analyzeSqlDestructive) so the
|
|
1265
|
+
// raw-regex smart rule is suppressed for bash — its cond1 read a grep
|
|
1266
|
+
// alternation's `|` as a shell pipe (`grep "…|mysql…"` → false positive).
|
|
1267
|
+
"review-drop-truncate-shell"
|
|
1223
1268
|
]);
|
|
1224
1269
|
FS_OP_CACHE_MAX = 5e3;
|
|
1225
1270
|
fsOpCache = /* @__PURE__ */ new Map();
|
|
@@ -2585,13 +2630,116 @@ var init_audit = __esm({
|
|
|
2585
2630
|
});
|
|
2586
2631
|
|
|
2587
2632
|
// src/pricing/litellm.ts
|
|
2633
|
+
import fs4 from "fs";
|
|
2634
|
+
import path5 from "path";
|
|
2635
|
+
import os5 from "os";
|
|
2588
2636
|
function normalizeModel(raw) {
|
|
2589
2637
|
return raw.replace(/-\d{8}$/, "").toLowerCase();
|
|
2590
2638
|
}
|
|
2639
|
+
function readCache() {
|
|
2640
|
+
try {
|
|
2641
|
+
const raw = JSON.parse(fs4.readFileSync(CACHE_FILE(), "utf-8"));
|
|
2642
|
+
if (typeof raw.fetchedAt !== "string" || typeof raw.prices !== "object" || raw.prices === null) {
|
|
2643
|
+
return null;
|
|
2644
|
+
}
|
|
2645
|
+
const ageMs = Date.now() - new Date(raw.fetchedAt).getTime();
|
|
2646
|
+
if (ageMs < 0 || ageMs > TTL_MS) return null;
|
|
2647
|
+
return raw.prices;
|
|
2648
|
+
} catch {
|
|
2649
|
+
return null;
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
function writeCache(prices) {
|
|
2653
|
+
try {
|
|
2654
|
+
const target = CACHE_FILE();
|
|
2655
|
+
const dir = path5.dirname(target);
|
|
2656
|
+
if (!fs4.existsSync(dir)) fs4.mkdirSync(dir, { recursive: true });
|
|
2657
|
+
const tmp = target + ".tmp";
|
|
2658
|
+
const body = {
|
|
2659
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2660
|
+
prices
|
|
2661
|
+
};
|
|
2662
|
+
fs4.writeFileSync(tmp, JSON.stringify(body) + "\n", "utf-8");
|
|
2663
|
+
fs4.renameSync(tmp, target);
|
|
2664
|
+
} catch (err) {
|
|
2665
|
+
try {
|
|
2666
|
+
fs4.appendFileSync(
|
|
2667
|
+
HOOK_DEBUG_LOG,
|
|
2668
|
+
`[pricing] cache write failed: ${err.message}
|
|
2669
|
+
`
|
|
2670
|
+
);
|
|
2671
|
+
} catch {
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
function tupleFromLiteLLM(entry) {
|
|
2676
|
+
if (!entry || typeof entry !== "object") return null;
|
|
2677
|
+
const e = entry;
|
|
2678
|
+
const num2 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
2679
|
+
const inCost = num2(e.input_cost_per_token);
|
|
2680
|
+
const outCost = num2(e.output_cost_per_token);
|
|
2681
|
+
if (inCost === 0 && outCost === 0) return null;
|
|
2682
|
+
return [
|
|
2683
|
+
inCost,
|
|
2684
|
+
outCost,
|
|
2685
|
+
num2(e.cache_creation_input_token_cost),
|
|
2686
|
+
num2(e.cache_read_input_token_cost)
|
|
2687
|
+
];
|
|
2688
|
+
}
|
|
2689
|
+
async function fetchLiteLLMPricing() {
|
|
2690
|
+
try {
|
|
2691
|
+
const res = await fetch(LITELLM_URL, {
|
|
2692
|
+
signal: AbortSignal.timeout(15e3)
|
|
2693
|
+
});
|
|
2694
|
+
if (!res.ok) return null;
|
|
2695
|
+
const json = await res.json();
|
|
2696
|
+
if (!json || typeof json !== "object") return null;
|
|
2697
|
+
const out = {};
|
|
2698
|
+
for (const [key, value] of Object.entries(json)) {
|
|
2699
|
+
const tuple = tupleFromLiteLLM(value);
|
|
2700
|
+
if (tuple) out[key.toLowerCase()] = tuple;
|
|
2701
|
+
}
|
|
2702
|
+
if (Object.keys(out).length < 10) {
|
|
2703
|
+
return null;
|
|
2704
|
+
}
|
|
2705
|
+
return out;
|
|
2706
|
+
} catch {
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
async function ensurePricingLoaded() {
|
|
2711
|
+
if (memCache !== null && Date.now() - memCacheAt < TTL_MS) return;
|
|
2712
|
+
const fromDisk = readCache();
|
|
2713
|
+
if (fromDisk && Object.keys(fromDisk).length > 0) {
|
|
2714
|
+
memCache = fromDisk;
|
|
2715
|
+
memCacheAt = Date.now();
|
|
2716
|
+
lookupCache.clear();
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
const fetched = await fetchLiteLLMPricing();
|
|
2720
|
+
if (fetched && Object.keys(fetched).length > 0) {
|
|
2721
|
+
memCache = fetched;
|
|
2722
|
+
memCacheAt = Date.now();
|
|
2723
|
+
writeCache(fetched);
|
|
2724
|
+
lookupCache.clear();
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
memCache = { ...BUNDLED_PRICING };
|
|
2728
|
+
memCacheAt = Date.now();
|
|
2729
|
+
lookupCache.clear();
|
|
2730
|
+
}
|
|
2591
2731
|
function pricingFor(model) {
|
|
2592
2732
|
const norm = normalizeModel(model);
|
|
2593
2733
|
const cached = lookupCache.get(norm);
|
|
2594
2734
|
if (cached !== void 0) return cached;
|
|
2735
|
+
if (memCache === null && !diskChecked) {
|
|
2736
|
+
diskChecked = true;
|
|
2737
|
+
const disk = readCache();
|
|
2738
|
+
if (disk && Object.keys(disk).length > 0) {
|
|
2739
|
+
memCache = disk;
|
|
2740
|
+
memCacheAt = Date.now();
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2595
2743
|
const sources = [];
|
|
2596
2744
|
if (memCache) sources.push(memCache);
|
|
2597
2745
|
sources.push(BUNDLED_PRICING);
|
|
@@ -2616,11 +2764,12 @@ function pricingFor(model) {
|
|
|
2616
2764
|
lookupCache.set(norm, resolved);
|
|
2617
2765
|
return resolved;
|
|
2618
2766
|
}
|
|
2619
|
-
var BUNDLED_PRICING, TTL_MS, memCache, lookupCache;
|
|
2767
|
+
var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
|
|
2620
2768
|
var init_litellm = __esm({
|
|
2621
2769
|
"src/pricing/litellm.ts"() {
|
|
2622
2770
|
"use strict";
|
|
2623
2771
|
init_audit();
|
|
2772
|
+
LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
2624
2773
|
BUNDLED_PRICING = {
|
|
2625
2774
|
// Anthropic
|
|
2626
2775
|
"claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
|
|
@@ -2637,25 +2786,80 @@ var init_litellm = __esm({
|
|
|
2637
2786
|
"claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
|
|
2638
2787
|
"claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
|
|
2639
2788
|
"claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
|
|
2640
|
-
// OpenAI
|
|
2789
|
+
// OpenAI. gpt-5 family + o-series copied from the live LiteLLM table
|
|
2790
|
+
// (verified 2026-06-14) — the bundled gpt-5 was stale at $10/$30 vs the real
|
|
2791
|
+
// $1.25/$10, and Codex models (gpt-5-codex etc.) were absent, so the offline
|
|
2792
|
+
// fallback mispriced every Codex session. See cost-codex.codexPriceFor.
|
|
2641
2793
|
"gpt-4o": [5e-6, 15e-6, 0, 25e-7],
|
|
2642
2794
|
"gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
|
|
2643
|
-
"gpt-5": [
|
|
2644
|
-
|
|
2795
|
+
"gpt-5": [125e-8, 1e-5, 0, 125e-9],
|
|
2796
|
+
"gpt-5-codex": [125e-8, 1e-5, 0, 125e-9],
|
|
2797
|
+
"gpt-5-mini": [25e-8, 2e-6, 0, 25e-9],
|
|
2798
|
+
o3: [2e-6, 8e-6, 0, 5e-7],
|
|
2799
|
+
"o4-mini": [11e-7, 44e-7, 0, 275e-9],
|
|
2800
|
+
// Google. Values copied from the live LiteLLM table (verified 2026-06-14)
|
|
2801
|
+
// so the bundled fallback prices the current Gemini tiers correctly offline
|
|
2802
|
+
// — the local cost readers were carrying a stale hardcoded copy where
|
|
2803
|
+
// gemini-2.5-flash read $0.15/$0.60 vs the real $0.30/$2.50 (~4× under on
|
|
2804
|
+
// output). See cost-gemini.geminiPriceFor (the single Gemini price source).
|
|
2805
|
+
"gemini-2.5-pro": [125e-8, 1e-5, 0, 125e-9],
|
|
2806
|
+
"gemini-2.5-flash": [3e-7, 25e-7, 0, 3e-8],
|
|
2645
2807
|
"gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
|
|
2646
2808
|
"gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
|
|
2647
2809
|
};
|
|
2810
|
+
CACHE_FILE = () => path5.join(os5.homedir(), ".node9", "model-pricing.json");
|
|
2648
2811
|
TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2649
2812
|
memCache = null;
|
|
2813
|
+
memCacheAt = 0;
|
|
2814
|
+
diskChecked = false;
|
|
2650
2815
|
lookupCache = /* @__PURE__ */ new Map();
|
|
2651
2816
|
}
|
|
2652
2817
|
});
|
|
2653
2818
|
|
|
2654
2819
|
// src/cost-codex.ts
|
|
2820
|
+
function codexPriceFor(model) {
|
|
2821
|
+
return pricingFor(model) ?? CODEX_FALLBACK;
|
|
2822
|
+
}
|
|
2823
|
+
function codexSessionCost(model, tokens) {
|
|
2824
|
+
const nonCached = Math.max(0, tokens.input - tokens.cached);
|
|
2825
|
+
const [pin, pout, , pcr] = codexPriceFor(model || "gpt-5");
|
|
2826
|
+
return nonCached * pin + tokens.cached * pcr + tokens.output * pout;
|
|
2827
|
+
}
|
|
2828
|
+
var CODEX_FALLBACK;
|
|
2655
2829
|
var init_cost_codex = __esm({
|
|
2656
2830
|
"src/cost-codex.ts"() {
|
|
2657
2831
|
"use strict";
|
|
2658
2832
|
init_litellm();
|
|
2833
|
+
CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
|
|
2834
|
+
}
|
|
2835
|
+
});
|
|
2836
|
+
|
|
2837
|
+
// src/cost-gemini.ts
|
|
2838
|
+
function geminiPriceFor(model) {
|
|
2839
|
+
let tuple = pricingFor(model);
|
|
2840
|
+
if (!tuple && /^gemini-/i.test(model)) {
|
|
2841
|
+
for (const proxy of GEMINI_FALLBACK_MODELS) {
|
|
2842
|
+
tuple = pricingFor(proxy);
|
|
2843
|
+
if (tuple) break;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
if (!tuple) return null;
|
|
2847
|
+
return { input: tuple[0], output: tuple[1], cacheRead: tuple[3] || tuple[0] };
|
|
2848
|
+
}
|
|
2849
|
+
var GEMINI_FALLBACK_MODELS;
|
|
2850
|
+
var init_cost_gemini = __esm({
|
|
2851
|
+
"src/cost-gemini.ts"() {
|
|
2852
|
+
"use strict";
|
|
2853
|
+
init_litellm();
|
|
2854
|
+
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
2855
|
+
}
|
|
2856
|
+
});
|
|
2857
|
+
|
|
2858
|
+
// src/cost-copilot.ts
|
|
2859
|
+
var init_cost_copilot = __esm({
|
|
2860
|
+
"src/cost-copilot.ts"() {
|
|
2861
|
+
"use strict";
|
|
2862
|
+
init_litellm();
|
|
2659
2863
|
}
|
|
2660
2864
|
});
|
|
2661
2865
|
|
|
@@ -2671,6 +2875,8 @@ var init_costSync = __esm({
|
|
|
2671
2875
|
init_audit();
|
|
2672
2876
|
init_litellm();
|
|
2673
2877
|
init_cost_codex();
|
|
2878
|
+
init_cost_gemini();
|
|
2879
|
+
init_cost_copilot();
|
|
2674
2880
|
SYNC_INTERVAL_MS = 10 * 60 * 1e3;
|
|
2675
2881
|
}
|
|
2676
2882
|
});
|
|
@@ -2687,9 +2893,9 @@ var init_scan_watermark = __esm({
|
|
|
2687
2893
|
});
|
|
2688
2894
|
|
|
2689
2895
|
// src/cli/aggregate/report-audit.ts
|
|
2690
|
-
import
|
|
2691
|
-
import
|
|
2692
|
-
import
|
|
2896
|
+
import fs5 from "fs";
|
|
2897
|
+
import os6 from "os";
|
|
2898
|
+
import path6 from "path";
|
|
2693
2899
|
function buildTestTimestamps(allEntries) {
|
|
2694
2900
|
const testTs = /* @__PURE__ */ new Set();
|
|
2695
2901
|
for (const e of allEntries) {
|
|
@@ -2770,8 +2976,8 @@ function getDateRange(period, now) {
|
|
|
2770
2976
|
}
|
|
2771
2977
|
}
|
|
2772
2978
|
function parseAuditLog(logPath) {
|
|
2773
|
-
if (!
|
|
2774
|
-
const raw =
|
|
2979
|
+
if (!fs5.existsSync(logPath)) return [];
|
|
2980
|
+
const raw = fs5.readFileSync(logPath, "utf-8");
|
|
2775
2981
|
return raw.split("\n").flatMap((line) => {
|
|
2776
2982
|
if (!line.trim()) return [];
|
|
2777
2983
|
try {
|
|
@@ -2788,11 +2994,10 @@ function isDlp(checkedBy) {
|
|
|
2788
2994
|
return !!checkedBy?.includes("dlp");
|
|
2789
2995
|
}
|
|
2790
2996
|
function claudeModelPrice(model) {
|
|
2791
|
-
const
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
}
|
|
2795
|
-
return null;
|
|
2997
|
+
const t = pricingFor(model);
|
|
2998
|
+
if (!t) return null;
|
|
2999
|
+
const [i, o, cw, cr] = t;
|
|
3000
|
+
return { i, o, cw, cr };
|
|
2796
3001
|
}
|
|
2797
3002
|
function emptyClaudeCostAccumulator() {
|
|
2798
3003
|
return {
|
|
@@ -2819,25 +3024,25 @@ function freezeClaudeCost(acc) {
|
|
|
2819
3024
|
};
|
|
2820
3025
|
}
|
|
2821
3026
|
function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
2822
|
-
const projPath =
|
|
3027
|
+
const projPath = path6.join(projectsDir, proj);
|
|
2823
3028
|
let files;
|
|
2824
3029
|
try {
|
|
2825
|
-
const stat =
|
|
3030
|
+
const stat = fs5.statSync(projPath);
|
|
2826
3031
|
if (!stat.isDirectory()) return;
|
|
2827
|
-
files =
|
|
3032
|
+
files = fs5.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
2828
3033
|
} catch {
|
|
2829
3034
|
return;
|
|
2830
3035
|
}
|
|
2831
3036
|
const startMs = start.getTime();
|
|
2832
3037
|
for (const file of files) {
|
|
2833
|
-
const filePath =
|
|
3038
|
+
const filePath = path6.join(projPath, file);
|
|
2834
3039
|
try {
|
|
2835
|
-
if (
|
|
3040
|
+
if (fs5.statSync(filePath).mtimeMs < startMs) continue;
|
|
2836
3041
|
} catch {
|
|
2837
3042
|
continue;
|
|
2838
3043
|
}
|
|
2839
3044
|
try {
|
|
2840
|
-
const raw =
|
|
3045
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
2841
3046
|
for (const line of raw.split("\n")) {
|
|
2842
3047
|
if (!line.trim()) continue;
|
|
2843
3048
|
let entry;
|
|
@@ -2887,10 +3092,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
|
|
|
2887
3092
|
}
|
|
2888
3093
|
function loadClaudeCost(start, end, projectsDir) {
|
|
2889
3094
|
const acc = emptyClaudeCostAccumulator();
|
|
2890
|
-
if (!
|
|
3095
|
+
if (!fs5.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
2891
3096
|
let dirs;
|
|
2892
3097
|
try {
|
|
2893
|
-
dirs =
|
|
3098
|
+
dirs = fs5.readdirSync(projectsDir);
|
|
2894
3099
|
} catch {
|
|
2895
3100
|
return freezeClaudeCost(acc);
|
|
2896
3101
|
}
|
|
@@ -2901,10 +3106,10 @@ function loadClaudeCost(start, end, projectsDir) {
|
|
|
2901
3106
|
}
|
|
2902
3107
|
async function loadClaudeCostAsync(start, end, projectsDir) {
|
|
2903
3108
|
const acc = emptyClaudeCostAccumulator();
|
|
2904
|
-
if (!
|
|
3109
|
+
if (!fs5.existsSync(projectsDir)) return freezeClaudeCost(acc);
|
|
2905
3110
|
let dirs;
|
|
2906
3111
|
try {
|
|
2907
|
-
dirs =
|
|
3112
|
+
dirs = fs5.readdirSync(projectsDir);
|
|
2908
3113
|
} catch {
|
|
2909
3114
|
return freezeClaudeCost(acc);
|
|
2910
3115
|
}
|
|
@@ -2917,11 +3122,12 @@ async function loadClaudeCostAsync(start, end, projectsDir) {
|
|
|
2917
3122
|
function processCodexCostFile(filePath, start, end, acc) {
|
|
2918
3123
|
let lines;
|
|
2919
3124
|
try {
|
|
2920
|
-
lines =
|
|
3125
|
+
lines = fs5.readFileSync(filePath, "utf-8").split("\n");
|
|
2921
3126
|
} catch {
|
|
2922
3127
|
return;
|
|
2923
3128
|
}
|
|
2924
3129
|
let sessionStart = "";
|
|
3130
|
+
let model = "";
|
|
2925
3131
|
let lastTotalInput = 0;
|
|
2926
3132
|
let lastTotalCached = 0;
|
|
2927
3133
|
let lastTotalOutput = 0;
|
|
@@ -2939,6 +3145,10 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
2939
3145
|
sessionStart = String(p["timestamp"] ?? "");
|
|
2940
3146
|
continue;
|
|
2941
3147
|
}
|
|
3148
|
+
if (entry.type === "turn_context" && typeof p["model"] === "string") {
|
|
3149
|
+
model = p["model"];
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
2942
3152
|
if (entry.type === "event_msg" && p["type"] === "token_count") {
|
|
2943
3153
|
const info = p["info"] ?? {};
|
|
2944
3154
|
const usage = info["total_token_usage"] ?? {};
|
|
@@ -2953,40 +3163,45 @@ function processCodexCostFile(filePath, start, end, acc) {
|
|
|
2953
3163
|
if (!sessionStart) return;
|
|
2954
3164
|
const ts = new Date(sessionStart);
|
|
2955
3165
|
if (ts < start || ts > end) return;
|
|
2956
|
-
const
|
|
2957
|
-
|
|
3166
|
+
const cost = codexSessionCost(model, {
|
|
3167
|
+
input: lastTotalInput,
|
|
3168
|
+
cached: lastTotalCached,
|
|
3169
|
+
output: lastTotalOutput
|
|
3170
|
+
});
|
|
2958
3171
|
acc.total += cost;
|
|
2959
3172
|
acc.toolCalls += sessionToolCalls;
|
|
2960
3173
|
const dateKey = sessionStart.slice(0, 10);
|
|
2961
3174
|
acc.byDay.set(dateKey, (acc.byDay.get(dateKey) ?? 0) + cost);
|
|
3175
|
+
const normModel = normalizeModel(model || "gpt-5");
|
|
3176
|
+
acc.byModel.set(normModel, (acc.byModel.get(normModel) ?? 0) + cost);
|
|
2962
3177
|
}
|
|
2963
3178
|
function listCodexSessionFiles(sessionsBase) {
|
|
2964
3179
|
const jsonlFiles = [];
|
|
2965
|
-
if (!
|
|
3180
|
+
if (!fs5.existsSync(sessionsBase)) return jsonlFiles;
|
|
2966
3181
|
try {
|
|
2967
|
-
for (const year of
|
|
2968
|
-
const yearPath =
|
|
3182
|
+
for (const year of fs5.readdirSync(sessionsBase)) {
|
|
3183
|
+
const yearPath = path6.join(sessionsBase, year);
|
|
2969
3184
|
try {
|
|
2970
|
-
if (!
|
|
3185
|
+
if (!fs5.statSync(yearPath).isDirectory()) continue;
|
|
2971
3186
|
} catch {
|
|
2972
3187
|
continue;
|
|
2973
3188
|
}
|
|
2974
|
-
for (const month of
|
|
2975
|
-
const monthPath =
|
|
3189
|
+
for (const month of fs5.readdirSync(yearPath)) {
|
|
3190
|
+
const monthPath = path6.join(yearPath, month);
|
|
2976
3191
|
try {
|
|
2977
|
-
if (!
|
|
3192
|
+
if (!fs5.statSync(monthPath).isDirectory()) continue;
|
|
2978
3193
|
} catch {
|
|
2979
3194
|
continue;
|
|
2980
3195
|
}
|
|
2981
|
-
for (const day of
|
|
2982
|
-
const dayPath =
|
|
3196
|
+
for (const day of fs5.readdirSync(monthPath)) {
|
|
3197
|
+
const dayPath = path6.join(monthPath, day);
|
|
2983
3198
|
try {
|
|
2984
|
-
if (!
|
|
3199
|
+
if (!fs5.statSync(dayPath).isDirectory()) continue;
|
|
2985
3200
|
} catch {
|
|
2986
3201
|
continue;
|
|
2987
3202
|
}
|
|
2988
|
-
for (const file of
|
|
2989
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
3203
|
+
for (const file of fs5.readdirSync(dayPath)) {
|
|
3204
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path6.join(dayPath, file));
|
|
2990
3205
|
}
|
|
2991
3206
|
}
|
|
2992
3207
|
}
|
|
@@ -2996,16 +3211,33 @@ function listCodexSessionFiles(sessionsBase) {
|
|
|
2996
3211
|
}
|
|
2997
3212
|
return jsonlFiles;
|
|
2998
3213
|
}
|
|
3214
|
+
function mergeByModel(...maps) {
|
|
3215
|
+
const out = /* @__PURE__ */ new Map();
|
|
3216
|
+
for (const m of maps) {
|
|
3217
|
+
for (const [k, v] of m) out.set(k, (out.get(k) ?? 0) + v);
|
|
3218
|
+
}
|
|
3219
|
+
return out;
|
|
3220
|
+
}
|
|
2999
3221
|
function loadCodexCost(start, end, sessionsBase) {
|
|
3000
|
-
const acc = {
|
|
3222
|
+
const acc = {
|
|
3223
|
+
total: 0,
|
|
3224
|
+
toolCalls: 0,
|
|
3225
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
3226
|
+
byModel: /* @__PURE__ */ new Map()
|
|
3227
|
+
};
|
|
3001
3228
|
const files = listCodexSessionFiles(sessionsBase);
|
|
3002
3229
|
for (const filePath of files) {
|
|
3003
3230
|
processCodexCostFile(filePath, start, end, acc);
|
|
3004
3231
|
}
|
|
3005
|
-
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
3232
|
+
return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
|
|
3006
3233
|
}
|
|
3007
3234
|
async function loadCodexCostAsync(start, end, sessionsBase) {
|
|
3008
|
-
const acc = {
|
|
3235
|
+
const acc = {
|
|
3236
|
+
total: 0,
|
|
3237
|
+
toolCalls: 0,
|
|
3238
|
+
byDay: /* @__PURE__ */ new Map(),
|
|
3239
|
+
byModel: /* @__PURE__ */ new Map()
|
|
3240
|
+
};
|
|
3009
3241
|
const files = listCodexSessionFiles(sessionsBase);
|
|
3010
3242
|
const CHUNK_SIZE = 5;
|
|
3011
3243
|
for (let i = 0; i < files.length; i++) {
|
|
@@ -3014,12 +3246,12 @@ async function loadCodexCostAsync(start, end, sessionsBase) {
|
|
|
3014
3246
|
await new Promise((resolve) => setImmediate(resolve));
|
|
3015
3247
|
}
|
|
3016
3248
|
}
|
|
3017
|
-
return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
|
|
3249
|
+
return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
|
|
3018
3250
|
}
|
|
3019
|
-
function
|
|
3251
|
+
function geminiPriceFor2(model) {
|
|
3020
3252
|
let tuple = pricingFor(model);
|
|
3021
3253
|
if (!tuple && /^gemini-/i.test(model)) {
|
|
3022
|
-
for (const proxy of
|
|
3254
|
+
for (const proxy of GEMINI_FALLBACK_MODELS2) {
|
|
3023
3255
|
tuple = pricingFor(proxy);
|
|
3024
3256
|
if (tuple) break;
|
|
3025
3257
|
}
|
|
@@ -3050,13 +3282,13 @@ function freezeGeminiCost(acc) {
|
|
|
3050
3282
|
function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
3051
3283
|
const startMs = start.getTime();
|
|
3052
3284
|
try {
|
|
3053
|
-
if (
|
|
3285
|
+
if (fs5.statSync(filePath).mtimeMs < startMs) return;
|
|
3054
3286
|
} catch {
|
|
3055
3287
|
return;
|
|
3056
3288
|
}
|
|
3057
3289
|
let raw;
|
|
3058
3290
|
try {
|
|
3059
|
-
raw =
|
|
3291
|
+
raw = fs5.readFileSync(filePath, "utf-8");
|
|
3060
3292
|
} catch {
|
|
3061
3293
|
return;
|
|
3062
3294
|
}
|
|
@@ -3077,7 +3309,7 @@ function processGeminiCostFile(filePath, projectKey, start, end, acc) {
|
|
|
3077
3309
|
}
|
|
3078
3310
|
const ts = new Date(entry.timestamp);
|
|
3079
3311
|
if (ts < start || ts > end) continue;
|
|
3080
|
-
const price =
|
|
3312
|
+
const price = geminiPriceFor2(entry.model);
|
|
3081
3313
|
if (!price) continue;
|
|
3082
3314
|
const inp = entry.tokens.input ?? 0;
|
|
3083
3315
|
const out = entry.tokens.output ?? 0;
|
|
@@ -3105,30 +3337,30 @@ function listGeminiSessionFiles(geminiTmpDir) {
|
|
|
3105
3337
|
const out = [];
|
|
3106
3338
|
let dirs;
|
|
3107
3339
|
try {
|
|
3108
|
-
if (!
|
|
3109
|
-
dirs =
|
|
3340
|
+
if (!fs5.statSync(geminiTmpDir).isDirectory()) return out;
|
|
3341
|
+
dirs = fs5.readdirSync(geminiTmpDir);
|
|
3110
3342
|
} catch {
|
|
3111
3343
|
return out;
|
|
3112
3344
|
}
|
|
3113
3345
|
for (const proj of dirs) {
|
|
3114
|
-
const chatsDir =
|
|
3346
|
+
const chatsDir = path6.join(geminiTmpDir, proj, "chats");
|
|
3115
3347
|
let files;
|
|
3116
3348
|
try {
|
|
3117
|
-
if (!
|
|
3118
|
-
files =
|
|
3349
|
+
if (!fs5.statSync(chatsDir).isDirectory()) continue;
|
|
3350
|
+
files = fs5.readdirSync(chatsDir);
|
|
3119
3351
|
} catch {
|
|
3120
3352
|
continue;
|
|
3121
3353
|
}
|
|
3122
3354
|
for (const f of files) {
|
|
3123
3355
|
if (!f.endsWith(".jsonl")) continue;
|
|
3124
|
-
out.push({ projectKey: proj, file:
|
|
3356
|
+
out.push({ projectKey: proj, file: path6.join(chatsDir, f) });
|
|
3125
3357
|
}
|
|
3126
3358
|
}
|
|
3127
3359
|
return out;
|
|
3128
3360
|
}
|
|
3129
3361
|
function loadGeminiCost(start, end, geminiTmpDir) {
|
|
3130
3362
|
const acc = emptyGeminiAccumulator();
|
|
3131
|
-
if (!
|
|
3363
|
+
if (!fs5.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
3132
3364
|
for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
|
|
3133
3365
|
processGeminiCostFile(file, projectKey, start, end, acc);
|
|
3134
3366
|
}
|
|
@@ -3136,7 +3368,7 @@ function loadGeminiCost(start, end, geminiTmpDir) {
|
|
|
3136
3368
|
}
|
|
3137
3369
|
async function loadGeminiCostAsync(start, end, geminiTmpDir) {
|
|
3138
3370
|
const acc = emptyGeminiAccumulator();
|
|
3139
|
-
if (!
|
|
3371
|
+
if (!fs5.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
|
|
3140
3372
|
const files = listGeminiSessionFiles(geminiTmpDir);
|
|
3141
3373
|
const CHUNK_SIZE = 5;
|
|
3142
3374
|
for (let i = 0; i < files.length; i++) {
|
|
@@ -3149,11 +3381,11 @@ async function loadGeminiCostAsync(start, end, geminiTmpDir) {
|
|
|
3149
3381
|
}
|
|
3150
3382
|
function aggregateReportFromAudit(period, opts = {}) {
|
|
3151
3383
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
3152
|
-
const auditLogPath2 = opts.auditLogPath ??
|
|
3153
|
-
const claudeProjectsDir = opts.claudeProjectsDir ??
|
|
3154
|
-
const codexSessionsDir = opts.codexSessionsDir ??
|
|
3155
|
-
const geminiTmpDir = opts.geminiTmpDir ??
|
|
3156
|
-
const hasAuditFile =
|
|
3384
|
+
const auditLogPath2 = opts.auditLogPath ?? path6.join(os6.homedir(), ".node9", "audit.log");
|
|
3385
|
+
const claudeProjectsDir = opts.claudeProjectsDir ?? path6.join(os6.homedir(), ".claude", "projects");
|
|
3386
|
+
const codexSessionsDir = opts.codexSessionsDir ?? path6.join(os6.homedir(), ".codex", "sessions");
|
|
3387
|
+
const geminiTmpDir = opts.geminiTmpDir ?? path6.join(os6.homedir(), ".gemini", "tmp");
|
|
3388
|
+
const hasAuditFile = fs5.existsSync(auditLogPath2);
|
|
3157
3389
|
const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath2);
|
|
3158
3390
|
const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
|
|
3159
3391
|
const { start, end } = getDateRange(period, now);
|
|
@@ -3315,7 +3547,7 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
3315
3547
|
cacheWriteTokens: claudeCost.cacheWriteTokens,
|
|
3316
3548
|
cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
|
|
3317
3549
|
byDay: claudeCost.byDay,
|
|
3318
|
-
byModel: claudeCost.byModel,
|
|
3550
|
+
byModel: mergeByModel(claudeCost.byModel, codexCost.byModel),
|
|
3319
3551
|
byProject: claudeCost.byProject
|
|
3320
3552
|
},
|
|
3321
3553
|
toolMap,
|
|
@@ -3329,43 +3561,32 @@ function aggregateReportFromAudit(period, opts = {}) {
|
|
|
3329
3561
|
};
|
|
3330
3562
|
return { data, hasAuditFile, responseDlpEntries };
|
|
3331
3563
|
}
|
|
3332
|
-
var TEST_COMMAND_RE, SUPERSEDE_WINDOW_MS,
|
|
3564
|
+
var TEST_COMMAND_RE, SUPERSEDE_WINDOW_MS, GEMINI_FALLBACK_MODELS2;
|
|
3333
3565
|
var init_report_audit = __esm({
|
|
3334
3566
|
"src/cli/aggregate/report-audit.ts"() {
|
|
3335
3567
|
"use strict";
|
|
3336
3568
|
init_costSync();
|
|
3337
3569
|
init_litellm();
|
|
3570
|
+
init_cost_codex();
|
|
3338
3571
|
TEST_COMMAND_RE = /(?:^|\s)(npm\s+(?:run\s+)?test|npx\s+(?:vitest|jest|mocha)|yarn\s+(?:run\s+)?test|pnpm\s+(?:run\s+)?test|vitest|jest|mocha|pytest|py\.test|cargo\s+test|go\s+test|bundle\s+exec\s+rspec|rspec|phpunit|dotnet\s+test)\b/i;
|
|
3339
3572
|
SUPERSEDE_WINDOW_MS = 6e4;
|
|
3340
|
-
|
|
3341
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
3342
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
3343
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
3344
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
3345
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
3346
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
3347
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
3348
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
3349
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
3350
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
3351
|
-
};
|
|
3352
|
-
GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
3573
|
+
GEMINI_FALLBACK_MODELS2 = ["gemini-2.5-flash", "gemini-2.0-flash"];
|
|
3353
3574
|
}
|
|
3354
3575
|
});
|
|
3355
3576
|
|
|
3356
3577
|
// src/utils/provenance.ts
|
|
3357
|
-
import
|
|
3358
|
-
import
|
|
3578
|
+
import path7 from "path";
|
|
3579
|
+
import os7 from "os";
|
|
3359
3580
|
var USER_PREFIXES;
|
|
3360
3581
|
var init_provenance = __esm({
|
|
3361
3582
|
"src/utils/provenance.ts"() {
|
|
3362
3583
|
"use strict";
|
|
3363
3584
|
USER_PREFIXES = [
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3585
|
+
path7.join(os7.homedir(), "bin"),
|
|
3586
|
+
path7.join(os7.homedir(), ".local", "bin"),
|
|
3587
|
+
path7.join(os7.homedir(), ".cargo", "bin"),
|
|
3588
|
+
path7.join(os7.homedir(), ".npm-global", "bin"),
|
|
3589
|
+
path7.join(os7.homedir(), ".volta", "bin")
|
|
3369
3590
|
];
|
|
3370
3591
|
}
|
|
3371
3592
|
});
|
|
@@ -3469,24 +3690,20 @@ var init_scan_history = __esm({
|
|
|
3469
3690
|
|
|
3470
3691
|
// src/cli/commands/scan.ts
|
|
3471
3692
|
import chalk4 from "chalk";
|
|
3472
|
-
import
|
|
3473
|
-
import
|
|
3474
|
-
import
|
|
3693
|
+
import fs6 from "fs";
|
|
3694
|
+
import path8 from "path";
|
|
3695
|
+
import os8 from "os";
|
|
3475
3696
|
import stringWidth2 from "string-width";
|
|
3476
3697
|
function claudeModelPrice2(model) {
|
|
3477
|
-
const
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
}
|
|
3481
|
-
return null;
|
|
3698
|
+
const t = pricingFor(model);
|
|
3699
|
+
if (!t) return null;
|
|
3700
|
+
const [i, o, cw, cr] = t;
|
|
3701
|
+
return { i, o, cw, cr };
|
|
3482
3702
|
}
|
|
3483
3703
|
function geminiModelPrice(model) {
|
|
3484
|
-
const
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
}
|
|
3488
|
-
if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
|
|
3489
|
-
return null;
|
|
3704
|
+
const p = geminiPriceFor(model);
|
|
3705
|
+
if (!p) return null;
|
|
3706
|
+
return { i: p.input, o: p.output, cr: p.cacheRead };
|
|
3490
3707
|
}
|
|
3491
3708
|
function isNode9SelfOutput(text) {
|
|
3492
3709
|
let hits = 0;
|
|
@@ -3624,7 +3841,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
3624
3841
|
const sessionId = file.replace(/\.jsonl$/, "");
|
|
3625
3842
|
let raw;
|
|
3626
3843
|
try {
|
|
3627
|
-
raw =
|
|
3844
|
+
raw = fs6.readFileSync(path8.join(projPath, file), "utf-8");
|
|
3628
3845
|
} catch {
|
|
3629
3846
|
return;
|
|
3630
3847
|
}
|
|
@@ -3676,7 +3893,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
3676
3893
|
if (block.type !== "tool_result") continue;
|
|
3677
3894
|
const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
|
|
3678
3895
|
if (filePath) {
|
|
3679
|
-
const ext =
|
|
3896
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
3680
3897
|
if (CODE_EXTENSIONS.has(ext)) continue;
|
|
3681
3898
|
}
|
|
3682
3899
|
const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
|
|
@@ -3733,7 +3950,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
3733
3950
|
const rawCmd = String(input.command ?? "").trimStart();
|
|
3734
3951
|
if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
|
|
3735
3952
|
const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
|
|
3736
|
-
const inputFileExt = inputFilePath ?
|
|
3953
|
+
const inputFileExt = inputFilePath ? path8.extname(inputFilePath).toLowerCase() : "";
|
|
3737
3954
|
if (CODE_EXTENSIONS.has(inputFileExt)) continue;
|
|
3738
3955
|
const dlpMatch = scanArgs(input);
|
|
3739
3956
|
if (dlpMatch) {
|
|
@@ -3830,19 +4047,19 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
|
|
|
3830
4047
|
}
|
|
3831
4048
|
}
|
|
3832
4049
|
async function processClaudeProjectAsync(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
|
|
3833
|
-
const projPath =
|
|
4050
|
+
const projPath = path8.join(projectsDir, proj);
|
|
3834
4051
|
try {
|
|
3835
|
-
if (!
|
|
4052
|
+
if (!fs6.statSync(projPath).isDirectory()) return;
|
|
3836
4053
|
} catch {
|
|
3837
4054
|
return;
|
|
3838
4055
|
}
|
|
3839
|
-
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(
|
|
4056
|
+
const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os8.homedir(), "~")).slice(
|
|
3840
4057
|
0,
|
|
3841
4058
|
40
|
|
3842
4059
|
);
|
|
3843
4060
|
let files;
|
|
3844
4061
|
try {
|
|
3845
|
-
files =
|
|
4062
|
+
files = fs6.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
|
|
3846
4063
|
} catch {
|
|
3847
4064
|
return;
|
|
3848
4065
|
}
|
|
@@ -3880,12 +4097,12 @@ function emptyClaudeScan() {
|
|
|
3880
4097
|
};
|
|
3881
4098
|
}
|
|
3882
4099
|
async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
|
|
3883
|
-
const projectsDir =
|
|
4100
|
+
const projectsDir = path8.join(os8.homedir(), ".claude", "projects");
|
|
3884
4101
|
const result = emptyClaudeScan();
|
|
3885
|
-
if (!
|
|
4102
|
+
if (!fs6.existsSync(projectsDir)) return result;
|
|
3886
4103
|
let projDirs;
|
|
3887
4104
|
try {
|
|
3888
|
-
projDirs =
|
|
4105
|
+
projDirs = fs6.readdirSync(projectsDir);
|
|
3889
4106
|
} catch {
|
|
3890
4107
|
return result;
|
|
3891
4108
|
}
|
|
@@ -3906,7 +4123,7 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
|
|
|
3906
4123
|
return result;
|
|
3907
4124
|
}
|
|
3908
4125
|
function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
3909
|
-
const tmpDir =
|
|
4126
|
+
const tmpDir = path8.join(os8.homedir(), ".gemini", "tmp");
|
|
3910
4127
|
const result = {
|
|
3911
4128
|
filesScanned: 0,
|
|
3912
4129
|
sessions: 0,
|
|
@@ -3921,50 +4138,64 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
3921
4138
|
sessionsWithEarlySecrets: 0
|
|
3922
4139
|
};
|
|
3923
4140
|
const dedup = emptyScanDedup();
|
|
3924
|
-
if (!
|
|
4141
|
+
if (!fs6.existsSync(tmpDir)) return result;
|
|
3925
4142
|
let slugDirs;
|
|
3926
4143
|
try {
|
|
3927
|
-
slugDirs =
|
|
4144
|
+
slugDirs = fs6.readdirSync(tmpDir);
|
|
3928
4145
|
} catch {
|
|
3929
4146
|
return result;
|
|
3930
4147
|
}
|
|
3931
4148
|
const ruleSources = buildRuleSources();
|
|
3932
4149
|
for (const slug of slugDirs) {
|
|
3933
|
-
const slugPath =
|
|
4150
|
+
const slugPath = path8.join(tmpDir, slug);
|
|
3934
4151
|
try {
|
|
3935
|
-
if (!
|
|
4152
|
+
if (!fs6.statSync(slugPath).isDirectory()) continue;
|
|
3936
4153
|
} catch {
|
|
3937
4154
|
continue;
|
|
3938
4155
|
}
|
|
3939
4156
|
let projLabel = stripTerminalEscapes(slug).slice(0, 40);
|
|
3940
4157
|
try {
|
|
3941
4158
|
projLabel = stripTerminalEscapes(
|
|
3942
|
-
|
|
3943
|
-
).replace(
|
|
4159
|
+
fs6.readFileSync(path8.join(slugPath, ".project_root"), "utf-8").trim()
|
|
4160
|
+
).replace(os8.homedir(), "~").slice(0, 40);
|
|
3944
4161
|
} catch {
|
|
3945
4162
|
}
|
|
3946
|
-
const chatsDir =
|
|
3947
|
-
if (!
|
|
4163
|
+
const chatsDir = path8.join(slugPath, "chats");
|
|
4164
|
+
if (!fs6.existsSync(chatsDir)) continue;
|
|
3948
4165
|
let chatFiles;
|
|
3949
4166
|
try {
|
|
3950
|
-
chatFiles =
|
|
4167
|
+
chatFiles = fs6.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
|
|
3951
4168
|
} catch {
|
|
3952
4169
|
continue;
|
|
3953
4170
|
}
|
|
4171
|
+
const seenSessions = /* @__PURE__ */ new Set();
|
|
3954
4172
|
for (const chatFile of chatFiles) {
|
|
4173
|
+
const sessionId = chatFile.replace(/\.jsonl?$/, "");
|
|
4174
|
+
if (seenSessions.has(sessionId)) continue;
|
|
4175
|
+
seenSessions.add(sessionId);
|
|
3955
4176
|
result.filesScanned++;
|
|
3956
4177
|
onProgress?.(result.filesScanned);
|
|
3957
|
-
const sessionId = chatFile.replace(/\.json$/, "");
|
|
3958
4178
|
let raw;
|
|
3959
4179
|
try {
|
|
3960
|
-
raw =
|
|
4180
|
+
raw = fs6.readFileSync(path8.join(chatsDir, chatFile), "utf-8");
|
|
3961
4181
|
} catch {
|
|
3962
4182
|
continue;
|
|
3963
4183
|
}
|
|
3964
4184
|
const sessionCalls = [];
|
|
3965
4185
|
let session;
|
|
3966
4186
|
try {
|
|
3967
|
-
|
|
4187
|
+
if (chatFile.endsWith(".jsonl")) {
|
|
4188
|
+
const messages = raw.split("\n").filter((l) => l.trim()).map((l) => {
|
|
4189
|
+
try {
|
|
4190
|
+
return JSON.parse(l);
|
|
4191
|
+
} catch {
|
|
4192
|
+
return null;
|
|
4193
|
+
}
|
|
4194
|
+
}).filter((m) => m !== null);
|
|
4195
|
+
session = { messages };
|
|
4196
|
+
} else {
|
|
4197
|
+
session = JSON.parse(raw);
|
|
4198
|
+
}
|
|
3968
4199
|
} catch {
|
|
3969
4200
|
continue;
|
|
3970
4201
|
}
|
|
@@ -4119,7 +4350,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
|
|
|
4119
4350
|
return result;
|
|
4120
4351
|
}
|
|
4121
4352
|
function scanCodexHistory(startDate, onProgress, onLine) {
|
|
4122
|
-
const sessionsBase =
|
|
4353
|
+
const sessionsBase = path8.join(os8.homedir(), ".codex", "sessions");
|
|
4123
4354
|
const result = {
|
|
4124
4355
|
filesScanned: 0,
|
|
4125
4356
|
sessions: 0,
|
|
@@ -4134,32 +4365,32 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4134
4365
|
sessionsWithEarlySecrets: 0
|
|
4135
4366
|
};
|
|
4136
4367
|
const dedup = emptyScanDedup();
|
|
4137
|
-
if (!
|
|
4368
|
+
if (!fs6.existsSync(sessionsBase)) return result;
|
|
4138
4369
|
const jsonlFiles = [];
|
|
4139
4370
|
try {
|
|
4140
|
-
for (const year of
|
|
4141
|
-
const yearPath =
|
|
4371
|
+
for (const year of fs6.readdirSync(sessionsBase)) {
|
|
4372
|
+
const yearPath = path8.join(sessionsBase, year);
|
|
4142
4373
|
try {
|
|
4143
|
-
if (!
|
|
4374
|
+
if (!fs6.statSync(yearPath).isDirectory()) continue;
|
|
4144
4375
|
} catch {
|
|
4145
4376
|
continue;
|
|
4146
4377
|
}
|
|
4147
|
-
for (const month of
|
|
4148
|
-
const monthPath =
|
|
4378
|
+
for (const month of fs6.readdirSync(yearPath)) {
|
|
4379
|
+
const monthPath = path8.join(yearPath, month);
|
|
4149
4380
|
try {
|
|
4150
|
-
if (!
|
|
4381
|
+
if (!fs6.statSync(monthPath).isDirectory()) continue;
|
|
4151
4382
|
} catch {
|
|
4152
4383
|
continue;
|
|
4153
4384
|
}
|
|
4154
|
-
for (const day of
|
|
4155
|
-
const dayPath =
|
|
4385
|
+
for (const day of fs6.readdirSync(monthPath)) {
|
|
4386
|
+
const dayPath = path8.join(monthPath, day);
|
|
4156
4387
|
try {
|
|
4157
|
-
if (!
|
|
4388
|
+
if (!fs6.statSync(dayPath).isDirectory()) continue;
|
|
4158
4389
|
} catch {
|
|
4159
4390
|
continue;
|
|
4160
4391
|
}
|
|
4161
|
-
for (const file of
|
|
4162
|
-
if (file.endsWith(".jsonl")) jsonlFiles.push(
|
|
4392
|
+
for (const file of fs6.readdirSync(dayPath)) {
|
|
4393
|
+
if (file.endsWith(".jsonl")) jsonlFiles.push(path8.join(dayPath, file));
|
|
4163
4394
|
}
|
|
4164
4395
|
}
|
|
4165
4396
|
}
|
|
@@ -4173,7 +4404,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4173
4404
|
onProgress?.(result.filesScanned);
|
|
4174
4405
|
let lines;
|
|
4175
4406
|
try {
|
|
4176
|
-
lines =
|
|
4407
|
+
lines = fs6.readFileSync(filePath, "utf-8").split("\n");
|
|
4177
4408
|
} catch {
|
|
4178
4409
|
continue;
|
|
4179
4410
|
}
|
|
@@ -4185,6 +4416,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4185
4416
|
let lastTotalInput = 0;
|
|
4186
4417
|
let lastTotalCached = 0;
|
|
4187
4418
|
let lastTotalOutput = 0;
|
|
4419
|
+
let model = "";
|
|
4188
4420
|
for (const line of lines) {
|
|
4189
4421
|
if (!line.trim()) continue;
|
|
4190
4422
|
onLine?.();
|
|
@@ -4199,7 +4431,11 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4199
4431
|
sessionId = String(payload["id"] ?? filePath);
|
|
4200
4432
|
startTime = String(payload["timestamp"] ?? "");
|
|
4201
4433
|
const cwd = String(payload["cwd"] ?? "");
|
|
4202
|
-
projLabel = stripTerminalEscapes(cwd.replace(
|
|
4434
|
+
projLabel = stripTerminalEscapes(cwd.replace(os8.homedir(), "~")).slice(0, 40);
|
|
4435
|
+
continue;
|
|
4436
|
+
}
|
|
4437
|
+
if (entry.type === "turn_context" && typeof payload["model"] === "string") {
|
|
4438
|
+
model = payload["model"];
|
|
4203
4439
|
continue;
|
|
4204
4440
|
}
|
|
4205
4441
|
if (entry.type === "event_msg" && payload["type"] === "token_count") {
|
|
@@ -4345,13 +4581,16 @@ function scanCodexHistory(startDate, onProgress, onLine) {
|
|
|
4345
4581
|
}
|
|
4346
4582
|
}
|
|
4347
4583
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4584
|
+
result.totalCostUSD += codexSessionCost(model, {
|
|
4585
|
+
input: lastTotalInput,
|
|
4586
|
+
cached: lastTotalCached,
|
|
4587
|
+
output: lastTotalOutput
|
|
4588
|
+
});
|
|
4350
4589
|
result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
|
|
4351
4590
|
}
|
|
4352
4591
|
return result;
|
|
4353
4592
|
}
|
|
4354
|
-
var
|
|
4593
|
+
var CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
|
|
4355
4594
|
var init_scan = __esm({
|
|
4356
4595
|
"src/cli/commands/scan.ts"() {
|
|
4357
4596
|
"use strict";
|
|
@@ -4360,6 +4599,9 @@ var init_scan = __esm({
|
|
|
4360
4599
|
init_policy();
|
|
4361
4600
|
init_dist();
|
|
4362
4601
|
init_dlp();
|
|
4602
|
+
init_litellm();
|
|
4603
|
+
init_cost_gemini();
|
|
4604
|
+
init_cost_codex();
|
|
4363
4605
|
init_hook_payload();
|
|
4364
4606
|
init_dist();
|
|
4365
4607
|
init_scan_summary();
|
|
@@ -4369,26 +4611,6 @@ var init_scan = __esm({
|
|
|
4369
4611
|
init_protection();
|
|
4370
4612
|
init_scan_json();
|
|
4371
4613
|
init_scan_history();
|
|
4372
|
-
CLAUDE_PRICING2 = {
|
|
4373
|
-
"claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
4374
|
-
"claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
|
|
4375
|
-
"claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
|
|
4376
|
-
"claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
4377
|
-
"claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
4378
|
-
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
4379
|
-
"claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
4380
|
-
"claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
|
|
4381
|
-
"claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
|
|
4382
|
-
"claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
|
|
4383
|
-
};
|
|
4384
|
-
GEMINI_PRICING = {
|
|
4385
|
-
"gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
|
|
4386
|
-
"gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
|
|
4387
|
-
"gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
|
|
4388
|
-
"gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
|
|
4389
|
-
"gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
|
|
4390
|
-
"gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
|
|
4391
|
-
};
|
|
4392
4614
|
CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
4393
4615
|
".ts",
|
|
4394
4616
|
".tsx",
|
|
@@ -4451,23 +4673,23 @@ var init_scan = __esm({
|
|
|
4451
4673
|
});
|
|
4452
4674
|
|
|
4453
4675
|
// src/tui/dashboard/data.ts
|
|
4454
|
-
import
|
|
4455
|
-
import
|
|
4456
|
-
import
|
|
4676
|
+
import fs7 from "fs";
|
|
4677
|
+
import os9 from "os";
|
|
4678
|
+
import path9 from "path";
|
|
4457
4679
|
import http from "http";
|
|
4458
4680
|
function auditLogPath() {
|
|
4459
|
-
return
|
|
4681
|
+
return path9.join(os9.homedir(), ".node9", "audit.log");
|
|
4460
4682
|
}
|
|
4461
4683
|
function readAuditEntriesAsync(chunkSize = 1e3, customPath) {
|
|
4462
4684
|
return new Promise((resolve) => {
|
|
4463
4685
|
const p = customPath ?? auditLogPath();
|
|
4464
|
-
if (!
|
|
4686
|
+
if (!fs7.existsSync(p)) {
|
|
4465
4687
|
resolve([]);
|
|
4466
4688
|
return;
|
|
4467
4689
|
}
|
|
4468
4690
|
let raw;
|
|
4469
4691
|
try {
|
|
4470
|
-
raw =
|
|
4692
|
+
raw = fs7.readFileSync(p, "utf8");
|
|
4471
4693
|
} catch {
|
|
4472
4694
|
resolve([]);
|
|
4473
4695
|
return;
|
|
@@ -4596,15 +4818,16 @@ function loadBlast() {
|
|
|
4596
4818
|
}
|
|
4597
4819
|
}
|
|
4598
4820
|
function shortenPath(p) {
|
|
4599
|
-
const home =
|
|
4821
|
+
const home = os9.homedir();
|
|
4600
4822
|
return p.startsWith(home) ? p.replace(home, "~") : p;
|
|
4601
4823
|
}
|
|
4602
4824
|
async function loadReportAuditAsync(period) {
|
|
4603
|
-
const claudeProjectsDir =
|
|
4604
|
-
const codexSessionsDir =
|
|
4605
|
-
const geminiTmpDir =
|
|
4825
|
+
const claudeProjectsDir = path9.join(os9.homedir(), ".claude", "projects");
|
|
4826
|
+
const codexSessionsDir = path9.join(os9.homedir(), ".codex", "sessions");
|
|
4827
|
+
const geminiTmpDir = path9.join(os9.homedir(), ".gemini", "tmp");
|
|
4606
4828
|
const { start, end } = getDateRange(period, /* @__PURE__ */ new Date());
|
|
4607
4829
|
const entries = await readAuditEntriesAsync();
|
|
4830
|
+
void ensurePricingLoaded();
|
|
4608
4831
|
const claudeCost = await loadClaudeCostAsync(start, end, claudeProjectsDir);
|
|
4609
4832
|
const codexCost = await loadCodexCostAsync(start, end, codexSessionsDir);
|
|
4610
4833
|
const geminiCost = await loadGeminiCostAsync(start, end, geminiTmpDir);
|
|
@@ -4945,6 +5168,7 @@ var init_data = __esm({
|
|
|
4945
5168
|
init_shields();
|
|
4946
5169
|
init_scan_watermark();
|
|
4947
5170
|
init_report_audit();
|
|
5171
|
+
init_litellm();
|
|
4948
5172
|
init_scan();
|
|
4949
5173
|
init_protection();
|
|
4950
5174
|
TEST_TOOLS = /* @__PURE__ */ new Set(["Bash", "bash"]);
|
|
@@ -5706,8 +5930,8 @@ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-run
|
|
|
5706
5930
|
function TopToolsProjects({ audit }) {
|
|
5707
5931
|
const data = audit?.data;
|
|
5708
5932
|
const tools = data ? [...data.toolMap.entries()].sort(([, a], [, b]) => b.calls - a.calls).slice(0, ROW_LIMIT) : [];
|
|
5709
|
-
const projects = data ? [...data.cost.byProject.entries()].map(([
|
|
5710
|
-
name: basenameOf(
|
|
5933
|
+
const projects = data ? [...data.cost.byProject.entries()].map(([path10, r]) => ({
|
|
5934
|
+
name: basenameOf(path10),
|
|
5711
5935
|
cost: r.cost,
|
|
5712
5936
|
tokens: r.inputTokens + r.outputTokens
|
|
5713
5937
|
})).sort((a, b) => b.cost - a.cost).slice(0, ROW_LIMIT) : [];
|
|
@@ -6144,8 +6368,8 @@ function pickTopLoopFile(loops) {
|
|
|
6144
6368
|
map.set(k, (map.get(k) ?? 0) + (l.count ?? 0));
|
|
6145
6369
|
}
|
|
6146
6370
|
if (map.size === 0) return void 0;
|
|
6147
|
-
const [
|
|
6148
|
-
return { path:
|
|
6371
|
+
const [path10, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
6372
|
+
return { path: path10, count };
|
|
6149
6373
|
}
|
|
6150
6374
|
var EMPTY_FILTERED_SCAN;
|
|
6151
6375
|
var init_derive = __esm({
|