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 +426 -104
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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": {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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:
|
|
206
|
-
"gpt-5.1-codex-mini": { inputCostPerToken: 25e-8, outputCostPerToken: 2e-6, cacheWriteCostPerToken: 25e-8, cacheReadCostPerToken:
|
|
207
|
-
"gpt-5.2-codex": { inputCostPerToken: 175e-8, outputCostPerToken: 14e-6, cacheWriteCostPerToken: 175e-8, cacheReadCostPerToken:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
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
|
-
})
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
3577
|
-
|
|
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
|
|
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
|
|
3628
|
-
|
|
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() -
|
|
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() -
|
|
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() -
|
|
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
|
-
|
|
6791
|
-
let scanRange;
|
|
7064
|
+
const scanRange = periodInfo.range;
|
|
6792
7065
|
const todayRange = { start: todayStart, end: periodInfo.range.end };
|
|
6793
|
-
const
|
|
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
|
|
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]
|
|
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
|
|
6864
|
-
calls: prov
|
|
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
|
-
|
|
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 [
|
|
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(
|
|
6902
|
-
|
|
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();
|