@node9/proxy 1.33.0 → 1.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/dist/cli.js +1116 -1099
  2. package/dist/cli.mjs +1114 -1097
  3. package/dist/dashboard.mjs +335 -174
  4. package/package.json +1 -1
@@ -547,9 +547,9 @@ function matchesPattern(text, patterns) {
547
547
  const withoutDotSlash = text.replace(/^\.\//, "");
548
548
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
549
549
  }
550
- function getNestedValue(obj, path9) {
550
+ function getNestedValue(obj, path10) {
551
551
  if (!obj || typeof obj !== "object") return null;
552
- const segments = path9.split(".");
552
+ const segments = path10.split(".");
553
553
  for (const seg of segments) {
554
554
  if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
555
555
  }
@@ -2630,13 +2630,116 @@ var init_audit = __esm({
2630
2630
  });
2631
2631
 
2632
2632
  // src/pricing/litellm.ts
2633
+ import fs4 from "fs";
2634
+ import path5 from "path";
2635
+ import os5 from "os";
2633
2636
  function normalizeModel(raw) {
2634
2637
  return raw.replace(/-\d{8}$/, "").toLowerCase();
2635
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
+ }
2636
2731
  function pricingFor(model) {
2637
2732
  const norm = normalizeModel(model);
2638
2733
  const cached = lookupCache.get(norm);
2639
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
+ }
2640
2743
  const sources = [];
2641
2744
  if (memCache) sources.push(memCache);
2642
2745
  sources.push(BUNDLED_PRICING);
@@ -2661,11 +2764,12 @@ function pricingFor(model) {
2661
2764
  lookupCache.set(norm, resolved);
2662
2765
  return resolved;
2663
2766
  }
2664
- var BUNDLED_PRICING, TTL_MS, memCache, lookupCache;
2767
+ var LITELLM_URL, BUNDLED_PRICING, CACHE_FILE, TTL_MS, memCache, memCacheAt, diskChecked, lookupCache;
2665
2768
  var init_litellm = __esm({
2666
2769
  "src/pricing/litellm.ts"() {
2667
2770
  "use strict";
2668
2771
  init_audit();
2772
+ LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
2669
2773
  BUNDLED_PRICING = {
2670
2774
  // Anthropic
2671
2775
  "claude-opus-4": [5e-6, 25e-6, 625e-8, 5e-7],
@@ -2682,33 +2786,72 @@ var init_litellm = __esm({
2682
2786
  "claude-3-5-sonnet": [3e-6, 15e-6, 375e-8, 3e-7],
2683
2787
  "claude-3-5-haiku": [8e-7, 4e-6, 1e-6, 8e-8],
2684
2788
  "claude-3-haiku": [25e-8, 125e-8, 3e-7, 3e-8],
2685
- // 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.
2686
2793
  "gpt-4o": [5e-6, 15e-6, 0, 25e-7],
2687
2794
  "gpt-4o-mini": [15e-8, 6e-7, 0, 75e-9],
2688
- "gpt-5": [1e-5, 3e-5, 0, 5e-6],
2689
- // Google
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],
2690
2807
  "gemini-2.0-flash": [75e-9, 3e-7, 0, 0],
2691
2808
  "gemini-1.5-pro": [125e-8, 5e-6, 0, 0]
2692
2809
  };
2810
+ CACHE_FILE = () => path5.join(os5.homedir(), ".node9", "model-pricing.json");
2693
2811
  TTL_MS = 24 * 60 * 60 * 1e3;
2694
2812
  memCache = null;
2813
+ memCacheAt = 0;
2814
+ diskChecked = false;
2695
2815
  lookupCache = /* @__PURE__ */ new Map();
2696
2816
  }
2697
2817
  });
2698
2818
 
2699
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;
2700
2829
  var init_cost_codex = __esm({
2701
2830
  "src/cost-codex.ts"() {
2702
2831
  "use strict";
2703
2832
  init_litellm();
2833
+ CODEX_FALLBACK = [5e-6, 15e-6, 0, 25e-7];
2704
2834
  }
2705
2835
  });
2706
2836
 
2707
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;
2708
2850
  var init_cost_gemini = __esm({
2709
2851
  "src/cost-gemini.ts"() {
2710
2852
  "use strict";
2711
2853
  init_litellm();
2854
+ GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
2712
2855
  }
2713
2856
  });
2714
2857
 
@@ -2750,9 +2893,9 @@ var init_scan_watermark = __esm({
2750
2893
  });
2751
2894
 
2752
2895
  // src/cli/aggregate/report-audit.ts
2753
- import fs4 from "fs";
2754
- import os5 from "os";
2755
- import path5 from "path";
2896
+ import fs5 from "fs";
2897
+ import os6 from "os";
2898
+ import path6 from "path";
2756
2899
  function buildTestTimestamps(allEntries) {
2757
2900
  const testTs = /* @__PURE__ */ new Set();
2758
2901
  for (const e of allEntries) {
@@ -2833,8 +2976,8 @@ function getDateRange(period, now) {
2833
2976
  }
2834
2977
  }
2835
2978
  function parseAuditLog(logPath) {
2836
- if (!fs4.existsSync(logPath)) return [];
2837
- const raw = fs4.readFileSync(logPath, "utf-8");
2979
+ if (!fs5.existsSync(logPath)) return [];
2980
+ const raw = fs5.readFileSync(logPath, "utf-8");
2838
2981
  return raw.split("\n").flatMap((line) => {
2839
2982
  if (!line.trim()) return [];
2840
2983
  try {
@@ -2851,11 +2994,10 @@ function isDlp(checkedBy) {
2851
2994
  return !!checkedBy?.includes("dlp");
2852
2995
  }
2853
2996
  function claudeModelPrice(model) {
2854
- const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
2855
- for (const [key, p] of Object.entries(CLAUDE_PRICING)) {
2856
- if (base === key || base.startsWith(key + "-") || base.startsWith(key)) return p;
2857
- }
2858
- 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 };
2859
3001
  }
2860
3002
  function emptyClaudeCostAccumulator() {
2861
3003
  return {
@@ -2882,25 +3024,25 @@ function freezeClaudeCost(acc) {
2882
3024
  };
2883
3025
  }
2884
3026
  function processClaudeCostProject(proj, projectsDir, start, end, acc) {
2885
- const projPath = path5.join(projectsDir, proj);
3027
+ const projPath = path6.join(projectsDir, proj);
2886
3028
  let files;
2887
3029
  try {
2888
- const stat = fs4.statSync(projPath);
3030
+ const stat = fs5.statSync(projPath);
2889
3031
  if (!stat.isDirectory()) return;
2890
- files = fs4.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
3032
+ files = fs5.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
2891
3033
  } catch {
2892
3034
  return;
2893
3035
  }
2894
3036
  const startMs = start.getTime();
2895
3037
  for (const file of files) {
2896
- const filePath = path5.join(projPath, file);
3038
+ const filePath = path6.join(projPath, file);
2897
3039
  try {
2898
- if (fs4.statSync(filePath).mtimeMs < startMs) continue;
3040
+ if (fs5.statSync(filePath).mtimeMs < startMs) continue;
2899
3041
  } catch {
2900
3042
  continue;
2901
3043
  }
2902
3044
  try {
2903
- const raw = fs4.readFileSync(filePath, "utf-8");
3045
+ const raw = fs5.readFileSync(filePath, "utf-8");
2904
3046
  for (const line of raw.split("\n")) {
2905
3047
  if (!line.trim()) continue;
2906
3048
  let entry;
@@ -2950,10 +3092,10 @@ function processClaudeCostProject(proj, projectsDir, start, end, acc) {
2950
3092
  }
2951
3093
  function loadClaudeCost(start, end, projectsDir) {
2952
3094
  const acc = emptyClaudeCostAccumulator();
2953
- if (!fs4.existsSync(projectsDir)) return freezeClaudeCost(acc);
3095
+ if (!fs5.existsSync(projectsDir)) return freezeClaudeCost(acc);
2954
3096
  let dirs;
2955
3097
  try {
2956
- dirs = fs4.readdirSync(projectsDir);
3098
+ dirs = fs5.readdirSync(projectsDir);
2957
3099
  } catch {
2958
3100
  return freezeClaudeCost(acc);
2959
3101
  }
@@ -2964,10 +3106,10 @@ function loadClaudeCost(start, end, projectsDir) {
2964
3106
  }
2965
3107
  async function loadClaudeCostAsync(start, end, projectsDir) {
2966
3108
  const acc = emptyClaudeCostAccumulator();
2967
- if (!fs4.existsSync(projectsDir)) return freezeClaudeCost(acc);
3109
+ if (!fs5.existsSync(projectsDir)) return freezeClaudeCost(acc);
2968
3110
  let dirs;
2969
3111
  try {
2970
- dirs = fs4.readdirSync(projectsDir);
3112
+ dirs = fs5.readdirSync(projectsDir);
2971
3113
  } catch {
2972
3114
  return freezeClaudeCost(acc);
2973
3115
  }
@@ -2980,11 +3122,12 @@ async function loadClaudeCostAsync(start, end, projectsDir) {
2980
3122
  function processCodexCostFile(filePath, start, end, acc) {
2981
3123
  let lines;
2982
3124
  try {
2983
- lines = fs4.readFileSync(filePath, "utf-8").split("\n");
3125
+ lines = fs5.readFileSync(filePath, "utf-8").split("\n");
2984
3126
  } catch {
2985
3127
  return;
2986
3128
  }
2987
3129
  let sessionStart = "";
3130
+ let model = "";
2988
3131
  let lastTotalInput = 0;
2989
3132
  let lastTotalCached = 0;
2990
3133
  let lastTotalOutput = 0;
@@ -3002,6 +3145,10 @@ function processCodexCostFile(filePath, start, end, acc) {
3002
3145
  sessionStart = String(p["timestamp"] ?? "");
3003
3146
  continue;
3004
3147
  }
3148
+ if (entry.type === "turn_context" && typeof p["model"] === "string") {
3149
+ model = p["model"];
3150
+ continue;
3151
+ }
3005
3152
  if (entry.type === "event_msg" && p["type"] === "token_count") {
3006
3153
  const info = p["info"] ?? {};
3007
3154
  const usage = info["total_token_usage"] ?? {};
@@ -3016,40 +3163,45 @@ function processCodexCostFile(filePath, start, end, acc) {
3016
3163
  if (!sessionStart) return;
3017
3164
  const ts = new Date(sessionStart);
3018
3165
  if (ts < start || ts > end) return;
3019
- const nonCached = Math.max(0, lastTotalInput - lastTotalCached);
3020
- const cost = nonCached * 5e-6 + lastTotalCached * 25e-7 + lastTotalOutput * 15e-6;
3166
+ const cost = codexSessionCost(model, {
3167
+ input: lastTotalInput,
3168
+ cached: lastTotalCached,
3169
+ output: lastTotalOutput
3170
+ });
3021
3171
  acc.total += cost;
3022
3172
  acc.toolCalls += sessionToolCalls;
3023
3173
  const dateKey = sessionStart.slice(0, 10);
3024
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);
3025
3177
  }
3026
3178
  function listCodexSessionFiles(sessionsBase) {
3027
3179
  const jsonlFiles = [];
3028
- if (!fs4.existsSync(sessionsBase)) return jsonlFiles;
3180
+ if (!fs5.existsSync(sessionsBase)) return jsonlFiles;
3029
3181
  try {
3030
- for (const year of fs4.readdirSync(sessionsBase)) {
3031
- const yearPath = path5.join(sessionsBase, year);
3182
+ for (const year of fs5.readdirSync(sessionsBase)) {
3183
+ const yearPath = path6.join(sessionsBase, year);
3032
3184
  try {
3033
- if (!fs4.statSync(yearPath).isDirectory()) continue;
3185
+ if (!fs5.statSync(yearPath).isDirectory()) continue;
3034
3186
  } catch {
3035
3187
  continue;
3036
3188
  }
3037
- for (const month of fs4.readdirSync(yearPath)) {
3038
- const monthPath = path5.join(yearPath, month);
3189
+ for (const month of fs5.readdirSync(yearPath)) {
3190
+ const monthPath = path6.join(yearPath, month);
3039
3191
  try {
3040
- if (!fs4.statSync(monthPath).isDirectory()) continue;
3192
+ if (!fs5.statSync(monthPath).isDirectory()) continue;
3041
3193
  } catch {
3042
3194
  continue;
3043
3195
  }
3044
- for (const day of fs4.readdirSync(monthPath)) {
3045
- const dayPath = path5.join(monthPath, day);
3196
+ for (const day of fs5.readdirSync(monthPath)) {
3197
+ const dayPath = path6.join(monthPath, day);
3046
3198
  try {
3047
- if (!fs4.statSync(dayPath).isDirectory()) continue;
3199
+ if (!fs5.statSync(dayPath).isDirectory()) continue;
3048
3200
  } catch {
3049
3201
  continue;
3050
3202
  }
3051
- for (const file of fs4.readdirSync(dayPath)) {
3052
- if (file.endsWith(".jsonl")) jsonlFiles.push(path5.join(dayPath, file));
3203
+ for (const file of fs5.readdirSync(dayPath)) {
3204
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path6.join(dayPath, file));
3053
3205
  }
3054
3206
  }
3055
3207
  }
@@ -3059,16 +3211,33 @@ function listCodexSessionFiles(sessionsBase) {
3059
3211
  }
3060
3212
  return jsonlFiles;
3061
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
+ }
3062
3221
  function loadCodexCost(start, end, sessionsBase) {
3063
- const acc = { total: 0, toolCalls: 0, byDay: /* @__PURE__ */ new Map() };
3222
+ const acc = {
3223
+ total: 0,
3224
+ toolCalls: 0,
3225
+ byDay: /* @__PURE__ */ new Map(),
3226
+ byModel: /* @__PURE__ */ new Map()
3227
+ };
3064
3228
  const files = listCodexSessionFiles(sessionsBase);
3065
3229
  for (const filePath of files) {
3066
3230
  processCodexCostFile(filePath, start, end, acc);
3067
3231
  }
3068
- return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
3232
+ return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
3069
3233
  }
3070
3234
  async function loadCodexCostAsync(start, end, sessionsBase) {
3071
- const acc = { total: 0, toolCalls: 0, byDay: /* @__PURE__ */ new Map() };
3235
+ const acc = {
3236
+ total: 0,
3237
+ toolCalls: 0,
3238
+ byDay: /* @__PURE__ */ new Map(),
3239
+ byModel: /* @__PURE__ */ new Map()
3240
+ };
3072
3241
  const files = listCodexSessionFiles(sessionsBase);
3073
3242
  const CHUNK_SIZE = 5;
3074
3243
  for (let i = 0; i < files.length; i++) {
@@ -3077,12 +3246,12 @@ async function loadCodexCostAsync(start, end, sessionsBase) {
3077
3246
  await new Promise((resolve) => setImmediate(resolve));
3078
3247
  }
3079
3248
  }
3080
- return { total: acc.total, byDay: acc.byDay, toolCalls: acc.toolCalls };
3249
+ return { total: acc.total, byDay: acc.byDay, byModel: acc.byModel, toolCalls: acc.toolCalls };
3081
3250
  }
3082
- function geminiPriceFor(model) {
3251
+ function geminiPriceFor2(model) {
3083
3252
  let tuple = pricingFor(model);
3084
3253
  if (!tuple && /^gemini-/i.test(model)) {
3085
- for (const proxy of GEMINI_FALLBACK_MODELS) {
3254
+ for (const proxy of GEMINI_FALLBACK_MODELS2) {
3086
3255
  tuple = pricingFor(proxy);
3087
3256
  if (tuple) break;
3088
3257
  }
@@ -3113,13 +3282,13 @@ function freezeGeminiCost(acc) {
3113
3282
  function processGeminiCostFile(filePath, projectKey, start, end, acc) {
3114
3283
  const startMs = start.getTime();
3115
3284
  try {
3116
- if (fs4.statSync(filePath).mtimeMs < startMs) return;
3285
+ if (fs5.statSync(filePath).mtimeMs < startMs) return;
3117
3286
  } catch {
3118
3287
  return;
3119
3288
  }
3120
3289
  let raw;
3121
3290
  try {
3122
- raw = fs4.readFileSync(filePath, "utf-8");
3291
+ raw = fs5.readFileSync(filePath, "utf-8");
3123
3292
  } catch {
3124
3293
  return;
3125
3294
  }
@@ -3140,7 +3309,7 @@ function processGeminiCostFile(filePath, projectKey, start, end, acc) {
3140
3309
  }
3141
3310
  const ts = new Date(entry.timestamp);
3142
3311
  if (ts < start || ts > end) continue;
3143
- const price = geminiPriceFor(entry.model);
3312
+ const price = geminiPriceFor2(entry.model);
3144
3313
  if (!price) continue;
3145
3314
  const inp = entry.tokens.input ?? 0;
3146
3315
  const out = entry.tokens.output ?? 0;
@@ -3168,30 +3337,30 @@ function listGeminiSessionFiles(geminiTmpDir) {
3168
3337
  const out = [];
3169
3338
  let dirs;
3170
3339
  try {
3171
- if (!fs4.statSync(geminiTmpDir).isDirectory()) return out;
3172
- dirs = fs4.readdirSync(geminiTmpDir);
3340
+ if (!fs5.statSync(geminiTmpDir).isDirectory()) return out;
3341
+ dirs = fs5.readdirSync(geminiTmpDir);
3173
3342
  } catch {
3174
3343
  return out;
3175
3344
  }
3176
3345
  for (const proj of dirs) {
3177
- const chatsDir = path5.join(geminiTmpDir, proj, "chats");
3346
+ const chatsDir = path6.join(geminiTmpDir, proj, "chats");
3178
3347
  let files;
3179
3348
  try {
3180
- if (!fs4.statSync(chatsDir).isDirectory()) continue;
3181
- files = fs4.readdirSync(chatsDir);
3349
+ if (!fs5.statSync(chatsDir).isDirectory()) continue;
3350
+ files = fs5.readdirSync(chatsDir);
3182
3351
  } catch {
3183
3352
  continue;
3184
3353
  }
3185
3354
  for (const f of files) {
3186
3355
  if (!f.endsWith(".jsonl")) continue;
3187
- out.push({ projectKey: proj, file: path5.join(chatsDir, f) });
3356
+ out.push({ projectKey: proj, file: path6.join(chatsDir, f) });
3188
3357
  }
3189
3358
  }
3190
3359
  return out;
3191
3360
  }
3192
3361
  function loadGeminiCost(start, end, geminiTmpDir) {
3193
3362
  const acc = emptyGeminiAccumulator();
3194
- if (!fs4.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
3363
+ if (!fs5.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
3195
3364
  for (const { projectKey, file } of listGeminiSessionFiles(geminiTmpDir)) {
3196
3365
  processGeminiCostFile(file, projectKey, start, end, acc);
3197
3366
  }
@@ -3199,7 +3368,7 @@ function loadGeminiCost(start, end, geminiTmpDir) {
3199
3368
  }
3200
3369
  async function loadGeminiCostAsync(start, end, geminiTmpDir) {
3201
3370
  const acc = emptyGeminiAccumulator();
3202
- if (!fs4.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
3371
+ if (!fs5.existsSync(geminiTmpDir)) return freezeGeminiCost(acc);
3203
3372
  const files = listGeminiSessionFiles(geminiTmpDir);
3204
3373
  const CHUNK_SIZE = 5;
3205
3374
  for (let i = 0; i < files.length; i++) {
@@ -3212,11 +3381,11 @@ async function loadGeminiCostAsync(start, end, geminiTmpDir) {
3212
3381
  }
3213
3382
  function aggregateReportFromAudit(period, opts = {}) {
3214
3383
  const now = opts.now ?? /* @__PURE__ */ new Date();
3215
- const auditLogPath2 = opts.auditLogPath ?? path5.join(os5.homedir(), ".node9", "audit.log");
3216
- const claudeProjectsDir = opts.claudeProjectsDir ?? path5.join(os5.homedir(), ".claude", "projects");
3217
- const codexSessionsDir = opts.codexSessionsDir ?? path5.join(os5.homedir(), ".codex", "sessions");
3218
- const geminiTmpDir = opts.geminiTmpDir ?? path5.join(os5.homedir(), ".gemini", "tmp");
3219
- const hasAuditFile = fs4.existsSync(auditLogPath2);
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);
3220
3389
  const allEntries = opts.preloadedAuditEntries ?? parseAuditLog(auditLogPath2);
3221
3390
  const unackedDlp = allEntries.filter((e) => e.source === "response-dlp");
3222
3391
  const { start, end } = getDateRange(period, now);
@@ -3378,7 +3547,7 @@ function aggregateReportFromAudit(period, opts = {}) {
3378
3547
  cacheWriteTokens: claudeCost.cacheWriteTokens,
3379
3548
  cacheReadTokens: claudeCost.cacheReadTokens + geminiCost.cacheReadTokens,
3380
3549
  byDay: claudeCost.byDay,
3381
- byModel: claudeCost.byModel,
3550
+ byModel: mergeByModel(claudeCost.byModel, codexCost.byModel),
3382
3551
  byProject: claudeCost.byProject
3383
3552
  },
3384
3553
  toolMap,
@@ -3392,43 +3561,32 @@ function aggregateReportFromAudit(period, opts = {}) {
3392
3561
  };
3393
3562
  return { data, hasAuditFile, responseDlpEntries };
3394
3563
  }
3395
- var TEST_COMMAND_RE, SUPERSEDE_WINDOW_MS, CLAUDE_PRICING, GEMINI_FALLBACK_MODELS;
3564
+ var TEST_COMMAND_RE, SUPERSEDE_WINDOW_MS, GEMINI_FALLBACK_MODELS2;
3396
3565
  var init_report_audit = __esm({
3397
3566
  "src/cli/aggregate/report-audit.ts"() {
3398
3567
  "use strict";
3399
3568
  init_costSync();
3400
3569
  init_litellm();
3570
+ init_cost_codex();
3401
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;
3402
3572
  SUPERSEDE_WINDOW_MS = 6e4;
3403
- CLAUDE_PRICING = {
3404
- "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
3405
- "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
3406
- "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
3407
- "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
3408
- "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
3409
- "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
3410
- "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
3411
- "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
3412
- "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
3413
- "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
3414
- };
3415
- GEMINI_FALLBACK_MODELS = ["gemini-2.5-flash", "gemini-2.0-flash"];
3573
+ GEMINI_FALLBACK_MODELS2 = ["gemini-2.5-flash", "gemini-2.0-flash"];
3416
3574
  }
3417
3575
  });
3418
3576
 
3419
3577
  // src/utils/provenance.ts
3420
- import path6 from "path";
3421
- import os6 from "os";
3578
+ import path7 from "path";
3579
+ import os7 from "os";
3422
3580
  var USER_PREFIXES;
3423
3581
  var init_provenance = __esm({
3424
3582
  "src/utils/provenance.ts"() {
3425
3583
  "use strict";
3426
3584
  USER_PREFIXES = [
3427
- path6.join(os6.homedir(), "bin"),
3428
- path6.join(os6.homedir(), ".local", "bin"),
3429
- path6.join(os6.homedir(), ".cargo", "bin"),
3430
- path6.join(os6.homedir(), ".npm-global", "bin"),
3431
- path6.join(os6.homedir(), ".volta", "bin")
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")
3432
3590
  ];
3433
3591
  }
3434
3592
  });
@@ -3532,24 +3690,20 @@ var init_scan_history = __esm({
3532
3690
 
3533
3691
  // src/cli/commands/scan.ts
3534
3692
  import chalk4 from "chalk";
3535
- import fs5 from "fs";
3536
- import path7 from "path";
3537
- import os7 from "os";
3693
+ import fs6 from "fs";
3694
+ import path8 from "path";
3695
+ import os8 from "os";
3538
3696
  import stringWidth2 from "string-width";
3539
3697
  function claudeModelPrice2(model) {
3540
- const base = model.replace(/@.*$/, "").replace(/-\d{8}$/, "");
3541
- for (const [key, p] of Object.entries(CLAUDE_PRICING2)) {
3542
- if (base === key || base.startsWith(key)) return p;
3543
- }
3544
- 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 };
3545
3702
  }
3546
3703
  function geminiModelPrice(model) {
3547
- const base = model.replace(/-preview$/, "").replace(/-exp$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
3548
- for (const [key, p] of Object.entries(GEMINI_PRICING)) {
3549
- if (base === key || base.startsWith(key)) return p;
3550
- }
3551
- if (base.includes("flash")) return GEMINI_PRICING["gemini-2.0-flash"];
3552
- return null;
3704
+ const p = geminiPriceFor(model);
3705
+ if (!p) return null;
3706
+ return { i: p.input, o: p.output, cr: p.cacheRead };
3553
3707
  }
3554
3708
  function isNode9SelfOutput(text) {
3555
3709
  let hits = 0;
@@ -3687,7 +3841,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
3687
3841
  const sessionId = file.replace(/\.jsonl$/, "");
3688
3842
  let raw;
3689
3843
  try {
3690
- raw = fs5.readFileSync(path7.join(projPath, file), "utf-8");
3844
+ raw = fs6.readFileSync(path8.join(projPath, file), "utf-8");
3691
3845
  } catch {
3692
3846
  return;
3693
3847
  }
@@ -3739,7 +3893,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
3739
3893
  if (block.type !== "tool_result") continue;
3740
3894
  const filePath = block.tool_use_id ? toolUseFilePaths.get(block.tool_use_id) : void 0;
3741
3895
  if (filePath) {
3742
- const ext = path7.extname(filePath).toLowerCase();
3896
+ const ext = path8.extname(filePath).toLowerCase();
3743
3897
  if (CODE_EXTENSIONS.has(ext)) continue;
3744
3898
  }
3745
3899
  const resultText = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map((c) => c.text ?? "").join("\n") : null;
@@ -3796,7 +3950,7 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
3796
3950
  const rawCmd = String(input.command ?? "").trimStart();
3797
3951
  if (/^node9\s+(scan|explain|report|tail|dlp|status|sessions|audit)\b/.test(rawCmd)) continue;
3798
3952
  const inputFilePath = typeof input.file_path === "string" ? input.file_path : "";
3799
- const inputFileExt = inputFilePath ? path7.extname(inputFilePath).toLowerCase() : "";
3953
+ const inputFileExt = inputFilePath ? path8.extname(inputFilePath).toLowerCase() : "";
3800
3954
  if (CODE_EXTENSIONS.has(inputFileExt)) continue;
3801
3955
  const dlpMatch = scanArgs(input);
3802
3956
  if (dlpMatch) {
@@ -3893,19 +4047,19 @@ function processClaudeFile(file, projPath, projLabel, ruleSources, startDate, re
3893
4047
  }
3894
4048
  }
3895
4049
  async function processClaudeProjectAsync(proj, projectsDir, ruleSources, startDate, result, dedup, onProgress, onLine) {
3896
- const projPath = path7.join(projectsDir, proj);
4050
+ const projPath = path8.join(projectsDir, proj);
3897
4051
  try {
3898
- if (!fs5.statSync(projPath).isDirectory()) return;
4052
+ if (!fs6.statSync(projPath).isDirectory()) return;
3899
4053
  } catch {
3900
4054
  return;
3901
4055
  }
3902
- const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os7.homedir(), "~")).slice(
4056
+ const projLabel = stripTerminalEscapes(decodeURIComponent(proj).replace(os8.homedir(), "~")).slice(
3903
4057
  0,
3904
4058
  40
3905
4059
  );
3906
4060
  let files;
3907
4061
  try {
3908
- files = fs5.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
4062
+ files = fs6.readdirSync(projPath).filter((f) => f.endsWith(".jsonl") && !f.startsWith("agent-"));
3909
4063
  } catch {
3910
4064
  return;
3911
4065
  }
@@ -3943,12 +4097,12 @@ function emptyClaudeScan() {
3943
4097
  };
3944
4098
  }
3945
4099
  async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
3946
- const projectsDir = path7.join(os7.homedir(), ".claude", "projects");
4100
+ const projectsDir = path8.join(os8.homedir(), ".claude", "projects");
3947
4101
  const result = emptyClaudeScan();
3948
- if (!fs5.existsSync(projectsDir)) return result;
4102
+ if (!fs6.existsSync(projectsDir)) return result;
3949
4103
  let projDirs;
3950
4104
  try {
3951
- projDirs = fs5.readdirSync(projectsDir);
4105
+ projDirs = fs6.readdirSync(projectsDir);
3952
4106
  } catch {
3953
4107
  return result;
3954
4108
  }
@@ -3969,7 +4123,7 @@ async function scanClaudeHistoryAsync(startDate, onProgress, onLine) {
3969
4123
  return result;
3970
4124
  }
3971
4125
  function scanGeminiHistory(startDate, onProgress, onLine) {
3972
- const tmpDir = path7.join(os7.homedir(), ".gemini", "tmp");
4126
+ const tmpDir = path8.join(os8.homedir(), ".gemini", "tmp");
3973
4127
  const result = {
3974
4128
  filesScanned: 0,
3975
4129
  sessions: 0,
@@ -3984,50 +4138,64 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
3984
4138
  sessionsWithEarlySecrets: 0
3985
4139
  };
3986
4140
  const dedup = emptyScanDedup();
3987
- if (!fs5.existsSync(tmpDir)) return result;
4141
+ if (!fs6.existsSync(tmpDir)) return result;
3988
4142
  let slugDirs;
3989
4143
  try {
3990
- slugDirs = fs5.readdirSync(tmpDir);
4144
+ slugDirs = fs6.readdirSync(tmpDir);
3991
4145
  } catch {
3992
4146
  return result;
3993
4147
  }
3994
4148
  const ruleSources = buildRuleSources();
3995
4149
  for (const slug of slugDirs) {
3996
- const slugPath = path7.join(tmpDir, slug);
4150
+ const slugPath = path8.join(tmpDir, slug);
3997
4151
  try {
3998
- if (!fs5.statSync(slugPath).isDirectory()) continue;
4152
+ if (!fs6.statSync(slugPath).isDirectory()) continue;
3999
4153
  } catch {
4000
4154
  continue;
4001
4155
  }
4002
4156
  let projLabel = stripTerminalEscapes(slug).slice(0, 40);
4003
4157
  try {
4004
4158
  projLabel = stripTerminalEscapes(
4005
- fs5.readFileSync(path7.join(slugPath, ".project_root"), "utf-8").trim()
4006
- ).replace(os7.homedir(), "~").slice(0, 40);
4159
+ fs6.readFileSync(path8.join(slugPath, ".project_root"), "utf-8").trim()
4160
+ ).replace(os8.homedir(), "~").slice(0, 40);
4007
4161
  } catch {
4008
4162
  }
4009
- const chatsDir = path7.join(slugPath, "chats");
4010
- if (!fs5.existsSync(chatsDir)) continue;
4163
+ const chatsDir = path8.join(slugPath, "chats");
4164
+ if (!fs6.existsSync(chatsDir)) continue;
4011
4165
  let chatFiles;
4012
4166
  try {
4013
- chatFiles = fs5.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
4167
+ chatFiles = fs6.readdirSync(chatsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort((a, b) => Number(b.endsWith(".jsonl")) - Number(a.endsWith(".jsonl")));
4014
4168
  } catch {
4015
4169
  continue;
4016
4170
  }
4171
+ const seenSessions = /* @__PURE__ */ new Set();
4017
4172
  for (const chatFile of chatFiles) {
4173
+ const sessionId = chatFile.replace(/\.jsonl?$/, "");
4174
+ if (seenSessions.has(sessionId)) continue;
4175
+ seenSessions.add(sessionId);
4018
4176
  result.filesScanned++;
4019
4177
  onProgress?.(result.filesScanned);
4020
- const sessionId = chatFile.replace(/\.json$/, "");
4021
4178
  let raw;
4022
4179
  try {
4023
- raw = fs5.readFileSync(path7.join(chatsDir, chatFile), "utf-8");
4180
+ raw = fs6.readFileSync(path8.join(chatsDir, chatFile), "utf-8");
4024
4181
  } catch {
4025
4182
  continue;
4026
4183
  }
4027
4184
  const sessionCalls = [];
4028
4185
  let session;
4029
4186
  try {
4030
- session = JSON.parse(raw);
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
+ }
4031
4199
  } catch {
4032
4200
  continue;
4033
4201
  }
@@ -4182,7 +4350,7 @@ function scanGeminiHistory(startDate, onProgress, onLine) {
4182
4350
  return result;
4183
4351
  }
4184
4352
  function scanCodexHistory(startDate, onProgress, onLine) {
4185
- const sessionsBase = path7.join(os7.homedir(), ".codex", "sessions");
4353
+ const sessionsBase = path8.join(os8.homedir(), ".codex", "sessions");
4186
4354
  const result = {
4187
4355
  filesScanned: 0,
4188
4356
  sessions: 0,
@@ -4197,32 +4365,32 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4197
4365
  sessionsWithEarlySecrets: 0
4198
4366
  };
4199
4367
  const dedup = emptyScanDedup();
4200
- if (!fs5.existsSync(sessionsBase)) return result;
4368
+ if (!fs6.existsSync(sessionsBase)) return result;
4201
4369
  const jsonlFiles = [];
4202
4370
  try {
4203
- for (const year of fs5.readdirSync(sessionsBase)) {
4204
- const yearPath = path7.join(sessionsBase, year);
4371
+ for (const year of fs6.readdirSync(sessionsBase)) {
4372
+ const yearPath = path8.join(sessionsBase, year);
4205
4373
  try {
4206
- if (!fs5.statSync(yearPath).isDirectory()) continue;
4374
+ if (!fs6.statSync(yearPath).isDirectory()) continue;
4207
4375
  } catch {
4208
4376
  continue;
4209
4377
  }
4210
- for (const month of fs5.readdirSync(yearPath)) {
4211
- const monthPath = path7.join(yearPath, month);
4378
+ for (const month of fs6.readdirSync(yearPath)) {
4379
+ const monthPath = path8.join(yearPath, month);
4212
4380
  try {
4213
- if (!fs5.statSync(monthPath).isDirectory()) continue;
4381
+ if (!fs6.statSync(monthPath).isDirectory()) continue;
4214
4382
  } catch {
4215
4383
  continue;
4216
4384
  }
4217
- for (const day of fs5.readdirSync(monthPath)) {
4218
- const dayPath = path7.join(monthPath, day);
4385
+ for (const day of fs6.readdirSync(monthPath)) {
4386
+ const dayPath = path8.join(monthPath, day);
4219
4387
  try {
4220
- if (!fs5.statSync(dayPath).isDirectory()) continue;
4388
+ if (!fs6.statSync(dayPath).isDirectory()) continue;
4221
4389
  } catch {
4222
4390
  continue;
4223
4391
  }
4224
- for (const file of fs5.readdirSync(dayPath)) {
4225
- if (file.endsWith(".jsonl")) jsonlFiles.push(path7.join(dayPath, file));
4392
+ for (const file of fs6.readdirSync(dayPath)) {
4393
+ if (file.endsWith(".jsonl")) jsonlFiles.push(path8.join(dayPath, file));
4226
4394
  }
4227
4395
  }
4228
4396
  }
@@ -4236,7 +4404,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4236
4404
  onProgress?.(result.filesScanned);
4237
4405
  let lines;
4238
4406
  try {
4239
- lines = fs5.readFileSync(filePath, "utf-8").split("\n");
4407
+ lines = fs6.readFileSync(filePath, "utf-8").split("\n");
4240
4408
  } catch {
4241
4409
  continue;
4242
4410
  }
@@ -4248,6 +4416,7 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4248
4416
  let lastTotalInput = 0;
4249
4417
  let lastTotalCached = 0;
4250
4418
  let lastTotalOutput = 0;
4419
+ let model = "";
4251
4420
  for (const line of lines) {
4252
4421
  if (!line.trim()) continue;
4253
4422
  onLine?.();
@@ -4262,7 +4431,11 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4262
4431
  sessionId = String(payload["id"] ?? filePath);
4263
4432
  startTime = String(payload["timestamp"] ?? "");
4264
4433
  const cwd = String(payload["cwd"] ?? "");
4265
- projLabel = stripTerminalEscapes(cwd.replace(os7.homedir(), "~")).slice(0, 40);
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"];
4266
4439
  continue;
4267
4440
  }
4268
4441
  if (entry.type === "event_msg" && payload["type"] === "token_count") {
@@ -4408,13 +4581,16 @@ function scanCodexHistory(startDate, onProgress, onLine) {
4408
4581
  }
4409
4582
  }
4410
4583
  }
4411
- const nonCached = Math.max(0, lastTotalInput - lastTotalCached);
4412
- result.totalCostUSD += nonCached * 5e-6 + lastTotalCached * 25e-7 + lastTotalOutput * 15e-6;
4584
+ result.totalCostUSD += codexSessionCost(model, {
4585
+ input: lastTotalInput,
4586
+ cached: lastTotalCached,
4587
+ output: lastTotalOutput
4588
+ });
4413
4589
  result.loopFindings.push(...detectLoops(sessionCalls, projLabel, sessionId, "codex"));
4414
4590
  }
4415
4591
  return result;
4416
4592
  }
4417
- var CLAUDE_PRICING2, GEMINI_PRICING, CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
4593
+ var CODE_EXTENSIONS, SELF_OUTPUT_MARKERS, FIXTURE_TOKEN_PATTERNS, TERMINAL_ESCAPE_RE, LOOP_TOOLS, LOOP_THRESHOLD, LOOP_TIMESPAN_THRESHOLD_MS;
4418
4594
  var init_scan = __esm({
4419
4595
  "src/cli/commands/scan.ts"() {
4420
4596
  "use strict";
@@ -4423,6 +4599,9 @@ var init_scan = __esm({
4423
4599
  init_policy();
4424
4600
  init_dist();
4425
4601
  init_dlp();
4602
+ init_litellm();
4603
+ init_cost_gemini();
4604
+ init_cost_codex();
4426
4605
  init_hook_payload();
4427
4606
  init_dist();
4428
4607
  init_scan_summary();
@@ -4432,26 +4611,6 @@ var init_scan = __esm({
4432
4611
  init_protection();
4433
4612
  init_scan_json();
4434
4613
  init_scan_history();
4435
- CLAUDE_PRICING2 = {
4436
- "claude-opus-4-6": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
4437
- "claude-opus-4-5": { i: 5e-6, o: 25e-6, cw: 625e-8, cr: 5e-7 },
4438
- "claude-opus-4": { i: 15e-6, o: 75e-6, cw: 1875e-8, cr: 15e-7 },
4439
- "claude-sonnet-4-6": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
4440
- "claude-sonnet-4-5": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
4441
- "claude-sonnet-4": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
4442
- "claude-3-7-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
4443
- "claude-3-5-sonnet": { i: 3e-6, o: 15e-6, cw: 375e-8, cr: 3e-7 },
4444
- "claude-haiku-4-5": { i: 1e-6, o: 5e-6, cw: 125e-8, cr: 1e-7 },
4445
- "claude-3-5-haiku": { i: 8e-7, o: 4e-6, cw: 1e-6, cr: 8e-8 }
4446
- };
4447
- GEMINI_PRICING = {
4448
- "gemini-2.5-pro": { i: 125e-8, o: 1e-5, cr: 31e-8 },
4449
- "gemini-2.5-flash": { i: 15e-8, o: 6e-7, cr: 375e-10 },
4450
- "gemini-2.0-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 },
4451
- "gemini-1.5-pro": { i: 125e-8, o: 5e-6, cr: 3125e-10 },
4452
- "gemini-1.5-flash": { i: 75e-9, o: 3e-7, cr: 1875e-11 },
4453
- "gemini-3-flash": { i: 1e-7, o: 4e-7, cr: 25e-9 }
4454
- };
4455
4614
  CODE_EXTENSIONS = /* @__PURE__ */ new Set([
4456
4615
  ".ts",
4457
4616
  ".tsx",
@@ -4514,23 +4673,23 @@ var init_scan = __esm({
4514
4673
  });
4515
4674
 
4516
4675
  // src/tui/dashboard/data.ts
4517
- import fs6 from "fs";
4518
- import os8 from "os";
4519
- import path8 from "path";
4676
+ import fs7 from "fs";
4677
+ import os9 from "os";
4678
+ import path9 from "path";
4520
4679
  import http from "http";
4521
4680
  function auditLogPath() {
4522
- return path8.join(os8.homedir(), ".node9", "audit.log");
4681
+ return path9.join(os9.homedir(), ".node9", "audit.log");
4523
4682
  }
4524
4683
  function readAuditEntriesAsync(chunkSize = 1e3, customPath) {
4525
4684
  return new Promise((resolve) => {
4526
4685
  const p = customPath ?? auditLogPath();
4527
- if (!fs6.existsSync(p)) {
4686
+ if (!fs7.existsSync(p)) {
4528
4687
  resolve([]);
4529
4688
  return;
4530
4689
  }
4531
4690
  let raw;
4532
4691
  try {
4533
- raw = fs6.readFileSync(p, "utf8");
4692
+ raw = fs7.readFileSync(p, "utf8");
4534
4693
  } catch {
4535
4694
  resolve([]);
4536
4695
  return;
@@ -4659,15 +4818,16 @@ function loadBlast() {
4659
4818
  }
4660
4819
  }
4661
4820
  function shortenPath(p) {
4662
- const home = os8.homedir();
4821
+ const home = os9.homedir();
4663
4822
  return p.startsWith(home) ? p.replace(home, "~") : p;
4664
4823
  }
4665
4824
  async function loadReportAuditAsync(period) {
4666
- const claudeProjectsDir = path8.join(os8.homedir(), ".claude", "projects");
4667
- const codexSessionsDir = path8.join(os8.homedir(), ".codex", "sessions");
4668
- const geminiTmpDir = path8.join(os8.homedir(), ".gemini", "tmp");
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");
4669
4828
  const { start, end } = getDateRange(period, /* @__PURE__ */ new Date());
4670
4829
  const entries = await readAuditEntriesAsync();
4830
+ void ensurePricingLoaded();
4671
4831
  const claudeCost = await loadClaudeCostAsync(start, end, claudeProjectsDir);
4672
4832
  const codexCost = await loadCodexCostAsync(start, end, codexSessionsDir);
4673
4833
  const geminiCost = await loadGeminiCostAsync(start, end, geminiTmpDir);
@@ -5008,6 +5168,7 @@ var init_data = __esm({
5008
5168
  init_shields();
5009
5169
  init_scan_watermark();
5010
5170
  init_report_audit();
5171
+ init_litellm();
5011
5172
  init_scan();
5012
5173
  init_protection();
5013
5174
  TEST_TOOLS = /* @__PURE__ */ new Set(["Bash", "bash"]);
@@ -5769,8 +5930,8 @@ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-run
5769
5930
  function TopToolsProjects({ audit }) {
5770
5931
  const data = audit?.data;
5771
5932
  const tools = data ? [...data.toolMap.entries()].sort(([, a], [, b]) => b.calls - a.calls).slice(0, ROW_LIMIT) : [];
5772
- const projects = data ? [...data.cost.byProject.entries()].map(([path9, r]) => ({
5773
- name: basenameOf(path9),
5933
+ const projects = data ? [...data.cost.byProject.entries()].map(([path10, r]) => ({
5934
+ name: basenameOf(path10),
5774
5935
  cost: r.cost,
5775
5936
  tokens: r.inputTokens + r.outputTokens
5776
5937
  })).sort((a, b) => b.cost - a.cost).slice(0, ROW_LIMIT) : [];
@@ -6207,8 +6368,8 @@ function pickTopLoopFile(loops) {
6207
6368
  map.set(k, (map.get(k) ?? 0) + (l.count ?? 0));
6208
6369
  }
6209
6370
  if (map.size === 0) return void 0;
6210
- const [path9, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
6211
- return { path: path9, count };
6371
+ const [path10, count] = [...map.entries()].sort((a, b) => b[1] - a[1])[0];
6372
+ return { path: path10, count };
6212
6373
  }
6213
6374
  var EMPTY_FILTERED_SCAN;
6214
6375
  var init_derive = __esm({