@tokscale/cli 1.4.2 → 2.0.0

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 -1523
  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 -105
  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 -22
  47. package/dist/submit.d.ts.map +0 -1
  48. package/dist/submit.js +0 -292
  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 -350
  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 -467
  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 -25
  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 -68
  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 -716
  149. package/dist/wrapped.js.map +0 -1
  150. package/src/auth.ts +0 -211
  151. package/src/cli.ts +0 -1865
  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 -631
  159. package/src/sessions/types.ts +0 -59
  160. package/src/spinner.ts +0 -283
  161. package/src/submit.ts +0 -358
  162. package/src/table.ts +0 -233
  163. package/src/tui/App.tsx +0 -443
  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 -368
  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 -557
  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 -172
  183. package/src/tui/utils/cleanup.ts +0 -65
  184. package/src/tui/utils/colors.ts +0 -76
  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 -845
@@ -1,368 +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
- <Show when={!isVeryNarrowTerminal()}>
153
- <text dim>|</text>
154
- <SortButton
155
- label="Date"
156
- sortType="date"
157
- active={props.sortBy === "date"}
158
- onClick={props.onSortChange}
159
- />
160
- <SortButton
161
- label="Cost"
162
- sortType="cost"
163
- active={props.sortBy === "cost"}
164
- onClick={props.onSortChange}
165
- />
166
- <SortButton
167
- label="Tokens"
168
- sortType="tokens"
169
- active={props.sortBy === "tokens"}
170
- onClick={props.onSortChange}
171
- />
172
- </Show>
173
- <Show when={showScrollInfo() && !isVeryNarrowTerminal()}>
174
- <text dim>|</text>
175
- <text
176
- dim
177
- >{`↓ ${props.scrollStart! + 1}-${props.scrollEnd} of ${props.totalItems}`}</text>
178
- </Show>
179
- </box>
180
- <box flexDirection="row" gap={1}>
181
- <text fg="cyan">{formatTokens(totals().total)}</text>
182
- <text dim>tokens</text>
183
- <text dim>|</text>
184
- <text fg="green" bold>{`$${totals().cost.toFixed(2)}`}</text>
185
- <Show when={!isVeryNarrowTerminal()}>
186
- <text dim>({props.modelCount} models)</text>
187
- </Show>
188
- </box>
189
- </box>
190
- <box flexDirection="row" gap={1}>
191
- <Show
192
- when={props.statusMessage}
193
- fallback={
194
- <Show
195
- when={isVeryNarrowTerminal()}
196
- fallback={
197
- <>
198
- <text dim>↑↓ scroll • ←→/tab view • y copy •</text>
199
- <box onMouseDown={props.onPaletteChange}>
200
- <text fg="magenta">{`[p:${palette().name}]`}</text>
201
- </box>
202
- <text fg={props.autoRefreshEnabled ? "green" : "gray"}>
203
- {`[Shift+R:auto update ${formatIntervalSeconds(props.autoRefreshMs)}]`}
204
- </text>
205
- <text dim>[-/+ interval]•</text>
206
- <box onMouseDown={props.onRefresh}>
207
- <text fg="yellow">[r:refresh]</text>
208
- </box>
209
- <text dim>• e export • q quit</text>
210
- </>
211
- }
212
- >
213
- <text dim>↑↓•←→•y•</text>
214
- <box onMouseDown={props.onPaletteChange}>
215
- <text fg="magenta">[p]</text>
216
- </box>
217
- <text fg={props.autoRefreshEnabled ? "green" : "gray"}>
218
- {`[Shift+R:auto update ${formatIntervalSeconds(props.autoRefreshMs)}]`}
219
- </text>
220
- <text dim>-+•</text>
221
- <box onMouseDown={props.onRefresh}>
222
- <text fg="yellow">[r]</text>
223
- </box>
224
- <text dim>•e•q</text>
225
- </Show>
226
- }
227
- >
228
- <text fg="green" bold>
229
- {props.statusMessage}
230
- </text>
231
- </Show>
232
- </box>
233
- <Show when={props.isRefreshing}>
234
- <LoadingStatusLine phase={props.loadingPhase} />
235
- </Show>
236
- <Show when={!props.isRefreshing && props.cacheTimestamp}>
237
- <box flexDirection="row" gap={1}>
238
- <text
239
- dim
240
- >{`Last updated: ${formatTimeAgo(props.cacheTimestamp!, now())}`}</text>
241
- <Show when={props.autoRefreshEnabled}>
242
- <text
243
- dim
244
- >{`• Auto: ${formatIntervalSeconds(props.autoRefreshMs)}`}</text>
245
- </Show>
246
- </box>
247
- </Show>
248
- </box>
249
- );
250
- }
251
-
252
- interface SourceBadgeProps {
253
- name: string;
254
- source: SourceType;
255
- enabled: boolean;
256
- onToggle?: (source: SourceType) => void;
257
- }
258
-
259
- function SourceBadge(props: SourceBadgeProps) {
260
- const handleClick = () => props.onToggle?.(props.source);
261
-
262
- return (
263
- <box onMouseDown={handleClick}>
264
- <text fg={props.enabled ? "green" : "gray"}>
265
- {`[${props.enabled ? "●" : "○"}${props.name}]`}
266
- </text>
267
- </box>
268
- );
269
- }
270
-
271
- interface SortButtonProps {
272
- label: string;
273
- sortType: SortType;
274
- active: boolean;
275
- onClick?: (sort: SortType) => void;
276
- }
277
-
278
- function SortButton(props: SortButtonProps) {
279
- const handleClick = () => props.onClick?.(props.sortType);
280
-
281
- return (
282
- <box onMouseDown={handleClick}>
283
- <text fg={props.active ? "white" : "gray"} bold={props.active}>
284
- {props.label}
285
- </text>
286
- </box>
287
- );
288
- }
289
-
290
- const SPINNER_COLORS = [
291
- "#00FFFF",
292
- "#00D7D7",
293
- "#00AFAF",
294
- "#008787",
295
- "#666666",
296
- "#666666",
297
- ];
298
- const SPINNER_WIDTH = 6;
299
- const SPINNER_HOLD_START = 20;
300
- const SPINNER_HOLD_END = 6;
301
- const SPINNER_TRAIL = 3;
302
- const SPINNER_INTERVAL = 40;
303
-
304
- const PHASE_MESSAGES: Record<LoadingPhase, string> = {
305
- idle: "Initializing...",
306
- "parsing-sources": "Scanning session data...",
307
- "loading-pricing": "Loading pricing data...",
308
- "finalizing-report": "Finalizing report...",
309
- complete: "Complete",
310
- };
311
-
312
- interface LoadingStatusLineProps {
313
- phase?: LoadingPhase;
314
- }
315
-
316
- function LoadingStatusLine(props: LoadingStatusLineProps) {
317
- const [frame, setFrame] = createSignal(0);
318
-
319
- onMount(() => {
320
- const id = setInterval(() => setFrame((f) => f + 1), SPINNER_INTERVAL);
321
- onCleanup(() => clearInterval(id));
322
- });
323
-
324
- const getSpinnerState = () => {
325
- const forwardFrames = SPINNER_WIDTH;
326
- const backwardFrames = SPINNER_WIDTH - 1;
327
- const totalCycle =
328
- forwardFrames + SPINNER_HOLD_END + backwardFrames + SPINNER_HOLD_START;
329
- const normalized = frame() % totalCycle;
330
-
331
- if (normalized < forwardFrames) {
332
- return { position: normalized, forward: true };
333
- } else if (normalized < forwardFrames + SPINNER_HOLD_END) {
334
- return { position: SPINNER_WIDTH - 1, forward: true };
335
- } else if (normalized < forwardFrames + SPINNER_HOLD_END + backwardFrames) {
336
- return {
337
- position:
338
- SPINNER_WIDTH - 2 - (normalized - forwardFrames - SPINNER_HOLD_END),
339
- forward: false,
340
- };
341
- }
342
- return { position: 0, forward: false };
343
- };
344
-
345
- const getCharProps = (index: number) => {
346
- const { position, forward } = getSpinnerState();
347
- const distance = forward ? position - index : index - position;
348
- if (distance >= 0 && distance < SPINNER_TRAIL) {
349
- return { char: "■", color: SPINNER_COLORS[distance] };
350
- }
351
- return { char: "⬝", color: "#444444" };
352
- };
353
-
354
- const message = () =>
355
- props.phase ? PHASE_MESSAGES[props.phase] : "Refreshing...";
356
-
357
- return (
358
- <box flexDirection="row" gap={1}>
359
- <box flexDirection="row" gap={0}>
360
- {Array.from({ length: SPINNER_WIDTH }, (_, i) => {
361
- const { char, color } = getCharProps(i);
362
- return <text fg={color}>{char}</text>;
363
- })}
364
- </box>
365
- <text dim>{message()}</text>
366
- </box>
367
- );
368
- }
@@ -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
- }