@praeviso/code-env-switch 0.1.3 → 0.1.5
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/README.md +17 -0
- package/README_zh.md +17 -0
- package/bin/commands/list.js +44 -2
- package/bin/statusline/debug.js +42 -0
- package/bin/statusline/format.js +57 -0
- package/bin/statusline/git.js +96 -0
- package/bin/statusline/index.js +107 -558
- package/bin/statusline/input.js +167 -0
- package/bin/statusline/style.js +22 -0
- package/bin/statusline/types.js +2 -0
- package/bin/statusline/usage/claude.js +181 -0
- package/bin/statusline/usage/codex.js +168 -0
- package/bin/statusline/usage.js +67 -0
- package/bin/statusline/utils.js +35 -0
- package/bin/usage/index.js +396 -41
- package/bin/usage/pricing.js +303 -0
- package/code-env.example.json +55 -0
- package/package.json +1 -1
- package/src/commands/list.ts +74 -4
- package/src/statusline/debug.ts +40 -0
- package/src/statusline/format.ts +67 -0
- package/src/statusline/git.ts +82 -0
- package/src/statusline/index.ts +143 -764
- package/src/statusline/input.ts +159 -0
- package/src/statusline/style.ts +19 -0
- package/src/statusline/types.ts +111 -0
- package/src/statusline/usage/claude.ts +299 -0
- package/src/statusline/usage/codex.ts +263 -0
- package/src/statusline/usage.ts +80 -0
- package/src/statusline/utils.ts +27 -0
- package/src/types.ts +23 -0
- package/src/usage/index.ts +519 -35
- package/src/usage/pricing.ts +323 -0
- package/PLAN.md +0 -33
package/bin/usage/index.js
CHANGED
|
@@ -8,7 +8,10 @@ exports.getClaudeSessionsPath = getClaudeSessionsPath;
|
|
|
8
8
|
exports.formatTokenCount = formatTokenCount;
|
|
9
9
|
exports.buildUsageTotals = buildUsageTotals;
|
|
10
10
|
exports.readUsageTotalsIndex = readUsageTotalsIndex;
|
|
11
|
+
exports.readUsageCostIndex = readUsageCostIndex;
|
|
12
|
+
exports.readUsageSessionCost = readUsageSessionCost;
|
|
11
13
|
exports.resolveUsageTotalsForProfile = resolveUsageTotalsForProfile;
|
|
14
|
+
exports.resolveUsageCostForProfile = resolveUsageCostForProfile;
|
|
12
15
|
exports.syncUsageFromStatuslineInput = syncUsageFromStatuslineInput;
|
|
13
16
|
exports.logProfileUse = logProfileUse;
|
|
14
17
|
exports.logSessionBinding = logSessionBinding;
|
|
@@ -23,6 +26,23 @@ const path = require("path");
|
|
|
23
26
|
const os = require("os");
|
|
24
27
|
const utils_1 = require("../shell/utils");
|
|
25
28
|
const type_1 = require("../profile/type");
|
|
29
|
+
const pricing_1 = require("./pricing");
|
|
30
|
+
function resolveProfileForRecord(config, type, record) {
|
|
31
|
+
if (record.profileKey && config.profiles && config.profiles[record.profileKey]) {
|
|
32
|
+
return config.profiles[record.profileKey];
|
|
33
|
+
}
|
|
34
|
+
if (record.profileName && config.profiles) {
|
|
35
|
+
const matches = Object.entries(config.profiles).find(([key, entry]) => {
|
|
36
|
+
const displayName = (0, type_1.getProfileDisplayName)(key, entry, type || undefined);
|
|
37
|
+
return (displayName === record.profileName ||
|
|
38
|
+
entry.name === record.profileName ||
|
|
39
|
+
key === record.profileName);
|
|
40
|
+
});
|
|
41
|
+
if (matches)
|
|
42
|
+
return matches[1];
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
26
46
|
function resolveDefaultConfigDir(configPath) {
|
|
27
47
|
if (configPath)
|
|
28
48
|
return path.dirname(configPath);
|
|
@@ -72,42 +92,135 @@ function formatTokenCount(value) {
|
|
|
72
92
|
return `${(value / 1000000).toFixed(2)}M`;
|
|
73
93
|
return `${(value / 1000000000).toFixed(2)}B`;
|
|
74
94
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
95
|
+
function createUsageTotals() {
|
|
96
|
+
return {
|
|
97
|
+
today: 0,
|
|
98
|
+
total: 0,
|
|
99
|
+
todayInput: 0,
|
|
100
|
+
totalInput: 0,
|
|
101
|
+
todayOutput: 0,
|
|
102
|
+
totalOutput: 0,
|
|
103
|
+
todayCacheRead: 0,
|
|
104
|
+
totalCacheRead: 0,
|
|
105
|
+
todayCacheWrite: 0,
|
|
106
|
+
totalCacheWrite: 0,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function createUsageCostTotals() {
|
|
110
|
+
return { today: 0, total: 0, todayTokens: 0, totalTokens: 0 };
|
|
111
|
+
}
|
|
112
|
+
function toUsageNumber(value) {
|
|
113
|
+
const num = Number(value !== null && value !== void 0 ? value : 0);
|
|
114
|
+
return Number.isFinite(num) ? num : 0;
|
|
115
|
+
}
|
|
116
|
+
function getTodayWindow() {
|
|
78
117
|
const todayStart = new Date();
|
|
79
118
|
todayStart.setHours(0, 0, 0, 0);
|
|
80
|
-
const
|
|
119
|
+
const startMs = todayStart.getTime();
|
|
81
120
|
const tomorrowStart = new Date(todayStart);
|
|
82
121
|
tomorrowStart.setDate(todayStart.getDate() + 1);
|
|
83
|
-
|
|
122
|
+
return { startMs, endMs: tomorrowStart.getTime() };
|
|
123
|
+
}
|
|
124
|
+
function isTimestampInWindow(ts, startMs, endMs) {
|
|
125
|
+
if (!ts)
|
|
126
|
+
return false;
|
|
127
|
+
const time = new Date(ts).getTime();
|
|
128
|
+
if (Number.isNaN(time))
|
|
129
|
+
return false;
|
|
130
|
+
return time >= startMs && time < endMs;
|
|
131
|
+
}
|
|
132
|
+
function buildUsageTotals(records) {
|
|
133
|
+
var _a;
|
|
134
|
+
const byKey = new Map();
|
|
135
|
+
const byName = new Map();
|
|
136
|
+
const { startMs, endMs } = getTodayWindow();
|
|
84
137
|
const isToday = (ts) => {
|
|
85
|
-
|
|
86
|
-
return false;
|
|
87
|
-
const time = new Date(ts).getTime();
|
|
88
|
-
if (Number.isNaN(time))
|
|
89
|
-
return false;
|
|
90
|
-
return time >= todayStartMs && time < tomorrowStartMs;
|
|
138
|
+
return isTimestampInWindow(ts, startMs, endMs);
|
|
91
139
|
};
|
|
92
|
-
const addTotals = (map, key,
|
|
140
|
+
const addTotals = (map, key, amounts, ts) => {
|
|
93
141
|
if (!key)
|
|
94
142
|
return;
|
|
95
|
-
const current = map.get(key) ||
|
|
96
|
-
current.total +=
|
|
97
|
-
|
|
98
|
-
|
|
143
|
+
const current = map.get(key) || createUsageTotals();
|
|
144
|
+
current.total += amounts.total;
|
|
145
|
+
current.totalInput += amounts.input;
|
|
146
|
+
current.totalOutput += amounts.output;
|
|
147
|
+
current.totalCacheRead += amounts.cacheRead;
|
|
148
|
+
current.totalCacheWrite += amounts.cacheWrite;
|
|
149
|
+
if (isToday(ts)) {
|
|
150
|
+
current.today += amounts.total;
|
|
151
|
+
current.todayInput += amounts.input;
|
|
152
|
+
current.todayOutput += amounts.output;
|
|
153
|
+
current.todayCacheRead += amounts.cacheRead;
|
|
154
|
+
current.todayCacheWrite += amounts.cacheWrite;
|
|
155
|
+
}
|
|
99
156
|
map.set(key, current);
|
|
100
157
|
};
|
|
101
158
|
for (const record of records) {
|
|
102
159
|
const type = normalizeUsageType(record.type) || "";
|
|
103
|
-
const
|
|
160
|
+
const input = toUsageNumber(record.inputTokens);
|
|
161
|
+
const output = toUsageNumber(record.outputTokens);
|
|
162
|
+
const cacheRead = toUsageNumber(record.cacheReadTokens);
|
|
163
|
+
const cacheWrite = toUsageNumber(record.cacheWriteTokens);
|
|
164
|
+
const computedTotal = input + output + cacheRead + cacheWrite;
|
|
165
|
+
const rawTotal = Number((_a = record.totalTokens) !== null && _a !== void 0 ? _a : computedTotal);
|
|
166
|
+
const total = Number.isFinite(rawTotal)
|
|
167
|
+
? Math.max(rawTotal, computedTotal)
|
|
168
|
+
: computedTotal;
|
|
104
169
|
if (!Number.isFinite(total))
|
|
105
170
|
continue;
|
|
106
171
|
if (record.profileKey) {
|
|
107
|
-
addTotals(byKey, `${type}||${record.profileKey}`, total, record.ts);
|
|
172
|
+
addTotals(byKey, `${type}||${record.profileKey}`, { total, input, output, cacheRead, cacheWrite }, record.ts);
|
|
108
173
|
}
|
|
109
174
|
if (record.profileName) {
|
|
110
|
-
addTotals(byName, `${type}||${record.profileName}`, total, record.ts);
|
|
175
|
+
addTotals(byName, `${type}||${record.profileName}`, { total, input, output, cacheRead, cacheWrite }, record.ts);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { byKey, byName };
|
|
179
|
+
}
|
|
180
|
+
function buildUsageCostIndex(records, config) {
|
|
181
|
+
const byKey = new Map();
|
|
182
|
+
const byName = new Map();
|
|
183
|
+
const { startMs, endMs } = getTodayWindow();
|
|
184
|
+
const addCost = (map, key, cost, tokens, ts) => {
|
|
185
|
+
if (!key)
|
|
186
|
+
return;
|
|
187
|
+
const current = map.get(key) || createUsageCostTotals();
|
|
188
|
+
current.total += cost;
|
|
189
|
+
current.totalTokens += tokens;
|
|
190
|
+
if (isTimestampInWindow(ts, startMs, endMs)) {
|
|
191
|
+
current.today += cost;
|
|
192
|
+
current.todayTokens += tokens;
|
|
193
|
+
}
|
|
194
|
+
map.set(key, current);
|
|
195
|
+
};
|
|
196
|
+
for (const record of records) {
|
|
197
|
+
const model = normalizeModelValue(record.model);
|
|
198
|
+
if (!model)
|
|
199
|
+
continue;
|
|
200
|
+
const type = normalizeUsageType(record.type) || "";
|
|
201
|
+
const profile = record.profileKey && config.profiles ? config.profiles[record.profileKey] : null;
|
|
202
|
+
const pricing = (0, pricing_1.resolvePricingForProfile)(config, profile || null, model);
|
|
203
|
+
if (!pricing)
|
|
204
|
+
continue;
|
|
205
|
+
const cost = (0, pricing_1.calculateUsageCost)({
|
|
206
|
+
totalTokens: record.totalTokens,
|
|
207
|
+
inputTokens: record.inputTokens,
|
|
208
|
+
outputTokens: record.outputTokens,
|
|
209
|
+
cacheReadTokens: record.cacheReadTokens,
|
|
210
|
+
cacheWriteTokens: record.cacheWriteTokens,
|
|
211
|
+
}, pricing);
|
|
212
|
+
if (cost === null || !Number.isFinite(cost))
|
|
213
|
+
continue;
|
|
214
|
+
const billedTokens = toUsageNumber(record.inputTokens) +
|
|
215
|
+
toUsageNumber(record.outputTokens) +
|
|
216
|
+
toUsageNumber(record.cacheReadTokens) +
|
|
217
|
+
toUsageNumber(record.cacheWriteTokens);
|
|
218
|
+
const billedTotal = billedTokens > 0 ? billedTokens : toUsageNumber(record.totalTokens);
|
|
219
|
+
if (record.profileKey) {
|
|
220
|
+
addCost(byKey, `${type}||${record.profileKey}`, cost, billedTotal, record.ts);
|
|
221
|
+
}
|
|
222
|
+
if (record.profileName) {
|
|
223
|
+
addCost(byName, `${type}||${record.profileName}`, cost, billedTotal, record.ts);
|
|
111
224
|
}
|
|
112
225
|
}
|
|
113
226
|
return { byKey, byName };
|
|
@@ -121,6 +234,12 @@ function normalizeUsageType(type) {
|
|
|
121
234
|
const trimmed = String(type).trim();
|
|
122
235
|
return trimmed ? trimmed : null;
|
|
123
236
|
}
|
|
237
|
+
function normalizeModelValue(value) {
|
|
238
|
+
if (typeof value !== "string")
|
|
239
|
+
return null;
|
|
240
|
+
const trimmed = value.trim();
|
|
241
|
+
return trimmed ? trimmed : null;
|
|
242
|
+
}
|
|
124
243
|
function buildSessionKey(type, sessionId) {
|
|
125
244
|
const normalized = normalizeUsageType(type || "");
|
|
126
245
|
return normalized ? `${normalized}::${sessionId}` : sessionId;
|
|
@@ -153,6 +272,66 @@ function readUsageTotalsIndex(config, configPath, syncUsage) {
|
|
|
153
272
|
return null;
|
|
154
273
|
return buildUsageTotals(records);
|
|
155
274
|
}
|
|
275
|
+
function readUsageCostIndex(config, configPath, syncUsage) {
|
|
276
|
+
const usagePath = getUsagePath(config, configPath);
|
|
277
|
+
if (!usagePath)
|
|
278
|
+
return null;
|
|
279
|
+
if (syncUsage) {
|
|
280
|
+
syncUsageFromSessions(config, configPath, usagePath);
|
|
281
|
+
}
|
|
282
|
+
const records = readUsageRecords(usagePath);
|
|
283
|
+
if (records.length === 0)
|
|
284
|
+
return null;
|
|
285
|
+
const costs = buildUsageCostIndex(records, config);
|
|
286
|
+
if (costs.byKey.size === 0 && costs.byName.size === 0)
|
|
287
|
+
return null;
|
|
288
|
+
return costs;
|
|
289
|
+
}
|
|
290
|
+
function readUsageSessionCost(config, configPath, type, sessionId, syncUsage) {
|
|
291
|
+
if (!sessionId)
|
|
292
|
+
return null;
|
|
293
|
+
const usagePath = getUsagePath(config, configPath);
|
|
294
|
+
if (!usagePath)
|
|
295
|
+
return null;
|
|
296
|
+
if (syncUsage) {
|
|
297
|
+
syncUsageFromSessions(config, configPath, usagePath);
|
|
298
|
+
}
|
|
299
|
+
const records = readUsageRecords(usagePath);
|
|
300
|
+
if (records.length === 0)
|
|
301
|
+
return null;
|
|
302
|
+
const normalizedType = normalizeUsageType(type);
|
|
303
|
+
let total = 0;
|
|
304
|
+
let hasCost = false;
|
|
305
|
+
for (const record of records) {
|
|
306
|
+
if (!record.sessionId)
|
|
307
|
+
continue;
|
|
308
|
+
if (record.sessionId !== sessionId)
|
|
309
|
+
continue;
|
|
310
|
+
if (normalizedType &&
|
|
311
|
+
normalizeUsageType(record.type) !== normalizedType) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const model = normalizeModelValue(record.model);
|
|
315
|
+
if (!model)
|
|
316
|
+
continue;
|
|
317
|
+
const profile = resolveProfileForRecord(config, normalizedType, record);
|
|
318
|
+
const pricing = (0, pricing_1.resolvePricingForProfile)(config, profile, model);
|
|
319
|
+
if (!pricing)
|
|
320
|
+
continue;
|
|
321
|
+
const cost = (0, pricing_1.calculateUsageCost)({
|
|
322
|
+
totalTokens: record.totalTokens,
|
|
323
|
+
inputTokens: record.inputTokens,
|
|
324
|
+
outputTokens: record.outputTokens,
|
|
325
|
+
cacheReadTokens: record.cacheReadTokens,
|
|
326
|
+
cacheWriteTokens: record.cacheWriteTokens,
|
|
327
|
+
}, pricing);
|
|
328
|
+
if (cost === null || !Number.isFinite(cost))
|
|
329
|
+
continue;
|
|
330
|
+
total += cost;
|
|
331
|
+
hasCost = true;
|
|
332
|
+
}
|
|
333
|
+
return hasCost ? total : null;
|
|
334
|
+
}
|
|
156
335
|
function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
|
|
157
336
|
const keyLookup = buildUsageLookupKey(type, profileKey);
|
|
158
337
|
const nameLookup = buildUsageLookupKey(type, profileName);
|
|
@@ -160,14 +339,24 @@ function resolveUsageTotalsForProfile(totals, type, profileKey, profileName) {
|
|
|
160
339
|
(nameLookup && totals.byName.get(nameLookup)) ||
|
|
161
340
|
null);
|
|
162
341
|
}
|
|
163
|
-
function
|
|
164
|
-
|
|
342
|
+
function resolveUsageCostForProfile(costs, type, profileKey, profileName) {
|
|
343
|
+
const keyLookup = buildUsageLookupKey(type, profileKey);
|
|
344
|
+
const nameLookup = buildUsageLookupKey(type, profileName);
|
|
345
|
+
return ((keyLookup && costs.byKey.get(keyLookup)) ||
|
|
346
|
+
(nameLookup && costs.byName.get(nameLookup)) ||
|
|
347
|
+
null);
|
|
348
|
+
}
|
|
349
|
+
function syncUsageFromStatuslineInput(config, configPath, type, profileKey, profileName, sessionId, totals, cwd, model) {
|
|
350
|
+
var _a, _b, _c, _d, _e;
|
|
165
351
|
if (!sessionId)
|
|
166
352
|
return;
|
|
167
353
|
if (!totals)
|
|
168
354
|
return;
|
|
169
355
|
if (!profileKey && !profileName)
|
|
170
356
|
return;
|
|
357
|
+
const resolvedModel = normalizeModelValue(model);
|
|
358
|
+
if (!resolvedModel)
|
|
359
|
+
return;
|
|
171
360
|
const normalizedType = (0, type_1.normalizeType)(type || "");
|
|
172
361
|
if (!normalizedType)
|
|
173
362
|
return;
|
|
@@ -176,7 +365,12 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
176
365
|
return;
|
|
177
366
|
const inputTokens = (_a = toFiniteNumber(totals.inputTokens)) !== null && _a !== void 0 ? _a : 0;
|
|
178
367
|
const outputTokens = (_b = toFiniteNumber(totals.outputTokens)) !== null && _b !== void 0 ? _b : 0;
|
|
179
|
-
const
|
|
368
|
+
const cacheReadTokens = (_c = toFiniteNumber(totals.cacheReadTokens)) !== null && _c !== void 0 ? _c : 0;
|
|
369
|
+
const cacheWriteTokens = (_d = toFiniteNumber(totals.cacheWriteTokens)) !== null && _d !== void 0 ? _d : 0;
|
|
370
|
+
const totalTokens = (_e = toFiniteNumber(totals.totalTokens)) !== null && _e !== void 0 ? _e : inputTokens +
|
|
371
|
+
outputTokens +
|
|
372
|
+
cacheReadTokens +
|
|
373
|
+
cacheWriteTokens;
|
|
180
374
|
if (!Number.isFinite(totalTokens))
|
|
181
375
|
return;
|
|
182
376
|
const statePath = getUsageStatePath(usagePath, config);
|
|
@@ -189,15 +383,25 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
189
383
|
const sessions = state.sessions || {};
|
|
190
384
|
const key = buildSessionKey(normalizedType, sessionId);
|
|
191
385
|
const prev = sessions[key];
|
|
192
|
-
const prevInput = prev ? prev.inputTokens : 0;
|
|
193
|
-
const prevOutput = prev ? prev.outputTokens : 0;
|
|
194
|
-
const
|
|
386
|
+
const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
|
|
387
|
+
const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
|
|
388
|
+
const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
|
|
389
|
+
const prevCacheWrite = prev ? toUsageNumber(prev.cacheWriteTokens) : 0;
|
|
390
|
+
const prevTotal = prev ? toUsageNumber(prev.totalTokens) : 0;
|
|
195
391
|
let deltaInput = inputTokens - prevInput;
|
|
196
392
|
let deltaOutput = outputTokens - prevOutput;
|
|
393
|
+
let deltaCacheRead = cacheReadTokens - prevCacheRead;
|
|
394
|
+
let deltaCacheWrite = cacheWriteTokens - prevCacheWrite;
|
|
197
395
|
let deltaTotal = totalTokens - prevTotal;
|
|
198
|
-
if (deltaTotal < 0 ||
|
|
396
|
+
if (deltaTotal < 0 ||
|
|
397
|
+
deltaInput < 0 ||
|
|
398
|
+
deltaOutput < 0 ||
|
|
399
|
+
deltaCacheRead < 0 ||
|
|
400
|
+
deltaCacheWrite < 0) {
|
|
199
401
|
deltaInput = inputTokens;
|
|
200
402
|
deltaOutput = outputTokens;
|
|
403
|
+
deltaCacheRead = cacheReadTokens;
|
|
404
|
+
deltaCacheWrite = cacheWriteTokens;
|
|
201
405
|
deltaTotal = totalTokens;
|
|
202
406
|
}
|
|
203
407
|
if (deltaTotal > 0) {
|
|
@@ -206,8 +410,12 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
206
410
|
type: normalizedType,
|
|
207
411
|
profileKey: profileKey || null,
|
|
208
412
|
profileName: profileName || null,
|
|
413
|
+
model: resolvedModel,
|
|
414
|
+
sessionId,
|
|
209
415
|
inputTokens: deltaInput,
|
|
210
416
|
outputTokens: deltaOutput,
|
|
417
|
+
cacheReadTokens: deltaCacheRead,
|
|
418
|
+
cacheWriteTokens: deltaCacheWrite,
|
|
211
419
|
totalTokens: deltaTotal,
|
|
212
420
|
};
|
|
213
421
|
appendUsageRecord(usagePath, record);
|
|
@@ -217,10 +425,13 @@ function syncUsageFromStatuslineInput(config, configPath, type, profileKey, prof
|
|
|
217
425
|
type: normalizedType,
|
|
218
426
|
inputTokens,
|
|
219
427
|
outputTokens,
|
|
428
|
+
cacheReadTokens,
|
|
429
|
+
cacheWriteTokens,
|
|
220
430
|
totalTokens,
|
|
221
431
|
startTs: prev ? prev.startTs : now,
|
|
222
432
|
endTs: now,
|
|
223
433
|
cwd: cwd || (prev ? prev.cwd : null),
|
|
434
|
+
model: resolvedModel,
|
|
224
435
|
};
|
|
225
436
|
state.sessions = sessions;
|
|
226
437
|
writeUsageState(statePath, state);
|
|
@@ -433,6 +644,64 @@ function updateMinMaxTs(current, ts) {
|
|
|
433
644
|
current.end = ts;
|
|
434
645
|
}
|
|
435
646
|
}
|
|
647
|
+
function isPlainObject(value) {
|
|
648
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
649
|
+
}
|
|
650
|
+
function coerceModelFromValue(value) {
|
|
651
|
+
if (typeof value === "string")
|
|
652
|
+
return normalizeModelValue(value);
|
|
653
|
+
if (!isPlainObject(value))
|
|
654
|
+
return null;
|
|
655
|
+
return (normalizeModelValue(value.displayName) ||
|
|
656
|
+
normalizeModelValue(value.display_name) ||
|
|
657
|
+
normalizeModelValue(value.name) ||
|
|
658
|
+
normalizeModelValue(value.id) ||
|
|
659
|
+
normalizeModelValue(value.model) ||
|
|
660
|
+
normalizeModelValue(value.model_name) ||
|
|
661
|
+
normalizeModelValue(value.model_id) ||
|
|
662
|
+
normalizeModelValue(value.modelId) ||
|
|
663
|
+
null);
|
|
664
|
+
}
|
|
665
|
+
function pickModelFromObject(record) {
|
|
666
|
+
return (coerceModelFromValue(record.model) ||
|
|
667
|
+
normalizeModelValue(record.model_name) ||
|
|
668
|
+
normalizeModelValue(record.modelName) ||
|
|
669
|
+
normalizeModelValue(record.model_id) ||
|
|
670
|
+
normalizeModelValue(record.modelId) ||
|
|
671
|
+
normalizeModelValue(record.model_display_name) ||
|
|
672
|
+
normalizeModelValue(record.modelDisplayName) ||
|
|
673
|
+
null);
|
|
674
|
+
}
|
|
675
|
+
function extractModelFromRecord(record) {
|
|
676
|
+
const direct = pickModelFromObject(record);
|
|
677
|
+
if (direct)
|
|
678
|
+
return direct;
|
|
679
|
+
const message = isPlainObject(record.message)
|
|
680
|
+
? record.message
|
|
681
|
+
: null;
|
|
682
|
+
if (message) {
|
|
683
|
+
const fromMessage = pickModelFromObject(message);
|
|
684
|
+
if (fromMessage)
|
|
685
|
+
return fromMessage;
|
|
686
|
+
}
|
|
687
|
+
const payload = isPlainObject(record.payload)
|
|
688
|
+
? record.payload
|
|
689
|
+
: null;
|
|
690
|
+
if (payload) {
|
|
691
|
+
const fromPayload = pickModelFromObject(payload);
|
|
692
|
+
if (fromPayload)
|
|
693
|
+
return fromPayload;
|
|
694
|
+
const info = isPlainObject(payload.info)
|
|
695
|
+
? payload.info
|
|
696
|
+
: null;
|
|
697
|
+
if (info) {
|
|
698
|
+
const fromInfo = pickModelFromObject(info);
|
|
699
|
+
if (fromInfo)
|
|
700
|
+
return fromInfo;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
436
705
|
function parseCodexSessionFile(filePath) {
|
|
437
706
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
438
707
|
const lines = raw.split(/\r?\n/);
|
|
@@ -446,6 +715,7 @@ function parseCodexSessionFile(filePath) {
|
|
|
446
715
|
const tsRange = { start: null, end: null };
|
|
447
716
|
let cwd = null;
|
|
448
717
|
let sessionId = null;
|
|
718
|
+
let model = null;
|
|
449
719
|
for (const line of lines) {
|
|
450
720
|
const trimmed = line.trim();
|
|
451
721
|
if (!trimmed)
|
|
@@ -454,6 +724,11 @@ function parseCodexSessionFile(filePath) {
|
|
|
454
724
|
const parsed = JSON.parse(trimmed);
|
|
455
725
|
if (!parsed || typeof parsed !== "object")
|
|
456
726
|
continue;
|
|
727
|
+
if (!model) {
|
|
728
|
+
const candidate = extractModelFromRecord(parsed);
|
|
729
|
+
if (candidate)
|
|
730
|
+
model = candidate;
|
|
731
|
+
}
|
|
457
732
|
if (parsed.timestamp)
|
|
458
733
|
updateMinMaxTs(tsRange, String(parsed.timestamp));
|
|
459
734
|
if (!cwd && parsed.type === "session_meta") {
|
|
@@ -515,11 +790,14 @@ function parseCodexSessionFile(filePath) {
|
|
|
515
790
|
return {
|
|
516
791
|
inputTokens: maxInput,
|
|
517
792
|
outputTokens: maxOutput,
|
|
793
|
+
cacheReadTokens: 0,
|
|
794
|
+
cacheWriteTokens: 0,
|
|
518
795
|
totalTokens: maxTotal,
|
|
519
796
|
startTs: tsRange.start,
|
|
520
797
|
endTs: tsRange.end,
|
|
521
798
|
cwd,
|
|
522
799
|
sessionId,
|
|
800
|
+
model,
|
|
523
801
|
};
|
|
524
802
|
}
|
|
525
803
|
function parseClaudeSessionFile(filePath) {
|
|
@@ -529,9 +807,12 @@ function parseClaudeSessionFile(filePath) {
|
|
|
529
807
|
let totalTokens = 0;
|
|
530
808
|
let inputTokens = 0;
|
|
531
809
|
let outputTokens = 0;
|
|
810
|
+
let cacheReadTokens = 0;
|
|
811
|
+
let cacheWriteTokens = 0;
|
|
532
812
|
const tsRange = { start: null, end: null };
|
|
533
813
|
let cwd = null;
|
|
534
814
|
let sessionId = null;
|
|
815
|
+
let model = null;
|
|
535
816
|
for (const line of lines) {
|
|
536
817
|
const trimmed = line.trim();
|
|
537
818
|
if (!trimmed)
|
|
@@ -540,6 +821,11 @@ function parseClaudeSessionFile(filePath) {
|
|
|
540
821
|
const parsed = JSON.parse(trimmed);
|
|
541
822
|
if (!parsed || typeof parsed !== "object")
|
|
542
823
|
continue;
|
|
824
|
+
if (!model) {
|
|
825
|
+
const candidate = extractModelFromRecord(parsed);
|
|
826
|
+
if (candidate)
|
|
827
|
+
model = candidate;
|
|
828
|
+
}
|
|
543
829
|
if (parsed.timestamp)
|
|
544
830
|
updateMinMaxTs(tsRange, String(parsed.timestamp));
|
|
545
831
|
if (!cwd && parsed.cwd)
|
|
@@ -559,6 +845,10 @@ function parseClaudeSessionFile(filePath) {
|
|
|
559
845
|
inputTokens += input;
|
|
560
846
|
if (Number.isFinite(output))
|
|
561
847
|
outputTokens += output;
|
|
848
|
+
if (Number.isFinite(cacheCreate))
|
|
849
|
+
cacheWriteTokens += cacheCreate;
|
|
850
|
+
if (Number.isFinite(cacheRead))
|
|
851
|
+
cacheReadTokens += cacheRead;
|
|
562
852
|
totalTokens +=
|
|
563
853
|
(Number.isFinite(input) ? input : 0) +
|
|
564
854
|
(Number.isFinite(output) ? output : 0) +
|
|
@@ -572,11 +862,14 @@ function parseClaudeSessionFile(filePath) {
|
|
|
572
862
|
return {
|
|
573
863
|
inputTokens,
|
|
574
864
|
outputTokens,
|
|
865
|
+
cacheReadTokens,
|
|
866
|
+
cacheWriteTokens,
|
|
575
867
|
totalTokens,
|
|
576
868
|
startTs: tsRange.start,
|
|
577
869
|
endTs: tsRange.end,
|
|
578
870
|
cwd,
|
|
579
871
|
sessionId,
|
|
872
|
+
model,
|
|
580
873
|
};
|
|
581
874
|
}
|
|
582
875
|
const LOCK_STALE_MS = 10 * 60 * 1000;
|
|
@@ -670,7 +963,7 @@ function appendUsageRecord(usagePath, record) {
|
|
|
670
963
|
fs.appendFileSync(usagePath, `${JSON.stringify(record)}\n`, "utf8");
|
|
671
964
|
}
|
|
672
965
|
function readUsageRecords(usagePath) {
|
|
673
|
-
var _a, _b, _c, _d, _e;
|
|
966
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
674
967
|
if (!usagePath || !fs.existsSync(usagePath))
|
|
675
968
|
return [];
|
|
676
969
|
const raw = fs.readFileSync(usagePath, "utf8");
|
|
@@ -686,16 +979,40 @@ function readUsageRecords(usagePath) {
|
|
|
686
979
|
continue;
|
|
687
980
|
const input = Number((_a = parsed.inputTokens) !== null && _a !== void 0 ? _a : 0);
|
|
688
981
|
const output = Number((_b = parsed.outputTokens) !== null && _b !== void 0 ? _b : 0);
|
|
689
|
-
const
|
|
690
|
-
const
|
|
982
|
+
const cacheRead = Number((_e = (_d = (_c = parsed.cacheReadTokens) !== null && _c !== void 0 ? _c : parsed.cache_read_input_tokens) !== null && _d !== void 0 ? _d : parsed.cacheReadInputTokens) !== null && _e !== void 0 ? _e : 0);
|
|
983
|
+
const cacheWrite = Number((_j = (_h = (_g = (_f = parsed.cacheWriteTokens) !== null && _f !== void 0 ? _f : parsed.cache_creation_input_tokens) !== null && _g !== void 0 ? _g : parsed.cache_write_input_tokens) !== null && _h !== void 0 ? _h : parsed.cacheWriteInputTokens) !== null && _j !== void 0 ? _j : 0);
|
|
984
|
+
const computedTotal = (Number.isFinite(input) ? input : 0) +
|
|
985
|
+
(Number.isFinite(output) ? output : 0) +
|
|
986
|
+
(Number.isFinite(cacheRead) ? cacheRead : 0) +
|
|
987
|
+
(Number.isFinite(cacheWrite) ? cacheWrite : 0);
|
|
988
|
+
const total = Number((_k = parsed.totalTokens) !== null && _k !== void 0 ? _k : computedTotal);
|
|
989
|
+
const finalTotal = Number.isFinite(total)
|
|
990
|
+
? Math.max(total, computedTotal)
|
|
991
|
+
: computedTotal;
|
|
992
|
+
const type = (0, type_1.normalizeType)(parsed.type) || String((_l = parsed.type) !== null && _l !== void 0 ? _l : "unknown");
|
|
993
|
+
const model = normalizeModelValue(parsed.model) ||
|
|
994
|
+
normalizeModelValue(parsed.model_name) ||
|
|
995
|
+
normalizeModelValue(parsed.modelName) ||
|
|
996
|
+
normalizeModelValue(parsed.model_id) ||
|
|
997
|
+
normalizeModelValue(parsed.modelId) ||
|
|
998
|
+
null;
|
|
999
|
+
const sessionId = parsed.sessionId ||
|
|
1000
|
+
parsed.session_id ||
|
|
1001
|
+
parsed.sessionID ||
|
|
1002
|
+
parsed.session ||
|
|
1003
|
+
null;
|
|
691
1004
|
records.push({
|
|
692
|
-
ts: String((
|
|
1005
|
+
ts: String((_m = parsed.ts) !== null && _m !== void 0 ? _m : ""),
|
|
693
1006
|
type,
|
|
694
1007
|
profileKey: parsed.profileKey ? String(parsed.profileKey) : null,
|
|
695
1008
|
profileName: parsed.profileName ? String(parsed.profileName) : null,
|
|
1009
|
+
model,
|
|
1010
|
+
sessionId: sessionId ? String(sessionId) : null,
|
|
696
1011
|
inputTokens: Number.isFinite(input) ? input : 0,
|
|
697
1012
|
outputTokens: Number.isFinite(output) ? output : 0,
|
|
698
|
-
|
|
1013
|
+
cacheReadTokens: Number.isFinite(cacheRead) ? cacheRead : 0,
|
|
1014
|
+
cacheWriteTokens: Number.isFinite(cacheWrite) ? cacheWrite : 0,
|
|
1015
|
+
totalTokens: Number.isFinite(finalTotal) ? finalTotal : 0,
|
|
699
1016
|
});
|
|
700
1017
|
}
|
|
701
1018
|
catch {
|
|
@@ -747,30 +1064,52 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
747
1064
|
return;
|
|
748
1065
|
const sessionKey = stats.sessionId ? buildSessionKey(type, stats.sessionId) : null;
|
|
749
1066
|
const sessionPrev = sessionKey ? sessions[sessionKey] : null;
|
|
750
|
-
const
|
|
751
|
-
|
|
752
|
-
|
|
1067
|
+
const resolvedModel = (sessionPrev && sessionPrev.model) ||
|
|
1068
|
+
stats.model ||
|
|
1069
|
+
(prev && prev.model) ||
|
|
1070
|
+
null;
|
|
1071
|
+
const prevInput = prev ? toUsageNumber(prev.inputTokens) : 0;
|
|
1072
|
+
const prevOutput = prev ? toUsageNumber(prev.outputTokens) : 0;
|
|
1073
|
+
const prevCacheRead = prev ? toUsageNumber(prev.cacheReadTokens) : 0;
|
|
1074
|
+
const prevCacheWrite = prev ? toUsageNumber(prev.cacheWriteTokens) : 0;
|
|
1075
|
+
const prevTotal = prev ? toUsageNumber(prev.totalTokens) : 0;
|
|
753
1076
|
const prevInputMax = sessionPrev
|
|
754
|
-
? Math.max(prevInput, sessionPrev.inputTokens)
|
|
1077
|
+
? Math.max(prevInput, toUsageNumber(sessionPrev.inputTokens))
|
|
755
1078
|
: prevInput;
|
|
756
1079
|
const prevOutputMax = sessionPrev
|
|
757
|
-
? Math.max(prevOutput, sessionPrev.outputTokens)
|
|
1080
|
+
? Math.max(prevOutput, toUsageNumber(sessionPrev.outputTokens))
|
|
758
1081
|
: prevOutput;
|
|
1082
|
+
const prevCacheReadMax = sessionPrev
|
|
1083
|
+
? Math.max(prevCacheRead, toUsageNumber(sessionPrev.cacheReadTokens))
|
|
1084
|
+
: prevCacheRead;
|
|
1085
|
+
const prevCacheWriteMax = sessionPrev
|
|
1086
|
+
? Math.max(prevCacheWrite, toUsageNumber(sessionPrev.cacheWriteTokens))
|
|
1087
|
+
: prevCacheWrite;
|
|
759
1088
|
const prevTotalMax = sessionPrev
|
|
760
|
-
? Math.max(prevTotal, sessionPrev.totalTokens)
|
|
1089
|
+
? Math.max(prevTotal, toUsageNumber(sessionPrev.totalTokens))
|
|
761
1090
|
: prevTotal;
|
|
762
1091
|
let deltaInput = stats.inputTokens - prevInputMax;
|
|
763
1092
|
let deltaOutput = stats.outputTokens - prevOutputMax;
|
|
1093
|
+
let deltaCacheRead = stats.cacheReadTokens - prevCacheReadMax;
|
|
1094
|
+
let deltaCacheWrite = stats.cacheWriteTokens - prevCacheWriteMax;
|
|
764
1095
|
let deltaTotal = stats.totalTokens - prevTotalMax;
|
|
765
|
-
if (deltaTotal < 0 ||
|
|
1096
|
+
if (deltaTotal < 0 ||
|
|
1097
|
+
deltaInput < 0 ||
|
|
1098
|
+
deltaOutput < 0 ||
|
|
1099
|
+
deltaCacheRead < 0 ||
|
|
1100
|
+
deltaCacheWrite < 0) {
|
|
766
1101
|
if (sessionPrev) {
|
|
767
1102
|
deltaInput = 0;
|
|
768
1103
|
deltaOutput = 0;
|
|
1104
|
+
deltaCacheRead = 0;
|
|
1105
|
+
deltaCacheWrite = 0;
|
|
769
1106
|
deltaTotal = 0;
|
|
770
1107
|
}
|
|
771
1108
|
else {
|
|
772
1109
|
deltaInput = stats.inputTokens;
|
|
773
1110
|
deltaOutput = stats.outputTokens;
|
|
1111
|
+
deltaCacheRead = stats.cacheReadTokens;
|
|
1112
|
+
deltaCacheWrite = stats.cacheWriteTokens;
|
|
774
1113
|
deltaTotal = stats.totalTokens;
|
|
775
1114
|
}
|
|
776
1115
|
}
|
|
@@ -780,30 +1119,43 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
780
1119
|
type,
|
|
781
1120
|
profileKey: resolved.match.profileKey,
|
|
782
1121
|
profileName: resolved.match.profileName,
|
|
1122
|
+
model: resolvedModel,
|
|
1123
|
+
sessionId: stats.sessionId,
|
|
783
1124
|
inputTokens: deltaInput,
|
|
784
1125
|
outputTokens: deltaOutput,
|
|
1126
|
+
cacheReadTokens: deltaCacheRead,
|
|
1127
|
+
cacheWriteTokens: deltaCacheWrite,
|
|
785
1128
|
totalTokens: deltaTotal,
|
|
786
1129
|
};
|
|
787
1130
|
appendUsageRecord(usagePath, record);
|
|
788
1131
|
}
|
|
789
1132
|
if (sessionKey) {
|
|
790
1133
|
const nextInput = sessionPrev
|
|
791
|
-
? Math.max(sessionPrev.inputTokens, stats.inputTokens)
|
|
1134
|
+
? Math.max(toUsageNumber(sessionPrev.inputTokens), stats.inputTokens)
|
|
792
1135
|
: stats.inputTokens;
|
|
793
1136
|
const nextOutput = sessionPrev
|
|
794
|
-
? Math.max(sessionPrev.outputTokens, stats.outputTokens)
|
|
1137
|
+
? Math.max(toUsageNumber(sessionPrev.outputTokens), stats.outputTokens)
|
|
795
1138
|
: stats.outputTokens;
|
|
1139
|
+
const nextCacheRead = sessionPrev
|
|
1140
|
+
? Math.max(toUsageNumber(sessionPrev.cacheReadTokens), stats.cacheReadTokens)
|
|
1141
|
+
: stats.cacheReadTokens;
|
|
1142
|
+
const nextCacheWrite = sessionPrev
|
|
1143
|
+
? Math.max(toUsageNumber(sessionPrev.cacheWriteTokens), stats.cacheWriteTokens)
|
|
1144
|
+
: stats.cacheWriteTokens;
|
|
796
1145
|
const nextTotal = sessionPrev
|
|
797
|
-
? Math.max(sessionPrev.totalTokens, stats.totalTokens)
|
|
1146
|
+
? Math.max(toUsageNumber(sessionPrev.totalTokens), stats.totalTokens)
|
|
798
1147
|
: stats.totalTokens;
|
|
799
1148
|
sessions[sessionKey] = {
|
|
800
1149
|
type,
|
|
801
1150
|
inputTokens: nextInput,
|
|
802
1151
|
outputTokens: nextOutput,
|
|
1152
|
+
cacheReadTokens: nextCacheRead,
|
|
1153
|
+
cacheWriteTokens: nextCacheWrite,
|
|
803
1154
|
totalTokens: nextTotal,
|
|
804
1155
|
startTs: sessionPrev ? sessionPrev.startTs : stats.startTs,
|
|
805
1156
|
endTs: stats.endTs || (sessionPrev ? sessionPrev.endTs : null),
|
|
806
1157
|
cwd: stats.cwd || (sessionPrev ? sessionPrev.cwd : null),
|
|
1158
|
+
model: resolvedModel,
|
|
807
1159
|
};
|
|
808
1160
|
}
|
|
809
1161
|
files[filePath] = {
|
|
@@ -812,10 +1164,13 @@ function syncUsageFromSessions(config, configPath, usagePath) {
|
|
|
812
1164
|
type,
|
|
813
1165
|
inputTokens: stats.inputTokens,
|
|
814
1166
|
outputTokens: stats.outputTokens,
|
|
1167
|
+
cacheReadTokens: stats.cacheReadTokens,
|
|
1168
|
+
cacheWriteTokens: stats.cacheWriteTokens,
|
|
815
1169
|
totalTokens: stats.totalTokens,
|
|
816
1170
|
startTs: stats.startTs,
|
|
817
1171
|
endTs: stats.endTs,
|
|
818
1172
|
cwd: stats.cwd,
|
|
1173
|
+
model: resolvedModel,
|
|
819
1174
|
};
|
|
820
1175
|
};
|
|
821
1176
|
for (const filePath of codexFiles)
|