@tokscale/cli 1.2.2 → 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/src/cli.ts CHANGED
@@ -121,6 +121,7 @@ interface FilterOptions {
121
121
  amp?: boolean;
122
122
  droid?: boolean;
123
123
  openclaw?: boolean;
124
+ pi?: boolean;
124
125
  }
125
126
 
126
127
  interface DateFilterOptions {
@@ -446,6 +447,7 @@ async function main() {
446
447
  .option("--amp", "Show only Amp usage")
447
448
  .option("--droid", "Show only Factory Droid usage")
448
449
  .option("--openclaw", "Show only OpenClaw usage")
450
+ .option("--pi", "Show only Pi usage")
449
451
  .option("--today", "Show only today's usage")
450
452
  .option("--week", "Show last 7 days")
451
453
  .option("--month", "Show current month")
@@ -483,6 +485,7 @@ async function main() {
483
485
  .option("--amp", "Show only Amp usage")
484
486
  .option("--droid", "Show only Factory Droid usage")
485
487
  .option("--openclaw", "Show only OpenClaw usage")
488
+ .option("--pi", "Show only Pi usage")
486
489
  .option("--today", "Show only today's usage")
487
490
  .option("--week", "Show last 7 days")
488
491
  .option("--month", "Show current month")
@@ -529,12 +532,13 @@ async function main() {
529
532
  path.join(homeDir, ".moltbot", "agents"),
530
533
  path.join(homeDir, ".moldbot", "agents"),
531
534
  ];
535
+ const piSessions = path.join(homeDir, ".pi", "agent", "sessions");
532
536
 
533
537
  let localMessages: ParsedMessages | null = null;
534
538
  try {
535
539
  localMessages = await parseLocalSourcesAsync({
536
540
  homeDir,
537
- sources: ["opencode", "claude", "codex", "gemini", "amp", "droid", "openclaw"],
541
+ sources: ["opencode", "claude", "codex", "gemini", "amp", "droid", "openclaw", "pi"],
538
542
  });
539
543
  } catch (e) {
540
544
  console.error(`Error: ${(e as Error).message}`);
@@ -634,6 +638,15 @@ async function main() {
634
638
  headlessPaths: [],
635
639
  headlessMessageCount: 0,
636
640
  },
641
+ {
642
+ source: "pi",
643
+ label: "Pi",
644
+ sessionsPath: piSessions,
645
+ messageCount: localMessages.piCount,
646
+ headlessSupported: false,
647
+ headlessPaths: [],
648
+ headlessMessageCount: 0,
649
+ },
637
650
  ];
638
651
 
639
652
  if (options.json) {
@@ -723,6 +736,7 @@ async function main() {
723
736
  .option("--amp", "Include only Amp data")
724
737
  .option("--droid", "Include only Factory Droid data")
725
738
  .option("--openclaw", "Include only OpenClaw data")
739
+ .option("--pi", "Include only Pi data")
726
740
  .option("--today", "Show only today's usage")
727
741
  .option("--week", "Show last 7 days")
728
742
  .option("--month", "Show current month")
@@ -748,6 +762,7 @@ async function main() {
748
762
  .option("--amp", "Include only Amp data")
749
763
  .option("--droid", "Include only Factory Droid data")
750
764
  .option("--openclaw", "Include only OpenClaw data")
765
+ .option("--pi", "Include only Pi data")
751
766
  .option("--no-spinner", "Disable loading spinner (for scripting)")
752
767
  .option("--short", "Display total tokens in abbreviated format (e.g., 7.14B)")
753
768
  .addOption(new Option("--agents", "Show Top OpenCode Agents (default)").conflicts("clients"))
@@ -793,6 +808,7 @@ async function main() {
793
808
  .option("--amp", "Include only Amp data")
794
809
  .option("--droid", "Include only Factory Droid data")
795
810
  .option("--openclaw", "Include only OpenClaw data")
811
+ .option("--pi", "Include only Pi data")
796
812
  .option("--since <date>", "Start date (YYYY-MM-DD)")
797
813
  .option("--until <date>", "End date (YYYY-MM-DD)")
798
814
  .option("--year <year>", "Filter to specific year")
@@ -807,6 +823,7 @@ async function main() {
807
823
  amp: options.amp,
808
824
  droid: options.droid,
809
825
  openclaw: options.openclaw,
826
+ pi: options.pi,
810
827
  since: options.since,
811
828
  until: options.until,
812
829
  year: options.year,
@@ -829,6 +846,7 @@ async function main() {
829
846
  .option("--amp", "Show only Amp usage")
830
847
  .option("--droid", "Show only Factory Droid usage")
831
848
  .option("--openclaw", "Show only OpenClaw usage")
849
+ .option("--pi", "Show only Pi usage")
832
850
  .option("--today", "Show only today's usage")
833
851
  .option("--week", "Show last 7 days")
834
852
  .option("--month", "Show current month")
@@ -985,7 +1003,7 @@ async function main() {
985
1003
  }
986
1004
 
987
1005
  function getEnabledSources(options: FilterOptions): SourceType[] | undefined {
988
- const hasFilter = options.opencode || options.claude || options.codex || options.gemini || options.cursor || options.amp || options.droid || options.openclaw;
1006
+ const hasFilter = options.opencode || options.claude || options.codex || options.gemini || options.cursor || options.amp || options.droid || options.openclaw || options.pi;
989
1007
  if (!hasFilter) return undefined; // All sources
990
1008
 
991
1009
  const sources: SourceType[] = [];
@@ -997,6 +1015,7 @@ function getEnabledSources(options: FilterOptions): SourceType[] | undefined {
997
1015
  if (options.amp) sources.push("amp");
998
1016
  if (options.droid) sources.push("droid");
999
1017
  if (options.openclaw) sources.push("openclaw");
1018
+ if (options.pi) sources.push("pi");
1000
1019
  return sources;
1001
1020
  }
1002
1021
 
@@ -1086,7 +1105,7 @@ async function showModelReport(options: FilterOptions & DateFilterOptions & { be
1086
1105
  const useSpinner = extraOptions?.spinner !== false;
1087
1106
  const spinner = useSpinner ? createSpinner({ color: "cyan" }) : null;
1088
1107
 
1089
- const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw'])
1108
+ const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw', 'pi'])
1090
1109
  .filter(s => s !== 'cursor');
1091
1110
 
1092
1111
  spinner?.start(pc.gray("Scanning session data..."));
@@ -1116,7 +1135,7 @@ async function showModelReport(options: FilterOptions & DateFilterOptions & { be
1116
1135
 
1117
1136
  let report: ModelReport;
1118
1137
  try {
1119
- const emptyMessages: ParsedMessages = { messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, processingTimeMs: 0 };
1138
+ const emptyMessages: ParsedMessages = { messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, piCount: 0, processingTimeMs: 0 };
1120
1139
  report = await finalizeReportAsync({
1121
1140
  localMessages: localMessages || emptyMessages,
1122
1141
  includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
@@ -1223,7 +1242,7 @@ async function showMonthlyReport(options: FilterOptions & DateFilterOptions & {
1223
1242
 
1224
1243
  const dateFilters = getDateFilters(options);
1225
1244
  const enabledSources = getEnabledSources(options);
1226
- const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw'])
1245
+ const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw', 'pi'])
1227
1246
  .filter(s => s !== 'cursor');
1228
1247
  const includeCursor = !enabledSources || enabledSources.includes('cursor');
1229
1248
 
@@ -1332,7 +1351,7 @@ async function outputJsonReport(
1332
1351
  const enabledSources = getEnabledSources(options);
1333
1352
  const onlyCursor = enabledSources?.length === 1 && enabledSources[0] === 'cursor';
1334
1353
  const includeCursor = !enabledSources || enabledSources.includes('cursor');
1335
- const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw'])
1354
+ const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw', 'pi'])
1336
1355
  .filter(s => s !== 'cursor');
1337
1356
 
1338
1357
  const { cursorSync, localMessages } = await loadDataSourcesParallel(
@@ -1345,7 +1364,7 @@ async function outputJsonReport(
1345
1364
  process.exit(1);
1346
1365
  }
1347
1366
 
1348
- const emptyMessages: ParsedMessages = { messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, processingTimeMs: 0 };
1367
+ const emptyMessages: ParsedMessages = { messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, piCount: 0, processingTimeMs: 0 };
1349
1368
 
1350
1369
  if (reportType === "models") {
1351
1370
  const report = await finalizeReportAsync({
@@ -1380,7 +1399,7 @@ async function handleGraphCommand(options: GraphCommandOptions) {
1380
1399
 
1381
1400
  const dateFilters = getDateFilters(options);
1382
1401
  const enabledSources = getEnabledSources(options);
1383
- const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw'])
1402
+ const localSources: SourceType[] = (enabledSources || ['opencode', 'claude', 'codex', 'gemini', 'cursor', 'amp', 'droid', 'openclaw', 'pi'])
1384
1403
  .filter(s => s !== 'cursor');
1385
1404
  const includeCursor = !enabledSources || enabledSources.includes('cursor');
1386
1405
 
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Valid source identifiers
8
8
  */
9
- export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid" | "openclaw";
9
+ export type SourceType = "opencode" | "claude" | "codex" | "gemini" | "cursor" | "amp" | "droid" | "openclaw" | "pi";
10
10
 
11
11
  /**
12
12
  * Token breakdown by category
package/src/native.ts CHANGED
@@ -153,8 +153,9 @@ interface NativeParsedMessages {
153
153
  codexCount: number;
154
154
  geminiCount: number;
155
155
  ampCount: number;
156
- droidCount?: number;
157
- openclawCount?: number;
156
+ droidCount: number;
157
+ openclawCount: number;
158
+ piCount: number;
158
159
  processingTimeMs: number;
159
160
  }
160
161
 
@@ -355,6 +356,7 @@ export interface ParsedMessages {
355
356
  ampCount: number;
356
357
  droidCount: number;
357
358
  openclawCount: number;
359
+ piCount: number;
358
360
  processingTimeMs: number;
359
361
  }
360
362
 
@@ -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") {
@@ -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
 
@@ -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;
@@ -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