exe-watcher 0.2.2 → 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);
@@ -151,10 +161,17 @@ function getShortModelName(model) {
151
161
  "gpt-4.1-mini": "GPT-4.1 Mini",
152
162
  "gpt-4.1": "GPT-4.1",
153
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",
154
167
  "gpt-5.4-mini": "GPT-5.4 Mini",
155
168
  "gpt-5.4": "GPT-5.4",
156
169
  "gpt-5.3-codex-spark": "GPT-5.3 Codex Spark",
157
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",
158
175
  "gpt-5-mini": "GPT-5 Mini",
159
176
  "gpt-5": "GPT-5",
160
177
  "gemini-2.5-pro": "Gemini 2.5 Pro",
@@ -163,12 +180,41 @@ function getShortModelName(model) {
163
180
  "MiniMax-M2.7-highspeed": "MiniMax M2.7 Highspeed",
164
181
  "MiniMax-M2.7": "MiniMax M2.7"
165
182
  };
166
- 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)) {
167
185
  if (canonical.startsWith(key)) return name;
168
186
  }
169
187
  return canonical;
170
188
  }
171
- 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;
172
218
  var init_models = __esm({
173
219
  "src/models.ts"() {
174
220
  "use strict";
@@ -176,6 +222,9 @@ var init_models = __esm({
176
222
  LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
177
223
  CACHE_TTL_MS2 = 24 * 60 * 60 * 1e3;
178
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/"];
179
228
  FALLBACK_PRICING = {
180
229
  "claude-opus-4-7": { inputCostPerToken: 5e-6, outputCostPerToken: 25e-6, cacheWriteCostPerToken: 625e-8, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 6 },
181
230
  "claude-opus-4-6": { inputCostPerToken: 5e-6, outputCostPerToken: 25e-6, cacheWriteCostPerToken: 625e-8, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 6 },
@@ -191,14 +240,60 @@ var init_models = __esm({
191
240
  "claude-3-5-haiku": { inputCostPerToken: 8e-7, outputCostPerToken: 4e-6, cacheWriteCostPerToken: 1e-6, cacheReadCostPerToken: 8e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
192
241
  "gpt-4o": { inputCostPerToken: 25e-7, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 25e-7, cacheReadCostPerToken: 125e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
193
242
  "gpt-4o-mini": { inputCostPerToken: 15e-8, outputCostPerToken: 6e-7, cacheWriteCostPerToken: 15e-8, cacheReadCostPerToken: 75e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
194
- "gemini-2.5-pro": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 315e-9, 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
+ },
195
257
  "gpt-5.5": { inputCostPerToken: 5e-6, outputCostPerToken: 3e-5, cacheWriteCostPerToken: 5e-6, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
196
- "gpt-5.4": { inputCostPerToken: 25e-7, outputCostPerToken: 15e-6, cacheWriteCostPerToken: 25e-7, cacheReadCostPerToken: 25e-8, 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
+ },
197
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 },
198
289
  "gpt-5.3-codex": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken: 175e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
199
290
  "gpt-5.3-codex-spark": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken: 175e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
200
- "gpt-5": { inputCostPerToken: 25e-7, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 25e-7, cacheReadCostPerToken: 125e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
201
- "gpt-5-mini": { inputCostPerToken: 4e-7, outputCostPerToken: 16e-7, cacheWriteCostPerToken: 4e-7, cacheReadCostPerToken: 2e-7, 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 },
202
297
  "gpt-4.1": { inputCostPerToken: 2e-6, outputCostPerToken: 8e-6, cacheWriteCostPerToken: 2e-6, cacheReadCostPerToken: 5e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
203
298
  "gpt-4.1-mini": { inputCostPerToken: 4e-7, outputCostPerToken: 16e-7, cacheWriteCostPerToken: 4e-7, cacheReadCostPerToken: 1e-7, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
204
299
  "gpt-4.1-nano": { inputCostPerToken: 1e-7, outputCostPerToken: 4e-7, cacheWriteCostPerToken: 1e-7, cacheReadCostPerToken: 25e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
@@ -206,9 +301,9 @@ var init_models = __esm({
206
301
  "o4-mini": { inputCostPerToken: 11e-7, outputCostPerToken: 44e-7, cacheWriteCostPerToken: 11e-7, cacheReadCostPerToken: 275e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
207
302
  "codex-mini-latest": { inputCostPerToken: 15e-7, outputCostPerToken: 6e-6, cacheWriteCostPerToken: 15e-7, cacheReadCostPerToken: 375e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
208
303
  "codex-mini": { inputCostPerToken: 15e-7, outputCostPerToken: 6e-6, cacheWriteCostPerToken: 15e-7, cacheReadCostPerToken: 375e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
209
- "gpt-5.1-codex": { inputCostPerToken: 125e-8, outputCostPerToken: 1e-5, cacheWriteCostPerToken: 125e-8, cacheReadCostPerToken: 625e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
210
- "gpt-5.1-codex-mini": { inputCostPerToken: 25e-8, outputCostPerToken: 2e-6, cacheWriteCostPerToken: 25e-8, cacheReadCostPerToken: 125e-9, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
211
- "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 },
212
307
  "MiniMax-M2.7-highspeed": { inputCostPerToken: 6e-7, outputCostPerToken: 24e-7, cacheWriteCostPerToken: 375e-9, cacheReadCostPerToken: 6e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 },
213
308
  "MiniMax-M2.7": { inputCostPerToken: 3e-7, outputCostPerToken: 12e-7, cacheWriteCostPerToken: 375e-9, cacheReadCostPerToken: 6e-8, webSearchCostPerRequest: WEB_SEARCH_COST, fastMultiplier: 1 }
214
309
  };
@@ -870,7 +965,7 @@ __export(cursor_agent_exports, {
870
965
  createCursorAgentProvider: () => createCursorAgentProvider,
871
966
  cursor_agent: () => cursor_agent
872
967
  });
873
- import { createHash } from "crypto";
968
+ import { createHash as createHash2 } from "crypto";
874
969
  import { existsSync as existsSync2 } from "fs";
875
970
  import { readdir as readdir7, readFile as readFile6, stat as stat9 } from "fs/promises";
876
971
  import { join as join14, basename as basename6 } from "path";
@@ -934,7 +1029,7 @@ function resolveModel3(raw) {
934
1029
  function toConversationId(transcriptPath) {
935
1030
  const filename = basename6(transcriptPath, ".txt");
936
1031
  if (filename.length === 36 && UUID_LIKE.test(filename)) return filename;
937
- return createHash("sha1").update(transcriptPath).digest("hex").slice(0, 16);
1032
+ return createHash2("sha1").update(transcriptPath).digest("hex").slice(0, 16);
938
1033
  }
939
1034
  function extractUserQuery(userBlock) {
940
1035
  const chunks = [];
@@ -1548,6 +1643,9 @@ function formatCost(costUSD) {
1548
1643
  }
1549
1644
 
1550
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
+ }
1551
1649
  function emptyEntry(date) {
1552
1650
  return {
1553
1651
  date,
@@ -1565,9 +1663,23 @@ function emptyEntry(date) {
1565
1663
  providers: {}
1566
1664
  };
1567
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
+ }
1568
1681
  function dateKey(iso) {
1569
- const d = new Date(iso);
1570
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1682
+ return formatDate(new Date(iso));
1571
1683
  }
1572
1684
  function aggregateProjectsIntoDays(projects) {
1573
1685
  const byDate = /* @__PURE__ */ new Map();
@@ -1579,10 +1691,20 @@ function aggregateProjectsIntoDays(projects) {
1579
1691
  }
1580
1692
  return d;
1581
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
+ };
1582
1703
  for (const project of projects) {
1583
1704
  for (const session of project.sessions) {
1584
1705
  const sessionDate = dateKey(session.firstTimestamp);
1585
1706
  ensure(sessionDate).sessions += 1;
1707
+ const providersSeenInSession = /* @__PURE__ */ new Set();
1586
1708
  for (const turn of session.turns) {
1587
1709
  if (turn.assistantCalls.length === 0) continue;
1588
1710
  const turnDate = dateKey(turn.assistantCalls[0].timestamp);
@@ -1598,7 +1720,11 @@ function aggregateProjectsIntoDays(projects) {
1598
1720
  cat.editTurns += editTurns;
1599
1721
  cat.oneShotTurns += oneShotTurns;
1600
1722
  turnDay.categories[turn.category] = cat;
1723
+ const callsByProvider = /* @__PURE__ */ new Map();
1601
1724
  for (const call of turn.assistantCalls) {
1725
+ const existingProviderCalls = callsByProvider.get(call.provider) ?? [];
1726
+ existingProviderCalls.push(call);
1727
+ callsByProvider.set(call.provider, existingProviderCalls);
1602
1728
  const callDate = dateKey(call.timestamp);
1603
1729
  const callDay = ensure(callDate);
1604
1730
  callDay.cost += call.costUSD;
@@ -1622,39 +1748,90 @@ function aggregateProjectsIntoDays(projects) {
1622
1748
  model.cacheReadTokens += call.usage.cacheReadInputTokens;
1623
1749
  model.cacheWriteTokens += call.usage.cacheCreationInputTokens;
1624
1750
  callDay.models[call.model] = model;
1625
- const provider = callDay.providers[call.provider] ?? { calls: 0, cost: 0 };
1626
- provider.calls += 1;
1627
- provider.cost += call.costUSD;
1628
- callDay.providers[call.provider] = provider;
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
+ }
1629
1789
  }
1630
1790
  }
1791
+ for (const provider of providersSeenInSession) {
1792
+ ensureProvider(sessionDate, provider).sessions += 1;
1793
+ }
1631
1794
  }
1632
1795
  }
1633
1796
  return [...byDate.values()].sort((a, b) => a.date.localeCompare(b.date));
1634
1797
  }
1635
1798
  function filterDaysToProvider(days, provider) {
1636
1799
  return days.map((d) => {
1637
- const p = d.providers[provider];
1638
- if (!p) return null;
1800
+ const p = d.providers[provider] ?? emptyProviderBreakdown();
1639
1801
  return {
1640
1802
  ...d,
1641
1803
  cost: p.cost,
1642
1804
  calls: p.calls,
1643
- // sessions count is not per-provider in cache — approximate at 0
1644
- sessions: 0,
1645
- inputTokens: 0,
1646
- outputTokens: 0,
1647
- cacheReadTokens: 0,
1648
- cacheWriteTokens: 0,
1649
- editTurns: 0,
1650
- oneShotTurns: 0,
1651
- models: {},
1652
- categories: {},
1653
- 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 } : {}
1654
1815
  };
1655
- }).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;
1656
1829
  }
1657
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
+ }
1658
1835
  let cost = 0, calls = 0, sessions = 0;
1659
1836
  let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0, cacheWriteTokens = 0;
1660
1837
  const catTotals = {};
@@ -2201,6 +2378,7 @@ var modelDisplayNames = {
2201
2378
  "gpt-4o-mini": "GPT-4o Mini",
2202
2379
  "gpt-4o": "GPT-4o"
2203
2380
  };
2381
+ var modelDisplayEntries = Object.entries(modelDisplayNames).sort((a, b) => b[0].length - a[0].length);
2204
2382
  var toolNameMap = {
2205
2383
  exec_command: "Bash",
2206
2384
  read_file: "Read",
@@ -2392,7 +2570,7 @@ function createCodexProvider(codexDir) {
2392
2570
  name: "codex",
2393
2571
  displayName: "Codex",
2394
2572
  modelDisplayName(model) {
2395
- for (const [key, name] of Object.entries(modelDisplayNames)) {
2573
+ for (const [key, name] of modelDisplayEntries) {
2396
2574
  if (model.startsWith(key)) return name;
2397
2575
  }
2398
2576
  return model;
@@ -2444,7 +2622,7 @@ var toolNameMap2 = {
2444
2622
  fetch_webpage: "WebFetch",
2445
2623
  github_repo: "GitHub"
2446
2624
  };
2447
- 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);
2448
2626
  function getCopilotSessionStateDir(override) {
2449
2627
  return override ?? join9(homedir6(), ".copilot", "session-state");
2450
2628
  }
@@ -2540,7 +2718,7 @@ function createCopilotProvider(sessionStateDir) {
2540
2718
  name: "copilot",
2541
2719
  displayName: "Copilot",
2542
2720
  modelDisplayName(model) {
2543
- for (const [key, name] of modelDisplayEntries) {
2721
+ for (const [key, name] of modelDisplayEntries2) {
2544
2722
  if (model === key || model.startsWith(key + "-")) return name;
2545
2723
  }
2546
2724
  return model;
@@ -2585,7 +2763,7 @@ var toolNameMap3 = {
2585
2763
  todo: "TodoWrite",
2586
2764
  patch: "Patch"
2587
2765
  };
2588
- 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);
2589
2767
  function getPiSessionsDir(override) {
2590
2768
  return override ?? join10(homedir7(), ".pi", "agent", "sessions");
2591
2769
  }
@@ -2707,7 +2885,7 @@ function createPiProvider(sessionsDir) {
2707
2885
  name: "pi",
2708
2886
  displayName: "Pi",
2709
2887
  modelDisplayName(model) {
2710
- for (const [key, name] of modelDisplayEntries2) {
2888
+ for (const [key, name] of modelDisplayEntries3) {
2711
2889
  if (model.startsWith(key)) return name;
2712
2890
  }
2713
2891
  return model;
@@ -2730,7 +2908,7 @@ function createOmpProvider(sessionsDir) {
2730
2908
  name: "omp",
2731
2909
  displayName: "OMP",
2732
2910
  modelDisplayName(model) {
2733
- for (const [key, name] of modelDisplayEntries2) {
2911
+ for (const [key, name] of modelDisplayEntries3) {
2734
2912
  if (model.startsWith(key)) return name;
2735
2913
  }
2736
2914
  return model;
@@ -3315,9 +3493,68 @@ async function parseProviderSources(providerName, sources, seenKeys, dateRange)
3315
3493
  var CACHE_TTL_MS3 = 6e4;
3316
3494
  var MAX_CACHE_ENTRIES = 10;
3317
3495
  var sessionCache = /* @__PURE__ */ new Map();
3318
- 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") {
3319
3507
  const s = dateRange ? `${dateRange.start.getTime()}:${dateRange.end.getTime()}` : "none";
3320
- 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
+ }
3321
3558
  }
3322
3559
  function cachePut(key, data) {
3323
3560
  const now = Date.now();
@@ -3351,12 +3588,13 @@ function filterProjectsByName(projects, include, exclude) {
3351
3588
  return result;
3352
3589
  }
3353
3590
  async function parseAllSessions(dateRange, providerFilter) {
3354
- const key = cacheKey(dateRange, providerFilter);
3591
+ const sourceContext = await getSourceContext(providerFilter);
3592
+ const allSources = sourceContext.sources;
3593
+ const key = cacheKey(dateRange, providerFilter, sourceContext.fingerprint);
3355
3594
  const cached = sessionCache.get(key);
3356
3595
  if (cached && Date.now() - cached.ts < CACHE_TTL_MS3) return cached.data;
3357
3596
  const seenMsgIds = /* @__PURE__ */ new Set();
3358
3597
  const seenKeys = /* @__PURE__ */ new Set();
3359
- const allSources = await discoverAllSessions(providerFilter);
3360
3598
  const claudeSources = allSources.filter((s) => s.provider === "claude");
3361
3599
  const nonClaudeSources = allSources.filter((s) => s.provider !== "claude");
3362
3600
  const claudeDirs = claudeSources.map((s) => ({ path: s.path, name: s.project }));
@@ -3543,7 +3781,7 @@ function buildHistory(daily) {
3543
3781
  const trimmed = sorted.slice(-HISTORY_DAYS_LIMIT);
3544
3782
  return { daily: trimmed };
3545
3783
  }
3546
- function buildMenubarPayload(current, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge) {
3784
+ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge, diagnostics) {
3547
3785
  return {
3548
3786
  generated: (/* @__PURE__ */ new Date()).toISOString(),
3549
3787
  current: {
@@ -3561,6 +3799,7 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
3561
3799
  },
3562
3800
  optimize: buildOptimize(optimize),
3563
3801
  history: buildHistory(dailyHistory),
3802
+ diagnostics: diagnostics ?? { daysCount: 0, parseTimeMs: 0, warnings: [] },
3564
3803
  agentStats: agentStats ?? null,
3565
3804
  exeOsDetected: exeOsDetected ?? false,
3566
3805
  statsFileAge: statsFileAge ?? null,
@@ -3570,43 +3809,62 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
3570
3809
 
3571
3810
  // src/daily-cache.ts
3572
3811
  init_cache_dir();
3573
- import { randomBytes as randomBytes5 } from "crypto";
3812
+ import { createHash as createHash3, randomBytes as randomBytes5 } from "crypto";
3574
3813
  import { existsSync as existsSync3 } from "fs";
3575
3814
  import { mkdir as mkdir7, open as open5, readFile as readFile7, rename as rename6, unlink as unlink3 } from "fs/promises";
3576
3815
  import { join as join16 } from "path";
3577
- var DAILY_CACHE_VERSION = 3;
3816
+ var DAILY_CACHE_VERSION = 4;
3817
+ var DEFAULT_DAILY_CACHE_SCOPE = "global";
3578
3818
  var DAILY_CACHE_FILENAME = "daily-cache.json";
3579
- function getCachePath3() {
3580
- 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`);
3581
3824
  }
3582
- function emptyCache() {
3583
- return { version: DAILY_CACHE_VERSION, lastComputedDate: null, days: [] };
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();
3830
+ }
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)}`;
3584
3839
  }
3585
3840
  function isValidCache(parsed) {
3586
3841
  if (!parsed || typeof parsed !== "object") return false;
3587
3842
  const c = parsed;
3588
3843
  if (c.version !== DAILY_CACHE_VERSION) return false;
3844
+ if (typeof c.scopeKey !== "string" || c.scopeKey.length === 0) return false;
3589
3845
  if (!Array.isArray(c.days)) return false;
3590
3846
  return true;
3591
3847
  }
3592
- async function loadDailyCache() {
3593
- const path = getCachePath3();
3594
- 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);
3595
3851
  try {
3596
3852
  const raw = await readFile7(path, "utf-8");
3597
3853
  const parsed = JSON.parse(raw);
3598
- if (!isValidCache(parsed)) return emptyCache();
3854
+ if (!isValidCache(parsed)) return emptyCache(scopeKey);
3855
+ if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
3599
3856
  return parsed;
3600
3857
  } catch {
3601
- return emptyCache();
3858
+ return emptyCache(scopeKey);
3602
3859
  }
3603
3860
  }
3604
3861
  async function saveDailyCache(cache) {
3605
3862
  const dir = getCacheDir();
3606
3863
  if (!existsSync3(dir)) await mkdir7(dir, { recursive: true });
3607
- const finalPath = getCachePath3();
3864
+ const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
3865
+ const finalPath = getCachePath3(scopeKey);
3608
3866
  const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
3609
- const payload = JSON.stringify(cache);
3867
+ const payload = JSON.stringify({ ...cache, scopeKey });
3610
3868
  const handle = await open5(tempPath, "w", 384);
3611
3869
  try {
3612
3870
  await handle.writeFile(payload, { encoding: "utf-8" });
@@ -3624,14 +3882,20 @@ async function saveDailyCache(cache) {
3624
3882
  throw err;
3625
3883
  }
3626
3884
  }
3627
- function addNewDays(cache, incoming, newestDate) {
3885
+ function addNewDays(cache, incoming, newestDate, options = {}) {
3628
3886
  const byDate = new Map(cache.days.map((d) => [d.date, d]));
3629
3887
  for (const day of incoming) {
3630
3888
  byDate.set(day.date, day);
3631
3889
  }
3632
3890
  const merged = Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date));
3633
- const nextLast = cache.lastComputedDate && cache.lastComputedDate > newestDate ? cache.lastComputedDate : newestDate;
3634
- 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
+ };
3635
3899
  }
3636
3900
  function getDaysInRange(cache, start, end) {
3637
3901
  return cache.days.filter((d) => d.date >= start && d.date <= end);
@@ -3858,6 +4122,9 @@ async function scanSessions(dateRange) {
3858
4122
  });
3859
4123
  return { toolCalls: allCalls, projectCwds: allCwds, apiCalls: allApiCalls, userMessages: allUserMessages };
3860
4124
  }
4125
+ function prewarmOptimizeScan(dateRange) {
4126
+ return scanSessions(dateRange);
4127
+ }
3861
4128
  function readJsonFile(path) {
3862
4129
  const raw = readSessionFileSync(path);
3863
4130
  if (raw === null) return null;
@@ -4368,7 +4635,7 @@ function cacheKey2(projects, dateRange) {
4368
4635
  const fingerprint = projects.length + ":" + projects.reduce((s, p) => s + p.totalApiCalls, 0);
4369
4636
  return `${dr}:${fingerprint}`;
4370
4637
  }
4371
- async function scanAndDetect(projects, dateRange) {
4638
+ async function scanAndDetect(projects, dateRange, preScanned) {
4372
4639
  if (projects.length === 0) {
4373
4640
  return { findings: [], costRate: 0, healthScore: 100, healthGrade: "A" };
4374
4641
  }
@@ -4376,7 +4643,7 @@ async function scanAndDetect(projects, dateRange) {
4376
4643
  const cached = resultCache.get(key);
4377
4644
  if (cached && Date.now() - cached.ts < RESULT_CACHE_TTL_MS) return cached.data;
4378
4645
  const costRate = computeInputCostRate(projects);
4379
- const { toolCalls, projectCwds, apiCalls, userMessages } = await scanSessions(dateRange);
4646
+ const { toolCalls, projectCwds, apiCalls, userMessages } = preScanned ? await preScanned : await scanSessions(dateRange);
4380
4647
  const findings = [];
4381
4648
  const syncDetectors = [
4382
4649
  () => detectCacheBloat(apiCalls, projects, dateRange),
@@ -4487,13 +4754,13 @@ function renderOptimize(findings, costRate, periodLabel, periodCost, sessionCoun
4487
4754
  lines.push("");
4488
4755
  return lines.join("\n");
4489
4756
  }
4490
- async function runOptimize(projects, periodLabel, dateRange) {
4757
+ async function runOptimize(projects, periodLabel, dateRange, preScanned) {
4491
4758
  if (projects.length === 0) {
4492
4759
  console.log(chalk2.dim("\n No usage data found for this period.\n"));
4493
4760
  return;
4494
4761
  }
4495
4762
  process.stderr.write(chalk2.dim(" Analyzing your sessions...\n"));
4496
- const { findings, costRate, healthScore, healthGrade } = await scanAndDetect(projects, dateRange);
4763
+ const { findings, costRate, healthScore, healthGrade } = await scanAndDetect(projects, dateRange, preScanned);
4497
4764
  const sessions = projects.flatMap((p) => p.sessions);
4498
4765
  const periodCost = projects.reduce((s, p) => s + p.totalCostUSD, 0);
4499
4766
  const callCount = projects.reduce((s, p) => s + p.totalApiCalls, 0);
@@ -6504,7 +6771,7 @@ function getDateRange2(period) {
6504
6771
  return { range: { start, end: yesterdayEnd }, label: `Yesterday (${toDateString(start)})` };
6505
6772
  }
6506
6773
  case "week": {
6507
- const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
6774
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
6508
6775
  return { range: { start, end }, label: "Last 7 Days" };
6509
6776
  }
6510
6777
  case "month": {
@@ -6512,7 +6779,7 @@ function getDateRange2(period) {
6512
6779
  return { range: { start, end }, label: `${now.toLocaleString("default", { month: "long" })} ${now.getFullYear()}` };
6513
6780
  }
6514
6781
  case "30days": {
6515
- const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 30);
6782
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 29);
6516
6783
  return { range: { start, end }, label: "Last 30 Days" };
6517
6784
  }
6518
6785
  case "all": {
@@ -6520,7 +6787,7 @@ function getDateRange2(period) {
6520
6787
  return { range: { start, end }, label: "Last 6 months" };
6521
6788
  }
6522
6789
  default: {
6523
- const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7);
6790
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
6524
6791
  return { range: { start, end }, label: "Last 7 Days" };
6525
6792
  }
6526
6793
  }
@@ -6770,8 +7037,9 @@ program.command("status").description("Compact status output (today + week + mon
6770
7037
  const yesterdayEnd = new Date(todayStart.getTime() - 1);
6771
7038
  const yesterdayStr = toDateString(new Date(todayStart.getTime() - MS_PER_DAY3));
6772
7039
  const isAllProviders = pf === "all";
7040
+ const dailyCacheScope = buildDailyCacheScopeKey(opts.project, opts.exclude);
6773
7041
  const cache = await withDailyCacheLock(async () => {
6774
- let c = await loadDailyCache();
7042
+ let c = await loadDailyCache(dailyCacheScope);
6775
7043
  const hadYesterday = c.days.some((d) => d.date >= yesterdayStr);
6776
7044
  if (hadYesterday) {
6777
7045
  const freshDays = c.days.filter((d) => d.date < yesterdayStr);
@@ -6786,32 +7054,53 @@ program.command("status").description("Compact status output (today + week + mon
6786
7054
  if (gapStart.getTime() <= yesterdayEnd.getTime()) {
6787
7055
  const gapRange = { start: gapStart, end: yesterdayEnd };
6788
7056
  const gapProjects = filterProjectsByName(await parseAllSessions(gapRange, "all"), opts.project, opts.exclude);
6789
- const gapDays = aggregateProjectsIntoDays(gapProjects);
6790
- c = addNewDays(c, gapDays, yesterdayStr);
7057
+ const gapDays = fillMissingDays(gapRange.start, gapRange.end, aggregateProjectsIntoDays(gapProjects));
7058
+ c = addNewDays(c, gapDays, yesterdayStr, { coveredThrough: yesterdayStr });
6791
7059
  await saveDailyCache(c);
6792
7060
  }
6793
7061
  return c;
6794
7062
  });
6795
7063
  let currentData;
6796
- let scanProjects;
6797
- let scanRange;
7064
+ const scanRange = periodInfo.range;
6798
7065
  const todayRange = { start: todayStart, end: periodInfo.range.end };
6799
- 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;
6800
7089
  const todayDays = aggregateProjectsIntoDays(todayProjects);
7090
+ let daysCount = 0;
6801
7091
  {
6802
7092
  const rangeStartStr = toDateString(periodInfo.range.start);
6803
7093
  const rangeEndStr = toDateString(periodInfo.range.end);
6804
7094
  const historicalDays = getDaysInRange(cache, rangeStartStr, yesterdayStr);
6805
7095
  const todayOnly = todayDays.filter((d) => d.date > yesterdayStr && d.date <= rangeEndStr);
6806
7096
  const allDays = [...historicalDays, ...todayOnly].sort((a, b) => a.date.localeCompare(b.date));
7097
+ daysCount = allDays.length;
6807
7098
  if (isAllProviders) {
6808
7099
  currentData = buildPeriodDataFromDays(allDays, periodInfo.label);
6809
7100
  } else {
6810
7101
  const providerDays = filterDaysToProvider(allDays, pf);
6811
7102
  currentData = buildPeriodDataFromDays(providerDays, periodInfo.label);
6812
7103
  }
6813
- scanProjects = todayProjects;
6814
- scanRange = periodInfo.range;
6815
7104
  }
6816
7105
  const allProviders = await getAllProviders();
6817
7106
  const displayNameByName = new Map(allProviders.map((p) => [p.name, p.displayName]));
@@ -6822,6 +7111,12 @@ program.command("status").description("Compact status output (today + week + mon
6822
7111
  ...getDaysInRange(cache, rangeStartStr, yesterdayStr),
6823
7112
  ...todayDays.filter((d) => d.date > yesterdayStr)
6824
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
+ }
6825
7120
  const providerTotals = {};
6826
7121
  for (const d of allDaysForProviders) {
6827
7122
  for (const [name, p] of Object.entries(d.providers)) {
@@ -6845,7 +7140,7 @@ program.command("status").description("Compact status output (today + week + mon
6845
7140
  const fullHistory = [...allCacheDays, ...todayDays.filter((d) => d.date > yesterdayStr)];
6846
7141
  const dailyHistory = fullHistory.map((d) => {
6847
7142
  if (isAllProviders) {
6848
- 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]) => ({
6849
7144
  name,
6850
7145
  cost: m.cost,
6851
7146
  calls: m.calls,
@@ -6860,22 +7155,42 @@ program.command("status").description("Compact status output (today + week + mon
6860
7155
  outputTokens: d.outputTokens,
6861
7156
  cacheReadTokens: d.cacheReadTokens,
6862
7157
  cacheWriteTokens: d.cacheWriteTokens,
6863
- topModels
7158
+ topModels: topModels2
6864
7159
  };
6865
7160
  }
6866
- 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
+ }));
6867
7169
  return {
6868
7170
  date: d.date,
6869
- cost: prov.cost,
6870
- calls: prov.calls,
6871
- inputTokens: 0,
6872
- outputTokens: 0,
6873
- cacheReadTokens: 0,
6874
- cacheWriteTokens: 0,
6875
- 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
6876
7178
  };
6877
7179
  });
6878
- 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
+ }
6879
7194
  const exeOsDir = join21(homedir14(), ".exe-os");
6880
7195
  const exeOsDetected = existsSync6(exeOsDir);
6881
7196
  let agentStats = null;
@@ -6899,13 +7214,13 @@ program.command("status").description("Compact status output (today + week + mon
6899
7214
  if (agentStats) {
6900
7215
  agentStats = estimateAgentCosts(agentStats);
6901
7216
  }
6902
- const [proj24h, proj7d, proj30d] = await Promise.all([
6903
- parseAllSessions(getDateRange2("today").range, "all"),
7217
+ const [proj7d, proj30d] = await Promise.all([
6904
7218
  parseAllSessions(getDateRange2("week").range, "all"),
6905
7219
  parseAllSessions(getDateRange2("30days").range, "all")
6906
7220
  ]);
6907
- const projectSpend = buildProjectSpend(proj24h, proj7d, proj30d);
6908
- 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)));
6909
7224
  return;
6910
7225
  }
6911
7226
  if (opts.format === "json") {
@@ -7203,8 +7518,9 @@ program.command("plan [action] [id]").description("Show or configure a subscript
7203
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) => {
7204
7519
  await loadPricing();
7205
7520
  const { range, label } = getDateRange2(opts.period);
7521
+ const scanPromise = prewarmOptimizeScan(range);
7206
7522
  const projects = await parseAllSessions(range, opts.provider);
7207
- await runOptimize(projects, label, range);
7523
+ await runOptimize(projects, label, range, scanPromise);
7208
7524
  });
7209
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) => {
7210
7526
  await loadPricing();