@tokscale/cli 1.0.17 → 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 +214 -91
  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 +20 -48
  50. package/dist/wrapped.js.map +1 -1
  51. package/package.json +2 -2
  52. package/src/cli.ts +232 -97
  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 +21 -54
  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
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Valid source identifiers
8
8
  */
9
- export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor";
9
+ export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid";
10
10
 
11
11
  /**
12
12
  * Token breakdown by category
@@ -76,16 +76,16 @@ async function main() {
76
76
  result = nativeCore.parseLocalSources(args[0] as Parameters<typeof nativeCore.parseLocalSources>[0]);
77
77
  break;
78
78
  case "finalizeReport":
79
- result = nativeCore.finalizeReport(args[0] as Parameters<typeof nativeCore.finalizeReport>[0]);
79
+ result = await nativeCore.finalizeReport(args[0] as Parameters<typeof nativeCore.finalizeReport>[0]);
80
80
  break;
81
81
  case "finalizeMonthlyReport":
82
- result = nativeCore.finalizeMonthlyReport(args[0] as Parameters<typeof nativeCore.finalizeMonthlyReport>[0]);
82
+ result = await nativeCore.finalizeMonthlyReport(args[0] as Parameters<typeof nativeCore.finalizeMonthlyReport>[0]);
83
83
  break;
84
84
  case "finalizeGraph":
85
- result = nativeCore.finalizeGraph(args[0] as Parameters<typeof nativeCore.finalizeGraph>[0]);
85
+ result = await nativeCore.finalizeGraph(args[0] as Parameters<typeof nativeCore.finalizeGraph>[0]);
86
86
  break;
87
- case "generateGraphWithPricing":
88
- result = nativeCore.generateGraphWithPricing(args[0] as Parameters<typeof nativeCore.generateGraphWithPricing>[0]);
87
+ case "finalizeReportAndGraph":
88
+ result = await nativeCore.finalizeReportAndGraph(args[0] as Parameters<typeof nativeCore.finalizeReportAndGraph>[0]);
89
89
  break;
90
90
  default:
91
91
  throw new Error(`Unknown method: ${method}`);
package/src/native.ts CHANGED
@@ -2,48 +2,19 @@
2
2
  * Native module loader for Rust core
3
3
  *
4
4
  * Exposes all Rust functions with proper TypeScript types.
5
- * Falls back to TypeScript implementations when native module is unavailable.
5
+ * Native module is REQUIRED - no TypeScript fallback.
6
6
  */
7
7
 
8
- import type { PricingEntry } from "./pricing.js";
9
8
  import type {
10
9
  TokenContributionData,
11
10
  GraphOptions as TSGraphOptions,
12
11
  SourceType,
13
12
  } from "./graph-types.js";
14
- import {
15
- parseLocalSources as parseLocalSourcesTS,
16
- type ParsedMessages as TSParsedMessages,
17
- type UnifiedMessage,
18
- } from "./sessions/index.js";
19
- import {
20
- generateModelReport as generateModelReportTS,
21
- generateMonthlyReport as generateMonthlyReportTS,
22
- generateGraphData as generateGraphDataTS,
23
- } from "./sessions/reports.js";
24
- import { readCursorMessagesFromCache } from "./cursor.js";
25
13
 
26
14
  // =============================================================================
27
15
  // Types matching Rust exports
28
16
  // =============================================================================
29
17
 
30
- interface NativeGraphOptions {
31
- homeDir?: string;
32
- sources?: string[];
33
- since?: string;
34
- until?: string;
35
- year?: string;
36
- threads?: number;
37
- }
38
-
39
- interface NativeScanStats {
40
- opencodeFiles: number;
41
- claudeFiles: number;
42
- codexFiles: number;
43
- geminiFiles: number;
44
- totalFiles: number;
45
- }
46
-
47
18
  interface NativeTokenBreakdown {
48
19
  input: number;
49
20
  output: number;
@@ -109,21 +80,9 @@ interface NativeGraphResult {
109
80
  contributions: NativeDailyContribution[];
110
81
  }
111
82
 
112
- // Types for pricing-aware APIs
113
- interface NativePricingEntry {
114
- modelId: string;
115
- pricing: {
116
- inputCostPerToken: number;
117
- outputCostPerToken: number;
118
- cacheReadInputTokenCost?: number;
119
- cacheCreationInputTokenCost?: number;
120
- };
121
- }
122
-
123
83
  interface NativeReportOptions {
124
84
  homeDir?: string;
125
85
  sources?: string[];
126
- pricing: NativePricingEntry[];
127
86
  since?: string;
128
87
  until?: string;
129
88
  year?: string;
@@ -192,6 +151,8 @@ interface NativeParsedMessages {
192
151
  claudeCount: number;
193
152
  codexCount: number;
194
153
  geminiCount: number;
154
+ ampCount: number;
155
+ droidCount?: number;
195
156
  processingTimeMs: number;
196
157
  }
197
158
 
@@ -206,7 +167,6 @@ interface NativeLocalParseOptions {
206
167
  interface NativeFinalizeReportOptions {
207
168
  homeDir?: string;
208
169
  localMessages: NativeParsedMessages;
209
- pricing: NativePricingEntry[];
210
170
  includeCursor: boolean;
211
171
  since?: string;
212
172
  until?: string;
@@ -216,12 +176,6 @@ interface NativeFinalizeReportOptions {
216
176
  interface NativeCore {
217
177
  version(): string;
218
178
  healthCheck(): string;
219
- generateGraph(options: NativeGraphOptions): NativeGraphResult;
220
- generateGraphWithPricing(options: NativeReportOptions): NativeGraphResult;
221
- scanSessions(homeDir?: string, sources?: string[]): NativeScanStats;
222
- getModelReport(options: NativeReportOptions): NativeModelReport;
223
- getMonthlyReport(options: NativeReportOptions): NativeMonthlyReport;
224
- // Two-phase processing (parallel optimization)
225
179
  parseLocalSources(options: NativeLocalParseOptions): NativeParsedMessages;
226
180
  finalizeReport(options: NativeFinalizeReportOptions): NativeModelReport;
227
181
  finalizeMonthlyReport(options: NativeFinalizeReportOptions): NativeMonthlyReport;
@@ -236,7 +190,11 @@ let nativeCore: NativeCore | null = null;
236
190
  let loadError: Error | null = null;
237
191
 
238
192
  try {
239
- nativeCore = await import("@tokscale/core").then((m) => m.default || m);
193
+ // Type assertion needed because dynamic import returns module namespace
194
+ // nativeCore.version() is called directly, async functions go through subprocess
195
+ nativeCore = await import("@tokscale/core").then(
196
+ (m) => (m.default || m) as unknown as NativeCore
197
+ );
240
198
  } catch (e) {
241
199
  loadError = e as Error;
242
200
  }
@@ -252,13 +210,6 @@ export function isNativeAvailable(): boolean {
252
210
  return nativeCore !== null;
253
211
  }
254
212
 
255
- /**
256
- * Get native module load error (if any)
257
- */
258
- export function getNativeLoadError(): Error | null {
259
- return loadError;
260
- }
261
-
262
213
  /**
263
214
  * Get native module version
264
215
  */
@@ -266,33 +217,6 @@ export function getNativeVersion(): string | null {
266
217
  return nativeCore?.version() ?? null;
267
218
  }
268
219
 
269
- /**
270
- * Scan sessions using native module
271
- */
272
- export function scanSessionsNative(homeDir?: string, sources?: string[]): NativeScanStats | null {
273
- if (!nativeCore) {
274
- return null;
275
- }
276
- return nativeCore.scanSessions(homeDir, sources);
277
- }
278
-
279
- // =============================================================================
280
- // Graph generation
281
- // =============================================================================
282
-
283
- /**
284
- * Convert TypeScript graph options to native format
285
- */
286
- function toNativeOptions(options: TSGraphOptions): NativeGraphOptions {
287
- return {
288
- homeDir: undefined,
289
- sources: options.sources,
290
- since: options.since,
291
- until: options.until,
292
- year: options.year,
293
- };
294
- }
295
-
296
220
  /**
297
221
  * Convert native result to TypeScript format
298
222
  */
@@ -358,22 +282,6 @@ function fromNativeResult(result: NativeGraphResult): TokenContributionData {
358
282
  };
359
283
  }
360
284
 
361
- /**
362
- * Generate graph data using native module (without pricing - uses embedded costs)
363
- * @deprecated Use generateGraphWithPricing instead
364
- */
365
- export function generateGraphNative(options: TSGraphOptions = {}): TokenContributionData {
366
- if (!nativeCore) {
367
- throw new Error("Native module not available: " + (loadError?.message || "unknown error"));
368
- }
369
-
370
- const nativeOptions = toNativeOptions(options);
371
- const result = nativeCore.generateGraph(nativeOptions);
372
- return fromNativeResult(result);
373
- }
374
-
375
-
376
-
377
285
  // =============================================================================
378
286
  // Reports
379
287
  // =============================================================================
@@ -442,6 +350,8 @@ export interface ParsedMessages {
442
350
  claudeCount: number;
443
351
  codexCount: number;
444
352
  geminiCount: number;
353
+ ampCount: number;
354
+ droidCount: number;
445
355
  processingTimeMs: number;
446
356
  }
447
357
 
@@ -450,13 +360,10 @@ export interface LocalParseOptions {
450
360
  since?: string;
451
361
  until?: string;
452
362
  year?: string;
453
- /** Force TypeScript fallback even when native module is available (needed for agent field) */
454
- forceTypescript?: boolean;
455
363
  }
456
364
 
457
365
  export interface FinalizeOptions {
458
366
  localMessages: ParsedMessages;
459
- pricing: PricingEntry[];
460
367
  includeCursor: boolean;
461
368
  since?: string;
462
369
  until?: string;
@@ -641,36 +548,8 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
641
548
  }
642
549
 
643
550
  export async function parseLocalSourcesAsync(options: LocalParseOptions): Promise<ParsedMessages> {
644
- // Use TypeScript fallback when native module is not available or when explicitly requested
645
- if (!isNativeAvailable() || options.forceTypescript) {
646
- const result = parseLocalSourcesTS({
647
- sources: options.sources,
648
- since: options.since,
649
- until: options.until,
650
- year: options.year,
651
- });
652
-
653
- return {
654
- messages: result.messages.map((msg) => ({
655
- source: msg.source,
656
- modelId: msg.modelId,
657
- providerId: msg.providerId,
658
- timestamp: msg.timestamp,
659
- date: msg.date,
660
- input: msg.tokens.input,
661
- output: msg.tokens.output,
662
- cacheRead: msg.tokens.cacheRead,
663
- cacheWrite: msg.tokens.cacheWrite,
664
- reasoning: msg.tokens.reasoning,
665
- sessionId: msg.sessionId,
666
- agent: msg.agent,
667
- })),
668
- opencodeCount: result.opencodeCount,
669
- claudeCount: result.claudeCount,
670
- codexCount: result.codexCount,
671
- geminiCount: result.geminiCount,
672
- processingTimeMs: result.processingTimeMs,
673
- };
551
+ if (!isNativeAvailable()) {
552
+ throw new Error("Native module required. Run: bun run build:core");
674
553
  }
675
554
 
676
555
  const nativeOptions: NativeLocalParseOptions = {
@@ -684,52 +563,14 @@ export async function parseLocalSourcesAsync(options: LocalParseOptions): Promis
684
563
  return runInSubprocess<ParsedMessages>("parseLocalSources", [nativeOptions]);
685
564
  }
686
565
 
687
- function buildMessagesForFallback(options: FinalizeOptions): UnifiedMessage[] {
688
- const messages: UnifiedMessage[] = options.localMessages.messages.map((msg) => ({
689
- source: msg.source,
690
- modelId: msg.modelId,
691
- providerId: msg.providerId,
692
- sessionId: msg.sessionId,
693
- timestamp: msg.timestamp,
694
- date: msg.date,
695
- tokens: {
696
- input: msg.input,
697
- output: msg.output,
698
- cacheRead: msg.cacheRead,
699
- cacheWrite: msg.cacheWrite,
700
- reasoning: msg.reasoning,
701
- },
702
- cost: 0,
703
- agent: msg.agent,
704
- }));
705
-
706
- if (options.includeCursor) {
707
- const cursorMessages = readCursorMessagesFromCache();
708
- for (const cursor of cursorMessages) {
709
- const inRange =
710
- (!options.year || cursor.date.startsWith(options.year)) &&
711
- (!options.since || cursor.date >= options.since) &&
712
- (!options.until || cursor.date <= options.until);
713
- if (inRange) {
714
- messages.push(cursor);
715
- }
716
- }
717
- }
718
-
719
- return messages;
720
- }
721
-
722
566
  export async function finalizeReportAsync(options: FinalizeOptions): Promise<ModelReport> {
723
567
  if (!isNativeAvailable()) {
724
- const startTime = performance.now();
725
- const messages = buildMessagesForFallback(options);
726
- return generateModelReportTS(messages, options.pricing, startTime);
568
+ throw new Error("Native module required. Run: bun run build:core");
727
569
  }
728
570
 
729
571
  const nativeOptions: NativeFinalizeReportOptions = {
730
572
  homeDir: undefined,
731
573
  localMessages: options.localMessages,
732
- pricing: options.pricing,
733
574
  includeCursor: options.includeCursor,
734
575
  since: options.since,
735
576
  until: options.until,
@@ -741,15 +582,12 @@ export async function finalizeReportAsync(options: FinalizeOptions): Promise<Mod
741
582
 
742
583
  export async function finalizeMonthlyReportAsync(options: FinalizeOptions): Promise<MonthlyReport> {
743
584
  if (!isNativeAvailable()) {
744
- const startTime = performance.now();
745
- const messages = buildMessagesForFallback(options);
746
- return generateMonthlyReportTS(messages, options.pricing, startTime);
585
+ throw new Error("Native module required. Run: bun run build:core");
747
586
  }
748
587
 
749
588
  const nativeOptions: NativeFinalizeReportOptions = {
750
589
  homeDir: undefined,
751
590
  localMessages: options.localMessages,
752
- pricing: options.pricing,
753
591
  includeCursor: options.includeCursor,
754
592
  since: options.since,
755
593
  until: options.until,
@@ -761,15 +599,12 @@ export async function finalizeMonthlyReportAsync(options: FinalizeOptions): Prom
761
599
 
762
600
  export async function finalizeGraphAsync(options: FinalizeOptions): Promise<TokenContributionData> {
763
601
  if (!isNativeAvailable()) {
764
- const startTime = performance.now();
765
- const messages = buildMessagesForFallback(options);
766
- return generateGraphDataTS(messages, options.pricing, startTime);
602
+ throw new Error("Native module required. Run: bun run build:core");
767
603
  }
768
604
 
769
605
  const nativeOptions: NativeFinalizeReportOptions = {
770
606
  homeDir: undefined,
771
607
  localMessages: options.localMessages,
772
- pricing: options.pricing,
773
608
  includeCursor: options.includeCursor,
774
609
  since: options.since,
775
610
  until: options.until,
@@ -780,33 +615,33 @@ export async function finalizeGraphAsync(options: FinalizeOptions): Promise<Toke
780
615
  return fromNativeResult(result);
781
616
  }
782
617
 
783
- export async function generateGraphWithPricingAsync(
784
- options: TSGraphOptions & { pricing: PricingEntry[] }
785
- ): Promise<TokenContributionData> {
786
- // Use TypeScript fallback when native module is not available
787
- if (!isNativeAvailable()) {
788
- const startTime = performance.now();
789
-
790
- // Parse local sources using TS fallback
791
- const parsed = parseLocalSourcesTS({
792
- sources: options.sources,
793
- since: options.since,
794
- until: options.until,
795
- year: options.year,
796
- });
618
+ export interface ReportAndGraph {
619
+ report: ModelReport;
620
+ graph: TokenContributionData;
621
+ }
622
+
623
+ interface NativeReportAndGraph {
624
+ report: NativeModelReport;
625
+ graph: NativeGraphResult;
626
+ }
797
627
 
798
- return generateGraphDataTS(parsed.messages, options.pricing, startTime);
628
+ export async function finalizeReportAndGraphAsync(options: FinalizeOptions): Promise<ReportAndGraph> {
629
+ if (!isNativeAvailable()) {
630
+ throw new Error("Native module required. Run: bun run build:core");
799
631
  }
800
632
 
801
- const nativeOptions: NativeReportOptions = {
633
+ const nativeOptions: NativeFinalizeReportOptions = {
802
634
  homeDir: undefined,
803
- sources: options.sources,
804
- pricing: options.pricing,
635
+ localMessages: options.localMessages,
636
+ includeCursor: options.includeCursor,
805
637
  since: options.since,
806
638
  until: options.until,
807
639
  year: options.year,
808
640
  };
809
641
 
810
- const result = await runInSubprocess<NativeGraphResult>("generateGraphWithPricing", [nativeOptions]);
811
- return fromNativeResult(result);
642
+ const result = await runInSubprocess<NativeReportAndGraph>("finalizeReportAndGraph", [nativeOptions]);
643
+ return {
644
+ report: result.report,
645
+ graph: fromNativeResult(result.graph),
646
+ };
812
647
  }
@@ -22,7 +22,7 @@ export interface UnifiedMessage {
22
22
  agent?: string;
23
23
  }
24
24
 
25
- export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor";
25
+ export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid";
26
26
 
27
27
  /**
28
28
  * Convert Unix milliseconds timestamp to YYYY-MM-DD date string
package/src/submit.ts CHANGED
@@ -5,11 +5,8 @@
5
5
 
6
6
  import pc from "picocolors";
7
7
  import { loadCredentials, getApiBaseUrl } from "./credentials.js";
8
- import { PricingFetcher } from "./pricing.js";
9
- import {
10
- isNativeAvailable,
11
- generateGraphWithPricingAsync,
12
- } from "./native.js";
8
+ import { parseLocalSourcesAsync, finalizeReportAndGraphAsync, type ParsedMessages } from "./native.js";
9
+ import { syncCursorCache, loadCursorCredentials } from "./cursor.js";
13
10
  import type { TokenContributionData } from "./graph-types.js";
14
11
  import { formatCurrency } from "./table.js";
15
12
 
@@ -19,6 +16,8 @@ interface SubmitOptions {
19
16
  codex?: boolean;
20
17
  gemini?: boolean;
21
18
  cursor?: boolean;
19
+ amp?: boolean;
20
+ droid?: boolean;
22
21
  since?: string;
23
22
  until?: string;
24
23
  year?: string;
@@ -44,7 +43,7 @@ interface SubmitResponse {
44
43
  details?: string[];
45
44
  }
46
45
 
47
- type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor";
46
+ type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid";
48
47
 
49
48
  /**
50
49
  * Submit command - sends usage data to the platform
@@ -58,24 +57,13 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
58
57
  process.exit(1);
59
58
  }
60
59
 
61
- // Step 2: Log native module status (TS fallback available)
62
- if (!isNativeAvailable()) {
63
- console.log(pc.yellow("\n Note: Using TypeScript fallback (native module not available)"));
64
- console.log(pc.gray(" Run 'bun run build:core' for faster processing.\n"));
65
- }
66
-
67
60
  console.log(pc.cyan("\n Tokscale - Submit Usage Data\n"));
68
61
 
69
- // Step 3: Generate graph data
70
62
  console.log(pc.gray(" Scanning local session data..."));
71
63
 
72
- const fetcher = new PricingFetcher();
73
- await fetcher.fetchPricing();
74
- const pricingEntries = fetcher.toPricingEntries();
75
-
76
- // Determine sources
77
- const hasFilter = options.opencode || options.claude || options.codex || options.gemini || options.cursor;
64
+ const hasFilter = options.opencode || options.claude || options.codex || options.gemini || options.cursor || options.amp || options.droid;
78
65
  let sources: SourceType[] | undefined;
66
+ let includeCursor = true;
79
67
  if (hasFilter) {
80
68
  sources = [];
81
69
  if (options.opencode) sources.push("opencode");
@@ -83,17 +71,43 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
83
71
  if (options.codex) sources.push("codex");
84
72
  if (options.gemini) sources.push("gemini");
85
73
  if (options.cursor) sources.push("cursor");
74
+ if (options.amp) sources.push("amp");
75
+ if (options.droid) sources.push("droid");
76
+ includeCursor = sources.includes("cursor");
86
77
  }
87
78
 
79
+ // Filter out cursor from local sources (it's handled separately via sync)
80
+ const localSources = sources?.filter((s): s is Exclude<SourceType, "cursor"> => s !== "cursor");
81
+
88
82
  let data: TokenContributionData;
89
83
  try {
90
- data = await generateGraphWithPricingAsync({
91
- sources,
92
- pricing: pricingEntries,
84
+ // Two-phase processing (same as TUI) for consistency:
85
+ // Phase 1: Parse local sources + sync cursor in parallel
86
+ const [localMessages, cursorSync] = await Promise.all([
87
+ parseLocalSourcesAsync({
88
+ sources: localSources,
89
+ since: options.since,
90
+ until: options.until,
91
+ year: options.year,
92
+ }),
93
+ includeCursor && loadCursorCredentials()
94
+ ? syncCursorCache()
95
+ : Promise.resolve({ synced: false, rows: 0 }),
96
+ ]);
97
+
98
+ // Phase 2: Finalize with pricing (combines local + cursor)
99
+ // Single subprocess call ensures consistent pricing for both report and graph
100
+ const { report, graph } = await finalizeReportAndGraphAsync({
101
+ localMessages,
102
+ includeCursor: includeCursor && cursorSync.synced,
93
103
  since: options.since,
94
104
  until: options.until,
95
105
  year: options.year,
96
106
  });
107
+
108
+ // Use graph structure for submission, report's cost for display
109
+ data = graph;
110
+ data.summary.totalCost = report.totalCost;
97
111
  } catch (error) {
98
112
  console.error(pc.red(`\n Error generating data: ${(error as Error).message}\n`));
99
113
  process.exit(1);
package/src/tui/App.tsx CHANGED
@@ -162,8 +162,12 @@ export function App(props: AppProps) {
162
162
  };
163
163
 
164
164
  const handleSortChange = (sort: SortType) => {
165
- setSortBy(sort);
166
- setSortDesc(true);
165
+ if (sortBy() === sort) {
166
+ setSortDesc(!sortDesc());
167
+ } else {
168
+ setSortBy(sort);
169
+ setSortDesc(true);
170
+ }
167
171
  };
168
172
 
169
173
  useKeyboard((key) => {
@@ -216,8 +220,7 @@ export function App(props: AppProps) {
216
220
  }
217
221
 
218
222
  if (key.name === "c" && !key.meta && !key.ctrl) {
219
- setSortBy("cost");
220
- setSortDesc(true);
223
+ handleSortChange("cost");
221
224
  return;
222
225
  }
223
226
 
@@ -263,8 +266,7 @@ export function App(props: AppProps) {
263
266
  return;
264
267
  }
265
268
  if (key.name === "t") {
266
- setSortBy("tokens");
267
- setSortDesc(true);
269
+ handleSortChange("tokens");
268
270
  return;
269
271
  }
270
272
 
@@ -278,6 +280,7 @@ export function App(props: AppProps) {
278
280
  if (key.name === "3") { handleSourceToggle("codex"); return; }
279
281
  if (key.name === "4") { handleSourceToggle("cursor"); return; }
280
282
  if (key.name === "5") { handleSourceToggle("gemini"); return; }
283
+ if (key.name === "6") { handleSourceToggle("amp"); return; }
281
284
 
282
285
  if (key.name === "up") {
283
286
  if (activeTab() === "overview") {
@@ -55,7 +55,26 @@ export function DailyView(props: DailyViewProps) {
55
55
  });
56
56
  });
57
57
 
58
- const visibleEntries = createMemo(() => sortedEntries().slice(0, props.height - 3));
58
+ const visibleEntries = createMemo(() => {
59
+ const maxRows = Math.max(props.height - 3, 0);
60
+ return sortedEntries().slice(0, maxRows);
61
+ });
62
+
63
+ const formattedRows = createMemo(() => {
64
+ const dateColWidth = dateColumnWidths().column;
65
+ const narrow = isNarrowTerminal();
66
+ return visibleEntries().map((entry) => ({
67
+ entry,
68
+ dateColWidth,
69
+ narrow,
70
+ input: formatTokensCompact(entry.input),
71
+ output: formatTokensCompact(entry.output),
72
+ cacheRead: formatTokensCompact(entry.cacheRead),
73
+ cacheWrite: formatTokensCompact(entry.cacheWrite),
74
+ total: formatTokensCompact(entry.total),
75
+ cost: formatCostFull(entry.cost),
76
+ }));
77
+ });
59
78
 
60
79
  const sortArrow = () => (props.sortDesc ? "▼" : "▲");
61
80
  const dateHeader = () => "Date";
@@ -70,12 +89,11 @@ export function DailyView(props: DailyViewProps) {
70
89
  return `${(" " + dateHeader()).padEnd(dateColWidth)}${"Input".padStart(INPUT_COL_WIDTH)}${"Output".padStart(OUTPUT_COL_WIDTH)}${"C.Read".padStart(CACHE_READ_COL_WIDTH)}${"C.Write".padStart(CACHE_WRITE_COL_WIDTH)}${totalHeader().padStart(TOTAL_COL_WIDTH)}${costHeader().padStart(COST_COL_WIDTH)}`;
71
90
  };
72
91
 
73
- const renderRow = (entry: typeof visibleEntries extends () => (infer T)[] ? T : never) => {
74
- const dateColWidth = dateColumnWidths().column;
75
- if (isNarrowTerminal()) {
76
- return `${entry.date.padEnd(dateColWidth)}${formatTokensCompact(entry.total).padStart(TOTAL_COL_WIDTH)}`;
92
+ const renderRowData = (row: typeof formattedRows extends () => (infer T)[] ? T : never) => {
93
+ if (row.narrow) {
94
+ return `${row.entry.date.padEnd(row.dateColWidth)}${row.total.padStart(TOTAL_COL_WIDTH)}`;
77
95
  }
78
- return `${entry.date.padEnd(dateColWidth)}${formatTokensCompact(entry.input).padStart(INPUT_COL_WIDTH)}${formatTokensCompact(entry.output).padStart(OUTPUT_COL_WIDTH)}${formatTokensCompact(entry.cacheRead).padStart(CACHE_READ_COL_WIDTH)}${formatTokensCompact(entry.cacheWrite).padStart(CACHE_WRITE_COL_WIDTH)}${formatTokensCompact(entry.total).padStart(TOTAL_COL_WIDTH)}`;
96
+ return `${row.entry.date.padEnd(row.dateColWidth)}${row.input.padStart(INPUT_COL_WIDTH)}${row.output.padStart(OUTPUT_COL_WIDTH)}${row.cacheRead.padStart(CACHE_READ_COL_WIDTH)}${row.cacheWrite.padStart(CACHE_WRITE_COL_WIDTH)}${row.total.padStart(TOTAL_COL_WIDTH)}`;
79
97
  };
80
98
 
81
99
  return (
@@ -86,24 +104,24 @@ export function DailyView(props: DailyViewProps) {
86
104
  </text>
87
105
  </box>
88
106
 
89
- <For each={visibleEntries()}>
90
- {(entry, i) => {
107
+ <For each={formattedRows()}>
108
+ {(row, i) => {
91
109
  const isActive = createMemo(() => i() === props.selectedIndex());
92
110
  const rowBg = createMemo(() => isActive() ? "blue" : (i() % 2 === 1 ? STRIPE_BG : undefined));
93
-
111
+
94
112
  return (
95
113
  <box flexDirection="row">
96
114
  <text
97
115
  bg={rowBg()}
98
116
  fg={isActive() ? "white" : undefined}
99
117
  >
100
- {renderRow(entry)}
118
+ {renderRowData(row)}
101
119
  </text>
102
120
  <text
103
121
  fg="green"
104
122
  bg={rowBg()}
105
123
  >
106
- {formatCostFull(entry.cost).padStart(COST_COL_WIDTH)}
124
+ {row.cost.padStart(COST_COL_WIDTH)}
107
125
  </text>
108
126
  </box>
109
127
  );
@@ -1,6 +1,6 @@
1
1
  import { For, createMemo } from "solid-js";
2
2
  import type { DailyModelBreakdown } from "../types/index.js";
3
- import { getSourceColor } from "../utils/colors.js";
3
+ import { getSourceColor, getSourceDisplayName } from "../utils/colors.js";
4
4
  import { formatTokens, formatCost } from "../utils/format.js";
5
5
  import { ModelRow } from "./ModelRow.js";
6
6
 
@@ -48,7 +48,7 @@ export function DateBreakdownPanel(props: DateBreakdownPanelProps) {
48
48
  {([source, models]) => (
49
49
  <box flexDirection="column">
50
50
  <box flexDirection="row" gap={1}>
51
- <text fg={getSourceColor(source)} bold>{`● ${source.toUpperCase()}`}</text>
51
+ <text fg={getSourceColor(source)} bold>{`● ${getSourceDisplayName(source)}`}</text>
52
52
  <text dim>{`(${models.length} model${models.length > 1 ? "s" : ""})`}</text>
53
53
  </box>
54
54
  <For each={models}>