exe-watcher 0.2.2 → 0.2.4
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 +420 -100
- 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);
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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": {
|
|
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.
|
|
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:
|
|
201
|
-
"gpt-5
|
|
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:
|
|
210
|
-
"gpt-5.1-codex-mini": { inputCostPerToken: 25e-8, outputCostPerToken: 2e-6, cacheWriteCostPerToken: 25e-8, cacheReadCostPerToken:
|
|
211
|
-
"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 },
|
|
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
|
|
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
|
-
|
|
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,11 +1748,48 @@ 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;
|
|
1629
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;
|
|
1630
1793
|
}
|
|
1631
1794
|
}
|
|
1632
1795
|
}
|
|
@@ -1634,27 +1797,41 @@ function aggregateProjectsIntoDays(projects) {
|
|
|
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
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
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
|
-
})
|
|
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",
|
|
@@ -2301,6 +2479,10 @@ function createParser(source, seenKeys) {
|
|
|
2301
2479
|
sessionModel = entry.payload?.model;
|
|
2302
2480
|
continue;
|
|
2303
2481
|
}
|
|
2482
|
+
if (entry.type === "turn_context" && entry.payload?.model) {
|
|
2483
|
+
sessionModel = entry.payload.model;
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2304
2486
|
if (entry.type === "response_item" && entry.payload?.type === "function_call") {
|
|
2305
2487
|
const rawName = entry.payload.name ?? "";
|
|
2306
2488
|
pendingTools.push(toolNameMap[rawName] ?? rawName);
|
|
@@ -2392,7 +2574,7 @@ function createCodexProvider(codexDir) {
|
|
|
2392
2574
|
name: "codex",
|
|
2393
2575
|
displayName: "Codex",
|
|
2394
2576
|
modelDisplayName(model) {
|
|
2395
|
-
for (const [key, name] of
|
|
2577
|
+
for (const [key, name] of modelDisplayEntries) {
|
|
2396
2578
|
if (model.startsWith(key)) return name;
|
|
2397
2579
|
}
|
|
2398
2580
|
return model;
|
|
@@ -2444,7 +2626,7 @@ var toolNameMap2 = {
|
|
|
2444
2626
|
fetch_webpage: "WebFetch",
|
|
2445
2627
|
github_repo: "GitHub"
|
|
2446
2628
|
};
|
|
2447
|
-
var
|
|
2629
|
+
var modelDisplayEntries2 = Object.entries(modelDisplayNames2).sort((a, b) => b[0].length - a[0].length);
|
|
2448
2630
|
function getCopilotSessionStateDir(override) {
|
|
2449
2631
|
return override ?? join9(homedir6(), ".copilot", "session-state");
|
|
2450
2632
|
}
|
|
@@ -2540,7 +2722,7 @@ function createCopilotProvider(sessionStateDir) {
|
|
|
2540
2722
|
name: "copilot",
|
|
2541
2723
|
displayName: "Copilot",
|
|
2542
2724
|
modelDisplayName(model) {
|
|
2543
|
-
for (const [key, name] of
|
|
2725
|
+
for (const [key, name] of modelDisplayEntries2) {
|
|
2544
2726
|
if (model === key || model.startsWith(key + "-")) return name;
|
|
2545
2727
|
}
|
|
2546
2728
|
return model;
|
|
@@ -2585,7 +2767,7 @@ var toolNameMap3 = {
|
|
|
2585
2767
|
todo: "TodoWrite",
|
|
2586
2768
|
patch: "Patch"
|
|
2587
2769
|
};
|
|
2588
|
-
var
|
|
2770
|
+
var modelDisplayEntries3 = Object.entries(modelDisplayNames3).sort((a, b) => b[0].length - a[0].length);
|
|
2589
2771
|
function getPiSessionsDir(override) {
|
|
2590
2772
|
return override ?? join10(homedir7(), ".pi", "agent", "sessions");
|
|
2591
2773
|
}
|
|
@@ -2707,7 +2889,7 @@ function createPiProvider(sessionsDir) {
|
|
|
2707
2889
|
name: "pi",
|
|
2708
2890
|
displayName: "Pi",
|
|
2709
2891
|
modelDisplayName(model) {
|
|
2710
|
-
for (const [key, name] of
|
|
2892
|
+
for (const [key, name] of modelDisplayEntries3) {
|
|
2711
2893
|
if (model.startsWith(key)) return name;
|
|
2712
2894
|
}
|
|
2713
2895
|
return model;
|
|
@@ -2730,7 +2912,7 @@ function createOmpProvider(sessionsDir) {
|
|
|
2730
2912
|
name: "omp",
|
|
2731
2913
|
displayName: "OMP",
|
|
2732
2914
|
modelDisplayName(model) {
|
|
2733
|
-
for (const [key, name] of
|
|
2915
|
+
for (const [key, name] of modelDisplayEntries3) {
|
|
2734
2916
|
if (model.startsWith(key)) return name;
|
|
2735
2917
|
}
|
|
2736
2918
|
return model;
|
|
@@ -3315,9 +3497,68 @@ async function parseProviderSources(providerName, sources, seenKeys, dateRange)
|
|
|
3315
3497
|
var CACHE_TTL_MS3 = 6e4;
|
|
3316
3498
|
var MAX_CACHE_ENTRIES = 10;
|
|
3317
3499
|
var sessionCache = /* @__PURE__ */ new Map();
|
|
3318
|
-
|
|
3500
|
+
var inFlightSourceContexts = /* @__PURE__ */ new Map();
|
|
3501
|
+
function sourceContextCacheKey(providerFilter) {
|
|
3502
|
+
return JSON.stringify({
|
|
3503
|
+
provider: providerFilter ?? "all",
|
|
3504
|
+
claudeConfigDir: process.env["CLAUDE_CONFIG_DIR"] ?? "",
|
|
3505
|
+
codexHome: process.env["CODEX_HOME"] ?? "",
|
|
3506
|
+
xdgDataHome: process.env["XDG_DATA_HOME"] ?? "",
|
|
3507
|
+
home: process.env["HOME"] ?? ""
|
|
3508
|
+
});
|
|
3509
|
+
}
|
|
3510
|
+
function cacheKey(dateRange, providerFilter, sourceFingerprint = "static") {
|
|
3319
3511
|
const s = dateRange ? `${dateRange.start.getTime()}:${dateRange.end.getTime()}` : "none";
|
|
3320
|
-
return `${s}:${providerFilter ?? "all"}`;
|
|
3512
|
+
return `${s}:${providerFilter ?? "all"}:${sourceFingerprint}`;
|
|
3513
|
+
}
|
|
3514
|
+
async function buildSourceFingerprint(path) {
|
|
3515
|
+
try {
|
|
3516
|
+
const fp = await stat10(path);
|
|
3517
|
+
if (!fp.isDirectory()) return `f:${Math.round(fp.mtimeMs)}:${fp.size}`;
|
|
3518
|
+
const entries = await readdir8(path);
|
|
3519
|
+
const children = await Promise.all(entries.map(async (entry) => stat10(join15(path, entry)).catch(() => null)));
|
|
3520
|
+
let childCount = 0;
|
|
3521
|
+
let childSizeBytes = 0;
|
|
3522
|
+
let newestChildMtimeMs = 0;
|
|
3523
|
+
for (const child of children) {
|
|
3524
|
+
if (!child) continue;
|
|
3525
|
+
childCount += 1;
|
|
3526
|
+
childSizeBytes += child.size;
|
|
3527
|
+
newestChildMtimeMs = Math.max(newestChildMtimeMs, child.mtimeMs);
|
|
3528
|
+
}
|
|
3529
|
+
return `d:${Math.round(fp.mtimeMs)}:${fp.size}:${childCount}:${Math.round(newestChildMtimeMs)}:${childSizeBytes}`;
|
|
3530
|
+
} catch {
|
|
3531
|
+
return "missing";
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
async function buildCacheFingerprint(sources) {
|
|
3535
|
+
const parts = await Promise.all(sources.map(async (source) => {
|
|
3536
|
+
const fingerprint = await buildSourceFingerprint(source.path);
|
|
3537
|
+
return `${source.provider}:${source.project}:${source.path}:${fingerprint}`;
|
|
3538
|
+
}));
|
|
3539
|
+
return parts.sort().join("|");
|
|
3540
|
+
}
|
|
3541
|
+
async function getSourceContext(providerFilter) {
|
|
3542
|
+
const key = sourceContextCacheKey(providerFilter);
|
|
3543
|
+
const cached = inFlightSourceContexts.get(key);
|
|
3544
|
+
if (cached) {
|
|
3545
|
+
return cached;
|
|
3546
|
+
}
|
|
3547
|
+
const promise = (async () => {
|
|
3548
|
+
const sources = await discoverAllSessions(providerFilter);
|
|
3549
|
+
const fingerprint = await buildCacheFingerprint(sources);
|
|
3550
|
+
return { sources, fingerprint };
|
|
3551
|
+
})();
|
|
3552
|
+
inFlightSourceContexts.set(key, promise);
|
|
3553
|
+
try {
|
|
3554
|
+
return await promise;
|
|
3555
|
+
} catch (err) {
|
|
3556
|
+
throw err;
|
|
3557
|
+
} finally {
|
|
3558
|
+
if (inFlightSourceContexts.get(key) === promise) {
|
|
3559
|
+
inFlightSourceContexts.delete(key);
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3321
3562
|
}
|
|
3322
3563
|
function cachePut(key, data) {
|
|
3323
3564
|
const now = Date.now();
|
|
@@ -3351,12 +3592,13 @@ function filterProjectsByName(projects, include, exclude) {
|
|
|
3351
3592
|
return result;
|
|
3352
3593
|
}
|
|
3353
3594
|
async function parseAllSessions(dateRange, providerFilter) {
|
|
3354
|
-
const
|
|
3595
|
+
const sourceContext = await getSourceContext(providerFilter);
|
|
3596
|
+
const allSources = sourceContext.sources;
|
|
3597
|
+
const key = cacheKey(dateRange, providerFilter, sourceContext.fingerprint);
|
|
3355
3598
|
const cached = sessionCache.get(key);
|
|
3356
3599
|
if (cached && Date.now() - cached.ts < CACHE_TTL_MS3) return cached.data;
|
|
3357
3600
|
const seenMsgIds = /* @__PURE__ */ new Set();
|
|
3358
3601
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
3359
|
-
const allSources = await discoverAllSessions(providerFilter);
|
|
3360
3602
|
const claudeSources = allSources.filter((s) => s.provider === "claude");
|
|
3361
3603
|
const nonClaudeSources = allSources.filter((s) => s.provider !== "claude");
|
|
3362
3604
|
const claudeDirs = claudeSources.map((s) => ({ path: s.path, name: s.project }));
|
|
@@ -3543,7 +3785,7 @@ function buildHistory(daily) {
|
|
|
3543
3785
|
const trimmed = sorted.slice(-HISTORY_DAYS_LIMIT);
|
|
3544
3786
|
return { daily: trimmed };
|
|
3545
3787
|
}
|
|
3546
|
-
function buildMenubarPayload(current, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge) {
|
|
3788
|
+
function buildMenubarPayload(current, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge, diagnostics) {
|
|
3547
3789
|
return {
|
|
3548
3790
|
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3549
3791
|
current: {
|
|
@@ -3561,6 +3803,7 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
|
|
|
3561
3803
|
},
|
|
3562
3804
|
optimize: buildOptimize(optimize),
|
|
3563
3805
|
history: buildHistory(dailyHistory),
|
|
3806
|
+
diagnostics: diagnostics ?? { daysCount: 0, parseTimeMs: 0, warnings: [] },
|
|
3564
3807
|
agentStats: agentStats ?? null,
|
|
3565
3808
|
exeOsDetected: exeOsDetected ?? false,
|
|
3566
3809
|
statsFileAge: statsFileAge ?? null,
|
|
@@ -3570,43 +3813,62 @@ function buildMenubarPayload(current, providers, optimize, dailyHistory, agentSt
|
|
|
3570
3813
|
|
|
3571
3814
|
// src/daily-cache.ts
|
|
3572
3815
|
init_cache_dir();
|
|
3573
|
-
import { randomBytes as randomBytes5 } from "crypto";
|
|
3816
|
+
import { createHash as createHash3, randomBytes as randomBytes5 } from "crypto";
|
|
3574
3817
|
import { existsSync as existsSync3 } from "fs";
|
|
3575
3818
|
import { mkdir as mkdir7, open as open5, readFile as readFile7, rename as rename6, unlink as unlink3 } from "fs/promises";
|
|
3576
3819
|
import { join as join16 } from "path";
|
|
3577
|
-
var DAILY_CACHE_VERSION =
|
|
3820
|
+
var DAILY_CACHE_VERSION = 4;
|
|
3821
|
+
var DEFAULT_DAILY_CACHE_SCOPE = "global";
|
|
3578
3822
|
var DAILY_CACHE_FILENAME = "daily-cache.json";
|
|
3579
|
-
function getCachePath3() {
|
|
3580
|
-
|
|
3823
|
+
function getCachePath3(scopeKey) {
|
|
3824
|
+
if (scopeKey === DEFAULT_DAILY_CACHE_SCOPE) {
|
|
3825
|
+
return join16(getCacheDir(), DAILY_CACHE_FILENAME);
|
|
3826
|
+
}
|
|
3827
|
+
return join16(getCacheDir(), `daily-cache-${scopeKey}.json`);
|
|
3828
|
+
}
|
|
3829
|
+
function emptyCache(scopeKey) {
|
|
3830
|
+
return { version: DAILY_CACHE_VERSION, scopeKey, lastComputedDate: null, days: [] };
|
|
3831
|
+
}
|
|
3832
|
+
function normalizeScopeTerms(values) {
|
|
3833
|
+
return [...new Set((values ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean))].sort();
|
|
3581
3834
|
}
|
|
3582
|
-
function
|
|
3583
|
-
|
|
3835
|
+
function buildDailyCacheScopeKey(include, exclude) {
|
|
3836
|
+
const normalizedInclude = normalizeScopeTerms(include);
|
|
3837
|
+
const normalizedExclude = normalizeScopeTerms(exclude);
|
|
3838
|
+
if (normalizedInclude.length === 0 && normalizedExclude.length === 0) {
|
|
3839
|
+
return DEFAULT_DAILY_CACHE_SCOPE;
|
|
3840
|
+
}
|
|
3841
|
+
const raw = JSON.stringify({ include: normalizedInclude, exclude: normalizedExclude });
|
|
3842
|
+
return `scope-${createHash3("sha1").update(raw).digest("hex").slice(0, 12)}`;
|
|
3584
3843
|
}
|
|
3585
3844
|
function isValidCache(parsed) {
|
|
3586
3845
|
if (!parsed || typeof parsed !== "object") return false;
|
|
3587
3846
|
const c = parsed;
|
|
3588
3847
|
if (c.version !== DAILY_CACHE_VERSION) return false;
|
|
3848
|
+
if (typeof c.scopeKey !== "string" || c.scopeKey.length === 0) return false;
|
|
3589
3849
|
if (!Array.isArray(c.days)) return false;
|
|
3590
3850
|
return true;
|
|
3591
3851
|
}
|
|
3592
|
-
async function loadDailyCache() {
|
|
3593
|
-
const path = getCachePath3();
|
|
3594
|
-
if (!existsSync3(path)) return emptyCache();
|
|
3852
|
+
async function loadDailyCache(scopeKey = DEFAULT_DAILY_CACHE_SCOPE) {
|
|
3853
|
+
const path = getCachePath3(scopeKey);
|
|
3854
|
+
if (!existsSync3(path)) return emptyCache(scopeKey);
|
|
3595
3855
|
try {
|
|
3596
3856
|
const raw = await readFile7(path, "utf-8");
|
|
3597
3857
|
const parsed = JSON.parse(raw);
|
|
3598
|
-
if (!isValidCache(parsed)) return emptyCache();
|
|
3858
|
+
if (!isValidCache(parsed)) return emptyCache(scopeKey);
|
|
3859
|
+
if (parsed.scopeKey !== scopeKey) return emptyCache(scopeKey);
|
|
3599
3860
|
return parsed;
|
|
3600
3861
|
} catch {
|
|
3601
|
-
return emptyCache();
|
|
3862
|
+
return emptyCache(scopeKey);
|
|
3602
3863
|
}
|
|
3603
3864
|
}
|
|
3604
3865
|
async function saveDailyCache(cache) {
|
|
3605
3866
|
const dir = getCacheDir();
|
|
3606
3867
|
if (!existsSync3(dir)) await mkdir7(dir, { recursive: true });
|
|
3607
|
-
const
|
|
3868
|
+
const scopeKey = cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE;
|
|
3869
|
+
const finalPath = getCachePath3(scopeKey);
|
|
3608
3870
|
const tempPath = `${finalPath}.${randomBytes5(8).toString("hex")}.tmp`;
|
|
3609
|
-
const payload = JSON.stringify(cache);
|
|
3871
|
+
const payload = JSON.stringify({ ...cache, scopeKey });
|
|
3610
3872
|
const handle = await open5(tempPath, "w", 384);
|
|
3611
3873
|
try {
|
|
3612
3874
|
await handle.writeFile(payload, { encoding: "utf-8" });
|
|
@@ -3624,14 +3886,20 @@ async function saveDailyCache(cache) {
|
|
|
3624
3886
|
throw err;
|
|
3625
3887
|
}
|
|
3626
3888
|
}
|
|
3627
|
-
function addNewDays(cache, incoming, newestDate) {
|
|
3889
|
+
function addNewDays(cache, incoming, newestDate, options = {}) {
|
|
3628
3890
|
const byDate = new Map(cache.days.map((d) => [d.date, d]));
|
|
3629
3891
|
for (const day of incoming) {
|
|
3630
3892
|
byDate.set(day.date, day);
|
|
3631
3893
|
}
|
|
3632
3894
|
const merged = Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date));
|
|
3633
|
-
const
|
|
3634
|
-
|
|
3895
|
+
const candidateLast = options.coveredThrough ?? newestDate;
|
|
3896
|
+
const nextLast = cache.lastComputedDate && cache.lastComputedDate > candidateLast ? cache.lastComputedDate : candidateLast;
|
|
3897
|
+
return {
|
|
3898
|
+
version: DAILY_CACHE_VERSION,
|
|
3899
|
+
scopeKey: cache.scopeKey || DEFAULT_DAILY_CACHE_SCOPE,
|
|
3900
|
+
lastComputedDate: nextLast,
|
|
3901
|
+
days: merged
|
|
3902
|
+
};
|
|
3635
3903
|
}
|
|
3636
3904
|
function getDaysInRange(cache, start, end) {
|
|
3637
3905
|
return cache.days.filter((d) => d.date >= start && d.date <= end);
|
|
@@ -3858,6 +4126,9 @@ async function scanSessions(dateRange) {
|
|
|
3858
4126
|
});
|
|
3859
4127
|
return { toolCalls: allCalls, projectCwds: allCwds, apiCalls: allApiCalls, userMessages: allUserMessages };
|
|
3860
4128
|
}
|
|
4129
|
+
function prewarmOptimizeScan(dateRange) {
|
|
4130
|
+
return scanSessions(dateRange);
|
|
4131
|
+
}
|
|
3861
4132
|
function readJsonFile(path) {
|
|
3862
4133
|
const raw = readSessionFileSync(path);
|
|
3863
4134
|
if (raw === null) return null;
|
|
@@ -4368,7 +4639,7 @@ function cacheKey2(projects, dateRange) {
|
|
|
4368
4639
|
const fingerprint = projects.length + ":" + projects.reduce((s, p) => s + p.totalApiCalls, 0);
|
|
4369
4640
|
return `${dr}:${fingerprint}`;
|
|
4370
4641
|
}
|
|
4371
|
-
async function scanAndDetect(projects, dateRange) {
|
|
4642
|
+
async function scanAndDetect(projects, dateRange, preScanned) {
|
|
4372
4643
|
if (projects.length === 0) {
|
|
4373
4644
|
return { findings: [], costRate: 0, healthScore: 100, healthGrade: "A" };
|
|
4374
4645
|
}
|
|
@@ -4376,7 +4647,7 @@ async function scanAndDetect(projects, dateRange) {
|
|
|
4376
4647
|
const cached = resultCache.get(key);
|
|
4377
4648
|
if (cached && Date.now() - cached.ts < RESULT_CACHE_TTL_MS) return cached.data;
|
|
4378
4649
|
const costRate = computeInputCostRate(projects);
|
|
4379
|
-
const { toolCalls, projectCwds, apiCalls, userMessages } = await scanSessions(dateRange);
|
|
4650
|
+
const { toolCalls, projectCwds, apiCalls, userMessages } = preScanned ? await preScanned : await scanSessions(dateRange);
|
|
4380
4651
|
const findings = [];
|
|
4381
4652
|
const syncDetectors = [
|
|
4382
4653
|
() => detectCacheBloat(apiCalls, projects, dateRange),
|
|
@@ -4487,13 +4758,13 @@ function renderOptimize(findings, costRate, periodLabel, periodCost, sessionCoun
|
|
|
4487
4758
|
lines.push("");
|
|
4488
4759
|
return lines.join("\n");
|
|
4489
4760
|
}
|
|
4490
|
-
async function runOptimize(projects, periodLabel, dateRange) {
|
|
4761
|
+
async function runOptimize(projects, periodLabel, dateRange, preScanned) {
|
|
4491
4762
|
if (projects.length === 0) {
|
|
4492
4763
|
console.log(chalk2.dim("\n No usage data found for this period.\n"));
|
|
4493
4764
|
return;
|
|
4494
4765
|
}
|
|
4495
4766
|
process.stderr.write(chalk2.dim(" Analyzing your sessions...\n"));
|
|
4496
|
-
const { findings, costRate, healthScore, healthGrade } = await scanAndDetect(projects, dateRange);
|
|
4767
|
+
const { findings, costRate, healthScore, healthGrade } = await scanAndDetect(projects, dateRange, preScanned);
|
|
4497
4768
|
const sessions = projects.flatMap((p) => p.sessions);
|
|
4498
4769
|
const periodCost = projects.reduce((s, p) => s + p.totalCostUSD, 0);
|
|
4499
4770
|
const callCount = projects.reduce((s, p) => s + p.totalApiCalls, 0);
|
|
@@ -6504,7 +6775,7 @@ function getDateRange2(period) {
|
|
|
6504
6775
|
return { range: { start, end: yesterdayEnd }, label: `Yesterday (${toDateString(start)})` };
|
|
6505
6776
|
}
|
|
6506
6777
|
case "week": {
|
|
6507
|
-
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() -
|
|
6778
|
+
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
|
|
6508
6779
|
return { range: { start, end }, label: "Last 7 Days" };
|
|
6509
6780
|
}
|
|
6510
6781
|
case "month": {
|
|
@@ -6512,7 +6783,7 @@ function getDateRange2(period) {
|
|
|
6512
6783
|
return { range: { start, end }, label: `${now.toLocaleString("default", { month: "long" })} ${now.getFullYear()}` };
|
|
6513
6784
|
}
|
|
6514
6785
|
case "30days": {
|
|
6515
|
-
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() -
|
|
6786
|
+
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 29);
|
|
6516
6787
|
return { range: { start, end }, label: "Last 30 Days" };
|
|
6517
6788
|
}
|
|
6518
6789
|
case "all": {
|
|
@@ -6520,7 +6791,7 @@ function getDateRange2(period) {
|
|
|
6520
6791
|
return { range: { start, end }, label: "Last 6 months" };
|
|
6521
6792
|
}
|
|
6522
6793
|
default: {
|
|
6523
|
-
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() -
|
|
6794
|
+
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
|
|
6524
6795
|
return { range: { start, end }, label: "Last 7 Days" };
|
|
6525
6796
|
}
|
|
6526
6797
|
}
|
|
@@ -6770,8 +7041,9 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
6770
7041
|
const yesterdayEnd = new Date(todayStart.getTime() - 1);
|
|
6771
7042
|
const yesterdayStr = toDateString(new Date(todayStart.getTime() - MS_PER_DAY3));
|
|
6772
7043
|
const isAllProviders = pf === "all";
|
|
7044
|
+
const dailyCacheScope = buildDailyCacheScopeKey(opts.project, opts.exclude);
|
|
6773
7045
|
const cache = await withDailyCacheLock(async () => {
|
|
6774
|
-
let c = await loadDailyCache();
|
|
7046
|
+
let c = await loadDailyCache(dailyCacheScope);
|
|
6775
7047
|
const hadYesterday = c.days.some((d) => d.date >= yesterdayStr);
|
|
6776
7048
|
if (hadYesterday) {
|
|
6777
7049
|
const freshDays = c.days.filter((d) => d.date < yesterdayStr);
|
|
@@ -6786,32 +7058,53 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
6786
7058
|
if (gapStart.getTime() <= yesterdayEnd.getTime()) {
|
|
6787
7059
|
const gapRange = { start: gapStart, end: yesterdayEnd };
|
|
6788
7060
|
const gapProjects = filterProjectsByName(await parseAllSessions(gapRange, "all"), opts.project, opts.exclude);
|
|
6789
|
-
const gapDays = aggregateProjectsIntoDays(gapProjects);
|
|
6790
|
-
c = addNewDays(c, gapDays, yesterdayStr);
|
|
7061
|
+
const gapDays = fillMissingDays(gapRange.start, gapRange.end, aggregateProjectsIntoDays(gapProjects));
|
|
7062
|
+
c = addNewDays(c, gapDays, yesterdayStr, { coveredThrough: yesterdayStr });
|
|
6791
7063
|
await saveDailyCache(c);
|
|
6792
7064
|
}
|
|
6793
7065
|
return c;
|
|
6794
7066
|
});
|
|
6795
7067
|
let currentData;
|
|
6796
|
-
|
|
6797
|
-
let scanRange;
|
|
7068
|
+
const scanRange = periodInfo.range;
|
|
6798
7069
|
const todayRange = { start: todayStart, end: periodInfo.range.end };
|
|
6799
|
-
const
|
|
7070
|
+
const warnings = [];
|
|
7071
|
+
const parseStart = Date.now();
|
|
7072
|
+
let todayProjects;
|
|
7073
|
+
let allTodayProjects;
|
|
7074
|
+
try {
|
|
7075
|
+
allTodayProjects = await parseAllSessions(todayRange, "all");
|
|
7076
|
+
todayProjects = fp(allTodayProjects);
|
|
7077
|
+
} catch (err) {
|
|
7078
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7079
|
+
const warn2 = `parseAllSessions failed: ${msg}`;
|
|
7080
|
+
warnings.push(warn2);
|
|
7081
|
+
process.stderr.write(`[exe-watcher] WARNING: ${warn2}
|
|
7082
|
+
`);
|
|
7083
|
+
allTodayProjects = [];
|
|
7084
|
+
todayProjects = [];
|
|
7085
|
+
}
|
|
7086
|
+
if (todayProjects.length === 0) {
|
|
7087
|
+
const warn2 = `parseAllSessions returned 0 projects for range ${toDateString(todayRange.start)}..${toDateString(todayRange.end)}`;
|
|
7088
|
+
warnings.push(warn2);
|
|
7089
|
+
process.stderr.write(`[exe-watcher] WARNING: ${warn2}
|
|
7090
|
+
`);
|
|
7091
|
+
}
|
|
7092
|
+
const parseTimeMs = Date.now() - parseStart;
|
|
6800
7093
|
const todayDays = aggregateProjectsIntoDays(todayProjects);
|
|
7094
|
+
let daysCount = 0;
|
|
6801
7095
|
{
|
|
6802
7096
|
const rangeStartStr = toDateString(periodInfo.range.start);
|
|
6803
7097
|
const rangeEndStr = toDateString(periodInfo.range.end);
|
|
6804
7098
|
const historicalDays = getDaysInRange(cache, rangeStartStr, yesterdayStr);
|
|
6805
7099
|
const todayOnly = todayDays.filter((d) => d.date > yesterdayStr && d.date <= rangeEndStr);
|
|
6806
7100
|
const allDays = [...historicalDays, ...todayOnly].sort((a, b) => a.date.localeCompare(b.date));
|
|
7101
|
+
daysCount = allDays.length;
|
|
6807
7102
|
if (isAllProviders) {
|
|
6808
7103
|
currentData = buildPeriodDataFromDays(allDays, periodInfo.label);
|
|
6809
7104
|
} else {
|
|
6810
7105
|
const providerDays = filterDaysToProvider(allDays, pf);
|
|
6811
7106
|
currentData = buildPeriodDataFromDays(providerDays, periodInfo.label);
|
|
6812
7107
|
}
|
|
6813
|
-
scanProjects = todayProjects;
|
|
6814
|
-
scanRange = periodInfo.range;
|
|
6815
7108
|
}
|
|
6816
7109
|
const allProviders = await getAllProviders();
|
|
6817
7110
|
const displayNameByName = new Map(allProviders.map((p) => [p.name, p.displayName]));
|
|
@@ -6822,6 +7115,12 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
6822
7115
|
...getDaysInRange(cache, rangeStartStr, yesterdayStr),
|
|
6823
7116
|
...todayDays.filter((d) => d.date > yesterdayStr)
|
|
6824
7117
|
];
|
|
7118
|
+
if (allDaysForProviders.length === 0) {
|
|
7119
|
+
const warn2 = "allDaysForProviders is empty \u2014 all providers will show $0";
|
|
7120
|
+
warnings.push(warn2);
|
|
7121
|
+
process.stderr.write(`[exe-watcher] WARNING: ${warn2}
|
|
7122
|
+
`);
|
|
7123
|
+
}
|
|
6825
7124
|
const providerTotals = {};
|
|
6826
7125
|
for (const d of allDaysForProviders) {
|
|
6827
7126
|
for (const [name, p] of Object.entries(d.providers)) {
|
|
@@ -6845,7 +7144,7 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
6845
7144
|
const fullHistory = [...allCacheDays, ...todayDays.filter((d) => d.date > yesterdayStr)];
|
|
6846
7145
|
const dailyHistory = fullHistory.map((d) => {
|
|
6847
7146
|
if (isAllProviders) {
|
|
6848
|
-
const
|
|
7147
|
+
const topModels2 = Object.entries(d.models).filter(([name]) => name !== "<synthetic>").sort(([, a], [, b]) => b.cost - a.cost).slice(0, 5).map(([name, m]) => ({
|
|
6849
7148
|
name,
|
|
6850
7149
|
cost: m.cost,
|
|
6851
7150
|
calls: m.calls,
|
|
@@ -6860,22 +7159,42 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
6860
7159
|
outputTokens: d.outputTokens,
|
|
6861
7160
|
cacheReadTokens: d.cacheReadTokens,
|
|
6862
7161
|
cacheWriteTokens: d.cacheWriteTokens,
|
|
6863
|
-
topModels
|
|
7162
|
+
topModels: topModels2
|
|
6864
7163
|
};
|
|
6865
7164
|
}
|
|
6866
|
-
const prov = d.providers[pf]
|
|
7165
|
+
const prov = d.providers[pf];
|
|
7166
|
+
const topModels = Object.entries(prov?.models ?? {}).filter(([name]) => name !== "<synthetic>").sort(([, a], [, b]) => b.cost - a.cost).slice(0, 5).map(([name, m]) => ({
|
|
7167
|
+
name,
|
|
7168
|
+
cost: m.cost,
|
|
7169
|
+
calls: m.calls,
|
|
7170
|
+
inputTokens: m.inputTokens,
|
|
7171
|
+
outputTokens: m.outputTokens
|
|
7172
|
+
}));
|
|
6867
7173
|
return {
|
|
6868
7174
|
date: d.date,
|
|
6869
|
-
cost: prov
|
|
6870
|
-
calls: prov
|
|
6871
|
-
inputTokens: 0,
|
|
6872
|
-
outputTokens: 0,
|
|
6873
|
-
cacheReadTokens: 0,
|
|
6874
|
-
cacheWriteTokens: 0,
|
|
6875
|
-
topModels
|
|
7175
|
+
cost: prov?.cost ?? 0,
|
|
7176
|
+
calls: prov?.calls ?? 0,
|
|
7177
|
+
inputTokens: prov?.inputTokens ?? 0,
|
|
7178
|
+
outputTokens: prov?.outputTokens ?? 0,
|
|
7179
|
+
cacheReadTokens: prov?.cacheReadTokens ?? 0,
|
|
7180
|
+
cacheWriteTokens: prov?.cacheWriteTokens ?? 0,
|
|
7181
|
+
topModels
|
|
6876
7182
|
};
|
|
6877
7183
|
});
|
|
6878
|
-
|
|
7184
|
+
let optimize = null;
|
|
7185
|
+
if (opts.optimize !== false) {
|
|
7186
|
+
try {
|
|
7187
|
+
const optimizeProjects = isAllProviders && opts.period === "today" ? todayProjects : fp(await parseAllSessions(scanRange, isAllProviders ? "all" : pf));
|
|
7188
|
+
optimize = await scanAndDetect(optimizeProjects, scanRange);
|
|
7189
|
+
} catch (err) {
|
|
7190
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7191
|
+
const warn2 = `optimize parse failed: ${msg}`;
|
|
7192
|
+
warnings.push(warn2);
|
|
7193
|
+
process.stderr.write(`[exe-watcher] WARNING: ${warn2}
|
|
7194
|
+
`);
|
|
7195
|
+
optimize = await scanAndDetect(todayProjects, scanRange);
|
|
7196
|
+
}
|
|
7197
|
+
}
|
|
6879
7198
|
const exeOsDir = join21(homedir14(), ".exe-os");
|
|
6880
7199
|
const exeOsDetected = existsSync6(exeOsDir);
|
|
6881
7200
|
let agentStats = null;
|
|
@@ -6899,13 +7218,13 @@ program.command("status").description("Compact status output (today + week + mon
|
|
|
6899
7218
|
if (agentStats) {
|
|
6900
7219
|
agentStats = estimateAgentCosts(agentStats);
|
|
6901
7220
|
}
|
|
6902
|
-
const [
|
|
6903
|
-
parseAllSessions(getDateRange2("today").range, "all"),
|
|
7221
|
+
const [proj7d, proj30d] = await Promise.all([
|
|
6904
7222
|
parseAllSessions(getDateRange2("week").range, "all"),
|
|
6905
7223
|
parseAllSessions(getDateRange2("30days").range, "all")
|
|
6906
7224
|
]);
|
|
6907
|
-
const projectSpend = buildProjectSpend(
|
|
6908
|
-
|
|
7225
|
+
const projectSpend = buildProjectSpend(allTodayProjects, proj7d, proj30d);
|
|
7226
|
+
const diagnostics = { daysCount, parseTimeMs, warnings };
|
|
7227
|
+
console.log(JSON.stringify(buildMenubarPayload(currentData, providers, optimize, dailyHistory, agentStats, projectSpend, exeOsDetected, statsFileAge, diagnostics)));
|
|
6909
7228
|
return;
|
|
6910
7229
|
}
|
|
6911
7230
|
if (opts.format === "json") {
|
|
@@ -7203,8 +7522,9 @@ program.command("plan [action] [id]").description("Show or configure a subscript
|
|
|
7203
7522
|
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
7523
|
await loadPricing();
|
|
7205
7524
|
const { range, label } = getDateRange2(opts.period);
|
|
7525
|
+
const scanPromise = prewarmOptimizeScan(range);
|
|
7206
7526
|
const projects = await parseAllSessions(range, opts.provider);
|
|
7207
|
-
await runOptimize(projects, label, range);
|
|
7527
|
+
await runOptimize(projects, label, range, scanPromise);
|
|
7208
7528
|
});
|
|
7209
7529
|
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
7530
|
await loadPricing();
|