@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,380 +0,0 @@
1
- import {
2
- Show,
3
- createSignal,
4
- createEffect,
5
- onMount,
6
- onCleanup,
7
- } from "solid-js";
8
- import type {
9
- SourceType,
10
- SortType,
11
- TabType,
12
- LoadingPhase,
13
- } from "../types/index.js";
14
- import type { ColorPaletteName } from "../config/themes.js";
15
- import type { TotalBreakdown } from "../hooks/useData.js";
16
- import { getPalette } from "../config/themes.js";
17
- import { formatTokens } from "../utils/format.js";
18
- import { isVeryNarrow } from "../utils/responsive.js";
19
-
20
- interface FooterProps {
21
- enabledSources: Set<SourceType>;
22
- sortBy: SortType;
23
- totals?: TotalBreakdown;
24
- modelCount: number;
25
- activeTab: TabType;
26
- scrollStart?: number;
27
- scrollEnd?: number;
28
- totalItems?: number;
29
- colorPalette: ColorPaletteName;
30
- statusMessage?: string | null;
31
- isRefreshing?: boolean;
32
- loadingPhase?: LoadingPhase;
33
- cacheTimestamp?: number | null;
34
- autoRefreshEnabled?: boolean;
35
- autoRefreshMs?: number;
36
- width?: number;
37
- onSourceToggle?: (source: SourceType) => void;
38
- onSortChange?: (sort: SortType) => void;
39
- onPaletteChange?: () => void;
40
- onRefresh?: () => void;
41
- }
42
-
43
- function formatTimeAgo(timestamp: number, now: number): string {
44
- const seconds = Math.max(Math.floor((now - timestamp) / 1000), 0);
45
- if (seconds < 60) return `${seconds}s ago`;
46
- const minutes = Math.floor(seconds / 60);
47
- if (minutes < 60) return `${minutes}m ago`;
48
- const hours = Math.floor(minutes / 60);
49
- if (hours < 24) return `${hours}h ago`;
50
- const days = Math.floor(hours / 24);
51
- return `${days}d ago`;
52
- }
53
-
54
- function formatIntervalSeconds(ms: number | undefined): string {
55
- if (!ms || ms <= 0) return "0s";
56
- const seconds = Math.round(ms / 1000);
57
- if (seconds < 60) return `${seconds}s`;
58
- const minutes = Math.round(seconds / 60);
59
- return `${minutes}m`;
60
- }
61
-
62
- export function Footer(props: FooterProps) {
63
- const palette = () => getPalette(props.colorPalette);
64
- const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
65
- const [now, setNow] = createSignal(Date.now());
66
- let nowInterval: ReturnType<typeof setInterval> | null = null;
67
-
68
- createEffect(() => {
69
- if (props.cacheTimestamp) {
70
- if (!nowInterval) {
71
- nowInterval = setInterval(() => setNow(Date.now()), 1000);
72
- }
73
- } else if (nowInterval) {
74
- clearInterval(nowInterval);
75
- nowInterval = null;
76
- }
77
- });
78
-
79
- onCleanup(() => {
80
- if (nowInterval) clearInterval(nowInterval);
81
- });
82
-
83
- const showScrollInfo = () =>
84
- props.activeTab === "overview" &&
85
- props.totalItems &&
86
- props.scrollStart !== undefined &&
87
- props.scrollEnd !== undefined;
88
-
89
- const totals = () =>
90
- props.totals || {
91
- input: 0,
92
- output: 0,
93
- cacheRead: 0,
94
- cacheWrite: 0,
95
- reasoning: 0,
96
- total: 0,
97
- cost: 0,
98
- };
99
-
100
- return (
101
- <box flexDirection="column" paddingX={1}>
102
- <box flexDirection="row" justifyContent="space-between">
103
- <box flexDirection="row" gap={1}>
104
- <SourceBadge
105
- name={isVeryNarrowTerminal() ? "1" : "1:OC"}
106
- source="opencode"
107
- enabled={props.enabledSources.has("opencode")}
108
- onToggle={props.onSourceToggle}
109
- />
110
- <SourceBadge
111
- name={isVeryNarrowTerminal() ? "2" : "2:CC"}
112
- source="claude"
113
- enabled={props.enabledSources.has("claude")}
114
- onToggle={props.onSourceToggle}
115
- />
116
- <SourceBadge
117
- name={isVeryNarrowTerminal() ? "3" : "3:CX"}
118
- source="codex"
119
- enabled={props.enabledSources.has("codex")}
120
- onToggle={props.onSourceToggle}
121
- />
122
- <SourceBadge
123
- name={isVeryNarrowTerminal() ? "4" : "4:CR"}
124
- source="cursor"
125
- enabled={props.enabledSources.has("cursor")}
126
- onToggle={props.onSourceToggle}
127
- />
128
- <SourceBadge
129
- name={isVeryNarrowTerminal() ? "5" : "5:GM"}
130
- source="gemini"
131
- enabled={props.enabledSources.has("gemini")}
132
- onToggle={props.onSourceToggle}
133
- />
134
- <SourceBadge
135
- name={isVeryNarrowTerminal() ? "6" : "6:AM"}
136
- source="amp"
137
- enabled={props.enabledSources.has("amp")}
138
- onToggle={props.onSourceToggle}
139
- />
140
- <SourceBadge
141
- name={isVeryNarrowTerminal() ? "7" : "7:DR"}
142
- source="droid"
143
- enabled={props.enabledSources.has("droid")}
144
- onToggle={props.onSourceToggle}
145
- />
146
- <SourceBadge
147
- name={isVeryNarrowTerminal() ? "8" : "8:CL"}
148
- source="openclaw"
149
- enabled={props.enabledSources.has("openclaw")}
150
- onToggle={props.onSourceToggle}
151
- />
152
- <SourceBadge
153
- name={isVeryNarrowTerminal() ? "9" : "9:PI"}
154
- source="pi"
155
- enabled={props.enabledSources.has("pi")}
156
- onToggle={props.onSourceToggle}
157
- />
158
- <SourceBadge
159
- name={isVeryNarrowTerminal() ? "0" : "0:KM"}
160
- source="kimi"
161
- enabled={props.enabledSources.has("kimi")}
162
- onToggle={props.onSourceToggle}
163
- />
164
- <Show when={!isVeryNarrowTerminal()}>
165
- <text dim>|</text>
166
- <SortButton
167
- label="Date"
168
- sortType="date"
169
- active={props.sortBy === "date"}
170
- onClick={props.onSortChange}
171
- />
172
- <SortButton
173
- label="Cost"
174
- sortType="cost"
175
- active={props.sortBy === "cost"}
176
- onClick={props.onSortChange}
177
- />
178
- <SortButton
179
- label="Tokens"
180
- sortType="tokens"
181
- active={props.sortBy === "tokens"}
182
- onClick={props.onSortChange}
183
- />
184
- </Show>
185
- <Show when={showScrollInfo() && !isVeryNarrowTerminal()}>
186
- <text dim>|</text>
187
- <text
188
- dim
189
- >{`↓ ${props.scrollStart! + 1}-${props.scrollEnd} of ${props.totalItems}`}</text>
190
- </Show>
191
- </box>
192
- <box flexDirection="row" gap={1}>
193
- <text fg="cyan">{formatTokens(totals().total)}</text>
194
- <text dim>tokens</text>
195
- <text dim>|</text>
196
- <text fg="green" bold>{`$${totals().cost.toFixed(2)}`}</text>
197
- <Show when={!isVeryNarrowTerminal()}>
198
- <text dim>({props.modelCount} models)</text>
199
- </Show>
200
- </box>
201
- </box>
202
- <box flexDirection="row" gap={1}>
203
- <Show
204
- when={props.statusMessage}
205
- fallback={
206
- <Show
207
- when={isVeryNarrowTerminal()}
208
- fallback={
209
- <>
210
- <text dim>↑↓ scroll • ←→/tab view • y copy •</text>
211
- <box onMouseDown={props.onPaletteChange}>
212
- <text fg="magenta">{`[p:${palette().name}]`}</text>
213
- </box>
214
- <text fg={props.autoRefreshEnabled ? "green" : "gray"}>
215
- {`[Shift+R:auto update ${formatIntervalSeconds(props.autoRefreshMs)}]`}
216
- </text>
217
- <text dim>[-/+ interval]•</text>
218
- <box onMouseDown={props.onRefresh}>
219
- <text fg="yellow">[r:refresh]</text>
220
- </box>
221
- <text dim>• e export • q quit</text>
222
- </>
223
- }
224
- >
225
- <text dim>↑↓•←→•y•</text>
226
- <box onMouseDown={props.onPaletteChange}>
227
- <text fg="magenta">[p]</text>
228
- </box>
229
- <text fg={props.autoRefreshEnabled ? "green" : "gray"}>
230
- {`[Shift+R:auto update ${formatIntervalSeconds(props.autoRefreshMs)}]`}
231
- </text>
232
- <text dim>-+•</text>
233
- <box onMouseDown={props.onRefresh}>
234
- <text fg="yellow">[r]</text>
235
- </box>
236
- <text dim>•e•q</text>
237
- </Show>
238
- }
239
- >
240
- <text fg="green" bold>
241
- {props.statusMessage}
242
- </text>
243
- </Show>
244
- </box>
245
- <Show when={props.isRefreshing}>
246
- <LoadingStatusLine phase={props.loadingPhase} />
247
- </Show>
248
- <Show when={!props.isRefreshing && props.cacheTimestamp}>
249
- <box flexDirection="row" gap={1}>
250
- <text
251
- dim
252
- >{`Last updated: ${formatTimeAgo(props.cacheTimestamp!, now())}`}</text>
253
- <Show when={props.autoRefreshEnabled}>
254
- <text
255
- dim
256
- >{`• Auto: ${formatIntervalSeconds(props.autoRefreshMs)}`}</text>
257
- </Show>
258
- </box>
259
- </Show>
260
- </box>
261
- );
262
- }
263
-
264
- interface SourceBadgeProps {
265
- name: string;
266
- source: SourceType;
267
- enabled: boolean;
268
- onToggle?: (source: SourceType) => void;
269
- }
270
-
271
- function SourceBadge(props: SourceBadgeProps) {
272
- const handleClick = () => props.onToggle?.(props.source);
273
-
274
- return (
275
- <box onMouseDown={handleClick}>
276
- <text fg={props.enabled ? "green" : "gray"}>
277
- {`[${props.enabled ? "●" : "○"}${props.name}]`}
278
- </text>
279
- </box>
280
- );
281
- }
282
-
283
- interface SortButtonProps {
284
- label: string;
285
- sortType: SortType;
286
- active: boolean;
287
- onClick?: (sort: SortType) => void;
288
- }
289
-
290
- function SortButton(props: SortButtonProps) {
291
- const handleClick = () => props.onClick?.(props.sortType);
292
-
293
- return (
294
- <box onMouseDown={handleClick}>
295
- <text fg={props.active ? "white" : "gray"} bold={props.active}>
296
- {props.label}
297
- </text>
298
- </box>
299
- );
300
- }
301
-
302
- const SPINNER_COLORS = [
303
- "#00FFFF",
304
- "#00D7D7",
305
- "#00AFAF",
306
- "#008787",
307
- "#666666",
308
- "#666666",
309
- ];
310
- const SPINNER_WIDTH = 6;
311
- const SPINNER_HOLD_START = 20;
312
- const SPINNER_HOLD_END = 6;
313
- const SPINNER_TRAIL = 3;
314
- const SPINNER_INTERVAL = 40;
315
-
316
- const PHASE_MESSAGES: Record<LoadingPhase, string> = {
317
- idle: "Initializing...",
318
- "parsing-sources": "Scanning session data...",
319
- "loading-pricing": "Loading pricing data...",
320
- "finalizing-report": "Finalizing report...",
321
- complete: "Complete",
322
- };
323
-
324
- interface LoadingStatusLineProps {
325
- phase?: LoadingPhase;
326
- }
327
-
328
- function LoadingStatusLine(props: LoadingStatusLineProps) {
329
- const [frame, setFrame] = createSignal(0);
330
-
331
- onMount(() => {
332
- const id = setInterval(() => setFrame((f) => f + 1), SPINNER_INTERVAL);
333
- onCleanup(() => clearInterval(id));
334
- });
335
-
336
- const getSpinnerState = () => {
337
- const forwardFrames = SPINNER_WIDTH;
338
- const backwardFrames = SPINNER_WIDTH - 1;
339
- const totalCycle =
340
- forwardFrames + SPINNER_HOLD_END + backwardFrames + SPINNER_HOLD_START;
341
- const normalized = frame() % totalCycle;
342
-
343
- if (normalized < forwardFrames) {
344
- return { position: normalized, forward: true };
345
- } else if (normalized < forwardFrames + SPINNER_HOLD_END) {
346
- return { position: SPINNER_WIDTH - 1, forward: true };
347
- } else if (normalized < forwardFrames + SPINNER_HOLD_END + backwardFrames) {
348
- return {
349
- position:
350
- SPINNER_WIDTH - 2 - (normalized - forwardFrames - SPINNER_HOLD_END),
351
- forward: false,
352
- };
353
- }
354
- return { position: 0, forward: false };
355
- };
356
-
357
- const getCharProps = (index: number) => {
358
- const { position, forward } = getSpinnerState();
359
- const distance = forward ? position - index : index - position;
360
- if (distance >= 0 && distance < SPINNER_TRAIL) {
361
- return { char: "■", color: SPINNER_COLORS[distance] };
362
- }
363
- return { char: "⬝", color: "#444444" };
364
- };
365
-
366
- const message = () =>
367
- props.phase ? PHASE_MESSAGES[props.phase] : "Refreshing...";
368
-
369
- return (
370
- <box flexDirection="row" gap={1}>
371
- <box flexDirection="row" gap={0}>
372
- {Array.from({ length: SPINNER_WIDTH }, (_, i) => {
373
- const { char, color } = getCharProps(i);
374
- return <text fg={color}>{char}</text>;
375
- })}
376
- </box>
377
- <text dim>{message()}</text>
378
- </box>
379
- );
380
- }
@@ -1,68 +0,0 @@
1
- import { Show } from "solid-js";
2
- import { exec } from "node:child_process";
3
- import type { TabType } from "../types/index.js";
4
- import { isNarrow, isVeryNarrow } from "../utils/responsive.js";
5
-
6
- const REPO_URL = "https://github.com/junhoyeo/tokscale";
7
-
8
- function openUrl(url: string) {
9
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
10
- exec(`${cmd} ${url}`);
11
- }
12
-
13
- interface HeaderProps {
14
- activeTab: TabType;
15
- onTabClick?: (tab: TabType) => void;
16
- width?: number;
17
- }
18
-
19
- export function Header(props: HeaderProps) {
20
- const isNarrowTerminal = () => isNarrow(props.width);
21
- const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
22
-
23
- const getTabName = (fullName: string, shortName: string) =>
24
- isVeryNarrowTerminal() ? shortName : fullName;
25
-
26
- return (
27
- <box flexDirection="row" paddingX={1} paddingY={0} justifyContent="space-between">
28
- <box flexDirection="row" gap={isVeryNarrowTerminal() ? 1 : 2}>
29
- <Tab name={getTabName("Overview", "Ovw")} tabId="overview" active={props.activeTab === "overview"} onClick={props.onTabClick} />
30
- <Tab name={getTabName("Models", "Mod")} tabId="model" active={props.activeTab === "model"} onClick={props.onTabClick} />
31
- <Tab name={getTabName("Daily", "Day")} tabId="daily" active={props.activeTab === "daily"} onClick={props.onTabClick} />
32
- <Tab name={getTabName("Stats", "Sta")} tabId="stats" active={props.activeTab === "stats"} onClick={props.onTabClick} />
33
- </box>
34
- <Show when={!isNarrowTerminal()}>
35
- <box flexDirection="row" onMouseDown={() => openUrl(REPO_URL)}>
36
- <text fg="cyan" bold>tokscale</text>
37
- <text fg="#666666">{" | GitHub"}</text>
38
- </box>
39
- </Show>
40
- </box>
41
- );
42
- }
43
-
44
- interface TabProps {
45
- name: string;
46
- tabId: TabType;
47
- active: boolean;
48
- onClick?: (tab: TabType) => void;
49
- }
50
-
51
- function Tab(props: TabProps) {
52
- const handleClick = () => props.onClick?.(props.tabId);
53
-
54
- return (
55
- <Show
56
- when={props.active}
57
- fallback={
58
- <box onMouseDown={handleClick}>
59
- <text dim>{props.name}</text>
60
- </box>
61
- }
62
- >
63
- <box onMouseDown={handleClick}>
64
- <text bg="cyan" fg="black" bold>{` ${props.name} `}</text>
65
- </box>
66
- </Show>
67
- );
68
- }
@@ -1,39 +0,0 @@
1
- import { For, Show } from "solid-js";
2
- import { getModelColor } from "../utils/colors.js";
3
- import { isNarrow, isVeryNarrow } from "../utils/responsive.js";
4
-
5
- interface LegendProps {
6
- models: string[];
7
- width?: number;
8
- }
9
-
10
- export function Legend(props: LegendProps) {
11
- const isNarrowTerminal = () => isNarrow(props.width);
12
- const isVeryNarrowTerminal = () => isVeryNarrow(props.width);
13
-
14
- const maxModelNameWidth = () => isVeryNarrowTerminal() ? 12 : isNarrowTerminal() ? 18 : 30;
15
- const truncateModelName = (name: string) => {
16
- const max = maxModelNameWidth();
17
- return name.length > max ? name.slice(0, max - 1) + "…" : name;
18
- };
19
-
20
- const models = () => props.models;
21
-
22
- return (
23
- <Show when={models().length > 0}>
24
- <box flexDirection="row" gap={1} flexWrap="wrap">
25
- <For each={models()}>
26
- {(modelId, i) => (
27
- <box flexDirection="row" gap={0}>
28
- <text fg={getModelColor(modelId)}>●</text>
29
- <text>{` ${truncateModelName(modelId)}`}</text>
30
- <Show when={i() < models().length - 1}>
31
- <text dim>{isVeryNarrowTerminal() ? " " : " ·"}</text>
32
- </Show>
33
- </box>
34
- )}
35
- </For>
36
- </box>
37
- </Show>
38
- );
39
- }
@@ -1,81 +0,0 @@
1
- import { createSignal, onMount, onCleanup } from "solid-js";
2
- import type { LoadingPhase } from "../types/index.js";
3
-
4
- const COLORS = ["#00FFFF", "#00D7D7", "#00AFAF", "#008787", "#666666", "#666666", "#666666", "#666666"];
5
- const WIDTH = 8;
6
- const HOLD_START = 30;
7
- const HOLD_END = 9;
8
- const TRAIL_LENGTH = 4;
9
- const INTERVAL = 40;
10
-
11
- interface SpinnerState {
12
- position: number;
13
- forward: boolean;
14
- }
15
-
16
- function getScannerState(frame: number): SpinnerState {
17
- const forwardFrames = WIDTH;
18
- const backwardFrames = WIDTH - 1;
19
- const totalCycle = forwardFrames + HOLD_END + backwardFrames + HOLD_START;
20
- const normalized = frame % totalCycle;
21
-
22
- if (normalized < forwardFrames) {
23
- return { position: normalized, forward: true };
24
- } else if (normalized < forwardFrames + HOLD_END) {
25
- return { position: WIDTH - 1, forward: true };
26
- } else if (normalized < forwardFrames + HOLD_END + backwardFrames) {
27
- return { position: WIDTH - 2 - (normalized - forwardFrames - HOLD_END), forward: false };
28
- }
29
- return { position: 0, forward: false };
30
- }
31
-
32
- const PHASE_MESSAGES: Record<LoadingPhase, string> = {
33
- "idle": "Initializing...",
34
- "parsing-sources": "Scanning session data...",
35
- "loading-pricing": "Loading pricing data...",
36
- "finalizing-report": "Finalizing report...",
37
- "complete": "Complete",
38
- };
39
-
40
- interface LoadingSpinnerProps {
41
- message?: string;
42
- phase?: LoadingPhase;
43
- }
44
-
45
- export function LoadingSpinner(props: LoadingSpinnerProps) {
46
- const [frame, setFrame] = createSignal(0);
47
-
48
- onMount(() => {
49
- const id = setInterval(() => {
50
- setFrame((f) => f + 1);
51
- }, INTERVAL);
52
- onCleanup(() => clearInterval(id));
53
- });
54
-
55
- const state = () => getScannerState(frame());
56
- const displayMessage = () => props.message || (props.phase ? PHASE_MESSAGES[props.phase] : "Loading data...");
57
-
58
- const getCharProps = (index: number) => {
59
- const { position, forward } = state();
60
- const distance = forward ? position - index : index - position;
61
-
62
- if (distance >= 0 && distance < TRAIL_LENGTH) {
63
- return { char: "■", color: COLORS[distance] };
64
- }
65
- return { char: "⬝", color: "#444444" };
66
- };
67
-
68
- return (
69
- <box flexDirection="column" justifyContent="center" alignItems="center" flexGrow={1}>
70
- <box flexDirection="row" gap={0}>
71
- {Array.from({ length: WIDTH }, (_, i) => {
72
- const { char, color } = getCharProps(i);
73
- return <text fg={color}>{char}</text>;
74
- })}
75
- </box>
76
- <box marginTop={1}>
77
- <text dim>{displayMessage()}</text>
78
- </box>
79
- </box>
80
- );
81
- }
@@ -1,47 +0,0 @@
1
- import { Show, createMemo } from "solid-js";
2
- import { TokenBreakdown, type TokenBreakdownData } from "./TokenBreakdown.js";
3
- import { getModelColor } from "../utils/colors.js";
4
-
5
- interface ModelRowProps {
6
- modelId: string;
7
- tokens: TokenBreakdownData;
8
- percentage?: number;
9
- isActive?: boolean;
10
- compact?: boolean;
11
- indent?: number;
12
- maxNameWidth?: number;
13
- }
14
-
15
- export function ModelRow(props: ModelRowProps) {
16
- const color = () => getModelColor(props.modelId);
17
- const bgColor = createMemo(() => props.isActive ? "blue" : undefined);
18
-
19
- const truncateName = (name: string) => {
20
- const max = props.maxNameWidth ?? 50;
21
- return name.length > max ? name.slice(0, max - 1) + "…" : name;
22
- };
23
-
24
- const indentStr = () => " ".repeat(props.indent ?? 0);
25
-
26
- return (
27
- <box flexDirection="column">
28
- <box flexDirection="row" backgroundColor={bgColor()}>
29
- <Show when={props.indent}>
30
- <text>{indentStr()}</text>
31
- </Show>
32
- <text fg={color()} bg={bgColor()}>●</text>
33
- <text fg={props.isActive ? "white" : undefined} bg={bgColor()}>{` ${truncateName(props.modelId)}`}</text>
34
- <Show when={props.percentage !== undefined}>
35
- <text dim bg={bgColor()}>{` (${props.percentage!.toFixed(1)}%)`}</text>
36
- </Show>
37
- </box>
38
- <box flexDirection="row">
39
- <TokenBreakdown
40
- tokens={props.tokens}
41
- compact={props.compact}
42
- indent={(props.indent ?? 0) + 2}
43
- />
44
- </box>
45
- </box>
46
- );
47
- }