exe-watcher 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -48,12 +48,23 @@ async function fetchAndCachePricing() {
48
48
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
49
49
  const data = await response.json();
50
50
  const pricing = /* @__PURE__ */ new Map();
51
+ const preferredStripped = /* @__PURE__ */ new Map();
52
+ const fallbackStripped = /* @__PURE__ */ new Map();
51
53
  for (const [name, entry] of Object.entries(data)) {
52
54
  const costs = parseLiteLLMEntry(entry);
53
55
  if (!costs) continue;
54
56
  pricing.set(name, costs);
55
57
  const stripped = name.replace(/^[^/]+\//, "");
56
- if (stripped !== name && !pricing.has(stripped)) pricing.set(stripped, costs);
58
+ if (stripped === name) continue;
59
+ const isDirectProvider = DIRECT_PROVIDER_PREFIXES.some((prefix) => name.toLowerCase().startsWith(prefix));
60
+ const target = isDirectProvider ? preferredStripped : fallbackStripped;
61
+ if (!target.has(stripped)) target.set(stripped, costs);
62
+ }
63
+ for (const [name, costs] of preferredStripped) {
64
+ if (!pricing.has(name)) pricing.set(name, costs);
65
+ }
66
+ for (const [name, costs] of fallbackStripped) {
67
+ if (!pricing.has(name)) pricing.set(name, costs);
57
68
  }
58
69
  await mkdir5(getCacheDir(), { recursive: true });
59
70
  const finalPath = getCachePath();
@@ -114,18 +125,17 @@ function getCanonicalName(model) {
114
125
  }
115
126
  function getModelCosts(model) {
116
127
  const canonical = resolveAlias(getCanonicalName(model));
117
- if (Object.hasOwn(FALLBACK_PRICING, canonical)) return FALLBACK_PRICING[canonical];
118
- if (pricingCache?.has(canonical)) return pricingCache.get(canonical);
119
- for (const [key, costs] of pricingCache ?? /* @__PURE__ */ new Map()) {
120
- if (canonical.startsWith(key)) return costs;
121
- }
122
- for (const [key, costs] of Object.entries(FALLBACK_PRICING)) {
123
- if (canonical.startsWith(key)) return costs;
124
- }
125
- return null;
128
+ const exactFetched = pricingCache?.get(canonical) ?? null;
129
+ const exactFallback = Object.hasOwn(FALLBACK_PRICING, canonical) ? FALLBACK_PRICING[canonical] : null;
130
+ const exactMatch = mergeModelCosts(exactFetched, exactFallback);
131
+ if (exactMatch) return exactMatch;
132
+ const prefixFetched = findPrefixMatch(pricingCache ?? /* @__PURE__ */ new Map(), canonical);
133
+ const prefixFallback = findPrefixMatch(new Map(Object.entries(FALLBACK_PRICING)), canonical);
134
+ return mergeModelCosts(prefixFetched, prefixFallback);
126
135
  }
127
136
  function calculateCost(model, inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, webSearchRequests, speed = "standard") {
128
- const costs = getModelCosts(model);
137
+ const baseCosts = getModelCosts(model);
138
+ const costs = applyContextTier(baseCosts, inputTokens);
129
139
  if (!costs) return 0;
130
140
  const multiplier = speed === "fast" ? costs.fastMultiplier : 1;
131
141
  return multiplier * (inputTokens * costs.inputCostPerToken + outputTokens * costs.outputCostPerToken + cacheCreationTokens * costs.cacheWriteCostPerToken + cacheReadTokens * costs.cacheReadCostPerToken + webSearchRequests * costs.webSearchCostPerRequest);
@@ -150,9 +160,18 @@ function getShortModelName(model) {
150
160
  "gpt-4.1-nano": "GPT-4.1 Nano",
151
161
  "gpt-4.1-mini": "GPT-4.1 Mini",
152
162
  "gpt-4.1": "GPT-4.1",
163
+ "gpt-5.5": "GPT-5.5",
164
+ "gpt-5.5-pro": "GPT-5.5 Pro",
165
+ "gpt-5.4-pro": "GPT-5.4 Pro",
166
+ "gpt-5.4-nano": "GPT-5.4 Nano",
153
167
  "gpt-5.4-mini": "GPT-5.4 Mini",
154
168
  "gpt-5.4": "GPT-5.4",
169
+ "gpt-5.3-codex-spark": "GPT-5.3 Codex Spark",
155
170
  "gpt-5.3-codex": "GPT-5.3 Codex",
171
+ "gpt-5.2": "GPT-5.2",
172
+ "gpt-5.1": "GPT-5.1",
173
+ "gpt-5-codex": "GPT-5-Codex",
174
+ "gpt-5-nano": "GPT-5 Nano",
156
175
  "gpt-5-mini": "GPT-5 Mini",
157
176
  "gpt-5": "GPT-5",
158
177
  "gemini-2.5-pro": "Gemini 2.5 Pro",
@@ -161,12 +180,41 @@ function getShortModelName(model) {
161
180
  "MiniMax-M2.7-highspeed": "MiniMax M2.7 Highspeed",
162
181
  "MiniMax-M2.7": "MiniMax M2.7"
163
182
  };
164
- for (const [key, name] of Object.entries(shortNames2)) {
183
+ if (Object.hasOwn(shortNames2, canonical)) return shortNames2[canonical];
184
+ for (const [key, name] of Object.entries(shortNames2).sort((a, b) => b[0].length - a[0].length)) {
165
185
  if (canonical.startsWith(key)) return name;
166
186
  }
167
187
  return canonical;
168
188
  }
169
- var LITELLM_URL, CACHE_TTL_MS2, WEB_SEARCH_COST, FALLBACK_PRICING, pricingCache, BUILTIN_ALIASES, userAliases;
189
+ function mergeModelCosts(primary, fallback) {
190
+ if (!primary && !fallback) return null;
191
+ if (!primary) return fallback ?? null;
192
+ if (!fallback) return primary;
193
+ return {
194
+ ...fallback,
195
+ ...primary,
196
+ contextTiers: primary.contextTiers ?? fallback.contextTiers
197
+ };
198
+ }
199
+ function findPrefixMatch(entries, canonical) {
200
+ for (const [key, costs] of entries) {
201
+ if (canonical.startsWith(key)) return costs;
202
+ }
203
+ return null;
204
+ }
205
+ function applyContextTier(costs, inputTokens) {
206
+ if (!costs || !costs.contextTiers || costs.contextTiers.length === 0) return costs;
207
+ const tier = costs.contextTiers.filter((candidate) => inputTokens > candidate.minPromptTokens).sort((a, b) => b.minPromptTokens - a.minPromptTokens)[0];
208
+ if (!tier) return costs;
209
+ return {
210
+ ...costs,
211
+ inputCostPerToken: tier.inputCostPerToken ?? costs.inputCostPerToken,
212
+ outputCostPerToken: tier.outputCostPerToken ?? costs.outputCostPerToken,
213
+ cacheWriteCostPerToken: tier.cacheWriteCostPerToken ?? costs.cacheWriteCostPerToken,
214
+ cacheReadCostPerToken: tier.cacheReadCostPerToken ?? costs.cacheReadCostPerToken
215
+ };
216
+ }
217
+ var LITELLM_URL, CACHE_TTL_MS2, WEB_SEARCH_COST, LONG_CONTEXT_1M_THRESHOLD_TOKENS, GEMINI_LONG_CONTEXT_THRESHOLD_TOKENS, DIRECT_PROVIDER_PREFIXES, FALLBACK_PRICING, pricingCache, BUILTIN_ALIASES, userAliases;
170
218
  var init_models = __esm({
171
219
  "src/models.ts"() {
172
220
  "use strict";
@@ -174,6 +222,9 @@ var init_models = __esm({
174
222
  LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
175
223
  CACHE_TTL_MS2 = 24 * 60 * 60 * 1e3;
176
224
  WEB_SEARCH_COST = 0.01;
225
+ LONG_CONTEXT_1M_THRESHOLD_TOKENS = 272e3;
226
+ GEMINI_LONG_CONTEXT_THRESHOLD_TOKENS = 2e5;
227
+ DIRECT_PROVIDER_PREFIXES = ["openai/", "anthropic/", "google/", "vertex_ai/", "minimax/"];
177
228
  FALLBACK_PRICING = {
178
229
  "claude-opus-4-7": { inputCostPerToken: 5e-6, outputCostPerToken: 25e-6, cacheWriteCostPerToken: 625e-8, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 6 },
179
230
  "claude-opus-4-6": { inputCostPerToken: 5e-6, outputCostPerToken: 25e-6, cacheWriteCostPerToken: 625e-8, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 6 },
@@ -189,12 +240,60 @@ var init_models = __esm({
189
240
  "claude-3-5-haiku": { inputCostPerToken: 8e-7, outputCostPerToken: 4e-6, cacheWriteCostPerToken: 1e-6, cacheReadCostPerToken: 8e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
190
241
  "gpt-4o": { inputCostPerToken: 25e-7, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 25e-7, cacheReadCostPerToken: 125e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
191
242
  "gpt-4o-mini": { inputCostPerToken: 15e-8, outputCostPerToken: 6e-7, cacheWriteCostPerToken: 15e-8, cacheReadCostPerToken: 75e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
192
- "gemini-2.5-pro": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 315e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
193
- "gpt-5.3-codex": { inputCostPerToken: 25e-7, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 25e-7, cacheReadCostPerToken: 125e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
194
- "gpt-5.4": { inputCostPerToken: 25e-7, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 25e-7, cacheReadCostPerToken: 125e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
195
- "gpt-5.4-mini": { inputCostPerToken: 4e-7, outputCostPerToken: 16e-7, cacheWriteCostPerToken: 4e-7, cacheReadCostPerToken: 2e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
196
- "gpt-5": { inputCostPerToken: 25e-7, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 25e-7, cacheReadCostPerToken: 125e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
197
- "gpt-5-mini": { inputCostPerToken: 4e-7, outputCostPerToken: 16e-7, cacheWriteCostPerToken: 4e-7, cacheReadCostPerToken: 2e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
243
+ "gemini-2.5-pro": {
244
+ inputCostPerToken: 125e-8,
245
+ outputCostPerToken: 1e-5,
246
+ cacheWriteCostPerToken: 125e-8,
247
+ cacheReadCostPerToken: 125e-9,
248
+ webSearchCostPerRequest: WEB_SEARCH_COST,
249
+ fastMultiplier: 1,
250
+ contextTiers: [{
251
+ minPromptTokens: GEMINI_LONG_CONTEXT_THRESHOLD_TOKENS,
252
+ inputCostPerToken: 25e-7,
253
+ outputCostPerToken: 15e-6,
254
+ cacheReadCostPerToken: 25e-8
255
+ }]
256
+ },
257
+ "gpt-5.5": { inputCostPerToken: 5e-6, outputCostPerToken: 3e-5, cacheWriteCostPerToken: 5e-6, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
258
+ "gpt-5.5-pro": { inputCostPerToken: 3e-5, outputCostPerToken: 18e-5, cacheWriteCostPerToken: 0, cacheReadCostPerToken: 0, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
259
+ "gpt-5.4": {
260
+ inputCostPerToken: 25e-7,
261
+ outputCostPerToken: 15e-6,
262
+ cacheWriteCostPerToken: 25e-7,
263
+ cacheReadCostPerToken: 25e-8,
264
+ webSearchCostPerRequest: WEB_SEARCH_COST,
265
+ fastMultiplier: 1,
266
+ contextTiers: [{
267
+ minPromptTokens: LONG_CONTEXT_1M_THRESHOLD_TOKENS,
268
+ inputCostPerToken: 5e-6,
269
+ outputCostPerToken: 225e-7,
270
+ cacheWriteCostPerToken: 5e-6,
271
+ cacheReadCostPerToken: 5e-7
272
+ }]
273
+ },
274
+ "gpt-5.4-pro": {
275
+ inputCostPerToken: 3e-5,
276
+ outputCostPerToken: 18e-5,
277
+ cacheWriteCostPerToken: 0,
278
+ cacheReadCostPerToken: 0,
279
+ webSearchCostPerRequest: WEB_SEARCH_COST,
280
+ fastMultiplier: 1,
281
+ contextTiers: [{
282
+ minPromptTokens: LONG_CONTEXT_1M_THRESHOLD_TOKENS,
283
+ inputCostPerToken: 6e-5,
284
+ outputCostPerToken: 27e-5
285
+ }]
286
+ },
287
+ "gpt-5.4-mini": { inputCostPerToken: 75e-8, outputCostPerToken: 45e-7, cacheWriteCostPerToken: 75e-8, cacheReadCostPerToken: 75e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
288
+ "gpt-5.4-nano": { inputCostPerToken: 2e-7, outputCostPerToken: 125e-8, cacheWriteCostPerToken: 2e-7, cacheReadCostPerToken: 2e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
289
+ "gpt-5.3-codex": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken: 175e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
290
+ "gpt-5.3-codex-spark": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken: 175e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
291
+ "gpt-5.2": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken: 175e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
292
+ "gpt-5.1": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 125e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
293
+ "gpt-5": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 125e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
294
+ "gpt-5-codex": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 125e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
295
+ "gpt-5-mini": { inputCostPerToken: 25e-8, outputCostPerToken: 2e-6, cacheWriteCostPerToken: 25e-8, cacheReadCostPerToken: 25e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
296
+ "gpt-5-nano": { inputCostPerToken: 5e-8, outputCostPerToken: 4e-7, cacheWriteCostPerToken: 5e-8, cacheReadCostPerToken: 5e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
198
297
  "gpt-4.1": { inputCostPerToken: 2e-6, outputCostPerToken: 8e-6, cacheWriteCostPerToken: 2e-6, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
199
298
  "gpt-4.1-mini": { inputCostPerToken: 4e-7, outputCostPerToken: 16e-7, cacheWriteCostPerToken: 4e-7, cacheReadCostPerToken: 1e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
200
299
  "gpt-4.1-nano": { inputCostPerToken: 1e-7, outputCostPerToken: 4e-7, cacheWriteCostPerToken: 1e-7, cacheReadCostPerToken: 25e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
@@ -202,9 +301,9 @@ var init_models = __esm({
202
301
  "o4-mini": { inputCostPerToken: 11e-7, outputCostPerToken: 44e-7, cacheWriteCostPerToken: 11e-7, cacheReadCostPerToken: 275e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
203
302
  "codex-mini-latest": { inputCostPerToken: 15e-7, outputCostPerToken: 6e-6, cacheWriteCostPerToken: 15e-7, cacheReadCostPerToken: 375e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
204
303
  "codex-mini": { inputCostPerToken: 15e-7, outputCostPerToken: 6e-6, cacheWriteCostPerToken: 15e-7, cacheReadCostPerToken: 375e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
205
- "gpt-5.1-codex": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 625e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
206
- "gpt-5.1-codex-mini": { inputCostPerToken: 25e-8, outputCostPerToken: 2e-6, cacheWriteCostPerToken: 25e-8, cacheReadCostPerToken: 125e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
207
- "gpt-5.2-codex": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken: 875e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
304
+ "gpt-5.1-codex": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 125e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
305
+ "gpt-5.1-codex-mini": { inputCostPerToken: 25e-8, outputCostPerToken: 2e-6, cacheWriteCostPerToken: 25e-8, cacheReadCostPerToken: 25e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
306
+ "gpt-5.2-codex": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken: 175e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
208
307
  "MiniMax-M2.7-highspeed": { inputCostPerToken: 6e-7, outputCostPerToken: 24e-7, cacheWriteCostPerToken: 375e-9, cacheReadCostPerToken: 6e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
209
308
  "MiniMax-M2.7": { inputCostPerToken: 3e-7, outputCostPerToken: 12e-7, cacheWriteCostPerToken: 375e-9, cacheReadCostPerToken: 6e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 }
210
309
  };
@@ -866,7 +965,7 @@ __export(cursor_agent_exports, {
866
965
  createCursorAgentProvider: () => createCursorAgentProvider,
867
966
  cursor_agent: () => cursor_agent
868
967
  });
869
- import { createHash } from "crypto";
968
+ import { createHash as createHash2 } from "crypto";
870
969
  import { existsSync as existsSync2 } from "fs";
871
970
  import { readdir as readdir7, readFile as readFile6, stat as stat9 } from "fs/promises";
872
971
  import { join as join14, basename as basename6 } from "path";
@@ -930,7 +1029,7 @@ function resolveModel3(raw) {
930
1029
  function toConversationId(transcriptPath) {
931
1030
  const filename = basename6(transcriptPath, ".txt");
932
1031
  if (filename.length === 36 && UUID_LIKE.test(filename)) return filename;
933
- return createHash("sha1").update(transcriptPath).digest("hex").slice(0, 16);
1032
+ return createHash2("sha1").update(transcriptPath).digest("hex").slice(0, 16);
934
1033
  }
935
1034
  function extractUserQuery(userBlock) {
936
1035
  const chunks = [];
@@ -1544,6 +1643,9 @@ function formatCost(costUSD) {
1544
1643
  }
1545
1644
 
1546
1645
  // src/day-aggregator.ts
1646
+ function formatDate(date) {
1647
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
1648
+ }
1547
1649
  function emptyEntry(date) {
1548
1650
  return {
1549
1651
  date,
@@ -1561,9 +1663,23 @@ function emptyEntry(date) {
1561
1663
  providers: {}
1562
1664
  };
1563
1665
  }
1666
+ function emptyProviderBreakdown() {
1667
+ return {
1668
+ calls: 0,
1669
+ cost: 0,
1670
+ sessions: 0,
1671
+ inputTokens: 0,
1672
+ outputTokens: 0,
1673
+ cacheReadTokens: 0,
1674
+ cacheWriteTokens: 0,
1675
+ editTurns: 0,
1676
+ oneShotTurns: 0,
1677
+ models: {},
1678
+ categories: {}
1679
+ };
1680
+ }
1564
1681
  function dateKey(iso) {
1565
- const d = new Date(iso);
1566
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1682
+ return formatDate(new Date(iso));
1567
1683
  }
1568
1684
  function aggregateProjectsIntoDays(projects) {
1569
1685
  const byDate = /* @__PURE__ */ new Map();
@@ -1575,10 +1691,20 @@ function aggregateProjectsIntoDays(projects) {
1575
1691
  }
1576
1692
  return d;
1577
1693
  };
1694
+ const ensureProvider = (date, provider) => {
1695
+ const day = ensure(date);
1696
+ let breakdown = day.providers[provider];
1697
+ if (!breakdown) {
1698
+ breakdown = emptyProviderBreakdown();
1699
+ day.providers[provider] = breakdown;
1700
+ }
1701
+ return breakdown;
1702
+ };
1578
1703
  for (const project of projects) {
1579
1704
  for (const session of project.sessions) {
1580
1705
  const sessionDate = dateKey(session.firstTimestamp);
1581
1706
  ensure(sessionDate).sessions += 1;
1707
+ const providersSeenInSession = /* @__PURE__ */ new Set();
1582
1708
  for (const turn of session.turns) {
1583
1709
  if (turn.assistantCalls.length === 0) continue;
1584
1710
  const turnDate = dateKey(turn.assistantCalls[0].timestamp);
@@ -1594,7 +1720,11 @@ function aggregateProjectsIntoDays(projects) {
1594
1720
  cat.editTurns += editTurns;
1595
1721
  cat.oneShotTurns += oneShotTurns;
1596
1722
  turnDay.categories[turn.category] = cat;
1723
+ const callsByProvider = /* @__PURE__ */ new Map();
1597
1724
  for (const call of turn.assistantCalls) {
1725
+ const existingProviderCalls = callsByProvider.get(call.provider) ?? [];
1726
+ existingProviderCalls.push(call);
1727
+ callsByProvider.set(call.provider, existingProviderCalls);
1598
1728
  const callDate = dateKey(call.timestamp);
1599
1729
  const callDay = ensure(callDate);
1600
1730
  callDay.cost += call.costUSD;
@@ -1618,11 +1748,48 @@ function aggregateProjectsIntoDays(projects) {
1618
1748
  model.cacheReadTokens += call.usage.cacheReadInputTokens;
1619
1749
  model.cacheWriteTokens += call.usage.cacheCreationInputTokens;
1620
1750
  callDay.models[call.model] = model;
1621
- const provider = callDay.providers[call.provider] ?? { calls: 0, cost: 0 };
1622
- provider.calls += 1;
1623
- provider.cost += call.costUSD;
1624
- callDay.providers[call.provider] = provider;
1625
1751
  }
1752
+ for (const [provider, calls] of callsByProvider) {
1753
+ providersSeenInSession.add(provider);
1754
+ const providerTurnDay = ensureProvider(turnDate, provider);
1755
+ const providerTurnCost = calls.reduce((sum, call) => sum + call.costUSD, 0);
1756
+ providerTurnDay.editTurns += editTurns;
1757
+ providerTurnDay.oneShotTurns += oneShotTurns;
1758
+ const providerCategory = providerTurnDay.categories[turn.category] ?? { turns: 0, cost: 0, editTurns: 0, oneShotTurns: 0 };
1759
+ providerCategory.turns += 1;
1760
+ providerCategory.cost += providerTurnCost;
1761
+ providerCategory.editTurns += editTurns;
1762
+ providerCategory.oneShotTurns += oneShotTurns;
1763
+ providerTurnDay.categories[turn.category] = providerCategory;
1764
+ for (const call of calls) {
1765
+ const callDate = dateKey(call.timestamp);
1766
+ const providerCallDay = ensureProvider(callDate, provider);
1767
+ providerCallDay.cost += call.costUSD;
1768
+ providerCallDay.calls += 1;
1769
+ providerCallDay.inputTokens += call.usage.inputTokens;
1770
+ providerCallDay.outputTokens += call.usage.outputTokens;
1771
+ providerCallDay.cacheReadTokens += call.usage.cacheReadInputTokens;
1772
+ providerCallDay.cacheWriteTokens += call.usage.cacheCreationInputTokens;
1773
+ const model = providerCallDay.models[call.model] ?? {
1774
+ calls: 0,
1775
+ cost: 0,
1776
+ inputTokens: 0,
1777
+ outputTokens: 0,
1778
+ cacheReadTokens: 0,
1779
+ cacheWriteTokens: 0
1780
+ };
1781
+ model.calls += 1;
1782
+ model.cost += call.costUSD;
1783
+ model.inputTokens += call.usage.inputTokens;
1784
+ model.outputTokens += call.usage.outputTokens;
1785
+ model.cacheReadTokens += call.usage.cacheReadInputTokens;
1786
+ model.cacheWriteTokens += call.usage.cacheCreationInputTokens;
1787
+ providerCallDay.models[call.model] = model;
1788
+ }
1789
+ }
1790
+ }
1791
+ for (const provider of providersSeenInSession) {
1792
+ ensureProvider(sessionDate, provider).sessions += 1;
1626
1793
  }
1627
1794
  }
1628
1795
  }
@@ -1630,27 +1797,41 @@ function aggregateProjectsIntoDays(projects) {
1630
1797
  }
1631
1798
  function filterDaysToProvider(days, provider) {
1632
1799
  return days.map((d) => {
1633
- const p = d.providers[provider];
1634
- if (!p) return null;
1800
+ const p = d.providers[provider] ?? emptyProviderBreakdown();
1635
1801
  return {
1636
1802
  ...d,
1637
1803
  cost: p.cost,
1638
1804
  calls: p.calls,
1639
- // sessions count is not per-provider in cache — approximate at 0
1640
- sessions: 0,
1641
- inputTokens: 0,
1642
- outputTokens: 0,
1643
- cacheReadTokens: 0,
1644
- cacheWriteTokens: 0,
1645
- editTurns: 0,
1646
- oneShotTurns: 0,
1647
- models: {},
1648
- categories: {},
1649
- providers: { [provider]: p }
1805
+ sessions: p.sessions,
1806
+ inputTokens: p.inputTokens,
1807
+ outputTokens: p.outputTokens,
1808
+ cacheReadTokens: p.cacheReadTokens,
1809
+ cacheWriteTokens: p.cacheWriteTokens,
1810
+ editTurns: p.editTurns,
1811
+ oneShotTurns: p.oneShotTurns,
1812
+ models: { ...p.models },
1813
+ categories: { ...p.categories },
1814
+ providers: d.providers[provider] ? { [provider]: p } : {}
1650
1815
  };
1651
- }).filter((d) => d !== null);
1816
+ });
1817
+ }
1818
+ function fillMissingDays(start, end, days) {
1819
+ const byDate = new Map(days.map((day) => [day.date, day]));
1820
+ const filled = [];
1821
+ const cursor2 = new Date(start.getFullYear(), start.getMonth(), start.getDate());
1822
+ const endDate = new Date(end.getFullYear(), end.getMonth(), end.getDate());
1823
+ while (cursor2.getTime() <= endDate.getTime()) {
1824
+ const key = formatDate(cursor2);
1825
+ filled.push(byDate.get(key) ?? emptyEntry(key));
1826
+ cursor2.setDate(cursor2.getDate() + 1);
1827
+ }
1828
+ return filled;
1652
1829
  }
1653
1830
  function buildPeriodDataFromDays(days, label) {
1831
+ if (days.length === 0) {
1832
+ process.stderr.write(`[exe-watcher] WARNING: buildPeriodDataFromDays called with 0 days for label '${label}'
1833
+ `);
1834
+ }
1654
1835
  let cost = 0, calls = 0, sessions = 0;
1655
1836
  let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0, cacheWriteTokens = 0;
1656
1837
  const catTotals = {};
@@ -2188,13 +2369,16 @@ import { basename as basename2, join as join8 } from "path";
2188
2369
  import { homedir as homedir5 } from "os";
2189
2370
  init_models();
2190
2371
  var modelDisplayNames = {
2191
- "gpt-5.3-codex": "GPT-5.3 Codex",
2192
- "gpt-5.4-mini": "GPT-5.4 Mini",
2372
+ "gpt-5.5": "GPT-5.5",
2193
2373
  "gpt-5.4": "GPT-5.4",
2374
+ "gpt-5.4-mini": "GPT-5.4 Mini",
2375
+ "gpt-5.3-codex-spark": "GPT-5.3 Codex Spark",
2376
+ "gpt-5.3-codex": "GPT-5.3 Codex",
2194
2377
  "gpt-5": "GPT-5",
2195
2378
  "gpt-4o-mini": "GPT-4o Mini",
2196
2379
  "gpt-4o": "GPT-4o"
2197
2380
  };
2381
+ var modelDisplayEntries = Object.entries(modelDisplayNames).sort((a, b) => b[0].length - a[0].length);
2198
2382
  var toolNameMap = {
2199
2383
  exec_command: "Bash",
2200
2384
  read_file: "Read",
@@ -2386,7 +2570,7 @@ function createCodexProvider(codexDir) {
2386
2570
  name: "codex",
2387
2571
  displayName: "Codex",
2388
2572
  modelDisplayName(model) {
2389
- for (const [key, name] of Object.entries(modelDisplayNames)) {
2573
+ for (const [key, name] of modelDisplayEntries) {
2390
2574
  if (model.startsWith(key)) return name;
2391
2575
  }
2392
2576
  return model;
@@ -2438,7 +2622,7 @@ var toolNameMap2 = {
2438
2622
  fetch_webpage: "WebFetch",
2439
2623
  github_repo: "GitHub"
2440
2624
  };
2441
- var modelDisplayEntries = Object.entries(modelDisplayNames2).sort((a, b) => b[0].length - a[0].length);
2625
+ var modelDisplayEntries2 = Object.entries(modelDisplayNames2).sort((a, b) => b[0].length - a[0].length);
2442
2626
  function getCopilotSessionStateDir(override) {
2443
2627
  return override ?? join9(homedir6(), ".copilot", "session-state");
2444
2628
  }
@@ -2534,7 +2718,7 @@ function createCopilotProvider(sessionStateDir) {
2534
2718
  name: "copilot",
2535
2719
  displayName: "Copilot",
2536
2720
  modelDisplayName(model) {
2537
- for (const [key, name] of modelDisplayEntries) {
2721
+ for (const [key, name] of modelDisplayEntries2) {
2538
2722
  if (model === key || model.startsWith(key + "-")) return name;
2539
2723
  }
2540
2724
  return model;
@@ -2579,7 +2763,7 @@ var toolNameMap3 = {
2579
2763
  todo: "TodoWrite",
2580
2764
  patch: "Patch"
2581
2765
  };
2582
- var modelDisplayEntries2 = Object.entries(modelDisplayNames3).sort((a, b) => b[0].length - a[0].length);
2766
+ var modelDisplayEntries3 = Object.entries(modelDisplayNames3).sort((a, b) => b[0].length - a[0].length);
2583
2767
  function getPiSessionsDir(override) {
2584
2768
  return override ?? join10(homedir7(), ".pi", "agent", "sessions");
2585
2769
  }
@@ -2701,7 +2885,7 @@ function createPiProvider(sessionsDir) {
2701
2885
  name: "pi",
2702
2886
  displayName: "Pi",
2703
2887
  modelDisplayName(model) {
2704
- for (const [key, name] of modelDisplayEntries2) {
2888
+ for (const [key, name] of modelDisplayEntries3) {
2705
2889
  if (model.startsWith(key)) return name;
2706
2890
  }
2707
2891
  return model;
@@ -2724,7 +2908,7 @@ function createOmpProvider(sessionsDir) {
2724
2908
  name: "omp",
2725
2909
  displayName: "OMP",
2726
2910
  modelDisplayName(model) {
2727
- for (const [key, name] of modelDisplayEntries2) {
2911
+ for (const [key, name] of modelDisplayEntries3) {
2728
2912
  if (model.startsWith(key)) return name;
2729
2913
  }
2730
2914
  return model;
@@ -3309,9 +3493,68 @@ async function parseProviderSources(providerName, sources, seenKeys, dateRange)
3309
3493
  var CACHE_TTL_MS3 = 6e4;
3310
3494
  var MAX_CACHE_ENTRIES = 10;
3311
3495
  var sessionCache = /* @__PURE__ */ new Map();
3312
- function cacheKey(dateRange, providerFilter) {
3496
+ var inFlightSourceContexts = /* @__PURE__ */ new Map();
3497
+ function sourceContextCacheKey(providerFilter) {
3498
+ return JSON.stringify({
3499
+ provider: providerFilter ?? "all",
3500
+ claudeConfigDir: process.env["CLAUDE_CONFIG_DIR"] ?? "",
3501
+ codexHome: process.env["CODEX_HOME"] ?? "",
3502
+ xdgDataHome: process.env["XDG_DATA_HOME"] ?? "",
3503
+ home: process.env["HOME"] ?? ""
3504
+ });
3505
+ }
3506
+ function cacheKey(dateRange, providerFilter, sourceFingerprint = "static") {
3313
3507
  const s = dateRange ? `${dateRange.start.getTime()}:${dateRange.end.getTime()}` : "none";
3314
- return `${s}:${providerFilter ?? "all"}`;
3508
+ return `${s}:${providerFilter ?? "all"}:${sourceFingerprint}`;
3509
+ }
3510
+ async function buildSourceFingerprint(path) {
3511
+ try {
3512
+ const fp = await stat10(path);
3513
+ if (!fp.isDirectory()) return `f:${Math.round(fp.mtimeMs)}:${fp.size}`;
3514
+ const entries = await readdir8(path);
3515
+ const children = await Promise.all(entries.map(async (entry) => stat10(join15(path, entry)).catch(() => null)));
3516
+ let childCount = 0;
3517
+ let childSizeBytes = 0;
3518
+ let newestChildMtimeMs = 0;
3519
+ for (const child of children) {
3520
+ if (!child) continue;
3521
+ childCount += 1;
3522
+ childSizeBytes += child.size;
3523
+ newestChildMtimeMs = Math.max(newestChildMtimeMs, child.mtimeMs);
3524
+ }
3525
+ return `d:${Math.round(fp.mtimeMs)}:${fp.size}:${childCount}:${Math.round(newestChildMtimeMs)}:${childSizeBytes}`;
3526
+ } catch {
3527
+ return "missing";
3528
+ }
3529
+ }
3530
+ async function buildCacheFingerprint(sources) {
3531
+ const parts = await Promise.all(sources.map(async (source) => {
3532
+ const fingerprint = await buildSourceFingerprint(source.path);
3533
+ return `${source.provider}:${source.project}:${source.path}:${fingerprint}`;
3534
+ }));
3535
+ return parts.sort().join("|");
3536
+ }
3537
+ async function getSourceContext(providerFilter) {
3538
+ const key = sourceContextCacheKey(providerFilter);
3539
+ const cached = inFlightSourceContexts.get(key);
3540
+ if (cached) {
3541
+ return cached;
3542
+ }
3543
+ const promise = (async () => {
3544
+ const sources = await discoverAllSessions(providerFilter);
3545
+ const fingerprint = await buildCacheFingerprint(sources);
3546
+ return { sources, fingerprint };
3547
+ })();
3548
+ inFlightSourceContexts.set(key, promise);
3549
+ try {
3550
+ return await promise;
3551
+ } catch (err) {
3552
+ throw err;
3553
+ } finally {
3554
+ if (inFlightSourceContexts.get(key) === promise) {
3555
+ inFlightSourceContexts.delete(key);
3556
+ }
3557
+ }
3315
3558
  }
3316
3559
  function cachePut(key, data) {
3317
3560
  const now = Date.now();
@@ -3345,12 +3588,13 @@ function filterProjectsByName(projects, include, exclude) {
3345
3588
  return result;
3346
3589
  }
3347
3590
  async function parseAllSessions(dateRange, providerFilter) {
3348
- const key = cacheKey(dateRange, providerFilter);
3591
+ const sourceContext = await getSourceContext(providerFilter);
3592
+ const allSources = sourceContext.sources;
3593
+ const key = cacheKey(dateRange, providerFilter, sourceContext.fingerprint);
3349
3594
  const cached = sessionCache.get(key);
3350
3595
  if (cached && Date.now() - cached.ts < CACHE_TTL_MS3) return cached.data;
3351
3596
  const seenMsgIds = /* @__PURE__ */ new Set();
3352
3597
  const seenKeys = /* @__PURE__ */ new Set();
3353
- const allSources = await discoverAllSessions(providerFilter);
3354
3598
  const claudeSources = allSources.filter((s) => s.provider === "claude");
3355
3599
  const nonClaudeSources = allSources.filter((s) => s.provider !== "claude");
3356
3600
  const claudeDirs = claudeSources.map((s) => ({ path: s.path, name: s.project }));
@@ -3537,7 +3781,7 @@ function buildHistory(daily) {
3537
3781
  const trimmed = sorted.slice(-HISTORY_DAYS_LIMIT);
3538
3782
  return { daily: trimmed };
3539
3783
  }
3540
- function buildMenubarPayload(current, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge) {
3784
+ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge, diagnostics) {
3541
3785
  return {
3542
3786
  generated: (/* @__PURE__ */ new Date()).toISOString(),
3543
3787
  current: {
@@ -3555,6 +3799,7 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
3555
3799
  },
3556
3800
  optimize: buildOptimize(optimize),
3557
3801
  history: buildHistory(dailyHistory),
3802
+ diagnostics: diagnostics ?? { daysCount: 0, parseTimeMs: 0, warnings: [] },
3558
3803
  agentStats: agentStats ?? null,
3559
3804
  exeOsDetected: exeOsDetected ?? false,
3560
3805
  statsFileAge: statsFileAge ?? null,
@@ -3564,43 +3809,62 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
3564
3809
 
3565
3810
  // src/daily-cache.ts
3566
3811
  init_cache_dir();
3567
- import { randomBytes as randomBytes5 } from "crypto";
3812
+ import { createHash as createHash3, randomBytes as randomBytes5 } from "crypto";
3568
3813
  import { existsSync as existsSync3 } from "fs";
3569
3814
  import { mkdir as mkdir7, open as open5, readFile as readFile7, rename as rename6, unlink as unlink3 } from "fs/promises";
3570
3815
  import { join as join16 } from "path";
3571
- var DAILY_CACHE_VERSION = 3;
3816
+ var DAILY_CACHE_VERSION = 4;
3817
+ var DEFAULT_DAILY_CACHE_SCOPE = "global";
3572
3818
  var DAILY_CACHE_FILENAME = "daily-cache.json";
3573
- function getCachePath3() {
3574
- return join16(getCacheDir(), DAILY_CACHE_FILENAME);
3819
+ function getCachePath3(scopeKey) {
3820
+ if (scopeKey === DEFAULT_DAILY_CACHE_SCOPE) {
3821
+ return join16(getCacheDir(), DAILY_CACHE_FILENAME);
3822
+ }
3823
+ return join16(getCacheDir(), `daily-cache-${scopeKey}.json`);
3824
+ }
3825
+ function emptyCache(scopeKey) {
3826
+ return { version: DAILY_CACHE_VERSION, scopeKey, lastComputedDate: null, days: [] };
3827
+ }
3828
+ function normalizeScopeTerms(values) {
3829
+ return [...new Set((values ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean))].sort();
3575
3830
  }
3576
- function emptyCache() {
3577
- return { version: DAILY_CACHE_VERSION, lastComputedDate: null, days: [] };
3831
+ function buildDailyCacheScopeKey(include, exclude) {
3832
+ const normalizedInclude = normalizeScopeTerms(include);
3833
+ const normalizedExclude = normalizeScopeTerms(exclude);
3834
+ if (normalizedInclude.length === 0 && normalizedExclude.length === 0) {
3835
+ return DEFAULT_DAILY_CACHE_SCOPE;
3836
+ }
3837
+ const raw = JSON.stringify({ include: normalizedInclude, exclude: normalizedExclude });
3838
+ return `scope-${createHash3("sha1").update(raw).digest("hex").slice(0, 12)}`;
3578
3839
  }
3579
3840
  function isValidCache(parsed) {
3580
3841
  if (!parsed || typeof parsed !== "object") return false;
3581
3842
  const c = parsed;
3582
3843
  if (c.version !== DAILY_CACHE_VERSION) return false;
3844
+ if (typeof c.scopeKey !== "string" || c.scopeKey.length === 0) return false;
3583
3845
  if (!Array.isArray(c.days)) return false;
3584
3846
  return true;
3585
3847
  }
3586
- async function loadDailyCache() {
3587
- const path = getCachePath3();
3588
- if (!existsSync3(path)) return emptyCache();
3848
+ async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
3849
+ const path = getCachePath3(scopeKey);
3850
+ if (!existsSync3(path)) return emptyCache(scopeKey);
3589
3851
  try {
3590
3852
  const raw = await readFile7(path, "utf-8");
3591
3853
  const parsed = JSON.parse(raw);
3592
- if (!isValidCache(parsed)) return emptyCache();
3854
+ if (!isValidCache(parsed)) return emptyCache(scopeKey);
3855
+ if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
3593
3856
  return parsed;
3594
3857
  } catch {
3595
- return emptyCache();
3858
+ return emptyCache(scopeKey);
3596
3859
  }
3597
3860
  }
3598
3861
  async function saveDailyCache(cache) {
3599
3862
  const dir = getCacheDir();
3600
3863
  if (!existsSync3(dir)) await mkdir7(dir, { recursive: true });
3601
- const finalPath = getCachePath3();
3864
+ const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
3865
+ const finalPath = getCachePath3(scopeKey);
3602
3866
  const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
3603
- const payload = JSON.stringify(cache);
3867
+ const payload = JSON.stringify({ ...cache, scopeKey });
3604
3868
  const handle = await open5(tempPath, "w", 384);
3605
3869
  try {
3606
3870
  await handle.writeFile(payload, { encoding: "utf-8" });
@@ -3618,14 +3882,20 @@ async function saveDailyCache(cache) {
3618
3882
  throw err;
3619
3883
  }
3620
3884
  }
3621
- function addNewDays(cache, incoming, newestDate) {
3885
+ function addNewDays(cache, incoming, newestDate, options = {}) {
3622
3886
  const byDate = new Map(cache.days.map((d) => [d.date, d]));
3623
3887
  for (const day of incoming) {
3624
3888
  byDate.set(day.date, day);
3625
3889
  }
3626
3890
  const merged = Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date));
3627
- const nextLast = cache.lastComputedDate && cache.lastComputedDate > newestDate ? cache.lastComputedDate : newestDate;
3628
- return { version: DAILY_CACHE_VERSION, lastComputedDate: nextLast, days: merged };
3891
+ const candidateLast = options.coveredThrough ?? newestDate;
3892
+ const nextLast = cache.lastComputedDate && cache.lastComputedDate > candidateLast ? cache.lastComputedDate : candidateLast;
3893
+ return {
3894
+ version: DAILY_CACHE_VERSION,
3895
+ scopeKey: cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE,
3896
+ lastComputedDate: nextLast,
3897
+ days: merged
3898
+ };
3629
3899
  }
3630
3900
  function getDaysInRange(cache, start, end) {
3631
3901
  return cache.days.filter((d) => d.date >= start && d.date <= end);
@@ -3852,6 +4122,9 @@ async function scanSessions(dateRange) {
3852
4122
  });
3853
4123
  return { toolCalls: allCalls, projectCwds: allCwds, apiCalls: allApiCalls, userMessages: allUserMessages };
3854
4124
  }
4125
+ function prewarmOptimizeScan(dateRange) {
4126
+ return scanSessions(dateRange);
4127
+ }
3855
4128
  function readJsonFile(path) {
3856
4129
  const raw = readSessionFileSync(path);
3857
4130
  if (raw === null) return null;
@@ -4362,7 +4635,7 @@ function cacheKey2(projects, dateRange) {
4362
4635
  const fingerprint = projects.length + ":" + projects.reduce((s, p) => s + p.totalApiCalls, 0);
4363
4636
  return `${dr}:${fingerprint}`;
4364
4637
  }
4365
- async function scanAndDetect(projects, dateRange) {
4638
+ async function scanAndDetect(projects, dateRange, preScanned) {
4366
4639
  if (projects.length === 0) {
4367
4640
  return { findings: [], costRate: 0, healthScore: 100, healthGrade: "A" };
4368
4641
  }
@@ -4370,7 +4643,7 @@ async function scanAndDetect(projects, dateRange) {
4370
4643
  const cached = resultCache.get(key);
4371
4644
  if (cached && Date.now() - cached.ts < RESULT_CACHE_TTL_MS) return cached.data;
4372
4645
  const costRate = computeInputCostRate(projects);
4373
- const { toolCalls, projectCwds, apiCalls, userMessages } = await scanSessions(dateRange);
4646
+ const { toolCalls, projectCwds, apiCalls, userMessages } = preScanned ? await preScanned : await scanSessions(dateRange);
4374
4647
  const findings = [];
4375
4648
  const syncDetectors = [
4376
4649
  () => detectCacheBloat(apiCalls, projects, dateRange),
@@ -4481,13 +4754,13 @@ function renderOptimize(findings, costRate, periodLabel, periodCost, sessionCoun
4481
4754
  lines.push("");
4482
4755
  return lines.join("\n");
4483
4756
  }
4484
- async function runOptimize(projects, periodLabel, dateRange) {
4757
+ async function runOptimize(projects, periodLabel, dateRange, preScanned) {
4485
4758
  if (projects.length === 0) {
4486
4759
  console.log(chalk2.dim("\n No usage data found for this period.\n"));
4487
4760
  return;
4488
4761
  }
4489
4762
  process.stderr.write(chalk2.dim(" Analyzing your sessions...\n"));
4490
- const { findings, costRate, healthScore, healthGrade } = await scanAndDetect(projects, dateRange);
4763
+ const { findings, costRate, healthScore, healthGrade } = await scanAndDetect(projects, dateRange, preScanned);
4491
4764
  const sessions = projects.flatMap((p) => p.sessions);
4492
4765
  const periodCost = projects.reduce((s, p) => s + p.totalCostUSD, 0);
4493
4766
  const callCount = projects.reduce((s, p) => s + p.totalApiCalls, 0);
@@ -6498,7 +6771,7 @@ function getDateRange2(period) {
6498
6771
  return { range: { start, end: yesterdayEnd }, label: `Yesterday (${toDateString(start)})` };
6499
6772
  }
6500
6773
  case "week": {
6501
- const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
6774
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
6502
6775
  return { range: { start, end }, label: "Last 7 Days" };
6503
6776
  }
6504
6777
  case "month": {
@@ -6506,7 +6779,7 @@ function getDateRange2(period) {
6506
6779
  return { range: { start, end }, label: `${now.toLocaleString("default", { month: "long" })} ${now.getFullYear()}` };
6507
6780
  }
6508
6781
  case "30days": {
6509
- const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30);
6782
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 29);
6510
6783
  return { range: { start, end }, label: "Last 30 Days" };
6511
6784
  }
6512
6785
  case "all": {
@@ -6514,7 +6787,7 @@ function getDateRange2(period) {
6514
6787
  return { range: { start, end }, label: "Last 6 months" };
6515
6788
  }
6516
6789
  default: {
6517
- const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
6790
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
6518
6791
  return { range: { start, end }, label: "Last 7 Days" };
6519
6792
  }
6520
6793
  }
@@ -6764,8 +7037,9 @@ program.command("status").description("Compact status output (today + week + mon
6764
7037
  const yesterdayEnd = new Date(todayStart.getTime() - 1);
6765
7038
  const yesterdayStr = toDateString(new Date(todayStart.getTime() - MS_PER_DAY3));
6766
7039
  const isAllProviders = pf === "all";
7040
+ const dailyCacheScope = buildDailyCacheScopeKey(opts.project, opts.exclude);
6767
7041
  const cache = await withDailyCacheLock(async () => {
6768
- let c = await loadDailyCache();
7042
+ let c = await loadDailyCache(dailyCacheScope);
6769
7043
  const hadYesterday = c.days.some((d) => d.date >= yesterdayStr);
6770
7044
  if (hadYesterday) {
6771
7045
  const freshDays = c.days.filter((d) => d.date < yesterdayStr);
@@ -6780,32 +7054,53 @@ program.command("status").description("Compact status output (today + week + mon
6780
7054
  if (gapStart.getTime() <= yesterdayEnd.getTime()) {
6781
7055
  const gapRange = { start: gapStart, end: yesterdayEnd };
6782
7056
  const gapProjects = filterProjectsByName(await parseAllSessions(gapRange, "all"), opts.project, opts.exclude);
6783
- const gapDays = aggregateProjectsIntoDays(gapProjects);
6784
- c = addNewDays(c, gapDays, yesterdayStr);
7057
+ const gapDays = fillMissingDays(gapRange.start, gapRange.end, aggregateProjectsIntoDays(gapProjects));
7058
+ c = addNewDays(c, gapDays, yesterdayStr, { coveredThrough: yesterdayStr });
6785
7059
  await saveDailyCache(c);
6786
7060
  }
6787
7061
  return c;
6788
7062
  });
6789
7063
  let currentData;
6790
- let scanProjects;
6791
- let scanRange;
7064
+ const scanRange = periodInfo.range;
6792
7065
  const todayRange = { start: todayStart, end: periodInfo.range.end };
6793
- const todayProjects = fp(await parseAllSessions(todayRange, "all"));
7066
+ const warnings = [];
7067
+ const parseStart = Date.now();
7068
+ let todayProjects;
7069
+ let allTodayProjects;
7070
+ try {
7071
+ allTodayProjects = await parseAllSessions(todayRange, "all");
7072
+ todayProjects = fp(allTodayProjects);
7073
+ } catch (err) {
7074
+ const msg = err instanceof Error ? err.message : String(err);
7075
+ const warn2 = `parseAllSessions failed: ${msg}`;
7076
+ warnings.push(warn2);
7077
+ process.stderr.write(`[exe-watcher] WARNING: ${warn2}
7078
+ `);
7079
+ allTodayProjects = [];
7080
+ todayProjects = [];
7081
+ }
7082
+ if (todayProjects.length === 0) {
7083
+ const warn2 = `parseAllSessions returned 0 projects for range ${toDateString(todayRange.start)}..${toDateString(todayRange.end)}`;
7084
+ warnings.push(warn2);
7085
+ process.stderr.write(`[exe-watcher] WARNING: ${warn2}
7086
+ `);
7087
+ }
7088
+ const parseTimeMs = Date.now() - parseStart;
6794
7089
  const todayDays = aggregateProjectsIntoDays(todayProjects);
7090
+ let daysCount = 0;
6795
7091
  {
6796
7092
  const rangeStartStr = toDateString(periodInfo.range.start);
6797
7093
  const rangeEndStr = toDateString(periodInfo.range.end);
6798
7094
  const historicalDays = getDaysInRange(cache, rangeStartStr, yesterdayStr);
6799
7095
  const todayOnly = todayDays.filter((d) => d.date > yesterdayStr && d.date <= rangeEndStr);
6800
7096
  const allDays = [...historicalDays, ...todayOnly].sort((a, b) => a.date.localeCompare(b.date));
7097
+ daysCount = allDays.length;
6801
7098
  if (isAllProviders) {
6802
7099
  currentData = buildPeriodDataFromDays(allDays, periodInfo.label);
6803
7100
  } else {
6804
7101
  const providerDays = filterDaysToProvider(allDays, pf);
6805
7102
  currentData = buildPeriodDataFromDays(providerDays, periodInfo.label);
6806
7103
  }
6807
- scanProjects = todayProjects;
6808
- scanRange = periodInfo.range;
6809
7104
  }
6810
7105
  const allProviders = await getAllProviders();
6811
7106
  const displayNameByName = new Map(allProviders.map((p) => [p.name, p.displayName]));
@@ -6816,6 +7111,12 @@ program.command("status").description("Compact status output (today + week + mon
6816
7111
  ...getDaysInRange(cache, rangeStartStr, yesterdayStr),
6817
7112
  ...todayDays.filter((d) => d.date > yesterdayStr)
6818
7113
  ];
7114
+ if (allDaysForProviders.length === 0) {
7115
+ const warn2 = "allDaysForProviders is empty \u2014 all providers will show $0";
7116
+ warnings.push(warn2);
7117
+ process.stderr.write(`[exe-watcher] WARNING: ${warn2}
7118
+ `);
7119
+ }
6819
7120
  const providerTotals = {};
6820
7121
  for (const d of allDaysForProviders) {
6821
7122
  for (const [name, p] of Object.entries(d.providers)) {
@@ -6839,7 +7140,7 @@ program.command("status").description("Compact status output (today + week + mon
6839
7140
  const fullHistory = [...allCacheDays, ...todayDays.filter((d) => d.date > yesterdayStr)];
6840
7141
  const dailyHistory = fullHistory.map((d) => {
6841
7142
  if (isAllProviders) {
6842
- const topModels = Object.entries(d.models).filter(([name]) => name !== "<synthetic>").sort(([, a], [, b]) => b.cost - a.cost).slice(0, 5).map(([name, m]) => ({
7143
+ const topModels2 = Object.entries(d.models).filter(([name]) => name !== "<synthetic>").sort(([, a], [, b]) => b.cost - a.cost).slice(0, 5).map(([name, m]) => ({
6843
7144
  name,
6844
7145
  cost: m.cost,
6845
7146
  calls: m.calls,
@@ -6854,22 +7155,42 @@ program.command("status").description("Compact status output (today + week + mon
6854
7155
  outputTokens: d.outputTokens,
6855
7156
  cacheReadTokens: d.cacheReadTokens,
6856
7157
  cacheWriteTokens: d.cacheWriteTokens,
6857
- topModels
7158
+ topModels: topModels2
6858
7159
  };
6859
7160
  }
6860
- const prov = d.providers[pf] ?? { calls: 0, cost: 0 };
7161
+ const prov = d.providers[pf];
7162
+ const topModels = Object.entries(prov?.models ?? {}).filter(([name]) => name !== "<synthetic>").sort(([, a], [, b]) => b.cost - a.cost).slice(0, 5).map(([name, m]) => ({
7163
+ name,
7164
+ cost: m.cost,
7165
+ calls: m.calls,
7166
+ inputTokens: m.inputTokens,
7167
+ outputTokens: m.outputTokens
7168
+ }));
6861
7169
  return {
6862
7170
  date: d.date,
6863
- cost: prov.cost,
6864
- calls: prov.calls,
6865
- inputTokens: 0,
6866
- outputTokens: 0,
6867
- cacheReadTokens: 0,
6868
- cacheWriteTokens: 0,
6869
- topModels: []
7171
+ cost: prov?.cost ?? 0,
7172
+ calls: prov?.calls ?? 0,
7173
+ inputTokens: prov?.inputTokens ?? 0,
7174
+ outputTokens: prov?.outputTokens ?? 0,
7175
+ cacheReadTokens: prov?.cacheReadTokens ?? 0,
7176
+ cacheWriteTokens: prov?.cacheWriteTokens ?? 0,
7177
+ topModels
6870
7178
  };
6871
7179
  });
6872
- const optimize = opts.optimize === false ? null : await scanAndDetect(scanProjects, scanRange);
7180
+ let optimize = null;
7181
+ if (opts.optimize !== false) {
7182
+ try {
7183
+ const optimizeProjects = isAllProviders && opts.period === "today" ? todayProjects : fp(await parseAllSessions(scanRange, isAllProviders ? "all" : pf));
7184
+ optimize = await scanAndDetect(optimizeProjects, scanRange);
7185
+ } catch (err) {
7186
+ const msg = err instanceof Error ? err.message : String(err);
7187
+ const warn2 = `optimize parse failed: ${msg}`;
7188
+ warnings.push(warn2);
7189
+ process.stderr.write(`[exe-watcher] WARNING: ${warn2}
7190
+ `);
7191
+ optimize = await scanAndDetect(todayProjects, scanRange);
7192
+ }
7193
+ }
6873
7194
  const exeOsDir = join21(homedir14(), ".exe-os");
6874
7195
  const exeOsDetected = existsSync6(exeOsDir);
6875
7196
  let agentStats = null;
@@ -6893,13 +7214,13 @@ program.command("status").description("Compact status output (today + week + mon
6893
7214
  if (agentStats) {
6894
7215
  agentStats = estimateAgentCosts(agentStats);
6895
7216
  }
6896
- const [proj24h, proj7d, proj30d] = await Promise.all([
6897
- parseAllSessions(getDateRange2("today").range, "all"),
7217
+ const [proj7d, proj30d] = await Promise.all([
6898
7218
  parseAllSessions(getDateRange2("week").range, "all"),
6899
7219
  parseAllSessions(getDateRange2("30days").range, "all")
6900
7220
  ]);
6901
- const projectSpend = buildProjectSpend(proj24h, proj7d, proj30d);
6902
- console.log(JSON.stringify(buildMenubarPayload(currentData, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge)));
7221
+ const projectSpend = buildProjectSpend(allTodayProjects, proj7d, proj30d);
7222
+ const diagnostics = { daysCount, parseTimeMs, warnings };
7223
+ console.log(JSON.stringify(buildMenubarPayload(currentData, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge, diagnostics)));
6903
7224
  return;
6904
7225
  }
6905
7226
  if (opts.format === "json") {
@@ -7197,8 +7518,9 @@ program.command("plan [action] [id]").description("Show or configure a subscript
7197
7518
  program.command("optimize").description("Find token waste and get exact fixes").option("-p, --period <period>", "Analysis period: today, week, 30days, month, all", "30days").option("--provider <provider>", "Filter by provider: all, claude, codex, cursor", "all").action(async (opts) => {
7198
7519
  await loadPricing();
7199
7520
  const { range, label } = getDateRange2(opts.period);
7521
+ const scanPromise = prewarmOptimizeScan(range);
7200
7522
  const projects = await parseAllSessions(range, opts.provider);
7201
- await runOptimize(projects, label, range);
7523
+ await runOptimize(projects, label, range, scanPromise);
7202
7524
  });
7203
7525
  program.command("compare").description("Compare two AI models side-by-side").option("-p, --period <period>", "Analysis period: today, week, 30days, month, all", "all").option("--provider <provider>", "Filter by provider: all, claude, codex, cursor", "all").action(async (opts) => {
7204
7526
  await loadPricing();