@tokscale/cli 1.2.1 → 1.2.3
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 +27 -8
- 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 -1
- package/dist/native-runner.js +6 -6
- package/dist/native-runner.js.map +1 -1
- package/dist/native.d.ts +1 -0
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +32 -59
- 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 +1 -0
- package/dist/submit.d.ts.map +1 -1
- package/dist/submit.js +3 -1
- package/dist/submit.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +4 -0
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/config/settings.d.ts +1 -0
- package/dist/tui/config/settings.d.ts.map +1 -1
- package/dist/tui/config/settings.js +10 -2
- 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 +2 -1
- package/dist/tui/hooks/useData.js.map +1 -1
- package/dist/tui/types/index.d.ts +1 -1
- package/dist/tui/types/index.d.ts.map +1 -1
- package/dist/tui/types/index.js +2 -1
- package/dist/tui/types/index.js.map +1 -1
- package/dist/tui/utils/colors.d.ts.map +1 -1
- package/dist/tui/utils/colors.js +3 -0
- package/dist/tui/utils/colors.js.map +1 -1
- package/dist/wrapped.d.ts.map +1 -1
- package/dist/wrapped.js +5 -2
- package/dist/wrapped.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +27 -8
- package/src/graph-types.ts +1 -1
- package/src/native-runner.ts +17 -17
- package/src/native.ts +35 -78
- package/src/sessions/types.ts +1 -1
- package/src/submit.ts +4 -2
- package/src/tui/App.tsx +1 -0
- package/src/tui/config/settings.ts +13 -2
- package/src/tui/hooks/useData.ts +3 -2
- package/src/tui/types/index.ts +3 -2
- package/src/tui/utils/colors.ts +2 -0
- package/src/wrapped.ts +6 -3
package/src/native.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
GraphOptions as TSGraphOptions,
|
|
11
11
|
SourceType,
|
|
12
12
|
} from "./graph-types.js";
|
|
13
|
+
import { loadSettings } from "./tui/config/settings.js";
|
|
13
14
|
|
|
14
15
|
// =============================================================================
|
|
15
16
|
// Types matching Rust exports
|
|
@@ -152,8 +153,9 @@ interface NativeParsedMessages {
|
|
|
152
153
|
codexCount: number;
|
|
153
154
|
geminiCount: number;
|
|
154
155
|
ampCount: number;
|
|
155
|
-
droidCount
|
|
156
|
-
openclawCount
|
|
156
|
+
droidCount: number;
|
|
157
|
+
openclawCount: number;
|
|
158
|
+
piCount: number;
|
|
157
159
|
processingTimeMs: number;
|
|
158
160
|
}
|
|
159
161
|
|
|
@@ -354,6 +356,7 @@ export interface ParsedMessages {
|
|
|
354
356
|
ampCount: number;
|
|
355
357
|
droidCount: number;
|
|
356
358
|
openclawCount: number;
|
|
359
|
+
piCount: number;
|
|
357
360
|
processingTimeMs: number;
|
|
358
361
|
}
|
|
359
362
|
|
|
@@ -381,25 +384,21 @@ export interface FinalizeOptions {
|
|
|
381
384
|
|
|
382
385
|
import { fileURLToPath } from "node:url";
|
|
383
386
|
import { dirname, join } from "node:path";
|
|
384
|
-
import { writeFileSync, unlinkSync, mkdirSync } from "node:fs";
|
|
387
|
+
import { writeFileSync, readFileSync, unlinkSync, mkdirSync, existsSync } from "node:fs";
|
|
385
388
|
import { tmpdir } from "node:os";
|
|
386
389
|
import { randomUUID } from "node:crypto";
|
|
387
390
|
|
|
388
391
|
const __filename = fileURLToPath(import.meta.url);
|
|
389
392
|
const __dirname = dirname(__filename);
|
|
390
393
|
|
|
391
|
-
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
392
|
-
const NATIVE_TIMEOUT_MS = parseInt(
|
|
393
|
-
process.env.TOKSCALE_NATIVE_TIMEOUT_MS || String(DEFAULT_TIMEOUT_MS),
|
|
394
|
-
10
|
|
395
|
-
);
|
|
396
|
-
|
|
397
394
|
const SIGKILL_GRACE_MS = 500;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
)
|
|
395
|
+
|
|
396
|
+
function getNativeTimeoutMs(): number {
|
|
397
|
+
const settings = loadSettings();
|
|
398
|
+
return process.env.TOKSCALE_NATIVE_TIMEOUT_MS
|
|
399
|
+
? parseInt(process.env.TOKSCALE_NATIVE_TIMEOUT_MS, 10)
|
|
400
|
+
: (settings.nativeTimeoutMs ?? 300_000);
|
|
401
|
+
}
|
|
403
402
|
|
|
404
403
|
interface BunSubprocess {
|
|
405
404
|
stdout: { text: () => Promise<string> };
|
|
@@ -411,8 +410,8 @@ interface BunSubprocess {
|
|
|
411
410
|
}
|
|
412
411
|
|
|
413
412
|
interface BunSpawnOptions {
|
|
414
|
-
stdout:
|
|
415
|
-
stderr:
|
|
413
|
+
stdout: "pipe" | "ignore";
|
|
414
|
+
stderr: "pipe" | "ignore";
|
|
416
415
|
}
|
|
417
416
|
|
|
418
417
|
interface BunGlobalType {
|
|
@@ -426,89 +425,46 @@ function safeKill(proc: unknown, signal?: string): void {
|
|
|
426
425
|
}
|
|
427
426
|
|
|
428
427
|
async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
|
|
428
|
+
const NATIVE_TIMEOUT_MS = getNativeTimeoutMs();
|
|
429
429
|
const runnerPath = join(__dirname, "native-runner.js");
|
|
430
430
|
const input = JSON.stringify({ method, args });
|
|
431
431
|
|
|
432
432
|
const tmpDir = join(tmpdir(), "tokscale");
|
|
433
433
|
mkdirSync(tmpDir, { recursive: true });
|
|
434
|
-
const
|
|
435
|
-
|
|
434
|
+
const id = randomUUID();
|
|
435
|
+
const inputFile = join(tmpDir, `input-${id}.json`);
|
|
436
|
+
const outputFile = join(tmpDir, `output-${id}.json`);
|
|
437
|
+
|
|
436
438
|
writeFileSync(inputFile, input, "utf-8");
|
|
437
439
|
|
|
438
440
|
const BunGlobal = (globalThis as Record<string, unknown>).Bun as BunGlobalType;
|
|
439
441
|
|
|
440
442
|
let proc: BunSubprocess;
|
|
441
443
|
try {
|
|
442
|
-
proc = BunGlobal.spawn([process.execPath, runnerPath, inputFile], {
|
|
443
|
-
stdout: "
|
|
444
|
+
proc = BunGlobal.spawn([process.execPath, runnerPath, inputFile, outputFile], {
|
|
445
|
+
stdout: "ignore",
|
|
444
446
|
stderr: "pipe",
|
|
445
447
|
});
|
|
446
448
|
} catch (e) {
|
|
447
|
-
unlinkSync(inputFile);
|
|
449
|
+
try { unlinkSync(inputFile); } catch {}
|
|
448
450
|
throw new Error(`Failed to spawn subprocess: ${(e as Error).message}`);
|
|
449
451
|
}
|
|
450
452
|
|
|
451
453
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
452
454
|
let sigkillId: ReturnType<typeof setTimeout> | null = null;
|
|
453
455
|
let weInitiatedKill = false;
|
|
454
|
-
let aborted = false;
|
|
455
456
|
|
|
456
457
|
const cleanup = async () => {
|
|
457
458
|
if (timeoutId) clearTimeout(timeoutId);
|
|
458
459
|
if (sigkillId) clearTimeout(sigkillId);
|
|
459
460
|
try { unlinkSync(inputFile); } catch {}
|
|
460
|
-
|
|
461
|
-
safeKill(proc, "SIGKILL");
|
|
462
|
-
await proc.exited.catch(() => {});
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
|
|
466
|
-
const abort = () => {
|
|
467
|
-
aborted = true;
|
|
468
|
-
weInitiatedKill = true;
|
|
461
|
+
try { unlinkSync(outputFile); } catch {}
|
|
469
462
|
};
|
|
470
463
|
|
|
471
464
|
try {
|
|
472
|
-
const stdoutChunks: Uint8Array[] = [];
|
|
473
|
-
const stderrChunks: Uint8Array[] = [];
|
|
474
|
-
let stdoutBytes = 0;
|
|
475
|
-
let stderrBytes = 0;
|
|
476
|
-
|
|
477
|
-
const readStream = async (
|
|
478
|
-
stream: BunSubprocess["stdout"],
|
|
479
|
-
chunks: Uint8Array[],
|
|
480
|
-
getBytesRef: () => number,
|
|
481
|
-
setBytesRef: (n: number) => void
|
|
482
|
-
): Promise<string> => {
|
|
483
|
-
const reader = (stream as unknown as ReadableStream<Uint8Array>).getReader();
|
|
484
|
-
try {
|
|
485
|
-
while (!aborted) {
|
|
486
|
-
const { done, value } = await reader.read();
|
|
487
|
-
if (done) break;
|
|
488
|
-
const newTotal = getBytesRef() + value.length;
|
|
489
|
-
if (newTotal > MAX_OUTPUT_BYTES) {
|
|
490
|
-
abort();
|
|
491
|
-
throw new Error(`Output exceeded ${MAX_OUTPUT_BYTES} bytes`);
|
|
492
|
-
}
|
|
493
|
-
setBytesRef(newTotal);
|
|
494
|
-
chunks.push(value);
|
|
495
|
-
}
|
|
496
|
-
} finally {
|
|
497
|
-
await reader.cancel().catch(() => {});
|
|
498
|
-
reader.releaseLock();
|
|
499
|
-
}
|
|
500
|
-
const combined = new Uint8Array(getBytesRef());
|
|
501
|
-
let offset = 0;
|
|
502
|
-
for (const chunk of chunks) {
|
|
503
|
-
combined.set(chunk, offset);
|
|
504
|
-
offset += chunk.length;
|
|
505
|
-
}
|
|
506
|
-
return new TextDecoder().decode(combined);
|
|
507
|
-
};
|
|
508
|
-
|
|
509
465
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
510
466
|
timeoutId = setTimeout(() => {
|
|
511
|
-
|
|
467
|
+
weInitiatedKill = true;
|
|
512
468
|
safeKill(proc, "SIGTERM");
|
|
513
469
|
sigkillId = setTimeout(() => {
|
|
514
470
|
safeKill(proc, "SIGKILL");
|
|
@@ -519,15 +475,10 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
|
|
|
519
475
|
}, NATIVE_TIMEOUT_MS);
|
|
520
476
|
});
|
|
521
477
|
|
|
522
|
-
const
|
|
523
|
-
readStream(proc.stdout, stdoutChunks, () => stdoutBytes, (n) => { stdoutBytes = n; }),
|
|
524
|
-
readStream(proc.stderr, stderrChunks, () => stderrBytes, (n) => { stderrBytes = n; }),
|
|
525
|
-
proc.exited,
|
|
526
|
-
]);
|
|
478
|
+
const exitCode = await Promise.race([proc.exited, timeoutPromise]);
|
|
527
479
|
|
|
528
|
-
|
|
480
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
529
481
|
|
|
530
|
-
// Note: proc.killed is always true after exit in Bun (even for normal exits), so we only check signalCode
|
|
531
482
|
if (weInitiatedKill || proc.signalCode) {
|
|
532
483
|
throw new Error(
|
|
533
484
|
`Subprocess '${method}' was killed (signal: ${proc.signalCode || "SIGTERM"})`
|
|
@@ -535,6 +486,7 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
|
|
|
535
486
|
}
|
|
536
487
|
|
|
537
488
|
if (exitCode !== 0) {
|
|
489
|
+
const stderr = await proc.stderr.text();
|
|
538
490
|
let errorMsg = stderr || `Process exited with code ${exitCode}`;
|
|
539
491
|
try {
|
|
540
492
|
const parsed = JSON.parse(stderr);
|
|
@@ -543,11 +495,16 @@ async function runInSubprocess<T>(method: string, args: unknown[]): Promise<T> {
|
|
|
543
495
|
throw new Error(`Subprocess '${method}' failed: ${errorMsg}`);
|
|
544
496
|
}
|
|
545
497
|
|
|
498
|
+
if (!existsSync(outputFile)) {
|
|
499
|
+
throw new Error(`Subprocess '${method}' did not produce output file`);
|
|
500
|
+
}
|
|
501
|
+
|
|
546
502
|
try {
|
|
547
|
-
|
|
503
|
+
const output = readFileSync(outputFile, "utf-8");
|
|
504
|
+
return JSON.parse(output) as T;
|
|
548
505
|
} catch (e) {
|
|
549
506
|
throw new Error(
|
|
550
|
-
`Failed to parse subprocess output: ${(e as Error).message}
|
|
507
|
+
`Failed to parse subprocess output: ${(e as Error).message}`
|
|
551
508
|
);
|
|
552
509
|
}
|
|
553
510
|
} finally {
|
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" | "amp" | "droid" | "openclaw";
|
|
25
|
+
export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid" | "openclaw" | "pi";
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Convert Unix milliseconds timestamp to YYYY-MM-DD date string
|
package/src/submit.ts
CHANGED
|
@@ -25,6 +25,7 @@ interface SubmitOptions {
|
|
|
25
25
|
amp?: boolean;
|
|
26
26
|
droid?: boolean;
|
|
27
27
|
openclaw?: boolean;
|
|
28
|
+
pi?: boolean;
|
|
28
29
|
since?: string;
|
|
29
30
|
until?: string;
|
|
30
31
|
year?: string;
|
|
@@ -50,7 +51,7 @@ interface SubmitResponse {
|
|
|
50
51
|
details?: string[];
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid" | "openclaw";
|
|
54
|
+
type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid" | "openclaw" | "pi";
|
|
54
55
|
|
|
55
56
|
async function checkGhCliExists(): Promise<boolean> {
|
|
56
57
|
try {
|
|
@@ -193,7 +194,7 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
193
194
|
|
|
194
195
|
console.log(pc.gray(" Scanning local session data..."));
|
|
195
196
|
|
|
196
|
-
const hasFilter = options.opencode || options.claude || options.codex || options.gemini || options.cursor || options.amp || options.droid || options.openclaw;
|
|
197
|
+
const hasFilter = options.opencode || options.claude || options.codex || options.gemini || options.cursor || options.amp || options.droid || options.openclaw || options.pi;
|
|
197
198
|
let sources: SourceType[] | undefined;
|
|
198
199
|
let includeCursor = true;
|
|
199
200
|
if (hasFilter) {
|
|
@@ -206,6 +207,7 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
206
207
|
if (options.amp) sources.push("amp");
|
|
207
208
|
if (options.droid) sources.push("droid");
|
|
208
209
|
if (options.openclaw) sources.push("openclaw");
|
|
210
|
+
if (options.pi) sources.push("pi");
|
|
209
211
|
includeCursor = sources.includes("cursor");
|
|
210
212
|
}
|
|
211
213
|
|
package/src/tui/App.tsx
CHANGED
|
@@ -288,6 +288,7 @@ export function App(props: AppProps) {
|
|
|
288
288
|
if (key.name === "6") { handleSourceToggle("amp"); return; }
|
|
289
289
|
if (key.name === "7") { handleSourceToggle("droid"); return; }
|
|
290
290
|
if (key.name === "8") { handleSourceToggle("openclaw"); return; }
|
|
291
|
+
if (key.name === "9") { handleSourceToggle("pi"); return; }
|
|
291
292
|
|
|
292
293
|
if (key.name === "up") {
|
|
293
294
|
if (activeTab() === "overview") {
|
|
@@ -14,11 +14,16 @@ const MIN_AUTO_REFRESH_MS = 30000;
|
|
|
14
14
|
const MAX_AUTO_REFRESH_MS = 3600000;
|
|
15
15
|
const DEFAULT_AUTO_REFRESH_MS = 60000;
|
|
16
16
|
|
|
17
|
+
const DEFAULT_NATIVE_TIMEOUT_MS = 300_000; // 5 minutes
|
|
18
|
+
const MIN_NATIVE_TIMEOUT_MS = 5_000; // 5 seconds
|
|
19
|
+
const MAX_NATIVE_TIMEOUT_MS = 3_600_000; // 1 hour
|
|
20
|
+
|
|
17
21
|
export interface TokscaleSettings {
|
|
18
22
|
colorPalette: string;
|
|
19
23
|
autoRefreshEnabled?: boolean;
|
|
20
24
|
autoRefreshMs?: number;
|
|
21
25
|
includeUnusedModels?: boolean;
|
|
26
|
+
nativeTimeoutMs?: number;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
function validateSettings(raw: unknown): TokscaleSettings {
|
|
@@ -27,6 +32,7 @@ function validateSettings(raw: unknown): TokscaleSettings {
|
|
|
27
32
|
autoRefreshEnabled: false,
|
|
28
33
|
autoRefreshMs: DEFAULT_AUTO_REFRESH_MS,
|
|
29
34
|
includeUnusedModels: false,
|
|
35
|
+
nativeTimeoutMs: DEFAULT_NATIVE_TIMEOUT_MS,
|
|
30
36
|
};
|
|
31
37
|
|
|
32
38
|
if (!raw || typeof raw !== "object") return defaults;
|
|
@@ -43,7 +49,12 @@ function validateSettings(raw: unknown): TokscaleSettings {
|
|
|
43
49
|
|
|
44
50
|
const includeUnusedModels = typeof obj.includeUnusedModels === "boolean" ? obj.includeUnusedModels : defaults.includeUnusedModels;
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
let nativeTimeoutMs = defaults.nativeTimeoutMs;
|
|
53
|
+
if (typeof obj.nativeTimeoutMs === "number" && Number.isFinite(obj.nativeTimeoutMs)) {
|
|
54
|
+
nativeTimeoutMs = Math.min(MAX_NATIVE_TIMEOUT_MS, Math.max(MIN_NATIVE_TIMEOUT_MS, obj.nativeTimeoutMs));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { colorPalette, autoRefreshEnabled, autoRefreshMs, includeUnusedModels, nativeTimeoutMs };
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
interface CachedTUIData {
|
|
@@ -66,7 +77,7 @@ export function loadSettings(): TokscaleSettings {
|
|
|
66
77
|
}
|
|
67
78
|
} catch {
|
|
68
79
|
}
|
|
69
|
-
return { colorPalette: "blue", autoRefreshEnabled: false, autoRefreshMs: DEFAULT_AUTO_REFRESH_MS, includeUnusedModels: false };
|
|
80
|
+
return { colorPalette: "blue", autoRefreshEnabled: false, autoRefreshMs: DEFAULT_AUTO_REFRESH_MS, includeUnusedModels: false, nativeTimeoutMs: DEFAULT_NATIVE_TIMEOUT_MS };
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
export function saveSettings(updates: Partial<TokscaleSettings>): void {
|
package/src/tui/hooks/useData.ts
CHANGED
|
@@ -158,8 +158,8 @@ async function loadData(
|
|
|
158
158
|
const phase1Results = await Promise.allSettled([
|
|
159
159
|
includeCursor && isCursorLoggedIn() ? syncCursorCache() : Promise.resolve({ synced: false, rows: 0, error: undefined }),
|
|
160
160
|
localSources.length > 0
|
|
161
|
-
? parseLocalSourcesAsync({ sources: localSources as ("opencode" | "claude" | "codex" | "gemini" | "amp" | "droid" | "openclaw")[], since, until, year })
|
|
162
|
-
: Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, processingTimeMs: 0 } as ParsedMessages),
|
|
161
|
+
? parseLocalSourcesAsync({ sources: localSources as ("opencode" | "claude" | "codex" | "gemini" | "amp" | "droid" | "openclaw" | "pi")[], since, until, year })
|
|
162
|
+
: Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, piCount: 0, processingTimeMs: 0 } as ParsedMessages),
|
|
163
163
|
]);
|
|
164
164
|
|
|
165
165
|
const cursorSync = phase1Results[0].status === "fulfilled"
|
|
@@ -184,6 +184,7 @@ async function loadData(
|
|
|
184
184
|
ampCount: 0,
|
|
185
185
|
droidCount: 0,
|
|
186
186
|
openclawCount: 0,
|
|
187
|
+
piCount: 0,
|
|
187
188
|
processingTimeMs: 0,
|
|
188
189
|
};
|
|
189
190
|
|
package/src/tui/types/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ColorPaletteName } from "../config/themes.js";
|
|
|
2
2
|
|
|
3
3
|
export type TabType = "overview" | "model" | "daily" | "stats";
|
|
4
4
|
export type SortType = "cost" | "tokens" | "date";
|
|
5
|
-
export type SourceType = "opencode" | "claude" | "codex" | "cursor" | "gemini" | "amp" | "droid" | "openclaw";
|
|
5
|
+
export type SourceType = "opencode" | "claude" | "codex" | "cursor" | "gemini" | "amp" | "droid" | "openclaw" | "pi";
|
|
6
6
|
|
|
7
7
|
export type { ColorPaletteName };
|
|
8
8
|
|
|
@@ -163,7 +163,8 @@ export const SOURCE_LABELS: Record<SourceType, string> = {
|
|
|
163
163
|
amp: "AM",
|
|
164
164
|
droid: "DR",
|
|
165
165
|
openclaw: "CL",
|
|
166
|
+
pi: "PI",
|
|
166
167
|
} as const;
|
|
167
168
|
|
|
168
169
|
export const TABS: readonly TabType[] = ["overview", "model", "daily", "stats"] as const;
|
|
169
|
-
export const ALL_SOURCES: readonly SourceType[] = ["opencode", "claude", "codex", "cursor", "gemini", "amp", "droid", "openclaw"] as const;
|
|
170
|
+
export const ALL_SOURCES: readonly SourceType[] = ["opencode", "claude", "codex", "cursor", "gemini", "amp", "droid", "openclaw", "pi"] as const;
|
package/src/tui/utils/colors.ts
CHANGED
|
@@ -61,6 +61,7 @@ export const SOURCE_COLORS: Record<SourceType, string> = {
|
|
|
61
61
|
amp: "#EC4899",
|
|
62
62
|
droid: "#10b981",
|
|
63
63
|
openclaw: "#ef4444",
|
|
64
|
+
pi: "#f97316",
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
export function getSourceColor(source: SourceType | string): string {
|
|
@@ -70,5 +71,6 @@ export function getSourceColor(source: SourceType | string): string {
|
|
|
70
71
|
export function getSourceDisplayName(source: string): string {
|
|
71
72
|
if (source === "droid") return "Droid";
|
|
72
73
|
if (source === "openclaw") return "OpenClaw";
|
|
74
|
+
if (source === "pi") return "Pi";
|
|
73
75
|
return source.charAt(0).toUpperCase() + source.slice(1);
|
|
74
76
|
}
|
package/src/wrapped.ts
CHANGED
|
@@ -65,6 +65,7 @@ const SOURCE_DISPLAY_NAMES: Record<string, string> = {
|
|
|
65
65
|
amp: "Amp",
|
|
66
66
|
droid: "Droid",
|
|
67
67
|
openclaw: "OpenClaw",
|
|
68
|
+
pi: "Pi",
|
|
68
69
|
};
|
|
69
70
|
|
|
70
71
|
const ASSETS_BASE_URL = "https://tokscale.ai/assets/logos";
|
|
@@ -97,6 +98,7 @@ const CLIENT_LOGO_URLS: Record<string, string> = {
|
|
|
97
98
|
"Amp": `${ASSETS_BASE_URL}/amp.png`,
|
|
98
99
|
"Droid": `${ASSETS_BASE_URL}/droid.png`,
|
|
99
100
|
"OpenClaw": `${ASSETS_BASE_URL}/openclaw.png`,
|
|
101
|
+
"Pi": `${ASSETS_BASE_URL}/pi.png`,
|
|
100
102
|
};
|
|
101
103
|
|
|
102
104
|
const PROVIDER_LOGO_URLS: Record<string, string> = {
|
|
@@ -214,8 +216,8 @@ async function ensureFontsLoaded(): Promise<void> {
|
|
|
214
216
|
|
|
215
217
|
async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
|
|
216
218
|
const year = options.year || new Date().getFullYear().toString();
|
|
217
|
-
const sources = options.sources || ["opencode", "claude", "codex", "gemini", "cursor", "amp", "droid", "openclaw"];
|
|
218
|
-
const localSources = sources.filter(s => s !== "cursor") as ("opencode" | "claude" | "codex" | "gemini" | "amp" | "droid" | "openclaw")[];
|
|
219
|
+
const sources = options.sources || ["opencode", "claude", "codex", "gemini", "cursor", "amp", "droid", "openclaw", "pi"];
|
|
220
|
+
const localSources = sources.filter(s => s !== "cursor") as ("opencode" | "claude" | "codex" | "gemini" | "amp" | "droid" | "openclaw" | "pi")[];
|
|
219
221
|
const includeCursor = sources.includes("cursor");
|
|
220
222
|
|
|
221
223
|
const since = `${year}-01-01`;
|
|
@@ -225,7 +227,7 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
|
|
|
225
227
|
includeCursor && isCursorLoggedIn() ? syncCursorCache() : Promise.resolve({ synced: false, rows: 0, error: undefined }),
|
|
226
228
|
localSources.length > 0
|
|
227
229
|
? parseLocalSourcesAsync({ sources: localSources, since, until, year })
|
|
228
|
-
: Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, processingTimeMs: 0 } as ParsedMessages),
|
|
230
|
+
: Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, piCount: 0, processingTimeMs: 0 } as ParsedMessages),
|
|
229
231
|
]);
|
|
230
232
|
|
|
231
233
|
const cursorSync = phase1Results[0].status === "fulfilled"
|
|
@@ -249,6 +251,7 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
|
|
|
249
251
|
ampCount: 0,
|
|
250
252
|
droidCount: 0,
|
|
251
253
|
openclawCount: 0,
|
|
254
|
+
piCount: 0,
|
|
252
255
|
processingTimeMs: 0,
|
|
253
256
|
};
|
|
254
257
|
|