exe-watcher 0.2.52 → 0.2.53

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 (2) hide show
  1. package/dist/cli.js +336 -153
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -27,9 +27,13 @@ var init_cache_dir = __esm({
27
27
  // src/resource-budget.ts
28
28
  import { getHeapStatistics } from "v8";
29
29
  import { AsyncLocalStorage } from "async_hooks";
30
+ function effectiveMaxBytes(requested) {
31
+ const override = Number(process.env["EXE_WATCHER_MAX_SCAN_BYTES"]);
32
+ return Number.isFinite(override) && override > 0 ? Math.min(requested, override) : requested;
33
+ }
30
34
  function withScanBudget(fn, maxRecords = 2e5, signal, maxBytes = 1024 * 1024 * 1024) {
31
35
  if (scans.getStore()) return fn();
32
- return scans.run({ bytes: 0, records: 0, started: Date.now(), maxRecords, signal, maxBytes }, fn);
36
+ return scans.run({ bytes: 0, records: 0, started: Date.now(), maxRecords, signal, maxBytes: effectiveMaxBytes(maxBytes) }, fn);
33
37
  }
34
38
  function chargeRead(bytes) {
35
39
  const budget = scans.getStore();
@@ -632,17 +636,17 @@ var init_bash_utils = __esm({
632
636
  });
633
637
 
634
638
  // src/cursor-cache.ts
635
- import { readFile as readFile7, mkdir as mkdir8, stat as stat9, rename as rename6 } from "fs/promises";
639
+ import { readFile as readFile8, mkdir as mkdir9, stat as stat10, rename as rename7 } from "fs/promises";
636
640
  import { open as open6 } from "fs/promises";
637
- import { join as join13 } from "path";
641
+ import { join as join14 } from "path";
638
642
  import { randomBytes as randomBytes4 } from "crypto";
639
643
  function getCachePath2() {
640
- return join13(getCacheDir(), CACHE_FILE);
644
+ return join14(getCacheDir(), CACHE_FILE);
641
645
  }
642
646
  async function getDbFingerprint(dbPath) {
643
647
  try {
644
- const s = await stat9(dbPath);
645
- const wal = await stat9(`${dbPath}-wal`).catch(() => null);
648
+ const s = await stat10(dbPath);
649
+ const wal = await stat10(`${dbPath}-wal`).catch(() => null);
646
650
  return [dbPath, s.dev, s.ino, s.ctimeMs, s.mtimeMs, s.size, wal?.ino, wal?.ctimeMs, wal?.mtimeMs, wal?.size, getPricingFingerprint()].join(":");
647
651
  } catch {
648
652
  return null;
@@ -652,10 +656,10 @@ async function readCachedResults(dbPath) {
652
656
  try {
653
657
  const fp = await getDbFingerprint(dbPath);
654
658
  if (!fp) return null;
655
- const size = (await stat9(getCachePath2())).size;
659
+ const size = (await stat10(getCachePath2())).size;
656
660
  if (size > 16 * 1024 * 1024) return null;
657
661
  chargeRead(size);
658
- const raw = await readFile7(getCachePath2(), "utf-8");
662
+ const raw = await readFile8(getCachePath2(), "utf-8");
659
663
  const cache = JSON.parse(raw);
660
664
  if (cache.fingerprint === fp) {
661
665
  return cache.calls;
@@ -671,7 +675,7 @@ async function writeCachedResults(dbPath, calls, expected) {
671
675
  const fp = await getDbFingerprint(dbPath);
672
676
  if (!fp || expected !== void 0 && expected !== fp) return;
673
677
  const dir = getCacheDir();
674
- await mkdir8(dir, { recursive: true });
678
+ await mkdir9(dir, { recursive: true });
675
679
  const cache = {
676
680
  fingerprint: fp,
677
681
  calls
@@ -687,7 +691,7 @@ async function writeCachedResults(dbPath, calls, expected) {
687
691
  } finally {
688
692
  await handle.close();
689
693
  }
690
- await rename6(tmpPath, finalPath);
694
+ await rename7(tmpPath, finalPath);
691
695
  } catch {
692
696
  }
693
697
  }
@@ -786,16 +790,16 @@ __export(cursor_exports, {
786
790
  cursor: () => cursor
787
791
  });
788
792
  import { existsSync } from "fs";
789
- import { join as join14 } from "path";
793
+ import { join as join15 } from "path";
790
794
  import { homedir as homedir8 } from "os";
791
795
  function getCursorDbPath() {
792
796
  if (process.platform === "darwin") {
793
- return join14(homedir8(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
797
+ return join15(homedir8(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
794
798
  }
795
799
  if (process.platform === "win32") {
796
- return join14(homedir8(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
800
+ return join15(homedir8(), "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb");
797
801
  }
798
- return join14(homedir8(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
802
+ return join15(homedir8(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
799
803
  }
800
804
  function extractLanguages(codeBlocksJson) {
801
805
  if (!codeBlocksJson) return [];
@@ -1037,19 +1041,19 @@ __export(opencode_exports, {
1037
1041
  opencode: () => opencode
1038
1042
  });
1039
1043
  import { readdir as readdir8 } from "fs/promises";
1040
- import { join as join15 } from "path";
1044
+ import { join as join16 } from "path";
1041
1045
  import { homedir as homedir9 } from "os";
1042
- function sanitize(dir) {
1046
+ function sanitize2(dir) {
1043
1047
  return dir.replace(/^\//, "").replace(/\//g, "-");
1044
1048
  }
1045
1049
  function getDataDir(dataDir) {
1046
- const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join15(homedir9(), ".local", "share");
1047
- return join15(base, "opencode");
1050
+ const base = dataDir ?? process.env["XDG_DATA_HOME"] ?? join16(homedir9(), ".local", "share");
1051
+ return join16(base, "opencode");
1048
1052
  }
1049
1053
  async function findDbFiles(dir) {
1050
1054
  try {
1051
1055
  const entries = await readdir8(dir);
1052
- return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join15(dir, f));
1056
+ return entries.filter((f) => f.startsWith("opencode") && f.endsWith(".db")).map((f) => join16(dir, f));
1053
1057
  } catch (error) {
1054
1058
  if (error instanceof ResourceBudgetError) throw error;
1055
1059
  return [];
@@ -1204,7 +1208,7 @@ async function discoverFromDb(dbPath) {
1204
1208
  );
1205
1209
  return rows.map((row) => ({
1206
1210
  path: `${dbPath}:${row.id}`,
1207
- project: row.directory ? sanitize(row.directory) : sanitize(row.title),
1211
+ project: row.directory ? sanitize2(row.directory) : sanitize2(row.title),
1208
1212
  provider: "opencode"
1209
1213
  }));
1210
1214
  } catch (error) {
@@ -1275,18 +1279,18 @@ __export(cursor_agent_exports, {
1275
1279
  });
1276
1280
  import { createHash as createHash3 } from "crypto";
1277
1281
  import { existsSync as existsSync2 } from "fs";
1278
- import { readdir as readdir9, stat as stat10 } from "fs/promises";
1279
- import { join as join16, basename as basename7 } from "path";
1282
+ import { readdir as readdir9, stat as stat11 } from "fs/promises";
1283
+ import { join as join17, basename as basename7 } from "path";
1280
1284
  import { homedir as homedir10 } from "os";
1281
1285
  function getCursorAgentBaseDir(baseDirOverride) {
1282
1286
  if (baseDirOverride) return baseDirOverride;
1283
- return join16(homedir10(), ".cursor");
1287
+ return join17(homedir10(), ".cursor");
1284
1288
  }
1285
1289
  function getProjectsDir2(baseDir) {
1286
- return join16(baseDir, "projects");
1290
+ return join17(baseDir, "projects");
1287
1291
  }
1288
1292
  function getAttributionDbPath(baseDir) {
1289
- return join16(baseDir, "ai-tracking", "ai-code-tracking.db");
1293
+ return join17(baseDir, "ai-tracking", "ai-code-tracking.db");
1290
1294
  }
1291
1295
  function estimateTokens(charCount) {
1292
1296
  if (charCount <= 0) return 0;
@@ -1476,7 +1480,7 @@ function createParser6(source, seenKeys, dbPath, summariesByConversationId) {
1476
1480
  }
1477
1481
  let timestamp = summary?.updatedAt ?? null;
1478
1482
  if (!timestamp) {
1479
- const fileStat = await stat10(source.path);
1483
+ const fileStat = await stat11(source.path);
1480
1484
  timestamp = fileStat.mtime.toISOString();
1481
1485
  }
1482
1486
  const model = resolveModel3(summary?.model ?? null);
@@ -1545,13 +1549,13 @@ function createCursorAgentProvider(baseDirOverride) {
1545
1549
  for (const entry of projectEntries) {
1546
1550
  if (!entry.isDirectory()) continue;
1547
1551
  const projectId = prettifyProjectId(entry.name);
1548
- const transcriptDir = join16(projectsDir, entry.name, "agent-transcripts");
1552
+ const transcriptDir = join17(projectsDir, entry.name, "agent-transcripts");
1549
1553
  if (!existsSync2(transcriptDir)) continue;
1550
1554
  const transcriptEntries = await readdir9(transcriptDir, { withFileTypes: true });
1551
1555
  for (const transcript of transcriptEntries) {
1552
1556
  if (!transcript.isFile()) continue;
1553
1557
  if (!transcript.name.endsWith(".txt")) continue;
1554
- const transcriptPath = join16(transcriptDir, transcript.name);
1558
+ const transcriptPath = join17(transcriptDir, transcript.name);
1555
1559
  sources.push({
1556
1560
  path: transcriptPath,
1557
1561
  project: projectId,
@@ -1681,7 +1685,7 @@ async function acquireFileLock(path, timeoutMs = 15e3) {
1681
1685
  // src/cli.ts
1682
1686
  init_resource_budget();
1683
1687
  import { existsSync as existsSync7, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
1684
- import { join as join24 } from "path";
1688
+ import { join as join25 } from "path";
1685
1689
  import { homedir as homedir14 } from "os";
1686
1690
  import { Command } from "commander";
1687
1691
 
@@ -3135,8 +3139,8 @@ async function cachedFileParse(path, kind, parse, warnings) {
3135
3139
  init_resource_budget();
3136
3140
  init_fs_utils();
3137
3141
  init_models();
3138
- import { readdir as readdir10, stat as stat12 } from "fs/promises";
3139
- import { basename as basename8, join as join18 } from "path";
3142
+ import { readdir as readdir10, stat as stat13 } from "fs/promises";
3143
+ import { basename as basename8, join as join19 } from "path";
3140
3144
 
3141
3145
  // src/providers/index.ts
3142
3146
  init_resource_budget();
@@ -3236,12 +3240,133 @@ var claude = {
3236
3240
  }
3237
3241
  };
3238
3242
 
3243
+ // src/providers/codex.ts
3244
+ import { readdir as readdir5, stat as stat7 } from "fs/promises";
3245
+ import { basename as basename3, join as join11 } from "path";
3246
+ import { homedir as homedir5 } from "os";
3247
+
3248
+ // src/discovery-cache.ts
3249
+ init_cache_dir();
3250
+ import { randomUUID as randomUUID2 } from "crypto";
3251
+ import { mkdir as mkdir8, readFile as readFile7, rename as rename6, stat as stat6, unlink as unlink5, writeFile as writeFile3 } from "fs/promises";
3252
+ import { join as join10 } from "path";
3253
+ init_resource_budget();
3254
+ var VERSION2 = 1;
3255
+ var MANIFEST = "discovery-index.json";
3256
+ var MAX_MANIFEST_BYTES = 16 * 1024 * 1024;
3257
+ var MAX_ENTRIES = 2e4;
3258
+ var MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
3259
+ var TOUCH_INTERVAL_MS = 6 * 60 * 60 * 1e3;
3260
+ var loaded = null;
3261
+ var loadedFrom = "";
3262
+ var dirty = false;
3263
+ function manifestPath() {
3264
+ return join10(getCacheDir(), MANIFEST);
3265
+ }
3266
+ function fingerprintOf(s) {
3267
+ return [s.dev, s.ino, s.size, s.mtimeMs, s.ctimeMs].join(":");
3268
+ }
3269
+ function emptyManifest() {
3270
+ return { v: VERSION2, e: /* @__PURE__ */ Object.create(null) };
3271
+ }
3272
+ function sanitize(raw) {
3273
+ if (!raw || typeof raw !== "object") return emptyManifest();
3274
+ const parsed = raw;
3275
+ if (parsed.v !== VERSION2 || !parsed.e || typeof parsed.e !== "object") return emptyManifest();
3276
+ const out = emptyManifest();
3277
+ for (const [key, value] of Object.entries(parsed.e)) {
3278
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
3279
+ if (!value || typeof value !== "object") continue;
3280
+ const entry = value;
3281
+ if (typeof entry.f !== "string" || typeof entry.t !== "number") continue;
3282
+ out.e[key] = { f: entry.f, r: entry.r ?? null, t: entry.t };
3283
+ }
3284
+ return out;
3285
+ }
3286
+ async function load() {
3287
+ const path = manifestPath();
3288
+ if (loaded && loadedFrom === path) return loaded;
3289
+ loaded = emptyManifest();
3290
+ loadedFrom = path;
3291
+ dirty = false;
3292
+ try {
3293
+ const s = await stat6(path);
3294
+ if (s.size > MAX_MANIFEST_BYTES) return loaded;
3295
+ chargeRead(s.size);
3296
+ loaded = sanitize(JSON.parse(await readFile7(path, "utf8")));
3297
+ } catch (err) {
3298
+ if (err instanceof ResourceBudgetError) throw err;
3299
+ }
3300
+ return loaded;
3301
+ }
3302
+ function evict(manifest) {
3303
+ const cutoff = Date.now() - MAX_AGE_MS;
3304
+ for (const [key, entry] of Object.entries(manifest.e)) {
3305
+ if (entry.t < cutoff) delete manifest.e[key];
3306
+ }
3307
+ const keys = Object.keys(manifest.e);
3308
+ if (keys.length <= MAX_ENTRIES) return;
3309
+ keys.sort((a, b) => manifest.e[a].t - manifest.e[b].t);
3310
+ for (const key of keys.slice(0, keys.length - MAX_ENTRIES)) delete manifest.e[key];
3311
+ }
3312
+ async function cachedDiscoveryHeader(kind, path, stats, compute) {
3313
+ const manifest = await load();
3314
+ const key = `${kind}\0${path}`;
3315
+ const fingerprint2 = fingerprintOf(stats);
3316
+ const hit = manifest.e[key];
3317
+ if (hit && hit.f === fingerprint2) {
3318
+ const now = Date.now();
3319
+ if (now - hit.t > TOUCH_INTERVAL_MS) {
3320
+ hit.t = now;
3321
+ dirty = true;
3322
+ }
3323
+ return hit.r;
3324
+ }
3325
+ const value = await compute();
3326
+ const after = await stat6(path).catch(() => null);
3327
+ if (!after || fingerprintOf(after) !== fingerprint2) return value;
3328
+ manifest.e[key] = { f: fingerprint2, r: value ?? null, t: Date.now() };
3329
+ dirty = true;
3330
+ return value;
3331
+ }
3332
+ async function flushDiscoveryCache() {
3333
+ if (!dirty || !loaded) return;
3334
+ const mine = loaded;
3335
+ dirty = false;
3336
+ const path = manifestPath();
3337
+ const dir = getCacheDir();
3338
+ try {
3339
+ await mkdir8(dir, { recursive: true });
3340
+ const release = await acquireFileLock(join10(dir, "discovery-index.lock"));
3341
+ try {
3342
+ const onDisk = await readFile7(path, "utf8").then((raw) => sanitize(JSON.parse(raw))).catch(() => emptyManifest());
3343
+ for (const [key, entry] of Object.entries(mine.e)) {
3344
+ const other = onDisk.e[key];
3345
+ if (!other || other.t <= entry.t) onDisk.e[key] = entry;
3346
+ }
3347
+ evict(onDisk);
3348
+ const payload = JSON.stringify(onDisk);
3349
+ if (Buffer.byteLength(payload) > MAX_MANIFEST_BYTES) return;
3350
+ const tmp = `${path}.${randomUUID2()}.tmp`;
3351
+ try {
3352
+ await writeFile3(tmp, payload, { mode: 384 });
3353
+ await rename6(tmp, path);
3354
+ } finally {
3355
+ await unlink5(tmp).catch(() => {
3356
+ });
3357
+ }
3358
+ loaded = onDisk;
3359
+ } finally {
3360
+ await release();
3361
+ }
3362
+ } catch {
3363
+ dirty = true;
3364
+ }
3365
+ }
3366
+
3239
3367
  // src/providers/codex.ts
3240
3368
  init_fs_utils();
3241
3369
  init_models();
3242
- import { readdir as readdir5, stat as stat6 } from "fs/promises";
3243
- import { basename as basename3, join as join10 } from "path";
3244
- import { homedir as homedir5 } from "os";
3245
3370
  var modelDisplayNames = {
3246
3371
  "gpt-5.5": "GPT-5.5",
3247
3372
  "gpt-5.4": "GPT-5.4",
@@ -3265,7 +3390,7 @@ var toolNameMap = {
3265
3390
  read_dir: "Glob"
3266
3391
  };
3267
3392
  function getCodexDir(override) {
3268
- return override ?? process.env["CODEX_HOME"] ?? join10(homedir5(), ".codex");
3393
+ return override ?? process.env["CODEX_HOME"] ?? join11(homedir5(), ".codex");
3269
3394
  }
3270
3395
  function sanitizeProject(cwd) {
3271
3396
  return cwd.replace(/^\//, "").replace(/\//g, "-");
@@ -3279,14 +3404,14 @@ async function readFirstLine(filePath) {
3279
3404
  return null;
3280
3405
  }
3281
3406
  }
3282
- async function isValidCodexSession(filePath) {
3407
+ async function readCodexHeader(filePath) {
3283
3408
  const entry = await readFirstLine(filePath);
3284
- if (!entry) return { valid: false };
3285
- const valid = entry.type === "session_meta" && typeof entry.payload?.originator === "string" && entry.payload.originator.toLowerCase().startsWith("codex");
3286
- return { valid, meta: valid ? entry : void 0 };
3409
+ const valid = entry?.type === "session_meta" && typeof entry.payload?.originator === "string" && entry.payload.originator.toLowerCase().startsWith("codex");
3410
+ if (!valid) return null;
3411
+ return { project: sanitizeProject(entry.payload?.cwd ?? "unknown") };
3287
3412
  }
3288
3413
  async function discoverSessionsInDir(codexDir) {
3289
- const sessionsDir = join10(codexDir, "sessions");
3414
+ const sessionsDir = join11(codexDir, "sessions");
3290
3415
  const sources = [];
3291
3416
  let years;
3292
3417
  try {
@@ -3296,25 +3421,24 @@ async function discoverSessionsInDir(codexDir) {
3296
3421
  }
3297
3422
  for (const year of years) {
3298
3423
  if (!/^\d{4}$/.test(year)) continue;
3299
- const yearDir = join10(sessionsDir, year);
3424
+ const yearDir = join11(sessionsDir, year);
3300
3425
  const months = await readdir5(yearDir).catch(() => []);
3301
3426
  for (const month of months) {
3302
3427
  if (!/^\d{2}$/.test(month)) continue;
3303
- const monthDir = join10(yearDir, month);
3428
+ const monthDir = join11(yearDir, month);
3304
3429
  const days = await readdir5(monthDir).catch(() => []);
3305
3430
  for (const day of days) {
3306
3431
  if (!/^\d{2}$/.test(day)) continue;
3307
- const dayDir = join10(monthDir, day);
3432
+ const dayDir = join11(monthDir, day);
3308
3433
  const files = await readdir5(dayDir).catch(() => []);
3309
3434
  for (const file of files) {
3310
3435
  if (!file.startsWith("rollout-") || !file.endsWith(".jsonl")) continue;
3311
- const filePath = join10(dayDir, file);
3312
- const s = await stat6(filePath).catch(() => null);
3436
+ const filePath = join11(dayDir, file);
3437
+ const s = await stat7(filePath).catch(() => null);
3313
3438
  if (!s?.isFile()) continue;
3314
- const { valid, meta } = await isValidCodexSession(filePath);
3315
- if (!valid || !meta) continue;
3316
- const cwd = meta.payload?.cwd ?? "unknown";
3317
- sources.push({ path: filePath, project: sanitizeProject(cwd), provider: "codex" });
3439
+ const header = await cachedDiscoveryHeader("codex", filePath, s, () => readCodexHeader(filePath));
3440
+ if (!header) continue;
3441
+ sources.push({ path: filePath, project: header.project, provider: "codex" });
3318
3442
  }
3319
3443
  }
3320
3444
  }
@@ -3471,7 +3595,11 @@ function createCodexProvider(codexDir) {
3471
3595
  return toolNameMap[rawTool] ?? rawTool;
3472
3596
  },
3473
3597
  async discoverSessions() {
3474
- return discoverSessionsInDir(dir);
3598
+ try {
3599
+ return await discoverSessionsInDir(dir);
3600
+ } finally {
3601
+ await flushDiscoveryCache();
3602
+ }
3475
3603
  },
3476
3604
  createSessionParser(source, seenKeys) {
3477
3605
  return createParser(source, seenKeys);
@@ -3483,8 +3611,8 @@ var codex = createCodexProvider();
3483
3611
  // src/providers/copilot.ts
3484
3612
  init_fs_utils();
3485
3613
  init_models();
3486
- import { readdir as readdir6, stat as stat7 } from "fs/promises";
3487
- import { basename as basename4, dirname as dirname3, join as join11 } from "path";
3614
+ import { readdir as readdir6, stat as stat8 } from "fs/promises";
3615
+ import { basename as basename4, dirname as dirname3, join as join12 } from "path";
3488
3616
  import { homedir as homedir6 } from "os";
3489
3617
  var modelDisplayNames2 = {
3490
3618
  "gpt-4.1-nano": "GPT-4.1 Nano",
@@ -3517,7 +3645,7 @@ var toolNameMap2 = {
3517
3645
  };
3518
3646
  var modelDisplayEntries2 = Object.entries(modelDisplayNames2).sort((a, b) => b[0].length - a[0].length);
3519
3647
  function getCopilotSessionStateDir(override) {
3520
- return override ?? join11(homedir6(), ".copilot", "session-state");
3648
+ return override ?? join12(homedir6(), ".copilot", "session-state");
3521
3649
  }
3522
3650
  function parseCwd(yaml) {
3523
3651
  const match = yaml.match(/^cwd:\s*(.+)$/m);
@@ -3592,11 +3720,11 @@ async function discoverSessionsInDir2(sessionStateDir) {
3592
3720
  return sources;
3593
3721
  }
3594
3722
  for (const sessionId of sessionDirs) {
3595
- const eventsPath = join11(sessionStateDir, sessionId, "events.jsonl");
3596
- const s = await stat7(eventsPath).catch(() => null);
3723
+ const eventsPath = join12(sessionStateDir, sessionId, "events.jsonl");
3724
+ const s = await stat8(eventsPath).catch(() => null);
3597
3725
  if (!s?.isFile()) continue;
3598
3726
  let project = sessionId;
3599
- const yaml = await readSessionFile(join11(sessionStateDir, sessionId, "workspace.yaml"));
3727
+ const yaml = await readSessionFile(join12(sessionStateDir, sessionId, "workspace.yaml"));
3600
3728
  if (yaml !== null) {
3601
3729
  const cwd = parseCwd(yaml);
3602
3730
  if (cwd) project = basename4(cwd);
@@ -3633,8 +3761,8 @@ var copilot = createCopilotProvider();
3633
3761
  init_fs_utils();
3634
3762
  init_models();
3635
3763
  init_bash_utils();
3636
- import { readdir as readdir7, stat as stat8 } from "fs/promises";
3637
- import { basename as basename6, join as join12 } from "path";
3764
+ import { readdir as readdir7, stat as stat9 } from "fs/promises";
3765
+ import { basename as basename6, join as join13 } from "path";
3638
3766
  import { homedir as homedir7 } from "os";
3639
3767
  var modelDisplayNames3 = {
3640
3768
  "gpt-5.4": "GPT-5.4",
@@ -3659,10 +3787,10 @@ var toolNameMap3 = {
3659
3787
  };
3660
3788
  var modelDisplayEntries3 = Object.entries(modelDisplayNames3).sort((a, b) => b[0].length - a[0].length);
3661
3789
  function getPiSessionsDir(override) {
3662
- return override ?? join12(homedir7(), ".pi", "agent", "sessions");
3790
+ return override ?? join13(homedir7(), ".pi", "agent", "sessions");
3663
3791
  }
3664
3792
  function getOmpSessionsDir(override) {
3665
- return override ?? join12(homedir7(), ".omp", "agent", "sessions");
3793
+ return override ?? join13(homedir7(), ".omp", "agent", "sessions");
3666
3794
  }
3667
3795
  async function readFirstEntry(filePath) {
3668
3796
  const line = await readSessionFirstLine(filePath);
@@ -3682,8 +3810,8 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
3682
3810
  return sources;
3683
3811
  }
3684
3812
  for (const dirName of projectDirs) {
3685
- const dirPath = join12(sessionsDir, dirName);
3686
- const dirStat = await stat8(dirPath).catch(() => null);
3813
+ const dirPath = join13(sessionsDir, dirName);
3814
+ const dirStat = await stat9(dirPath).catch(() => null);
3687
3815
  if (!dirStat?.isDirectory()) continue;
3688
3816
  let files;
3689
3817
  try {
@@ -3693,8 +3821,8 @@ async function discoverSessionsInDir3(sessionsDir, providerName) {
3693
3821
  }
3694
3822
  for (const file of files) {
3695
3823
  if (!file.endsWith(".jsonl")) continue;
3696
- const filePath = join12(dirPath, file);
3697
- const fileStat = await stat8(filePath).catch(() => null);
3824
+ const filePath = join13(dirPath, file);
3825
+ const fileStat = await stat9(filePath).catch(() => null);
3698
3826
  if (!fileStat?.isFile()) continue;
3699
3827
  const first = await readFirstEntry(filePath);
3700
3828
  if (!first || first.type !== "session") continue;
@@ -4056,10 +4184,10 @@ function classifyTurn(turn) {
4056
4184
 
4057
4185
  // src/session-index.ts
4058
4186
  init_cache_dir();
4059
- import { stat as stat11 } from "fs/promises";
4187
+ import { stat as stat12 } from "fs/promises";
4060
4188
  import { existsSync as existsSync3, unlinkSync } from "fs";
4061
- import { readFile as readFile9, mkdir as mkdir9, rename as rename7, unlink as unlink5, open as open7 } from "fs/promises";
4062
- import { join as join17 } from "path";
4189
+ import { readFile as readFile10, mkdir as mkdir10, rename as rename8, unlink as unlink6, open as open7 } from "fs/promises";
4190
+ import { join as join18 } from "path";
4063
4191
  import { randomBytes as randomBytes5 } from "crypto";
4064
4192
  var SESSION_INDEX_VERSION = 1;
4065
4193
  var INDEX_FILENAME = "session-index.json";
@@ -4068,12 +4196,12 @@ function emptyIndex() {
4068
4196
  return { v: SESSION_INDEX_VERSION, e: {} };
4069
4197
  }
4070
4198
  function indexPath() {
4071
- return join17(getCacheDir(), INDEX_FILENAME);
4199
+ return join18(getCacheDir(), INDEX_FILENAME);
4072
4200
  }
4073
4201
  async function loadSessionIndex() {
4074
4202
  if (currentIndex) return currentIndex;
4075
4203
  try {
4076
- const raw = await readFile9(indexPath(), "utf-8");
4204
+ const raw = await readFile10(indexPath(), "utf-8");
4077
4205
  const parsed = JSON.parse(raw);
4078
4206
  if (parsed.v !== SESSION_INDEX_VERSION) {
4079
4207
  currentIndex = emptyIndex();
@@ -4088,7 +4216,7 @@ async function loadSessionIndex() {
4088
4216
  }
4089
4217
  async function saveSessionIndex(index) {
4090
4218
  const dir = getCacheDir();
4091
- if (!existsSync3(dir)) await mkdir9(dir, { recursive: true });
4219
+ if (!existsSync3(dir)) await mkdir10(dir, { recursive: true });
4092
4220
  const finalPath = indexPath();
4093
4221
  const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
4094
4222
  const payload = JSON.stringify(index);
@@ -4100,10 +4228,10 @@ async function saveSessionIndex(index) {
4100
4228
  await handle.close();
4101
4229
  }
4102
4230
  try {
4103
- await rename7(tempPath, finalPath);
4231
+ await rename8(tempPath, finalPath);
4104
4232
  } catch (err) {
4105
4233
  try {
4106
- await unlink5(tempPath);
4234
+ await unlink6(tempPath);
4107
4235
  } catch {
4108
4236
  }
4109
4237
  throw err;
@@ -4115,7 +4243,7 @@ async function checkSessionFile(filePath, index, dateRange) {
4115
4243
  if (!entry) return "parse";
4116
4244
  let fileStat;
4117
4245
  try {
4118
- fileStat = await stat11(filePath);
4246
+ fileStat = await stat12(filePath);
4119
4247
  } catch {
4120
4248
  return "parse";
4121
4249
  }
@@ -4369,7 +4497,7 @@ function buildSessionSummary(sessionId, project, turns) {
4369
4497
  async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
4370
4498
  if (dateRange) {
4371
4499
  try {
4372
- const s = await stat12(filePath);
4500
+ const s = await stat13(filePath);
4373
4501
  if (s.mtimeMs < dateRange.start.getTime()) return null;
4374
4502
  } catch {
4375
4503
  }
@@ -4401,13 +4529,13 @@ async function parseSessionFile(filePath, project, seenMsgIds, dateRange) {
4401
4529
  }
4402
4530
  async function collectJsonlFiles2(dirPath) {
4403
4531
  const files = await readdir10(dirPath).catch(() => []);
4404
- const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join18(dirPath, f));
4532
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => join19(dirPath, f));
4405
4533
  for (const entry of files) {
4406
4534
  if (entry.endsWith(".jsonl")) continue;
4407
- const subagentsPath = join18(dirPath, entry, "subagents");
4535
+ const subagentsPath = join19(dirPath, entry, "subagents");
4408
4536
  const subFiles = await readdir10(subagentsPath).catch(() => []);
4409
4537
  for (const sf of subFiles) {
4410
- if (sf.endsWith(".jsonl")) jsonlFiles.push(join18(subagentsPath, sf));
4538
+ if (sf.endsWith(".jsonl")) jsonlFiles.push(join19(subagentsPath, sf));
4411
4539
  }
4412
4540
  }
4413
4541
  return jsonlFiles;
@@ -4425,7 +4553,7 @@ async function scanProjectDirs(dirs, seenMsgIds, dateRange) {
4425
4553
  if (action === "skip") continue;
4426
4554
  let preStat = null;
4427
4555
  try {
4428
- const s = await stat12(filePath);
4556
+ const s = await stat13(filePath);
4429
4557
  preStat = { size: s.size, mtimeMs: s.mtimeMs };
4430
4558
  } catch {
4431
4559
  }
@@ -4433,7 +4561,7 @@ async function scanProjectDirs(dirs, seenMsgIds, dateRange) {
4433
4561
  const hasApiCalls = session !== null && session.apiCalls > 0;
4434
4562
  if (preStat && (hasApiCalls || !dateRange)) {
4435
4563
  try {
4436
- const postStat = await stat12(filePath);
4564
+ const postStat = await stat13(filePath);
4437
4565
  if (postStat.size === preStat.size && postStat.mtimeMs === preStat.mtimeMs) {
4438
4566
  indexDirty = recordParseResult(filePath, sessionIndex, preStat.size, preStat.mtimeMs, hasApiCalls) || indexDirty;
4439
4567
  }
@@ -4501,8 +4629,8 @@ async function parseProviderSources(providerName, sources, seenKeys, dateRange)
4501
4629
  for (const source of sources) {
4502
4630
  if (dateRange) {
4503
4631
  try {
4504
- const s = await stat12(source.path);
4505
- const wal = await stat12(`${source.path}-wal`).catch(() => null);
4632
+ const s = await stat13(source.path);
4633
+ const wal = await stat13(`${source.path}-wal`).catch(() => null);
4506
4634
  if (Math.max(s.mtimeMs, wal?.mtimeMs ?? 0) < dateRange.start.getTime()) continue;
4507
4635
  } catch {
4508
4636
  }
@@ -4757,12 +4885,12 @@ function renderStatusBar(projects) {
4757
4885
 
4758
4886
  // src/menubar-json.ts
4759
4887
  import { readdirSync, readFileSync } from "fs";
4760
- import { join as join19, dirname as dirname4 } from "path";
4888
+ import { join as join20, dirname as dirname4 } from "path";
4761
4889
  init_models();
4762
4890
  import { fileURLToPath } from "url";
4763
4891
  function getCliVersion() {
4764
4892
  try {
4765
- const pkgPath = join19(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
4893
+ const pkgPath = join20(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
4766
4894
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
4767
4895
  return pkg.version ?? "0.0.0";
4768
4896
  } catch {
@@ -4917,16 +5045,16 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
4917
5045
  init_cache_dir();
4918
5046
  import { createHash as createHash4, randomBytes as randomBytes6 } from "crypto";
4919
5047
  import { existsSync as existsSync4 } from "fs";
4920
- import { mkdir as mkdir10, open as open8, readFile as readFile10, rename as rename8, unlink as unlink6 } from "fs/promises";
4921
- import { join as join20 } from "path";
5048
+ import { mkdir as mkdir11, open as open8, readFile as readFile11, rename as rename9, unlink as unlink7 } from "fs/promises";
5049
+ import { join as join21 } from "path";
4922
5050
  var DAILY_CACHE_VERSION = 6;
4923
5051
  var DEFAULT_DAILY_CACHE_SCOPE = "global";
4924
5052
  var DAILY_CACHE_FILENAME = "daily-cache.json";
4925
5053
  function getCachePath3(scopeKey) {
4926
5054
  if (scopeKey === DEFAULT_DAILY_CACHE_SCOPE) {
4927
- return join20(getCacheDir(), DAILY_CACHE_FILENAME);
5055
+ return join21(getCacheDir(), DAILY_CACHE_FILENAME);
4928
5056
  }
4929
- return join20(getCacheDir(), `daily-cache-${scopeKey}.json`);
5057
+ return join21(getCacheDir(), `daily-cache-${scopeKey}.json`);
4930
5058
  }
4931
5059
  function emptyCache(scopeKey) {
4932
5060
  return { version: DAILY_CACHE_VERSION, scopeKey, lastComputedDate: null, days: [] };
@@ -4955,7 +5083,7 @@ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
4955
5083
  const path = getCachePath3(scopeKey);
4956
5084
  if (!existsSync4(path)) return emptyCache(scopeKey);
4957
5085
  try {
4958
- const raw = await readFile10(path, "utf-8");
5086
+ const raw = await readFile11(path, "utf-8");
4959
5087
  const parsed = JSON.parse(raw);
4960
5088
  if (!isValidCache(parsed)) return emptyCache(scopeKey);
4961
5089
  if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
@@ -4966,12 +5094,12 @@ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
4966
5094
  }
4967
5095
  async function saveDailyCache(cache) {
4968
5096
  const dir = getCacheDir();
4969
- if (!existsSync4(dir)) await mkdir10(dir, { recursive: true });
5097
+ if (!existsSync4(dir)) await mkdir11(dir, { recursive: true });
4970
5098
  const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
4971
5099
  const finalPath = getCachePath3(scopeKey);
4972
5100
  const tempPath = `${finalPath}.${randomBytes6(8).toString("hex")}.tmp`;
4973
5101
  const payload = JSON.stringify({ ...cache, scopeKey });
4974
- if (await readFile10(finalPath, "utf8").catch(() => "") === payload) return;
5102
+ if (await readFile11(finalPath, "utf8").catch(() => "") === payload) return;
4975
5103
  const handle = await open8(tempPath, "w", 384);
4976
5104
  try {
4977
5105
  await handle.writeFile(payload, { encoding: "utf-8" });
@@ -4980,10 +5108,10 @@ async function saveDailyCache(cache) {
4980
5108
  await handle.close();
4981
5109
  }
4982
5110
  try {
4983
- await rename8(tempPath, finalPath);
5111
+ await rename9(tempPath, finalPath);
4984
5112
  } catch (err) {
4985
5113
  try {
4986
- await unlink6(tempPath);
5114
+ await unlink7(tempPath);
4987
5115
  } catch {
4988
5116
  }
4989
5117
  throw err;
@@ -5002,7 +5130,9 @@ function addNewDays(cache, incoming, newestDate, options = {}) {
5002
5130
  scopeKey: cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE,
5003
5131
  lastComputedDate: nextLast,
5004
5132
  days: merged,
5005
- revalidatedAt: cache.revalidatedAt
5133
+ revalidatedAt: cache.revalidatedAt,
5134
+ backfill: cache.backfill,
5135
+ partialDates: cache.partialDates
5006
5136
  };
5007
5137
  }
5008
5138
  function getDaysInRange(cache, start, end) {
@@ -5028,7 +5158,7 @@ var LatestLoad = class {
5028
5158
  this.generation++;
5029
5159
  this.controller?.abort();
5030
5160
  }
5031
- async run(load) {
5161
+ async run(load2) {
5032
5162
  const generation = ++this.generation;
5033
5163
  this.controller?.abort();
5034
5164
  const previous = this.tail;
@@ -5040,7 +5170,7 @@ var LatestLoad = class {
5040
5170
  const controller = new AbortController();
5041
5171
  this.controller = controller;
5042
5172
  try {
5043
- const result = await load(controller.signal);
5173
+ const result = await load2(controller.signal);
5044
5174
  return generation === this.generation ? result : void 0;
5045
5175
  } catch (error) {
5046
5176
  if (generation !== this.generation) return void 0;
@@ -5068,9 +5198,9 @@ init_models();
5068
5198
  // src/optimize.ts
5069
5199
  init_fs_utils();
5070
5200
  import chalk2 from "chalk";
5071
- import { readdir as readdir11, stat as stat13 } from "fs/promises";
5201
+ import { readdir as readdir11, stat as stat14 } from "fs/promises";
5072
5202
  import { existsSync as existsSync5, statSync as statSync2 } from "fs";
5073
- import { basename as basename9, join as join21 } from "path";
5203
+ import { basename as basename9, join as join22 } from "path";
5074
5204
  import { homedir as homedir11 } from "os";
5075
5205
  var ORANGE = "#F59E0B";
5076
5206
  var DIM = "#666666";
@@ -5154,13 +5284,13 @@ var DEFAULT_TREND_PERIOD_MS = DEFAULT_TREND_PERIOD_DAYS * 24 * 60 * 60 * 1e3;
5154
5284
  var IMPROVING_THRESHOLD = 0.5;
5155
5285
  async function collectJsonlFiles3(dirPath) {
5156
5286
  const files = await readdir11(dirPath).catch(() => []);
5157
- const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join21(dirPath, f));
5287
+ const result = files.filter((f) => f.endsWith(".jsonl")).map((f) => join22(dirPath, f));
5158
5288
  for (const entry of files) {
5159
5289
  if (entry.endsWith(".jsonl")) continue;
5160
- const subPath = join21(dirPath, entry, "subagents");
5290
+ const subPath = join22(dirPath, entry, "subagents");
5161
5291
  const subFiles = await readdir11(subPath).catch(() => []);
5162
5292
  for (const sf of subFiles) {
5163
- if (sf.endsWith(".jsonl")) result.push(join21(subPath, sf));
5293
+ if (sf.endsWith(".jsonl")) result.push(join22(subPath, sf));
5164
5294
  }
5165
5295
  }
5166
5296
  return result;
@@ -5168,7 +5298,7 @@ async function collectJsonlFiles3(dirPath) {
5168
5298
  async function isFileStaleForRange(filePath, range) {
5169
5299
  if (!range) return false;
5170
5300
  try {
5171
- const s = await stat13(filePath);
5301
+ const s = await stat14(filePath);
5172
5302
  return s.mtimeMs < range.start.getTime();
5173
5303
  } catch {
5174
5304
  return false;
@@ -5298,13 +5428,13 @@ function isReadTool(name) {
5298
5428
  function loadMcpConfigs(projectCwds) {
5299
5429
  const servers = /* @__PURE__ */ new Map();
5300
5430
  const configPaths = [
5301
- join21(homedir11(), ".claude", "settings.json"),
5302
- join21(homedir11(), ".claude", "settings.local.json")
5431
+ join22(homedir11(), ".claude", "settings.json"),
5432
+ join22(homedir11(), ".claude", "settings.local.json")
5303
5433
  ];
5304
5434
  for (const cwd of projectCwds) {
5305
- configPaths.push(join21(cwd, ".mcp.json"));
5306
- configPaths.push(join21(cwd, ".claude", "settings.json"));
5307
- configPaths.push(join21(cwd, ".claude", "settings.local.json"));
5435
+ configPaths.push(join22(cwd, ".mcp.json"));
5436
+ configPaths.push(join22(cwd, ".claude", "settings.json"));
5437
+ configPaths.push(join22(cwd, ".claude", "settings.local.json"));
5308
5438
  }
5309
5439
  for (const p of configPaths) {
5310
5440
  if (!existsSync5(p)) continue;
@@ -5457,12 +5587,12 @@ function expandImports(filePath, seen, depth) {
5457
5587
  if (content === null) return { totalLines: 0, importedFiles: 0 };
5458
5588
  let totalLines = content.split("\n").length;
5459
5589
  let importedFiles = 0;
5460
- const dir = join21(filePath, "..");
5590
+ const dir = join22(filePath, "..");
5461
5591
  IMPORT_PATTERN.lastIndex = 0;
5462
5592
  for (const match of content.matchAll(IMPORT_PATTERN)) {
5463
5593
  const rawPath = match[1];
5464
5594
  if (!rawPath) continue;
5465
- const resolved = rawPath.startsWith("/") ? rawPath : join21(dir, rawPath);
5595
+ const resolved = rawPath.startsWith("/") ? rawPath : join22(dir, rawPath);
5466
5596
  if (!existsSync5(resolved)) continue;
5467
5597
  const nested = expandImports(resolved, seen, depth + 1);
5468
5598
  totalLines += nested.totalLines;
@@ -5474,7 +5604,7 @@ function detectBloatedClaudeMd(projectCwds) {
5474
5604
  const bloated = [];
5475
5605
  for (const cwd of projectCwds) {
5476
5606
  for (const name of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
5477
- const fullPath = join21(cwd, name);
5607
+ const fullPath = join22(cwd, name);
5478
5608
  if (!existsSync5(fullPath)) continue;
5479
5609
  const { totalLines, importedFiles } = expandImports(fullPath, /* @__PURE__ */ new Set(), 0);
5480
5610
  if (totalLines > CLAUDEMD_HEALTHY_LINES) {
@@ -5617,7 +5747,7 @@ async function listSkillDirs(dir) {
5617
5747
  const entries = await readdir11(dir);
5618
5748
  const names = [];
5619
5749
  for (const entry of entries) {
5620
- if (existsSync5(join21(dir, entry, "SKILL.md"))) names.push(entry);
5750
+ if (existsSync5(join22(dir, entry, "SKILL.md"))) names.push(entry);
5621
5751
  }
5622
5752
  return names;
5623
5753
  } catch {
@@ -5625,7 +5755,7 @@ async function listSkillDirs(dir) {
5625
5755
  }
5626
5756
  }
5627
5757
  async function detectGhostAgents(calls) {
5628
- const defined = await listMarkdownFiles(join21(homedir11(), ".claude", "agents"));
5758
+ const defined = await listMarkdownFiles(join22(homedir11(), ".claude", "agents"));
5629
5759
  if (defined.length === 0) return null;
5630
5760
  const invoked = /* @__PURE__ */ new Set();
5631
5761
  for (const call of calls) {
@@ -5650,7 +5780,7 @@ async function detectGhostAgents(calls) {
5650
5780
  };
5651
5781
  }
5652
5782
  async function detectGhostSkills(calls) {
5653
- const defined = await listSkillDirs(join21(homedir11(), ".claude", "skills"));
5783
+ const defined = await listSkillDirs(join22(homedir11(), ".claude", "skills"));
5654
5784
  if (defined.length === 0) return null;
5655
5785
  const invoked = /* @__PURE__ */ new Set();
5656
5786
  for (const call of calls) {
@@ -5675,7 +5805,7 @@ async function detectGhostSkills(calls) {
5675
5805
  };
5676
5806
  }
5677
5807
  async function detectGhostCommands(userMessages) {
5678
- const defined = await listMarkdownFiles(join21(homedir11(), ".claude", "commands"));
5808
+ const defined = await listMarkdownFiles(join22(homedir11(), ".claude", "commands"));
5679
5809
  if (defined.length === 0) return null;
5680
5810
  const invoked = /* @__PURE__ */ new Set();
5681
5811
  for (const msg of userMessages) {
@@ -5703,7 +5833,7 @@ async function detectGhostCommands(userMessages) {
5703
5833
  }
5704
5834
  function readShellProfileLimit() {
5705
5835
  for (const profile of SHELL_PROFILES) {
5706
- const path = join21(homedir11(), profile);
5836
+ const path = join22(homedir11(), profile);
5707
5837
  if (!existsSync5(path)) continue;
5708
5838
  const content = readSessionFileSync(path);
5709
5839
  if (content === null) continue;
@@ -5927,7 +6057,7 @@ async function runOptimize(projects, periodLabel, dateRange, preScanned) {
5927
6057
  init_fs_utils();
5928
6058
  import { readdir as readdir12 } from "fs/promises";
5929
6059
  import { existsSync as existsSync6 } from "fs";
5930
- import { join as join22 } from "path";
6060
+ import { join as join23 } from "path";
5931
6061
  import { homedir as homedir12 } from "os";
5932
6062
  var CHARS_PER_TOKEN2 = 4;
5933
6063
  var SYSTEM_BASE_TOKENS = 10400;
@@ -5949,13 +6079,13 @@ async function readConfigFile(path) {
5949
6079
  async function countMcpTools(projectPath) {
5950
6080
  const home = homedir12();
5951
6081
  const configPaths = [
5952
- join22(home, ".claude", "settings.json"),
5953
- join22(home, ".claude", "settings.local.json")
6082
+ join23(home, ".claude", "settings.json"),
6083
+ join23(home, ".claude", "settings.local.json")
5954
6084
  ];
5955
6085
  if (projectPath) {
5956
- configPaths.push(join22(projectPath, ".mcp.json"));
5957
- configPaths.push(join22(projectPath, ".claude", "settings.json"));
5958
- configPaths.push(join22(projectPath, ".claude", "settings.local.json"));
6086
+ configPaths.push(join23(projectPath, ".mcp.json"));
6087
+ configPaths.push(join23(projectPath, ".claude", "settings.json"));
6088
+ configPaths.push(join23(projectPath, ".claude", "settings.local.json"));
5959
6089
  }
5960
6090
  const servers = /* @__PURE__ */ new Set();
5961
6091
  let toolCount = 0;
@@ -5972,15 +6102,15 @@ async function countMcpTools(projectPath) {
5972
6102
  return toolCount;
5973
6103
  }
5974
6104
  async function countSkills(projectPath) {
5975
- const dirs = [join22(homedir12(), ".claude", "skills")];
5976
- if (projectPath) dirs.push(join22(projectPath, ".claude", "skills"));
6105
+ const dirs = [join23(homedir12(), ".claude", "skills")];
6106
+ if (projectPath) dirs.push(join23(projectPath, ".claude", "skills"));
5977
6107
  let count = 0;
5978
6108
  for (const dir of dirs) {
5979
6109
  if (!existsSync6(dir)) continue;
5980
6110
  try {
5981
6111
  const entries = await readdir12(dir);
5982
6112
  for (const entry of entries) {
5983
- const skillFile = join22(dir, entry, "SKILL.md");
6113
+ const skillFile = join23(dir, entry, "SKILL.md");
5984
6114
  if (existsSync6(skillFile)) count++;
5985
6115
  }
5986
6116
  } catch {
@@ -5993,12 +6123,12 @@ async function scanMemoryFiles(projectPath) {
5993
6123
  const home = homedir12();
5994
6124
  const files = [];
5995
6125
  const paths = [
5996
- { path: join22(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
6126
+ { path: join23(home, ".claude", "CLAUDE.md"), name: "~/.claude/CLAUDE.md" }
5997
6127
  ];
5998
6128
  if (projectPath) {
5999
- paths.push({ path: join22(projectPath, "CLAUDE.md"), name: "CLAUDE.md" });
6000
- paths.push({ path: join22(projectPath, ".claude", "CLAUDE.md"), name: ".claude/CLAUDE.md" });
6001
- paths.push({ path: join22(projectPath, "CLAUDE.local.md"), name: "CLAUDE.local.md" });
6129
+ paths.push({ path: join23(projectPath, "CLAUDE.md"), name: "CLAUDE.md" });
6130
+ paths.push({ path: join23(projectPath, ".claude", "CLAUDE.md"), name: ".claude/CLAUDE.md" });
6131
+ paths.push({ path: join23(projectPath, "CLAUDE.local.md"), name: "CLAUDE.local.md" });
6002
6132
  }
6003
6133
  for (const { path, name } of paths) {
6004
6134
  if (!existsSync6(path)) continue;
@@ -6035,7 +6165,7 @@ async function discoverProjectCwd(sessionDir) {
6035
6165
  if (files.length === 0) return null;
6036
6166
  const firstFile = files[0];
6037
6167
  if (!firstFile) return null;
6038
- const content = await readSessionFile(join22(sessionDir, firstFile));
6168
+ const content = await readSessionFile(join23(sessionDir, firstFile));
6039
6169
  if (content === null) return null;
6040
6170
  for (const line of content.split("\n")) {
6041
6171
  if (!line.trim()) continue;
@@ -6632,7 +6762,7 @@ function planDisplayName(id) {
6632
6762
  }
6633
6763
 
6634
6764
  // src/dashboard.tsx
6635
- import { join as join23 } from "path";
6765
+ import { join as join24 } from "path";
6636
6766
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
6637
6767
  var PERIODS = ["today", "week", "30days", "month", "all"];
6638
6768
  var PERIOD_LABELS = {
@@ -7388,11 +7518,11 @@ function InteractiveDashboard({ initialPeriod, initialProvider, refreshSeconds,
7388
7518
  useEffect2(() => {
7389
7519
  let cancelled = false;
7390
7520
  async function loadBudgets() {
7391
- const claudeDir = join23(homedir13(), ".claude", "projects");
7521
+ const claudeDir = join24(homedir13(), ".claude", "projects");
7392
7522
  const budgets = /* @__PURE__ */ new Map();
7393
7523
  for (const project of projects.slice(0, 8)) {
7394
7524
  if (cancelled) return;
7395
- const cwd = await discoverProjectCwd(join23(claudeDir, project.project));
7525
+ const cwd = await discoverProjectCwd(join24(claudeDir, project.project));
7396
7526
  if (!cwd) continue;
7397
7527
  budgets.set(project.project, await estimateContextBudget(cwd));
7398
7528
  }
@@ -7670,6 +7800,29 @@ function computeProgressiveBackfillStart({
7670
7800
  }
7671
7801
  return gapStart;
7672
7802
  }
7803
+ var DEFAULT_BACKFILL_CHUNK_DAYS = 1;
7804
+ var MAX_BLOCKED_CHUNK_ATTEMPTS = 2;
7805
+ function planBackfillChunks(start, end, chunkDays = DEFAULT_BACKFILL_CHUNK_DAYS) {
7806
+ const size = Math.max(1, Math.floor(chunkDays));
7807
+ const chunks = [];
7808
+ if (start.getTime() > end.getTime()) return chunks;
7809
+ let cursor2 = new Date(start.getFullYear(), start.getMonth(), start.getDate());
7810
+ if (cursor2.getTime() < start.getTime()) cursor2 = new Date(start.getTime());
7811
+ while (cursor2.getTime() <= end.getTime()) {
7812
+ const dayStart = new Date(cursor2.getFullYear(), cursor2.getMonth(), cursor2.getDate());
7813
+ const lastDay = new Date(dayStart.getFullYear(), dayStart.getMonth(), dayStart.getDate() + size - 1);
7814
+ const chunkEnd = new Date(lastDay.getFullYear(), lastDay.getMonth(), lastDay.getDate() + 1).getTime() - 1;
7815
+ chunks.push({ start: new Date(cursor2.getTime()), end: new Date(Math.min(chunkEnd, end.getTime())) });
7816
+ cursor2 = new Date(dayStart.getFullYear(), dayStart.getMonth(), dayStart.getDate() + size);
7817
+ }
7818
+ return chunks;
7819
+ }
7820
+ function nextBackfillCursor(prev, date) {
7821
+ return prev && prev.blockedDate === date ? { blockedDate: date, attempts: prev.attempts + 1 } : { blockedDate: date, attempts: 1 };
7822
+ }
7823
+ function shouldSkipBlockedChunk(cursor2, date, maxAttempts = MAX_BLOCKED_CHUNK_ATTEMPTS) {
7824
+ return !!cursor2 && cursor2.blockedDate === date && cursor2.attempts >= maxAttempts;
7825
+ }
7673
7826
 
7674
7827
  // src/cli.ts
7675
7828
  import { createInterface } from "readline";
@@ -7963,6 +8116,49 @@ program.command("status").description("Compact status output (today + week + mon
7963
8116
  const yesterdayStr = toDateString(new Date(todayStart.getTime() - MS_PER_DAY4));
7964
8117
  const isAllProviders = pf === "all";
7965
8118
  const dailyCacheScope = buildDailyCacheScopeKey(opts.project, opts.exclude);
8119
+ const backfillWarnings = [];
8120
+ const runChunkedBackfill = async (cache2, start, end, order) => {
8121
+ let c = cache2;
8122
+ const chunks = planBackfillChunks(start, end);
8123
+ if (order === "newest-first") chunks.reverse();
8124
+ for (const chunk of chunks) {
8125
+ const chunkDate = toDateString(chunk.start);
8126
+ const coveredThrough = toDateString(chunk.end);
8127
+ if (shouldSkipBlockedChunk(c.backfill, chunkDate)) {
8128
+ const blanks = fillMissingDays(chunk.start, chunk.end, []);
8129
+ c = addNewDays(c, blanks, yesterdayStr, { coveredThrough });
8130
+ c.partialDates = [.../* @__PURE__ */ new Set([...c.partialDates ?? [], ...blanks.map((d) => d.date)])];
8131
+ c.backfill = null;
8132
+ c.revalidatedAt = Date.now();
8133
+ await saveDailyCache(c);
8134
+ const warn2 = `history for ${chunkDate} exceeded the scan budget ${MAX_BLOCKED_CHUNK_ATTEMPTS} times; recorded as incomplete so newer days can load`;
8135
+ backfillWarnings.push(warn2);
8136
+ process.stderr.write(`[exe-watcher] WARNING: ${warn2}
8137
+ `);
8138
+ continue;
8139
+ }
8140
+ try {
8141
+ const chunkRange = { start: chunk.start, end: chunk.end };
8142
+ const chunkProjects = filterProjectsByName(await parseAllSessions(chunkRange, "all"), opts.project, opts.exclude);
8143
+ const chunkDays = fillMissingDays(chunk.start, chunk.end, aggregateProjectsIntoDays(chunkProjects));
8144
+ c = addNewDays(c, chunkDays, yesterdayStr, { coveredThrough });
8145
+ c.backfill = null;
8146
+ c.revalidatedAt = Date.now();
8147
+ await saveDailyCache(c);
8148
+ } catch (err) {
8149
+ if (!(err instanceof ResourceBudgetError)) throw err;
8150
+ c.backfill = nextBackfillCursor(c.backfill, chunkDate);
8151
+ c.revalidatedAt = Date.now();
8152
+ await saveDailyCache(c);
8153
+ const warn2 = `history backfill paused at ${chunkDate} (attempt ${c.backfill.attempts}): ${err.message}`;
8154
+ backfillWarnings.push(warn2);
8155
+ process.stderr.write(`[exe-watcher] WARNING: ${warn2}
8156
+ `);
8157
+ break;
8158
+ }
8159
+ }
8160
+ return c;
8161
+ };
7966
8162
  const cache = await withDailyCacheLock(async () => {
7967
8163
  let c = await loadDailyCache(dailyCacheScope);
7968
8164
  const hadYesterday = c.days.some((d) => d.date >= yesterdayStr);
@@ -7982,13 +8178,7 @@ program.command("status").description("Compact status output (today + week + mon
7982
8178
  });
7983
8179
  const gapEnd = oldestCachedDate && effectiveGapStart.getTime() < new Date(oldestCachedDate).getTime() ? new Date(new Date(oldestCachedDate).getTime() - MS_PER_DAY4) : yesterdayEnd;
7984
8180
  if (effectiveGapStart.getTime() <= gapEnd.getTime()) {
7985
- const gapRange = { start: effectiveGapStart, end: gapEnd };
7986
- const gapProjects = filterProjectsByName(await parseAllSessions(gapRange, "all"), opts.project, opts.exclude);
7987
- const gapDays = fillMissingDays(gapRange.start, gapRange.end, aggregateProjectsIntoDays(gapProjects));
7988
- const coveredThrough = gapEnd.getTime() >= yesterdayEnd.getTime() ? yesterdayStr : void 0;
7989
- c = addNewDays(c, gapDays, yesterdayStr, coveredThrough ? { coveredThrough } : void 0);
7990
- c.revalidatedAt = Date.now();
7991
- await saveDailyCache(c);
8181
+ c = await runChunkedBackfill(c, effectiveGapStart, gapEnd, "oldest-first");
7992
8182
  }
7993
8183
  const updatedOldest = c.days.length > 0 ? c.days[0]?.date ?? null : null;
7994
8184
  const neededStart = new Date(todayStart.getTime() - (selectedPeriodHistoryDays - 1) * MS_PER_DAY4);
@@ -7997,12 +8187,7 @@ program.command("status").description("Compact status output (today + week + mon
7997
8187
  if (updatedOldest && clampedNeeded.getTime() < new Date(updatedOldest).getTime()) {
7998
8188
  const backEnd = new Date(new Date(updatedOldest).getTime() - MS_PER_DAY4);
7999
8189
  if (clampedNeeded.getTime() <= backEnd.getTime()) {
8000
- const backRange = { start: clampedNeeded, end: backEnd };
8001
- const backProjects = filterProjectsByName(await parseAllSessions(backRange, "all"), opts.project, opts.exclude);
8002
- const backDays = fillMissingDays(backRange.start, backRange.end, aggregateProjectsIntoDays(backProjects));
8003
- c = addNewDays(c, backDays, yesterdayStr);
8004
- c.revalidatedAt = Date.now();
8005
- await saveDailyCache(c);
8190
+ c = await runChunkedBackfill(c, clampedNeeded, backEnd, "newest-first");
8006
8191
  }
8007
8192
  }
8008
8193
  return c;
@@ -8010,7 +8195,7 @@ program.command("status").description("Compact status output (today + week + mon
8010
8195
  let currentData;
8011
8196
  const scanRange = periodInfo.range;
8012
8197
  const todayRange = { start: todayStart, end: periodInfo.range.end };
8013
- const warnings = [];
8198
+ const warnings = [...backfillWarnings];
8014
8199
  const parseStart = Date.now();
8015
8200
  let todayProjects;
8016
8201
  let allTodayProjects;
@@ -8018,7 +8203,6 @@ program.command("status").description("Compact status output (today + week + mon
8018
8203
  allTodayProjects = await parseAllSessions(todayRange, "all");
8019
8204
  todayProjects = fp(allTodayProjects);
8020
8205
  } catch (err) {
8021
- if (err instanceof ResourceBudgetError) throw err;
8022
8206
  const msg = err instanceof Error ? err.message : String(err);
8023
8207
  const warn2 = `parseAllSessions failed: ${msg}`;
8024
8208
  warnings.push(warn2);
@@ -8127,7 +8311,6 @@ program.command("status").description("Compact status output (today + week + mon
8127
8311
  const optimizeProjects = isAllProviders && opts.period === "today" ? todayProjects : fp(await parseAllSessions(scanRange, isAllProviders ? "all" : pf));
8128
8312
  optimize = await scanAndDetect(optimizeProjects, scanRange);
8129
8313
  } catch (err) {
8130
- if (err instanceof ResourceBudgetError) throw err;
8131
8314
  const msg = err instanceof Error ? err.message : String(err);
8132
8315
  const warn2 = `optimize parse failed: ${msg}`;
8133
8316
  warnings.push(warn2);
@@ -8136,12 +8319,12 @@ program.command("status").description("Compact status output (today + week + mon
8136
8319
  optimize = await scanAndDetect(todayProjects, scanRange);
8137
8320
  }
8138
8321
  }
8139
- const exeOsDir = join24(homedir14(), ".exe-os");
8322
+ const exeOsDir = join25(homedir14(), ".exe-os");
8140
8323
  const exeOsDetected = existsSync7(exeOsDir);
8141
8324
  let agentStats = null;
8142
8325
  let statsFileAge = null;
8143
8326
  try {
8144
- const statsPath = join24(exeOsDir, "agent-stats.json");
8327
+ const statsPath = join25(exeOsDir, "agent-stats.json");
8145
8328
  if (existsSync7(statsPath)) {
8146
8329
  statsFileAge = Math.round((Date.now() - statSync3(statsPath).mtimeMs) / 1e3);
8147
8330
  const parsed = JSON.parse(readFileSync2(statsPath, "utf-8"));
@@ -8490,9 +8673,9 @@ program.command("compare").description("Compare two AI models side-by-side").opt
8490
8673
  await renderCompare(range, opts.provider);
8491
8674
  });
8492
8675
  program.command("agent-status").description("Diagnose exe-os agent data pipeline").action(() => {
8493
- const exeOsDir = join24(homedir14(), ".exe-os");
8494
- const statsPath = join24(exeOsDir, "agent-stats.json");
8495
- const pidPath = join24(exeOsDir, "exed.pid");
8676
+ const exeOsDir = join25(homedir14(), ".exe-os");
8677
+ const statsPath = join25(exeOsDir, "agent-stats.json");
8678
+ const pidPath = join25(exeOsDir, "exed.pid");
8496
8679
  const dirExists = existsSync7(exeOsDir);
8497
8680
  const statsExists = dirExists && existsSync7(statsPath);
8498
8681
  const pidExists = dirExists && existsSync7(pidPath);
@@ -8526,7 +8709,7 @@ program.command("agent-status").description("Diagnose exe-os agent data pipeline
8526
8709
  });
8527
8710
  var releaseScan;
8528
8711
  program.hook("preAction", async (_root, command) => {
8529
- if (command.name() === "status") releaseScan = await acquireFileLock(join24(getCacheDir(), "scan.lock"));
8712
+ if (command.name() === "status") releaseScan = await acquireFileLock(join25(getCacheDir(), "scan.lock"));
8530
8713
  });
8531
8714
  program.hook("postAction", async () => {
8532
8715
  await releaseScan?.();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exe-watcher",
3
- "version": "0.2.52",
3
+ "version": "0.2.53",
4
4
  "description": "The owl watching your AI coding spend — cost, tokens, agents, and projects in your terminal and menu bar",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",