@tokscale/cli 1.0.17 → 1.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/cli.js +214 -91
  2. package/dist/cli.js.map +1 -1
  3. package/dist/graph-types.d.ts +1 -1
  4. package/dist/graph-types.d.ts.map +1 -1
  5. package/dist/native-runner.js +5 -5
  6. package/dist/native-runner.js.map +1 -1
  7. package/dist/native.d.ts +9 -30
  8. package/dist/native.d.ts.map +1 -1
  9. package/dist/native.js +18 -134
  10. package/dist/native.js.map +1 -1
  11. package/dist/sessions/types.d.ts +1 -1
  12. package/dist/sessions/types.d.ts.map +1 -1
  13. package/dist/submit.d.ts +2 -0
  14. package/dist/submit.d.ts.map +1 -1
  15. package/dist/submit.js +32 -16
  16. package/dist/submit.js.map +1 -1
  17. package/dist/tui/App.d.ts.map +1 -1
  18. package/dist/tui/App.js +13 -6
  19. package/dist/tui/App.js.map +1 -1
  20. package/dist/tui/components/DailyView.d.ts.map +1 -1
  21. package/dist/tui/components/DailyView.js +25 -8
  22. package/dist/tui/components/DailyView.js.map +1 -1
  23. package/dist/tui/components/DateBreakdownPanel.js +2 -2
  24. package/dist/tui/components/DateBreakdownPanel.js.map +1 -1
  25. package/dist/tui/components/Footer.d.ts.map +1 -1
  26. package/dist/tui/components/Footer.js +2 -3
  27. package/dist/tui/components/Footer.js.map +1 -1
  28. package/dist/tui/components/LoadingSpinner.d.ts.map +1 -1
  29. package/dist/tui/components/LoadingSpinner.js +1 -2
  30. package/dist/tui/components/LoadingSpinner.js.map +1 -1
  31. package/dist/tui/components/ModelView.js +2 -2
  32. package/dist/tui/components/ModelView.js.map +1 -1
  33. package/dist/tui/config/settings.d.ts +4 -4
  34. package/dist/tui/config/settings.d.ts.map +1 -1
  35. package/dist/tui/config/settings.js +11 -4
  36. package/dist/tui/config/settings.js.map +1 -1
  37. package/dist/tui/hooks/useData.d.ts.map +1 -1
  38. package/dist/tui/hooks/useData.js +29 -42
  39. package/dist/tui/hooks/useData.js.map +1 -1
  40. package/dist/tui/types/index.d.ts +2 -2
  41. package/dist/tui/types/index.d.ts.map +1 -1
  42. package/dist/tui/types/index.js +3 -1
  43. package/dist/tui/types/index.js.map +1 -1
  44. package/dist/tui/utils/colors.d.ts +1 -0
  45. package/dist/tui/utils/colors.d.ts.map +1 -1
  46. package/dist/tui/utils/colors.js +7 -0
  47. package/dist/tui/utils/colors.js.map +1 -1
  48. package/dist/wrapped.d.ts.map +1 -1
  49. package/dist/wrapped.js +20 -48
  50. package/dist/wrapped.js.map +1 -1
  51. package/package.json +2 -2
  52. package/src/cli.ts +232 -97
  53. package/src/graph-types.ts +1 -1
  54. package/src/native-runner.ts +5 -5
  55. package/src/native.ts +35 -200
  56. package/src/sessions/types.ts +1 -1
  57. package/src/submit.ts +36 -22
  58. package/src/tui/App.tsx +9 -6
  59. package/src/tui/components/DailyView.tsx +29 -11
  60. package/src/tui/components/DateBreakdownPanel.tsx +2 -2
  61. package/src/tui/components/Footer.tsx +7 -2
  62. package/src/tui/components/LoadingSpinner.tsx +1 -2
  63. package/src/tui/components/ModelView.tsx +2 -2
  64. package/src/tui/config/settings.ts +18 -9
  65. package/src/tui/hooks/useData.ts +36 -47
  66. package/src/tui/types/index.ts +5 -4
  67. package/src/tui/utils/colors.ts +7 -0
  68. package/src/wrapped.ts +21 -54
  69. package/dist/graph.d.ts +0 -29
  70. package/dist/graph.d.ts.map +0 -1
  71. package/dist/graph.js +0 -383
  72. package/dist/graph.js.map +0 -1
  73. package/dist/pricing.d.ts +0 -58
  74. package/dist/pricing.d.ts.map +0 -1
  75. package/dist/pricing.js +0 -232
  76. package/dist/pricing.js.map +0 -1
  77. package/dist/sessions/claudecode.d.ts +0 -8
  78. package/dist/sessions/claudecode.d.ts.map +0 -1
  79. package/dist/sessions/claudecode.js +0 -84
  80. package/dist/sessions/claudecode.js.map +0 -1
  81. package/dist/sessions/codex.d.ts +0 -8
  82. package/dist/sessions/codex.d.ts.map +0 -1
  83. package/dist/sessions/codex.js +0 -158
  84. package/dist/sessions/codex.js.map +0 -1
  85. package/dist/sessions/gemini.d.ts +0 -8
  86. package/dist/sessions/gemini.d.ts.map +0 -1
  87. package/dist/sessions/gemini.js +0 -66
  88. package/dist/sessions/gemini.js.map +0 -1
  89. package/dist/sessions/index.d.ts +0 -32
  90. package/dist/sessions/index.d.ts.map +0 -1
  91. package/dist/sessions/index.js +0 -96
  92. package/dist/sessions/index.js.map +0 -1
  93. package/dist/sessions/opencode.d.ts +0 -9
  94. package/dist/sessions/opencode.d.ts.map +0 -1
  95. package/dist/sessions/opencode.js +0 -69
  96. package/dist/sessions/opencode.js.map +0 -1
  97. package/dist/sessions/reports.d.ts +0 -58
  98. package/dist/sessions/reports.d.ts.map +0 -1
  99. package/dist/sessions/reports.js +0 -337
  100. package/dist/sessions/reports.js.map +0 -1
  101. package/src/graph.ts +0 -485
  102. package/src/pricing.ts +0 -309
  103. package/src/sessions/claudecode.ts +0 -119
  104. package/src/sessions/codex.ts +0 -227
  105. package/src/sessions/gemini.ts +0 -108
  106. package/src/sessions/index.ts +0 -126
  107. package/src/sessions/opencode.ts +0 -117
  108. package/src/sessions/reports.ts +0 -475
@@ -131,6 +131,12 @@ export function Footer(props: FooterProps) {
131
131
  enabled={props.enabledSources.has("gemini")}
132
132
  onToggle={props.onSourceToggle}
133
133
  />
134
+ <SourceBadge
135
+ name={isVeryNarrowTerminal() ? "6" : "6:AM"}
136
+ source="amp"
137
+ enabled={props.enabledSources.has("amp")}
138
+ onToggle={props.onSourceToggle}
139
+ />
134
140
  <Show when={!isVeryNarrowTerminal()}>
135
141
  <text dim>|</text>
136
142
  <SortButton
@@ -279,9 +285,8 @@ const SPINNER_INTERVAL = 40;
279
285
 
280
286
  const PHASE_MESSAGES: Record<LoadingPhase, string> = {
281
287
  idle: "Initializing...",
288
+ "parsing-sources": "Scanning session data...",
282
289
  "loading-pricing": "Loading pricing data...",
283
- "syncing-cursor": "Syncing Cursor data...",
284
- "parsing-sources": "Parsing session files...",
285
290
  "finalizing-report": "Finalizing report...",
286
291
  complete: "Complete",
287
292
  };
@@ -31,9 +31,8 @@ function getScannerState(frame: number): SpinnerState {
31
31
 
32
32
  const PHASE_MESSAGES: Record<LoadingPhase, string> = {
33
33
  "idle": "Initializing...",
34
+ "parsing-sources": "Scanning session data...",
34
35
  "loading-pricing": "Loading pricing data...",
35
- "syncing-cursor": "Syncing Cursor data...",
36
- "parsing-sources": "Parsing session files...",
37
36
  "finalizing-report": "Finalizing report...",
38
37
  "complete": "Complete",
39
38
  };
@@ -1,6 +1,6 @@
1
1
  import { For, createMemo, type Accessor } from "solid-js";
2
2
  import type { TUIData, SortType } from "../hooks/useData.js";
3
- import { getModelColor } from "../utils/colors.js";
3
+ import { getModelColor, getSourceDisplayName } from "../utils/colors.js";
4
4
  import { formatTokensCompact, formatCostFull } from "../utils/format.js";
5
5
  import { isNarrow, isVeryNarrow } from "../utils/responsive.js";
6
6
 
@@ -65,7 +65,7 @@ export function ModelView(props: ModelViewProps) {
65
65
  const formattedRows = createMemo(() => {
66
66
  const nameWidth = nameColumnWidths().text;
67
67
  return visibleEntries().map((entry) => {
68
- const sourceLabel = entry.source.charAt(0).toUpperCase() + entry.source.slice(1);
68
+ const sourceLabel = getSourceDisplayName(entry.source);
69
69
  const fullName = `${sourceLabel} ${entry.model}`;
70
70
  let displayName = fullName;
71
71
  if (fullName.length > nameWidth) {
@@ -5,7 +5,8 @@ import type { TUIData, DailyModelBreakdown } from "../types/index.js";
5
5
 
6
6
  const CONFIG_DIR = join(homedir(), ".config", "tokscale");
7
7
  const CACHE_DIR = join(homedir(), ".cache", "tokscale");
8
- const CONFIG_FILE = join(CONFIG_DIR, "tui-settings.json");
8
+ const CONFIG_FILE = join(CONFIG_DIR, "settings.json");
9
+ const LEGACY_CONFIG_FILE = join(CONFIG_DIR, "tui-settings.json");
9
10
  const CACHE_FILE = join(CACHE_DIR, "tui-data-cache.json");
10
11
 
11
12
  const CACHE_STALE_THRESHOLD_MS = 60 * 1000;
@@ -13,17 +14,19 @@ const MIN_AUTO_REFRESH_MS = 30000;
13
14
  const MAX_AUTO_REFRESH_MS = 3600000;
14
15
  const DEFAULT_AUTO_REFRESH_MS = 60000;
15
16
 
16
- interface TUISettings {
17
+ export interface TokscaleSettings {
17
18
  colorPalette: string;
18
19
  autoRefreshEnabled?: boolean;
19
20
  autoRefreshMs?: number;
21
+ includeUnusedModels?: boolean;
20
22
  }
21
23
 
22
- function validateSettings(raw: unknown): TUISettings {
23
- const defaults: TUISettings = {
24
+ function validateSettings(raw: unknown): TokscaleSettings {
25
+ const defaults: TokscaleSettings = {
24
26
  colorPalette: "blue",
25
27
  autoRefreshEnabled: false,
26
- autoRefreshMs: DEFAULT_AUTO_REFRESH_MS
28
+ autoRefreshMs: DEFAULT_AUTO_REFRESH_MS,
29
+ includeUnusedModels: false,
27
30
  };
28
31
 
29
32
  if (!raw || typeof raw !== "object") return defaults;
@@ -38,7 +41,9 @@ function validateSettings(raw: unknown): TUISettings {
38
41
  autoRefreshMs = Math.min(MAX_AUTO_REFRESH_MS, Math.max(MIN_AUTO_REFRESH_MS, obj.autoRefreshMs));
39
42
  }
40
43
 
41
- return { colorPalette, autoRefreshEnabled, autoRefreshMs };
44
+ const includeUnusedModels = typeof obj.includeUnusedModels === "boolean" ? obj.includeUnusedModels : defaults.includeUnusedModels;
45
+
46
+ return { colorPalette, autoRefreshEnabled, autoRefreshMs, includeUnusedModels };
42
47
  }
43
48
 
44
49
  interface CachedTUIData {
@@ -49,18 +54,22 @@ interface CachedTUIData {
49
54
  };
50
55
  }
51
56
 
52
- export function loadSettings(): TUISettings {
57
+ export function loadSettings(): TokscaleSettings {
53
58
  try {
54
59
  if (existsSync(CONFIG_FILE)) {
55
60
  const raw = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
56
61
  return validateSettings(raw);
57
62
  }
63
+ if (existsSync(LEGACY_CONFIG_FILE)) {
64
+ const raw = JSON.parse(readFileSync(LEGACY_CONFIG_FILE, "utf-8"));
65
+ return validateSettings(raw);
66
+ }
58
67
  } catch {
59
68
  }
60
- return { colorPalette: "blue", autoRefreshEnabled: false, autoRefreshMs: DEFAULT_AUTO_REFRESH_MS };
69
+ return { colorPalette: "blue", autoRefreshEnabled: false, autoRefreshMs: DEFAULT_AUTO_REFRESH_MS, includeUnusedModels: false };
61
70
  }
62
71
 
63
- export function saveSettings(updates: Partial<TUISettings>): void {
72
+ export function saveSettings(updates: Partial<TokscaleSettings>): void {
64
73
  try {
65
74
  if (!existsSync(CONFIG_DIR)) {
66
75
  mkdirSync(CONFIG_DIR, { recursive: true });
@@ -16,14 +16,13 @@ import type {
16
16
  } from "../types/index.js";
17
17
  import {
18
18
  parseLocalSourcesAsync,
19
- finalizeReportAsync,
20
- finalizeGraphAsync,
19
+ finalizeReportAndGraphAsync,
21
20
  type ParsedMessages,
22
21
  } from "../../native.js";
23
- import { PricingFetcher } from "../../pricing.js";
22
+
24
23
  import { syncCursorCache, loadCursorCredentials } from "../../cursor.js";
25
24
  import { getModelColor } from "../utils/colors.js";
26
- import { loadCachedData, saveCachedData, isCacheStale } from "../config/settings.js";
25
+ import { loadCachedData, saveCachedData, isCacheStale, loadSettings } from "../config/settings.js";
27
26
 
28
27
  export type {
29
28
  SortType,
@@ -144,27 +143,30 @@ function calculateLongestSession(messages: Array<{ sessionId: string; timestamp:
144
143
  return `${totalSeconds}s`;
145
144
  }
146
145
 
147
- async function loadData(enabledSources: Set<SourceType>, dateFilters?: DateFilters): Promise<TUIData> {
146
+ async function loadData(
147
+ enabledSources: Set<SourceType>,
148
+ dateFilters?: DateFilters,
149
+ setPhase?: (phase: LoadingPhase) => void
150
+ ): Promise<TUIData> {
148
151
  const sources = Array.from(enabledSources);
149
152
  const localSources = sources.filter(s => s !== "cursor");
150
153
  const includeCursor = sources.includes("cursor");
151
154
  const { since, until, year } = dateFilters ?? {};
152
155
 
153
- const pricingFetcher = new PricingFetcher();
156
+ setPhase?.("parsing-sources");
154
157
 
155
158
  const phase1Results = await Promise.allSettled([
156
- pricingFetcher.fetchPricing(),
157
159
  includeCursor && loadCursorCredentials() ? syncCursorCache() : Promise.resolve({ synced: false, rows: 0 }),
158
160
  localSources.length > 0
159
- ? parseLocalSourcesAsync({ sources: localSources as ("opencode" | "claude" | "codex" | "gemini")[], since, until, year })
160
- : Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, processingTimeMs: 0 } as ParsedMessages),
161
+ ? parseLocalSourcesAsync({ sources: localSources as ("opencode" | "claude" | "codex" | "gemini" | "amp" | "droid")[], since, until, year })
162
+ : Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, processingTimeMs: 0 } as ParsedMessages),
161
163
  ]);
162
164
 
163
- const cursorSync = phase1Results[1].status === "fulfilled"
164
- ? phase1Results[1].value
165
+ const cursorSync = phase1Results[0].status === "fulfilled"
166
+ ? phase1Results[0].value
165
167
  : { synced: false, rows: 0 };
166
- const localMessages = phase1Results[2].status === "fulfilled"
167
- ? phase1Results[2].value
168
+ const localMessages = phase1Results[1].status === "fulfilled"
169
+ ? phase1Results[1].value
168
170
  : null;
169
171
 
170
172
  const emptyMessages: ParsedMessages = {
@@ -173,39 +175,23 @@ async function loadData(enabledSources: Set<SourceType>, dateFilters?: DateFilte
173
175
  claudeCount: 0,
174
176
  codexCount: 0,
175
177
  geminiCount: 0,
178
+ ampCount: 0,
179
+ droidCount: 0,
176
180
  processingTimeMs: 0,
177
181
  };
178
182
 
179
- const phase2Results = await Promise.allSettled([
180
- finalizeReportAsync({
181
- localMessages: localMessages || emptyMessages,
182
- pricing: pricingFetcher.toPricingEntries(),
183
- includeCursor: includeCursor && cursorSync.synced,
184
- since,
185
- until,
186
- year,
187
- }),
188
- finalizeGraphAsync({
189
- localMessages: localMessages || emptyMessages,
190
- pricing: pricingFetcher.toPricingEntries(),
191
- includeCursor: includeCursor && cursorSync.synced,
192
- since,
193
- until,
194
- year,
195
- }),
196
- ]);
197
-
198
- if (phase2Results[0].status === "rejected") {
199
- throw new Error(`Failed to finalize report: ${phase2Results[0].reason}`);
200
- }
201
- if (phase2Results[1].status === "rejected") {
202
- throw new Error(`Failed to finalize graph: ${phase2Results[1].reason}`);
203
- }
204
-
205
- const report = phase2Results[0].value;
206
- const graph = phase2Results[1].value;
207
-
208
- const modelEntries: ModelEntry[] = report.entries.map(e => ({
183
+ setPhase?.("finalizing-report");
184
+ // Single call ensures consistent pricing between report and graph
185
+ const { report, graph } = await finalizeReportAndGraphAsync({
186
+ localMessages: localMessages || emptyMessages,
187
+ includeCursor: includeCursor && cursorSync.synced,
188
+ since,
189
+ until,
190
+ year,
191
+ });
192
+
193
+ const settings = loadSettings();
194
+ const allModelEntries: ModelEntry[] = report.entries.map(e => ({
209
195
  source: e.source,
210
196
  model: e.model,
211
197
  input: e.input,
@@ -216,6 +202,9 @@ async function loadData(enabledSources: Set<SourceType>, dateFilters?: DateFilte
216
202
  total: e.input + e.output + e.cacheWrite + e.cacheRead + e.reasoning,
217
203
  cost: e.cost,
218
204
  }));
205
+ const modelEntries = settings.includeUnusedModels
206
+ ? allModelEntries
207
+ : allModelEntries.filter(e => e.total > 0);
219
208
 
220
209
  const dailyMap = new Map<string, DailyEntry>();
221
210
  for (const contrib of graph.contributions) {
@@ -490,20 +479,20 @@ export function useData(enabledSources: Accessor<Set<SourceType>>, dateFilters?:
490
479
  setData(cachedData);
491
480
  setLoading(false);
492
481
  setIsRefreshing(true);
493
- setLoadingPhase("loading-pricing");
482
+ setLoadingPhase("idle");
494
483
  } else {
495
484
  setLoading(true);
496
- setLoadingPhase("loading-pricing");
485
+ setLoadingPhase("idle");
497
486
  }
498
487
  } else {
499
488
  setIsRefreshing(true);
500
- setLoadingPhase("loading-pricing");
489
+ setLoadingPhase("idle");
501
490
  setForceRefresh(false);
502
491
  }
503
492
 
504
493
  const requestId = currentRequestId;
505
494
  setError(null);
506
- loadData(sources, dateFilters)
495
+ loadData(sources, dateFilters, setLoadingPhase)
507
496
  .then((freshData) => {
508
497
  if (requestId !== currentRequestId) return;
509
498
  setData(freshData);
@@ -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";
5
- export type SourceType = "opencode" | "claude" | "codex" | "cursor" | "gemini";
5
+ export type SourceType = "opencode" | "claude" | "codex" | "cursor" | "gemini" | "amp" | "droid";
6
6
 
7
7
  export type { ColorPaletteName };
8
8
 
@@ -105,9 +105,8 @@ export interface TUISettings {
105
105
 
106
106
  export type LoadingPhase =
107
107
  | "idle"
108
- | "loading-pricing"
109
- | "syncing-cursor"
110
108
  | "parsing-sources"
109
+ | "loading-pricing"
111
110
  | "finalizing-report"
112
111
  | "complete";
113
112
 
@@ -161,7 +160,9 @@ export const SOURCE_LABELS: Record<SourceType, string> = {
161
160
  codex: "CX",
162
161
  cursor: "CR",
163
162
  gemini: "GM",
163
+ amp: "AM",
164
+ droid: "DR",
164
165
  } as const;
165
166
 
166
167
  export const TABS: readonly TabType[] = ["overview", "model", "daily", "stats"] as const;
167
- export const ALL_SOURCES: readonly SourceType[] = ["opencode", "claude", "codex", "cursor", "gemini"] as const;
168
+ export const ALL_SOURCES: readonly SourceType[] = ["opencode", "claude", "codex", "cursor", "gemini", "amp", "droid"] as const;
@@ -58,8 +58,15 @@ export const SOURCE_COLORS: Record<SourceType, string> = {
58
58
  codex: "#3b82f6",
59
59
  cursor: "#a855f7",
60
60
  gemini: "#06b6d4",
61
+ amp: "#EC4899",
62
+ droid: "#10b981",
61
63
  };
62
64
 
63
65
  export function getSourceColor(source: SourceType | string): string {
64
66
  return SOURCE_COLORS[source as SourceType] || "#888888";
65
67
  }
68
+
69
+ export function getSourceDisplayName(source: string): string {
70
+ if (source === "droid") return "Droid";
71
+ return source.charAt(0).toUpperCase() + source.slice(1);
72
+ }
package/src/wrapped.ts CHANGED
@@ -6,11 +6,9 @@ import * as os from "node:os";
6
6
  import pc from "picocolors";
7
7
  import {
8
8
  parseLocalSourcesAsync,
9
- finalizeReportAsync,
10
- finalizeGraphAsync,
9
+ finalizeReportAndGraphAsync,
11
10
  type ParsedMessages,
12
11
  } from "./native.js";
13
- import { PricingFetcher } from "./pricing.js";
14
12
  import { syncCursorCache, loadCursorCredentials } from "./cursor.js";
15
13
  import { loadCredentials } from "./credentials.js";
16
14
  import type { SourceType } from "./graph-types.js";
@@ -64,6 +62,7 @@ const SOURCE_DISPLAY_NAMES: Record<string, string> = {
64
62
  codex: "Codex CLI",
65
63
  gemini: "Gemini CLI",
66
64
  cursor: "Cursor IDE",
65
+ amp: "Amp",
67
66
  };
68
67
 
69
68
  const ASSETS_BASE_URL = "https://tokscale.ai/assets/logos";
@@ -93,6 +92,7 @@ const CLIENT_LOGO_URLS: Record<string, string> = {
93
92
  "Codex CLI": `${ASSETS_BASE_URL}/openai.jpg`,
94
93
  "Gemini CLI": `${ASSETS_BASE_URL}/gemini.png`,
95
94
  "Cursor IDE": `${ASSETS_BASE_URL}/cursor.jpg`,
95
+ "Amp": `${ASSETS_BASE_URL}/amp.png`,
96
96
  };
97
97
 
98
98
  const PROVIDER_LOGO_URLS: Record<string, string> = {
@@ -210,28 +210,25 @@ async function ensureFontsLoaded(): Promise<void> {
210
210
 
211
211
  async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
212
212
  const year = options.year || new Date().getFullYear().toString();
213
- const sources = options.sources || ["opencode", "claude", "codex", "gemini", "cursor"];
214
- const localSources = sources.filter(s => s !== "cursor") as ("opencode" | "claude" | "codex" | "gemini")[];
213
+ const sources = options.sources || ["opencode", "claude", "codex", "gemini", "cursor", "amp", "droid"];
214
+ const localSources = sources.filter(s => s !== "cursor") as ("opencode" | "claude" | "codex" | "gemini" | "amp" | "droid")[];
215
215
  const includeCursor = sources.includes("cursor");
216
216
 
217
217
  const since = `${year}-01-01`;
218
218
  const until = `${year}-12-31`;
219
219
 
220
- const pricingFetcher = new PricingFetcher();
221
-
222
220
  const phase1Results = await Promise.allSettled([
223
- pricingFetcher.fetchPricing(),
224
221
  includeCursor && loadCursorCredentials() ? syncCursorCache() : Promise.resolve({ synced: false, rows: 0 }),
225
222
  localSources.length > 0
226
- ? parseLocalSourcesAsync({ sources: localSources, since, until, year, forceTypescript: options.includeAgents !== false })
227
- : Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, processingTimeMs: 0 } as ParsedMessages),
223
+ ? parseLocalSourcesAsync({ sources: localSources, since, until, year })
224
+ : Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, processingTimeMs: 0 } as ParsedMessages),
228
225
  ]);
229
226
 
230
- const cursorSync = phase1Results[1].status === "fulfilled"
231
- ? phase1Results[1].value
227
+ const cursorSync = phase1Results[0].status === "fulfilled"
228
+ ? phase1Results[0].value
232
229
  : { synced: false, rows: 0 };
233
- const localMessages = phase1Results[2].status === "fulfilled"
234
- ? phase1Results[2].value
230
+ const localMessages = phase1Results[1].status === "fulfilled"
231
+ ? phase1Results[1].value
235
232
  : null;
236
233
 
237
234
  const emptyMessages: ParsedMessages = {
@@ -240,37 +237,18 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
240
237
  claudeCount: 0,
241
238
  codexCount: 0,
242
239
  geminiCount: 0,
240
+ ampCount: 0,
241
+ droidCount: 0,
243
242
  processingTimeMs: 0,
244
243
  };
245
244
 
246
- const [reportResult, graphResult] = await Promise.allSettled([
247
- finalizeReportAsync({
248
- localMessages: localMessages || emptyMessages,
249
- pricing: pricingFetcher.toPricingEntries(),
250
- includeCursor: includeCursor && cursorSync.synced,
251
- since,
252
- until,
253
- year,
254
- }),
255
- finalizeGraphAsync({
256
- localMessages: localMessages || emptyMessages,
257
- pricing: pricingFetcher.toPricingEntries(),
258
- includeCursor: includeCursor && cursorSync.synced,
259
- since,
260
- until,
261
- year,
262
- }),
263
- ]);
264
-
265
- if (reportResult.status === "rejected") {
266
- throw new Error(`Failed to generate report: ${reportResult.reason}`);
267
- }
268
- if (graphResult.status === "rejected") {
269
- throw new Error(`Failed to generate graph: ${graphResult.reason}`);
270
- }
271
-
272
- const report = reportResult.value;
273
- const graph = graphResult.value;
245
+ const { report, graph } = await finalizeReportAndGraphAsync({
246
+ localMessages: localMessages || emptyMessages,
247
+ includeCursor: includeCursor && cursorSync.synced,
248
+ since,
249
+ until,
250
+ year,
251
+ });
274
252
 
275
253
  const modelMap = new Map<string, { cost: number; tokens: number }>();
276
254
  for (const entry of report.entries) {
@@ -301,9 +279,6 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
301
279
 
302
280
  let topAgents: Array<{ name: string; cost: number; tokens: number; messages: number }> | undefined;
303
281
  if (options.includeAgents !== false && localMessages) {
304
- const pricingEntries = pricingFetcher.toPricingEntries();
305
- const pricingMap = new Map(pricingEntries.map(p => [p.modelId, p.pricing]));
306
-
307
282
  const agentMap = new Map<string, { cost: number; tokens: number; messages: number }>();
308
283
  for (const msg of localMessages.messages) {
309
284
  if (msg.source === "opencode" && msg.agent) {
@@ -311,17 +286,9 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
311
286
  const existing = agentMap.get(normalizedAgent) || { cost: 0, tokens: 0, messages: 0 };
312
287
 
313
288
  const msgTokens = msg.input + msg.output + msg.cacheRead + msg.cacheWrite + msg.reasoning;
314
- const pricing = pricingMap.get(msg.modelId);
315
- let msgCost = 0;
316
- if (pricing) {
317
- msgCost = (msg.input * pricing.inputCostPerToken) +
318
- (msg.output * pricing.outputCostPerToken) +
319
- (msg.cacheRead * (pricing.cacheReadInputTokenCost || 0)) +
320
- (msg.cacheWrite * (pricing.cacheCreationInputTokenCost || 0));
321
- }
322
289
 
323
290
  agentMap.set(normalizedAgent, {
324
- cost: existing.cost + msgCost,
291
+ cost: existing.cost,
325
292
  tokens: existing.tokens + msgTokens,
326
293
  messages: existing.messages + 1,
327
294
  });
package/dist/graph.d.ts DELETED
@@ -1,29 +0,0 @@
1
- /**
2
- * Graph data generation module
3
- * Aggregates token usage data by date for contribution graph visualization
4
- *
5
- * Key design: intensity is calculated based on COST ($), not tokens
6
- *
7
- * This module supports two implementations:
8
- * - Native Rust (fast, ~10x faster) - used when available
9
- * - Pure TypeScript (fallback) - always available
10
- */
11
- import type { TokenContributionData, GraphOptions } from "./graph-types.js";
12
- /**
13
- * Check if native implementation is available
14
- */
15
- export declare function isNativeAvailable(): boolean;
16
- /**
17
- * Generate contribution graph data from all sources
18
- *
19
- * Uses native Rust implementation if available, falls back to TypeScript.
20
- * Set `options.forceTypescript = true` to skip native module.
21
- */
22
- export declare function generateGraphData(options?: GraphOptions & {
23
- forceTypescript?: boolean;
24
- }): Promise<TokenContributionData>;
25
- /**
26
- * Pure TypeScript implementation of graph data generation
27
- */
28
- export declare function generateGraphDataTS(options?: GraphOptions): Promise<TokenContributionData>;
29
- //# sourceMappingURL=graph.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,OAAO,KAAK,EACV,qBAAqB,EAIrB,YAAY,EAGb,MAAM,kBAAkB,CAAC;AAY1B;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,GAAE,YAAY,GAAG;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAO,GACzD,OAAO,CAAC,qBAAqB,CAAC,CAahC;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,qBAAqB,CAAC,CAwChC"}