@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.
- package/dist/cli.js +214 -91
- package/dist/cli.js.map +1 -1
- package/dist/graph-types.d.ts +1 -1
- package/dist/graph-types.d.ts.map +1 -1
- package/dist/native-runner.d.ts +1 -2
- package/dist/native-runner.d.ts.map +1 -1
- package/dist/native-runner.js +11 -39
- package/dist/native-runner.js.map +1 -1
- package/dist/native.d.ts +9 -30
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +31 -138
- package/dist/native.js.map +1 -1
- package/dist/sessions/types.d.ts +1 -1
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/submit.d.ts +2 -0
- package/dist/submit.d.ts.map +1 -1
- package/dist/submit.js +32 -16
- package/dist/submit.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +14 -7
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/DailyView.d.ts.map +1 -1
- package/dist/tui/components/DailyView.js +25 -8
- package/dist/tui/components/DailyView.js.map +1 -1
- package/dist/tui/components/DateBreakdownPanel.js +2 -2
- package/dist/tui/components/DateBreakdownPanel.js.map +1 -1
- package/dist/tui/components/Footer.d.ts.map +1 -1
- package/dist/tui/components/Footer.js +2 -3
- package/dist/tui/components/Footer.js.map +1 -1
- package/dist/tui/components/LoadingSpinner.d.ts.map +1 -1
- package/dist/tui/components/LoadingSpinner.js +1 -2
- package/dist/tui/components/LoadingSpinner.js.map +1 -1
- package/dist/tui/components/ModelView.js +2 -2
- package/dist/tui/components/ModelView.js.map +1 -1
- package/dist/tui/config/settings.d.ts +4 -4
- package/dist/tui/config/settings.d.ts.map +1 -1
- package/dist/tui/config/settings.js +11 -4
- package/dist/tui/config/settings.js.map +1 -1
- package/dist/tui/hooks/useData.d.ts.map +1 -1
- package/dist/tui/hooks/useData.js +29 -42
- package/dist/tui/hooks/useData.js.map +1 -1
- package/dist/tui/types/index.d.ts +2 -2
- package/dist/tui/types/index.d.ts.map +1 -1
- package/dist/tui/types/index.js +3 -1
- package/dist/tui/types/index.js.map +1 -1
- package/dist/tui/utils/colors.d.ts +1 -0
- package/dist/tui/utils/colors.d.ts.map +1 -1
- package/dist/tui/utils/colors.js +7 -0
- package/dist/tui/utils/colors.js.map +1 -1
- package/dist/wrapped.d.ts.map +1 -1
- package/dist/wrapped.js +20 -48
- package/dist/wrapped.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +232 -97
- package/src/graph-types.ts +1 -1
- package/src/native-runner.js +4 -0
- package/src/native-runner.ts +12 -42
- package/src/native.ts +47 -207
- package/src/sessions/types.ts +1 -1
- package/src/submit.ts +36 -22
- package/src/tui/App.tsx +10 -7
- package/src/tui/components/DailyView.tsx +29 -11
- package/src/tui/components/DateBreakdownPanel.tsx +2 -2
- package/src/tui/components/Footer.tsx +7 -2
- package/src/tui/components/LoadingSpinner.tsx +1 -2
- package/src/tui/components/ModelView.tsx +2 -2
- package/src/tui/config/settings.ts +18 -9
- package/src/tui/hooks/useData.ts +36 -47
- package/src/tui/types/index.ts +5 -4
- package/src/tui/utils/colors.ts +7 -0
- package/src/wrapped.ts +21 -54
- package/dist/graph.d.ts +0 -29
- package/dist/graph.d.ts.map +0 -1
- package/dist/graph.js +0 -383
- package/dist/graph.js.map +0 -1
- package/dist/pricing.d.ts +0 -58
- package/dist/pricing.d.ts.map +0 -1
- package/dist/pricing.js +0 -232
- package/dist/pricing.js.map +0 -1
- package/dist/sessions/claudecode.d.ts +0 -8
- package/dist/sessions/claudecode.d.ts.map +0 -1
- package/dist/sessions/claudecode.js +0 -84
- package/dist/sessions/claudecode.js.map +0 -1
- package/dist/sessions/codex.d.ts +0 -8
- package/dist/sessions/codex.d.ts.map +0 -1
- package/dist/sessions/codex.js +0 -158
- package/dist/sessions/codex.js.map +0 -1
- package/dist/sessions/gemini.d.ts +0 -8
- package/dist/sessions/gemini.d.ts.map +0 -1
- package/dist/sessions/gemini.js +0 -66
- package/dist/sessions/gemini.js.map +0 -1
- package/dist/sessions/index.d.ts +0 -32
- package/dist/sessions/index.d.ts.map +0 -1
- package/dist/sessions/index.js +0 -96
- package/dist/sessions/index.js.map +0 -1
- package/dist/sessions/opencode.d.ts +0 -9
- package/dist/sessions/opencode.d.ts.map +0 -1
- package/dist/sessions/opencode.js +0 -69
- package/dist/sessions/opencode.js.map +0 -1
- package/dist/sessions/reports.d.ts +0 -58
- package/dist/sessions/reports.d.ts.map +0 -1
- package/dist/sessions/reports.js +0 -337
- package/dist/sessions/reports.js.map +0 -1
- package/src/graph.ts +0 -485
- package/src/pricing.ts +0 -309
- package/src/sessions/claudecode.ts +0 -119
- package/src/sessions/codex.ts +0 -227
- package/src/sessions/gemini.ts +0 -108
- package/src/sessions/index.ts +0 -126
- package/src/sessions/opencode.ts +0 -117
- package/src/sessions/reports.ts +0 -475
package/src/graph-types.ts
CHANGED
package/src/native-runner.ts
CHANGED
|
@@ -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:
|
|
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
|
|
20
|
+
const inputFile = process.argv[2];
|
|
53
21
|
|
|
54
|
-
if (!
|
|
55
|
-
process.stderr.write(JSON.stringify({ error: "No input
|
|
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 "
|
|
88
|
-
result = nativeCore.
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
645
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
-
|
|
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:
|
|
638
|
+
const nativeOptions: NativeFinalizeReportOptions = {
|
|
802
639
|
homeDir: undefined,
|
|
803
|
-
|
|
804
|
-
|
|
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<
|
|
811
|
-
return
|
|
647
|
+
const result = await runInSubprocess<NativeReportAndGraph>("finalizeReportAndGraph", [nativeOptions]);
|
|
648
|
+
return {
|
|
649
|
+
report: result.report,
|
|
650
|
+
graph: fromNativeResult(result.graph),
|
|
651
|
+
};
|
|
812
652
|
}
|
package/src/sessions/types.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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);
|