exe-watcher 0.2.66 → 0.2.68

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 (3) hide show
  1. package/README.md +6 -4
  2. package/dist/cli.js +675 -377
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -2125,12 +2125,12 @@ function getPricingWarnings() {
2125
2125
  }
2126
2126
  function getPricingMetadata() {
2127
2127
  return {
2128
- basis: "latest-rates",
2128
+ basis: "recorded-rates",
2129
2129
  currency: "USD",
2130
2130
  refreshedAt: _pricingLastUpdated ? new Date(_pricingLastUpdated).toISOString() : null,
2131
2131
  fallbackVerifiedOn: verified_pricing_default.verifiedOn,
2132
2132
  sources: OFFICIAL_PRICING_SOURCES,
2133
- note: "Historical and current usage are valued at the latest available API list rates. This is an API-equivalent estimate, not a reconstruction of past invoices."
2133
+ note: "Counted usage keeps its recorded API-equivalent cost. Latest available rates apply to newly observed usage; price refreshes do not revalue saved history. This is an estimate, not a reconstruction of invoices."
2134
2134
  };
2135
2135
  }
2136
2136
  function getCachePath() {
@@ -2690,12 +2690,223 @@ var init_discovery_cache = __esm({
2690
2690
  }
2691
2691
  });
2692
2692
 
2693
+ // src/sqlite.ts
2694
+ import { createRequire } from "module";
2695
+ function loadDriver() {
2696
+ if (loadAttempted) return DatabaseSync !== null;
2697
+ loadAttempted = true;
2698
+ const origEmit = process.emit.bind(process);
2699
+ let restored = false;
2700
+ const restore = () => {
2701
+ if (restored) return;
2702
+ restored = true;
2703
+ process.emit = origEmit;
2704
+ };
2705
+ process.emit = function patchedEmit(event, ...args) {
2706
+ if (event === "warning") {
2707
+ const warning = args[0];
2708
+ if (warning?.name === "ExperimentalWarning" && typeof warning.message === "string" && /SQLite/i.test(warning.message)) {
2709
+ return false;
2710
+ }
2711
+ }
2712
+ return origEmit.call(this, event, ...args);
2713
+ };
2714
+ try {
2715
+ const mod = requireForSqlite("node:sqlite");
2716
+ DatabaseSync = mod.DatabaseSync;
2717
+ return true;
2718
+ } catch (err) {
2719
+ const message = err instanceof Error ? err.message : String(err);
2720
+ loadError = `Watcher saved costs and SQLite-based providers need Node 22.13+ (Node 23 requires 23.4+) with node:sqlite.
2721
+ Current Node: ${process.version}.
2722
+ Upgrade Node (https://nodejs.org) and run exe-watcher again.
2723
+ (underlying error: ${message})`;
2724
+ return false;
2725
+ } finally {
2726
+ setTimeout(restore, 0);
2727
+ }
2728
+ }
2729
+ function isSqliteAvailable() {
2730
+ return loadDriver();
2731
+ }
2732
+ function getSqliteLoadError() {
2733
+ return loadError ?? "SQLite driver not available";
2734
+ }
2735
+ function openDatabase(path) {
2736
+ if (!loadDriver() || DatabaseSync === null) {
2737
+ throw new Error(getSqliteLoadError());
2738
+ }
2739
+ const db = new DatabaseSync(path, { readOnly: true });
2740
+ return {
2741
+ query(sql, params = []) {
2742
+ const rows2 = [];
2743
+ let bytes = 0;
2744
+ for (const row of db.prepare(sql).iterate(...params)) {
2745
+ const size = Buffer.byteLength(JSON.stringify(row));
2746
+ bytes += size;
2747
+ if (rows2.length >= 1e5 || bytes > 32 * 1024 * 1024) throw new ResourceBudgetError("SQLite result budget");
2748
+ chargeRead(size);
2749
+ rows2.push(row);
2750
+ }
2751
+ return rows2;
2752
+ },
2753
+ close() {
2754
+ db.close();
2755
+ }
2756
+ };
2757
+ }
2758
+ var requireForSqlite, DatabaseSync, loadAttempted, loadError;
2759
+ var init_sqlite = __esm({
2760
+ "src/sqlite.ts"() {
2761
+ "use strict";
2762
+ init_resource_budget();
2763
+ requireForSqlite = createRequire(import.meta.url);
2764
+ DatabaseSync = null;
2765
+ loadAttempted = false;
2766
+ loadError = null;
2767
+ }
2768
+ });
2769
+
2770
+ // src/recorded-costs.ts
2771
+ import { createHash as createHash2 } from "crypto";
2772
+ import { createRequire as createRequire2 } from "module";
2773
+ import { chmodSync, existsSync, lstatSync, mkdirSync } from "fs";
2774
+ import { join as join9 } from "path";
2775
+ function database() {
2776
+ const directory = getCacheDir();
2777
+ const path = join9(directory, "recorded-costs.sqlite");
2778
+ if (opened?.path === path && existsSync(path)) return opened.db;
2779
+ opened?.db.close();
2780
+ opened = void 0;
2781
+ if (!isSqliteAvailable()) throw new Error(getSqliteLoadError());
2782
+ mkdirSync(directory, { recursive: true, mode: 448 });
2783
+ if (existsSync(path) && !lstatSync(path).isFile()) throw new Error("Watcher pricing ledger must be a regular file");
2784
+ const { DatabaseSync: DatabaseSync2 } = require2("node:sqlite");
2785
+ const db = new DatabaseSync2(path);
2786
+ chmodSync(path, 384);
2787
+ db.exec("PRAGMA busy_timeout=1000; PRAGMA cache_size=-1024; PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;");
2788
+ const pageSize = Number(db.prepare("PRAGMA page_size").get().page_size);
2789
+ db.exec(`PRAGMA max_page_count=${Math.floor(512 * 1024 * 1024 / pageSize)}`);
2790
+ db.exec("CREATE TABLE IF NOT EXISTS costs (id BLOB PRIMARY KEY, usd REAL NOT NULL CHECK(usd >= 0)) WITHOUT ROWID");
2791
+ opened = { path, db };
2792
+ return db;
2793
+ }
2794
+ function costRow(value) {
2795
+ if (!value || typeof value !== "object") return null;
2796
+ const record = value;
2797
+ const { provider, model, deduplicationKey, costUSD } = record;
2798
+ if (typeof provider !== "string" || typeof model !== "string" || typeof deduplicationKey !== "string" || !deduplicationKey || typeof costUSD !== "number" || !Number.isFinite(costUSD) || costUSD < 0) return null;
2799
+ if (costUSD === 0 && !getModelCosts(model)) return "unpriced";
2800
+ const usage = record.usage ?? record;
2801
+ const fields2 = ["inputTokens", "outputTokens", "cacheCreationInputTokens", "cacheReadInputTokens", "cacheCreation1hInputTokens", "webSearchRequests"];
2802
+ const tokens = fields2.map((field) => usage[field] ?? 0);
2803
+ if (!tokens.every((value2) => typeof value2 === "number" && Number.isFinite(value2) && value2 >= 0)) return null;
2804
+ const id = createHash2("sha256").update(JSON.stringify([provider, deduplicationKey, model, record.speed ?? "standard", ...tokens])).digest();
2805
+ return { record, id };
2806
+ }
2807
+ function lockRecordedCosts(data) {
2808
+ let db;
2809
+ let batch = [];
2810
+ let hasUnpricedUsage = false;
2811
+ const flush = () => {
2812
+ if (!batch.length) return;
2813
+ chargeRead(0);
2814
+ db ??= database();
2815
+ const placeholders = batch.map(() => "?").join(",");
2816
+ const query = db.prepare(`SELECT id, usd FROM costs WHERE id IN (${placeholders})`);
2817
+ const read = () => new Map(query.all(...batch.map((row) => row.id)).map((row) => {
2818
+ const cost = Number(row.usd);
2819
+ if (!Number.isFinite(cost) || cost < 0) throw new Error("Watcher pricing ledger contains an invalid recorded cost");
2820
+ return [Buffer.from(row.id).toString("hex"), cost];
2821
+ }));
2822
+ let known = read();
2823
+ const missing = batch.filter((row) => !known.has(row.id.toString("hex")));
2824
+ if (missing.length) {
2825
+ db.exec("BEGIN IMMEDIATE");
2826
+ try {
2827
+ const insert = db.prepare("INSERT OR IGNORE INTO costs (id, usd) VALUES (?, ?)");
2828
+ for (const row of missing) insert.run(row.id, row.record.costUSD);
2829
+ db.exec("COMMIT");
2830
+ } catch (error) {
2831
+ db.exec("ROLLBACK");
2832
+ throw error;
2833
+ }
2834
+ known = read();
2835
+ }
2836
+ chargeRead(known.size * 40);
2837
+ for (const row of batch) row.record.costUSD = known.get(row.id.toString("hex"));
2838
+ batch = [];
2839
+ };
2840
+ const add2 = (value) => {
2841
+ const row = costRow(value);
2842
+ if (row === "unpriced") hasUnpricedUsage = true;
2843
+ else if (row) batch.push(row);
2844
+ if (batch.length === BATCH) flush();
2845
+ };
2846
+ for (const entry of data) {
2847
+ if (entry && typeof entry === "object" && Array.isArray(entry.assistantCalls)) {
2848
+ for (const call of entry.assistantCalls) add2(call);
2849
+ } else add2(entry);
2850
+ }
2851
+ flush();
2852
+ return hasUnpricedUsage;
2853
+ }
2854
+ var require2, BATCH, opened;
2855
+ var init_recorded_costs = __esm({
2856
+ "src/recorded-costs.ts"() {
2857
+ "use strict";
2858
+ init_cache_dir();
2859
+ init_models();
2860
+ init_resource_budget();
2861
+ init_sqlite();
2862
+ require2 = createRequire2(import.meta.url);
2863
+ BATCH = 128;
2864
+ }
2865
+ });
2866
+
2693
2867
  // src/parsed-file-cache.ts
2694
2868
  import { gzip, gunzip } from "zlib";
2695
2869
  import { promisify } from "util";
2696
- import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
2870
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
2697
2871
  import { mkdir as mkdir8, readFile as readFile7, stat as stat5, writeFile as writeFile3, rename as rename6, unlink as unlink5, readdir as readdir3 } from "fs/promises";
2698
- import { join as join9 } from "path";
2872
+ import { join as join10 } from "path";
2873
+ function encodeEntry(value) {
2874
+ let bytes = 0;
2875
+ try {
2876
+ return JSON.stringify(value, (key2, item) => {
2877
+ bytes += key2 ? Buffer.byteLength(JSON.stringify(key2)) + 2 : 0;
2878
+ bytes += item && typeof item === "object" ? 2 : Buffer.byteLength(JSON.stringify(item) ?? "null");
2879
+ if (bytes > MAX_ENTRY) throw new Error("parsed entry limit");
2880
+ return item;
2881
+ });
2882
+ } catch {
2883
+ return void 0;
2884
+ }
2885
+ }
2886
+ function fileProgress(path, dir, cachePath, before) {
2887
+ let startingOffset = 0;
2888
+ return {
2889
+ async load(valid) {
2890
+ if (!before) return;
2891
+ try {
2892
+ const size = (await stat5(cachePath)).size;
2893
+ if (size > MAX_ENTRY) return;
2894
+ chargeRead(size);
2895
+ const saved = JSON.parse((await decompress(await readFile7(cachePath), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
2896
+ if (saved.v !== VERSION2 || saved.fingerprint !== before || !Number.isSafeInteger(saved.offset) || saved.offset <= 0 || saved.offset > Number(before.split(":")[2]) || !valid(saved.state)) return;
2897
+ startingOffset = saved.offset;
2898
+ return { offset: saved.offset, state: saved.state };
2899
+ } catch (error) {
2900
+ if (error instanceof ResourceBudgetError) throw error;
2901
+ }
2902
+ },
2903
+ async save(state, offset) {
2904
+ if (!before || offset <= startingOffset || offset > Number(before.split(":")[2]) || await fingerprint(path) !== before) return;
2905
+ const payload = encodeEntry({ v: VERSION2, fingerprint: before, offset, state });
2906
+ if (payload) await storeParsedEntry(dir, cachePath, payload);
2907
+ }
2908
+ };
2909
+ }
2699
2910
  async function fingerprint(path) {
2700
2911
  try {
2701
2912
  const s = await stat5(path);
@@ -2706,12 +2917,15 @@ async function fingerprint(path) {
2706
2917
  }
2707
2918
  }
2708
2919
  async function cachedFileParse(path, kind, parse, warnings, range) {
2709
- const dir = join9(getCacheDir(), "parsed-files");
2710
- const key2 = createHash2("sha256").update(`${VERSION2}:${kind}:${path}:${getPricingFingerprint()}`).digest("hex");
2711
- const cachePath = join9(dir, `v${VERSION2}-${key2}.json.gz`);
2920
+ const dir = join10(getCacheDir(), "parsed-files");
2921
+ const key2 = createHash3("sha256").update(`${VERSION2}:${kind}:${path}`).digest("hex");
2922
+ const cachePath = join10(dir, `v${VERSION2}-${key2}.json.gz`);
2923
+ const progressPath = join10(dir, `v${VERSION2}-progress-${key2}.json.gz`);
2924
+ const legacyKey = createHash3("sha256").update(`3:${kind}:${path}:${getPricingFingerprint()}`).digest("hex");
2925
+ const legacyPath = join10(dir, `v3-${legacyKey}.json.gz`);
2712
2926
  const fingerprintPath = kind === "opencode" ? path.slice(0, path.lastIndexOf(":")) : path;
2713
2927
  const before = await fingerprint(fingerprintPath);
2714
- const rangeKind = `usage-range:${VERSION2}:${kind}`;
2928
+ const rangeKind = `usage-range:3:${kind}`;
2715
2929
  const bounds = range && before ? await readDiscoveryFact(rangeKind, path, before) : void 0;
2716
2930
  if (range && bounds && (bounds.last < range.start.getTime() || bounds.first > range.end.getTime())) {
2717
2931
  if (Array.isArray(bounds.warnings)) warnings?.push(...bounds.warnings);
@@ -2729,32 +2943,52 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
2729
2943
  }
2730
2944
  await rememberDiscoveryFact(rangeKind, path, before, { first: data2.length ? first : 0, last: data2.length ? last : -1, warnings });
2731
2945
  };
2946
+ let migrated;
2732
2947
  if (before) {
2733
- try {
2734
- const s = await stat5(cachePath);
2735
- if (s.size <= MAX_ENTRY) {
2736
- chargeRead(s.size);
2737
- const saved = JSON.parse((await decompress(await readFile7(cachePath), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
2738
- if (saved.v === VERSION2 && saved.fingerprint === before && Array.isArray(saved.data)) {
2739
- if (Array.isArray(saved.warnings)) warnings?.push(...saved.warnings.filter((w) => typeof w === "string"));
2740
- await rememberRange(saved.data);
2741
- return saved.data;
2948
+ for (const candidate of [cachePath, legacyPath]) {
2949
+ try {
2950
+ const s = await stat5(candidate);
2951
+ if (s.size <= MAX_ENTRY) {
2952
+ chargeRead(s.size);
2953
+ const saved = JSON.parse((await decompress(await readFile7(candidate), { maxOutputLength: MAX_ENTRY })).toString("utf8"));
2954
+ if ((saved.v === VERSION2 || saved.v === 3) && saved.fingerprint === before && Array.isArray(saved.data)) {
2955
+ if (Array.isArray(saved.warnings)) warnings?.push(...saved.warnings.filter((w) => typeof w === "string"));
2956
+ await rememberRange(saved.data);
2957
+ if (saved.v === VERSION2 && saved.pricesLocked === true) {
2958
+ if (!saved.hasUnpricedUsage || saved.pricingFingerprint === getPricingFingerprint()) return saved.data;
2959
+ continue;
2960
+ }
2961
+ migrated = saved.data;
2962
+ break;
2963
+ }
2742
2964
  }
2965
+ } catch (err) {
2966
+ if (err instanceof ResourceBudgetError) throw err;
2743
2967
  }
2744
- } catch (err) {
2745
- if (err instanceof ResourceBudgetError) throw err;
2746
2968
  }
2747
2969
  }
2748
- const data = await parse();
2970
+ const data = migrated ?? await parse(fileProgress(fingerprintPath, dir, progressPath, before));
2971
+ const hasUnpricedUsage = lockRecordedCosts(data);
2749
2972
  if (!before || await fingerprint(fingerprintPath) !== before) return data;
2750
2973
  await rememberRange(data);
2751
- const payload = JSON.stringify({ v: VERSION2, fingerprint: before, data, warnings });
2752
- if (Buffer.byteLength(payload) > MAX_ENTRY) return data;
2974
+ const payload = encodeEntry({
2975
+ v: VERSION2,
2976
+ fingerprint: before,
2977
+ data,
2978
+ warnings,
2979
+ pricesLocked: true,
2980
+ hasUnpricedUsage,
2981
+ pricingFingerprint: getPricingFingerprint()
2982
+ });
2983
+ if (payload) await storeParsedEntry(dir, cachePath, payload, progressPath);
2984
+ return data;
2985
+ }
2986
+ async function storeParsedEntry(dir, cachePath, payload, obsolete) {
2753
2987
  await mkdir8(dir, { recursive: true });
2754
2988
  const encoded = await compress(Buffer.from(payload));
2755
- const release = await acquireFileLock(join9(dir, "writes.lock"));
2989
+ const release = await acquireFileLock(join10(dir, "writes.lock"));
2756
2990
  try {
2757
- const quotaPath = join9(dir, "quota");
2991
+ const quotaPath = join10(dir, "quota");
2758
2992
  const quota = await readFile7(quotaPath, "utf8").catch(() => "");
2759
2993
  let total = /^\d+$/.test(quota) ? Number(quota) : NaN;
2760
2994
  const writeQuota = async (bytes) => {
@@ -2772,12 +3006,12 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
2772
3006
  const rows2 = [];
2773
3007
  for (const name of files) {
2774
3008
  if (!name.endsWith(".json") && !name.endsWith(".json.gz") && !name.endsWith(".tmp")) continue;
2775
- if (!name.startsWith(`v${VERSION2}-`)) {
2776
- await unlink5(join9(dir, name)).catch(() => {
3009
+ if (!name.startsWith(`v${VERSION2}-`) && !name.startsWith("v3-")) {
3010
+ await unlink5(join10(dir, name)).catch(() => {
2777
3011
  });
2778
3012
  continue;
2779
3013
  }
2780
- const p = join9(dir, name), s = await stat5(p).catch(() => null);
3014
+ const p = join10(dir, name), s = await stat5(p).catch(() => null);
2781
3015
  if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
2782
3016
  }
2783
3017
  total = rows2.reduce((n, r) => n + r.size, 0);
@@ -2792,12 +3026,12 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
2792
3026
  if (!(await readFile7(cachePath).catch(() => Buffer.alloc(0))).equals(encoded)) {
2793
3027
  const oldSize = (await stat5(cachePath).catch(() => null))?.size ?? 0;
2794
3028
  let nextSize = total + encoded.length;
2795
- if (!Number.isFinite(nextSize)) return data;
3029
+ if (!Number.isFinite(nextSize)) return;
2796
3030
  if (nextSize > MAX_DISK) {
2797
3031
  const rows2 = [];
2798
3032
  for (const name of await readdir3(dir)) {
2799
3033
  if (!name.endsWith(".json.gz")) continue;
2800
- const p = join9(dir, name);
3034
+ const p = join10(dir, name);
2801
3035
  if (p === cachePath) continue;
2802
3036
  const s = await stat5(p).catch(() => null);
2803
3037
  if (s) rows2.push({ path: p, size: s.size, mtime: s.mtimeMs });
@@ -2808,25 +3042,33 @@ async function cachedFileParse(path, kind, parse, warnings, range) {
2808
3042
  });
2809
3043
  nextSize -= row.size;
2810
3044
  }
2811
- if (nextSize > MAX_DISK) return data;
3045
+ if (nextSize > MAX_DISK) return;
2812
3046
  }
2813
3047
  const tmp = `${cachePath}.${randomUUID2()}.tmp`;
2814
3048
  try {
2815
3049
  await writeQuota(nextSize);
2816
3050
  await writeFile3(tmp, encoded, { mode: 384 });
2817
3051
  await rename6(tmp, cachePath);
3052
+ mutationCount++;
2818
3053
  if (oldSize) await writeQuota(nextSize - oldSize);
3054
+ total = nextSize - oldSize;
2819
3055
  } finally {
2820
3056
  await unlink5(tmp).catch(() => {
2821
3057
  });
2822
3058
  }
2823
3059
  }
3060
+ if (obsolete) {
3061
+ const size = (await stat5(obsolete).catch(() => null))?.size ?? 0;
3062
+ if (size) {
3063
+ await unlink5(obsolete);
3064
+ await writeQuota(Math.max(0, total - size));
3065
+ }
3066
+ }
2824
3067
  } finally {
2825
3068
  await release();
2826
3069
  }
2827
- return data;
2828
3070
  }
2829
- var VERSION2, compress, decompress, MAX_ENTRY, MAX_DISK;
3071
+ var VERSION2, compress, decompress, MAX_ENTRY, MAX_DISK, mutationCount, parsedFileProgress;
2830
3072
  var init_parsed_file_cache = __esm({
2831
3073
  "src/parsed-file-cache.ts"() {
2832
3074
  "use strict";
@@ -2835,11 +3077,14 @@ var init_parsed_file_cache = __esm({
2835
3077
  init_resource_budget();
2836
3078
  init_file_lock();
2837
3079
  init_discovery_cache();
2838
- VERSION2 = 3;
3080
+ init_recorded_costs();
3081
+ VERSION2 = 4;
2839
3082
  compress = promisify(gzip);
2840
3083
  decompress = promisify(gunzip);
2841
3084
  MAX_ENTRY = 16 * 1024 * 1024;
2842
3085
  MAX_DISK = 256 * 1024 * 1024;
3086
+ mutationCount = 0;
3087
+ parsedFileProgress = () => mutationCount;
2843
3088
  }
2844
3089
  });
2845
3090
 
@@ -2948,23 +3193,22 @@ function readSessionFileSync(filePath) {
2948
3193
  closeSync(fd);
2949
3194
  }
2950
3195
  }
2951
- async function* readSessionLines(filePath, startByte = 0, skipOversizedRecord) {
3196
+ async function* readSessionLines(filePath, startByte = 0, skipOversizedRecord, completedLine) {
2952
3197
  const handle = await open5(filePath, "r").catch(() => null);
2953
3198
  if (!handle) return;
2954
3199
  try {
2955
3200
  const size = (await handle.stat()).size;
2956
3201
  if (size <= startByte) return;
2957
- chargeRead(size - startByte);
2958
- const stream = handle.createReadStream({ encoding: "utf-8", start: startByte, end: size - 1, autoClose: false });
3202
+ const stream = handle.createReadStream({ start: startByte, end: size - 1, autoClose: false });
2959
3203
  let pieces = [];
2960
3204
  let lineBytes = 0;
2961
3205
  let discarding = false;
2962
3206
  const maxLineBytes = 32 * 1024 * 1024;
2963
3207
  const append = (piece) => {
2964
3208
  if (discarding) return;
2965
- lineBytes += Buffer.byteLength(piece);
3209
+ lineBytes += piece.length;
2966
3210
  if (lineBytes > maxLineBytes) {
2967
- const prefix = (pieces.slice(0, 4).join("") + piece.slice(0, 1024)).slice(0, 1024);
3211
+ const prefix = Buffer.concat([...pieces.slice(0, 4), piece.subarray(0, 1024)]).toString("utf8").slice(0, 1024);
2968
3212
  if (!skipOversizedRecord?.(prefix)) throw new ResourceBudgetError(`32 MiB JSONL record budget in ${filePath}`);
2969
3213
  discarding = true;
2970
3214
  pieces = [];
@@ -2972,23 +3216,31 @@ async function* readSessionLines(filePath, startByte = 0, skipOversizedRecord) {
2972
3216
  }
2973
3217
  pieces.push(piece);
2974
3218
  };
3219
+ let readThrough = startByte;
2975
3220
  try {
2976
3221
  for await (const chunk of stream) {
3222
+ chargeRead(chunk.length);
3223
+ const chunkStart = readThrough;
3224
+ readThrough += chunk.length;
2977
3225
  let start = 0;
2978
3226
  let newline;
2979
- while ((newline = chunk.indexOf("\n", start)) >= 0) {
2980
- append(chunk.slice(start, newline));
3227
+ while ((newline = chunk.indexOf(10, start)) >= 0) {
3228
+ append(chunk.subarray(start, newline));
2981
3229
  chargeRead(0);
2982
- const line = discarding ? null : pieces.join("").replace(/\r$/, "");
3230
+ const line = discarding ? null : (pieces.length === 1 ? pieces[0] : Buffer.concat(pieces)).toString("utf8").replace(/\r$/, "");
2983
3231
  pieces = [];
2984
3232
  lineBytes = 0;
2985
3233
  discarding = false;
2986
3234
  if (line !== null) yield line;
3235
+ completedLine?.(chunkStart + newline + 1);
2987
3236
  start = newline + 1;
2988
3237
  }
2989
- if (start < chunk.length) append(chunk.slice(start));
3238
+ if (start < chunk.length) append(chunk.subarray(start));
3239
+ }
3240
+ if (pieces.length) {
3241
+ yield (pieces.length === 1 ? pieces[0] : Buffer.concat(pieces)).toString("utf8").replace(/\r$/, "");
3242
+ completedLine?.(readThrough);
2990
3243
  }
2991
- if (pieces.length) yield pieces.join("").replace(/\r$/, "");
2992
3244
  } finally {
2993
3245
  stream.destroy();
2994
3246
  }
@@ -3011,15 +3263,15 @@ var init_fs_utils = __esm({
3011
3263
 
3012
3264
  // src/providers/claude.ts
3013
3265
  import { readdir as readdir4, stat as stat6 } from "fs/promises";
3014
- import { basename as basename2, join as join10, resolve as resolve2 } from "path";
3266
+ import { basename as basename2, join as join11, resolve as resolve2 } from "path";
3015
3267
  import { homedir as homedir4 } from "os";
3016
3268
  function getClaudeDir() {
3017
- return process.env["CLAUDE_CONFIG_DIR"] || join10(homedir4(), ".claude");
3269
+ return process.env["CLAUDE_CONFIG_DIR"] || join11(homedir4(), ".claude");
3018
3270
  }
3019
3271
  function getDesktopSessionsDir() {
3020
- if (process.platform === "darwin") return join10(homedir4(), "Library", "Application Support", "Claude", "local-agent-mode-sessions");
3021
- if (process.platform === "win32") return join10(homedir4(), "AppData", "Roaming", "Claude", "local-agent-mode-sessions");
3022
- return join10(homedir4(), ".config", "Claude", "local-agent-mode-sessions");
3272
+ if (process.platform === "darwin") return join11(homedir4(), "Library", "Application Support", "Claude", "local-agent-mode-sessions");
3273
+ if (process.platform === "win32") return join11(homedir4(), "AppData", "Roaming", "Claude", "local-agent-mode-sessions");
3274
+ return join11(homedir4(), ".config", "Claude", "local-agent-mode-sessions");
3023
3275
  }
3024
3276
  async function findDesktopProjectDirs(base) {
3025
3277
  const results = [];
@@ -3028,13 +3280,13 @@ async function findDesktopProjectDirs(base) {
3028
3280
  const entries = await readdir4(dir).catch(() => []);
3029
3281
  for (const entry of entries) {
3030
3282
  if (entry === "node_modules" || entry === ".git") continue;
3031
- const full = join10(dir, entry);
3283
+ const full = join11(dir, entry);
3032
3284
  const s = await stat6(full).catch(() => null);
3033
3285
  if (!s?.isDirectory()) continue;
3034
3286
  if (entry === "projects") {
3035
3287
  const projectDirs = await readdir4(full).catch(() => []);
3036
3288
  for (const pd of projectDirs) {
3037
- const pdFull = join10(full, pd);
3289
+ const pdFull = join11(full, pd);
3038
3290
  const pdStat = await stat6(pdFull).catch(() => null);
3039
3291
  if (pdStat?.isDirectory()) results.push(pdFull);
3040
3292
  }
@@ -3088,11 +3340,11 @@ var init_claude = __esm({
3088
3340
  const config = await readConfig();
3089
3341
  const roots = /* @__PURE__ */ new Set([getClaudeDir(), ...config.claudeConfigDirs ?? []]);
3090
3342
  for (const root of roots) {
3091
- const projectsDir = join10(root.startsWith("~/") ? join10(homedir4(), root.slice(2)) : resolve2(root), "projects");
3343
+ const projectsDir = join11(root.startsWith("~/") ? join11(homedir4(), root.slice(2)) : resolve2(root), "projects");
3092
3344
  try {
3093
3345
  const entries = await readdir4(projectsDir);
3094
3346
  for (const dirName of entries) {
3095
- const dirPath = join10(projectsDir, dirName);
3347
+ const dirPath = join11(projectsDir, dirName);
3096
3348
  const dirStat = await stat6(dirPath).catch(() => null);
3097
3349
  if (dirStat?.isDirectory()) {
3098
3350
  sources.push({ path: dirPath, project: dirName, provider: "claude" });
@@ -3119,10 +3371,10 @@ var init_claude = __esm({
3119
3371
 
3120
3372
  // src/providers/codex.ts
3121
3373
  import { readdir as readdir5, stat as stat7 } from "fs/promises";
3122
- import { basename as basename3, join as join11 } from "path";
3374
+ import { basename as basename3, join as join12 } from "path";
3123
3375
  import { homedir as homedir5 } from "os";
3124
3376
  function getCodexDir(override) {
3125
- return override ?? process.env["CODEX_HOME"] ?? join11(homedir5(), ".codex");
3377
+ return override ?? process.env["CODEX_HOME"] ?? join12(homedir5(), ".codex");
3126
3378
  }
3127
3379
  function sanitizeProject(cwd) {
3128
3380
  return cwd.replace(/^\//, "").replace(/\//g, "-");
@@ -3143,12 +3395,12 @@ async function readCodexHeader(filePath) {
3143
3395
  return { project: sanitizeProject(entry.payload?.cwd ?? "unknown") };
3144
3396
  }
3145
3397
  async function discoverSessionsInDir(codexDir) {
3146
- const sessionsDir = join11(codexDir, "sessions");
3398
+ const sessionsDir = join12(codexDir, "sessions");
3147
3399
  const sources = [];
3148
- const archivedDir = join11(codexDir, "archived_sessions");
3400
+ const archivedDir = join12(codexDir, "archived_sessions");
3149
3401
  for (const file of await readdir5(archivedDir).catch(() => [])) {
3150
3402
  if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
3151
- const filePath = join11(archivedDir, file);
3403
+ const filePath = join12(archivedDir, file);
3152
3404
  const s = await stat7(filePath).catch(() => null);
3153
3405
  if (!s?.isFile()) continue;
3154
3406
  const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
@@ -3162,19 +3414,19 @@ async function discoverSessionsInDir(codexDir) {
3162
3414
  }
3163
3415
  for (const year of years) {
3164
3416
  if (!/^\d{4}$/.test(year)) continue;
3165
- const yearDir = join11(sessionsDir, year);
3417
+ const yearDir = join12(sessionsDir, year);
3166
3418
  const months = await readdir5(yearDir).catch(() => []);
3167
3419
  for (const month of months) {
3168
3420
  if (!/^\d{2}$/.test(month)) continue;
3169
- const monthDir = join11(yearDir, month);
3421
+ const monthDir = join12(yearDir, month);
3170
3422
  const days = await readdir5(monthDir).catch(() => []);
3171
3423
  for (const day of days) {
3172
3424
  if (!/^\d{2}$/.test(day)) continue;
3173
- const dayDir = join11(monthDir, day);
3425
+ const dayDir = join12(monthDir, day);
3174
3426
  const files = await readdir5(dayDir).catch(() => []);
3175
3427
  for (const file of files) {
3176
3428
  if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
3177
- const filePath = join11(dayDir, file);
3429
+ const filePath = join12(dayDir, file);
3178
3430
  const s = await stat7(filePath).catch(() => null);
3179
3431
  if (!s?.isFile()) continue;
3180
3432
  const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
@@ -3393,10 +3645,10 @@ var init_codex = __esm({
3393
3645
 
3394
3646
  // src/providers/copilot.ts
3395
3647
  import { readdir as readdir6, stat as stat8 } from "fs/promises";
3396
- import { basename as basename4, dirname as dirname3, join as join12 } from "path";
3648
+ import { basename as basename4, dirname as dirname3, join as join13 } from "path";
3397
3649
  import { homedir as homedir6 } from "os";
3398
3650
  function getCopilotSessionStateDir(override) {
3399
- return override ?? join12(homedir6(), ".copilot", "session-state");
3651
+ return override ?? join13(homedir6(), ".copilot", "session-state");
3400
3652
  }
3401
3653
  function parseCwd(yaml) {
3402
3654
  const match = yaml.match(/^cwd:\s*(.+)$/m);
@@ -3471,11 +3723,11 @@ async function discoverSessionsInDir2(sessionStateDir) {
3471
3723
  return sources;
3472
3724
  }
3473
3725
  for (const sessionId of sessionDirs) {
3474
- const eventsPath = join12(sessionStateDir, sessionId, "events.jsonl");
3726
+ const eventsPath = join13(sessionStateDir, sessionId, "events.jsonl");
3475
3727
  const s = await stat8(eventsPath).catch(() => null);
3476
3728
  if (!s?.isFile()) continue;
3477
3729
  let project = sessionId;
3478
- const yaml = await readSessionFile(join12(sessionStateDir, sessionId, "workspace.yaml"));
3730
+ const yaml = await readSessionFile(join13(sessionStateDir, sessionId, "workspace.yaml"));
3479
3731
  if (yaml !== null) {
3480
3732
  const cwd = parseCwd(yaml);
3481
3733
  if (cwd) project = basename4(cwd);
@@ -3589,13 +3841,13 @@ var init_bash_utils = __esm({
3589
3841
 
3590
3842
  // src/providers/pi.ts
3591
3843
  import { readdir as readdir7, stat as stat9 } from "fs/promises";
3592
- import { basename as basename6, join as join13 } from "path";
3844
+ import { basename as basename6, join as join14 } from "path";
3593
3845
  import { homedir as homedir7 } from "os";
3594
3846
  function getPiSessionsDir(override) {
3595
- return override ?? join13(homedir7(), ".pi", "agent", "sessions");
3847
+ return override ?? join14(homedir7(), ".pi", "agent", "sessions");
3596
3848
  }
3597
3849
  function getOmpSessionsDir(override) {
3598
- return override ?? join13(homedir7(), ".omp", "agent", "sessions");
3850
+ return override ?? join14(homedir7(), ".omp", "agent", "sessions");
3599
3851
  }
3600
3852
  async function readFirstEntry(filePath) {
3601
3853
  const line = await readSessionFirstLine(filePath);
@@ -3615,7 +3867,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
3615
3867
  return sources;
3616
3868
  }
3617
3869
  for (const dirName of projectDirs) {
3618
- const dirPath = join13(sessionsDir, dirName);
3870
+ const dirPath = join14(sessionsDir, dirName);
3619
3871
  const dirStat = await stat9(dirPath).catch(() => null);
3620
3872
  if (!dirStat?.isDirectory()) continue;
3621
3873
  let files;
@@ -3626,7 +3878,7 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
3626
3878
  }
3627
3879
  for (const file of files) {
3628
3880
  if (!file.endsWith(".jsonl")) continue;
3629
- const filePath = join13(dirPath, file);
3881
+ const filePath = join14(dirPath, file);
3630
3882
  const fileStat = await stat9(filePath).catch(() => null);
3631
3883
  if (!fileStat?.isFile()) continue;
3632
3884
  const first = await readFirstEntry(filePath);
@@ -3783,12 +4035,12 @@ var init_pi = __esm({
3783
4035
  });
3784
4036
 
3785
4037
  // src/providers/kimi.ts
3786
- import { createHash as createHash3 } from "crypto";
4038
+ import { createHash as createHash4 } from "crypto";
3787
4039
  import { readdir as readdir8, readFile as readFile8, stat as stat10 } from "fs/promises";
3788
- import { basename as basename7, dirname as dirname4, join as join14 } from "path";
4040
+ import { basename as basename7, dirname as dirname4, join as join15 } from "path";
3789
4041
  import { homedir as homedir8 } from "os";
3790
4042
  function createKimiProvider(home) {
3791
- const root = () => home ?? process.env["KIMI_CODE_HOME"] ?? join14(homedir8(), ".kimi-code");
4043
+ const root = () => home ?? process.env["KIMI_CODE_HOME"] ?? join15(homedir8(), ".kimi-code");
3792
4044
  return {
3793
4045
  name: "kimi",
3794
4046
  displayName: "Kimi",
@@ -3796,7 +4048,7 @@ function createKimiProvider(home) {
3796
4048
  toolDisplayName: (tool) => tool,
3797
4049
  async discoverSessions() {
3798
4050
  const projects = /* @__PURE__ */ new Map();
3799
- const catalog = join14(root(), "workspaces.json");
4051
+ const catalog = join15(root(), "workspaces.json");
3800
4052
  try {
3801
4053
  const size = (await stat10(catalog)).size;
3802
4054
  if (size <= 2 * 1024 * 1024) {
@@ -3818,16 +4070,16 @@ function createKimiProvider(home) {
3818
4070
  const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
3819
4071
  for (const entry of entries) {
3820
4072
  if (entry.name.startsWith(".")) continue;
3821
- const path = join14(dir, entry.name);
4073
+ const path = join15(dir, entry.name);
3822
4074
  if (entry.isDirectory()) await walk(path, project, depth + 1);
3823
4075
  else if (entry.isFile() && entry.name === "wire.jsonl") sources.push({ path, project, provider: "kimi" });
3824
4076
  }
3825
4077
  }
3826
- const sessions = join14(root(), "sessions");
4078
+ const sessions = join15(root(), "sessions");
3827
4079
  for (const entry of await readdir8(sessions, { withFileTypes: true }).catch(() => [])) {
3828
4080
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
3829
4081
  const project = projects.get(entry.name) ?? entry.name.replace(/^wd_/, "").replace(/_[a-f\d]{12}$/, "");
3830
- await walk(join14(sessions, entry.name), project, 0);
4082
+ await walk(join15(sessions, entry.name), project, 0);
3831
4083
  }
3832
4084
  return sources.sort((a, b) => a.path.localeCompare(b.path));
3833
4085
  },
@@ -3866,7 +4118,7 @@ function createKimiProvider(home) {
3866
4118
  const warning = `No API pricing for model "${model}"; its tokens are counted but its cost is excluded. Configure a model alias only when the underlying API model is known.`;
3867
4119
  if (!warnings.includes(warning)) warnings.push(warning);
3868
4120
  }
3869
- const key2 = "kimi:" + createHash3("sha256").update(JSON.stringify([
4121
+ const key2 = "kimi:" + createHash4("sha256").update(JSON.stringify([
3870
4122
  data.agentId ?? agent,
3871
4123
  data.time,
3872
4124
  data.model,
@@ -3916,10 +4168,10 @@ var init_kimi = __esm({
3916
4168
  // src/cursor-cache.ts
3917
4169
  import { readFile as readFile9, mkdir as mkdir9, stat as stat11, rename as rename7 } from "fs/promises";
3918
4170
  import { open as open6 } from "fs/promises";
3919
- import { join as join15 } from "path";
4171
+ import { join as join16 } from "path";
3920
4172
  import { randomBytes as randomBytes4 } from "crypto";
3921
4173
  function getCachePath2() {
3922
- return join15(getCacheDir(), CACHE_FILE);
4174
+ return join16(getCacheDir(), CACHE_FILE);
3923
4175
  }
3924
4176
  async function getDbFingerprint(dbPath) {
3925
4177
  try {
@@ -3984,100 +4236,23 @@ var init_cursor_cache = __esm({
3984
4236
  }
3985
4237
  });
3986
4238
 
3987
- // src/sqlite.ts
3988
- import { createRequire } from "module";
3989
- function loadDriver() {
3990
- if (loadAttempted) return DatabaseSync !== null;
3991
- loadAttempted = true;
3992
- const origEmit = process.emit.bind(process);
3993
- let restored = false;
3994
- const restore = () => {
3995
- if (restored) return;
3996
- restored = true;
3997
- process.emit = origEmit;
3998
- };
3999
- process.emit = function patchedEmit(event, ...args) {
4000
- if (event === "warning") {
4001
- const warning = args[0];
4002
- if (warning?.name === "ExperimentalWarning" && typeof warning.message === "string" && /SQLite/i.test(warning.message)) {
4003
- return false;
4004
- }
4005
- }
4006
- return origEmit.call(this, event, ...args);
4007
- };
4008
- try {
4009
- const mod = requireForSqlite("node:sqlite");
4010
- DatabaseSync = mod.DatabaseSync;
4011
- return true;
4012
- } catch (err) {
4013
- const message = err instanceof Error ? err.message : String(err);
4014
- loadError = `SQLite-based providers (Cursor, OpenCode) need Node 22+ with the node:sqlite module.
4015
- Current Node: ${process.version}.
4016
- Upgrade Node (https://nodejs.org) and run exe-watcher again.
4017
- (underlying error: ${message})`;
4018
- return false;
4019
- } finally {
4020
- setTimeout(restore, 0);
4021
- }
4022
- }
4023
- function isSqliteAvailable() {
4024
- return loadDriver();
4025
- }
4026
- function getSqliteLoadError() {
4027
- return loadError ?? "SQLite driver not available";
4028
- }
4029
- function openDatabase(path) {
4030
- if (!loadDriver() || DatabaseSync === null) {
4031
- throw new Error(getSqliteLoadError());
4032
- }
4033
- const db = new DatabaseSync(path, { readOnly: true });
4034
- return {
4035
- query(sql, params = []) {
4036
- const rows2 = [];
4037
- let bytes = 0;
4038
- for (const row of db.prepare(sql).iterate(...params)) {
4039
- const size = Buffer.byteLength(JSON.stringify(row));
4040
- bytes += size;
4041
- if (rows2.length >= 1e5 || bytes > 32 * 1024 * 1024) throw new ResourceBudgetError("SQLite result budget");
4042
- chargeRead(size);
4043
- rows2.push(row);
4044
- }
4045
- return rows2;
4046
- },
4047
- close() {
4048
- db.close();
4049
- }
4050
- };
4051
- }
4052
- var requireForSqlite, DatabaseSync, loadAttempted, loadError;
4053
- var init_sqlite = __esm({
4054
- "src/sqlite.ts"() {
4055
- "use strict";
4056
- init_resource_budget();
4057
- requireForSqlite = createRequire(import.meta.url);
4058
- DatabaseSync = null;
4059
- loadAttempted = false;
4060
- loadError = null;
4061
- }
4062
- });
4063
-
4064
4239
  // src/providers/cursor.ts
4065
4240
  var cursor_exports = {};
4066
4241
  __export(cursor_exports, {
4067
4242
  createCursorProvider: () => createCursorProvider,
4068
4243
  cursor: () => cursor
4069
4244
  });
4070
- import { existsSync } from "fs";
4071
- import { join as join16 } from "path";
4245
+ import { existsSync as existsSync2 } from "fs";
4246
+ import { join as join17 } from "path";
4072
4247
  import { homedir as homedir9 } from "os";
4073
4248
  function getCursorDbPath() {
4074
4249
  if (process.platform === "darwin") {
4075
- return join16(homedir9(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
4250
+ return join17(homedir9(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
4076
4251
  }
4077
4252
  if (process.platform === "win32") {
4078
- return join16(homedir9(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
4253
+ return join17(homedir9(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
4079
4254
  }
4080
- return join16(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
4255
+ return join17(homedir9(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
4081
4256
  }
4082
4257
  function extractLanguages(codeBlocksJson) {
4083
4258
  if (!codeBlocksJson) return [];
@@ -4249,7 +4424,7 @@ function createCursorProvider(dbPathOverride) {
4249
4424
  async discoverSessions() {
4250
4425
  if (!isSqliteAvailable()) return [];
4251
4426
  const dbPath = dbPathOverride ?? getCursorDbPath();
4252
- if (!existsSync(dbPath)) return [];
4427
+ if (!existsSync2(dbPath)) return [];
4253
4428
  return [{ path: dbPath, project: "cursor", provider: "cursor" }];
4254
4429
  },
4255
4430
  createSessionParser(source, seenKeys) {
@@ -4320,19 +4495,19 @@ __export(opencode_exports, {
4320
4495
  opencode: () => opencode
4321
4496
  });
4322
4497
  import { readdir as readdir9 } from "fs/promises";
4323
- import { join as join17 } from "path";
4498
+ import { join as join18 } from "path";
4324
4499
  import { homedir as homedir10 } from "os";
4325
4500
  function sanitize2(dir) {
4326
4501
  return dir.replace(/^\//, "").replace(/\//g, "-");
4327
4502
  }
4328
4503
  function getDataDir(dataDir) {
4329
- const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join17(homedir10(), ".local", "share");
4330
- return join17(base, "opencode");
4504
+ const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join18(homedir10(), ".local", "share");
4505
+ return join18(base, "opencode");
4331
4506
  }
4332
4507
  async function findDbFiles(dir) {
4333
4508
  try {
4334
4509
  const entries = await readdir9(dir);
4335
- return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join17(dir, f));
4510
+ return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join18(dir, f));
4336
4511
  } catch (error) {
4337
4512
  if (error instanceof ResourceBudgetError) throw error;
4338
4513
  return [];
@@ -4561,20 +4736,20 @@ __export(cursor_agent_exports, {
4561
4736
  createCursorAgentProvider: () => createCursorAgentProvider,
4562
4737
  cursor_agent: () => cursor_agent
4563
4738
  });
4564
- import { createHash as createHash4 } from "crypto";
4565
- import { existsSync as existsSync2 } from "fs";
4739
+ import { createHash as createHash5 } from "crypto";
4740
+ import { existsSync as existsSync3 } from "fs";
4566
4741
  import { readdir as readdir10, stat as stat12 } from "fs/promises";
4567
- import { join as join18, basename as basename8 } from "path";
4742
+ import { join as join19, basename as basename8 } from "path";
4568
4743
  import { homedir as homedir11 } from "os";
4569
4744
  function getCursorAgentBaseDir(baseDirOverride) {
4570
4745
  if (baseDirOverride) return baseDirOverride;
4571
- return join18(homedir11(), ".cursor");
4746
+ return join19(homedir11(), ".cursor");
4572
4747
  }
4573
4748
  function getProjectsDir(baseDir) {
4574
- return join18(baseDir, "projects");
4749
+ return join19(baseDir, "projects");
4575
4750
  }
4576
4751
  function getAttributionDbPath(baseDir) {
4577
- return join18(baseDir, "ai-tracking", "ai-code-tracking.db");
4752
+ return join19(baseDir, "ai-tracking", "ai-code-tracking.db");
4578
4753
  }
4579
4754
  function estimateTokens(charCount) {
4580
4755
  if (charCount <= 0) return 0;
@@ -4625,7 +4800,7 @@ function resolveModel3(raw) {
4625
4800
  function toConversationId(transcriptPath) {
4626
4801
  const filename = basename8(transcriptPath, ".txt");
4627
4802
  if (filename.length === 36 && UUID_LIKE.test(filename)) return filename;
4628
- return createHash4("sha1").update(transcriptPath).digest("hex").slice(0, 16);
4803
+ return createHash5("sha1").update(transcriptPath).digest("hex").slice(0, 16);
4629
4804
  }
4630
4805
  function extractUserQuery(userBlock) {
4631
4806
  const chunks = [];
@@ -4734,7 +4909,7 @@ function createParser6(source, seenKeys, dbPath, summariesByConversationId) {
4734
4909
  let db = null;
4735
4910
  try {
4736
4911
  if (!summary) {
4737
- if (existsSync2(dbPath)) {
4912
+ if (existsSync3(dbPath)) {
4738
4913
  try {
4739
4914
  db = openDatabase(dbPath);
4740
4915
  const rows2 = db.query(CONVERSATION_SUMMARY_QUERY, [conversationId]);
@@ -4827,19 +5002,19 @@ function createCursorAgentProvider(baseDirOverride) {
4827
5002
  return rawTool;
4828
5003
  },
4829
5004
  async discoverSessions() {
4830
- if (!existsSync2(projectsDir)) return [];
5005
+ if (!existsSync3(projectsDir)) return [];
4831
5006
  const projectEntries = await readdir10(projectsDir, { withFileTypes: true });
4832
5007
  const sources = [];
4833
5008
  for (const entry of projectEntries) {
4834
5009
  if (!entry.isDirectory()) continue;
4835
5010
  const projectId = prettifyProjectId(entry.name);
4836
- const transcriptDir = join18(projectsDir, entry.name, "agent-transcripts");
4837
- if (!existsSync2(transcriptDir)) continue;
5011
+ const transcriptDir = join19(projectsDir, entry.name, "agent-transcripts");
5012
+ if (!existsSync3(transcriptDir)) continue;
4838
5013
  const transcriptEntries = await readdir10(transcriptDir, { withFileTypes: true });
4839
5014
  for (const transcript of transcriptEntries) {
4840
5015
  if (!transcript.isFile()) continue;
4841
5016
  if (!transcript.name.endsWith(".txt")) continue;
4842
- const transcriptPath = join18(transcriptDir, transcript.name);
5017
+ const transcriptPath = join19(transcriptDir, transcript.name);
4843
5018
  sources.push({
4844
5019
  path: transcriptPath,
4845
5020
  project: projectId,
@@ -5153,15 +5328,15 @@ var init_classifier = __esm({
5153
5328
 
5154
5329
  // src/session-index.ts
5155
5330
  import { stat as stat13 } from "fs/promises";
5156
- import { existsSync as existsSync3, unlinkSync } from "fs";
5331
+ import { existsSync as existsSync4, unlinkSync } from "fs";
5157
5332
  import { readFile as readFile11, mkdir as mkdir10, rename as rename8, unlink as unlink6, open as open7 } from "fs/promises";
5158
- import { join as join19 } from "path";
5333
+ import { join as join20 } from "path";
5159
5334
  import { randomBytes as randomBytes5 } from "crypto";
5160
5335
  function emptyIndex() {
5161
5336
  return { v: SESSION_INDEX_VERSION, e: {} };
5162
5337
  }
5163
5338
  function indexPath() {
5164
- return join19(getCacheDir(), INDEX_FILENAME);
5339
+ return join20(getCacheDir(), INDEX_FILENAME);
5165
5340
  }
5166
5341
  async function loadSessionIndex() {
5167
5342
  if (currentIndex) return currentIndex;
@@ -5181,7 +5356,7 @@ async function loadSessionIndex() {
5181
5356
  }
5182
5357
  async function saveSessionIndex(index) {
5183
5358
  const dir = getCacheDir();
5184
- if (!existsSync3(dir)) await mkdir10(dir, { recursive: true });
5359
+ if (!existsSync4(dir)) await mkdir10(dir, { recursive: true });
5185
5360
  const finalPath = indexPath();
5186
5361
  const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
5187
5362
  const payload = JSON.stringify(index);
@@ -5263,7 +5438,7 @@ __export(parser_exports, {
5263
5438
  });
5264
5439
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
5265
5440
  import { readdir as readdir11, stat as stat14 } from "fs/promises";
5266
- import { basename as basename9, join as join20 } from "path";
5441
+ import { basename as basename9, join as join21 } from "path";
5267
5442
  function getParseWarnings() {
5268
5443
  return _parseWarnings;
5269
5444
  }
@@ -5349,71 +5524,95 @@ function parseApiCall(entry) {
5349
5524
  deduplicationKey: msg.id ?? `claude:${entry.timestamp}`
5350
5525
  };
5351
5526
  }
5352
- async function parseTurns(filePath) {
5527
+ function validTurnScan(value) {
5528
+ const x = value;
5529
+ const amount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
5530
+ const calls = (list) => Array.isArray(list) && list.every((c) => c && typeof c.deduplicationKey === "string" && typeof c.model === "string" && typeof c.timestamp === "string" && amount(c.costUSD) && c.usage && ["inputTokens", "outputTokens", "cacheCreationInputTokens", "cacheReadInputTokens"].every((key2) => amount(c.usage[key2])) && Array.isArray(c.tools) && Array.isArray(c.bashCommands));
5531
+ return !!x && Array.isArray(x.turns) && x.turns.every((t) => t && calls(t.assistantCalls) && typeof t.userMessage === "string" && typeof t.timestamp === "string" && typeof t.sessionId === "string") && calls(x.currentCalls) && typeof x.currentUserMessage === "string" && Number.isSafeInteger(x.currentUserSignals) && x.currentUserSignals >= 0 && typeof x.currentTimestamp === "string" && typeof x.currentSessionId === "string" && Number.isSafeInteger(x.lineNumber) && x.lineNumber >= 0;
5532
+ }
5533
+ async function parseTurns(filePath, progress) {
5534
+ const checkpoint = await progress.load(validTurnScan);
5535
+ const saved = checkpoint?.state;
5353
5536
  const seenCalls = /* @__PURE__ */ new Map();
5354
- const turns = [];
5355
- let currentUserMessage = "";
5356
- let currentUserSignals = 0;
5357
- let currentCalls = [];
5358
- let currentTimestamp = "";
5359
- let currentSessionId = "";
5360
- let lineNumber = 0;
5361
- for await (const line of readSessionLines(filePath)) {
5362
- lineNumber++;
5363
- const entry = parseJsonlLine(line);
5364
- if (!entry) continue;
5365
- if (entry.type === "user") {
5366
- const text = getUserMessageText(entry);
5367
- if (text.trim()) {
5368
- if (currentCalls.length > 0) {
5369
- turns.push({
5370
- userMessage: currentUserMessage,
5371
- userMessageSignals: currentUserSignals,
5372
- assistantCalls: currentCalls,
5373
- timestamp: currentTimestamp,
5374
- sessionId: currentSessionId
5375
- });
5537
+ const turns = saved?.turns ?? [];
5538
+ let currentUserMessage = saved?.currentUserMessage ?? "";
5539
+ let currentUserSignals = saved?.currentUserSignals ?? 0;
5540
+ let currentCalls = saved?.currentCalls ?? [];
5541
+ let currentTimestamp = saved?.currentTimestamp ?? "";
5542
+ let currentSessionId = saved?.currentSessionId ?? "";
5543
+ for (const turn of turns) for (const call of turn.assistantCalls) seenCalls.set(call.deduplicationKey, call);
5544
+ for (const call of currentCalls) seenCalls.set(call.deduplicationKey, call);
5545
+ let lineNumber = saved?.lineNumber ?? 0;
5546
+ let offset = checkpoint?.offset ?? 0;
5547
+ try {
5548
+ for await (const line of readSessionLines(filePath, offset, void 0, (next) => {
5549
+ offset = next;
5550
+ })) {
5551
+ chargeRecord();
5552
+ lineNumber++;
5553
+ const entry = parseJsonlLine(line);
5554
+ if (!entry) continue;
5555
+ if (entry.type === "user") {
5556
+ const text = getUserMessageText(entry);
5557
+ if (text.trim()) {
5558
+ if (currentCalls.length > 0) {
5559
+ turns.push({
5560
+ userMessage: currentUserMessage,
5561
+ userMessageSignals: currentUserSignals,
5562
+ assistantCalls: currentCalls,
5563
+ timestamp: currentTimestamp,
5564
+ sessionId: currentSessionId
5565
+ });
5566
+ }
5567
+ const compact = compactUserMessage(text);
5568
+ currentUserMessage = compact.userMessage;
5569
+ currentUserSignals = compact.userMessageSignals;
5570
+ currentCalls = [];
5571
+ currentTimestamp = entry.timestamp ?? "";
5572
+ currentSessionId = entry.sessionId ?? "";
5376
5573
  }
5377
- const compact = compactUserMessage(text);
5378
- currentUserMessage = compact.userMessage;
5379
- currentUserSignals = compact.userMessageSignals;
5380
- currentCalls = [];
5381
- currentTimestamp = entry.timestamp ?? "";
5382
- currentSessionId = entry.sessionId ?? "";
5383
- }
5384
- } else if (entry.type === "assistant") {
5385
- const msgId = getMessageId(entry);
5386
- const call = parseApiCall(entry);
5387
- if (!call) continue;
5388
- const previous = msgId ? seenCalls.get(msgId) : void 0;
5389
- if (previous) {
5390
- for (const key2 of Object.keys(call.usage)) {
5391
- previous.usage[key2] = Math.max(previous.usage[key2] ?? 0, call.usage[key2] ?? 0);
5574
+ } else if (entry.type === "assistant") {
5575
+ const msgId = getMessageId(entry);
5576
+ const call = parseApiCall(entry);
5577
+ if (!call) continue;
5578
+ const previous = msgId ? seenCalls.get(msgId) : void 0;
5579
+ if (previous) {
5580
+ for (const key2 of Object.keys(call.usage)) {
5581
+ previous.usage[key2] = Math.max(previous.usage[key2] ?? 0, call.usage[key2] ?? 0);
5582
+ }
5583
+ previous.tools = [.../* @__PURE__ */ new Set([...previous.tools, ...call.tools])];
5584
+ previous.mcpTools = extractMcpTools(previous.tools);
5585
+ previous.bashCommands = [.../* @__PURE__ */ new Set([...previous.bashCommands, ...call.bashCommands])];
5586
+ previous.hasAgentSpawn ||= call.hasAgentSpawn;
5587
+ previous.hasPlanMode ||= call.hasPlanMode;
5588
+ if (call.speed === "fast") previous.speed = "fast";
5589
+ previous.costUSD = calculateCost(
5590
+ previous.model,
5591
+ previous.usage.inputTokens,
5592
+ previous.usage.outputTokens,
5593
+ previous.usage.cacheCreationInputTokens,
5594
+ previous.usage.cacheReadInputTokens,
5595
+ previous.usage.webSearchRequests,
5596
+ previous.speed,
5597
+ previous.usage.inputTokens + previous.usage.cacheCreationInputTokens + previous.usage.cacheReadInputTokens,
5598
+ previous.usage.cacheCreation1hInputTokens
5599
+ );
5600
+ continue;
5392
5601
  }
5393
- previous.tools = [.../* @__PURE__ */ new Set([...previous.tools, ...call.tools])];
5394
- previous.mcpTools = extractMcpTools(previous.tools);
5395
- previous.bashCommands = [.../* @__PURE__ */ new Set([...previous.bashCommands, ...call.bashCommands])];
5396
- previous.hasAgentSpawn ||= call.hasAgentSpawn;
5397
- previous.hasPlanMode ||= call.hasPlanMode;
5398
- if (call.speed === "fast") previous.speed = "fast";
5399
- previous.costUSD = calculateCost(
5400
- previous.model,
5401
- previous.usage.inputTokens,
5402
- previous.usage.outputTokens,
5403
- previous.usage.cacheCreationInputTokens,
5404
- previous.usage.cacheReadInputTokens,
5405
- previous.usage.webSearchRequests,
5406
- previous.speed,
5407
- previous.usage.inputTokens + previous.usage.cacheCreationInputTokens + previous.usage.cacheReadInputTokens,
5408
- previous.usage.cacheCreation1hInputTokens
5409
- );
5410
- continue;
5602
+ if (msgId) seenCalls.set(msgId, call);
5603
+ else call.deduplicationKey = `${filePath}:${lineNumber}`;
5604
+ currentCalls.push(call);
5605
+ }
5606
+ }
5607
+ } catch (error) {
5608
+ if (error instanceof ResourceBudgetError) {
5609
+ try {
5610
+ await progress.save({ turns, currentCalls, currentUserMessage, currentUserSignals, currentTimestamp, currentSessionId, lineNumber }, offset);
5611
+ } catch {
5612
+ _parseWarnings.push("A historical transcript checkpoint could not be saved; its unfinished usage remains unknown.");
5411
5613
  }
5412
- if (msgId) seenCalls.set(msgId, call);
5413
- else call.deduplicationKey = `${filePath}:${lineNumber}`;
5414
- chargeRecord();
5415
- currentCalls.push(call);
5416
5614
  }
5615
+ throw error;
5417
5616
  }
5418
5617
  if (currentCalls.length > 0) {
5419
5618
  turns.push({
@@ -5518,7 +5717,7 @@ async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
5518
5717
  } catch {
5519
5718
  }
5520
5719
  }
5521
- const rawTurns = await cachedFileParse(filePath, "claude-turns", () => parseTurns(filePath));
5720
+ const rawTurns = await cachedFileParse(filePath, "claude-turns", (progress) => parseTurns(filePath, progress));
5522
5721
  const sessionId = basename9(filePath, ".jsonl");
5523
5722
  const turns = [];
5524
5723
  for (const turn of rawTurns) {
@@ -5553,13 +5752,13 @@ async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
5553
5752
  }
5554
5753
  async function collectJsonlFiles2(dirPath) {
5555
5754
  const files = await readdir11(dirPath).catch(() => []);
5556
- const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join20(dirPath, f));
5755
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join21(dirPath, f));
5557
5756
  for (const entry of files) {
5558
5757
  if (entry.endsWith(".jsonl")) continue;
5559
- const subagentsPath = join20(dirPath, entry, "subagents");
5758
+ const subagentsPath = join21(dirPath, entry, "subagents");
5560
5759
  const subFiles = await readdir11(subagentsPath).catch(() => []);
5561
5760
  for (const sf of subFiles) {
5562
- if (sf.endsWith(".jsonl")) jsonlFiles.push(join20(subagentsPath, sf));
5761
+ if (sf.endsWith(".jsonl")) jsonlFiles.push(join21(subagentsPath, sf));
5563
5762
  }
5564
5763
  }
5565
5764
  return jsonlFiles;
@@ -5925,8 +6124,8 @@ var init_parser = __esm({
5925
6124
  init_cache_dir();
5926
6125
  init_file_lock();
5927
6126
  init_resource_budget();
5928
- import { existsSync as existsSync7, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
5929
- import { join as join30 } from "path";
6127
+ import { existsSync as existsSync8, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
6128
+ import { join as join31 } from "path";
5930
6129
  import { homedir as homedir17 } from "os";
5931
6130
  import { Command } from "commander";
5932
6131
 
@@ -6657,19 +6856,19 @@ function renderStatusBar(projects) {
6657
6856
 
6658
6857
  // src/menubar-json.ts
6659
6858
  import { readdirSync, readFileSync } from "fs";
6660
- import { join as join21, dirname as dirname5 } from "path";
6859
+ import { join as join22, dirname as dirname5 } from "path";
6661
6860
  init_models();
6662
6861
  import { fileURLToPath } from "url";
6663
6862
  function getCliVersion() {
6664
6863
  try {
6665
- const pkgPath = join21(dirname5(fileURLToPath(import.meta.url)), "..", "package.json");
6864
+ const pkgPath = join22(dirname5(fileURLToPath(import.meta.url)), "..", "package.json");
6666
6865
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
6667
6866
  return pkg.version ?? "0.0.0";
6668
6867
  } catch {
6669
6868
  return "0.0.0";
6670
6869
  }
6671
6870
  }
6672
- var MIN_APP_VERSION = "0.2.59";
6871
+ var MIN_APP_VERSION = "0.2.67";
6673
6872
  var TOP_ACTIVITIES_LIMIT = 20;
6674
6873
  var TOP_MODELS_LIMIT = 20;
6675
6874
  var TOP_FINDINGS_LIMIT = 10;
@@ -6825,18 +7024,18 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
6825
7024
  // src/daily-cache.ts
6826
7025
  init_cache_dir();
6827
7026
  init_models();
6828
- import { createHash as createHash5, randomBytes as randomBytes6 } from "crypto";
6829
- import { existsSync as existsSync4 } from "fs";
7027
+ import { createHash as createHash6, randomBytes as randomBytes6 } from "crypto";
7028
+ import { existsSync as existsSync5 } from "fs";
6830
7029
  import { mkdir as mkdir11, open as open8, readFile as readFile12, rename as rename9, unlink as unlink7 } from "fs/promises";
6831
- import { join as join22 } from "path";
7030
+ import { join as join23 } from "path";
6832
7031
  var DAILY_CACHE_VERSION = 9;
6833
7032
  var DEFAULT_DAILY_CACHE_SCOPE = "global";
6834
7033
  var DAILY_CACHE_FILENAME = "daily-cache.json";
6835
7034
  function getCachePath3(scopeKey) {
6836
7035
  if (scopeKey === DEFAULT_DAILY_CACHE_SCOPE) {
6837
- return join22(getCacheDir(), DAILY_CACHE_FILENAME);
7036
+ return join23(getCacheDir(), DAILY_CACHE_FILENAME);
6838
7037
  }
6839
- return join22(getCacheDir(), `daily-cache-${scopeKey}.json`);
7038
+ return join23(getCacheDir(), `daily-cache-${scopeKey}.json`);
6840
7039
  }
6841
7040
  function emptyCache(scopeKey) {
6842
7041
  return { version: DAILY_CACHE_VERSION, scopeKey, lastComputedDate: null, days: [] };
@@ -6851,7 +7050,7 @@ function buildDailyCacheScopeKey(include, exclude) {
6851
7050
  return DEFAULT_DAILY_CACHE_SCOPE;
6852
7051
  }
6853
7052
  const raw = JSON.stringify({ include: normalizedInclude, exclude: normalizedExclude });
6854
- return `scope-${createHash5("sha1").update(raw).digest("hex").slice(0, 12)}`;
7053
+ return `scope-${createHash6("sha1").update(raw).digest("hex").slice(0, 12)}`;
6855
7054
  }
6856
7055
  function isValidCache(parsed) {
6857
7056
  if (!parsed || typeof parsed !== "object") return false;
@@ -6859,6 +7058,7 @@ function isValidCache(parsed) {
6859
7058
  if (c.version !== DAILY_CACHE_VERSION && c.version !== 8) return false;
6860
7059
  if (typeof c.scopeKey !== "string" || c.scopeKey.length === 0) return false;
6861
7060
  if (!Array.isArray(c.days)) return false;
7061
+ if (c.historyScanProgress !== void 0 && (!Number.isSafeInteger(c.historyScanProgress) || c.historyScanProgress < 0)) return false;
6862
7062
  if (c.missingProviders && (!Array.isArray(c.missingProviders) || !c.missingProviders.every((p) => typeof p === "string"))) return false;
6863
7063
  if (c.historyBuild) {
6864
7064
  const stage = c.historyBuild;
@@ -6869,20 +7069,20 @@ function isValidCache(parsed) {
6869
7069
  }
6870
7070
  async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
6871
7071
  const path = getCachePath3(scopeKey);
6872
- if (!existsSync4(path)) return emptyCache(scopeKey);
7072
+ if (!existsSync5(path)) return emptyCache(scopeKey);
6873
7073
  try {
6874
7074
  const raw = await readFile12(path, "utf-8");
6875
7075
  const parsed = JSON.parse(raw);
6876
7076
  if (!isValidCache(parsed)) return emptyCache(scopeKey);
6877
7077
  if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
6878
- if ((parsed.days.length || parsed.historyBuild) && parsed.pricingFingerprint !== getPricingFingerprint()) {
6879
- return {
6880
- ...parsed,
6881
- version: DAILY_CACHE_VERSION,
6882
- historyBuild: void 0,
6883
- needsRepricing: true,
6884
- previousHistoryStart: parsed.days[0]?.date ?? parsed.historyBuild?.start
6885
- };
7078
+ if (parsed.needsRepricing) {
7079
+ const repricing = parsed.historyBuild;
7080
+ if (parsed.days.length) {
7081
+ parsed.historyBuild = void 0;
7082
+ parsed.backfill = repricing?.previousBackfill ?? null;
7083
+ }
7084
+ delete parsed.needsRepricing;
7085
+ delete parsed.previousHistoryStart;
6886
7086
  }
6887
7087
  if (parsed.version !== DAILY_CACHE_VERSION) {
6888
7088
  return {
@@ -6899,7 +7099,7 @@ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
6899
7099
  }
6900
7100
  async function saveDailyCache(cache) {
6901
7101
  const dir = getCacheDir();
6902
- if (!existsSync4(dir)) await mkdir11(dir, { recursive: true });
7102
+ if (!existsSync5(dir)) await mkdir11(dir, { recursive: true });
6903
7103
  const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
6904
7104
  const finalPath = getCachePath3(scopeKey);
6905
7105
  const tempPath = `${finalPath}.${randomBytes6(8).toString("hex")}.tmp`;
@@ -6937,6 +7137,7 @@ function addNewDays(cache, incoming, newestDate, options = {}) {
6937
7137
  lastComputedDate: nextLast,
6938
7138
  days: merged,
6939
7139
  revalidatedAt: cache.revalidatedAt,
7140
+ historyScanProgress: cache.historyScanProgress,
6940
7141
  backfill: cache.backfill,
6941
7142
  partialDates: cache.partialDates
6942
7143
  };
@@ -6959,16 +7160,16 @@ init_types();
6959
7160
  init_cache_dir();
6960
7161
  init_models();
6961
7162
  import { readFile as readFile13, stat as stat16, mkdir as mkdir12, writeFile as writeFile4, rename as rename10, unlink as unlink8 } from "fs/promises";
6962
- import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
7163
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
6963
7164
  import { homedir as homedir12 } from "os";
6964
- import { join as join24 } from "path";
7165
+ import { join as join25 } from "path";
6965
7166
 
6966
7167
  // src/history-start.ts
6967
7168
  init_discovery_cache();
6968
7169
  init_fs_utils();
6969
7170
  init_parser();
6970
7171
  import { readdir as readdir12, stat as stat15 } from "fs/promises";
6971
- import { join as join23 } from "path";
7172
+ import { join as join24 } from "path";
6972
7173
  function parseHistoryStartDate(value) {
6973
7174
  if (!value || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
6974
7175
  const [year, month, day] = value.split("-").map(Number);
@@ -6988,7 +7189,7 @@ async function resolveHistoryStart(configured, cachedDates, now = /* @__PURE__ *
6988
7189
  for (const source of sources) {
6989
7190
  const sourceStat = await stat15(source.path).catch(() => null);
6990
7191
  if (!sourceStat) continue;
6991
- const paths = sourceStat.isDirectory() ? (await readdir12(source.path)).filter((name) => name.endsWith(".jsonl")).map((name) => join23(source.path, name)) : source.path.endsWith(".jsonl") ? [source.path] : [];
7192
+ const paths = sourceStat.isDirectory() ? (await readdir12(source.path)).filter((name) => name.endsWith(".jsonl")).map((name) => join24(source.path, name)) : source.path.endsWith(".jsonl") ? [source.path] : [];
6992
7193
  for (const path of paths) {
6993
7194
  const fileStat = await stat15(path).catch(() => null);
6994
7195
  if (!fileStat) continue;
@@ -7007,6 +7208,20 @@ async function resolveHistoryStart(configured, cachedDates, now = /* @__PURE__ *
7007
7208
  }
7008
7209
 
7009
7210
  // src/claude-recovery.ts
7211
+ init_recorded_costs();
7212
+ function lockClaudeRecovery(recovery) {
7213
+ if (!recovery) return null;
7214
+ const rows2 = recovery.models.map((row) => ({
7215
+ ...row,
7216
+ provider: "claude",
7217
+ deduplicationKey: "claude-stats-recovery",
7218
+ cacheCreationInputTokens: row.cacheWriteTokens,
7219
+ cacheReadInputTokens: row.cacheReadTokens
7220
+ }));
7221
+ lockRecordedCosts(rows2);
7222
+ const models = recovery.models.map((row, index) => ({ ...row, costUSD: rows2[index].costUSD }));
7223
+ return { ...recovery, models, costUSD: models.reduce((sum2, row) => sum2 + row.costUSD, 0) };
7224
+ }
7010
7225
  var fields = ["inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"];
7011
7226
  var emptyTokens = () => ({ inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 });
7012
7227
  function applyClaudeRecovery(data, recovery) {
@@ -7025,9 +7240,9 @@ function applyClaudeRecovery(data, recovery) {
7025
7240
  }
7026
7241
  }
7027
7242
  async function readClaudeUsageSnapshot() {
7028
- const root = process.env["CLAUDE_CONFIG_DIR"] || join24(homedir12(), ".claude");
7029
- const path = join24(root, "stats-cache.json");
7030
- const savedPath = join24(getCacheDir(), `claude-lifetime-${createHash6("sha256").update(root).digest("hex").slice(0, 16)}.json`);
7243
+ const root = process.env["CLAUDE_CONFIG_DIR"] || join25(homedir12(), ".claude");
7244
+ const path = join25(root, "stats-cache.json");
7245
+ const savedPath = join25(getCacheDir(), `claude-lifetime-${createHash7("sha256").update(root).digest("hex").slice(0, 16)}.json`);
7031
7246
  const read = async (file) => {
7032
7247
  try {
7033
7248
  if ((await stat16(file)).size > 2 * 1024 ** 2) return null;
@@ -7111,7 +7326,7 @@ function recoverClaudeUsage(snapshot, recorded, rangeStart) {
7111
7326
  ...totals,
7112
7327
  costUSD: models.reduce((sum2, row) => sum2 + row.costUSD, 0),
7113
7328
  models,
7114
- note: "Includes historical Claude token counters missing from retained transcripts, reconciled per model and token type to avoid double-counting. Their dates, calls, projects, request sizes, cache TTL and speed are unavailable; recovered cost uses latest standard API rates, short-context rates and 5-minute cache writes. Time-based discounts cannot be reconstructed. Earlier usage without logs or counters remains unknown."
7329
+ note: "Includes historical Claude token counters missing from retained transcripts, reconciled per model and token type to avoid double-counting. Their dates, calls, projects, request sizes, cache TTL and speed are unavailable; recovered cost uses the standard rates available when counted, short-context rates and 5-minute cache writes. Saved estimates are not revalued on pricing refresh. Time-based discounts cannot be reconstructed. Earlier usage without logs or counters remains unknown."
7115
7330
  };
7116
7331
  }
7117
7332
  async function getClaudeRecoveryForRange(range, signal) {
@@ -7132,7 +7347,7 @@ async function getClaudeRecoveryForRange(range, signal) {
7132
7347
  recorded.set(model, total);
7133
7348
  }
7134
7349
  }
7135
- return recoverClaudeUsage(snapshot, recorded, range.start);
7350
+ return lockClaudeRecovery(recoverClaudeUsage(snapshot, recorded, range.start));
7136
7351
  }
7137
7352
 
7138
7353
  // src/latest-load.ts
@@ -7195,8 +7410,8 @@ init_resource_budget();
7195
7410
  init_providers();
7196
7411
  import chalk2 from "chalk";
7197
7412
  import { readdir as readdir13, stat as stat17 } from "fs/promises";
7198
- import { existsSync as existsSync5, statSync as statSync2 } from "fs";
7199
- import { basename as basename10, join as join25 } from "path";
7413
+ import { existsSync as existsSync6, statSync as statSync2 } from "fs";
7414
+ import { basename as basename10, join as join26 } from "path";
7200
7415
  import { homedir as homedir13 } from "os";
7201
7416
  var ORANGE = "#F59E0B";
7202
7417
  var DIM = "#666666";
@@ -7279,13 +7494,13 @@ var DEFAULT_TREND_PERIOD_MS = DEFAULT_TREND_PERIOD_DAYS * 24 * 60 * 60 * 1e3;
7279
7494
  var IMPROVING_THRESHOLD = 0.5;
7280
7495
  async function collectJsonlFiles3(dirPath) {
7281
7496
  const files = await readdir13(dirPath).catch(() => []);
7282
- const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join25(dirPath, f));
7497
+ const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join26(dirPath, f));
7283
7498
  for (const entry of files) {
7284
7499
  if (entry.endsWith(".jsonl")) continue;
7285
- const subPath = join25(dirPath, entry, "subagents");
7500
+ const subPath = join26(dirPath, entry, "subagents");
7286
7501
  const subFiles = await readdir13(subPath).catch(() => []);
7287
7502
  for (const sf of subFiles) {
7288
- if (sf.endsWith(".jsonl")) result.push(join25(subPath, sf));
7503
+ if (sf.endsWith(".jsonl")) result.push(join26(subPath, sf));
7289
7504
  }
7290
7505
  }
7291
7506
  return result;
@@ -7475,16 +7690,16 @@ function isReadTool(name) {
7475
7690
  function loadMcpConfigs(projectCwds) {
7476
7691
  const servers = /* @__PURE__ */ new Map();
7477
7692
  const configPaths = [
7478
- join25(homedir13(), ".claude", "settings.json"),
7479
- join25(homedir13(), ".claude", "settings.local.json")
7693
+ join26(homedir13(), ".claude", "settings.json"),
7694
+ join26(homedir13(), ".claude", "settings.local.json")
7480
7695
  ];
7481
7696
  for (const cwd of projectCwds) {
7482
- configPaths.push(join25(cwd, ".mcp.json"));
7483
- configPaths.push(join25(cwd, ".claude", "settings.json"));
7484
- configPaths.push(join25(cwd, ".claude", "settings.local.json"));
7697
+ configPaths.push(join26(cwd, ".mcp.json"));
7698
+ configPaths.push(join26(cwd, ".claude", "settings.json"));
7699
+ configPaths.push(join26(cwd, ".claude", "settings.local.json"));
7485
7700
  }
7486
7701
  for (const p of configPaths) {
7487
- if (!existsSync5(p)) continue;
7702
+ if (!existsSync6(p)) continue;
7488
7703
  const config = readJsonFile(p);
7489
7704
  if (!config) continue;
7490
7705
  let mtime = 0;
@@ -7634,13 +7849,13 @@ function expandImports(filePath, seen, depth) {
7634
7849
  if (content === null) return { totalLines: 0, importedFiles: 0 };
7635
7850
  let totalLines = content.split("\n").length;
7636
7851
  let importedFiles = 0;
7637
- const dir = join25(filePath, "..");
7852
+ const dir = join26(filePath, "..");
7638
7853
  IMPORT_PATTERN.lastIndex = 0;
7639
7854
  for (const match of content.matchAll(IMPORT_PATTERN)) {
7640
7855
  const rawPath = match[1];
7641
7856
  if (!rawPath) continue;
7642
- const resolved = rawPath.startsWith("/") ? rawPath : join25(dir, rawPath);
7643
- if (!existsSync5(resolved)) continue;
7857
+ const resolved = rawPath.startsWith("/") ? rawPath : join26(dir, rawPath);
7858
+ if (!existsSync6(resolved)) continue;
7644
7859
  const nested = expandImports(resolved, seen, depth + 1);
7645
7860
  totalLines += nested.totalLines;
7646
7861
  importedFiles += 1 + nested.importedFiles;
@@ -7651,8 +7866,8 @@ function detectBloatedClaudeMd(projectCwds) {
7651
7866
  const bloated = [];
7652
7867
  for (const cwd of projectCwds) {
7653
7868
  for (const name of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
7654
- const fullPath = join25(cwd, name);
7655
- if (!existsSync5(fullPath)) continue;
7869
+ const fullPath = join26(cwd, name);
7870
+ if (!existsSync6(fullPath)) continue;
7656
7871
  const { totalLines, importedFiles } = expandImports(fullPath, /* @__PURE__ */ new Set(), 0);
7657
7872
  if (totalLines > CLAUDEMD_HEALTHY_LINES) {
7658
7873
  bloated.push({ path: `${shortHomePath(cwd)}/${name}`, expandedLines: totalLines, imports: importedFiles });
@@ -7780,7 +7995,7 @@ function detectCacheBloat(apiCalls, projects, dateRange) {
7780
7995
  };
7781
7996
  }
7782
7997
  async function listMarkdownFiles(dir) {
7783
- if (!existsSync5(dir)) return [];
7998
+ if (!existsSync6(dir)) return [];
7784
7999
  try {
7785
8000
  const entries = await readdir13(dir);
7786
8001
  return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(/\.md$/, ""));
@@ -7789,12 +8004,12 @@ async function listMarkdownFiles(dir) {
7789
8004
  }
7790
8005
  }
7791
8006
  async function listSkillDirs(dir) {
7792
- if (!existsSync5(dir)) return [];
8007
+ if (!existsSync6(dir)) return [];
7793
8008
  try {
7794
8009
  const entries = await readdir13(dir);
7795
8010
  const names = [];
7796
8011
  for (const entry of entries) {
7797
- if (existsSync5(join25(dir, entry, "SKILL.md"))) names.push(entry);
8012
+ if (existsSync6(join26(dir, entry, "SKILL.md"))) names.push(entry);
7798
8013
  }
7799
8014
  return names;
7800
8015
  } catch {
@@ -7802,7 +8017,7 @@ async function listSkillDirs(dir) {
7802
8017
  }
7803
8018
  }
7804
8019
  async function detectGhostAgents(calls) {
7805
- const defined = await listMarkdownFiles(join25(homedir13(), ".claude", "agents"));
8020
+ const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "agents"));
7806
8021
  if (defined.length === 0) return null;
7807
8022
  const invoked = /* @__PURE__ */ new Set();
7808
8023
  for (const call of calls) {
@@ -7827,7 +8042,7 @@ async function detectGhostAgents(calls) {
7827
8042
  };
7828
8043
  }
7829
8044
  async function detectGhostSkills(calls) {
7830
- const defined = await listSkillDirs(join25(homedir13(), ".claude", "skills"));
8045
+ const defined = await listSkillDirs(join26(homedir13(), ".claude", "skills"));
7831
8046
  if (defined.length === 0) return null;
7832
8047
  const invoked = /* @__PURE__ */ new Set();
7833
8048
  for (const call of calls) {
@@ -7852,7 +8067,7 @@ async function detectGhostSkills(calls) {
7852
8067
  };
7853
8068
  }
7854
8069
  async function detectGhostCommands(userMessages) {
7855
- const defined = await listMarkdownFiles(join25(homedir13(), ".claude", "commands"));
8070
+ const defined = await listMarkdownFiles(join26(homedir13(), ".claude", "commands"));
7856
8071
  if (defined.length === 0) return null;
7857
8072
  const invoked = /* @__PURE__ */ new Set();
7858
8073
  for (const msg of userMessages) {
@@ -7880,8 +8095,8 @@ async function detectGhostCommands(userMessages) {
7880
8095
  }
7881
8096
  function readShellProfileLimit() {
7882
8097
  for (const profile of SHELL_PROFILES) {
7883
- const path = join25(homedir13(), profile);
7884
- if (!existsSync5(path)) continue;
8098
+ const path = join26(homedir13(), profile);
8099
+ if (!existsSync6(path)) continue;
7885
8100
  const content = readSessionFileSync(path);
7886
8101
  if (content === null) continue;
7887
8102
  const match = content.match(/^\s*export\s+BASH_MAX_OUTPUT_LENGTH\s*=\s*['"]?(\d+)['"]?/m);
@@ -8104,7 +8319,7 @@ import { spawn as spawn2 } from "child_process";
8104
8319
  import { randomUUID as randomUUID4 } from "crypto";
8105
8320
  import { stat as stat18, writeFile as writeFile5 } from "fs/promises";
8106
8321
  import { homedir as homedir14 } from "os";
8107
- import { join as join26, resolve as resolve3 } from "path";
8322
+ import { join as join27, resolve as resolve3 } from "path";
8108
8323
  function codeBlock(content, language) {
8109
8324
  const longest = (content.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
8110
8325
  const fence = "`".repeat(Math.max(3, longest + 1));
@@ -8198,7 +8413,7 @@ async function copyOptimizeMarkdown(markdown) {
8198
8413
  throw new Error("Could not copy. Press e to save a Markdown file instead.");
8199
8414
  }
8200
8415
  async function saveOptimizeMarkdown(markdown, directory) {
8201
- const downloads = join26(homedir14(), "Downloads");
8416
+ const downloads = join27(homedir14(), "Downloads");
8202
8417
  const folder = directory ?? ((await stat18(downloads).catch(() => null))?.isDirectory() ? downloads : process.cwd());
8203
8418
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8204
8419
  const path = resolve3(folder, `exe-watcher-optimizer-${stamp}-${randomUUID4().slice(0, 8)}.md`);
@@ -8209,8 +8424,8 @@ async function saveOptimizeMarkdown(markdown, directory) {
8209
8424
  // src/context-budget.ts
8210
8425
  init_fs_utils();
8211
8426
  import { readdir as readdir14 } from "fs/promises";
8212
- import { existsSync as existsSync6 } from "fs";
8213
- import { join as join27 } from "path";
8427
+ import { existsSync as existsSync7 } from "fs";
8428
+ import { join as join28 } from "path";
8214
8429
  import { homedir as homedir15 } from "os";
8215
8430
  var CHARS_PER_TOKEN2 = 4;
8216
8431
  var SYSTEM_BASE_TOKENS = 10400;
@@ -8220,7 +8435,7 @@ function estimateTokens2(text) {
8220
8435
  return Math.ceil(text.length / CHARS_PER_TOKEN2);
8221
8436
  }
8222
8437
  async function readConfigFile(path) {
8223
- if (!existsSync6(path)) return null;
8438
+ if (!existsSync7(path)) return null;
8224
8439
  const raw = await readSessionFile(path);
8225
8440
  if (raw === null) return null;
8226
8441
  try {
@@ -8232,13 +8447,13 @@ async function readConfigFile(path) {
8232
8447
  async function countMcpTools(projectPath) {
8233
8448
  const home = homedir15();
8234
8449
  const configPaths = [
8235
- join27(home, ".claude", "settings.json"),
8236
- join27(home, ".claude", "settings.local.json")
8450
+ join28(home, ".claude", "settings.json"),
8451
+ join28(home, ".claude", "settings.local.json")
8237
8452
  ];
8238
8453
  if (projectPath) {
8239
- configPaths.push(join27(projectPath, ".mcp.json"));
8240
- configPaths.push(join27(projectPath, ".claude", "settings.json"));
8241
- configPaths.push(join27(projectPath, ".claude", "settings.local.json"));
8454
+ configPaths.push(join28(projectPath, ".mcp.json"));
8455
+ configPaths.push(join28(projectPath, ".claude", "settings.json"));
8456
+ configPaths.push(join28(projectPath, ".claude", "settings.local.json"));
8242
8457
  }
8243
8458
  const servers = /* @__PURE__ */ new Set();
8244
8459
  let toolCount = 0;
@@ -8255,16 +8470,16 @@ async function countMcpTools(projectPath) {
8255
8470
  return toolCount;
8256
8471
  }
8257
8472
  async function countSkills(projectPath) {
8258
- const dirs = [join27(homedir15(), ".claude", "skills")];
8259
- if (projectPath) dirs.push(join27(projectPath, ".claude", "skills"));
8473
+ const dirs = [join28(homedir15(), ".claude", "skills")];
8474
+ if (projectPath) dirs.push(join28(projectPath, ".claude", "skills"));
8260
8475
  let count = 0;
8261
8476
  for (const dir of dirs) {
8262
- if (!existsSync6(dir)) continue;
8477
+ if (!existsSync7(dir)) continue;
8263
8478
  try {
8264
8479
  const entries = await readdir14(dir);
8265
8480
  for (const entry of entries) {
8266
- const skillFile = join27(dir, entry, "SKILL.md");
8267
- if (existsSync6(skillFile)) count++;
8481
+ const skillFile = join28(dir, entry, "SKILL.md");
8482
+ if (existsSync7(skillFile)) count++;
8268
8483
  }
8269
8484
  } catch {
8270
8485
  continue;
@@ -8276,15 +8491,15 @@ async function scanMemoryFiles(projectPath) {
8276
8491
  const home = homedir15();
8277
8492
  const files = [];
8278
8493
  const paths = [
8279
- { path: join27(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
8494
+ { path: join28(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
8280
8495
  ];
8281
8496
  if (projectPath) {
8282
- paths.push({ path: join27(projectPath, "CLAUDE.md"), name: "CLAUDE.md" });
8283
- paths.push({ path: join27(projectPath, ".claude", "CLAUDE.md"), name: ".claude/CLAUDE.md" });
8284
- paths.push({ path: join27(projectPath, "CLAUDE.local.md"), name: "CLAUDE.local.md" });
8497
+ paths.push({ path: join28(projectPath, "CLAUDE.md"), name: "CLAUDE.md" });
8498
+ paths.push({ path: join28(projectPath, ".claude", "CLAUDE.md"), name: ".claude/CLAUDE.md" });
8499
+ paths.push({ path: join28(projectPath, "CLAUDE.local.md"), name: "CLAUDE.local.md" });
8285
8500
  }
8286
8501
  for (const { path, name } of paths) {
8287
- if (!existsSync6(path)) continue;
8502
+ if (!existsSync7(path)) continue;
8288
8503
  const content = await readSessionFile(path);
8289
8504
  if (content === null) continue;
8290
8505
  files.push({ name, tokens: estimateTokens2(content) });
@@ -8318,7 +8533,7 @@ async function discoverProjectCwd(sessionDir) {
8318
8533
  if (files.length === 0) return null;
8319
8534
  const firstFile = files[0];
8320
8535
  if (!firstFile) return null;
8321
- const content = await readSessionFile(join27(sessionDir, firstFile));
8536
+ const content = await readSessionFile(join28(sessionDir, firstFile));
8322
8537
  if (content === null) return null;
8323
8538
  for (const line of content.split("\n")) {
8324
8539
  if (!line.trim()) continue;
@@ -9012,20 +9227,20 @@ function owlFrame(tick) {
9012
9227
  }
9013
9228
 
9014
9229
  // src/dashboard.tsx
9015
- import { join as join29 } from "path";
9230
+ import { join as join30 } from "path";
9016
9231
 
9017
9232
  // src/time-index.ts
9018
9233
  init_cache_dir();
9019
9234
  init_file_lock();
9020
9235
  init_resource_budget();
9021
- import { createHash as createHash8, randomUUID as randomUUID5 } from "crypto";
9236
+ import { createHash as createHash9, randomUUID as randomUUID5 } from "crypto";
9022
9237
  import { mkdir as mkdir13, open as open9, readFile as readFile14, rename as rename11, stat as stat19, unlink as unlink9, readdir as readdir15 } from "fs/promises";
9023
- import { basename as basename11, dirname as dirname6, join as join28 } from "path";
9238
+ import { basename as basename11, dirname as dirname6, join as join29 } from "path";
9024
9239
  import { gzip as gzip2, gunzip as gunzip2 } from "zlib";
9025
9240
  import { promisify as promisify2 } from "util";
9026
9241
 
9027
9242
  // src/time-tracking.ts
9028
- import { createHash as createHash7 } from "crypto";
9243
+ import { createHash as createHash8 } from "crypto";
9029
9244
  var TIME_HISTORY_START = (/* @__PURE__ */ new Date("2025-07-01T00:00:00")).getTime();
9030
9245
  var TIME_PROVIDERS = ["claude", "codex", "cursor", "cursor-agent", "copilot", "opencode", "kimi", "pi", "omp"];
9031
9246
  function timestamp(value) {
@@ -9238,7 +9453,7 @@ function summarizeTime(index, range, provider = "all", include, exclude) {
9238
9453
  runnableFiles: pending.length - waitingFiles,
9239
9454
  waitingFiles,
9240
9455
  pendingBytes: pending.reduce((n, f) => n + Math.max(0, f.size - f.offset), 0),
9241
- token: createHash7("sha256").update(files.map((f) => `${f.identity}:${f.size}:${f.mtime}:${f.offset}:${f.dbOffset ?? 0}:${!!f.waitingForAppend}:${!!f.unavailable}`).join("|")).digest("hex")
9456
+ token: createHash8("sha256").update(files.map((f) => `${f.identity}:${f.size}:${f.mtime}:${f.offset}:${f.dbOffset ?? 0}:${!!f.waitingForAppend}:${!!f.unavailable}`).join("|")).digest("hex")
9242
9457
  };
9243
9458
  return {
9244
9459
  indexedAt: index.updatedAt,
@@ -9371,7 +9586,7 @@ var MAX_DISK2 = 16 * 1024 ** 2;
9371
9586
  var MAX_FILES = 2e4;
9372
9587
  var BLOCK = 2 * 1024 ** 2;
9373
9588
  var adapterVersion = (provider) => provider === "opencode" ? 2 : 1;
9374
- var indexPath2 = () => join28(getCacheDir(), "time-index-v1.json.gz");
9589
+ var indexPath2 = () => join29(getCacheDir(), "time-index-v1.json.gz");
9375
9590
  var empty = () => ({ version: 1, files: /* @__PURE__ */ Object.create(null), updatedAt: (/* @__PURE__ */ new Date()).toISOString(), warnings: [] });
9376
9591
  async function readTimeIndex() {
9377
9592
  try {
@@ -9434,11 +9649,11 @@ async function expandSources(sources, warnings) {
9434
9649
  continue;
9435
9650
  }
9436
9651
  for (const entry of await readdir15(source.path, { withFileTypes: true }).catch(() => [])) {
9437
- if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path: join28(source.path, entry.name) });
9652
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push({ ...source, path: join29(source.path, entry.name) });
9438
9653
  else if (entry.isDirectory()) {
9439
- const folder = join28(source.path, entry.name, "subagents");
9654
+ const folder = join29(source.path, entry.name, "subagents");
9440
9655
  for (const agent of await readdir15(folder, { withFileTypes: true }).catch(() => [])) {
9441
- if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path: join28(folder, agent.name) });
9656
+ if (agent.isFile() && agent.name.endsWith(".jsonl")) out.push({ ...source, path: join29(folder, agent.name) });
9442
9657
  }
9443
9658
  }
9444
9659
  }
@@ -9452,7 +9667,7 @@ async function anchor(path, offset) {
9452
9667
  await handle.read(head, 0, head.length, 0);
9453
9668
  await handle.read(tail, 0, tail.length, Math.max(0, offset - tail.length));
9454
9669
  chargeRead(head.length + tail.length);
9455
- return createHash8("sha256").update(head).update(tail).digest("hex");
9670
+ return createHash9("sha256").update(head).update(tail).digest("hex");
9456
9671
  } finally {
9457
9672
  await handle.close();
9458
9673
  }
@@ -9535,7 +9750,7 @@ async function refreshTimeIndex(options = {}) {
9535
9750
  let current;
9536
9751
  let release;
9537
9752
  try {
9538
- release = await acquireFileLock(join28(getCacheDir(), "time-index.lock"), 100);
9753
+ release = await acquireFileLock(join29(getCacheDir(), "time-index.lock"), 100);
9539
9754
  } catch {
9540
9755
  current = await readTimeIndex();
9541
9756
  current.warnings.push("Another timing update is running; showing the saved timing snapshot.");
@@ -9555,17 +9770,17 @@ async function refreshTimeIndex(options = {}) {
9555
9770
  let fileCount = Object.keys(current.files).length;
9556
9771
  for (const source of expanded) {
9557
9772
  chargeRead(0);
9558
- const database = source.provider === "opencode" ? source.path.slice(0, source.path.lastIndexOf(":")) : source.path;
9559
- const s = await stat19(database).catch(() => null);
9773
+ const database2 = source.provider === "opencode" ? source.path.slice(0, source.path.lastIndexOf(":")) : source.path;
9774
+ const s = await stat19(database2).catch(() => null);
9560
9775
  if (!s) continue;
9561
- const wal = ["cursor", "opencode"].includes(source.provider) ? await stat19(database + "-wal").catch(() => null) : null;
9776
+ const wal = ["cursor", "opencode"].includes(source.provider) ? await stat19(database2 + "-wal").catch(() => null) : null;
9562
9777
  candidates.push({ source, s, wal });
9563
9778
  }
9564
9779
  candidates.sort((a, b) => b.s.mtimeMs - a.s.mtimeMs || a.source.path.localeCompare(b.source.path));
9565
9780
  if (candidates.length > MAX_FILES) current.warnings.push("Some timing sources exceed the bounded file limit; their time remains unknown.");
9566
9781
  const deadline = Date.now() + (options.history ? 15e3 : 750);
9567
9782
  for (const { source, s, wal } of candidates) {
9568
- const key2 = createHash8("sha256").update(`${source.provider}:${source.path}`).digest("hex");
9783
+ const key2 = createHash9("sha256").update(`${source.provider}:${source.path}`).digest("hex");
9569
9784
  if (!current.files[key2] && fileCount < MAX_FILES) {
9570
9785
  current.files[key2] = fresh(source, `${s.dev}:${s.ino}:${wal?.size ?? 0}:${wal?.mtimeMs ?? 0}`, s.size, s.mtimeMs, s.ctimeMs);
9571
9786
  changed = true;
@@ -9577,7 +9792,7 @@ async function refreshTimeIndex(options = {}) {
9577
9792
  }
9578
9793
  for (const { source, s, wal } of candidates) {
9579
9794
  chargeRead(0);
9580
- const key2 = createHash8("sha256").update(`${source.provider}:${source.path}`).digest("hex");
9795
+ const key2 = createHash9("sha256").update(`${source.provider}:${source.path}`).digest("hex");
9581
9796
  const identity = `${s.dev}:${s.ino}:${wal?.size ?? 0}:${wal?.mtimeMs ?? 0}`;
9582
9797
  let row = current.files[key2];
9583
9798
  const sameAdapter = (row?.adapterVersion ?? 1) === adapterVersion(source.provider);
@@ -10482,11 +10697,11 @@ function InteractiveDashboard({ initialPeriod, initialProvider, refreshSeconds,
10482
10697
  useEffect2(() => {
10483
10698
  let cancelled = false;
10484
10699
  async function loadBudgets() {
10485
- const claudeDir = join29(homedir16(), ".claude", "projects");
10700
+ const claudeDir = join30(homedir16(), ".claude", "projects");
10486
10701
  const budgets = /* @__PURE__ */ new Map();
10487
10702
  for (const project of projects.slice(0, 8)) {
10488
10703
  if (cancelled) return;
10489
- const cwd = await discoverProjectCwd(join29(claudeDir, project.project));
10704
+ const cwd = await discoverProjectCwd(join30(claudeDir, project.project));
10490
10705
  if (!cwd) continue;
10491
10706
  budgets.set(project.project, await estimateContextBudget(cwd));
10492
10707
  }
@@ -10809,6 +11024,10 @@ function shouldSkipBlockedChunk(cursor2, date2, maxAttempts = MAX_BLOCKED_CHUNK_
10809
11024
  // src/period-snapshots.ts
10810
11025
  init_day_aggregator();
10811
11026
  init_models();
11027
+ import { createHash as createHash10 } from "crypto";
11028
+ function hasRecordedHistory(cache, today) {
11029
+ return cache.days.some((day) => day.date < today && (day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0));
11030
+ }
10812
11031
  function assertHistoryComplete(cache, start, today) {
10813
11032
  if (start >= today) return;
10814
11033
  const required = Math.round((Date.parse(today) - Date.parse(start)) / 864e5);
@@ -10830,7 +11049,27 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
10830
11049
  for (const window of windows) {
10831
11050
  const required = Math.max(0, Math.round((Date.parse(today) - Date.parse(window.start)) / 864e5));
10832
11051
  const covered = [...dates].filter((d) => d >= window.start && !partial.has(d)).length;
10833
- if (window.key !== "today" && (cache.needsRepricing || cache.historyBuild || cache.missingProviders?.length || covered !== required)) continue;
11052
+ const incomplete = window.key !== "today" && (cache.needsRepricing || cache.historyBuild || cache.missingProviders?.length || covered !== required);
11053
+ const committed = closed.filter((day) => day.date >= window.start).sort((a, b) => a.date.localeCompare(b.date));
11054
+ const recorded = (day) => day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0;
11055
+ const hasData = committed.some(recorded) || window.key !== "all" && live2.some(recorded);
11056
+ const savedHistory = incomplete && (window.key !== "all" || hasData) ? {
11057
+ through: committed.at(-1)?.date ?? today,
11058
+ hasData,
11059
+ progressToken: createHash10("sha256").update(JSON.stringify([
11060
+ window.start,
11061
+ cache.lastComputedDate,
11062
+ cache.days.length,
11063
+ cache.historyBuild?.completedProviders,
11064
+ cache.historyBuild?.chunked,
11065
+ cache.missingProviders,
11066
+ cache.partialDates,
11067
+ cache.backfill,
11068
+ cache.historyScanProgress
11069
+ ])).digest("hex"),
11070
+ message: window.key === "all" ? `Saved history through ${committed.at(-1).date}${live2.length ? ", plus today" : ""}. Some history is still missing; this is not the complete All total.` : `${hasData ? "Showing known usage" : "Recovering local usage"} for ${window.label}. ${covered} of ${required} historical days loaded${live2.some(recorded) ? ", plus today" : ""}. Missing days are unknown; this total is incomplete.`
11071
+ } : void 0;
11072
+ if (incomplete && !savedHistory) continue;
10834
11073
  const selected = periodDays(window.start);
10835
11074
  const scoped = provider === "all" ? selected : filterDaysToProvider(selected, provider);
10836
11075
  const data = buildPeriodDataFromDays(scoped, window.label);
@@ -10852,6 +11091,7 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
10852
11091
  const providers = provider === "all" ? Object.entries(totals).map(([name, cost]) => ({ name: names.get(name) ?? name, cost })) : [{ name: names.get(provider) ?? provider, cost: data.cost }];
10853
11092
  const built = buildMenubarPayload(data, providers, null);
10854
11093
  result[window.key] = {
11094
+ ...savedHistory ? { savedHistory } : {},
10855
11095
  current: built.current,
10856
11096
  projectSpend: buildProjectSpendFromDays(selected, live2, periodDays(week.start), periodDays(month.start)),
10857
11097
  diagnostics: { daysCount: selected.length, parseTimeMs, warnings: notes },
@@ -10862,6 +11102,23 @@ function buildPeriodSnapshots(cache, todayDays, today, windows, provider, names,
10862
11102
  return result;
10863
11103
  }
10864
11104
 
11105
+ // src/saved-history.ts
11106
+ init_day_aggregator();
11107
+ function buildSavedHistory(cache, today, provider, window = { start: "0000-01-01", label: "All Time" }) {
11108
+ if (!hasRecordedHistory(cache, today)) throw new Error("No saved history yet. Watcher is recovering local usage.");
11109
+ const closed = cache.days.filter((day) => day.date >= window.start && day.date < today).sort((a, b) => a.date.localeCompare(b.date));
11110
+ if (!closed.some((day) => day.cost > 0 || day.calls > 0 || day.inputTokens > 0 || day.outputTokens > 0)) throw new Error("No saved usage in this period yet. Watcher is recovering local usage.");
11111
+ const days = provider === "all" ? closed : filterDaysToProvider(closed, provider);
11112
+ const current = buildPeriodDataFromDays(days, window.label);
11113
+ const totals = /* @__PURE__ */ new Map();
11114
+ for (const day of days) for (const [name, data] of Object.entries(day.providers)) totals.set(name, (totals.get(name) ?? 0) + data.cost);
11115
+ const payload = buildMenubarPayload(current, [...totals].map(([name, cost]) => ({ name, cost })), null);
11116
+ return { ...payload, savedHistory: {
11117
+ through: closed.at(-1).date,
11118
+ message: `Saved ${window.label} history through ${closed.at(-1).date}. Newer usage has not loaded yet.`
11119
+ } };
11120
+ }
11121
+
10865
11122
  // src/history-build.ts
10866
11123
  init_day_aggregator();
10867
11124
  init_report_summary();
@@ -10960,8 +11217,9 @@ async function completeHistoryBuild(cache, start, end, providers, scan, save2, o
10960
11217
  }
10961
11218
 
10962
11219
  // src/cli.ts
11220
+ init_parsed_file_cache();
10963
11221
  import { createInterface } from "readline";
10964
- import { createRequire as createRequire2 } from "module";
11222
+ import { createRequire as createRequire3 } from "module";
10965
11223
 
10966
11224
  // src/menubar-lifecycle.ts
10967
11225
  var live = { parent: () => process.ppid, checkAlive: (pid) => {
@@ -10995,8 +11253,8 @@ function buildTimeSnapshots(index, ranges, provider = "all", include, exclude) {
10995
11253
  }
10996
11254
 
10997
11255
  // src/cli.ts
10998
- var require2 = createRequire2(import.meta.url);
10999
- var { version } = require2("../package.json");
11256
+ var require3 = createRequire3(import.meta.url);
11257
+ var { version } = require3("../package.json");
11000
11258
  var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
11001
11259
  var configuredHistoryStart = null;
11002
11260
  function toDateString(date2) {
@@ -11111,14 +11369,14 @@ async function runJsonReport(period, provider, project, exclude) {
11111
11369
  console.log(JSON.stringify({ ...report, pricing: getPricingMetadata() }, null, 2));
11112
11370
  }
11113
11371
  var program = new Command().name("exe-watcher").description("See where your AI coding tokens go - by task, tool, model, and project").version(version).option("--verbose", "print warnings to stderr on read failures and skipped files");
11114
- program.hook("preAction", async (thisCommand) => {
11372
+ program.hook("preAction", async (thisCommand, command) => {
11115
11373
  const config = await readConfig();
11116
11374
  setModelAliases(config.modelAliases ?? {});
11117
11375
  configuredHistoryStart = parseHistoryStartDate(config.historyStartDate);
11118
11376
  if (thisCommand.opts().verbose) {
11119
11377
  process.env["EXE_WATCHER_VERBOSE"] = "1";
11120
11378
  }
11121
- await loadCurrency();
11379
+ if (!command.opts().cachedOnly) await loadCurrency();
11122
11380
  });
11123
11381
  function buildJsonReport(projects, period, periodKey) {
11124
11382
  const sessions = projects.flatMap((p) => p.sessions);
@@ -11308,7 +11566,19 @@ function buildPeriodData(label, projects) {
11308
11566
  models: Object.entries(modelTotals).sort(([, a], [, b]) => b.cost - a.cost).map(([name, d]) => ({ name, ...d }))
11309
11567
  };
11310
11568
  }
11311
- program.command("status").description("Compact status output (today + week + month)").option("--format <format>", "Output format: terminal, menubar-json, json", "terminal").option("--provider <provider>", "Filter by provider: all, claude, codex, cursor, cursor-agent, copilot, opencode, kimi, pi, omp", "all").option("--project <name>", "Show only projects matching name (repeatable)", collect, []).option("--exclude <name>", "Exclude projects matching name (repeatable)", collect, []).option("--period <period>", "Primary period for menubar-json: today, week, 30days, month, all", "today").option("--no-optimize", "Skip optimize findings (menubar-json only, faster)").action(async (opts) => {
11569
+ program.command("status").description("Compact status output (today + week + month)").option("--format <format>", "Output format: terminal, menubar-json, json", "terminal").option("--provider <provider>", "Filter by provider: all, claude, codex, cursor, cursor-agent, copilot, opencode, kimi, pi, omp", "all").option("--project <name>", "Show only projects matching name (repeatable)", collect, []).option("--exclude <name>", "Exclude projects matching name (repeatable)", collect, []).option("--period <period>", "Primary period for menubar-json: today, week, 30days, month, all", "today").option("--no-optimize", "Skip optimize findings (menubar-json only, faster)").option("--cached-only", "Read saved period totals without scanning or networking (menubar-json only)").action(async (opts) => {
11570
+ if (opts.cachedOnly) {
11571
+ if (opts.format !== "menubar-json" || !["week", "30days", "month", "all"].includes(opts.period) || opts.optimize !== false) throw new Error("--cached-only requires menubar-json, a historical period and --no-optimize");
11572
+ const cache = await loadDailyCache(buildDailyCacheScopeKey(opts.project, opts.exclude));
11573
+ const window = getDateRange2(opts.period);
11574
+ console.log(JSON.stringify(buildSavedHistory(
11575
+ cache,
11576
+ toDateString(/* @__PURE__ */ new Date()),
11577
+ opts.provider,
11578
+ { start: opts.period === "all" ? "0000-01-01" : toDateString(window.range.start), label: window.label }
11579
+ )));
11580
+ return;
11581
+ }
11312
11582
  await loadPricing();
11313
11583
  const pf = opts.provider;
11314
11584
  const fp = (p) => filterProjectsByName(p, opts.project, opts.exclude);
@@ -11327,20 +11597,31 @@ program.command("status").description("Compact status output (today + week + mon
11327
11597
  const isAllProviders = pf === "all";
11328
11598
  const dailyCacheScope = buildDailyCacheScopeKey(opts.project, opts.exclude);
11329
11599
  const backfillWarnings = [];
11330
- const runChunkedBackfill = async (cache2, start, end, order) => {
11600
+ let savedParserProgress = parsedFileProgress();
11601
+ const saveHistoryCache = async (c) => {
11602
+ const progress = parsedFileProgress();
11603
+ const advanced = progress > savedParserProgress;
11604
+ c.historyScanProgress = (c.historyScanProgress ?? 0) + progress - savedParserProgress;
11605
+ savedParserProgress = progress;
11606
+ if (c.backfill) {
11607
+ c.backfill.attempts = advanced ? 0 : Math.min(c.backfill.attempts, MAX_BLOCKED_CHUNK_ATTEMPTS);
11608
+ }
11609
+ await saveDailyCache(c);
11610
+ };
11611
+ const runChunkedBackfill = async (cache2, start, end, order, repair = false) => {
11331
11612
  let c = cache2;
11332
11613
  const chunks = planBackfillChunks(start, end);
11333
11614
  if (order === "newest-first") chunks.reverse();
11334
11615
  for (const chunk of chunks) {
11335
11616
  const chunkDate = toDateString(chunk.start);
11336
11617
  const coveredThrough = toDateString(chunk.end);
11337
- if (shouldSkipBlockedChunk(c.backfill, chunkDate)) {
11618
+ if (!repair && shouldSkipBlockedChunk(c.backfill, chunkDate)) {
11338
11619
  const blanks = fillMissingDays(chunk.start, chunk.end, []);
11339
11620
  c = addNewDays(c, blanks, yesterdayStr, { coveredThrough });
11340
11621
  c.partialDates = [.../* @__PURE__ */ new Set([...c.partialDates ?? [], ...blanks.map((d) => d.date)])];
11341
11622
  c.backfill = null;
11342
11623
  c.revalidatedAt = Date.now();
11343
- await saveDailyCache(c);
11624
+ await saveHistoryCache(c);
11344
11625
  const warn2 = `history for ${chunkDate} exceeded the scan budget ${MAX_BLOCKED_CHUNK_ATTEMPTS} times; recorded as incomplete so newer days can load`;
11345
11626
  backfillWarnings.push(warn2);
11346
11627
  process.stderr.write(`[exe-watcher] WARNING: ${warn2}
@@ -11352,14 +11633,15 @@ program.command("status").description("Compact status output (today + week + mon
11352
11633
  const chunkProjects = filterProjectsByName(await parseAllSessions(chunkRange, "all", true), opts.project, opts.exclude);
11353
11634
  const chunkDays = fillMissingDays(chunk.start, chunk.end, aggregateProjectsIntoDays(chunkProjects));
11354
11635
  c = addNewDays(c, chunkDays, yesterdayStr, { coveredThrough });
11636
+ c.partialDates = (c.partialDates ?? []).filter((date2) => date2 < chunkDate || date2 > coveredThrough);
11355
11637
  c.backfill = null;
11356
11638
  c.revalidatedAt = Date.now();
11357
- await saveDailyCache(c);
11639
+ await saveHistoryCache(c);
11358
11640
  } catch (err) {
11359
11641
  if (!(err instanceof ResourceBudgetError)) throw err;
11360
11642
  c.backfill = nextBackfillCursor(c.backfill, chunkDate);
11361
11643
  c.revalidatedAt = Date.now();
11362
- await saveDailyCache(c);
11644
+ await saveHistoryCache(c);
11363
11645
  const warn2 = `history backfill paused at ${chunkDate} (attempt ${c.backfill.attempts}): ${err.message}`;
11364
11646
  backfillWarnings.push(warn2);
11365
11647
  process.stderr.write(`[exe-watcher] WARNING: ${warn2}
@@ -11380,7 +11662,7 @@ program.command("status").description("Compact status output (today + week + mon
11380
11662
  opts.project,
11381
11663
  opts.exclude
11382
11664
  )),
11383
- saveDailyCache,
11665
+ saveHistoryCache,
11384
11666
  { mergeExisting }
11385
11667
  );
11386
11668
  const pausedHistory = (c) => {
@@ -11388,8 +11670,9 @@ program.command("status").description("Compact status output (today + week + mon
11388
11670
  backfillWarnings.push(`History rebuild paused at ${c.historyBuild.chunked?.nextDate ?? c.historyBuild.start}; completed scans are saved and will resume on the next historical refresh.`);
11389
11671
  return true;
11390
11672
  };
11391
- const cache = await withDailyCacheLock(async () => {
11673
+ const cache = await withScanBudget(() => withScanSlice(() => withDailyCacheLock(async () => {
11392
11674
  let c = await loadDailyCache(dailyCacheScope);
11675
+ const repairDates = [...new Set(c.partialDates ?? [])].sort();
11393
11676
  if (opts.period === "today" && opts.optimize === false) return c;
11394
11677
  if (c.missingProviders?.length && c.days.length && !c.needsRepricing) {
11395
11678
  const from = parseHistoryStartDate(c.days[0].date);
@@ -11405,19 +11688,13 @@ program.command("status").description("Compact status output (today + week + mon
11405
11688
  c = await buildHistory2(c, from, through);
11406
11689
  if (pausedHistory(c)) return c;
11407
11690
  }
11408
- if (c.needsRepricing || opts.period === "all" && c.days.length === 0) {
11691
+ if (opts.period === "all" && c.days.length === 0) {
11409
11692
  const start = parseHistoryStartDate(c.previousHistoryStart) ?? historyStart;
11410
11693
  if (start <= yesterdayEnd) {
11411
11694
  c = await buildHistory2(c, start, yesterdayEnd);
11412
11695
  if (pausedHistory(c)) return c;
11413
11696
  }
11414
11697
  }
11415
- const hadYesterday = c.days.some((d) => d.date >= yesterdayStr);
11416
- if (hadYesterday && (!c.revalidatedAt || Date.now() - c.revalidatedAt >= 5 * 6e4)) {
11417
- const freshDays = c.days.filter((d) => d.date < yesterdayStr);
11418
- const latestFresh = freshDays.length > 0 ? freshDays[freshDays.length - 1]?.date ?? null : null;
11419
- c = { ...c, days: freshDays, lastComputedDate: latestFresh };
11420
- }
11421
11698
  const oldestCachedDate = c.days.length > 0 ? c.days[0]?.date ?? null : null;
11422
11699
  const effectiveGapStart = computeProgressiveBackfillStart({
11423
11700
  lastComputedDate: c.lastComputedDate,
@@ -11441,9 +11718,22 @@ program.command("status").description("Compact status output (today + week + mon
11441
11718
  c = await runChunkedBackfill(c, clampedNeeded, backEnd, "newest-first");
11442
11719
  }
11443
11720
  }
11721
+ if (!c.backfill || repairDates.includes(c.backfill.blockedDate)) {
11722
+ const selectedStart = toDateString(periodInfo.range.start);
11723
+ for (const date2 of repairDates) {
11724
+ if (date2 < selectedStart || date2 > yesterdayStr || !c.partialDates?.includes(date2)) continue;
11725
+ const start = parseHistoryStartDate(date2);
11726
+ const end = new Date(start);
11727
+ end.setHours(23, 59, 59, 999);
11728
+ c = await runChunkedBackfill(c, start, end, "oldest-first", true);
11729
+ if (c.backfill) break;
11730
+ }
11731
+ }
11444
11732
  return c;
11445
- });
11446
- assertHistoryComplete(cache, toDateString(periodInfo.range.start), toDateString(todayStart));
11733
+ }), { maxMs: 1e4, maxBytes: 8 * 1024 ** 3, maxRecords: 2e6 }), 2e6, void 0, 8 * 1024 ** 3);
11734
+ if (opts.optimize !== false || opts.period === "all" && !hasRecordedHistory(cache, toDateString(todayStart))) {
11735
+ assertHistoryComplete(cache, toDateString(periodInfo.range.start), toDateString(todayStart));
11736
+ }
11447
11737
  let currentData;
11448
11738
  const scanRange = periodInfo.range;
11449
11739
  const todayRange = { start: todayStart, end: periodInfo.range.end };
@@ -11517,7 +11807,7 @@ program.command("status").description("Compact status output (today + week + mon
11517
11807
  let historicalRecovery = null;
11518
11808
  if ((opts.period === "all" || opts.optimize === false) && (isAllProviders || pf === "claude") && !opts.project.length && !opts.exclude.length && !cache.partialDates?.length && !cache.backfill) {
11519
11809
  const snapshot = await readClaudeUsageSnapshot();
11520
- historicalRecovery = recoverClaudeUsage(snapshot, sumClaudeModelTokens([...cache.days, ...todayDays], snapshot?.lastComputedDate ?? ""), historyStart);
11810
+ historicalRecovery = lockClaudeRecovery(recoverClaudeUsage(snapshot, sumClaudeModelTokens([...cache.days, ...todayDays], snapshot?.lastComputedDate ?? ""), historyStart));
11521
11811
  if (historicalRecovery && opts.period === "all") {
11522
11812
  applyClaudeRecovery(currentData, historicalRecovery);
11523
11813
  const claudeProvider = providers.find((p) => p.name.toLowerCase() === "claude");
@@ -11585,13 +11875,13 @@ program.command("status").description("Compact status output (today + week + mon
11585
11875
  `);
11586
11876
  }
11587
11877
  }
11588
- const exeOsDir = join30(homedir17(), ".exe-os");
11589
- const exeOsDetected = existsSync7(exeOsDir);
11878
+ const exeOsDir = join31(homedir17(), ".exe-os");
11879
+ const exeOsDetected = existsSync8(exeOsDir);
11590
11880
  let agentStats = null;
11591
11881
  let statsFileAge = null;
11592
11882
  try {
11593
- const statsPath = join30(exeOsDir, "agent-stats.json");
11594
- if (existsSync7(statsPath)) {
11883
+ const statsPath = join31(exeOsDir, "agent-stats.json");
11884
+ if (existsSync8(statsPath)) {
11595
11885
  statsFileAge = Math.round((Date.now() - statSync3(statsPath).mtimeMs) / 1e3);
11596
11886
  const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
11597
11887
  if (parsed && Array.isArray(parsed.agents) && typeof parsed.generated === "string") {
@@ -11654,7 +11944,15 @@ program.command("status").description("Compact status output (today + week + mon
11654
11944
  if (periodSnapshots) for (const [key2, snapshot] of Object.entries(periodSnapshots)) {
11655
11945
  snapshot.time = summarizeTime(timeIndex, getDateRange2(key2, now).range, pf, opts.project, opts.exclude);
11656
11946
  }
11657
- const json = JSON.stringify({ ...payload, time, periodSnapshots, costBasis: "api-equivalent", pricing: getPricingMetadata(), historicalRecovery: opts.period === "all" ? historicalRecovery : null });
11947
+ const json = JSON.stringify({
11948
+ ...payload,
11949
+ time,
11950
+ periodSnapshots,
11951
+ ...periodSnapshots?.[opts.period]?.savedHistory ? { savedHistory: periodSnapshots[opts.period].savedHistory } : {},
11952
+ costBasis: "api-equivalent",
11953
+ pricing: getPricingMetadata(),
11954
+ historicalRecovery: opts.period === "all" ? historicalRecovery : null
11955
+ });
11658
11956
  console.log(json);
11659
11957
  return;
11660
11958
  }
@@ -11984,12 +12282,12 @@ program.command("compare").description("Compare two AI models side-by-side").opt
11984
12282
  await renderCompare(range, opts.provider);
11985
12283
  });
11986
12284
  program.command("agent-status").description("Diagnose exe-os agent data pipeline").action(() => {
11987
- const exeOsDir = join30(homedir17(), ".exe-os");
11988
- const statsPath = join30(exeOsDir, "agent-stats.json");
11989
- const pidPath = join30(exeOsDir, "exed.pid");
11990
- const dirExists = existsSync7(exeOsDir);
11991
- const statsExists = dirExists && existsSync7(statsPath);
11992
- const pidExists = dirExists && existsSync7(pidPath);
12285
+ const exeOsDir = join31(homedir17(), ".exe-os");
12286
+ const statsPath = join31(exeOsDir, "agent-stats.json");
12287
+ const pidPath = join31(exeOsDir, "exed.pid");
12288
+ const dirExists = existsSync8(exeOsDir);
12289
+ const statsExists = dirExists && existsSync8(statsPath);
12290
+ const pidExists = dirExists && existsSync8(pidPath);
11993
12291
  const lines = [];
11994
12292
  lines.push(`exe-os directory: ${dirExists ? "\u2713 found" : "\u2717 not found"} (${exeOsDir})`);
11995
12293
  lines.push(`daemon pid file: ${pidExists ? "\u2713 found" : "\u2717 not found"}`);
@@ -12021,7 +12319,7 @@ program.command("agent-status").description("Diagnose exe-os agent data pipeline
12021
12319
  var releaseScan;
12022
12320
  var stopParentWatch = watchMenubarParent(Number(process.env["EXE_WATCHER_PARENT_PID"]));
12023
12321
  program.hook("preAction", async (_root, command) => {
12024
- if (command.name() === "status") releaseScan = await acquireFileLock(join30(getCacheDir(), "scan.lock"));
12322
+ if (command.name() === "status" && !command.opts().cachedOnly) releaseScan = await acquireFileLock(join31(getCacheDir(), "scan.lock"));
12025
12323
  });
12026
12324
  program.hook("postAction", async () => {
12027
12325
  await releaseScan?.();