@tokscale/cli 1.0.17 → 1.0.19

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 (111) 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.d.ts +1 -2
  6. package/dist/native-runner.d.ts.map +1 -1
  7. package/dist/native-runner.js +11 -39
  8. package/dist/native-runner.js.map +1 -1
  9. package/dist/native.d.ts +9 -30
  10. package/dist/native.d.ts.map +1 -1
  11. package/dist/native.js +31 -138
  12. package/dist/native.js.map +1 -1
  13. package/dist/sessions/types.d.ts +1 -1
  14. package/dist/sessions/types.d.ts.map +1 -1
  15. package/dist/submit.d.ts +2 -0
  16. package/dist/submit.d.ts.map +1 -1
  17. package/dist/submit.js +32 -16
  18. package/dist/submit.js.map +1 -1
  19. package/dist/tui/App.d.ts.map +1 -1
  20. package/dist/tui/App.js +14 -7
  21. package/dist/tui/App.js.map +1 -1
  22. package/dist/tui/components/DailyView.d.ts.map +1 -1
  23. package/dist/tui/components/DailyView.js +25 -8
  24. package/dist/tui/components/DailyView.js.map +1 -1
  25. package/dist/tui/components/DateBreakdownPanel.js +2 -2
  26. package/dist/tui/components/DateBreakdownPanel.js.map +1 -1
  27. package/dist/tui/components/Footer.d.ts.map +1 -1
  28. package/dist/tui/components/Footer.js +2 -3
  29. package/dist/tui/components/Footer.js.map +1 -1
  30. package/dist/tui/components/LoadingSpinner.d.ts.map +1 -1
  31. package/dist/tui/components/LoadingSpinner.js +1 -2
  32. package/dist/tui/components/LoadingSpinner.js.map +1 -1
  33. package/dist/tui/components/ModelView.js +2 -2
  34. package/dist/tui/components/ModelView.js.map +1 -1
  35. package/dist/tui/config/settings.d.ts +4 -4
  36. package/dist/tui/config/settings.d.ts.map +1 -1
  37. package/dist/tui/config/settings.js +11 -4
  38. package/dist/tui/config/settings.js.map +1 -1
  39. package/dist/tui/hooks/useData.d.ts.map +1 -1
  40. package/dist/tui/hooks/useData.js +29 -42
  41. package/dist/tui/hooks/useData.js.map +1 -1
  42. package/dist/tui/types/index.d.ts +2 -2
  43. package/dist/tui/types/index.d.ts.map +1 -1
  44. package/dist/tui/types/index.js +3 -1
  45. package/dist/tui/types/index.js.map +1 -1
  46. package/dist/tui/utils/colors.d.ts +1 -0
  47. package/dist/tui/utils/colors.d.ts.map +1 -1
  48. package/dist/tui/utils/colors.js +7 -0
  49. package/dist/tui/utils/colors.js.map +1 -1
  50. package/dist/wrapped.d.ts.map +1 -1
  51. package/dist/wrapped.js +20 -48
  52. package/dist/wrapped.js.map +1 -1
  53. package/package.json +2 -2
  54. package/src/cli.ts +232 -97
  55. package/src/graph-types.ts +1 -1
  56. package/src/native-runner.js +4 -0
  57. package/src/native-runner.ts +12 -42
  58. package/src/native.ts +47 -207
  59. package/src/sessions/types.ts +1 -1
  60. package/src/submit.ts +36 -22
  61. package/src/tui/App.tsx +10 -7
  62. package/src/tui/components/DailyView.tsx +29 -11
  63. package/src/tui/components/DateBreakdownPanel.tsx +2 -2
  64. package/src/tui/components/Footer.tsx +7 -2
  65. package/src/tui/components/LoadingSpinner.tsx +1 -2
  66. package/src/tui/components/ModelView.tsx +2 -2
  67. package/src/tui/config/settings.ts +18 -9
  68. package/src/tui/hooks/useData.ts +36 -47
  69. package/src/tui/types/index.ts +5 -4
  70. package/src/tui/utils/colors.ts +7 -0
  71. package/src/wrapped.ts +21 -54
  72. package/dist/graph.d.ts +0 -29
  73. package/dist/graph.d.ts.map +0 -1
  74. package/dist/graph.js +0 -383
  75. package/dist/graph.js.map +0 -1
  76. package/dist/pricing.d.ts +0 -58
  77. package/dist/pricing.d.ts.map +0 -1
  78. package/dist/pricing.js +0 -232
  79. package/dist/pricing.js.map +0 -1
  80. package/dist/sessions/claudecode.d.ts +0 -8
  81. package/dist/sessions/claudecode.d.ts.map +0 -1
  82. package/dist/sessions/claudecode.js +0 -84
  83. package/dist/sessions/claudecode.js.map +0 -1
  84. package/dist/sessions/codex.d.ts +0 -8
  85. package/dist/sessions/codex.d.ts.map +0 -1
  86. package/dist/sessions/codex.js +0 -158
  87. package/dist/sessions/codex.js.map +0 -1
  88. package/dist/sessions/gemini.d.ts +0 -8
  89. package/dist/sessions/gemini.d.ts.map +0 -1
  90. package/dist/sessions/gemini.js +0 -66
  91. package/dist/sessions/gemini.js.map +0 -1
  92. package/dist/sessions/index.d.ts +0 -32
  93. package/dist/sessions/index.d.ts.map +0 -1
  94. package/dist/sessions/index.js +0 -96
  95. package/dist/sessions/index.js.map +0 -1
  96. package/dist/sessions/opencode.d.ts +0 -9
  97. package/dist/sessions/opencode.d.ts.map +0 -1
  98. package/dist/sessions/opencode.js +0 -69
  99. package/dist/sessions/opencode.js.map +0 -1
  100. package/dist/sessions/reports.d.ts +0 -58
  101. package/dist/sessions/reports.d.ts.map +0 -1
  102. package/dist/sessions/reports.js +0 -337
  103. package/dist/sessions/reports.js.map +0 -1
  104. package/src/graph.ts +0 -485
  105. package/src/pricing.ts +0 -309
  106. package/src/sessions/claudecode.ts +0 -119
  107. package/src/sessions/codex.ts +0 -227
  108. package/src/sessions/gemini.ts +0 -108
  109. package/src/sessions/index.ts +0 -126
  110. package/src/sessions/opencode.ts +0 -117
  111. 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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bun
2
+ // Shim for bunx: when Bun loads src/ directly instead of dist/,
3
+ // this file redirects to the TypeScript source.
4
+ await import("./native-runner.ts");
@@ -5,57 +5,27 @@
5
5
  * This script runs in a separate process to keep the main event loop free
6
6
  * for UI rendering (e.g., spinner animation).
7
7
  *
8
- * Communication: stdin (JSON input) -> stdout (JSON output)
9
- * No temp files needed - pure Unix IPC pattern.
8
+ * Communication: file (JSON input) -> stdout (JSON output)
10
9
  */
11
10
 
12
11
  import nativeCore from "@tokscale/core";
13
-
14
- const MAX_INPUT_SIZE = 50 * 1024 * 1024; // 50MB
15
- const STDIN_TIMEOUT_MS = 30_000; // 30s
12
+ import { readFileSync } from "node:fs";
16
13
 
17
14
  interface NativeRunnerRequest {
18
15
  method: string;
19
16
  args: unknown[];
20
17
  }
21
18
 
22
- async function readStdinWithLimits(): Promise<string> {
23
- const chunks: Buffer[] = [];
24
- let totalSize = 0;
25
- let timeoutId: ReturnType<typeof setTimeout> | null = null;
26
-
27
- const timeoutPromise = new Promise<never>((_, reject) => {
28
- timeoutId = setTimeout(() => reject(new Error(`Stdin read timed out after ${STDIN_TIMEOUT_MS}ms`)), STDIN_TIMEOUT_MS);
29
- });
30
-
31
- const readPromise = (async () => {
32
- for await (const chunk of process.stdin) {
33
- const buf = Buffer.from(chunk as ArrayBuffer);
34
- totalSize += buf.length;
35
- if (totalSize > MAX_INPUT_SIZE) {
36
- throw new Error(`Input exceeds maximum size of ${MAX_INPUT_SIZE} bytes (${Math.round(MAX_INPUT_SIZE / 1024 / 1024)}MB)`);
37
- }
38
- chunks.push(buf);
39
- }
40
- return Buffer.concat(chunks).toString("utf-8");
41
- })();
42
-
43
- try {
44
- const result = await Promise.race([readPromise, timeoutPromise]);
45
- return result;
46
- } finally {
47
- if (timeoutId) clearTimeout(timeoutId);
48
- }
49
- }
50
-
51
19
  async function main() {
52
- const input = await readStdinWithLimits();
20
+ const inputFile = process.argv[2];
53
21
 
54
- if (!input.trim()) {
55
- process.stderr.write(JSON.stringify({ error: "No input received" }));
22
+ if (!inputFile) {
23
+ process.stderr.write(JSON.stringify({ error: "No input file provided" }));
56
24
  process.exit(1);
57
25
  }
58
26
 
27
+ const input = readFileSync(inputFile, "utf-8");
28
+
59
29
  let request: NativeRunnerRequest;
60
30
  try {
61
31
  request = JSON.parse(input) as NativeRunnerRequest;
@@ -76,16 +46,16 @@ async function main() {
76
46
  result = nativeCore.parseLocalSources(args[0] as Parameters<typeof nativeCore.parseLocalSources>[0]);
77
47
  break;
78
48
  case "finalizeReport":
79
- result = nativeCore.finalizeReport(args[0] as Parameters<typeof nativeCore.finalizeReport>[0]);
49
+ result = await nativeCore.finalizeReport(args[0] as Parameters<typeof nativeCore.finalizeReport>[0]);
80
50
  break;
81
51
  case "finalizeMonthlyReport":
82
- result = nativeCore.finalizeMonthlyReport(args[0] as Parameters<typeof nativeCore.finalizeMonthlyReport>[0]);
52
+ result = await nativeCore.finalizeMonthlyReport(args[0] as Parameters<typeof nativeCore.finalizeMonthlyReport>[0]);
83
53
  break;
84
54
  case "finalizeGraph":
85
- result = nativeCore.finalizeGraph(args[0] as Parameters<typeof nativeCore.finalizeGraph>[0]);
55
+ result = await nativeCore.finalizeGraph(args[0] as Parameters<typeof nativeCore.finalizeGraph>[0]);
86
56
  break;
87
- case "generateGraphWithPricing":
88
- result = nativeCore.generateGraphWithPricing(args[0] as Parameters<typeof nativeCore.generateGraphWithPricing>[0]);
57
+ case "finalizeReportAndGraph":
58
+ result = await nativeCore.finalizeReportAndGraph(args[0] as Parameters<typeof nativeCore.finalizeReportAndGraph>[0]);
89
59
  break;
90
60
  default:
91
61
  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;
@@ -471,6 +378,9 @@ export interface FinalizeOptions {
471
378
 
472
379
  import { fileURLToPath } from "node:url";
473
380
  import { dirname, join } from "node:path";
381
+ import { writeFileSync, unlinkSync, mkdirSync } from "node:fs";
382
+ import { tmpdir } from "node:os";
383
+ import { randomUUID } from "node:crypto";
474
384
 
475
385
  const __filename = fileURLToPath(import.meta.url);
476
386
  const __dirname = dirname(__filename);
@@ -489,7 +399,6 @@ const MAX_OUTPUT_BYTES = parseInt(
489
399
  );
490
400
 
491
401
  interface BunSubprocess {
492
- stdin: { write: (data: string) => void; end: () => void };
493
402
  stdout: { text: () => Promise<string> };
494
403
  stderr: { text: () => Promise<string> };
495
404
  exited: Promise<number>;
@@ -499,7 +408,6 @@ interface BunSubprocess {
499
408
  }
500
409
 
501
410
  interface BunSpawnOptions {
502
- stdin: string;
503
411
  stdout: string;
504
412
  stderr: string;
505
413
  }
@@ -518,16 +426,22 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
518
426
  const runnerPath = join(__dirname, "native-runner.js");
519
427
  const input = JSON.stringify({ method, args });
520
428
 
429
+ const tmpDir = join(tmpdir(), "tokscale");
430
+ mkdirSync(tmpDir, { recursive: true });
431
+ const inputFile = join(tmpDir, `input-${randomUUID()}.json`);
432
+
433
+ writeFileSync(inputFile, input, "utf-8");
434
+
521
435
  const BunGlobal = (globalThis as Record<string, unknown>).Bun as BunGlobalType;
522
436
 
523
437
  let proc: BunSubprocess;
524
438
  try {
525
- proc = BunGlobal.spawn([process.execPath, runnerPath], {
526
- stdin: "pipe",
439
+ proc = BunGlobal.spawn([process.execPath, runnerPath, inputFile], {
527
440
  stdout: "pipe",
528
441
  stderr: "pipe",
529
442
  });
530
443
  } catch (e) {
444
+ unlinkSync(inputFile);
531
445
  throw new Error(`Failed to spawn subprocess: ${(e as Error).message}`);
532
446
  }
533
447
 
@@ -539,6 +453,7 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
539
453
  const cleanup = async () => {
540
454
  if (timeoutId) clearTimeout(timeoutId);
541
455
  if (sigkillId) clearTimeout(sigkillId);
456
+ try { unlinkSync(inputFile); } catch {}
542
457
  if (aborted) {
543
458
  safeKill(proc, "SIGKILL");
544
459
  await proc.exited.catch(() => {});
@@ -551,9 +466,6 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
551
466
  };
552
467
 
553
468
  try {
554
- proc.stdin.write(input);
555
- proc.stdin.end();
556
-
557
469
  const stdoutChunks: Uint8Array[] = [];
558
470
  const stderrChunks: Uint8Array[] = [];
559
471
  let stdoutBytes = 0;
@@ -641,36 +553,8 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
641
553
  }
642
554
 
643
555
  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
- };
556
+ if (!isNativeAvailable()) {
557
+ throw new Error("Native module required. Run: bun run build:core");
674
558
  }
675
559
 
676
560
  const nativeOptions: NativeLocalParseOptions = {
@@ -684,52 +568,14 @@ export async function parseLocalSourcesAsync(options: LocalParseOptions): Promis
684
568
  return runInSubprocess<ParsedMessages>("parseLocalSources", [nativeOptions]);
685
569
  }
686
570
 
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
571
  export async function finalizeReportAsync(options: FinalizeOptions): Promise<ModelReport> {
723
572
  if (!isNativeAvailable()) {
724
- const startTime = performance.now();
725
- const messages = buildMessagesForFallback(options);
726
- return generateModelReportTS(messages, options.pricing, startTime);
573
+ throw new Error("Native module required. Run: bun run build:core");
727
574
  }
728
575
 
729
576
  const nativeOptions: NativeFinalizeReportOptions = {
730
577
  homeDir: undefined,
731
578
  localMessages: options.localMessages,
732
- pricing: options.pricing,
733
579
  includeCursor: options.includeCursor,
734
580
  since: options.since,
735
581
  until: options.until,
@@ -741,15 +587,12 @@ export async function finalizeReportAsync(options: FinalizeOptions): Promise<Mod
741
587
 
742
588
  export async function finalizeMonthlyReportAsync(options: FinalizeOptions): Promise<MonthlyReport> {
743
589
  if (!isNativeAvailable()) {
744
- const startTime = performance.now();
745
- const messages = buildMessagesForFallback(options);
746
- return generateMonthlyReportTS(messages, options.pricing, startTime);
590
+ throw new Error("Native module required. Run: bun run build:core");
747
591
  }
748
592
 
749
593
  const nativeOptions: NativeFinalizeReportOptions = {
750
594
  homeDir: undefined,
751
595
  localMessages: options.localMessages,
752
- pricing: options.pricing,
753
596
  includeCursor: options.includeCursor,
754
597
  since: options.since,
755
598
  until: options.until,
@@ -761,15 +604,12 @@ export async function finalizeMonthlyReportAsync(options: FinalizeOptions): Prom
761
604
 
762
605
  export async function finalizeGraphAsync(options: FinalizeOptions): Promise<TokenContributionData> {
763
606
  if (!isNativeAvailable()) {
764
- const startTime = performance.now();
765
- const messages = buildMessagesForFallback(options);
766
- return generateGraphDataTS(messages, options.pricing, startTime);
607
+ throw new Error("Native module required. Run: bun run build:core");
767
608
  }
768
609
 
769
610
  const nativeOptions: NativeFinalizeReportOptions = {
770
611
  homeDir: undefined,
771
612
  localMessages: options.localMessages,
772
- pricing: options.pricing,
773
613
  includeCursor: options.includeCursor,
774
614
  since: options.since,
775
615
  until: options.until,
@@ -780,33 +620,33 @@ export async function finalizeGraphAsync(options: FinalizeOptions): Promise<Toke
780
620
  return fromNativeResult(result);
781
621
  }
782
622
 
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
- });
623
+ export interface ReportAndGraph {
624
+ report: ModelReport;
625
+ graph: TokenContributionData;
626
+ }
627
+
628
+ interface NativeReportAndGraph {
629
+ report: NativeModelReport;
630
+ graph: NativeGraphResult;
631
+ }
797
632
 
798
- return generateGraphDataTS(parsed.messages, options.pricing, startTime);
633
+ export async function finalizeReportAndGraphAsync(options: FinalizeOptions): Promise<ReportAndGraph> {
634
+ if (!isNativeAvailable()) {
635
+ throw new Error("Native module required. Run: bun run build:core");
799
636
  }
800
637
 
801
- const nativeOptions: NativeReportOptions = {
638
+ const nativeOptions: NativeFinalizeReportOptions = {
802
639
  homeDir: undefined,
803
- sources: options.sources,
804
- pricing: options.pricing,
640
+ localMessages: options.localMessages,
641
+ includeCursor: options.includeCursor,
805
642
  since: options.since,
806
643
  until: options.until,
807
644
  year: options.year,
808
645
  };
809
646
 
810
- const result = await runInSubprocess<NativeGraphResult>("generateGraphWithPricing", [nativeOptions]);
811
- return fromNativeResult(result);
647
+ const result = await runInSubprocess<NativeReportAndGraph>("finalizeReportAndGraph", [nativeOptions]);
648
+ return {
649
+ report: result.report,
650
+ graph: fromNativeResult(result.graph),
651
+ };
812
652
  }
@@ -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);