@tokscale/cli 1.0.6 → 1.0.8
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.d.ts +1 -1
- package/dist/cli.js +8 -3
- package/dist/cli.js.map +1 -1
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +1 -114
- package/dist/native.js.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/package.json +5 -10
- package/src/auth.ts +211 -0
- package/src/cli.ts +1042 -0
- package/src/credentials.ts +123 -0
- package/src/cursor.ts +558 -0
- package/src/graph-types.ts +188 -0
- package/src/graph.ts +485 -0
- package/src/native-runner.ts +105 -0
- package/src/native.ts +807 -0
- package/src/pricing.ts +309 -0
- package/src/sessions/claudecode.ts +119 -0
- package/src/sessions/codex.ts +227 -0
- package/src/sessions/gemini.ts +108 -0
- package/src/sessions/index.ts +126 -0
- package/src/sessions/opencode.ts +94 -0
- package/src/sessions/reports.ts +475 -0
- package/src/sessions/types.ts +59 -0
- package/src/spinner.ts +283 -0
- package/src/submit.ts +175 -0
- package/src/table.ts +233 -0
- package/src/tui/components/Header.tsx +1 -1
- package/src/types.d.ts +28 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript fallback report generators
|
|
3
|
+
*
|
|
4
|
+
* Used when native Rust module is not available.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { UnifiedMessage, TokenBreakdown, SourceType } from "./types.js";
|
|
8
|
+
import type { PricingEntry } from "../pricing.js";
|
|
9
|
+
import { normalizeModelName, isWordBoundaryMatch } from "../pricing.js";
|
|
10
|
+
import type { TokenContributionData } from "../graph-types.js";
|
|
11
|
+
|
|
12
|
+
export interface ModelUsage {
|
|
13
|
+
source: string;
|
|
14
|
+
model: string;
|
|
15
|
+
provider: string;
|
|
16
|
+
input: number;
|
|
17
|
+
output: number;
|
|
18
|
+
cacheRead: number;
|
|
19
|
+
cacheWrite: number;
|
|
20
|
+
reasoning: number;
|
|
21
|
+
messageCount: number;
|
|
22
|
+
cost: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ModelReport {
|
|
26
|
+
entries: ModelUsage[];
|
|
27
|
+
totalInput: number;
|
|
28
|
+
totalOutput: number;
|
|
29
|
+
totalCacheRead: number;
|
|
30
|
+
totalCacheWrite: number;
|
|
31
|
+
totalMessages: number;
|
|
32
|
+
totalCost: number;
|
|
33
|
+
processingTimeMs: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MonthlyUsage {
|
|
37
|
+
month: string;
|
|
38
|
+
models: string[];
|
|
39
|
+
input: number;
|
|
40
|
+
output: number;
|
|
41
|
+
cacheRead: number;
|
|
42
|
+
cacheWrite: number;
|
|
43
|
+
messageCount: number;
|
|
44
|
+
cost: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface MonthlyReport {
|
|
48
|
+
entries: MonthlyUsage[];
|
|
49
|
+
totalCost: number;
|
|
50
|
+
processingTimeMs: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface PricingInfo {
|
|
54
|
+
inputCostPerToken: number;
|
|
55
|
+
outputCostPerToken: number;
|
|
56
|
+
cacheReadInputTokenCost?: number;
|
|
57
|
+
cacheCreationInputTokenCost?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface PricingLookup {
|
|
61
|
+
get(modelId: string): PricingInfo | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildPricingLookup(pricing: PricingEntry[]): PricingLookup {
|
|
65
|
+
const map = new Map<string, PricingInfo>();
|
|
66
|
+
for (const entry of pricing) {
|
|
67
|
+
map.set(entry.modelId, entry.pricing);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const sortedKeys = [...map.keys()].sort();
|
|
71
|
+
const prefixes = ["anthropic/", "openai/", "google/", "bedrock/"];
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
get(modelId: string): PricingInfo | undefined {
|
|
75
|
+
if (map.has(modelId)) return map.get(modelId);
|
|
76
|
+
|
|
77
|
+
for (const prefix of prefixes) {
|
|
78
|
+
if (map.has(prefix + modelId)) return map.get(prefix + modelId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const normalized = normalizeModelName(modelId);
|
|
82
|
+
if (normalized) {
|
|
83
|
+
if (map.has(normalized)) return map.get(normalized);
|
|
84
|
+
for (const prefix of prefixes) {
|
|
85
|
+
if (map.has(prefix + normalized)) return map.get(prefix + normalized);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const lowerModelId = modelId.toLowerCase();
|
|
90
|
+
const lowerNormalized = normalized?.toLowerCase();
|
|
91
|
+
|
|
92
|
+
for (const key of sortedKeys) {
|
|
93
|
+
const lowerKey = key.toLowerCase();
|
|
94
|
+
if (isWordBoundaryMatch(lowerKey, lowerModelId)) return map.get(key);
|
|
95
|
+
if (lowerNormalized && isWordBoundaryMatch(lowerKey, lowerNormalized)) return map.get(key);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const key of sortedKeys) {
|
|
99
|
+
const lowerKey = key.toLowerCase();
|
|
100
|
+
if (isWordBoundaryMatch(lowerModelId, lowerKey)) return map.get(key);
|
|
101
|
+
if (lowerNormalized && isWordBoundaryMatch(lowerNormalized, lowerKey)) return map.get(key);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return undefined;
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate cost for a message based on token counts and pricing
|
|
111
|
+
*/
|
|
112
|
+
function calculateCost(
|
|
113
|
+
tokens: TokenBreakdown,
|
|
114
|
+
pricing: PricingInfo | undefined
|
|
115
|
+
): number {
|
|
116
|
+
if (!pricing) return 0;
|
|
117
|
+
|
|
118
|
+
let cost = 0;
|
|
119
|
+
cost += tokens.input * pricing.inputCostPerToken;
|
|
120
|
+
cost += tokens.output * pricing.outputCostPerToken;
|
|
121
|
+
cost += tokens.cacheRead * (pricing.cacheReadInputTokenCost || 0);
|
|
122
|
+
cost += tokens.cacheWrite * (pricing.cacheCreationInputTokenCost || 0);
|
|
123
|
+
// Note: reasoning tokens are typically charged at output rate
|
|
124
|
+
cost += tokens.reasoning * pricing.outputCostPerToken;
|
|
125
|
+
|
|
126
|
+
return cost;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generate model report from parsed messages
|
|
131
|
+
*/
|
|
132
|
+
export function generateModelReport(
|
|
133
|
+
messages: UnifiedMessage[],
|
|
134
|
+
pricing: PricingEntry[],
|
|
135
|
+
startTime: number
|
|
136
|
+
): ModelReport {
|
|
137
|
+
const pricingMap = buildPricingLookup(pricing);
|
|
138
|
+
|
|
139
|
+
// Aggregate by source + model
|
|
140
|
+
const aggregated = new Map<string, ModelUsage>();
|
|
141
|
+
|
|
142
|
+
for (const msg of messages) {
|
|
143
|
+
const key = `${msg.source}:${msg.modelId}`;
|
|
144
|
+
let usage = aggregated.get(key);
|
|
145
|
+
|
|
146
|
+
if (!usage) {
|
|
147
|
+
usage = {
|
|
148
|
+
source: msg.source,
|
|
149
|
+
model: msg.modelId,
|
|
150
|
+
provider: msg.providerId,
|
|
151
|
+
input: 0,
|
|
152
|
+
output: 0,
|
|
153
|
+
cacheRead: 0,
|
|
154
|
+
cacheWrite: 0,
|
|
155
|
+
reasoning: 0,
|
|
156
|
+
messageCount: 0,
|
|
157
|
+
cost: 0,
|
|
158
|
+
};
|
|
159
|
+
aggregated.set(key, usage);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
usage.input += msg.tokens.input;
|
|
163
|
+
usage.output += msg.tokens.output;
|
|
164
|
+
usage.cacheRead += msg.tokens.cacheRead;
|
|
165
|
+
usage.cacheWrite += msg.tokens.cacheWrite;
|
|
166
|
+
usage.reasoning += msg.tokens.reasoning;
|
|
167
|
+
usage.messageCount++;
|
|
168
|
+
|
|
169
|
+
const calculatedCost = calculateCost(msg.tokens, pricingMap.get(msg.modelId));
|
|
170
|
+
usage.cost += calculatedCost > 0 ? calculatedCost : msg.cost;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const entries = Array.from(aggregated.values()).sort((a, b) => b.cost - a.cost);
|
|
174
|
+
|
|
175
|
+
const totals = entries.reduce(
|
|
176
|
+
(acc, e) => ({
|
|
177
|
+
input: acc.input + e.input,
|
|
178
|
+
output: acc.output + e.output,
|
|
179
|
+
cacheRead: acc.cacheRead + e.cacheRead,
|
|
180
|
+
cacheWrite: acc.cacheWrite + e.cacheWrite,
|
|
181
|
+
messages: acc.messages + e.messageCount,
|
|
182
|
+
cost: acc.cost + e.cost,
|
|
183
|
+
}),
|
|
184
|
+
{ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, messages: 0, cost: 0 }
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
entries,
|
|
189
|
+
totalInput: totals.input,
|
|
190
|
+
totalOutput: totals.output,
|
|
191
|
+
totalCacheRead: totals.cacheRead,
|
|
192
|
+
totalCacheWrite: totals.cacheWrite,
|
|
193
|
+
totalMessages: totals.messages,
|
|
194
|
+
totalCost: totals.cost,
|
|
195
|
+
processingTimeMs: performance.now() - startTime,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Generate monthly report from parsed messages
|
|
201
|
+
*/
|
|
202
|
+
export function generateMonthlyReport(
|
|
203
|
+
messages: UnifiedMessage[],
|
|
204
|
+
pricing: PricingEntry[],
|
|
205
|
+
startTime: number
|
|
206
|
+
): MonthlyReport {
|
|
207
|
+
const pricingMap = buildPricingLookup(pricing);
|
|
208
|
+
|
|
209
|
+
// Aggregate by month
|
|
210
|
+
const aggregated = new Map<
|
|
211
|
+
string,
|
|
212
|
+
{
|
|
213
|
+
models: Set<string>;
|
|
214
|
+
input: number;
|
|
215
|
+
output: number;
|
|
216
|
+
cacheRead: number;
|
|
217
|
+
cacheWrite: number;
|
|
218
|
+
messageCount: number;
|
|
219
|
+
cost: number;
|
|
220
|
+
}
|
|
221
|
+
>();
|
|
222
|
+
|
|
223
|
+
for (const msg of messages) {
|
|
224
|
+
// Extract YYYY-MM from date
|
|
225
|
+
const month = msg.date.slice(0, 7);
|
|
226
|
+
let usage = aggregated.get(month);
|
|
227
|
+
|
|
228
|
+
if (!usage) {
|
|
229
|
+
usage = {
|
|
230
|
+
models: new Set(),
|
|
231
|
+
input: 0,
|
|
232
|
+
output: 0,
|
|
233
|
+
cacheRead: 0,
|
|
234
|
+
cacheWrite: 0,
|
|
235
|
+
messageCount: 0,
|
|
236
|
+
cost: 0,
|
|
237
|
+
};
|
|
238
|
+
aggregated.set(month, usage);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
usage.models.add(msg.modelId);
|
|
242
|
+
usage.input += msg.tokens.input;
|
|
243
|
+
usage.output += msg.tokens.output;
|
|
244
|
+
usage.cacheRead += msg.tokens.cacheRead;
|
|
245
|
+
usage.cacheWrite += msg.tokens.cacheWrite;
|
|
246
|
+
usage.messageCount++;
|
|
247
|
+
|
|
248
|
+
const calculatedCost = calculateCost(msg.tokens, pricingMap.get(msg.modelId));
|
|
249
|
+
usage.cost += calculatedCost > 0 ? calculatedCost : msg.cost;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const entries: MonthlyUsage[] = Array.from(aggregated.entries())
|
|
253
|
+
.map(([month, data]) => ({
|
|
254
|
+
month,
|
|
255
|
+
models: Array.from(data.models),
|
|
256
|
+
input: data.input,
|
|
257
|
+
output: data.output,
|
|
258
|
+
cacheRead: data.cacheRead,
|
|
259
|
+
cacheWrite: data.cacheWrite,
|
|
260
|
+
messageCount: data.messageCount,
|
|
261
|
+
cost: data.cost,
|
|
262
|
+
}))
|
|
263
|
+
.sort((a, b) => b.month.localeCompare(a.month)); // Most recent first
|
|
264
|
+
|
|
265
|
+
const totalCost = entries.reduce((sum, e) => sum + e.cost, 0);
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
entries,
|
|
269
|
+
totalCost,
|
|
270
|
+
processingTimeMs: performance.now() - startTime,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Generate graph data from parsed messages
|
|
276
|
+
*/
|
|
277
|
+
export function generateGraphData(
|
|
278
|
+
messages: UnifiedMessage[],
|
|
279
|
+
pricing: PricingEntry[],
|
|
280
|
+
startTime: number
|
|
281
|
+
): TokenContributionData {
|
|
282
|
+
const pricingMap = buildPricingLookup(pricing);
|
|
283
|
+
|
|
284
|
+
// Group messages by date
|
|
285
|
+
const byDate = new Map<
|
|
286
|
+
string,
|
|
287
|
+
{
|
|
288
|
+
tokens: TokenBreakdown;
|
|
289
|
+
cost: number;
|
|
290
|
+
messages: number;
|
|
291
|
+
sources: Map<
|
|
292
|
+
string,
|
|
293
|
+
{
|
|
294
|
+
modelId: string;
|
|
295
|
+
providerId: string;
|
|
296
|
+
tokens: TokenBreakdown;
|
|
297
|
+
cost: number;
|
|
298
|
+
messages: number;
|
|
299
|
+
}
|
|
300
|
+
>;
|
|
301
|
+
}
|
|
302
|
+
>();
|
|
303
|
+
|
|
304
|
+
const allSources = new Set<string>();
|
|
305
|
+
const allModels = new Set<string>();
|
|
306
|
+
let totalTokens = 0;
|
|
307
|
+
let totalCost = 0;
|
|
308
|
+
let maxCostInSingleDay = 0;
|
|
309
|
+
|
|
310
|
+
for (const msg of messages) {
|
|
311
|
+
allSources.add(msg.source);
|
|
312
|
+
allModels.add(msg.modelId);
|
|
313
|
+
|
|
314
|
+
let dayData = byDate.get(msg.date);
|
|
315
|
+
if (!dayData) {
|
|
316
|
+
dayData = {
|
|
317
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 },
|
|
318
|
+
cost: 0,
|
|
319
|
+
messages: 0,
|
|
320
|
+
sources: new Map(),
|
|
321
|
+
};
|
|
322
|
+
byDate.set(msg.date, dayData);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const calculatedCost = calculateCost(msg.tokens, pricingMap.get(msg.modelId));
|
|
326
|
+
const msgCost = calculatedCost > 0 ? calculatedCost : msg.cost;
|
|
327
|
+
|
|
328
|
+
dayData.tokens.input += msg.tokens.input;
|
|
329
|
+
dayData.tokens.output += msg.tokens.output;
|
|
330
|
+
dayData.tokens.cacheRead += msg.tokens.cacheRead;
|
|
331
|
+
dayData.tokens.cacheWrite += msg.tokens.cacheWrite;
|
|
332
|
+
dayData.tokens.reasoning += msg.tokens.reasoning;
|
|
333
|
+
dayData.cost += msgCost;
|
|
334
|
+
dayData.messages++;
|
|
335
|
+
|
|
336
|
+
// Source contribution
|
|
337
|
+
const sourceKey = `${msg.source}:${msg.modelId}`;
|
|
338
|
+
let sourceData = dayData.sources.get(sourceKey);
|
|
339
|
+
if (!sourceData) {
|
|
340
|
+
sourceData = {
|
|
341
|
+
modelId: msg.modelId,
|
|
342
|
+
providerId: msg.providerId,
|
|
343
|
+
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0 },
|
|
344
|
+
cost: 0,
|
|
345
|
+
messages: 0,
|
|
346
|
+
};
|
|
347
|
+
dayData.sources.set(sourceKey, sourceData);
|
|
348
|
+
}
|
|
349
|
+
sourceData.tokens.input += msg.tokens.input;
|
|
350
|
+
sourceData.tokens.output += msg.tokens.output;
|
|
351
|
+
sourceData.tokens.cacheRead += msg.tokens.cacheRead;
|
|
352
|
+
sourceData.tokens.cacheWrite += msg.tokens.cacheWrite;
|
|
353
|
+
sourceData.tokens.reasoning += msg.tokens.reasoning;
|
|
354
|
+
sourceData.cost += msgCost;
|
|
355
|
+
sourceData.messages++;
|
|
356
|
+
|
|
357
|
+
const msgTokens =
|
|
358
|
+
msg.tokens.input +
|
|
359
|
+
msg.tokens.output +
|
|
360
|
+
msg.tokens.cacheRead +
|
|
361
|
+
msg.tokens.cacheWrite +
|
|
362
|
+
msg.tokens.reasoning;
|
|
363
|
+
totalTokens += msgTokens;
|
|
364
|
+
totalCost += msgCost;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Calculate max cost per day for intensity calculation
|
|
368
|
+
for (const dayData of byDate.values()) {
|
|
369
|
+
if (dayData.cost > maxCostInSingleDay) {
|
|
370
|
+
maxCostInSingleDay = dayData.cost;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Build contributions array
|
|
375
|
+
const contributions = Array.from(byDate.entries())
|
|
376
|
+
.map(([date, data]) => {
|
|
377
|
+
const dayTokens =
|
|
378
|
+
data.tokens.input +
|
|
379
|
+
data.tokens.output +
|
|
380
|
+
data.tokens.cacheRead +
|
|
381
|
+
data.tokens.cacheWrite +
|
|
382
|
+
data.tokens.reasoning;
|
|
383
|
+
|
|
384
|
+
// Calculate intensity (0-4 scale based on cost)
|
|
385
|
+
let intensity: 0 | 1 | 2 | 3 | 4 = 0;
|
|
386
|
+
if (maxCostInSingleDay > 0) {
|
|
387
|
+
const ratio = data.cost / maxCostInSingleDay;
|
|
388
|
+
if (ratio > 0.75) intensity = 4;
|
|
389
|
+
else if (ratio > 0.5) intensity = 3;
|
|
390
|
+
else if (ratio > 0.25) intensity = 2;
|
|
391
|
+
else if (ratio > 0) intensity = 1;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
date,
|
|
396
|
+
totals: {
|
|
397
|
+
tokens: dayTokens,
|
|
398
|
+
cost: data.cost,
|
|
399
|
+
messages: data.messages,
|
|
400
|
+
},
|
|
401
|
+
intensity,
|
|
402
|
+
tokenBreakdown: data.tokens,
|
|
403
|
+
sources: Array.from(data.sources.entries()).map(([key, src]) => ({
|
|
404
|
+
source: key.split(":")[0] as SourceType,
|
|
405
|
+
modelId: src.modelId,
|
|
406
|
+
providerId: src.providerId,
|
|
407
|
+
tokens: src.tokens,
|
|
408
|
+
cost: src.cost,
|
|
409
|
+
messages: src.messages,
|
|
410
|
+
})),
|
|
411
|
+
};
|
|
412
|
+
})
|
|
413
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
414
|
+
|
|
415
|
+
// Determine date range
|
|
416
|
+
const dates = Array.from(byDate.keys()).sort();
|
|
417
|
+
const rangeStart = dates[0] || new Date().toISOString().split("T")[0];
|
|
418
|
+
const rangeEnd = dates[dates.length - 1] || rangeStart;
|
|
419
|
+
|
|
420
|
+
// Group by year
|
|
421
|
+
const yearData = new Map<string, { tokens: number; cost: number; start: string; end: string }>();
|
|
422
|
+
for (const [date, data] of byDate.entries()) {
|
|
423
|
+
const year = date.slice(0, 4);
|
|
424
|
+
let yd = yearData.get(year);
|
|
425
|
+
if (!yd) {
|
|
426
|
+
yd = { tokens: 0, cost: 0, start: date, end: date };
|
|
427
|
+
yearData.set(year, yd);
|
|
428
|
+
}
|
|
429
|
+
const dayTokens =
|
|
430
|
+
data.tokens.input +
|
|
431
|
+
data.tokens.output +
|
|
432
|
+
data.tokens.cacheRead +
|
|
433
|
+
data.tokens.cacheWrite +
|
|
434
|
+
data.tokens.reasoning;
|
|
435
|
+
yd.tokens += dayTokens;
|
|
436
|
+
yd.cost += data.cost;
|
|
437
|
+
if (date < yd.start) yd.start = date;
|
|
438
|
+
if (date > yd.end) yd.end = date;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const years = Array.from(yearData.entries())
|
|
442
|
+
.map(([year, data]) => ({
|
|
443
|
+
year,
|
|
444
|
+
totalTokens: data.tokens,
|
|
445
|
+
totalCost: data.cost,
|
|
446
|
+
range: { start: data.start, end: data.end },
|
|
447
|
+
}))
|
|
448
|
+
.sort((a, b) => b.year.localeCompare(a.year));
|
|
449
|
+
|
|
450
|
+
const activeDays = byDate.size;
|
|
451
|
+
const totalDays =
|
|
452
|
+
activeDays > 0
|
|
453
|
+
? Math.ceil((new Date(rangeEnd).getTime() - new Date(rangeStart).getTime()) / 86400000) + 1
|
|
454
|
+
: 0;
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
meta: {
|
|
458
|
+
generatedAt: new Date().toISOString(),
|
|
459
|
+
version: "1.0.0-ts-fallback",
|
|
460
|
+
dateRange: { start: rangeStart, end: rangeEnd },
|
|
461
|
+
},
|
|
462
|
+
summary: {
|
|
463
|
+
totalTokens,
|
|
464
|
+
totalCost,
|
|
465
|
+
totalDays,
|
|
466
|
+
activeDays,
|
|
467
|
+
averagePerDay: activeDays > 0 ? totalCost / activeDays : 0,
|
|
468
|
+
maxCostInSingleDay,
|
|
469
|
+
sources: Array.from(allSources) as SourceType[],
|
|
470
|
+
models: Array.from(allModels),
|
|
471
|
+
},
|
|
472
|
+
years,
|
|
473
|
+
contributions,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified message types for session parsers (matches Rust UnifiedMessage)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface TokenBreakdown {
|
|
6
|
+
input: number;
|
|
7
|
+
output: number;
|
|
8
|
+
cacheRead: number;
|
|
9
|
+
cacheWrite: number;
|
|
10
|
+
reasoning: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UnifiedMessage {
|
|
14
|
+
source: string;
|
|
15
|
+
modelId: string;
|
|
16
|
+
providerId: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
timestamp: number; // Unix milliseconds
|
|
19
|
+
date: string; // YYYY-MM-DD
|
|
20
|
+
tokens: TokenBreakdown;
|
|
21
|
+
cost: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert Unix milliseconds timestamp to YYYY-MM-DD date string
|
|
28
|
+
*/
|
|
29
|
+
export function timestampToDate(timestampMs: number): string {
|
|
30
|
+
const date = new Date(timestampMs);
|
|
31
|
+
const year = date.getUTCFullYear();
|
|
32
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
33
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
34
|
+
return `${year}-${month}-${day}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a unified message
|
|
39
|
+
*/
|
|
40
|
+
export function createUnifiedMessage(
|
|
41
|
+
source: string,
|
|
42
|
+
modelId: string,
|
|
43
|
+
providerId: string,
|
|
44
|
+
sessionId: string,
|
|
45
|
+
timestamp: number,
|
|
46
|
+
tokens: TokenBreakdown,
|
|
47
|
+
cost: number = 0
|
|
48
|
+
): UnifiedMessage {
|
|
49
|
+
return {
|
|
50
|
+
source,
|
|
51
|
+
modelId,
|
|
52
|
+
providerId,
|
|
53
|
+
sessionId,
|
|
54
|
+
timestamp,
|
|
55
|
+
date: timestampToDate(timestamp),
|
|
56
|
+
tokens,
|
|
57
|
+
cost,
|
|
58
|
+
};
|
|
59
|
+
}
|