@tokscale/cli 1.4.3 → 2.0.1

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 (188) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +128 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +19 -26
  6. package/dist/auth.d.ts +0 -17
  7. package/dist/auth.d.ts.map +0 -1
  8. package/dist/auth.js +0 -162
  9. package/dist/auth.js.map +0 -1
  10. package/dist/cli.d.ts +0 -9
  11. package/dist/cli.d.ts.map +0 -1
  12. package/dist/cli.js +0 -1550
  13. package/dist/cli.js.map +0 -1
  14. package/dist/credentials.d.ts +0 -50
  15. package/dist/credentials.d.ts.map +0 -1
  16. package/dist/credentials.js +0 -151
  17. package/dist/credentials.js.map +0 -1
  18. package/dist/cursor.d.ts +0 -167
  19. package/dist/cursor.d.ts.map +0 -1
  20. package/dist/cursor.js +0 -906
  21. package/dist/cursor.js.map +0 -1
  22. package/dist/date-utils.d.ts +0 -10
  23. package/dist/date-utils.d.ts.map +0 -1
  24. package/dist/date-utils.js +0 -47
  25. package/dist/date-utils.js.map +0 -1
  26. package/dist/graph-types.d.ts +0 -142
  27. package/dist/graph-types.d.ts.map +0 -1
  28. package/dist/graph-types.js +0 -6
  29. package/dist/graph-types.js.map +0 -1
  30. package/dist/native-runner.d.ts +0 -11
  31. package/dist/native-runner.d.ts.map +0 -1
  32. package/dist/native-runner.js +0 -77
  33. package/dist/native-runner.js.map +0 -1
  34. package/dist/native.d.ts +0 -106
  35. package/dist/native.d.ts.map +0 -1
  36. package/dist/native.js +0 -302
  37. package/dist/native.js.map +0 -1
  38. package/dist/sessions/types.d.ts +0 -28
  39. package/dist/sessions/types.d.ts.map +0 -1
  40. package/dist/sessions/types.js +0 -27
  41. package/dist/sessions/types.js.map +0 -1
  42. package/dist/spinner.d.ts +0 -75
  43. package/dist/spinner.d.ts.map +0 -1
  44. package/dist/spinner.js +0 -203
  45. package/dist/spinner.js.map +0 -1
  46. package/dist/submit.d.ts +0 -23
  47. package/dist/submit.d.ts.map +0 -1
  48. package/dist/submit.js +0 -294
  49. package/dist/submit.js.map +0 -1
  50. package/dist/table.d.ts +0 -42
  51. package/dist/table.d.ts.map +0 -1
  52. package/dist/table.js +0 -181
  53. package/dist/table.js.map +0 -1
  54. package/dist/tui/App.d.ts +0 -4
  55. package/dist/tui/App.d.ts.map +0 -1
  56. package/dist/tui/App.js +0 -333
  57. package/dist/tui/App.js.map +0 -1
  58. package/dist/tui/components/BarChart.d.ts +0 -17
  59. package/dist/tui/components/BarChart.d.ts.map +0 -1
  60. package/dist/tui/components/BarChart.js +0 -146
  61. package/dist/tui/components/BarChart.js.map +0 -1
  62. package/dist/tui/components/DailyView.d.ts +0 -13
  63. package/dist/tui/components/DailyView.d.ts.map +0 -1
  64. package/dist/tui/components/DailyView.js +0 -86
  65. package/dist/tui/components/DailyView.js.map +0 -1
  66. package/dist/tui/components/DateBreakdownPanel.d.ts +0 -7
  67. package/dist/tui/components/DateBreakdownPanel.d.ts.map +0 -1
  68. package/dist/tui/components/DateBreakdownPanel.js +0 -36
  69. package/dist/tui/components/DateBreakdownPanel.js.map +0 -1
  70. package/dist/tui/components/Footer.d.ts +0 -28
  71. package/dist/tui/components/Footer.d.ts.map +0 -1
  72. package/dist/tui/components/Footer.js +0 -130
  73. package/dist/tui/components/Footer.js.map +0 -1
  74. package/dist/tui/components/Header.d.ts +0 -9
  75. package/dist/tui/components/Header.d.ts.map +0 -1
  76. package/dist/tui/components/Header.js +0 -20
  77. package/dist/tui/components/Header.js.map +0 -1
  78. package/dist/tui/components/Legend.d.ts +0 -7
  79. package/dist/tui/components/Legend.d.ts.map +0 -1
  80. package/dist/tui/components/Legend.js +0 -16
  81. package/dist/tui/components/Legend.js.map +0 -1
  82. package/dist/tui/components/LoadingSpinner.d.ts +0 -8
  83. package/dist/tui/components/LoadingSpinner.d.ts.map +0 -1
  84. package/dist/tui/components/LoadingSpinner.js +0 -55
  85. package/dist/tui/components/LoadingSpinner.js.map +0 -1
  86. package/dist/tui/components/ModelRow.d.ts +0 -13
  87. package/dist/tui/components/ModelRow.d.ts.map +0 -1
  88. package/dist/tui/components/ModelRow.js +0 -15
  89. package/dist/tui/components/ModelRow.js.map +0 -1
  90. package/dist/tui/components/ModelView.d.ts +0 -13
  91. package/dist/tui/components/ModelView.d.ts.map +0 -1
  92. package/dist/tui/components/ModelView.js +0 -96
  93. package/dist/tui/components/ModelView.js.map +0 -1
  94. package/dist/tui/components/OverviewView.d.ts +0 -14
  95. package/dist/tui/components/OverviewView.d.ts.map +0 -1
  96. package/dist/tui/components/OverviewView.js +0 -65
  97. package/dist/tui/components/OverviewView.js.map +0 -1
  98. package/dist/tui/components/StatsView.d.ts +0 -14
  99. package/dist/tui/components/StatsView.d.ts.map +0 -1
  100. package/dist/tui/components/StatsView.js +0 -102
  101. package/dist/tui/components/StatsView.js.map +0 -1
  102. package/dist/tui/components/TokenBreakdown.d.ts +0 -14
  103. package/dist/tui/components/TokenBreakdown.d.ts.map +0 -1
  104. package/dist/tui/components/TokenBreakdown.js +0 -10
  105. package/dist/tui/components/TokenBreakdown.js.map +0 -1
  106. package/dist/tui/components/index.d.ts +0 -16
  107. package/dist/tui/components/index.d.ts.map +0 -1
  108. package/dist/tui/components/index.js +0 -13
  109. package/dist/tui/components/index.js.map +0 -1
  110. package/dist/tui/config/settings.d.ts +0 -15
  111. package/dist/tui/config/settings.d.ts.map +0 -1
  112. package/dist/tui/config/settings.js +0 -147
  113. package/dist/tui/config/settings.js.map +0 -1
  114. package/dist/tui/config/themes.d.ts +0 -15
  115. package/dist/tui/config/themes.d.ts.map +0 -1
  116. package/dist/tui/config/themes.js +0 -82
  117. package/dist/tui/config/themes.js.map +0 -1
  118. package/dist/tui/hooks/useData.d.ts +0 -19
  119. package/dist/tui/hooks/useData.d.ts.map +0 -1
  120. package/dist/tui/hooks/useData.js +0 -468
  121. package/dist/tui/hooks/useData.js.map +0 -1
  122. package/dist/tui/index.d.ts +0 -4
  123. package/dist/tui/index.d.ts.map +0 -1
  124. package/dist/tui/index.js +0 -36
  125. package/dist/tui/index.js.map +0 -1
  126. package/dist/tui/types/index.d.ts +0 -137
  127. package/dist/tui/types/index.d.ts.map +0 -1
  128. package/dist/tui/types/index.js +0 -26
  129. package/dist/tui/types/index.js.map +0 -1
  130. package/dist/tui/utils/cleanup.d.ts +0 -22
  131. package/dist/tui/utils/cleanup.d.ts.map +0 -1
  132. package/dist/tui/utils/cleanup.js +0 -59
  133. package/dist/tui/utils/cleanup.js.map +0 -1
  134. package/dist/tui/utils/colors.d.ts +0 -19
  135. package/dist/tui/utils/colors.d.ts.map +0 -1
  136. package/dist/tui/utils/colors.js +0 -71
  137. package/dist/tui/utils/colors.js.map +0 -1
  138. package/dist/tui/utils/format.d.ts +0 -7
  139. package/dist/tui/utils/format.d.ts.map +0 -1
  140. package/dist/tui/utils/format.js +0 -45
  141. package/dist/tui/utils/format.js.map +0 -1
  142. package/dist/tui/utils/responsive.d.ts +0 -5
  143. package/dist/tui/utils/responsive.d.ts.map +0 -1
  144. package/dist/tui/utils/responsive.js +0 -5
  145. package/dist/tui/utils/responsive.js.map +0 -1
  146. package/dist/wrapped.d.ts +0 -43
  147. package/dist/wrapped.d.ts.map +0 -1
  148. package/dist/wrapped.js +0 -719
  149. package/dist/wrapped.js.map +0 -1
  150. package/src/auth.ts +0 -211
  151. package/src/cli.ts +0 -1892
  152. package/src/credentials.ts +0 -176
  153. package/src/cursor.ts +0 -1044
  154. package/src/date-utils.ts +0 -51
  155. package/src/graph-types.ts +0 -175
  156. package/src/native-runner.js +0 -4
  157. package/src/native-runner.ts +0 -91
  158. package/src/native.ts +0 -633
  159. package/src/sessions/types.ts +0 -59
  160. package/src/spinner.ts +0 -283
  161. package/src/submit.ts +0 -360
  162. package/src/table.ts +0 -233
  163. package/src/tui/App.tsx +0 -453
  164. package/src/tui/components/BarChart.tsx +0 -205
  165. package/src/tui/components/DailyView.tsx +0 -132
  166. package/src/tui/components/DateBreakdownPanel.tsx +0 -79
  167. package/src/tui/components/Footer.tsx +0 -380
  168. package/src/tui/components/Header.tsx +0 -68
  169. package/src/tui/components/Legend.tsx +0 -39
  170. package/src/tui/components/LoadingSpinner.tsx +0 -81
  171. package/src/tui/components/ModelRow.tsx +0 -47
  172. package/src/tui/components/ModelView.tsx +0 -147
  173. package/src/tui/components/OverviewView.tsx +0 -121
  174. package/src/tui/components/StatsView.tsx +0 -249
  175. package/src/tui/components/TokenBreakdown.tsx +0 -46
  176. package/src/tui/components/index.ts +0 -15
  177. package/src/tui/config/settings.ts +0 -183
  178. package/src/tui/config/themes.ts +0 -115
  179. package/src/tui/hooks/useData.ts +0 -558
  180. package/src/tui/index.tsx +0 -44
  181. package/src/tui/opentui.d.ts +0 -166
  182. package/src/tui/types/index.ts +0 -173
  183. package/src/tui/utils/cleanup.ts +0 -65
  184. package/src/tui/utils/colors.ts +0 -78
  185. package/src/tui/utils/format.ts +0 -36
  186. package/src/tui/utils/responsive.ts +0 -8
  187. package/src/types.d.ts +0 -28
  188. package/src/wrapped.ts +0 -848
@@ -1,558 +0,0 @@
1
- import { createSignal, createEffect, on, type Accessor } from "solid-js";
2
- import type {
3
- SourceType,
4
- SortType,
5
- ModelEntry,
6
- DailyEntry,
7
- ContributionDay,
8
- Stats,
9
- ModelWithPercentage,
10
- GridCell,
11
- TotalBreakdown,
12
- TUIData,
13
- ChartDataPoint,
14
- LoadingPhase,
15
- DailyModelBreakdown,
16
- } from "../types/index.js";
17
- import {
18
- parseLocalSourcesAsync,
19
- finalizeReportAndGraphAsync,
20
- type ParsedMessages,
21
- } from "../../native.js";
22
-
23
- import { syncCursorCache, isCursorLoggedIn, hasCursorUsageCache } from "../../cursor.js";
24
- import { getModelColor } from "../utils/colors.js";
25
- import { loadCachedData, saveCachedData, isCacheStale, loadSettings } from "../config/settings.js";
26
- import { formatDateLocal } from "../../date-utils.js";
27
-
28
- export type {
29
- SortType,
30
- ModelEntry,
31
- DailyEntry,
32
- ContributionDay,
33
- Stats,
34
- ModelWithPercentage,
35
- GridCell,
36
- TotalBreakdown,
37
- TUIData,
38
- LoadingPhase,
39
- };
40
-
41
- export interface DateFilters {
42
- since?: string;
43
- until?: string;
44
- year?: string;
45
- sinceTs?: number;
46
- untilTs?: number;
47
- }
48
-
49
- function buildContributionGrid(contributions: ContributionDay[]): GridCell[][] {
50
- const grid: GridCell[][] = Array.from({ length: 7 }, () => []);
51
-
52
- const today = new Date();
53
- const todayStr = formatDateLocal(today);
54
-
55
- const startDate = new Date(today);
56
- startDate.setDate(startDate.getDate() - 364);
57
- while (startDate.getDay() !== 0) {
58
- startDate.setDate(startDate.getDate() - 1);
59
- }
60
-
61
- const endDate = new Date(today);
62
- while (endDate.getDay() !== 6) {
63
- endDate.setDate(endDate.getDate() + 1);
64
- }
65
-
66
- const contributionMap = new Map(contributions.map(c => [c.date, c.level]));
67
-
68
- const currentDate = new Date(startDate);
69
- while (currentDate <= endDate) {
70
- const dateStr = formatDateLocal(currentDate);
71
- const dayOfWeek = currentDate.getDay();
72
-
73
- const isFuture = dateStr > todayStr;
74
- const level = isFuture ? 0 : (contributionMap.get(dateStr) || 0);
75
-
76
- grid[dayOfWeek].push({ date: isFuture ? null : dateStr, level });
77
- currentDate.setDate(currentDate.getDate() + 1);
78
- }
79
-
80
- return grid;
81
- }
82
-
83
- function calculatePeakHour(messages: Array<{ timestampMs: number }>): string {
84
- if (messages.length === 0) return "N/A";
85
-
86
- const hourCounts = new Array(24).fill(0);
87
- for (const msg of messages) {
88
- const hour = new Date(msg.timestampMs).getHours();
89
- hourCounts[hour]++;
90
- }
91
-
92
- let maxCount = 0;
93
- let peakHour = 0;
94
- for (let h = 0; h < 24; h++) {
95
- if (hourCounts[h] > maxCount) {
96
- maxCount = hourCounts[h];
97
- peakHour = h;
98
- }
99
- }
100
-
101
- if (maxCount === 0) return "N/A";
102
-
103
- const suffix = peakHour >= 12 ? "pm" : "am";
104
- const displayHour = peakHour === 0 ? 12 : peakHour > 12 ? peakHour - 12 : peakHour;
105
- return `${displayHour}${suffix}`;
106
- }
107
-
108
- function calculateLongestSession(messages: Array<{ sessionId: string; timestampMs: number }>): string {
109
- if (messages.length === 0) return "N/A";
110
-
111
- const sessions = new Map<string, number[]>();
112
- for (const msg of messages) {
113
- if (!msg.sessionId) continue;
114
- const timestamps = sessions.get(msg.sessionId) || [];
115
- timestamps.push(msg.timestampMs);
116
- sessions.set(msg.sessionId, timestamps);
117
- }
118
-
119
- if (sessions.size === 0) return "N/A";
120
-
121
- let maxDurationMs = 0;
122
- for (const [, timestamps] of sessions) {
123
- if (timestamps.length < 2) continue;
124
- const minTs = Math.min(...timestamps);
125
- const maxTs = Math.max(...timestamps);
126
- const duration = maxTs - minTs;
127
- if (duration > maxDurationMs) {
128
- maxDurationMs = duration;
129
- }
130
- }
131
-
132
- if (maxDurationMs === 0) return "N/A";
133
-
134
- const totalSeconds = Math.floor(maxDurationMs / 1000);
135
- const hours = Math.floor(totalSeconds / 3600);
136
- const minutes = Math.floor((totalSeconds % 3600) / 60);
137
-
138
- if (hours > 0) {
139
- return `${hours}h ${minutes}m`;
140
- } else if (minutes > 0) {
141
- return `${minutes}m`;
142
- }
143
- return `${totalSeconds}s`;
144
- }
145
-
146
- async function loadData(
147
- enabledSources: Set<SourceType>,
148
- dateFilters?: DateFilters,
149
- setPhase?: (phase: LoadingPhase) => void
150
- ): Promise<TUIData> {
151
- const sources = Array.from(enabledSources);
152
- const localSources = sources.filter(s => s !== "cursor");
153
- const includeCursor = sources.includes("cursor");
154
- const { since, until, year, sinceTs, untilTs } = dateFilters ?? {};
155
-
156
- setPhase?.("parsing-sources");
157
-
158
- const phase1Results = await Promise.allSettled([
159
- includeCursor && isCursorLoggedIn() ? syncCursorCache() : Promise.resolve({ synced: false, rows: 0, error: undefined }),
160
- localSources.length > 0
161
- ? parseLocalSourcesAsync({ sources: localSources as SourceType[], since, until, year, sinceTs, untilTs })
162
- : Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, openclawCount: 0, piCount: 0, kimiCount: 0, processingTimeMs: 0 } as ParsedMessages),
163
- ]);
164
-
165
- const cursorSync = phase1Results[0].status === "fulfilled"
166
- ? phase1Results[0].value
167
- : { synced: false, rows: 0, error: "Cursor sync failed" };
168
- const localMessages = phase1Results[1].status === "fulfilled"
169
- ? phase1Results[1].value
170
- : null;
171
-
172
- if (includeCursor && cursorSync.error && (cursorSync.synced || hasCursorUsageCache())) {
173
- // TUI should keep working; just emit a warning.
174
- const prefix = cursorSync.synced ? "Cursor sync warning" : "Cursor sync failed; using cached data";
175
- console.warn(`${prefix}: ${cursorSync.error}`);
176
- }
177
-
178
- const emptyMessages: ParsedMessages = {
179
- messages: [],
180
- opencodeCount: 0,
181
- claudeCount: 0,
182
- codexCount: 0,
183
- geminiCount: 0,
184
- ampCount: 0,
185
- droidCount: 0,
186
- openclawCount: 0,
187
- piCount: 0,
188
- kimiCount: 0,
189
- processingTimeMs: 0,
190
- };
191
-
192
- setPhase?.("finalizing-report");
193
- const { report, graph } = await finalizeReportAndGraphAsync({
194
- localMessages: localMessages || emptyMessages,
195
- includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
196
- since,
197
- until,
198
- year,
199
- sinceTs,
200
- untilTs,
201
- });
202
-
203
- const settings = loadSettings();
204
- const allModelEntries: ModelEntry[] = report.entries.map(e => ({
205
- source: e.source,
206
- model: e.model,
207
- input: e.input,
208
- output: e.output,
209
- cacheWrite: e.cacheWrite,
210
- cacheRead: e.cacheRead,
211
- reasoning: e.reasoning,
212
- total: e.input + e.output + e.cacheWrite + e.cacheRead + e.reasoning,
213
- cost: e.cost,
214
- }));
215
- const modelEntries = settings.includeUnusedModels
216
- ? allModelEntries
217
- : allModelEntries.filter(e => e.total > 0);
218
-
219
- const dailyMap = new Map<string, DailyEntry>();
220
- for (const contrib of graph.contributions) {
221
- const dateStr = contrib.date;
222
-
223
- if (!dailyMap.has(dateStr)) {
224
- dailyMap.set(dateStr, {
225
- date: dateStr,
226
- input: 0,
227
- output: 0,
228
- cacheRead: 0,
229
- cacheWrite: 0,
230
- total: 0,
231
- cost: 0,
232
- });
233
- }
234
- const entry = dailyMap.get(dateStr)!;
235
- entry.input += contrib.tokenBreakdown.input;
236
- entry.output += contrib.tokenBreakdown.output;
237
- entry.cacheRead += contrib.tokenBreakdown.cacheRead;
238
- entry.cacheWrite += contrib.tokenBreakdown.cacheWrite;
239
- entry.total += contrib.totals.tokens;
240
- entry.cost += contrib.totals.cost;
241
- }
242
- const dailyEntries = Array.from(dailyMap.values()).sort((a, b) => b.date.localeCompare(a.date));
243
-
244
- let maxCost = 1;
245
- for (const d of dailyEntries) {
246
- if (d.cost > maxCost) maxCost = d.cost;
247
- }
248
- let maxTokens = 1;
249
- for (const d of dailyEntries) {
250
- if (d.total > maxTokens) maxTokens = d.total;
251
- }
252
- const contributions: ContributionDay[] = dailyEntries.map(d => ({
253
- date: d.date,
254
- cost: d.cost,
255
- tokens: d.total,
256
- level: d.cost === 0 ? 0 : (Math.min(4, Math.ceil((d.cost / maxCost) * 4)) as 0 | 1 | 2 | 3 | 4),
257
- }));
258
-
259
- const contributionGrid = buildContributionGrid(contributions);
260
-
261
- const modelCosts = new Map<string, number>();
262
- for (const e of modelEntries) {
263
- const current = modelCosts.get(e.model) || 0;
264
- modelCosts.set(e.model, current + e.cost);
265
- }
266
- let favoriteModel = "N/A";
267
- let maxModelCost = 0;
268
- for (const [model, cost] of modelCosts) {
269
- if (cost > maxModelCost) {
270
- maxModelCost = cost;
271
- favoriteModel = model;
272
- }
273
- }
274
-
275
- let currentStreak = 0;
276
- let longestStreak = 0;
277
-
278
- const sortedDates = dailyEntries.map(d => d.date).sort();
279
- if (sortedDates.length > 0) {
280
- const todayParts = new Date().toISOString().split("T")[0].split("-").map(Number);
281
- const todayUTC = Date.UTC(todayParts[0], todayParts[1] - 1, todayParts[2]);
282
-
283
- let streak = 0;
284
- for (let i = sortedDates.length - 1; i >= 0; i--) {
285
- const dateParts = sortedDates[i].split("-").map(Number);
286
- const dateUTC = Date.UTC(dateParts[0], dateParts[1] - 1, dateParts[2]);
287
- const daysFromToday = Math.round((todayUTC - dateUTC) / (1000 * 60 * 60 * 24));
288
-
289
- if (i === sortedDates.length - 1) {
290
- if (daysFromToday <= 1) {
291
- streak = 1;
292
- } else {
293
- break;
294
- }
295
- } else {
296
- const prevParts = sortedDates[i + 1].split("-").map(Number);
297
- const prevUTC = Date.UTC(prevParts[0], prevParts[1] - 1, prevParts[2]);
298
- const diffDays = Math.round((prevUTC - dateUTC) / (1000 * 60 * 60 * 24));
299
- if (diffDays === 1) {
300
- streak++;
301
- } else {
302
- break;
303
- }
304
- }
305
- }
306
- currentStreak = streak;
307
-
308
- streak = 1;
309
- for (let i = 1; i < sortedDates.length; i++) {
310
- const prevParts = sortedDates[i - 1].split("-").map(Number);
311
- const currParts = sortedDates[i].split("-").map(Number);
312
- const prevDate = Date.UTC(prevParts[0], prevParts[1] - 1, prevParts[2]);
313
- const currDate = Date.UTC(currParts[0], currParts[1] - 1, currParts[2]);
314
- const diffDays = Math.round((currDate - prevDate) / (1000 * 60 * 60 * 24));
315
- if (diffDays === 1) {
316
- streak++;
317
- } else {
318
- longestStreak = Math.max(longestStreak, streak);
319
- streak = 1;
320
- }
321
- }
322
- longestStreak = Math.max(longestStreak, streak);
323
- }
324
-
325
- const stats: Stats = {
326
- favoriteModel,
327
- totalTokens: report.totalInput + report.totalOutput + report.totalCacheRead + report.totalCacheWrite,
328
- sessions: report.totalMessages,
329
- longestSession: calculateLongestSession((localMessages?.messages || []).map((m) => ({
330
- sessionId: m.sessionId,
331
- timestampMs: m.timestamp,
332
- }))),
333
- currentStreak,
334
- longestStreak,
335
- activeDays: dailyEntries.length,
336
- totalDays: graph.summary.totalDays,
337
- peakHour: calculatePeakHour((localMessages?.messages || []).map((m) => ({
338
- timestampMs: m.timestamp,
339
- }))),
340
- };
341
-
342
- const dailyModelMap = new Map<string, Map<string, number>>();
343
- for (const contrib of graph.contributions) {
344
- const dateStr = contrib.date;
345
- if (!dailyModelMap.has(dateStr)) {
346
- dailyModelMap.set(dateStr, new Map());
347
- }
348
- const modelMap = dailyModelMap.get(dateStr)!;
349
- for (const source of contrib.sources) {
350
- const modelId = source.modelId;
351
- const tokens = source.tokens.input + source.tokens.output + source.tokens.cacheRead;
352
- modelMap.set(modelId, (modelMap.get(modelId) || 0) + tokens);
353
- }
354
- }
355
-
356
- const chartData: ChartDataPoint[] = Array.from(dailyModelMap.entries())
357
- .map(([date, modelMap]) => {
358
- const models = Array.from(modelMap.entries()).map(([modelId, tokens]) => ({
359
- modelId,
360
- tokens,
361
- color: getModelColor(modelId),
362
- }));
363
- return {
364
- date,
365
- models,
366
- total: models.reduce((sum, m) => sum + m.tokens, 0),
367
- };
368
- })
369
- .sort((a, b) => a.date.localeCompare(b.date));
370
-
371
- const modelTokensMap = new Map<string, { input: number; output: number; cacheRead: number; cacheWrite: number; cost: number }>();
372
- for (const e of modelEntries) {
373
- const existing = modelTokensMap.get(e.model) || { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
374
- modelTokensMap.set(e.model, {
375
- input: existing.input + e.input,
376
- output: existing.output + e.output,
377
- cacheRead: existing.cacheRead + e.cacheRead,
378
- cacheWrite: existing.cacheWrite + e.cacheWrite,
379
- cost: existing.cost + e.cost,
380
- });
381
- }
382
-
383
- const totalCostSum = report.totalCost || 1;
384
- const topModels: ModelWithPercentage[] = Array.from(modelTokensMap.entries())
385
- .map(([modelId, data]) => {
386
- const totalTokens = data.input + data.output + data.cacheRead + data.cacheWrite;
387
- return {
388
- modelId,
389
- percentage: (data.cost / totalCostSum) * 100,
390
- inputTokens: data.input,
391
- outputTokens: data.output,
392
- cacheReadTokens: data.cacheRead,
393
- cacheWriteTokens: data.cacheWrite,
394
- totalTokens,
395
- cost: data.cost,
396
- };
397
- })
398
- .sort((a, b) => b.cost - a.cost);
399
-
400
- const totalReasoning = modelEntries.reduce((sum, e) => sum + e.reasoning, 0);
401
- const totals: TotalBreakdown = {
402
- input: report.totalInput,
403
- output: report.totalOutput,
404
- cacheWrite: report.totalCacheWrite,
405
- cacheRead: report.totalCacheRead,
406
- reasoning: totalReasoning,
407
- total: report.totalInput + report.totalOutput + report.totalCacheWrite + report.totalCacheRead + totalReasoning,
408
- cost: report.totalCost,
409
- };
410
-
411
- const dailyBreakdowns = new Map<string, DailyModelBreakdown>();
412
- for (const contrib of graph.contributions) {
413
- const dateStr = contrib.date;
414
-
415
- const models = contrib.sources.map((source: { modelId: string; source: string; tokens: { input: number; output: number; cacheRead: number; cacheWrite: number; reasoning?: number }; cost: number; messages: number }) => ({
416
- modelId: source.modelId,
417
- source: source.source,
418
- tokens: {
419
- input: source.tokens.input,
420
- output: source.tokens.output,
421
- cacheRead: source.tokens.cacheRead,
422
- cacheWrite: source.tokens.cacheWrite,
423
- reasoning: source.tokens.reasoning || 0,
424
- },
425
- cost: source.cost,
426
- messages: source.messages,
427
- }));
428
-
429
- const existing = dailyBreakdowns.get(dateStr);
430
- if (existing) {
431
- existing.cost += contrib.totals.cost;
432
- existing.totalTokens += contrib.totals.tokens;
433
- existing.models.push(...models);
434
- existing.models.sort((a, b) => b.cost - a.cost);
435
- } else {
436
- dailyBreakdowns.set(dateStr, {
437
- date: dateStr,
438
- cost: contrib.totals.cost,
439
- totalTokens: contrib.totals.tokens,
440
- models: models.sort((a, b) => b.cost - a.cost),
441
- });
442
- }
443
- }
444
-
445
- return {
446
- modelEntries,
447
- dailyEntries,
448
- contributions,
449
- contributionGrid,
450
- stats,
451
- totalCost: report.totalCost,
452
- totals,
453
- modelCount: modelEntries.length,
454
- chartData,
455
- topModels,
456
- dailyBreakdowns,
457
- };
458
- }
459
-
460
- export function useData(enabledSources: Accessor<Set<SourceType>>, dateFilters?: DateFilters) {
461
- const initialSources = enabledSources();
462
- const initialCachedData = loadCachedData(initialSources);
463
- const initialCacheIsStale = initialCachedData ? isCacheStale(initialSources) : true;
464
-
465
- const [data, setData] = createSignal<TUIData | null>(initialCachedData);
466
- const [loading, setLoading] = createSignal(!initialCachedData);
467
- const [error, setError] = createSignal<string | null>(null);
468
- const [refreshTrigger, setRefreshTrigger] = createSignal(0);
469
- const [loadingPhase, setLoadingPhase] = createSignal<LoadingPhase>(
470
- initialCachedData ? (initialCacheIsStale ? "loading-pricing" : "complete") : "idle"
471
- );
472
- const [isRefreshing, setIsRefreshing] = createSignal(initialCachedData ? initialCacheIsStale : false);
473
-
474
- const [forceRefresh, setForceRefresh] = createSignal(false);
475
- let pendingRefresh = false;
476
- let currentRequestId = 0;
477
-
478
- const refresh = () => {
479
- if (isRefreshing() || loading()) {
480
- pendingRefresh = true;
481
- return;
482
- }
483
- setIsRefreshing(true);
484
- setForceRefresh(true);
485
- setRefreshTrigger(prev => prev + 1);
486
- };
487
-
488
- const doLoad = (sources: Set<SourceType>, skipCacheCheck = false) => {
489
- ++currentRequestId; // Invalidate any in-flight requests immediately
490
- const shouldSkipCache = skipCacheCheck || forceRefresh();
491
-
492
- if (!shouldSkipCache) {
493
- const cachedData = loadCachedData(sources);
494
- const cacheIsStale = isCacheStale(sources);
495
-
496
- if (cachedData && !cacheIsStale) {
497
- setData(cachedData);
498
- setLoading(false);
499
- setLoadingPhase("complete");
500
- setIsRefreshing(false);
501
- return;
502
- }
503
-
504
- if (cachedData) {
505
- setData(cachedData);
506
- setLoading(false);
507
- setIsRefreshing(true);
508
- setLoadingPhase("idle");
509
- } else {
510
- setLoading(true);
511
- setLoadingPhase("idle");
512
- }
513
- } else {
514
- setIsRefreshing(true);
515
- setLoadingPhase("idle");
516
- setForceRefresh(false);
517
- }
518
-
519
- const requestId = currentRequestId;
520
- setError(null);
521
- loadData(sources, dateFilters, setLoadingPhase)
522
- .then((freshData) => {
523
- if (requestId !== currentRequestId) return;
524
- setData(freshData);
525
- saveCachedData(freshData, sources);
526
- })
527
- .catch((e: unknown) => {
528
- if (requestId !== currentRequestId) return;
529
- setError(e instanceof Error ? e.message : String(e));
530
- })
531
- .finally(() => {
532
- if (requestId !== currentRequestId) return;
533
- setLoading(false);
534
- setIsRefreshing(false);
535
- setLoadingPhase("complete");
536
- if (pendingRefresh) {
537
- pendingRefresh = false;
538
- refresh();
539
- }
540
- });
541
- };
542
-
543
- if (initialCachedData && initialCacheIsStale) {
544
- doLoad(initialSources, true);
545
- } else if (!initialCachedData) {
546
- doLoad(initialSources, false);
547
- }
548
-
549
- createEffect(on(
550
- () => [enabledSources(), refreshTrigger()] as const,
551
- ([sources]) => {
552
- doLoad(sources);
553
- },
554
- { defer: true }
555
- ));
556
-
557
- return { data, loading, error, refresh, loadingPhase, isRefreshing };
558
- }
package/src/tui/index.tsx DELETED
@@ -1,44 +0,0 @@
1
- import { render } from "@opentui/solid";
2
- import { App } from "./App.js";
3
- import type { TUIOptions } from "./types/index.js";
4
- import { restoreTerminalState } from "./utils/cleanup.js";
5
-
6
- export type { TUIOptions };
7
-
8
- export async function launchTUI(options?: TUIOptions) {
9
- const cleanup = () => {
10
- restoreTerminalState();
11
- };
12
-
13
- process.on('uncaughtException', (error) => {
14
- cleanup();
15
- console.error('Uncaught exception:', error);
16
- process.exit(1);
17
- });
18
-
19
- process.on('unhandledRejection', (reason) => {
20
- cleanup();
21
- console.error('Unhandled rejection:', reason);
22
- process.exit(1);
23
- });
24
-
25
- process.on('SIGINT', () => {
26
- cleanup();
27
- process.exit(0);
28
- });
29
-
30
- process.on('SIGTERM', () => {
31
- cleanup();
32
- process.exit(0);
33
- });
34
-
35
- process.on('beforeExit', cleanup);
36
-
37
- await render(() => <App {...(options ?? {})} />, {
38
- exitOnCtrlC: false,
39
- useAlternateScreen: true,
40
- useMouse: true,
41
- targetFps: 60,
42
- useKittyKeyboard: {},
43
- } as any);
44
- }