@tokscale/cli 1.0.16 → 1.0.18

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.
Files changed (108) hide show
  1. package/dist/cli.js +220 -96
  2. package/dist/cli.js.map +1 -1
  3. package/dist/graph-types.d.ts +1 -1
  4. package/dist/graph-types.d.ts.map +1 -1
  5. package/dist/native-runner.js +5 -5
  6. package/dist/native-runner.js.map +1 -1
  7. package/dist/native.d.ts +9 -30
  8. package/dist/native.d.ts.map +1 -1
  9. package/dist/native.js +18 -134
  10. package/dist/native.js.map +1 -1
  11. package/dist/sessions/types.d.ts +1 -1
  12. package/dist/sessions/types.d.ts.map +1 -1
  13. package/dist/submit.d.ts +2 -0
  14. package/dist/submit.d.ts.map +1 -1
  15. package/dist/submit.js +32 -16
  16. package/dist/submit.js.map +1 -1
  17. package/dist/tui/App.d.ts.map +1 -1
  18. package/dist/tui/App.js +13 -6
  19. package/dist/tui/App.js.map +1 -1
  20. package/dist/tui/components/DailyView.d.ts.map +1 -1
  21. package/dist/tui/components/DailyView.js +25 -8
  22. package/dist/tui/components/DailyView.js.map +1 -1
  23. package/dist/tui/components/DateBreakdownPanel.js +2 -2
  24. package/dist/tui/components/DateBreakdownPanel.js.map +1 -1
  25. package/dist/tui/components/Footer.d.ts.map +1 -1
  26. package/dist/tui/components/Footer.js +2 -3
  27. package/dist/tui/components/Footer.js.map +1 -1
  28. package/dist/tui/components/LoadingSpinner.d.ts.map +1 -1
  29. package/dist/tui/components/LoadingSpinner.js +1 -2
  30. package/dist/tui/components/LoadingSpinner.js.map +1 -1
  31. package/dist/tui/components/ModelView.js +2 -2
  32. package/dist/tui/components/ModelView.js.map +1 -1
  33. package/dist/tui/config/settings.d.ts +4 -4
  34. package/dist/tui/config/settings.d.ts.map +1 -1
  35. package/dist/tui/config/settings.js +11 -4
  36. package/dist/tui/config/settings.js.map +1 -1
  37. package/dist/tui/hooks/useData.d.ts.map +1 -1
  38. package/dist/tui/hooks/useData.js +29 -42
  39. package/dist/tui/hooks/useData.js.map +1 -1
  40. package/dist/tui/types/index.d.ts +2 -2
  41. package/dist/tui/types/index.d.ts.map +1 -1
  42. package/dist/tui/types/index.js +3 -1
  43. package/dist/tui/types/index.js.map +1 -1
  44. package/dist/tui/utils/colors.d.ts +1 -0
  45. package/dist/tui/utils/colors.d.ts.map +1 -1
  46. package/dist/tui/utils/colors.js +7 -0
  47. package/dist/tui/utils/colors.js.map +1 -1
  48. package/dist/wrapped.d.ts.map +1 -1
  49. package/dist/wrapped.js +35 -53
  50. package/dist/wrapped.js.map +1 -1
  51. package/package.json +2 -2
  52. package/src/cli.ts +240 -103
  53. package/src/graph-types.ts +1 -1
  54. package/src/native-runner.ts +5 -5
  55. package/src/native.ts +35 -200
  56. package/src/sessions/types.ts +1 -1
  57. package/src/submit.ts +36 -22
  58. package/src/tui/App.tsx +9 -6
  59. package/src/tui/components/DailyView.tsx +29 -11
  60. package/src/tui/components/DateBreakdownPanel.tsx +2 -2
  61. package/src/tui/components/Footer.tsx +7 -2
  62. package/src/tui/components/LoadingSpinner.tsx +1 -2
  63. package/src/tui/components/ModelView.tsx +2 -2
  64. package/src/tui/config/settings.ts +18 -9
  65. package/src/tui/hooks/useData.ts +36 -47
  66. package/src/tui/types/index.ts +5 -4
  67. package/src/tui/utils/colors.ts +7 -0
  68. package/src/wrapped.ts +39 -59
  69. package/dist/graph.d.ts +0 -29
  70. package/dist/graph.d.ts.map +0 -1
  71. package/dist/graph.js +0 -383
  72. package/dist/graph.js.map +0 -1
  73. package/dist/pricing.d.ts +0 -58
  74. package/dist/pricing.d.ts.map +0 -1
  75. package/dist/pricing.js +0 -232
  76. package/dist/pricing.js.map +0 -1
  77. package/dist/sessions/claudecode.d.ts +0 -8
  78. package/dist/sessions/claudecode.d.ts.map +0 -1
  79. package/dist/sessions/claudecode.js +0 -84
  80. package/dist/sessions/claudecode.js.map +0 -1
  81. package/dist/sessions/codex.d.ts +0 -8
  82. package/dist/sessions/codex.d.ts.map +0 -1
  83. package/dist/sessions/codex.js +0 -158
  84. package/dist/sessions/codex.js.map +0 -1
  85. package/dist/sessions/gemini.d.ts +0 -8
  86. package/dist/sessions/gemini.d.ts.map +0 -1
  87. package/dist/sessions/gemini.js +0 -66
  88. package/dist/sessions/gemini.js.map +0 -1
  89. package/dist/sessions/index.d.ts +0 -32
  90. package/dist/sessions/index.d.ts.map +0 -1
  91. package/dist/sessions/index.js +0 -96
  92. package/dist/sessions/index.js.map +0 -1
  93. package/dist/sessions/opencode.d.ts +0 -9
  94. package/dist/sessions/opencode.d.ts.map +0 -1
  95. package/dist/sessions/opencode.js +0 -69
  96. package/dist/sessions/opencode.js.map +0 -1
  97. package/dist/sessions/reports.d.ts +0 -58
  98. package/dist/sessions/reports.d.ts.map +0 -1
  99. package/dist/sessions/reports.js +0 -337
  100. package/dist/sessions/reports.js.map +0 -1
  101. package/src/graph.ts +0 -485
  102. package/src/pricing.ts +0 -309
  103. package/src/sessions/claudecode.ts +0 -119
  104. package/src/sessions/codex.ts +0 -227
  105. package/src/sessions/gemini.ts +0 -108
  106. package/src/sessions/index.ts +0 -126
  107. package/src/sessions/opencode.ts +0 -117
  108. package/src/sessions/reports.ts +0 -475
package/src/graph.ts DELETED
@@ -1,485 +0,0 @@
1
- /**
2
- * Graph data generation module
3
- * Aggregates token usage data by date for contribution graph visualization
4
- *
5
- * Key design: intensity is calculated based on COST ($), not tokens
6
- *
7
- * This module supports two implementations:
8
- * - Native Rust (fast, ~10x faster) - used when available
9
- * - Pure TypeScript (fallback) - always available
10
- */
11
-
12
- import { format } from "date-fns";
13
- import {
14
- parseOpenCodeMessages,
15
- parseClaudeCodeMessages,
16
- parseCodexMessages,
17
- parseGeminiMessages,
18
- } from "./sessions/index.js";
19
- import { PricingFetcher } from "./pricing.js";
20
- import type {
21
- TokenContributionData,
22
- DailyContribution,
23
- YearSummary,
24
- DataSummary,
25
- GraphOptions,
26
- UnifiedMessage,
27
- SourceType,
28
- } from "./graph-types.js";
29
-
30
- const VERSION = "1.0.0";
31
-
32
- // Try to load native module
33
- let nativeModule: typeof import("./native.js") | null = null;
34
- try {
35
- nativeModule = await import("./native.js");
36
- } catch {
37
- // Native module not available, will use TypeScript implementation
38
- }
39
-
40
- /**
41
- * Check if native implementation is available
42
- */
43
- export function isNativeAvailable(): boolean {
44
- return nativeModule?.isNativeAvailable() ?? false;
45
- }
46
-
47
- /**
48
- * Generate contribution graph data from all sources
49
- *
50
- * Uses native Rust implementation if available, falls back to TypeScript.
51
- * Set `options.forceTypescript = true` to skip native module.
52
- */
53
- export async function generateGraphData(
54
- options: GraphOptions & { forceTypescript?: boolean } = {}
55
- ): Promise<TokenContributionData> {
56
- // Try native implementation first (unless forced to use TypeScript)
57
- if (!options.forceTypescript && nativeModule?.isNativeAvailable()) {
58
- try {
59
- return nativeModule.generateGraphNative(options);
60
- } catch (e) {
61
- // Fall through to TypeScript implementation
62
- console.warn("Native module failed, falling back to TypeScript:", e);
63
- }
64
- }
65
-
66
- // TypeScript implementation
67
- return generateGraphDataTS(options);
68
- }
69
-
70
- /**
71
- * Pure TypeScript implementation of graph data generation
72
- */
73
- export async function generateGraphDataTS(
74
- options: GraphOptions = {}
75
- ): Promise<TokenContributionData> {
76
- const fetcher = new PricingFetcher();
77
- await fetcher.fetchPricing();
78
-
79
- // Collect all messages from enabled sources
80
- const messages = collectMessages(options, fetcher);
81
-
82
- // Filter by date range
83
- const filteredMessages = filterMessagesByDate(messages, options);
84
-
85
- // Aggregate by date
86
- const dailyMap = aggregateByDate(filteredMessages, fetcher);
87
-
88
- // Convert to sorted array
89
- let contributions = Array.from(dailyMap.values()).sort((a, b) =>
90
- a.date.localeCompare(b.date)
91
- );
92
-
93
- // Calculate intensity based on COST
94
- contributions = calculateAllIntensities(contributions);
95
-
96
- // Build final structure
97
- const summary = calculateSummary(contributions);
98
- const years = calculateYears(contributions);
99
-
100
- const dateRange = {
101
- start: contributions[0]?.date ?? "",
102
- end: contributions[contributions.length - 1]?.date ?? "",
103
- };
104
-
105
- return {
106
- meta: {
107
- generatedAt: new Date().toISOString(),
108
- version: VERSION,
109
- dateRange,
110
- },
111
- summary,
112
- years,
113
- contributions,
114
- };
115
- }
116
-
117
- /**
118
- * Collect messages from all enabled sources
119
- */
120
- function collectMessages(
121
- options: GraphOptions,
122
- fetcher: PricingFetcher
123
- ): UnifiedMessage[] {
124
- const messages: UnifiedMessage[] = [];
125
- const enabledSources = getEnabledSources(options);
126
-
127
- // OpenCode
128
- if (enabledSources.includes("opencode")) {
129
- const openCodeMessages = parseOpenCodeMessages();
130
- for (const msg of openCodeMessages) {
131
- const pricing = fetcher.getModelPricing(msg.modelId);
132
- const cost = pricing
133
- ? fetcher.calculateCost(
134
- {
135
- input: msg.tokens.input,
136
- output: msg.tokens.output,
137
- reasoning: msg.tokens.reasoning,
138
- cacheRead: msg.tokens.cacheRead,
139
- cacheWrite: msg.tokens.cacheWrite,
140
- },
141
- pricing
142
- )
143
- : 0;
144
-
145
- messages.push({
146
- source: "opencode",
147
- modelId: msg.modelId,
148
- providerId: msg.providerId,
149
- timestamp: msg.timestamp,
150
- tokens: msg.tokens,
151
- cost,
152
- });
153
- }
154
- }
155
-
156
- // Claude Code
157
- if (enabledSources.includes("claude")) {
158
- const claudeMessages = parseClaudeCodeMessages();
159
- for (const msg of claudeMessages) {
160
- const pricing = fetcher.getModelPricing(msg.modelId);
161
- const cost = pricing
162
- ? fetcher.calculateCost(
163
- {
164
- input: msg.tokens.input,
165
- output: msg.tokens.output,
166
- cacheRead: msg.tokens.cacheRead,
167
- cacheWrite: msg.tokens.cacheWrite,
168
- },
169
- pricing
170
- )
171
- : 0;
172
-
173
- messages.push({
174
- source: "claude",
175
- modelId: msg.modelId,
176
- providerId: msg.providerId,
177
- timestamp: msg.timestamp,
178
- tokens: msg.tokens,
179
- cost,
180
- });
181
- }
182
- }
183
-
184
- // Codex
185
- if (enabledSources.includes("codex")) {
186
- const codexMessages = parseCodexMessages();
187
- for (const msg of codexMessages) {
188
- const pricing = fetcher.getModelPricing(msg.modelId);
189
- const cost = pricing
190
- ? fetcher.calculateCost(
191
- {
192
- input: msg.tokens.input,
193
- output: msg.tokens.output,
194
- cacheRead: msg.tokens.cacheRead,
195
- cacheWrite: msg.tokens.cacheWrite,
196
- },
197
- pricing
198
- )
199
- : 0;
200
-
201
- messages.push({
202
- source: "codex",
203
- modelId: msg.modelId,
204
- providerId: msg.providerId,
205
- timestamp: msg.timestamp,
206
- tokens: msg.tokens,
207
- cost,
208
- });
209
- }
210
- }
211
-
212
- // Gemini
213
- if (enabledSources.includes("gemini")) {
214
- const geminiMessages = parseGeminiMessages();
215
- for (const msg of geminiMessages) {
216
- const pricing = fetcher.getModelPricing(msg.modelId);
217
- // Gemini: thoughts/reasoning count as output for billing
218
- const cost = pricing
219
- ? fetcher.calculateCost(
220
- {
221
- input: msg.tokens.input,
222
- output: msg.tokens.output + msg.tokens.reasoning,
223
- cacheRead: 0, // Gemini cached tokens are free
224
- cacheWrite: 0,
225
- },
226
- pricing
227
- )
228
- : 0;
229
-
230
- messages.push({
231
- source: "gemini",
232
- modelId: msg.modelId,
233
- providerId: msg.providerId,
234
- timestamp: msg.timestamp,
235
- tokens: msg.tokens,
236
- cost,
237
- });
238
- }
239
- }
240
-
241
- return messages;
242
- }
243
-
244
- /**
245
- * Get list of enabled sources based on options
246
- */
247
- function getEnabledSources(options: GraphOptions): SourceType[] {
248
- if (options.sources && options.sources.length > 0) {
249
- return options.sources;
250
- }
251
- return ["opencode", "claude", "codex", "gemini"];
252
- }
253
-
254
- /**
255
- * Filter messages by date range
256
- */
257
- function filterMessagesByDate(
258
- messages: UnifiedMessage[],
259
- options: GraphOptions
260
- ): UnifiedMessage[] {
261
- let filtered = messages;
262
-
263
- // Filter by year
264
- if (options.year) {
265
- const yearStart = new Date(`${options.year}-01-01`).getTime();
266
- const yearEnd = new Date(`${options.year}-12-31T23:59:59.999`).getTime();
267
- filtered = filtered.filter(
268
- (m) => m.timestamp >= yearStart && m.timestamp <= yearEnd
269
- );
270
- }
271
-
272
- // Filter by since
273
- if (options.since) {
274
- const sinceTime = new Date(options.since).getTime();
275
- filtered = filtered.filter((m) => m.timestamp >= sinceTime);
276
- }
277
-
278
- // Filter by until
279
- if (options.until) {
280
- const untilTime = new Date(`${options.until}T23:59:59.999`).getTime();
281
- filtered = filtered.filter((m) => m.timestamp <= untilTime);
282
- }
283
-
284
- return filtered;
285
- }
286
-
287
- /**
288
- * Aggregate messages by date
289
- */
290
- function aggregateByDate(
291
- messages: UnifiedMessage[],
292
- _fetcher: PricingFetcher
293
- ): Map<string, DailyContribution> {
294
- const dailyMap = new Map<string, DailyContribution>();
295
-
296
- for (const msg of messages) {
297
- const dateKey = format(new Date(msg.timestamp), "yyyy-MM-dd");
298
-
299
- let daily = dailyMap.get(dateKey);
300
- if (!daily) {
301
- daily = createEmptyDailyContribution(dateKey);
302
- dailyMap.set(dateKey, daily);
303
- }
304
-
305
- // Update totals
306
- const totalTokens =
307
- msg.tokens.input +
308
- msg.tokens.output +
309
- msg.tokens.cacheRead +
310
- msg.tokens.cacheWrite +
311
- msg.tokens.reasoning;
312
-
313
- daily.totals.tokens += totalTokens;
314
- daily.totals.cost += msg.cost;
315
- daily.totals.messages += 1;
316
-
317
- // Update token breakdown
318
- daily.tokenBreakdown.input += msg.tokens.input;
319
- daily.tokenBreakdown.output += msg.tokens.output;
320
- daily.tokenBreakdown.cacheRead += msg.tokens.cacheRead;
321
- daily.tokenBreakdown.cacheWrite += msg.tokens.cacheWrite;
322
- daily.tokenBreakdown.reasoning += msg.tokens.reasoning;
323
-
324
- // Update source contributions
325
- let sourceContrib = daily.sources.find(
326
- (s) => s.source === msg.source && s.modelId === msg.modelId
327
- );
328
-
329
- if (!sourceContrib) {
330
- sourceContrib = {
331
- source: msg.source,
332
- modelId: msg.modelId,
333
- providerId: msg.providerId,
334
- tokens: {
335
- input: 0,
336
- output: 0,
337
- cacheRead: 0,
338
- cacheWrite: 0,
339
- reasoning: 0,
340
- },
341
- cost: 0,
342
- messages: 0,
343
- };
344
- daily.sources.push(sourceContrib);
345
- }
346
-
347
- sourceContrib.tokens.input += msg.tokens.input;
348
- sourceContrib.tokens.output += msg.tokens.output;
349
- sourceContrib.tokens.cacheRead += msg.tokens.cacheRead;
350
- sourceContrib.tokens.cacheWrite += msg.tokens.cacheWrite;
351
- sourceContrib.tokens.reasoning += msg.tokens.reasoning;
352
- sourceContrib.cost += msg.cost;
353
- sourceContrib.messages += 1;
354
- }
355
-
356
- return dailyMap;
357
- }
358
-
359
- /**
360
- * Create empty daily contribution
361
- */
362
- function createEmptyDailyContribution(date: string): DailyContribution {
363
- return {
364
- date,
365
- totals: {
366
- tokens: 0,
367
- cost: 0,
368
- messages: 0,
369
- },
370
- intensity: 0,
371
- tokenBreakdown: {
372
- input: 0,
373
- output: 0,
374
- cacheRead: 0,
375
- cacheWrite: 0,
376
- reasoning: 0,
377
- },
378
- sources: [],
379
- };
380
- }
381
-
382
- /**
383
- * Calculate intensity for all contributions based on COST
384
- */
385
- function calculateAllIntensities(
386
- contributions: DailyContribution[]
387
- ): DailyContribution[] {
388
- if (contributions.length === 0) return contributions;
389
-
390
- // Find max cost for intensity calculation
391
- const maxCost = Math.max(...contributions.map((c) => c.totals.cost));
392
-
393
- return contributions.map((c) => ({
394
- ...c,
395
- intensity: calculateIntensity(c.totals.cost, maxCost),
396
- }));
397
- }
398
-
399
- /**
400
- * Calculate intensity grade based on cost ratio
401
- * 0 = no activity, 4 = highest
402
- */
403
- function calculateIntensity(
404
- cost: number,
405
- maxCost: number
406
- ): 0 | 1 | 2 | 3 | 4 {
407
- if (cost === 0 || maxCost === 0) return 0;
408
- const ratio = cost / maxCost;
409
- if (ratio >= 0.75) return 4;
410
- if (ratio >= 0.5) return 3;
411
- if (ratio >= 0.25) return 2;
412
- return 1;
413
- }
414
-
415
- /**
416
- * Calculate summary statistics
417
- */
418
- function calculateSummary(contributions: DailyContribution[]): DataSummary {
419
- const totalTokens = contributions.reduce((sum, c) => sum + c.totals.tokens, 0);
420
- const totalCost = contributions.reduce((sum, c) => sum + c.totals.cost, 0);
421
- const activeDays = contributions.filter((c) => c.totals.cost > 0).length;
422
- const maxCostInSingleDay = Math.max(
423
- ...contributions.map((c) => c.totals.cost),
424
- 0
425
- );
426
-
427
- // Collect unique sources and models
428
- const sourcesSet = new Set<SourceType>();
429
- const modelsSet = new Set<string>();
430
-
431
- for (const c of contributions) {
432
- for (const s of c.sources) {
433
- sourcesSet.add(s.source);
434
- modelsSet.add(s.modelId);
435
- }
436
- }
437
-
438
- return {
439
- totalTokens,
440
- totalCost,
441
- totalDays: contributions.length,
442
- activeDays,
443
- averagePerDay: activeDays > 0 ? totalCost / activeDays : 0,
444
- maxCostInSingleDay,
445
- sources: Array.from(sourcesSet),
446
- models: Array.from(modelsSet),
447
- };
448
- }
449
-
450
- /**
451
- * Calculate year summaries
452
- */
453
- function calculateYears(contributions: DailyContribution[]): YearSummary[] {
454
- const yearsMap = new Map<
455
- string,
456
- { tokens: number; cost: number; start: string; end: string }
457
- >();
458
-
459
- for (const c of contributions) {
460
- const year = c.date.substring(0, 4);
461
- let yearData = yearsMap.get(year);
462
-
463
- if (!yearData) {
464
- yearData = { tokens: 0, cost: 0, start: c.date, end: c.date };
465
- yearsMap.set(year, yearData);
466
- }
467
-
468
- yearData.tokens += c.totals.tokens;
469
- yearData.cost += c.totals.cost;
470
- if (c.date < yearData.start) yearData.start = c.date;
471
- if (c.date > yearData.end) yearData.end = c.date;
472
- }
473
-
474
- return Array.from(yearsMap.entries())
475
- .map(([year, data]) => ({
476
- year,
477
- totalTokens: data.tokens,
478
- totalCost: data.cost,
479
- range: {
480
- start: data.start,
481
- end: data.end,
482
- },
483
- }))
484
- .sort((a, b) => a.year.localeCompare(b.year));
485
- }