tokmon 0.19.8 → 0.20.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.
@@ -0,0 +1,3499 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PROVIDERS,
4
+ PROVIDER_ORDER,
5
+ TOKMON_WS_METHODS,
6
+ TOKMON_WS_PATH,
7
+ TokmonRpcGroup,
8
+ accountsByProvider,
9
+ buildAccounts,
10
+ coalesceTables,
11
+ col,
12
+ currency,
13
+ cursorModelSpend,
14
+ detectProviders,
15
+ fetchPeak,
16
+ resolveTimezone,
17
+ shortDate,
18
+ systemTimezone,
19
+ time,
20
+ tokens
21
+ } from "./chunk-MB6LRSEZ.js";
22
+ import {
23
+ COLOR_PALETTE,
24
+ DEFAULTS,
25
+ cacheDir,
26
+ configLocation,
27
+ generateAccountId,
28
+ getTrackedAccountRows,
29
+ isValidTimezone,
30
+ loadConfig,
31
+ normalizeConfig,
32
+ pickAccentColor,
33
+ sanitizeTyped,
34
+ saveConfigSync
35
+ } from "./chunk-MVMPQJ5S.js";
36
+ import {
37
+ glyphs
38
+ } from "./chunk-RF4GGQGM.js";
39
+
40
+ // src/bootstrap-ink.tsx
41
+ import { render } from "ink";
42
+ import { MouseProvider } from "@zenobius/ink-mouse";
43
+
44
+ // src/app.tsx
45
+ import { useState as useState4, useEffect as useEffect4, useCallback, useRef as useRef3, useMemo as useMemo2 } from "react";
46
+ import { Box as Box11, Text as Text11, useInput, useStdout, useApp } from "ink";
47
+ import { useMouse } from "@zenobius/ink-mouse";
48
+
49
+ // src/config-sync.ts
50
+ function deepEqual(a, b) {
51
+ if (a === b) return true;
52
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
53
+ if (Array.isArray(a) || Array.isArray(b)) {
54
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
55
+ for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
56
+ return true;
57
+ }
58
+ const ka = Object.keys(a);
59
+ const kb = Object.keys(b);
60
+ if (ka.length !== kb.length) return false;
61
+ for (const k of ka) {
62
+ if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
63
+ if (!deepEqual(a[k], b[k])) return false;
64
+ }
65
+ return true;
66
+ }
67
+ function reconcileDaemonConfig(previous, daemonConfig, pendingLocalConfig) {
68
+ if (pendingLocalConfig) {
69
+ if (deepEqual(daemonConfig, pendingLocalConfig)) {
70
+ return { config: previous && deepEqual(previous, daemonConfig) ? previous : daemonConfig, pendingLocalConfig: null };
71
+ }
72
+ return { config: previous ?? pendingLocalConfig, pendingLocalConfig };
73
+ }
74
+ if (previous && deepEqual(previous, daemonConfig)) {
75
+ return { config: previous, pendingLocalConfig: null };
76
+ }
77
+ if (previous?.onboarded === true && daemonConfig.onboarded === false) {
78
+ return { config: previous, pendingLocalConfig: null };
79
+ }
80
+ return { config: daemonConfig, pendingLocalConfig: null };
81
+ }
82
+
83
+ // src/client/seed-cache.ts
84
+ import { readFile } from "fs/promises";
85
+ import { join } from "path";
86
+ var snapshotCacheFile = () => join(cacheDir(), "web-snapshot.json");
87
+ async function loadSeedSnapshot() {
88
+ try {
89
+ const parsed = JSON.parse(await readFile(snapshotCacheFile(), "utf-8"));
90
+ if (!parsed || !Array.isArray(parsed.accounts)) return {};
91
+ const out = {};
92
+ for (const a of parsed.accounts) {
93
+ if (a.dashboard || a.billing) out[a.id] = { dashboard: a.dashboard ?? null, billing: a.billing ?? null };
94
+ }
95
+ return out;
96
+ } catch {
97
+ return {};
98
+ }
99
+ }
100
+
101
+ // src/ui/shared.tsx
102
+ import { appendFileSync } from "fs";
103
+ import { memo, useEffect, useRef, useState } from "react";
104
+ import { Box, Text } from "ink";
105
+ import { useOnMouseClick } from "@zenobius/ink-mouse";
106
+ import { jsx, jsxs } from "react/jsx-runtime";
107
+ function truncateName(s, n) {
108
+ const ell = glyphs().ellipsis;
109
+ return s.length > n ? s.slice(0, n - ell.length) + ell : s;
110
+ }
111
+ function ClickableBox({ onClick, children, ...props }) {
112
+ const ref = useRef(null);
113
+ useOnMouseClick(ref, (clicked) => {
114
+ if (clicked) onClick();
115
+ });
116
+ return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
117
+ }
118
+ var SGR_PRESS = /\x1b\[<(\d+);(\d+);(\d+)M/g;
119
+ var linkHits = /* @__PURE__ */ new Set();
120
+ function dispatchLinkClicks(chunk) {
121
+ if (linkHits.size === 0) return;
122
+ const s = typeof chunk === "string" ? chunk : chunk.toString("utf8");
123
+ SGR_PRESS.lastIndex = 0;
124
+ let m;
125
+ while ((m = SGR_PRESS.exec(s)) !== null) {
126
+ const code = Number(m[1]);
127
+ if (code & 64 || code & 32) continue;
128
+ const mx = Number(m[2]) - 1;
129
+ const my = Number(m[3]) - 1;
130
+ if (process.env.TOKMON_LINKDEBUG) {
131
+ try {
132
+ appendFileSync(process.env.TOKMON_LINKDEBUG, `DISPATCH code=${code} mx=${mx} my=${my} hits=${linkHits.size}
133
+ `);
134
+ } catch {
135
+ }
136
+ }
137
+ for (const hit of [...linkHits]) {
138
+ if (hit(mx, my)) break;
139
+ }
140
+ }
141
+ }
142
+ function nodeBox(node) {
143
+ const yn = node?.yogaNode;
144
+ if (!yn) return null;
145
+ const l = yn.getComputedLayout();
146
+ let left = l.left, top = l.top;
147
+ let p = node?.parentNode;
148
+ while (p) {
149
+ const pn = p.yogaNode;
150
+ if (!pn) break;
151
+ const pl = pn.getComputedLayout();
152
+ left += pl.left;
153
+ top += pl.top;
154
+ p = p.parentNode;
155
+ }
156
+ return { left, top, width: l.width, height: l.height };
157
+ }
158
+ function LinkBox({ onClick, children, ...props }) {
159
+ const ref = useRef(null);
160
+ const onClickRef = useRef(onClick);
161
+ onClickRef.current = onClick;
162
+ useEffect(() => {
163
+ const hit = (mx, my) => {
164
+ const box = nodeBox(ref.current);
165
+ if (process.env.TOKMON_LINKDEBUG) {
166
+ try {
167
+ appendFileSync(process.env.TOKMON_LINKDEBUG, `HIT? mx=${mx} my=${my} box=${box ? `${box.left},${box.top} ${box.width}x${box.height}` : "null"}
168
+ `);
169
+ } catch {
170
+ }
171
+ }
172
+ if (!box || box.width <= 0 || box.height <= 0) return false;
173
+ if (mx >= box.left && mx < box.left + box.width && my >= box.top && my < box.top + box.height) {
174
+ onClickRef.current();
175
+ return true;
176
+ }
177
+ return false;
178
+ };
179
+ linkHits.add(hit);
180
+ return () => {
181
+ linkHits.delete(hit);
182
+ };
183
+ }, []);
184
+ return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
185
+ }
186
+ function Spinner({ label }) {
187
+ const frames = glyphs().spinner;
188
+ const [i, setI] = useState(0);
189
+ useEffect(() => {
190
+ const id = setInterval(() => setI((n) => (n + 1) % frames.length), 80);
191
+ return () => clearInterval(id);
192
+ }, []);
193
+ return /* @__PURE__ */ jsxs(Box, { children: [
194
+ /* @__PURE__ */ jsxs(Text, { color: "green", children: [
195
+ frames[i],
196
+ " "
197
+ ] }),
198
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: label })
199
+ ] });
200
+ }
201
+ var TabBar = memo(function TabBar2({ tabs, active, onSelect }) {
202
+ return /* @__PURE__ */ jsx(Box, { children: tabs.map((t, i) => /* @__PURE__ */ jsx(ClickableBox, { onClick: () => onSelect(i), marginRight: 1, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, inverse: true, children: [
203
+ " ",
204
+ t,
205
+ " "
206
+ ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
207
+ " ",
208
+ t,
209
+ " "
210
+ ] }) }, t)) });
211
+ });
212
+ var PeakBadge = memo(function PeakBadge2({ peak }) {
213
+ const color = peak.state === "peak" ? "red" : "green";
214
+ return /* @__PURE__ */ jsxs(Box, { children: [
215
+ /* @__PURE__ */ jsxs(Text, { color, children: [
216
+ glyphs().dot,
217
+ " "
218
+ ] }),
219
+ /* @__PURE__ */ jsx(Text, { bold: true, color, children: peak.label }),
220
+ peak.minutesUntilChange !== null && peak.minutesUntilChange > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
221
+ " (",
222
+ fmtMinutes(peak.minutesUntilChange),
223
+ ")"
224
+ ] })
225
+ ] });
226
+ });
227
+ function fmtMinutes(mins) {
228
+ if (mins < 60) return `${mins}m`;
229
+ const h = Math.floor(mins / 60);
230
+ const m = mins % 60;
231
+ return m === 0 ? `${h}h` : `${h}h ${m}m`;
232
+ }
233
+ function currencySymbol(cur) {
234
+ return cur === "EUR" ? glyphs().eur : cur === "GBP" ? glyphs().gbp : "$";
235
+ }
236
+ function sparkline(values) {
237
+ if (values.length === 0) return "";
238
+ const spark = glyphs().spark;
239
+ const max = Math.max(...values);
240
+ if (max <= 0) return spark[0].repeat(values.length);
241
+ return values.map((v) => {
242
+ if (v <= 0) return spark[0];
243
+ const idx = Math.min(spark.length - 1, 1 + Math.round(v / max * (spark.length - 2)));
244
+ return spark[idx];
245
+ }).join("");
246
+ }
247
+ function Bar({ pct, color, width = 24 }) {
248
+ const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
249
+ return /* @__PURE__ */ jsxs(Text, { children: [
250
+ /* @__PURE__ */ jsx(Text, { color, children: glyphs().barFull.repeat(filled) }),
251
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: glyphs().barEmpty.repeat(width - filled) })
252
+ ] });
253
+ }
254
+ function metricValueText(m) {
255
+ if (m.format.kind === "dollars") {
256
+ const sym = currencySymbol(m.format.currency);
257
+ const used = `${sym}${m.used.toFixed(2)}`;
258
+ return m.limit != null ? `${used} / ${sym}${m.limit.toFixed(2)}` : `${used}`;
259
+ }
260
+ if (m.format.kind === "count") {
261
+ const suffix = m.format.suffix ? ` ${m.format.suffix}` : "";
262
+ const used = `${Math.round(m.used)}${suffix}`;
263
+ return m.limit != null ? `${used} / ${Math.round(m.limit)}` : used;
264
+ }
265
+ return `${Math.round(m.used)}%`;
266
+ }
267
+
268
+ // src/ui/dashboard.tsx
269
+ import { memo as memo2 } from "react";
270
+ import { Box as Box2, Text as Text2 } from "ink";
271
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
272
+ var GAP = 2;
273
+ var MIN_CARD = 56;
274
+ var MIN_CARD_DENSE = 50;
275
+ var CARD_H = { full: 14, compact: 12, mini: 8 };
276
+ var VARIANT_ORDER = ["full", "compact", "mini"];
277
+ var INDICATOR_ROWS = 1;
278
+ var MAX_SINGLE_CARD = Math.round(MIN_CARD * 1.6);
279
+ function chooseLayout(content, budget, n, single, cols) {
280
+ if (n <= 0) return { ncols: 1, variant: "mini", cardsPerPage: 1, pageCount: 1 };
281
+ const gridHeight = (rows, H2) => rows * H2 + Math.max(0, rows - 1);
282
+ const colCap = single ? 1 : cols >= 3 * MIN_CARD_DENSE + 2 * GAP ? 3 : cols >= 2 * MIN_CARD + GAP ? 2 : 1;
283
+ const maxCols = Math.max(1, Math.min(colCap, n));
284
+ const cardWidthAt = (nc) => nc <= 1 ? content : Math.floor((content - GAP * (nc - 1)) / nc);
285
+ const minWidthAt = (nc) => nc >= 3 ? MIN_CARD_DENSE : MIN_CARD;
286
+ for (const variant of VARIANT_ORDER) {
287
+ for (let nc = maxCols; nc >= 1; nc--) {
288
+ if (nc > 1 && cardWidthAt(nc) < minWidthAt(nc)) continue;
289
+ const rows = Math.ceil(n / nc);
290
+ if (gridHeight(rows, CARD_H[variant]) <= budget) {
291
+ return { ncols: nc, variant, cardsPerPage: n, pageCount: 1 };
292
+ }
293
+ }
294
+ }
295
+ let ncols = 1;
296
+ for (let nc = maxCols; nc >= 1; nc--) {
297
+ if (nc === 1 || cardWidthAt(nc) >= minWidthAt(nc)) {
298
+ ncols = nc;
299
+ break;
300
+ }
301
+ }
302
+ const H = CARD_H.mini;
303
+ const fitBudget = budget - INDICATOR_ROWS;
304
+ const rowsThatFit = Math.max(1, Math.floor((fitBudget + 1) / (H + 1)));
305
+ const cardsPerPage = Math.max(1, rowsThatFit * ncols);
306
+ const pageCount = Math.max(1, Math.ceil(n / cardsPerPage));
307
+ return { ncols, variant: "mini", cardsPerPage, pageCount };
308
+ }
309
+ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget, focusId, layout, page = 0 }) {
310
+ if (groups.length === 0) {
311
+ return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
312
+ "No providers enabled ",
313
+ glyphs().emDash,
314
+ " press s to pick providers."
315
+ ] });
316
+ }
317
+ let shown = groups;
318
+ if (layout === "single" && focusId === null) shown = groups.slice(0, 1);
319
+ const single = focusId !== null || layout === "single";
320
+ const content = Math.max(MIN_CARD, cols - 4);
321
+ const { ncols, variant, cardsPerPage, pageCount } = chooseLayout(content, budget, shown.length, single, cols);
322
+ let cardW = ncols <= 1 ? content : Math.floor((content - GAP * (ncols - 1)) / ncols);
323
+ if (ncols === 1 && cardW > MAX_SINGLE_CARD) cardW = MAX_SINGLE_CARD;
324
+ const pg = pageCount > 1 ? (page % pageCount + pageCount) % pageCount : 0;
325
+ const visible = pageCount > 1 ? shown.slice(pg * cardsPerPage, pg * cardsPerPage + cardsPerPage) : shown;
326
+ return /* @__PURE__ */ jsxs2(Box2, { height: budget, flexDirection: "column", overflow: "hidden", children: [
327
+ /* @__PURE__ */ jsx2(Box2, { width: content, flexWrap: "wrap", columnGap: GAP, rowGap: 1, children: visible.map((g) => /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, children: /* @__PURE__ */ jsx2(ProviderCard, { provider: g.provider, accounts: g.accounts, stats, width: cardW, variant }) }, g.provider)) }),
328
+ pageCount > 1 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
329
+ " ",
330
+ glyphs().middot,
331
+ " page ",
332
+ pg + 1,
333
+ "/",
334
+ pageCount,
335
+ " ",
336
+ glyphs().middot,
337
+ " scroll ",
338
+ glyphs().arrowU,
339
+ glyphs().arrowD
340
+ ] })
341
+ ] });
342
+ });
343
+ function ProviderCard({ provider, accounts, stats, width, variant }) {
344
+ const meta = PROVIDERS[provider];
345
+ const items = accounts.map((a) => ({ account: a, s: stats.get(a.id) }));
346
+ const dashboards = items.map((i) => i.s?.dashboard).filter((d) => !!d);
347
+ const agg = meta.hasUsage && dashboards.length > 0 ? aggregate(dashboards) : null;
348
+ const plan = items.map((i) => i.s?.billing?.plan).find(Boolean) ?? null;
349
+ const activity = items.map((i) => i.s?.billing?.activity).find(Boolean) ?? null;
350
+ const inner = width - 4;
351
+ const barW = Math.max(10, Math.min(46, inner - 20));
352
+ const hasSpark = !!agg && agg.series.some((v) => v > 0);
353
+ const showBars = variant !== "mini";
354
+ const showSpark = variant === "full";
355
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width, borderStyle: glyphs().border, borderColor: meta.color, paddingX: 1, children: [
356
+ /* @__PURE__ */ jsxs2(Box2, { children: [
357
+ /* @__PURE__ */ jsxs2(Text2, { color: meta.color, children: [
358
+ glyphs().dot,
359
+ " "
360
+ ] }),
361
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: meta.color, children: meta.name }),
362
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
363
+ plan && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: plan })
364
+ ] }),
365
+ meta.hasUsage && (agg ? /* @__PURE__ */ jsxs2(Fragment, { children: [
366
+ /* @__PURE__ */ jsx2(Box2, { height: 1 }),
367
+ /* @__PURE__ */ jsx2(SummaryRow, { label: "Today", s: agg.today }),
368
+ /* @__PURE__ */ jsx2(SummaryRow, { label: "This Week", s: agg.week }),
369
+ /* @__PURE__ */ jsx2(SummaryRow, { label: "This Month", s: agg.month }),
370
+ /* @__PURE__ */ jsx2(KpiLine, { agg })
371
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
372
+ /* @__PURE__ */ jsx2(Box2, { height: 1 }),
373
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
374
+ "Fetching usage",
375
+ glyphs().ellipsis
376
+ ] })
377
+ ] })),
378
+ meta.hasBilling && showBars && /* @__PURE__ */ jsxs2(Fragment, { children: [
379
+ meta.hasUsage && /* @__PURE__ */ jsx2(Rule, { inner }),
380
+ /* @__PURE__ */ jsx2(LimitsBlock, { items, barW })
381
+ ] }),
382
+ meta.hasBilling && !showBars && !meta.hasUsage && /* @__PURE__ */ jsx2(CompactBilling, { items }),
383
+ hasSpark && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
384
+ /* @__PURE__ */ jsx2(Rule, { inner }),
385
+ /* @__PURE__ */ jsx2(SparkFooter, { series: agg.series, month: agg.month.cost, color: meta.color })
386
+ ] }),
387
+ !meta.hasUsage && activity && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
388
+ /* @__PURE__ */ jsx2(Rule, { inner }),
389
+ /* @__PURE__ */ jsxs2(Box2, { children: [
390
+ /* @__PURE__ */ jsx2(Box2, { width: 4, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "14d" }) }),
391
+ /* @__PURE__ */ jsx2(Text2, { color: meta.color, children: sparkline(activity.series) }),
392
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: activity.summary }) })
393
+ ] })
394
+ ] })
395
+ ] });
396
+ }
397
+ function CompactBilling({ items }) {
398
+ const billing = items.map((i) => i.s?.billing).find(Boolean);
399
+ if (!billing) return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
400
+ "Fetching",
401
+ glyphs().ellipsis
402
+ ] });
403
+ if (billing.error) return /* @__PURE__ */ jsx2(Text2, { color: "red", children: billing.error });
404
+ const m = billing.metrics[0];
405
+ if (!m) return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" });
406
+ return /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: metricValueText(m) });
407
+ }
408
+ function Rule({ inner }) {
409
+ return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: glyphs().rule.repeat(Math.max(0, inner)) });
410
+ }
411
+ function SummaryRow({ label, s }) {
412
+ const cachedPct = s.tokens > 0 ? Math.round(s.cacheRead / s.tokens * 100) : 0;
413
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
414
+ /* @__PURE__ */ jsx2(Box2, { width: 11, flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: label }) }),
415
+ /* @__PURE__ */ jsx2(Box2, { width: 11, flexShrink: 0, justifyContent: "flex-end", children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", wrap: "truncate", children: currency(s.cost) }) }),
416
+ /* @__PURE__ */ jsx2(Box2, { width: 13, flexShrink: 0, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate", children: [
417
+ tokens(s.tokens),
418
+ " tok"
419
+ ] }) }),
420
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: cachedPct > 0 ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate", children: [
421
+ cachedPct,
422
+ "% cached"
423
+ ] }) : /* @__PURE__ */ jsx2(Text2, { children: " " }) })
424
+ ] });
425
+ }
426
+ function KpiLine({ agg }) {
427
+ const hasBurn = agg.burnRate > 0;
428
+ const hasSaved = agg.month.cacheSavings > 0;
429
+ if (!hasBurn && !hasSaved) return null;
430
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
431
+ hasBurn && /* @__PURE__ */ jsxs2(Fragment, { children: [
432
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Burn " }),
433
+ /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
434
+ currency(agg.burnRate),
435
+ "/hr"
436
+ ] })
437
+ ] }),
438
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
439
+ hasSaved && /* @__PURE__ */ jsxs2(Fragment, { children: [
440
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Cache saved " }),
441
+ /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
442
+ currency(agg.month.cacheSavings),
443
+ "/mo"
444
+ ] })
445
+ ] })
446
+ ] });
447
+ }
448
+ function LimitsBlock({ items, barW }) {
449
+ const showName = items.length > 1;
450
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map(({ account, s }, idx) => {
451
+ const billing = s?.billing;
452
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: showName && idx > 0 ? 1 : 0, children: [
453
+ showName && /* @__PURE__ */ jsxs2(Box2, { children: [
454
+ /* @__PURE__ */ jsxs2(Text2, { color: account.color, children: [
455
+ glyphs().dot,
456
+ " "
457
+ ] }),
458
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: truncateName(account.name, 22) })
459
+ ] }),
460
+ !billing ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
461
+ "Fetching",
462
+ glyphs().ellipsis
463
+ ] }) : billing.error ? /* @__PURE__ */ jsx2(Text2, { color: "red", children: billing.error }) : billing.metrics.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" }) : billing.metrics.map((m, i) => /* @__PURE__ */ jsx2(MetricRow, { m, color: account.color, barW }, `${m.label}${i}`))
464
+ ] }, account.id);
465
+ }) });
466
+ }
467
+ function MetricRow({ m, color, barW }) {
468
+ if (m.format.kind === "percent") {
469
+ const barColor = m.used >= 90 ? "red" : m.used >= 75 ? "yellow" : color;
470
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
471
+ /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: m.label }) }),
472
+ /* @__PURE__ */ jsx2(Bar, { pct: m.used, color: barColor, width: barW }),
473
+ /* @__PURE__ */ jsx2(Box2, { width: 5, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { bold: true, children: [
474
+ Math.round(m.used),
475
+ "%"
476
+ ] }) }),
477
+ /* @__PURE__ */ jsx2(Box2, { width: 8, justifyContent: "flex-end", children: m.resetsAt ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: m.resetsAt }) : /* @__PURE__ */ jsx2(Text2, { children: " " }) })
478
+ ] });
479
+ }
480
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
481
+ /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: m.label }) }),
482
+ /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: metricValueText(m) })
483
+ ] });
484
+ }
485
+ function SparkFooter({ series, month, color }) {
486
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
487
+ /* @__PURE__ */ jsx2(Box2, { width: 4, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "14d" }) }),
488
+ /* @__PURE__ */ jsx2(Text2, { color, children: sparkline(series) }),
489
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
490
+ currency(month),
491
+ " mo"
492
+ ] }) })
493
+ ] });
494
+ }
495
+ function aggregate(list) {
496
+ const zero = () => ({ cost: 0, tokens: 0, cacheRead: 0, cacheSavings: 0 });
497
+ const z = { today: zero(), week: zero(), month: zero(), burnRate: 0, series: [] };
498
+ for (const d of list) {
499
+ for (const k of ["today", "week", "month"]) {
500
+ z[k].cost += d[k].cost;
501
+ z[k].tokens += d[k].tokens;
502
+ z[k].cacheRead += d[k].cacheRead;
503
+ z[k].cacheSavings += d[k].cacheSavings;
504
+ }
505
+ z.burnRate += d.burnRate;
506
+ d.series.forEach((v, i) => {
507
+ z.series[i] = (z.series[i] ?? 0) + v;
508
+ });
509
+ }
510
+ return z;
511
+ }
512
+ var TotalsRow = memo2(function TotalsRow2({ groups, stats, cols }) {
513
+ const zero = () => ({ cost: 0, tokens: 0, cacheRead: 0, cacheSavings: 0 });
514
+ const t = zero(), w = zero(), m = zero();
515
+ for (const g of groups) {
516
+ if (!PROVIDERS[g.provider].hasUsage) continue;
517
+ for (const a of g.accounts) {
518
+ const d = stats.get(a.id)?.dashboard;
519
+ if (!d) continue;
520
+ t.cost += d.today.cost;
521
+ t.tokens += d.today.tokens;
522
+ w.cost += d.week.cost;
523
+ w.tokens += d.week.tokens;
524
+ m.cost += d.month.cost;
525
+ m.tokens += d.month.tokens;
526
+ }
527
+ }
528
+ const inner = cols - 4;
529
+ const dot = glyphs().middot;
530
+ const full = `${glyphs().dotAll} Today ${currency(t.cost)} (${tokens(t.tokens)} tok) ${dot} Week ${currency(w.cost)} (${tokens(w.tokens)} tok) ${dot} Month ${currency(m.cost)} (${tokens(m.tokens)} tok)`;
531
+ const noTok = `${glyphs().dotAll} Today ${currency(t.cost)} ${dot} Week ${currency(w.cost)} ${dot} Month ${currency(m.cost)}`;
532
+ const tight = `${glyphs().dotAll} ${currency(t.cost)} ${dot} ${currency(w.cost)} ${dot} ${currency(m.cost)}`;
533
+ const text = full.length <= inner ? full : noTok.length <= inner ? noTok : tight;
534
+ return /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: text }) });
535
+ });
536
+
537
+ // src/ui/table.tsx
538
+ import { memo as memo4 } from "react";
539
+ import { Box as Box4, Text as Text4 } from "ink";
540
+
541
+ // src/ui/settings.tsx
542
+ import { memo as memo3 } from "react";
543
+ import { Box as Box3, Text as Text3 } from "ink";
544
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
545
+ var GENERAL_ROWS = 6;
546
+ var SETTINGS_TABS = ["general", "providers", "accounts"];
547
+ var SETTINGS_TAB_LABELS = {
548
+ general: "General",
549
+ providers: "Providers",
550
+ accounts: "Accounts"
551
+ };
552
+ var FORM_FIELDS = ["provider", "name", "homeDir", "color"];
553
+ var SettingsView = memo3(function SettingsView2({
554
+ config,
555
+ cursor,
556
+ activeTab,
557
+ tzEdit,
558
+ tzCaret,
559
+ tzError,
560
+ resolvedTz,
561
+ accountForm,
562
+ activeAccountId,
563
+ trackedAccounts,
564
+ accountIdentities
565
+ }) {
566
+ if (accountForm) return /* @__PURE__ */ jsx3(AccountFormView, { form: accountForm, accounts: config.accounts });
567
+ const editingTz = tzEdit !== null;
568
+ const tzDisplay = config.timezone === null ? `System (${resolvedTz})` : config.timezone;
569
+ const tabFocused = cursor < 0;
570
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
571
+ /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Settings" }),
572
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: configLocation() }),
573
+ /* @__PURE__ */ jsx3(Box3, { height: 1 }),
574
+ /* @__PURE__ */ jsx3(SettingsTabBar, { active: activeTab, focused: tabFocused }),
575
+ /* @__PURE__ */ jsx3(Box3, { height: 1 }),
576
+ activeTab === "general" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
577
+ /* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "General" }),
578
+ /* @__PURE__ */ jsxs3(Row, { cursor, idx: 0, label: "Refresh interval", children: [
579
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
580
+ glyphs().caretL,
581
+ " "
582
+ ] }),
583
+ /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "yellow", children: [
584
+ config.interval,
585
+ "s"
586
+ ] }),
587
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
588
+ " ",
589
+ glyphs().caretR
590
+ ] })
591
+ ] }),
592
+ /* @__PURE__ */ jsxs3(Row, { cursor, idx: 1, label: "Billing poll", children: [
593
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
594
+ glyphs().caretL,
595
+ " "
596
+ ] }),
597
+ /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "yellow", children: [
598
+ config.billingInterval,
599
+ "m"
600
+ ] }),
601
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
602
+ " ",
603
+ glyphs().caretR
604
+ ] })
605
+ ] }),
606
+ /* @__PURE__ */ jsx3(Row, { cursor, idx: 2, label: "Clear screen", children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: config.clearScreen ? "green" : "red", children: config.clearScreen ? "on" : "off" }) }),
607
+ /* @__PURE__ */ jsx3(Row, { cursor, idx: 3, label: "Timezone", children: editingTz ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
608
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[" }),
609
+ /* @__PURE__ */ jsx3(CaretText, { value: tzEdit ?? "", caret: tzCaret, color: "cyan" }),
610
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "]" })
611
+ ] }) : /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: tzDisplay }) }),
612
+ cursor === 3 && tzError && /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
613
+ " ",
614
+ tzError
615
+ ] }),
616
+ /* @__PURE__ */ jsxs3(Row, { cursor, idx: 4, label: "Dashboard", children: [
617
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
618
+ glyphs().caretL,
619
+ " "
620
+ ] }),
621
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.dashboardLayout === "grid" ? "grid (all)" : "single (cycle)" }),
622
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
623
+ " ",
624
+ glyphs().caretR
625
+ ] })
626
+ ] }),
627
+ /* @__PURE__ */ jsxs3(Row, { cursor, idx: 5, label: "Default focus", children: [
628
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
629
+ glyphs().caretL,
630
+ " "
631
+ ] }),
632
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.defaultFocus === "all" ? "All" : "Last account" }),
633
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
634
+ " ",
635
+ glyphs().caretR
636
+ ] })
637
+ ] })
638
+ ] }),
639
+ activeTab === "providers" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
640
+ /* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "Providers" }),
641
+ PROVIDER_ORDER.map((pid, i) => {
642
+ const selected = cursor === i;
643
+ const enabled = !config.disabledProviders.includes(pid);
644
+ const p = PROVIDERS[pid];
645
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
646
+ /* @__PURE__ */ jsxs3(Text3, { color: selected ? "green" : void 0, children: [
647
+ selected ? glyphs().caretR : " ",
648
+ " "
649
+ ] }),
650
+ /* @__PURE__ */ jsx3(Text3, { bold: enabled, color: enabled ? p.color : void 0, dimColor: !enabled, children: enabled ? `[${glyphs().check}]` : "[ ]" }),
651
+ /* @__PURE__ */ jsxs3(Text3, { color: p.color, children: [
652
+ " ",
653
+ glyphs().dot,
654
+ " "
655
+ ] }),
656
+ /* @__PURE__ */ jsx3(Box3, { width: 9, children: /* @__PURE__ */ jsx3(Text3, { bold: selected, children: p.name }) }),
657
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: enabled ? "tracking" : "off" })
658
+ ] }, pid);
659
+ })
660
+ ] }),
661
+ activeTab === "accounts" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
662
+ /* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "Accounts" }),
663
+ trackedAccounts.length === 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
664
+ " none tracked ",
665
+ glyphs().emDash,
666
+ " enable a provider or add an account"
667
+ ] }),
668
+ trackedAccounts.map((acc, i) => {
669
+ const selected = cursor === i;
670
+ const isActive = acc.id === activeAccountId;
671
+ const provider = PROVIDERS[acc.providerId];
672
+ const identity = accountIdentities.get(acc.id);
673
+ const identityLabel = identity?.email || identity?.displayName || acc.name;
674
+ const plan = identity?.plan ?? null;
675
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
676
+ /* @__PURE__ */ jsxs3(Text3, { color: selected ? "green" : void 0, children: [
677
+ selected ? glyphs().caretR : " ",
678
+ " "
679
+ ] }),
680
+ /* @__PURE__ */ jsxs3(Text3, { color: acc.color || provider.color, children: [
681
+ isActive ? glyphs().dot : glyphs().radioOff,
682
+ " "
683
+ ] }),
684
+ /* @__PURE__ */ jsx3(Box3, { width: 28, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: truncateName(identityLabel, 27) }) }),
685
+ /* @__PURE__ */ jsx3(Box3, { width: 9, children: /* @__PURE__ */ jsx3(Text3, { color: provider.color, children: provider.name }) }),
686
+ /* @__PURE__ */ jsx3(Box3, { width: 18, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: plan ? truncateName(plan, 17) : "" }) }),
687
+ /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: acc.source === "auto" ? "auto tracking" : "configured" }) }),
688
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: truncateName(acc.homeDir, 24) })
689
+ ] }, `${acc.source}:${acc.id}`);
690
+ }),
691
+ /* @__PURE__ */ jsxs3(Box3, { children: [
692
+ /* @__PURE__ */ jsxs3(Text3, { color: cursor === trackedAccounts.length ? "green" : void 0, children: [
693
+ cursor === trackedAccounts.length ? glyphs().caretR : " ",
694
+ " "
695
+ ] }),
696
+ /* @__PURE__ */ jsx3(Text3, { color: "greenBright", children: "+ " }),
697
+ /* @__PURE__ */ jsx3(Text3, { children: "Add account" })
698
+ ] })
699
+ ] }),
700
+ /* @__PURE__ */ jsx3(Box3, { height: 1 }),
701
+ tabFocused ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
702
+ glyphs().arrowL,
703
+ glyphs().arrowR,
704
+ "/tab switch section ",
705
+ glyphs().middot,
706
+ " ",
707
+ glyphs().arrowD,
708
+ " rows ",
709
+ glyphs().middot,
710
+ " s/Esc close"
711
+ ] }) : editingTz ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
712
+ "type IANA name (e.g. Europe/London) ",
713
+ glyphs().middot,
714
+ " empty = System ",
715
+ glyphs().middot,
716
+ " Enter save ",
717
+ glyphs().middot,
718
+ " Esc cancel"
719
+ ] }) : activeTab === "providers" ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
720
+ glyphs().arrowU,
721
+ glyphs().arrowD,
722
+ " select ",
723
+ glyphs().middot,
724
+ " space toggle provider ",
725
+ glyphs().middot,
726
+ " s/Esc close"
727
+ ] }) : activeTab === "accounts" && cursor >= 0 && cursor < trackedAccounts.length ? trackedAccounts[cursor]?.source === "auto" ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
728
+ glyphs().arrowU,
729
+ glyphs().arrowD,
730
+ " select ",
731
+ glyphs().middot,
732
+ " Enter configure ",
733
+ glyphs().middot,
734
+ " space activate ",
735
+ glyphs().middot,
736
+ " s/Esc close"
737
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
738
+ glyphs().arrowU,
739
+ glyphs().arrowD,
740
+ " select ",
741
+ glyphs().middot,
742
+ " ",
743
+ glyphs().shift,
744
+ glyphs().arrowU,
745
+ glyphs().arrowD,
746
+ " reorder ",
747
+ glyphs().middot,
748
+ " Enter edit ",
749
+ glyphs().middot,
750
+ " space activate ",
751
+ glyphs().middot,
752
+ " d delete ",
753
+ glyphs().middot,
754
+ " s/Esc close"
755
+ ] }) : activeTab === "accounts" && cursor === trackedAccounts.length ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
756
+ glyphs().arrowU,
757
+ glyphs().arrowD,
758
+ " select ",
759
+ glyphs().middot,
760
+ " Enter add account ",
761
+ glyphs().middot,
762
+ " s/Esc close"
763
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
764
+ glyphs().arrowU,
765
+ glyphs().arrowD,
766
+ " select ",
767
+ glyphs().arrowL,
768
+ glyphs().arrowR,
769
+ " adjust Enter edit tab switch section s/Esc close"
770
+ ] })
771
+ ] });
772
+ });
773
+ function SettingsTabBar({ active, focused }) {
774
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
775
+ /* @__PURE__ */ jsxs3(Text3, { color: focused ? "green" : void 0, children: [
776
+ focused ? glyphs().caretR : " ",
777
+ " "
778
+ ] }),
779
+ SETTINGS_TABS.map((tab, i) => {
780
+ const selected = tab === active;
781
+ return /* @__PURE__ */ jsxs3(Box3, { marginRight: 1, children: [
782
+ selected ? /* @__PURE__ */ jsxs3(Text3, { bold: true, inverse: true, children: [
783
+ " ",
784
+ SETTINGS_TAB_LABELS[tab],
785
+ " "
786
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
787
+ " ",
788
+ SETTINGS_TAB_LABELS[tab],
789
+ " "
790
+ ] }),
791
+ i < SETTINGS_TABS.length - 1 && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " " })
792
+ ] }, tab);
793
+ })
794
+ ] });
795
+ }
796
+ function CaretText({ value, caret, color }) {
797
+ const c = Math.max(0, Math.min(caret, value.length));
798
+ if (c >= value.length) {
799
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
800
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color, children: value }),
801
+ /* @__PURE__ */ jsx3(Text3, { color, children: glyphs().vbar })
802
+ ] });
803
+ }
804
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
805
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color, children: value.slice(0, c) }),
806
+ /* @__PURE__ */ jsx3(Text3, { inverse: true, color, children: value[c] }),
807
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color, children: value.slice(c + 1) })
808
+ ] });
809
+ }
810
+ function Row({ cursor, idx, label, children }) {
811
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
812
+ /* @__PURE__ */ jsxs3(Text3, { color: cursor === idx ? "green" : void 0, children: [
813
+ cursor === idx ? glyphs().caretR : " ",
814
+ " "
815
+ ] }),
816
+ /* @__PURE__ */ jsx3(Box3, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { children: label }) }),
817
+ children
818
+ ] });
819
+ }
820
+ function AccountFormView({ form, accounts }) {
821
+ const previewId = form.mode === "add" ? generateAccountId(form.name || "account", accounts) : form.editingId ?? "";
822
+ const accent = form.color;
823
+ const stepIndex = { provider: 1, name: 2, homeDir: 3, color: 4 };
824
+ const step = stepIndex[form.field];
825
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
826
+ /* @__PURE__ */ jsxs3(Box3, { children: [
827
+ /* @__PURE__ */ jsx3(Text3, { color: accent, bold: true, children: glyphs().vbar }),
828
+ /* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
829
+ " ",
830
+ form.mode === "add" ? "NEW ACCOUNT" : "EDIT ACCOUNT"
831
+ ] }),
832
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
833
+ " step ",
834
+ step,
835
+ " of 4"
836
+ ] })
837
+ ] }),
838
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Stepper, { active: form.field, accent }) }),
839
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", borderStyle: glyphs().border, borderColor: accent, paddingX: 2, paddingY: 1, children: [
840
+ /* @__PURE__ */ jsx3(ProviderField, { value: form.providerId, focused: form.field === "provider" }),
841
+ /* @__PURE__ */ jsx3(Box3, { height: 1 }),
842
+ /* @__PURE__ */ jsx3(
843
+ FormField,
844
+ {
845
+ label: "Name",
846
+ hint: "display name for this account",
847
+ value: form.name,
848
+ focused: form.field === "name",
849
+ caret: form.caret,
850
+ accent,
851
+ placeholder: "e.g. Work, Personal"
852
+ }
853
+ ),
854
+ /* @__PURE__ */ jsx3(Box3, { height: 1 }),
855
+ /* @__PURE__ */ jsx3(
856
+ FormField,
857
+ {
858
+ label: "Home directory",
859
+ hint: `path containing the tool's data dir ${glyphs().middot} ~ for default`,
860
+ value: form.homeDir,
861
+ focused: form.field === "homeDir",
862
+ caret: form.caret,
863
+ accent,
864
+ placeholder: "~/work",
865
+ mono: true
866
+ }
867
+ ),
868
+ /* @__PURE__ */ jsx3(Box3, { height: 1 }),
869
+ /* @__PURE__ */ jsx3(ColorField, { value: form.color, focused: form.field === "color" }),
870
+ /* @__PURE__ */ jsx3(Box3, { height: 1 }),
871
+ /* @__PURE__ */ jsxs3(Box3, { children: [
872
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
873
+ "id ",
874
+ glyphs().boxMark,
875
+ " "
876
+ ] }),
877
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: accent, children: previewId || "account" }),
878
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
879
+ " ",
880
+ glyphs().boxMark,
881
+ " auto-generated from name"
882
+ ] })
883
+ ] })
884
+ ] }),
885
+ form.error && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
886
+ glyphs().warn,
887
+ " ",
888
+ form.error
889
+ ] }) }),
890
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
891
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
892
+ "tab/",
893
+ glyphs().arrowU,
894
+ glyphs().arrowD,
895
+ " "
896
+ ] }),
897
+ /* @__PURE__ */ jsx3(Text3, { children: "switch field" }),
898
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
899
+ " ",
900
+ glyphs().middot,
901
+ " "
902
+ ] }),
903
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "enter " }),
904
+ /* @__PURE__ */ jsx3(Text3, { children: form.field === "color" ? "save" : "next" }),
905
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
906
+ " ",
907
+ glyphs().middot,
908
+ " "
909
+ ] }),
910
+ form.field === "color" || form.field === "provider" ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
911
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
912
+ glyphs().arrowL,
913
+ glyphs().arrowR,
914
+ " "
915
+ ] }),
916
+ /* @__PURE__ */ jsx3(Text3, { children: form.field === "provider" ? "pick provider" : "pick color" }),
917
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
918
+ " ",
919
+ glyphs().middot,
920
+ " "
921
+ ] })
922
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
923
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
924
+ glyphs().arrowL,
925
+ glyphs().arrowR,
926
+ " "
927
+ ] }),
928
+ /* @__PURE__ */ jsx3(Text3, { children: "move caret" }),
929
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
930
+ " ",
931
+ glyphs().middot,
932
+ " "
933
+ ] })
934
+ ] }),
935
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "ctrl+s " }),
936
+ /* @__PURE__ */ jsx3(Text3, { children: "save" }),
937
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
938
+ " ",
939
+ glyphs().middot,
940
+ " "
941
+ ] }),
942
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "esc " }),
943
+ /* @__PURE__ */ jsx3(Text3, { children: "cancel" })
944
+ ] })
945
+ ] });
946
+ }
947
+ function Stepper({ active, accent }) {
948
+ const steps = [
949
+ { id: "provider", label: "Provider" },
950
+ { id: "name", label: "Name" },
951
+ { id: "homeDir", label: "Home" },
952
+ { id: "color", label: "Color" }
953
+ ];
954
+ const activeIdx = steps.findIndex((s) => s.id === active);
955
+ return /* @__PURE__ */ jsx3(Box3, { children: steps.map((s, i) => {
956
+ const done = i < activeIdx;
957
+ const cur = i === activeIdx;
958
+ const dot = done ? glyphs().dot : cur ? glyphs().dotSel : glyphs().radioOff;
959
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
960
+ /* @__PURE__ */ jsxs3(Text3, { color: cur || done ? accent : void 0, dimColor: !cur && !done, children: [
961
+ dot,
962
+ " "
963
+ ] }),
964
+ /* @__PURE__ */ jsx3(Text3, { bold: cur, color: cur ? accent : void 0, dimColor: !cur, children: s.label }),
965
+ i < steps.length - 1 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
966
+ " ",
967
+ glyphs().rule,
968
+ " "
969
+ ] })
970
+ ] }, s.id);
971
+ }) });
972
+ }
973
+ function ProviderField({ value, focused }) {
974
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
975
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: focused ? PROVIDERS[value].color : void 0, bold: focused, dimColor: !focused, children: [
976
+ focused ? glyphs().caretR : " ",
977
+ " Provider"
978
+ ] }) }),
979
+ /* @__PURE__ */ jsxs3(Box3, { children: [
980
+ /* @__PURE__ */ jsxs3(Text3, { children: [
981
+ " ",
982
+ focused ? glyphs().vbar : " ",
983
+ " "
984
+ ] }),
985
+ PROVIDER_ORDER.map((pid) => {
986
+ const selected = pid === value;
987
+ const p = PROVIDERS[pid];
988
+ return /* @__PURE__ */ jsx3(Box3, { marginRight: 2, children: selected ? /* @__PURE__ */ jsxs3(Text3, { bold: true, color: p.color, children: [
989
+ "[",
990
+ p.name,
991
+ "]"
992
+ ] }) : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: p.name }) }, pid);
993
+ })
994
+ ] }),
995
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " which tool this account tracks" }) })
996
+ ] });
997
+ }
998
+ function FormField({ label, hint, value, focused, caret, accent, placeholder, mono }) {
999
+ const isPlaceholder = value === "";
1000
+ const display = isPlaceholder ? placeholder : value;
1001
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1002
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: focused ? accent : void 0, bold: focused, dimColor: !focused, children: [
1003
+ focused ? glyphs().caretR : " ",
1004
+ " ",
1005
+ label
1006
+ ] }) }),
1007
+ /* @__PURE__ */ jsxs3(Box3, { children: [
1008
+ /* @__PURE__ */ jsxs3(Text3, { color: focused ? accent : void 0, children: [
1009
+ " ",
1010
+ focused ? glyphs().vbar : " ",
1011
+ " "
1012
+ ] }),
1013
+ focused ? isPlaceholder ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1014
+ /* @__PURE__ */ jsx3(Text3, { color: accent, children: glyphs().vbar }),
1015
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, italic: mono, children: placeholder })
1016
+ ] }) : /* @__PURE__ */ jsx3(CaretText, { value, caret: caret ?? value.length, color: accent }) : /* @__PURE__ */ jsx3(Text3, { dimColor: isPlaceholder, italic: mono && isPlaceholder, children: display })
1017
+ ] }),
1018
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1019
+ " ",
1020
+ hint
1021
+ ] }) })
1022
+ ] });
1023
+ }
1024
+ function ColorField({ value, focused }) {
1025
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1026
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: focused ? value : void 0, bold: focused, dimColor: !focused, children: [
1027
+ focused ? glyphs().caretR : " ",
1028
+ " Accent color"
1029
+ ] }) }),
1030
+ /* @__PURE__ */ jsxs3(Box3, { children: [
1031
+ /* @__PURE__ */ jsxs3(Text3, { children: [
1032
+ " ",
1033
+ focused ? glyphs().vbar : " ",
1034
+ " "
1035
+ ] }),
1036
+ COLOR_PALETTE.map((c) => /* @__PURE__ */ jsx3(Box3, { marginRight: 1, children: c === value ? /* @__PURE__ */ jsxs3(Text3, { bold: true, color: c, children: [
1037
+ "[",
1038
+ glyphs().dot,
1039
+ "]"
1040
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { color: c, dimColor: !focused, children: [
1041
+ " ",
1042
+ glyphs().dot
1043
+ ] }) }, c))
1044
+ ] }),
1045
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " shows on dashboard, account strip, borders" }) })
1046
+ ] });
1047
+ }
1048
+
1049
+ // src/ui/table.tsx
1050
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1051
+ var MONTHS = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1052
+ var TableProviderBar = memo4(function TableProviderBar2({ providers, active, onSelect }) {
1053
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
1054
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "provider " }),
1055
+ providers.map((p) => {
1056
+ const meta = PROVIDERS[p];
1057
+ return /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onSelect(p), marginRight: 1, children: p === active ? /* @__PURE__ */ jsxs4(Text4, { bold: true, color: meta.color, inverse: true, children: [
1058
+ " ",
1059
+ meta.name,
1060
+ " "
1061
+ ] }) : /* @__PURE__ */ jsxs4(Text4, { color: meta.color, dimColor: true, children: [
1062
+ " ",
1063
+ meta.name,
1064
+ " "
1065
+ ] }) }, p);
1066
+ }),
1067
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " p/P switch" })
1068
+ ] });
1069
+ });
1070
+ var ControlBar = memo4(function ControlBar2({ views, period, sort, search, searchCaret, searching, showPeriod }) {
1071
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
1072
+ showPeriod && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1073
+ views.map((v, i) => /* @__PURE__ */ jsx4(Box4, { marginRight: 2, children: i === period ? /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
1074
+ "[",
1075
+ v,
1076
+ "]"
1077
+ ] }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: v }) }, v)),
1078
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " " })
1079
+ ] }),
1080
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "sort " }),
1081
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "magenta", children: sort }),
1082
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1083
+ " o cycle ",
1084
+ glyphs().middot,
1085
+ " "
1086
+ ] }),
1087
+ searching ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1088
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "/" }),
1089
+ /* @__PURE__ */ jsx4(CaretText, { value: search, caret: searchCaret, color: "cyan" })
1090
+ ] }) : search ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1091
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "filter " }),
1092
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: search }),
1093
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1094
+ " (/ edit ",
1095
+ glyphs().middot,
1096
+ " esc clear)"
1097
+ ] })
1098
+ ] }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "/ filter" })
1099
+ ] });
1100
+ });
1101
+ var TokenTable = memo4(function TokenTable2({ rows, cursor, expanded, maxRows, cols, onRowClick }) {
1102
+ if (rows.length === 0) return /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No usage in this period (or filtered out)." });
1103
+ const wide = cols > 90;
1104
+ const base = wide ? { label: 12, input: 10, output: 10, cc: 14, cr: 12, total: 11, cost: 13 } : { label: 8, input: 7, output: 7, cc: 7, cr: 8, total: 0, cost: 11 };
1105
+ const fixed = base.label + base.input + base.output + base.cc + base.cr + base.total + base.cost;
1106
+ const available = cols - fixed - 6;
1107
+ const W = { ...base, models: Math.max(wide ? 22 : 14, available) };
1108
+ const lineW = W.label + W.models + W.input + W.output + W.cc + W.cr + W.total + W.cost;
1109
+ const totals = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, cost: 0 };
1110
+ for (const r of rows) {
1111
+ totals.input += r.input;
1112
+ totals.output += r.output;
1113
+ totals.cacheCreate += r.cacheCreate;
1114
+ totals.cacheRead += r.cacheRead;
1115
+ totals.cost += r.cost;
1116
+ }
1117
+ const clampedCursor = Math.min(cursor, rows.length - 1);
1118
+ const scrollStart = Math.max(0, Math.min(clampedCursor - Math.floor(maxRows / 2), rows.length - maxRows));
1119
+ const visible = rows.slice(scrollStart, scrollStart + maxRows);
1120
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1121
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1122
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
1123
+ " ",
1124
+ col("Date", W.label, "left")
1125
+ ] }),
1126
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Models", W.models, "left") }),
1127
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Input", W.input) }),
1128
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Output", W.output) }),
1129
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col(wide ? "Cache Create" : "CchCrt", W.cc) }),
1130
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col(wide ? "Cache Read" : "CchRd", W.cr) }),
1131
+ W.total > 0 && /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Total", W.total) }),
1132
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Cost", W.cost) })
1133
+ ] }),
1134
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
1135
+ visible.map((r, vi) => {
1136
+ const idx = scrollStart + vi;
1137
+ const selected = idx === clampedCursor;
1138
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1139
+ /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs4(Text4, { inverse: selected, children: [
1140
+ /* @__PURE__ */ jsxs4(Text4, { color: selected ? void 0 : "cyan", children: [
1141
+ selected ? `${glyphs().caretR} ` : " ",
1142
+ col(fmtLabel(r.label), W.label, "left")
1143
+ ] }),
1144
+ /* @__PURE__ */ jsx4(Text4, { dimColor: !selected, children: col(r.models.join(", "), W.models, "left") }),
1145
+ /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.input), W.input) }),
1146
+ /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.output), W.output) }),
1147
+ /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.cacheCreate), W.cc) }),
1148
+ /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.cacheRead), W.cr) }),
1149
+ W.total > 0 && /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.total), W.total) }),
1150
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: selected ? void 0 : "yellow", children: col(currency(r.cost), W.cost) })
1151
+ ] }) }),
1152
+ idx === expanded && /* @__PURE__ */ jsx4(RowDetail, { row: r, indent: W.label + 2 })
1153
+ ] }, r.label);
1154
+ }),
1155
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
1156
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1157
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "greenBright", children: [
1158
+ " ",
1159
+ col("Total", W.label, "left")
1160
+ ] }),
1161
+ /* @__PURE__ */ jsx4(Text4, { children: col("", W.models, "left") }),
1162
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.input), W.input) }),
1163
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.output), W.output) }),
1164
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.cacheCreate), W.cc) }),
1165
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.cacheRead), W.cr) }),
1166
+ W.total > 0 && /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.input + totals.output + totals.cacheCreate + totals.cacheRead), W.total) }),
1167
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellowBright", children: col(currency(totals.cost), W.cost) })
1168
+ ] }),
1169
+ /* @__PURE__ */ jsx4(Box4, { height: 1 }),
1170
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1171
+ glyphs().arrowU,
1172
+ glyphs().arrowD,
1173
+ " navigate ",
1174
+ glyphs().middot,
1175
+ " Enter detail ",
1176
+ glyphs().middot,
1177
+ " o sort ",
1178
+ glyphs().middot,
1179
+ " g/G top/bottom ",
1180
+ glyphs().middot,
1181
+ " ",
1182
+ clampedCursor + 1,
1183
+ "/",
1184
+ rows.length
1185
+ ] })
1186
+ ] });
1187
+ });
1188
+ function RowDetail({ row, indent }) {
1189
+ return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", paddingLeft: indent, children: row.breakdown.map((m, i) => /* @__PURE__ */ jsxs4(Text4, { children: [
1190
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1191
+ i === row.breakdown.length - 1 ? glyphs().treeEnd : glyphs().treeMid,
1192
+ " "
1193
+ ] }),
1194
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col(m.name, 16, "left") }),
1195
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1196
+ col(tokens(m.input), 8),
1197
+ " in "
1198
+ ] }),
1199
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1200
+ col(tokens(m.output), 8),
1201
+ " out "
1202
+ ] }),
1203
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1204
+ col(tokens(m.cacheCreate), 8),
1205
+ " cc "
1206
+ ] }),
1207
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1208
+ col(tokens(m.cacheRead), 9),
1209
+ " cr "
1210
+ ] }),
1211
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: currency(m.cost) })
1212
+ ] }, m.name)) });
1213
+ }
1214
+ var CursorSpendTable = memo4(function CursorSpendTable2({ rows, cursor, maxRows, onRowClick }) {
1215
+ if (rows.length === 0) return /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No Cursor spend recorded locally." });
1216
+ const total = rows.reduce((a, r) => a + r.usd, 0);
1217
+ const totalAmt = rows.reduce((a, r) => a + r.requests, 0);
1218
+ const clamped = Math.min(cursor, rows.length - 1);
1219
+ const scrollStart = Math.max(0, Math.min(clamped - Math.floor(maxRows / 2), rows.length - maxRows));
1220
+ const visible = rows.slice(scrollStart, scrollStart + maxRows);
1221
+ const W = { model: 34, cost: 12, amount: 12, share: 8 };
1222
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1223
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1224
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
1225
+ " ",
1226
+ col("Model", W.model, "left")
1227
+ ] }),
1228
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Cost", W.cost) }),
1229
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Amount", W.amount) }),
1230
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Share", W.share) })
1231
+ ] }),
1232
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(W.model + W.cost + W.amount + W.share + 2) }),
1233
+ visible.map((r, vi) => {
1234
+ const idx = scrollStart + vi;
1235
+ const selected = idx === clamped;
1236
+ const share = total > 0 ? r.usd / total * 100 : 0;
1237
+ return /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs4(Text4, { inverse: selected, children: [
1238
+ /* @__PURE__ */ jsxs4(Text4, { color: selected ? void 0 : "magenta", children: [
1239
+ selected ? `${glyphs().caretR} ` : " ",
1240
+ col(r.name, W.model, "left")
1241
+ ] }),
1242
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: selected ? void 0 : "yellow", children: col(currency(r.usd), W.cost) }),
1243
+ /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.requests), W.amount) }),
1244
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: col(share.toFixed(1) + "%", W.share) })
1245
+ ] }) }, r.name);
1246
+ }),
1247
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(W.model + W.cost + W.amount + W.share + 2) }),
1248
+ /* @__PURE__ */ jsxs4(Text4, { children: [
1249
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "greenBright", children: [
1250
+ " ",
1251
+ col("Total", W.model, "left")
1252
+ ] }),
1253
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellowBright", children: col(currency(total), W.cost) }),
1254
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totalAmt), W.amount) }),
1255
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: col("100%", W.share) })
1256
+ ] }),
1257
+ /* @__PURE__ */ jsx4(Box4, { height: 1 }),
1258
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1259
+ "local spend by model (composerData) ",
1260
+ glyphs().middot,
1261
+ " est. API-equivalent ",
1262
+ glyphs().middot,
1263
+ " ",
1264
+ clamped + 1,
1265
+ "/",
1266
+ rows.length
1267
+ ] })
1268
+ ] });
1269
+ });
1270
+ function fmtLabel(label) {
1271
+ if (label.length === 7 && label[4] === "-") {
1272
+ return `${MONTHS[Number(label.slice(5, 7))]} '${label.slice(2, 4)}`;
1273
+ }
1274
+ return shortDate(label);
1275
+ }
1276
+
1277
+ // src/ui/onboarding.tsx
1278
+ import { Box as Box5, Text as Text5 } from "ink";
1279
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1280
+ function Onboarding({ items, cursor, onToggle, onConfirm, heading = "Welcome to tokmon", subheading = "Pick the tools you want to track. You can change this anytime in settings." }) {
1281
+ const anyEnabled = items.some((it) => it.enabled);
1282
+ const startIdx = items.length;
1283
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1284
+ /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "greenBright", children: heading }) }),
1285
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: subheading }),
1286
+ /* @__PURE__ */ jsx5(Box5, { height: 1 }),
1287
+ items.map((it, i) => {
1288
+ const selected = cursor === i;
1289
+ const box = it.enabled ? `[${glyphs().check}]` : "[ ]";
1290
+ return /* @__PURE__ */ jsxs5(ClickableBox, { onClick: () => onToggle(i), children: [
1291
+ /* @__PURE__ */ jsxs5(Text5, { color: selected ? "green" : void 0, children: [
1292
+ selected ? glyphs().caretR : " ",
1293
+ " "
1294
+ ] }),
1295
+ /* @__PURE__ */ jsx5(Text5, { bold: it.enabled, color: it.enabled ? it.color : void 0, dimColor: !it.enabled, children: box }),
1296
+ /* @__PURE__ */ jsxs5(Text5, { color: it.color, children: [
1297
+ " ",
1298
+ glyphs().dot,
1299
+ " "
1300
+ ] }),
1301
+ /* @__PURE__ */ jsx5(Box5, { width: 13, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text5, { bold: selected, dimColor: !it.detected && !it.enabled, wrap: "truncate", children: it.name }) }),
1302
+ it.detected ? /* @__PURE__ */ jsx5(Text5, { color: "green", dimColor: true, children: "installed" }) : it.enabled ? /* @__PURE__ */ jsx5(Text5, { color: "yellow", dimColor: true, children: "manual" }) : /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "not found" })
1303
+ ] }, it.id);
1304
+ }),
1305
+ /* @__PURE__ */ jsx5(Box5, { height: 1 }),
1306
+ /* @__PURE__ */ jsxs5(ClickableBox, { onClick: onConfirm, children: [
1307
+ /* @__PURE__ */ jsxs5(Text5, { color: cursor === startIdx ? "green" : void 0, children: [
1308
+ cursor === startIdx ? glyphs().caretR : " ",
1309
+ " "
1310
+ ] }),
1311
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: anyEnabled ? "greenBright" : void 0, dimColor: !anyEnabled, children: anyEnabled ? `${glyphs().play} Start tokmon` : `${glyphs().play} Start (nothing selected)` })
1312
+ ] }),
1313
+ /* @__PURE__ */ jsx5(Box5, { height: 1 }),
1314
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1315
+ glyphs().arrowU,
1316
+ glyphs().arrowD,
1317
+ " move ",
1318
+ glyphs().middot,
1319
+ " space toggle ",
1320
+ glyphs().middot,
1321
+ " enter start ",
1322
+ glyphs().middot,
1323
+ " q quit"
1324
+ ] })
1325
+ ] });
1326
+ }
1327
+
1328
+ // src/ui/loading.tsx
1329
+ import { useState as useState2, useEffect as useEffect2 } from "react";
1330
+ import { Box as Box6, Text as Text6 } from "ink";
1331
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1332
+ function accountReady(s, providerId) {
1333
+ if (!s) return false;
1334
+ const p = PROVIDERS[providerId];
1335
+ if (p.hasBilling && (s.billingState === "error" || s.billing?.error)) return true;
1336
+ if (p.hasUsage && s.summaryState !== "ready" && s.summaryState !== "error") return false;
1337
+ if (p.hasBilling && s.billingState !== "ready" && s.billingState !== "error") return false;
1338
+ return true;
1339
+ }
1340
+ function statsReadyInput(s) {
1341
+ if (!s) return void 0;
1342
+ return {
1343
+ summaryState: s.dashboard ? "ready" : "pending",
1344
+ billingState: s.billing?.error ? "error" : s.billing ? "ready" : "pending",
1345
+ billing: s.billing
1346
+ };
1347
+ }
1348
+ function groupTodayCost(items) {
1349
+ return items.reduce((sum, s) => {
1350
+ const d = s?.dashboard;
1351
+ return sum + (d?.today.cost ?? 0);
1352
+ }, 0);
1353
+ }
1354
+ function headlineFor(group, items) {
1355
+ const meta = PROVIDERS[group.provider];
1356
+ if (meta.hasUsage) return `${currency(groupTodayCost(items))} today`;
1357
+ const billing = items.map((s) => s?.billing).find(Boolean);
1358
+ if (!billing) return "no data";
1359
+ if (billing.error) return billing.error;
1360
+ const m = billing.metrics[0];
1361
+ if (m) return metricValueText(m);
1362
+ return billing.plan ?? "no data";
1363
+ }
1364
+ var STAGGER_FRAMES = 2;
1365
+ function LoadingView({ groups, stats, cols, rows, readyInput }) {
1366
+ const resolveReady = readyInput ?? ((id) => statsReadyInput(stats.get(id)));
1367
+ const sp = glyphs().spinner;
1368
+ const [frame, setFrame] = useState2(0);
1369
+ useEffect2(() => {
1370
+ const id = setInterval(() => setFrame((f) => f + 1), 80);
1371
+ return () => clearInterval(id);
1372
+ }, []);
1373
+ const nameW = Math.min(13, groups.reduce((w, g) => Math.max(w, PROVIDERS[g.provider].name.length), 0));
1374
+ const readyCount = groups.filter((g) => g.accounts.every((a) => accountReady(resolveReady(a.id), g.provider))).length;
1375
+ const maxRows = Math.max(1, rows - 7);
1376
+ const visible = groups.slice(0, maxRows);
1377
+ const hidden = groups.length - visible.length;
1378
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1379
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "greenBright", children: [
1380
+ glyphs().dotSel,
1381
+ " tokmon"
1382
+ ] }),
1383
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1384
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1385
+ "Detecting installed tools",
1386
+ glyphs().ellipsis
1387
+ ] }),
1388
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1389
+ " ",
1390
+ groups.length,
1391
+ " found"
1392
+ ] })
1393
+ ] }),
1394
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1395
+ visible.map((g, i) => {
1396
+ const meta = PROVIDERS[g.provider];
1397
+ const items = g.accounts.map((a) => stats.get(a.id));
1398
+ const ready = g.accounts.every((a) => accountReady(resolveReady(a.id), g.provider));
1399
+ const errored = items.some((s) => !!s?.billing?.error);
1400
+ const revealed = frame >= i * STAGGER_FRAMES;
1401
+ const name = truncateName(meta.name, nameW);
1402
+ const namePad = " ".repeat(Math.max(0, nameW - name.length));
1403
+ if (!revealed) {
1404
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1405
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1406
+ glyphs().dot,
1407
+ " "
1408
+ ] }),
1409
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1410
+ name,
1411
+ namePad
1412
+ ] })
1413
+ ] }, g.provider);
1414
+ }
1415
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1416
+ /* @__PURE__ */ jsxs6(Text6, { color: meta.color, children: [
1417
+ glyphs().dot,
1418
+ " "
1419
+ ] }),
1420
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: meta.color, children: name }),
1421
+ /* @__PURE__ */ jsx6(Text6, { children: namePad }),
1422
+ /* @__PURE__ */ jsx6(Text6, { children: " " }),
1423
+ errored ? /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
1424
+ glyphs().warn,
1425
+ " "
1426
+ ] }) : ready ? /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
1427
+ glyphs().check,
1428
+ " "
1429
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
1430
+ sp[frame % sp.length],
1431
+ " "
1432
+ ] }),
1433
+ errored ? /* @__PURE__ */ jsx6(Text6, { color: "red", children: headlineFor(g, items) }) : ready ? /* @__PURE__ */ jsx6(Text6, { children: headlineFor(g, items) }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1434
+ "loading",
1435
+ glyphs().ellipsis
1436
+ ] })
1437
+ ] }, g.provider);
1438
+ }),
1439
+ hidden > 0 && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1440
+ "+",
1441
+ hidden,
1442
+ " more"
1443
+ ] })
1444
+ ] }),
1445
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1446
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1447
+ "loading ",
1448
+ readyCount,
1449
+ " / ",
1450
+ groups.length
1451
+ ] }),
1452
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1453
+ " ",
1454
+ glyphs().middot,
1455
+ " W opens the web dashboard once loaded"
1456
+ ] })
1457
+ ] })
1458
+ ] });
1459
+ }
1460
+
1461
+ // src/ui/app-layout.logic.ts
1462
+ function deriveSlots(accounts) {
1463
+ return accounts.length > 1 ? [{ id: null, name: "All", color: "whiteBright" }, ...accounts.map((a) => ({ id: a.id, name: a.name, color: a.color }))] : accounts.map((a) => ({ id: a.id, name: a.name, color: a.color }));
1464
+ }
1465
+ function findActiveSlot(slots, activeAccountId) {
1466
+ if (activeAccountId === null) return { activeSlotIdx: 0, focusId: slots[0]?.id ?? null };
1467
+ const i = slots.findIndex((s) => s.id === activeAccountId);
1468
+ const activeSlotIdx = i < 0 ? 0 : i;
1469
+ return { activeSlotIdx, focusId: slots[activeSlotIdx]?.id ?? null };
1470
+ }
1471
+ function computeChrome(slots, cols, rows) {
1472
+ const hasStrip = slots.length > 1;
1473
+ const stripChipW = (s) => 2 + 2 + truncateName(s.name, 16).length + 2;
1474
+ const stripChars = slots.reduce((sum, s) => sum + stripChipW(s), 0);
1475
+ const stripLines = hasStrip ? Math.max(1, Math.ceil(stripChars / Math.max(1, cols - 4 - 7))) : 0;
1476
+ const headerRows = cols < 70 ? 2 : 1;
1477
+ const CHROME = 2 + headerRows + 3 + (hasStrip ? 1 + stripLines : 0) + 2 + 2;
1478
+ const gridBudget = Math.max(1, rows - CHROME);
1479
+ return { hasStrip, stripChipW, stripChars, stripLines, headerRows, CHROME, gridBudget };
1480
+ }
1481
+
1482
+ // src/ui/resizing.tsx
1483
+ import { Box as Box7, Text as Text7 } from "ink";
1484
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1485
+ function ResizingView({ cols, rows }) {
1486
+ return /* @__PURE__ */ jsx7(Box7, { width: cols, height: rows, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1487
+ glyphs().dotSel,
1488
+ " resizing\u2026 ",
1489
+ /* @__PURE__ */ jsx7(Text7, { color: "greenBright", children: cols }),
1490
+ "\xD7",
1491
+ /* @__PURE__ */ jsx7(Text7, { color: "greenBright", children: rows })
1492
+ ] }) });
1493
+ }
1494
+
1495
+ // src/ui/account-strip.tsx
1496
+ import { memo as memo5 } from "react";
1497
+ import { Box as Box8, Text as Text8 } from "ink";
1498
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1499
+ var AccountStrip = memo5(function AccountStrip2({ slots, activeIdx, onSelect }) {
1500
+ return /* @__PURE__ */ jsx8(Box8, { flexWrap: "wrap", children: slots.map((s, i) => {
1501
+ const active = i === activeIdx;
1502
+ const dot = s.id === null ? glyphs().dotAll : glyphs().dot;
1503
+ const label = truncateName(s.name, 16);
1504
+ return /* @__PURE__ */ jsxs8(ClickableBox, { onClick: () => onSelect(i), marginRight: 2, children: [
1505
+ /* @__PURE__ */ jsx8(Text8, { dimColor: !active, children: i }),
1506
+ /* @__PURE__ */ jsx8(Text8, { children: " " }),
1507
+ /* @__PURE__ */ jsx8(Text8, { color: s.color, bold: active, dimColor: !active, children: dot }),
1508
+ /* @__PURE__ */ jsx8(Text8, { children: " " }),
1509
+ active ? /* @__PURE__ */ jsx8(Text8, { bold: true, color: s.color, children: label }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: label })
1510
+ ] }, s.id ?? "__all__");
1511
+ }) });
1512
+ });
1513
+
1514
+ // src/ui/footer.tsx
1515
+ import { memo as memo6 } from "react";
1516
+ import { Box as Box9, Text as Text9, Transform } from "ink";
1517
+
1518
+ // src/ui/terminal.ts
1519
+ import { spawn } from "child_process";
1520
+ import { appendFileSync as appendFileSync2 } from "fs";
1521
+ var IS_TTY = process.stdin.isTTY === true;
1522
+ var REPO_URL = "https://github.com/DavidIlie/tokmon";
1523
+ var SITE_URL = "https://davidilie.com";
1524
+ var IS_APPLE_TERMINAL = process.env.TERM_PROGRAM === "Apple_Terminal";
1525
+ function detectHyperlinks(env, isTTY) {
1526
+ const force = env.FORCE_HYPERLINK;
1527
+ if (force != null && force !== "") return force !== "0" && force.toLowerCase() !== "false";
1528
+ if (!isTTY || env.TERM === "dumb" || env.NO_HYPERLINK) return false;
1529
+ if (env.WT_SESSION || env.ConEmuANSI === "ON" || env.KITTY_WINDOW_ID || env.TERM === "xterm-kitty") return true;
1530
+ if (env.KONSOLE_VERSION || env.TERMINAL_EMULATOR === "JetBrains-JediTerm") return true;
1531
+ if (env.VTE_VERSION && Number(env.VTE_VERSION) >= 5e3) return true;
1532
+ const tp = env.TERM_PROGRAM;
1533
+ if (tp) {
1534
+ const [maj, min] = (env.TERM_PROGRAM_VERSION ?? "").split(".").map((n) => Number(n) || 0);
1535
+ if (tp === "iTerm.app") return maj > 3 || maj === 3 && min >= 1;
1536
+ if (tp === "vscode" || tp === "WezTerm" || tp === "ghostty" || tp === "Hyper" || tp === "Tabby" || tp === "rio") return true;
1537
+ }
1538
+ return false;
1539
+ }
1540
+ var HYPERLINKS = detectHyperlinks(process.env, process.stdout.isTTY === true);
1541
+ function openUrl(url) {
1542
+ if (process.env.TOKMON_OPENLOG) {
1543
+ try {
1544
+ appendFileSync2(process.env.TOKMON_OPENLOG, url + "\n");
1545
+ } catch {
1546
+ }
1547
+ return;
1548
+ }
1549
+ try {
1550
+ if (process.platform === "darwin") spawn("open", [url], { stdio: "ignore", detached: true }).unref();
1551
+ else if (process.platform === "win32") spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
1552
+ else spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
1553
+ } catch {
1554
+ }
1555
+ }
1556
+ function osc8(text, url) {
1557
+ if (!HYPERLINKS) return text;
1558
+ return `\x1B]8;;${url}\x07${text}\x1B]8;;\x07`;
1559
+ }
1560
+
1561
+ // src/ui/footer.tsx
1562
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1563
+ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
1564
+ const inner = cols - 4;
1565
+ const BASE = "by David Ilie (davidilie.com) \xB7 O=repo W=web s=settings q=quit".length;
1566
+ const optHint = (glyphs().shift === "\u21E7" ? "\u2325" : "opt") + "-click links ";
1567
+ const OPT = IS_APPLE_TERMINAL ? optHint.length : 0;
1568
+ const JUMP = "0-9=jump a/A=cycle ".length;
1569
+ const PAGE = "scroll=page ".length;
1570
+ const showOpt = IS_APPLE_TERMINAL && inner >= BASE + OPT;
1571
+ const showJump = hasAccounts && inner >= BASE + (showOpt ? OPT : 0) + JUMP + (paginated ? PAGE : 0);
1572
+ const showPage = paginated && inner >= BASE + (showOpt ? OPT : 0) + (showJump ? JUMP : 0) + PAGE;
1573
+ return /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexWrap: "nowrap", children: [
1574
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "by " }),
1575
+ /* @__PURE__ */ jsx9(LinkBox, { onClick: () => openUrl(REPO_URL), children: /* @__PURE__ */ jsx9(Transform, { transform: (s) => osc8(s, REPO_URL), children: /* @__PURE__ */ jsx9(Text9, { underline: true, children: "David Ilie" }) }) }),
1576
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " (" }),
1577
+ /* @__PURE__ */ jsx9(LinkBox, { onClick: () => openUrl(SITE_URL), children: /* @__PURE__ */ jsx9(Transform, { transform: (s) => osc8(s, SITE_URL), children: /* @__PURE__ */ jsx9(Text9, { color: "cyan", underline: true, children: "davidilie.com" }) }) }),
1578
+ /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
1579
+ ") ",
1580
+ glyphs().middot,
1581
+ " O=repo W=web s=settings "
1582
+ ] }),
1583
+ showOpt && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: optHint }),
1584
+ showJump && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "0-9=jump a/A=cycle " }),
1585
+ showPage && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "scroll=page " }),
1586
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "q=quit" })
1587
+ ] });
1588
+ });
1589
+
1590
+ // src/ui/tiny-fallback.tsx
1591
+ import { Box as Box10, Text as Text10 } from "ink";
1592
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1593
+ function TinyFallback({ groups, stats, rows, cols }) {
1594
+ const maxLines = Math.max(1, rows - 4);
1595
+ const visible = groups.slice(0, maxLines);
1596
+ const hidden = groups.length - visible.length;
1597
+ const w = Math.max(8, cols - 2);
1598
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, height: rows, overflow: "hidden", children: [
1599
+ /* @__PURE__ */ jsxs10(Text10, { bold: true, color: "greenBright", children: [
1600
+ glyphs().dotSel,
1601
+ " tokmon"
1602
+ ] }),
1603
+ groups.length === 0 ? /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
1604
+ "No providers ",
1605
+ glyphs().emDash,
1606
+ " s=settings"
1607
+ ] }) : visible.map((g) => /* @__PURE__ */ jsx10(TinyRow, { provider: g.provider, accounts: g.accounts, stats, width: w }, g.provider)),
1608
+ hidden > 0 && /* @__PURE__ */ jsxs10(Text10, { dimColor: true, children: [
1609
+ "+",
1610
+ hidden,
1611
+ " more (enlarge terminal)"
1612
+ ] }),
1613
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1 }),
1614
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "s=settings q=quit" })
1615
+ ] });
1616
+ }
1617
+ function TinyRow({ provider, accounts, stats, width }) {
1618
+ const meta = PROVIDERS[provider];
1619
+ const dashboards = accounts.map((a) => stats.get(a.id)?.dashboard).filter(Boolean);
1620
+ const billings = accounts.map((a) => stats.get(a.id)?.billing).filter(Boolean);
1621
+ const todayCost = dashboards.reduce((sum, d) => sum + (d?.today.cost ?? 0), 0);
1622
+ const pctMetric = billings.flatMap((b) => b?.metrics ?? []).find((m) => m.format.kind === "percent");
1623
+ const detail = meta.hasUsage ? `${currency(todayCost)} today` : pctMetric ? `${Math.round(pctMetric.used)}%` : "billing";
1624
+ const name = truncateName(meta.name, Math.max(4, width - 18));
1625
+ return /* @__PURE__ */ jsxs10(Box10, { width, children: [
1626
+ /* @__PURE__ */ jsxs10(Text10, { color: meta.color, children: [
1627
+ glyphs().dot,
1628
+ " "
1629
+ ] }),
1630
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: meta.color, children: name }),
1631
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1 }),
1632
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: detail })
1633
+ ] });
1634
+ }
1635
+
1636
+ // src/client/use-daemon.ts
1637
+ import { useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState3 } from "react";
1638
+
1639
+ // src/client/daemon-rpc-client.ts
1640
+ import { Cause, Context, Duration, Effect, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
1641
+ import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
1642
+ import * as Socket from "effect/unstable/socket/Socket";
1643
+ var TokmonRpcClient = class extends Context.Service()(
1644
+ "tokmon/client/DaemonRpcClient/TokmonRpcClient"
1645
+ ) {
1646
+ };
1647
+ var dynamicImport = new Function("specifier", "return import(specifier)");
1648
+ function toWsUrl(baseUrl, token) {
1649
+ const base = typeof window === "undefined" ? void 0 : window.location.origin;
1650
+ const url = new URL(baseUrl, base);
1651
+ if (url.protocol === "http:") url.protocol = "ws:";
1652
+ else if (url.protocol === "https:") url.protocol = "wss:";
1653
+ if (url.protocol !== "ws:" && url.protocol !== "wss:") {
1654
+ throw new Error(`unsupported daemon RPC protocol: ${url.protocol}`);
1655
+ }
1656
+ url.pathname = TOKMON_WS_PATH;
1657
+ if (token) url.searchParams.set("wsToken", token);
1658
+ return url.toString();
1659
+ }
1660
+ function shouldUseNodeTransport(transport) {
1661
+ if (transport === "node") return true;
1662
+ if (transport === "browser") return false;
1663
+ return typeof window === "undefined";
1664
+ }
1665
+ async function socketLayerFor(url, transport) {
1666
+ if (shouldUseNodeTransport(transport)) {
1667
+ const NodeSocket = await dynamicImport("@effect/platform-node/NodeSocket");
1668
+ return NodeSocket.layerWebSocket(url);
1669
+ }
1670
+ return Socket.layerWebSocket(url).pipe(
1671
+ Layer.provide(Socket.layerWebSocketConstructorGlobal)
1672
+ );
1673
+ }
1674
+ function retryPolicy(options) {
1675
+ const attempts = options.reconnectAttempts ?? 5;
1676
+ const baseDelay = options.reconnectBaseDelayMs ?? 250;
1677
+ return Schedule.addDelay(
1678
+ Schedule.recurs(attempts),
1679
+ (retryCount) => Effect.succeed(Duration.millis(Math.min(baseDelay * (retryCount + 1), 2500)))
1680
+ );
1681
+ }
1682
+ function createDaemonRpcClient(baseUrl, options = {}) {
1683
+ const url = toWsUrl(baseUrl, options.wsToken);
1684
+ const fibers = /* @__PURE__ */ new Set();
1685
+ let session = null;
1686
+ let sessionPromise = null;
1687
+ let closed = false;
1688
+ const setConn = (state, error) => {
1689
+ options.onConn?.(state, error);
1690
+ };
1691
+ const makeProtocolLayer = async () => {
1692
+ const socketLayer = await socketLayerFor(url, options.transport);
1693
+ const connectionHooksLayer = Layer.succeed(
1694
+ RpcClient.ConnectionHooks,
1695
+ RpcClient.ConnectionHooks.of({
1696
+ onConnect: Effect.sync(() => {
1697
+ setConn("live");
1698
+ }),
1699
+ onDisconnect: Effect.sync(() => {
1700
+ if (!closed) setConn("reconnecting");
1701
+ })
1702
+ })
1703
+ );
1704
+ return Layer.effect(
1705
+ RpcClient.Protocol,
1706
+ RpcClient.makeProtocolSocket({
1707
+ retryPolicy: retryPolicy(options),
1708
+ retryTransientErrors: true
1709
+ })
1710
+ ).pipe(
1711
+ Layer.provide(Layer.mergeAll(socketLayer, RpcSerialization.layerJson, connectionHooksLayer))
1712
+ );
1713
+ };
1714
+ const ensureSession = async () => {
1715
+ if (closed) throw new Error("daemon RPC client is closed");
1716
+ if (session) return session;
1717
+ if (sessionPromise) return sessionPromise;
1718
+ setConn("connecting");
1719
+ sessionPromise = (async () => {
1720
+ let runtime;
1721
+ try {
1722
+ const protocolLayer = await makeProtocolLayer();
1723
+ const clientLayer = Layer.effect(
1724
+ TokmonRpcClient,
1725
+ RpcClient.make(TokmonRpcGroup)
1726
+ ).pipe(
1727
+ Layer.provide(protocolLayer)
1728
+ );
1729
+ runtime = ManagedRuntime.make(clientLayer);
1730
+ await runtime.runPromise(TokmonRpcClient.asEffect());
1731
+ if (closed) {
1732
+ await runtime.dispose();
1733
+ throw new Error("daemon RPC client is closed");
1734
+ }
1735
+ session = { runtime };
1736
+ return session;
1737
+ } catch (error) {
1738
+ sessionPromise = null;
1739
+ await runtime?.dispose().catch(() => {
1740
+ });
1741
+ if (!closed) setConn("error", error);
1742
+ throw error;
1743
+ }
1744
+ })();
1745
+ return sessionPromise;
1746
+ };
1747
+ const run = async (effectFor) => {
1748
+ const active = await ensureSession();
1749
+ try {
1750
+ return await active.runtime.runPromise(
1751
+ TokmonRpcClient.use((client) => effectFor(client))
1752
+ );
1753
+ } catch (error) {
1754
+ if (!closed) setConn("error", error);
1755
+ throw error;
1756
+ }
1757
+ };
1758
+ const subscribe = (streamFor, onValue) => {
1759
+ if (closed) return () => {
1760
+ };
1761
+ let fiber = null;
1762
+ let unsubscribed = false;
1763
+ void (async () => {
1764
+ try {
1765
+ const active = await ensureSession();
1766
+ if (closed || unsubscribed) return;
1767
+ fiber = active.runtime.runFork(
1768
+ TokmonRpcClient.use(
1769
+ (client) => streamFor(client).pipe(
1770
+ Stream.runForEach(
1771
+ (value) => Effect.sync(() => {
1772
+ try {
1773
+ onValue(value);
1774
+ } catch {
1775
+ }
1776
+ })
1777
+ )
1778
+ )
1779
+ ).pipe(Effect.catchCause(
1780
+ (cause) => Effect.sync(() => {
1781
+ if (!closed && !unsubscribed) setConn("error", Cause.squash(cause));
1782
+ })
1783
+ ))
1784
+ );
1785
+ fibers.add(fiber);
1786
+ fiber.addObserver(() => {
1787
+ if (fiber) fibers.delete(fiber);
1788
+ });
1789
+ } catch (error) {
1790
+ if (!closed && !unsubscribed) setConn("error", error);
1791
+ }
1792
+ })();
1793
+ return () => {
1794
+ unsubscribed = true;
1795
+ if (!fiber) return;
1796
+ fibers.delete(fiber);
1797
+ void (session?.runtime.runPromise(Fiber.interrupt(fiber)) ?? Effect.runPromise(Fiber.interrupt(fiber))).catch(() => {
1798
+ });
1799
+ };
1800
+ };
1801
+ return {
1802
+ getConfig: () => run((client) => client[TOKMON_WS_METHODS.getConfig]({})),
1803
+ setConfig: (config) => run((client) => client[TOKMON_WS_METHODS.setConfig](config)),
1804
+ refresh: (scope = "all") => run((client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
1805
+ browseFs: (path) => run((client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
1806
+ subscribeSnapshot: (onSnapshot) => subscribe((client) => client[TOKMON_WS_METHODS.snapshot]({}), onSnapshot),
1807
+ subscribeConfig: (onConfig) => subscribe((client) => client[TOKMON_WS_METHODS.config]({}), onConfig),
1808
+ async close() {
1809
+ if (closed) return;
1810
+ closed = true;
1811
+ setConn("closed");
1812
+ const active = [...fibers];
1813
+ fibers.clear();
1814
+ const activeSession = session ?? await sessionPromise?.catch(() => null) ?? null;
1815
+ await Promise.all(active.map(
1816
+ (fiber) => (activeSession?.runtime.runPromise(Fiber.interrupt(fiber)) ?? Effect.runPromise(Fiber.interrupt(fiber))).catch(() => {
1817
+ })
1818
+ ));
1819
+ session = null;
1820
+ sessionPromise = null;
1821
+ if (activeSession) {
1822
+ await activeSession.runtime.dispose().catch(() => {
1823
+ });
1824
+ }
1825
+ }
1826
+ };
1827
+ }
1828
+
1829
+ // src/client/use-daemon.ts
1830
+ function useDaemon(baseUrl, wsToken) {
1831
+ const [snapshot, setSnapshot] = useState3(null);
1832
+ const [conn, setConn] = useState3("connecting");
1833
+ const [config, setConfigState] = useState3(null);
1834
+ const client = useMemo(() => {
1835
+ if (!baseUrl || !wsToken) return null;
1836
+ return createDaemonRpcClient(baseUrl, {
1837
+ transport: "node",
1838
+ wsToken,
1839
+ onConn: (state) => {
1840
+ if (state !== "closed") setConn(state);
1841
+ }
1842
+ });
1843
+ }, [baseUrl, wsToken]);
1844
+ const clientRef = useRef2(client);
1845
+ clientRef.current = client;
1846
+ useEffect3(() => {
1847
+ if (!client) return;
1848
+ const unsubSnapshot = client.subscribeSnapshot(setSnapshot);
1849
+ const unsubConfig = client.subscribeConfig(setConfigState);
1850
+ return () => {
1851
+ unsubSnapshot();
1852
+ unsubConfig();
1853
+ void client.close();
1854
+ };
1855
+ }, [client]);
1856
+ const requireClient = () => {
1857
+ if (!clientRef.current) throw new Error("daemon RPC client is unavailable");
1858
+ return clientRef.current;
1859
+ };
1860
+ return {
1861
+ snapshot,
1862
+ conn,
1863
+ config,
1864
+ setConfig: (next) => requireClient().setConfig(next),
1865
+ refresh: (scope) => requireClient().refresh(scope),
1866
+ browse: (path) => requireClient().browseFs(path)
1867
+ };
1868
+ }
1869
+
1870
+ // src/client/snapshot-adapter.ts
1871
+ function indexById(snapshot) {
1872
+ const m = /* @__PURE__ */ new Map();
1873
+ if (snapshot) for (const a of snapshot.accounts) m.set(a.id, a);
1874
+ return m;
1875
+ }
1876
+ function toStatsMap(snapshot, accounts) {
1877
+ const byId = indexById(snapshot);
1878
+ const out = /* @__PURE__ */ new Map();
1879
+ for (const account of accounts) {
1880
+ const wa = byId.get(account.id);
1881
+ out.set(account.id, {
1882
+ account,
1883
+ dashboard: wa?.dashboard ?? null,
1884
+ billing: wa?.billing ?? null
1885
+ });
1886
+ }
1887
+ return out;
1888
+ }
1889
+ function toCursorRows(snapshot, accountId) {
1890
+ if (!snapshot || !accountId) return null;
1891
+ const wa = snapshot.accounts.find((a) => a.id === accountId);
1892
+ if (!wa) return null;
1893
+ if (wa.billingState === "pending") return null;
1894
+ const spend = wa.billing?.modelSpend;
1895
+ if (!spend) return [];
1896
+ return spend.map((m) => ({ name: m.name, usd: m.usd, requests: m.requests }));
1897
+ }
1898
+ function pickTable(snapshot, accountIds) {
1899
+ if (!snapshot) return null;
1900
+ const byId = indexById(snapshot);
1901
+ const tables = [];
1902
+ for (const id of accountIds) {
1903
+ const t = byId.get(id)?.table;
1904
+ if (t) tables.push(t);
1905
+ }
1906
+ return coalesceTables(tables);
1907
+ }
1908
+
1909
+ // src/app.logic.ts
1910
+ var TABS = ["Dashboard", "Table"];
1911
+ var VIEWS = ["Daily", "Weekly", "Monthly"];
1912
+ var SORTS = [
1913
+ { label: "date", dir: "up" },
1914
+ { label: "date", dir: "down" },
1915
+ { label: "cost", dir: "up" },
1916
+ { label: "cost", dir: "down" }
1917
+ ];
1918
+ var CURSOR_SORTS = [
1919
+ { label: "cost", dir: "down" },
1920
+ { label: "amount", dir: "down" },
1921
+ { label: "model", dir: null }
1922
+ ];
1923
+ var acctKey = (a) => `${a.id}:${a.homeDir ?? ""}`;
1924
+ var clampCaret = (caret, len) => Math.max(0, Math.min(caret, len));
1925
+ function spliceInsert(value, caret, text) {
1926
+ const c = clampCaret(caret, value.length);
1927
+ return { value: value.slice(0, c) + text + value.slice(c), caret: c + text.length };
1928
+ }
1929
+ function spliceBackspace(value, caret) {
1930
+ const c = clampCaret(caret, value.length);
1931
+ if (c === 0) return { value, caret: 0 };
1932
+ return { value: value.slice(0, c - 1) + value.slice(c), caret: c - 1 };
1933
+ }
1934
+ function applyStartup(c, cliInterval) {
1935
+ if (cliInterval) c = { ...c, interval: cliInterval / 1e3 };
1936
+ if (c.defaultFocus === "all") c = { ...c, activeAccountId: null };
1937
+ return c;
1938
+ }
1939
+ function upsert(prev, account, patch) {
1940
+ const next = new Map(prev);
1941
+ const cur = next.get(account.id) ?? { account, dashboard: null, billing: null };
1942
+ next.set(account.id, { ...cur, account, ...patch });
1943
+ return next;
1944
+ }
1945
+ async function fetchScopeTable(scope, tz) {
1946
+ const tables = await Promise.all(scope.map(async (acc) => {
1947
+ const provider = PROVIDERS[acc.providerId];
1948
+ if (!provider.fetchTable) return null;
1949
+ try {
1950
+ return await provider.fetchTable(acc, tz);
1951
+ } catch {
1952
+ return null;
1953
+ }
1954
+ }));
1955
+ const valid = tables.filter((t) => t !== null);
1956
+ return coalesceTables(valid);
1957
+ }
1958
+ function sortLabel(entry) {
1959
+ if (entry.dir === "up") return `${entry.label} ${glyphs().arrowU}`;
1960
+ if (entry.dir === "down") return `${entry.label} ${glyphs().arrowD}`;
1961
+ return entry.label;
1962
+ }
1963
+ function sortRows(rows, sortIdx) {
1964
+ const sorted = [...rows];
1965
+ switch (sortIdx % SORTS.length) {
1966
+ case 0:
1967
+ return sorted.sort((a, b) => a.label.localeCompare(b.label));
1968
+ case 1:
1969
+ return sorted.sort((a, b) => b.label.localeCompare(a.label));
1970
+ case 2:
1971
+ return sorted.sort((a, b) => a.cost - b.cost);
1972
+ case 3:
1973
+ return sorted.sort((a, b) => b.cost - a.cost);
1974
+ default:
1975
+ return sorted;
1976
+ }
1977
+ }
1978
+ function filterTokenRows(rows, q) {
1979
+ if (!q) return rows;
1980
+ const s = q.toLowerCase();
1981
+ return rows.filter((r) => r.label.toLowerCase().includes(s) || r.models.some((m) => m.toLowerCase().includes(s)));
1982
+ }
1983
+ function filterCursorRows(rows, q) {
1984
+ if (!q) return rows;
1985
+ const s = q.toLowerCase();
1986
+ return rows.filter((r) => r.name.toLowerCase().includes(s));
1987
+ }
1988
+ function sortCursorRows(rows, sortIdx) {
1989
+ const out = [...rows];
1990
+ switch (sortIdx % CURSOR_SORTS.length) {
1991
+ case 1:
1992
+ return out.sort((a, b) => b.requests - a.requests);
1993
+ case 2:
1994
+ return out.sort((a, b) => a.name.localeCompare(b.name));
1995
+ default:
1996
+ return out.sort((a, b) => b.usd - a.usd);
1997
+ }
1998
+ }
1999
+
2000
+ // src/ui/keybindings.ts
2001
+ function handleKey(input, key, ctx) {
2002
+ const {
2003
+ showPicker,
2004
+ pickerProviders,
2005
+ onboardCursor,
2006
+ setOnboardCursor,
2007
+ toggleOnboard,
2008
+ confirmOnboarding,
2009
+ exit,
2010
+ showSettings,
2011
+ accountForm,
2012
+ setAccountForm,
2013
+ commitAccountForm,
2014
+ cycleFormField,
2015
+ cycleProvider,
2016
+ cycleColor,
2017
+ isPrintable,
2018
+ insertText,
2019
+ tzEdit,
2020
+ setTzEdit,
2021
+ setTzError,
2022
+ updateConfig,
2023
+ setTzCaret,
2024
+ tzValueRef,
2025
+ tzCaretRef,
2026
+ tab,
2027
+ searchMode,
2028
+ setSearchMode,
2029
+ search,
2030
+ setSearch,
2031
+ setSearchCaret,
2032
+ searchValueRef,
2033
+ searchCaretRef,
2034
+ showLoader,
2035
+ configReady,
2036
+ toggleWeb,
2037
+ settingsCursor,
2038
+ settingsTab,
2039
+ setSettingsTab,
2040
+ setShowSettings,
2041
+ cfg,
2042
+ trackedAccountRows,
2043
+ moveAccount,
2044
+ setSettingsCursor,
2045
+ toggleProvider,
2046
+ openEditAccount,
2047
+ openConfigureAccount,
2048
+ deleteAccount,
2049
+ openAddAccount,
2050
+ cycleAccount,
2051
+ setTab,
2052
+ resetView,
2053
+ slots,
2054
+ dashPaginated,
2055
+ dashPageCount,
2056
+ setDashPage,
2057
+ cycleTableProvider,
2058
+ setExpanded,
2059
+ setSort,
2060
+ SORTS_FOR,
2061
+ tableIsCursor,
2062
+ setView,
2063
+ cursor,
2064
+ rowCountRef,
2065
+ rows,
2066
+ setCursor,
2067
+ clampRow
2068
+ } = ctx;
2069
+ if (showPicker) {
2070
+ if (input === "q") {
2071
+ exit();
2072
+ return;
2073
+ }
2074
+ const startIdx = pickerProviders.length;
2075
+ if (key.upArrow) {
2076
+ setOnboardCursor((c) => Math.max(0, c - 1));
2077
+ return;
2078
+ }
2079
+ if (key.downArrow) {
2080
+ setOnboardCursor((c) => Math.min(startIdx, c + 1));
2081
+ return;
2082
+ }
2083
+ if (input === " ") {
2084
+ toggleOnboard(onboardCursor);
2085
+ return;
2086
+ }
2087
+ if (key.return) {
2088
+ if (onboardCursor === startIdx) confirmOnboarding();
2089
+ else toggleOnboard(onboardCursor);
2090
+ return;
2091
+ }
2092
+ return;
2093
+ }
2094
+ if (showSettings && accountForm) {
2095
+ if (key.escape) {
2096
+ setAccountForm(null);
2097
+ return;
2098
+ }
2099
+ if (key.ctrl && input === "s") {
2100
+ commitAccountForm();
2101
+ return;
2102
+ }
2103
+ if (key.tab) {
2104
+ cycleFormField(key.shift ? -1 : 1);
2105
+ return;
2106
+ }
2107
+ if (key.upArrow) {
2108
+ cycleFormField(-1);
2109
+ return;
2110
+ }
2111
+ if (key.downArrow) {
2112
+ cycleFormField(1);
2113
+ return;
2114
+ }
2115
+ if (accountForm.field === "provider") {
2116
+ if (key.leftArrow) {
2117
+ cycleProvider(-1);
2118
+ return;
2119
+ }
2120
+ if (key.rightArrow) {
2121
+ cycleProvider(1);
2122
+ return;
2123
+ }
2124
+ if (key.return) {
2125
+ setAccountForm((f) => f && { ...f, field: "name", caret: f.name.length });
2126
+ return;
2127
+ }
2128
+ return;
2129
+ }
2130
+ if (accountForm.field === "color") {
2131
+ if (key.leftArrow) {
2132
+ cycleColor(-1);
2133
+ return;
2134
+ }
2135
+ if (key.rightArrow) {
2136
+ cycleColor(1);
2137
+ return;
2138
+ }
2139
+ if (key.return) {
2140
+ commitAccountForm();
2141
+ return;
2142
+ }
2143
+ return;
2144
+ }
2145
+ const tf = accountForm.field;
2146
+ if (key.leftArrow) {
2147
+ setAccountForm((f) => f && { ...f, caret: clampCaret(f.caret - 1, f[tf].length) });
2148
+ return;
2149
+ }
2150
+ if (key.rightArrow) {
2151
+ setAccountForm((f) => f && { ...f, caret: clampCaret(f.caret + 1, f[tf].length) });
2152
+ return;
2153
+ }
2154
+ if (key.ctrl && input === "a") {
2155
+ setAccountForm((f) => f && { ...f, caret: 0 });
2156
+ return;
2157
+ }
2158
+ if (key.ctrl && input === "e") {
2159
+ setAccountForm((f) => f && { ...f, caret: f[tf].length });
2160
+ return;
2161
+ }
2162
+ if (key.return) {
2163
+ if (tf === "name" && accountForm.name.trim() === "") {
2164
+ setAccountForm((f) => f && { ...f, error: "Name required", caret: f.name.length });
2165
+ return;
2166
+ }
2167
+ setAccountForm((f) => f && { ...f, field: tf === "name" ? "homeDir" : "color", caret: tf === "name" ? f.homeDir.length : f.caret });
2168
+ return;
2169
+ }
2170
+ if (key.backspace || key.delete) {
2171
+ setAccountForm((f) => {
2172
+ if (!f || f.field !== "name" && f.field !== "homeDir") return f;
2173
+ const r = spliceBackspace(f[f.field], f.caret);
2174
+ return { ...f, [f.field]: r.value, caret: r.caret, error: null };
2175
+ });
2176
+ return;
2177
+ }
2178
+ if (isPrintable(input, key)) {
2179
+ const clean = sanitizeTyped(input);
2180
+ if (clean) insertText(clean);
2181
+ }
2182
+ return;
2183
+ }
2184
+ if (showSettings && tzEdit !== null) {
2185
+ if (key.escape) {
2186
+ setTzEdit(null);
2187
+ setTzError(null);
2188
+ return;
2189
+ }
2190
+ if (key.return) {
2191
+ const val = tzEdit.trim();
2192
+ if (val === "" || val.toLowerCase() === "system") {
2193
+ updateConfig((c) => ({ ...c, timezone: null }));
2194
+ setTzEdit(null);
2195
+ setTzError(null);
2196
+ } else if (isValidTimezone(val)) {
2197
+ updateConfig((c) => ({ ...c, timezone: val }));
2198
+ setTzEdit(null);
2199
+ setTzError(null);
2200
+ } else {
2201
+ setTzError(`Invalid: ${val}`);
2202
+ }
2203
+ return;
2204
+ }
2205
+ if (key.leftArrow) {
2206
+ setTzCaret((c) => clampCaret(c - 1, tzEdit.length));
2207
+ return;
2208
+ }
2209
+ if (key.rightArrow) {
2210
+ setTzCaret((c) => clampCaret(c + 1, tzEdit.length));
2211
+ return;
2212
+ }
2213
+ if (key.ctrl && input === "a") {
2214
+ setTzCaret(0);
2215
+ return;
2216
+ }
2217
+ if (key.ctrl && input === "e") {
2218
+ setTzCaret(tzEdit.length);
2219
+ return;
2220
+ }
2221
+ if (key.backspace || key.delete) {
2222
+ const r = spliceBackspace(tzValueRef.current, tzCaretRef.current);
2223
+ tzValueRef.current = r.value;
2224
+ tzCaretRef.current = r.caret;
2225
+ setTzEdit(r.value);
2226
+ setTzCaret(r.caret);
2227
+ setTzError(null);
2228
+ return;
2229
+ }
2230
+ if (isPrintable(input, key)) {
2231
+ const clean = sanitizeTyped(input);
2232
+ if (clean) insertText(clean);
2233
+ }
2234
+ return;
2235
+ }
2236
+ if (tab === 1 && searchMode) {
2237
+ if (key.return || key.escape) {
2238
+ setSearchMode(false);
2239
+ if (key.escape) {
2240
+ setSearch("");
2241
+ setSearchCaret(0);
2242
+ }
2243
+ return;
2244
+ }
2245
+ if (key.leftArrow) {
2246
+ setSearchCaret((c) => clampCaret(c - 1, search.length));
2247
+ return;
2248
+ }
2249
+ if (key.rightArrow) {
2250
+ setSearchCaret((c) => clampCaret(c + 1, search.length));
2251
+ return;
2252
+ }
2253
+ if (key.ctrl && input === "a") {
2254
+ setSearchCaret(0);
2255
+ return;
2256
+ }
2257
+ if (key.ctrl && input === "e") {
2258
+ setSearchCaret(search.length);
2259
+ return;
2260
+ }
2261
+ if (key.backspace || key.delete) {
2262
+ const r = spliceBackspace(searchValueRef.current, searchCaretRef.current);
2263
+ searchValueRef.current = r.value;
2264
+ searchCaretRef.current = r.caret;
2265
+ setSearch(r.value);
2266
+ setSearchCaret(r.caret);
2267
+ return;
2268
+ }
2269
+ if (isPrintable(input, key)) {
2270
+ const clean = sanitizeTyped(input);
2271
+ if (clean) insertText(clean);
2272
+ }
2273
+ return;
2274
+ }
2275
+ if (input === "q") {
2276
+ exit();
2277
+ return;
2278
+ }
2279
+ if (input === "O") {
2280
+ openUrl(REPO_URL);
2281
+ return;
2282
+ }
2283
+ if (input === "W" || input === "w" && tab !== 1 && !showSettings) {
2284
+ if (showLoader || !configReady) return;
2285
+ void toggleWeb();
2286
+ return;
2287
+ }
2288
+ if (showSettings) {
2289
+ if (key.escape || input === "s") {
2290
+ setShowSettings(false);
2291
+ return;
2292
+ }
2293
+ const switchSettingsTab = (dir) => {
2294
+ const idx = SETTINGS_TABS.indexOf(settingsTab);
2295
+ setSettingsTab(SETTINGS_TABS[(idx + dir + SETTINGS_TABS.length) % SETTINGS_TABS.length]);
2296
+ setSettingsCursor(-1);
2297
+ setTzEdit(null);
2298
+ setTzError(null);
2299
+ };
2300
+ const rowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "providers" ? PROVIDER_ORDER.length : trackedAccountRows.length + 1;
2301
+ if (key.tab) {
2302
+ switchSettingsTab(key.shift ? -1 : 1);
2303
+ return;
2304
+ }
2305
+ if (input === "[") {
2306
+ switchSettingsTab(-1);
2307
+ return;
2308
+ }
2309
+ if (input === "]") {
2310
+ switchSettingsTab(1);
2311
+ return;
2312
+ }
2313
+ if (settingsCursor < 0) {
2314
+ if (key.leftArrow) {
2315
+ switchSettingsTab(-1);
2316
+ return;
2317
+ }
2318
+ if (key.rightArrow) {
2319
+ switchSettingsTab(1);
2320
+ return;
2321
+ }
2322
+ if (key.downArrow || key.return) {
2323
+ setSettingsCursor(0);
2324
+ return;
2325
+ }
2326
+ if (key.upArrow) {
2327
+ setSettingsCursor(Math.max(0, rowCount - 1));
2328
+ return;
2329
+ }
2330
+ return;
2331
+ }
2332
+ const selectedAccountRow = settingsTab === "accounts" && settingsCursor >= 0 && settingsCursor < trackedAccountRows.length ? trackedAccountRows[settingsCursor] : null;
2333
+ if (selectedAccountRow?.source === "configured" && selectedAccountRow.explicitIndex !== void 0 && key.shift && (key.upArrow || key.downArrow)) {
2334
+ moveAccount(selectedAccountRow.explicitIndex, key.upArrow ? -1 : 1);
2335
+ return;
2336
+ }
2337
+ if (key.upArrow) {
2338
+ setSettingsCursor((c) => Math.max(-1, c - 1));
2339
+ return;
2340
+ }
2341
+ if (key.downArrow) {
2342
+ setSettingsCursor((c) => Math.min(rowCount - 1, c + 1));
2343
+ return;
2344
+ }
2345
+ if (settingsTab === "general") {
2346
+ if (settingsCursor === 0) {
2347
+ if (key.leftArrow) updateConfig((c) => ({ ...c, interval: Math.max(1, c.interval - 1) }));
2348
+ if (key.rightArrow) updateConfig((c) => ({ ...c, interval: c.interval + 1 }));
2349
+ return;
2350
+ }
2351
+ if (settingsCursor === 1) {
2352
+ if (key.leftArrow) updateConfig((c) => ({ ...c, billingInterval: Math.max(1, c.billingInterval - 1) }));
2353
+ if (key.rightArrow) updateConfig((c) => ({ ...c, billingInterval: c.billingInterval + 1 }));
2354
+ return;
2355
+ }
2356
+ if (settingsCursor === 2 && (key.leftArrow || key.rightArrow || key.return)) {
2357
+ updateConfig((c) => ({ ...c, clearScreen: !c.clearScreen }));
2358
+ return;
2359
+ }
2360
+ if (settingsCursor === 3) {
2361
+ if (key.return) {
2362
+ const init = cfg.timezone ?? "";
2363
+ setTzEdit(init);
2364
+ setTzCaret(init.length);
2365
+ setTzError(null);
2366
+ }
2367
+ if (key.leftArrow || key.rightArrow) updateConfig((c) => ({ ...c, timezone: c.timezone === null ? systemTimezone() : null }));
2368
+ return;
2369
+ }
2370
+ if (settingsCursor === 4 && (key.leftArrow || key.rightArrow || key.return)) {
2371
+ updateConfig((c) => ({ ...c, dashboardLayout: c.dashboardLayout === "grid" ? "single" : "grid" }));
2372
+ return;
2373
+ }
2374
+ if (settingsCursor === 5 && (key.leftArrow || key.rightArrow || key.return)) {
2375
+ updateConfig((c) => ({ ...c, defaultFocus: c.defaultFocus === "all" ? "last" : "all" }));
2376
+ return;
2377
+ }
2378
+ return;
2379
+ }
2380
+ if (settingsTab === "providers") {
2381
+ if (settingsCursor >= 0 && settingsCursor < PROVIDER_ORDER.length && (input === " " || key.return || key.leftArrow || key.rightArrow)) {
2382
+ toggleProvider(PROVIDER_ORDER[settingsCursor]);
2383
+ }
2384
+ return;
2385
+ }
2386
+ if (settingsTab === "accounts" && settingsCursor >= 0 && settingsCursor < trackedAccountRows.length) {
2387
+ const row = trackedAccountRows[settingsCursor];
2388
+ if (key.return) {
2389
+ if (row.source === "configured") {
2390
+ const acc = cfg.accounts.find((a) => a.id === row.explicitId);
2391
+ if (acc) openEditAccount(acc);
2392
+ } else {
2393
+ openConfigureAccount(row);
2394
+ }
2395
+ return;
2396
+ }
2397
+ if (row.source === "configured" && row.explicitId && (input === "d" || input === "x")) {
2398
+ deleteAccount(row.explicitId);
2399
+ return;
2400
+ }
2401
+ if (input === " ") {
2402
+ updateConfig((c) => ({ ...c, activeAccountId: row.id }));
2403
+ return;
2404
+ }
2405
+ return;
2406
+ }
2407
+ if (settingsTab === "accounts" && settingsCursor === trackedAccountRows.length && key.return) {
2408
+ openAddAccount();
2409
+ }
2410
+ return;
2411
+ }
2412
+ if (input === "s") {
2413
+ setSettingsTab("general");
2414
+ setShowSettings(true);
2415
+ setSettingsCursor(-1);
2416
+ return;
2417
+ }
2418
+ if (input === "a") {
2419
+ cycleAccount(1);
2420
+ return;
2421
+ }
2422
+ if (input === "A") {
2423
+ cycleAccount(-1);
2424
+ return;
2425
+ }
2426
+ if (key.tab) {
2427
+ setTab((t) => (t + 1) % TABS.length);
2428
+ resetView();
2429
+ return;
2430
+ }
2431
+ if (input && /^[0-9]$/.test(input) && slots.length > 1) {
2432
+ const target = slots[parseInt(input, 10)];
2433
+ if (target) {
2434
+ updateConfig((c) => ({ ...c, activeAccountId: target.id }));
2435
+ resetView();
2436
+ }
2437
+ return;
2438
+ }
2439
+ if (tab === 0) {
2440
+ if (dashPaginated) {
2441
+ if (input === "]" || key.downArrow || key.pageDown) {
2442
+ setDashPage((p) => Math.min(dashPageCount - 1, p + 1));
2443
+ return;
2444
+ }
2445
+ if (input === "[" || key.upArrow || key.pageUp) {
2446
+ setDashPage((p) => Math.max(0, p - 1));
2447
+ return;
2448
+ }
2449
+ }
2450
+ if (key.upArrow || key.downArrow || key.pageUp || key.pageDown || input === "G" || input === "g") return;
2451
+ }
2452
+ if (tab === 1) {
2453
+ if (input === "p") {
2454
+ cycleTableProvider(1);
2455
+ return;
2456
+ }
2457
+ if (input === "P") {
2458
+ cycleTableProvider(-1);
2459
+ return;
2460
+ }
2461
+ if (input === "/") {
2462
+ setSearchMode(true);
2463
+ setSearchCaret(search.length);
2464
+ return;
2465
+ }
2466
+ if (key.escape) {
2467
+ if (search) {
2468
+ setSearch("");
2469
+ setSearchCaret(0);
2470
+ } else setExpanded(-1);
2471
+ return;
2472
+ }
2473
+ if (input === "o") {
2474
+ setSort((s) => (s + 1) % SORTS_FOR.length);
2475
+ resetView();
2476
+ return;
2477
+ }
2478
+ if (!tableIsCursor) {
2479
+ if (input === "d") {
2480
+ setView(0);
2481
+ resetView();
2482
+ return;
2483
+ }
2484
+ if (input === "w") {
2485
+ setView(1);
2486
+ resetView();
2487
+ return;
2488
+ }
2489
+ if (input === "m") {
2490
+ setView(2);
2491
+ resetView();
2492
+ return;
2493
+ }
2494
+ if (key.leftArrow) {
2495
+ setView((v) => (v - 1 + VIEWS.length) % VIEWS.length);
2496
+ resetView();
2497
+ return;
2498
+ }
2499
+ if (key.rightArrow) {
2500
+ setView((v) => (v + 1) % VIEWS.length);
2501
+ resetView();
2502
+ return;
2503
+ }
2504
+ if (key.return) {
2505
+ setExpanded((e) => e === cursor ? -1 : cursor);
2506
+ return;
2507
+ }
2508
+ }
2509
+ } else {
2510
+ if (key.leftArrow || key.rightArrow) {
2511
+ setTab((t) => (t + 1) % TABS.length);
2512
+ resetView();
2513
+ return;
2514
+ }
2515
+ }
2516
+ if (tab === 1 && !tableIsCursor) {
2517
+ if (key.upArrow) {
2518
+ setCursor((c) => Math.max(0, c - 1));
2519
+ return;
2520
+ }
2521
+ if (key.downArrow) {
2522
+ setCursor((c) => clampRow(c + 1));
2523
+ return;
2524
+ }
2525
+ if (key.pageDown || input === "G") {
2526
+ setCursor((c) => clampRow(input === "G" ? rowCountRef.current - 1 : c + Math.max(1, rows - 12)));
2527
+ return;
2528
+ }
2529
+ if (key.pageUp || input === "g") {
2530
+ setCursor((c) => input === "g" ? 0 : Math.max(0, c - Math.max(1, rows - 12)));
2531
+ return;
2532
+ }
2533
+ }
2534
+ }
2535
+
2536
+ // src/app.tsx
2537
+ import { Fragment as Fragment4, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2538
+ var DEBOUNCE_MS = 300;
2539
+ var LOADER_GRACE_MS = 600;
2540
+ var LOADER_MAX_MS = 8e3;
2541
+ var LOADER_MIN_VISIBLE_MS = 700;
2542
+ function useTerminalSize(settleMs = 90) {
2543
+ const { stdout } = useStdout();
2544
+ const read = () => ({ cols: stdout?.columns || 80, rows: stdout?.rows || 24 });
2545
+ const [size, setSize] = useState4(read);
2546
+ const [live, setLive] = useState4(read);
2547
+ const [resizing, setResizing] = useState4(false);
2548
+ useEffect4(() => {
2549
+ if (!stdout) return;
2550
+ let t;
2551
+ const now = () => ({ cols: stdout.columns || 80, rows: stdout.rows || 24 });
2552
+ const settle = () => {
2553
+ setSize(now());
2554
+ setResizing(false);
2555
+ };
2556
+ const onResize = () => {
2557
+ setLive(now());
2558
+ setResizing(true);
2559
+ if (t) clearTimeout(t);
2560
+ t = setTimeout(settle, settleMs);
2561
+ };
2562
+ stdout.on("resize", onResize);
2563
+ return () => {
2564
+ if (t) clearTimeout(t);
2565
+ stdout.off("resize", onResize);
2566
+ };
2567
+ }, [stdout, settleMs]);
2568
+ return { cols: size.cols, rows: size.rows, resizing, live };
2569
+ }
2570
+ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = null, mode = "degraded" }) {
2571
+ const connected = mode === "connected" && baseUrl !== null && wsToken !== null;
2572
+ const degraded = !connected;
2573
+ const daemon = useDaemon(connected ? baseUrl : null, connected ? wsToken : null);
2574
+ const [config, setConfig] = useState4(() => initialConfig ? applyStartup(initialConfig, cliInterval) : null);
2575
+ const [detected, setDetected] = useState4([]);
2576
+ const [statsLocal, setStats] = useState4(/* @__PURE__ */ new Map());
2577
+ const [peakLocal, setPeak] = useState4(null);
2578
+ const [tableLocal, setTable] = useState4(null);
2579
+ const [tableLoading, setTableLoading] = useState4(false);
2580
+ const [error, setError] = useState4(null);
2581
+ const [updatedLocal, setUpdated] = useState4(/* @__PURE__ */ new Date());
2582
+ const [tab, setTab] = useState4(0);
2583
+ const [view, setView] = useState4(0);
2584
+ const [cursor, setCursor] = useState4(0);
2585
+ const [expanded, setExpanded] = useState4(-1);
2586
+ const [sort, setSort] = useState4(1);
2587
+ const [tableProvider, setTableProvider] = useState4(null);
2588
+ const [search, setSearch] = useState4("");
2589
+ const [searchMode, setSearchMode] = useState4(false);
2590
+ const [cursorRowsLocal, setCursorRows] = useState4(null);
2591
+ const [showSettings, setShowSettings] = useState4(false);
2592
+ const [settingsTab, setSettingsTab] = useState4("general");
2593
+ const [settingsCursor, setSettingsCursor] = useState4(0);
2594
+ const [tzEdit, setTzEdit] = useState4(null);
2595
+ const [tzError, setTzError] = useState4(null);
2596
+ const [tzCaret, setTzCaret] = useState4(0);
2597
+ const [searchCaret, setSearchCaret] = useState4(0);
2598
+ const [accountForm, setAccountForm] = useState4(null);
2599
+ const [onboardSel, setOnboardSel] = useState4(null);
2600
+ const [onboardCursor, setOnboardCursor] = useState4(0);
2601
+ const [dashPage, setDashPage] = useState4(0);
2602
+ const [debouncePassed, setDebouncePassed] = useState4(false);
2603
+ const [graceHold, setGraceHold] = useState4(false);
2604
+ const [loaderShownAt, setLoaderShownAt] = useState4(null);
2605
+ const loaderDone = useRef3(false);
2606
+ const [loaderDoneState, setLoaderDoneFlag] = useState4(false);
2607
+ const setLoaderDone = useCallback((v) => {
2608
+ loaderDone.current = v;
2609
+ setLoaderDoneFlag(v);
2610
+ }, []);
2611
+ const prevShowPicker = useRef3(false);
2612
+ const { exit } = useApp();
2613
+ const { cols, rows, resizing, live } = useTerminalSize();
2614
+ const webRef = useRef3(null);
2615
+ const webStartingRef = useRef3(false);
2616
+ useEffect4(() => () => {
2617
+ void webRef.current?.stop();
2618
+ }, []);
2619
+ const cfg = config ?? DEFAULTS;
2620
+ const interval = cliInterval ?? cfg.interval * 1e3;
2621
+ const billingMs = cfg.billingInterval * 6e4;
2622
+ const tz = resolveTimezone(cfg.timezone);
2623
+ const configReady = config !== null;
2624
+ const accounts = useMemo2(() => buildAccounts(cfg, detected), [cfg, detected]);
2625
+ const trackedAccountRows = useMemo2(() => getTrackedAccountRows(cfg, detected, accounts), [cfg, detected, accounts]);
2626
+ const settingsRowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "providers" ? PROVIDER_ORDER.length : trackedAccountRows.length + 1;
2627
+ const accountsRef = useRef3([]);
2628
+ accountsRef.current = accounts;
2629
+ const rowCountRef = useRef3(0);
2630
+ const tabRef = useRef3(0);
2631
+ tabRef.current = tab;
2632
+ const dashPageCountRef = useRef3(1);
2633
+ const seededRef = useRef3(false);
2634
+ const pasteBufRef = useRef3(null);
2635
+ const pasteCarryRef = useRef3("");
2636
+ const pendingLocalConfigRef = useRef3(null);
2637
+ const insertPasteRef = useRef3(() => {
2638
+ });
2639
+ const tzValueRef = useRef3("");
2640
+ const tzCaretRef = useRef3(0);
2641
+ const searchValueRef = useRef3("");
2642
+ const searchCaretRef = useRef3(0);
2643
+ const accountsKey = useMemo2(() => accounts.map(acctKey).join("|"), [accounts]);
2644
+ const snapshot = daemon.snapshot;
2645
+ const stats = useMemo2(
2646
+ () => connected ? toStatsMap(snapshot, accounts) : statsLocal,
2647
+ [connected, snapshot, accounts, statsLocal]
2648
+ );
2649
+ const accountIdentities = useMemo2(() => {
2650
+ const out = /* @__PURE__ */ new Map();
2651
+ for (const [id, stat] of stats) {
2652
+ const billing = stat.billing;
2653
+ if (!billing) continue;
2654
+ out.set(id, {
2655
+ email: billing.email ?? null,
2656
+ displayName: billing.displayName ?? null,
2657
+ plan: billing.plan ?? null
2658
+ });
2659
+ }
2660
+ return out;
2661
+ }, [stats]);
2662
+ const showPeak = accounts.some((a) => a.providerId === "claude");
2663
+ const peak = connected ? showPeak ? snapshot?.peak ?? null : null : peakLocal;
2664
+ const updated = useMemo2(
2665
+ () => connected ? new Date(snapshot?.generatedAt ?? Date.now()) : updatedLocal,
2666
+ [connected, snapshot, updatedLocal]
2667
+ );
2668
+ const intervalLabel = connected && snapshot?.intervalMs ? Math.round(snapshot.intervalMs / 1e3) : cfg.interval;
2669
+ const readyInputFor = useCallback((id) => {
2670
+ if (connected) {
2671
+ const wa = snapshot?.accounts.find((a) => a.id === id);
2672
+ if (!wa) return void 0;
2673
+ return { summaryState: wa.summaryState, billingState: wa.billingState, billing: wa.billing };
2674
+ }
2675
+ return statsReadyInput(statsLocal.get(id));
2676
+ }, [connected, snapshot, statsLocal]);
2677
+ const slots = useMemo2(() => deriveSlots(accounts), [accounts]);
2678
+ const { activeSlotIdx, focusId } = useMemo2(
2679
+ () => findActiveSlot(slots, cfg.activeAccountId),
2680
+ [slots, cfg.activeAccountId]
2681
+ );
2682
+ const visibleAccounts = useMemo2(
2683
+ () => focusId === null ? accounts : accounts.filter((a) => a.id === focusId),
2684
+ [accounts, focusId]
2685
+ );
2686
+ const allGroups = useMemo2(() => accountsByProvider(accounts), [accounts]);
2687
+ const groups = useMemo2(
2688
+ () => focusId === null ? allGroups : accountsByProvider(visibleAccounts),
2689
+ [allGroups, visibleAccounts, focusId]
2690
+ );
2691
+ const tableProvs = useMemo2(() => allGroups.map((g) => g.provider), [allGroups]);
2692
+ const TOO_SMALL = cols < 40 || rows < 12;
2693
+ const allReady = accounts.length > 0 && accounts.every((a) => accountReady(readyInputFor(a.id), a.providerId));
2694
+ const { gridBudget } = useMemo2(() => computeChrome(slots, cols, rows), [slots, cols, rows]);
2695
+ const dashLayout = useMemo2(() => chooseLayout(
2696
+ Math.max(56, cols - 4),
2697
+ gridBudget,
2698
+ groups.length,
2699
+ focusId !== null || cfg.dashboardLayout === "single",
2700
+ cols
2701
+ ), [cols, gridBudget, groups.length, focusId, cfg.dashboardLayout]);
2702
+ const dashPageCount = dashLayout.pageCount;
2703
+ const dashPaginated = dashPageCount > 1;
2704
+ dashPageCountRef.current = dashPageCount;
2705
+ tzValueRef.current = tzEdit ?? "";
2706
+ tzCaretRef.current = clampCaret(tzCaret, (tzEdit ?? "").length);
2707
+ searchValueRef.current = search;
2708
+ searchCaretRef.current = clampCaret(searchCaret, search.length);
2709
+ const isPrintable = (input, key) => !!input && !key.ctrl && !key.meta && !isPasteInput(input);
2710
+ const insertText = (text) => {
2711
+ if (showSettings && accountForm && (accountForm.field === "name" || accountForm.field === "homeDir")) {
2712
+ setAccountForm((f) => {
2713
+ if (!f || f.field !== "name" && f.field !== "homeDir") return f;
2714
+ const r = spliceInsert(f[f.field], f.caret, text);
2715
+ return { ...f, [f.field]: r.value, caret: r.caret, error: null };
2716
+ });
2717
+ } else if (showSettings && tzEdit !== null) {
2718
+ const r = spliceInsert(tzValueRef.current, tzCaretRef.current, text);
2719
+ tzValueRef.current = r.value;
2720
+ tzCaretRef.current = r.caret;
2721
+ setTzEdit(r.value);
2722
+ setTzCaret(r.caret);
2723
+ setTzError(null);
2724
+ } else if (tab === 1 && searchMode) {
2725
+ const r = spliceInsert(searchValueRef.current, searchCaretRef.current, text);
2726
+ searchValueRef.current = r.value;
2727
+ searchCaretRef.current = r.caret;
2728
+ setSearch(r.value);
2729
+ setSearchCaret(r.caret);
2730
+ }
2731
+ };
2732
+ insertPasteRef.current = insertText;
2733
+ const effTableProvider = tableProvider && tableProvs.includes(tableProvider) ? tableProvider : tableProvs[0] ?? null;
2734
+ const tableIsCursor = !!effTableProvider && !PROVIDERS[effTableProvider].hasUsage;
2735
+ const tableAccounts = useMemo2(
2736
+ () => effTableProvider ? accounts.filter((a) => a.providerId === effTableProvider) : [],
2737
+ [accounts, effTableProvider]
2738
+ );
2739
+ const SORTS_FOR = tableIsCursor ? CURSOR_SORTS : SORTS;
2740
+ const tableAccountIds = useMemo2(() => tableAccounts.map((a) => a.id), [tableAccounts]);
2741
+ const table = useMemo2(
2742
+ () => connected ? pickTable(snapshot, tableAccountIds) : tableLocal,
2743
+ [connected, snapshot, tableAccountIds, tableLocal]
2744
+ );
2745
+ const cursorRows = useMemo2(
2746
+ () => connected ? toCursorRows(snapshot, tableAccounts[0]?.id) : cursorRowsLocal,
2747
+ [connected, snapshot, tableAccounts, cursorRowsLocal]
2748
+ );
2749
+ const needsOnboarding = configReady && !cfg.onboarded;
2750
+ const newProviders = configReady && cfg.onboarded ? PROVIDER_ORDER.filter((p) => !cfg.knownProviders.includes(p) && detected.includes(p)) : [];
2751
+ const showPicker = needsOnboarding || newProviders.length > 0;
2752
+ const minVisibleHold = loaderShownAt !== null && Date.now() - loaderShownAt < LOADER_MIN_VISIBLE_MS;
2753
+ const showLoader = configReady && !showPicker && !showSettings && !TOO_SMALL && accounts.length > 0 && (!allReady || graceHold || minVisibleHold) && (debouncePassed || loaderShownAt !== null) && !loaderDoneState;
2754
+ const pickerProviders = needsOnboarding ? PROVIDER_ORDER : newProviders;
2755
+ const onboardEnabled = onboardSel ?? detected;
2756
+ const onboardItems = pickerProviders.map((pid) => ({
2757
+ id: pid,
2758
+ name: PROVIDERS[pid].name,
2759
+ color: PROVIDERS[pid].color,
2760
+ detected: detected.includes(pid),
2761
+ enabled: onboardEnabled.includes(pid)
2762
+ }));
2763
+ useEffect4(() => {
2764
+ const wasPicker = prevShowPicker.current;
2765
+ prevShowPicker.current = showPicker;
2766
+ if (wasPicker && !showPicker) {
2767
+ setLoaderDone(false);
2768
+ setDebouncePassed(false);
2769
+ setGraceHold(false);
2770
+ setLoaderShownAt(null);
2771
+ }
2772
+ }, [showPicker]);
2773
+ useEffect4(() => {
2774
+ if (showLoader && loaderShownAt === null) setLoaderShownAt(Date.now());
2775
+ }, [showLoader, loaderShownAt]);
2776
+ useEffect4(() => {
2777
+ if (!initialConfig) loadConfig().then((c) => setConfig(applyStartup(c, cliInterval)));
2778
+ detectProviders().then(setDetected);
2779
+ }, []);
2780
+ useEffect4(() => {
2781
+ if (!degraded) return;
2782
+ if (seededRef.current || !configReady || showPicker || accounts.length === 0) return;
2783
+ seededRef.current = true;
2784
+ loadSeedSnapshot().then((snap) => {
2785
+ setStats((prev) => {
2786
+ if (prev.size > 0) return prev;
2787
+ const next = new Map(prev);
2788
+ for (const acc of accountsRef.current) {
2789
+ const s = snap[acc.id];
2790
+ if (s && (s.dashboard || s.billing)) next.set(acc.id, { account: acc, dashboard: s.dashboard ?? null, billing: s.billing ?? null });
2791
+ }
2792
+ return next;
2793
+ });
2794
+ });
2795
+ }, [degraded, configReady, showPicker, accountsKey]);
2796
+ useEffect4(() => {
2797
+ if (!configReady || showPicker || accounts.length === 0) return;
2798
+ if (allReady || loaderDone.current) return;
2799
+ const debounce = setTimeout(() => setDebouncePassed(true), DEBOUNCE_MS);
2800
+ const deadline = setTimeout(() => {
2801
+ setLoaderDone(true);
2802
+ setDebouncePassed(false);
2803
+ }, LOADER_MAX_MS);
2804
+ return () => {
2805
+ clearTimeout(debounce);
2806
+ clearTimeout(deadline);
2807
+ };
2808
+ }, [configReady, showPicker, accountsKey]);
2809
+ useEffect4(() => {
2810
+ if (!allReady || loaderDone.current) return;
2811
+ if (loaderShownAt === null) {
2812
+ setLoaderDone(true);
2813
+ return;
2814
+ }
2815
+ setGraceHold(true);
2816
+ const minRemaining = Math.max(0, LOADER_MIN_VISIBLE_MS - (Date.now() - loaderShownAt));
2817
+ const hold = Math.max(LOADER_GRACE_MS, minRemaining);
2818
+ const t = setTimeout(() => {
2819
+ setLoaderDone(true);
2820
+ setGraceHold(false);
2821
+ }, hold);
2822
+ return () => clearTimeout(t);
2823
+ }, [allReady, loaderShownAt]);
2824
+ useEffect4(() => {
2825
+ if (!degraded || !configReady || showPicker) return;
2826
+ let active = true;
2827
+ let timer;
2828
+ const load = async () => {
2829
+ try {
2830
+ await Promise.all(accountsRef.current.map(async (acc) => {
2831
+ const provider = PROVIDERS[acc.providerId];
2832
+ if (!provider.hasUsage || !provider.fetchSummary) return;
2833
+ try {
2834
+ const dashboard = await provider.fetchSummary(acc, tz);
2835
+ if (active) setStats((prev) => upsert(prev, acc, { dashboard }));
2836
+ } catch {
2837
+ }
2838
+ }));
2839
+ if (active) {
2840
+ setError(null);
2841
+ setUpdated(/* @__PURE__ */ new Date());
2842
+ }
2843
+ } finally {
2844
+ if (active) timer = setTimeout(load, interval);
2845
+ }
2846
+ };
2847
+ load();
2848
+ return () => {
2849
+ active = false;
2850
+ clearTimeout(timer);
2851
+ };
2852
+ }, [degraded, interval, tz, configReady, showPicker, accountsKey]);
2853
+ useEffect4(() => {
2854
+ if (!degraded || !configReady || showPicker) return;
2855
+ let active = true;
2856
+ let timer;
2857
+ const load = async () => {
2858
+ try {
2859
+ const peakP = accountsRef.current.some((a) => a.providerId === "claude") ? fetchPeak() : Promise.resolve(null);
2860
+ await Promise.all(accountsRef.current.map(async (acc) => {
2861
+ const provider = PROVIDERS[acc.providerId];
2862
+ if (!provider.hasBilling || !provider.fetchBilling) return;
2863
+ try {
2864
+ const billing = await provider.fetchBilling(acc);
2865
+ if (active) setStats((prev) => upsert(prev, acc, { billing }));
2866
+ } catch {
2867
+ }
2868
+ }));
2869
+ const p = await peakP;
2870
+ if (active && p) setPeak(p);
2871
+ } finally {
2872
+ if (active) timer = setTimeout(load, billingMs);
2873
+ }
2874
+ };
2875
+ load();
2876
+ return () => {
2877
+ active = false;
2878
+ clearTimeout(timer);
2879
+ };
2880
+ }, [degraded, billingMs, configReady, showPicker, accountsKey]);
2881
+ const tableKey = useMemo2(
2882
+ () => `${effTableProvider}|${tableAccounts.map(acctKey).join(",")}|${tz}`,
2883
+ [effTableProvider, tableAccounts, tz]
2884
+ );
2885
+ useEffect4(() => {
2886
+ setTable(null);
2887
+ setCursorRows(null);
2888
+ setCursor(0);
2889
+ setExpanded(-1);
2890
+ setSort(tableIsCursor ? 0 : 1);
2891
+ setTableLoading(false);
2892
+ }, [tableKey]);
2893
+ useEffect4(() => {
2894
+ if (!degraded || tab !== 1 || !effTableProvider) return;
2895
+ let active = true;
2896
+ let timer;
2897
+ const fetchOnce = async () => {
2898
+ try {
2899
+ if (tableIsCursor) {
2900
+ const s = await cursorModelSpend(tableAccounts[0]?.homeDir);
2901
+ if (active) setCursorRows(s?.models ?? []);
2902
+ } else {
2903
+ const r = await fetchScopeTable(tableAccounts, tz);
2904
+ if (active) setTable(r);
2905
+ }
2906
+ } catch {
2907
+ }
2908
+ };
2909
+ const run = async () => {
2910
+ setTableLoading(true);
2911
+ await fetchOnce();
2912
+ if (!active) return;
2913
+ setTableLoading(false);
2914
+ const loop = async () => {
2915
+ await fetchOnce();
2916
+ if (active) timer = setTimeout(loop, Math.max(interval, 1e4));
2917
+ };
2918
+ timer = setTimeout(loop, Math.max(interval, 1e4));
2919
+ };
2920
+ run();
2921
+ return () => {
2922
+ active = false;
2923
+ clearTimeout(timer);
2924
+ };
2925
+ }, [degraded, tab, tableKey, interval]);
2926
+ useEffect4(() => {
2927
+ setCursor(0);
2928
+ setExpanded(-1);
2929
+ }, [search]);
2930
+ useEffect4(() => {
2931
+ setDashPage((p) => Math.min(p, dashPageCount - 1));
2932
+ }, [dashPageCount]);
2933
+ useEffect4(() => {
2934
+ setSettingsCursor((c) => Math.max(-1, Math.min(c, settingsRowCount - 1)));
2935
+ }, [settingsRowCount]);
2936
+ const resetView = useCallback(() => {
2937
+ setCursor(0);
2938
+ setExpanded(-1);
2939
+ }, []);
2940
+ const clampRow = (n) => Math.max(0, Math.min(rowCountRef.current - 1, n));
2941
+ const PASTE_START = "\x1B[200~";
2942
+ const PASTE_END = "\x1B[201~";
2943
+ const PASTE_MAX = 1 << 20;
2944
+ const handlePasteData = useCallback((chunk) => {
2945
+ const s = typeof chunk === "string" ? chunk : chunk.toString("utf8");
2946
+ if (pasteBufRef.current !== null) {
2947
+ const combined2 = pasteBufRef.current + s;
2948
+ const end2 = combined2.indexOf(PASTE_END);
2949
+ if (end2 === -1) {
2950
+ if (combined2.length >= PASTE_MAX) {
2951
+ const clean3 = sanitizeTyped(combined2);
2952
+ pasteBufRef.current = null;
2953
+ if (clean3) insertPasteRef.current(clean3);
2954
+ return true;
2955
+ }
2956
+ pasteBufRef.current = combined2;
2957
+ return true;
2958
+ }
2959
+ const clean2 = sanitizeTyped(combined2.slice(0, end2));
2960
+ pasteBufRef.current = null;
2961
+ if (clean2) insertPasteRef.current(clean2);
2962
+ return end2 + PASTE_END.length >= combined2.length;
2963
+ }
2964
+ const combined = pasteCarryRef.current + s;
2965
+ const start = combined.indexOf(PASTE_START);
2966
+ if (start === -1) {
2967
+ const keep = Math.min(combined.length, PASTE_START.length - 1);
2968
+ pasteCarryRef.current = combined.slice(combined.length - keep);
2969
+ return false;
2970
+ }
2971
+ pasteCarryRef.current = "";
2972
+ const rest = combined.slice(start + PASTE_START.length);
2973
+ const end = rest.indexOf(PASTE_END);
2974
+ if (end === -1) {
2975
+ pasteBufRef.current = rest;
2976
+ return true;
2977
+ }
2978
+ const clean = sanitizeTyped(rest.slice(0, end));
2979
+ if (clean) insertPasteRef.current(clean);
2980
+ return true;
2981
+ }, []);
2982
+ const isPasteInput = useCallback((input) => {
2983
+ if (pasteBufRef.current !== null) return true;
2984
+ return input.includes("[200~") || input.includes("[201~");
2985
+ }, []);
2986
+ const mouse = useMouse();
2987
+ useEffect4(() => {
2988
+ if (!IS_TTY) return;
2989
+ mouse.enable();
2990
+ if (process.stdout.isTTY) {
2991
+ try {
2992
+ process.stdout.write("\x1B[?1003l\x1B[?1002l\x1B[?1015l");
2993
+ } catch {
2994
+ }
2995
+ }
2996
+ const onScroll = (_pos, dir) => {
2997
+ const up = dir === "scrollup";
2998
+ const t = tabRef.current;
2999
+ if (t === 1) {
3000
+ setCursor((c) => up ? Math.max(0, c - 3) : clampRow(c + 3));
3001
+ } else if (t === 0 && dashPageCountRef.current > 1) {
3002
+ setDashPage((p) => up ? Math.max(0, p - 1) : Math.min(dashPageCountRef.current - 1, p + 1));
3003
+ }
3004
+ };
3005
+ mouse.events.on("scroll", onScroll);
3006
+ const onData = (d) => {
3007
+ if (!handlePasteData(d)) dispatchLinkClicks(d);
3008
+ };
3009
+ process.stdin.on("data", onData);
3010
+ return () => {
3011
+ mouse.events.off("scroll", onScroll);
3012
+ process.stdin.off("data", onData);
3013
+ };
3014
+ }, []);
3015
+ const updateConfig = useCallback((fn) => {
3016
+ setConfig((prev) => {
3017
+ const next = normalizeConfig(fn(prev ?? DEFAULTS));
3018
+ pendingLocalConfigRef.current = connected ? next : null;
3019
+ saveConfigSync(next);
3020
+ if (connected) {
3021
+ void daemon.setConfig(next).then((saved) => {
3022
+ if (pendingLocalConfigRef.current && reconcileDaemonConfig(next, saved, pendingLocalConfigRef.current).pendingLocalConfig === null) {
3023
+ pendingLocalConfigRef.current = null;
3024
+ }
3025
+ }).catch(() => {
3026
+ });
3027
+ }
3028
+ return next;
3029
+ });
3030
+ }, [connected, daemon]);
3031
+ const daemonConfig = daemon.config;
3032
+ useEffect4(() => {
3033
+ if (!connected || !daemonConfig) return;
3034
+ setConfig((prev) => {
3035
+ const reconciled = reconcileDaemonConfig(prev, daemonConfig, pendingLocalConfigRef.current);
3036
+ pendingLocalConfigRef.current = reconciled.pendingLocalConfig;
3037
+ return reconciled.config;
3038
+ });
3039
+ }, [connected, daemonConfig]);
3040
+ function toggleOnboard(i) {
3041
+ if (i < 0 || i >= pickerProviders.length) return;
3042
+ const pid = pickerProviders[i];
3043
+ setOnboardSel((prev) => {
3044
+ const base = prev ?? detected;
3045
+ return base.includes(pid) ? base.filter((p) => p !== pid) : [...base, pid];
3046
+ });
3047
+ }
3048
+ function toggleProvider(pid) {
3049
+ updateConfig((c) => ({
3050
+ ...c,
3051
+ knownProviders: c.knownProviders.includes(pid) ? c.knownProviders : [...c.knownProviders, pid],
3052
+ disabledProviders: c.disabledProviders.includes(pid) ? c.disabledProviders.filter((p) => p !== pid) : [...c.disabledProviders, pid]
3053
+ }));
3054
+ }
3055
+ function confirmOnboarding() {
3056
+ const enabled = onboardEnabled;
3057
+ updateConfig((c) => {
3058
+ if (!c.onboarded) {
3059
+ return {
3060
+ ...c,
3061
+ disabledProviders: PROVIDER_ORDER.filter((p) => !enabled.includes(p)),
3062
+ knownProviders: [...PROVIDER_ORDER],
3063
+ onboarded: true
3064
+ };
3065
+ }
3066
+ const newlyDisabled = pickerProviders.filter((p) => !enabled.includes(p));
3067
+ return {
3068
+ ...c,
3069
+ disabledProviders: [.../* @__PURE__ */ new Set([...c.disabledProviders, ...newlyDisabled])],
3070
+ knownProviders: [.../* @__PURE__ */ new Set([...c.knownProviders, ...pickerProviders])]
3071
+ };
3072
+ });
3073
+ setOnboardSel(null);
3074
+ setOnboardCursor(0);
3075
+ }
3076
+ const cycleAccount = useCallback((dir) => {
3077
+ if (slots.length <= 1) return;
3078
+ const next = (activeSlotIdx + dir + slots.length) % slots.length;
3079
+ updateConfig((c) => ({ ...c, activeAccountId: slots[next].id }));
3080
+ resetView();
3081
+ }, [slots, activeSlotIdx, updateConfig, resetView]);
3082
+ const cycleTableProvider = useCallback((dir) => {
3083
+ if (tableProvs.length <= 1) return;
3084
+ const cur = effTableProvider ? tableProvs.indexOf(effTableProvider) : 0;
3085
+ const nextProv = tableProvs[(cur + dir + tableProvs.length) % tableProvs.length];
3086
+ setTableProvider(nextProv);
3087
+ const nextIsCursor = !!nextProv && !PROVIDERS[nextProv].hasUsage;
3088
+ setSort(nextIsCursor ? 0 : 1);
3089
+ setCursor(0);
3090
+ setExpanded(-1);
3091
+ setSearch("");
3092
+ setSearchCaret(0);
3093
+ setSearchMode(false);
3094
+ }, [tableProvs, effTableProvider]);
3095
+ function openAddAccount(defaults) {
3096
+ const providerId = defaults?.providerId ?? (detected[0] ?? "claude");
3097
+ setAccountForm({
3098
+ mode: "add",
3099
+ field: "provider",
3100
+ providerId,
3101
+ name: defaults?.name ?? "",
3102
+ homeDir: defaults?.homeDir ?? "~",
3103
+ color: defaults?.color ?? pickAccentColor(cfg.accounts),
3104
+ caret: defaults?.name?.length ?? 0,
3105
+ editingId: null,
3106
+ error: null
3107
+ });
3108
+ }
3109
+ function openConfigureAccount(row) {
3110
+ openAddAccount(row);
3111
+ }
3112
+ function openEditAccount(acc) {
3113
+ setAccountForm({
3114
+ mode: "edit",
3115
+ field: "provider",
3116
+ providerId: acc.providerId,
3117
+ name: acc.name,
3118
+ homeDir: acc.homeDir,
3119
+ color: acc.color || PROVIDERS[acc.providerId].color,
3120
+ caret: acc.name.length,
3121
+ editingId: acc.id,
3122
+ error: null
3123
+ });
3124
+ }
3125
+ function commitAccountForm() {
3126
+ if (!accountForm) return;
3127
+ const name = accountForm.name.trim();
3128
+ const homeDir = accountForm.homeDir.trim() || "~";
3129
+ if (!name) {
3130
+ setAccountForm({ ...accountForm, error: "Name required", field: "name", caret: accountForm.name.length });
3131
+ return;
3132
+ }
3133
+ updateConfig((c) => {
3134
+ if (accountForm.mode === "add") {
3135
+ const id = generateAccountId(name, c.accounts);
3136
+ const account = { id, providerId: accountForm.providerId, name, homeDir, color: accountForm.color };
3137
+ return { ...c, accounts: [...c.accounts, account] };
3138
+ }
3139
+ return {
3140
+ ...c,
3141
+ accounts: c.accounts.map((a) => a.id === accountForm.editingId ? { ...a, providerId: accountForm.providerId, name, homeDir, color: accountForm.color } : a)
3142
+ };
3143
+ });
3144
+ setAccountForm(null);
3145
+ }
3146
+ const cycleFormField = useCallback((dir) => {
3147
+ setAccountForm((f) => {
3148
+ if (!f) return f;
3149
+ const i = FORM_FIELDS.indexOf(f.field);
3150
+ const next = FORM_FIELDS[(i + dir + FORM_FIELDS.length) % FORM_FIELDS.length];
3151
+ const caret = next === "name" ? f.name.length : next === "homeDir" ? f.homeDir.length : f.caret;
3152
+ return { ...f, field: next, caret };
3153
+ });
3154
+ }, []);
3155
+ const cycleProvider = useCallback((dir) => {
3156
+ setAccountForm((f) => {
3157
+ if (!f) return f;
3158
+ const i = PROVIDER_ORDER.indexOf(f.providerId);
3159
+ return { ...f, providerId: PROVIDER_ORDER[(i + dir + PROVIDER_ORDER.length) % PROVIDER_ORDER.length] };
3160
+ });
3161
+ }, []);
3162
+ const cycleColor = useCallback((dir) => {
3163
+ setAccountForm((f) => {
3164
+ if (!f) return f;
3165
+ const i = COLOR_PALETTE.indexOf(f.color);
3166
+ const idx = i < 0 ? 0 : i;
3167
+ return { ...f, color: COLOR_PALETTE[(idx + dir + COLOR_PALETTE.length) % COLOR_PALETTE.length] };
3168
+ });
3169
+ }, []);
3170
+ function deleteAccount(id) {
3171
+ updateConfig((c) => ({
3172
+ ...c,
3173
+ accounts: c.accounts.filter((a) => a.id !== id),
3174
+ activeAccountId: c.activeAccountId === id ? null : c.activeAccountId
3175
+ }));
3176
+ }
3177
+ function moveAccount(idx, dir) {
3178
+ updateConfig((c) => {
3179
+ const next = [...c.accounts];
3180
+ const target = idx + dir;
3181
+ if (target < 0 || target >= next.length) return c;
3182
+ [next[idx], next[target]] = [next[target], next[idx]];
3183
+ return { ...c, accounts: next };
3184
+ });
3185
+ setSettingsCursor((c) => Math.max(0, Math.min(trackedAccountRows.length - 1, c + dir)));
3186
+ }
3187
+ async function toggleWeb() {
3188
+ if (connected) {
3189
+ if (baseUrl) openUrl(baseUrl);
3190
+ return;
3191
+ }
3192
+ if (webRef.current) {
3193
+ openUrl(webRef.current.url);
3194
+ return;
3195
+ }
3196
+ if (webStartingRef.current) return;
3197
+ webStartingRef.current = true;
3198
+ try {
3199
+ const { startWebServer } = await import("./server-SP3FOHM2.js");
3200
+ const ctrl = await startWebServer({ config: cfg, log: false });
3201
+ webRef.current = ctrl;
3202
+ openUrl(ctrl.url);
3203
+ } catch {
3204
+ } finally {
3205
+ webStartingRef.current = false;
3206
+ }
3207
+ }
3208
+ const onTabSelect = useCallback((i) => {
3209
+ setTab(i);
3210
+ resetView();
3211
+ }, [resetView]);
3212
+ const onStripSelect = useCallback((i) => {
3213
+ updateConfig((c) => ({ ...c, activeAccountId: slots[i].id }));
3214
+ resetView();
3215
+ }, [slots, updateConfig, resetView]);
3216
+ const onProviderSelect = useCallback((p) => {
3217
+ setTableProvider(p);
3218
+ setCursor(0);
3219
+ setExpanded(-1);
3220
+ setSearch("");
3221
+ setSearchCaret(0);
3222
+ setSearchMode(false);
3223
+ }, []);
3224
+ const onRowClickToken = useCallback((idx) => {
3225
+ if (idx === cursor) setExpanded((e) => e === idx ? -1 : idx);
3226
+ else setCursor(idx);
3227
+ }, [cursor]);
3228
+ const onRowClickCursor = useCallback((idx) => setCursor(idx), []);
3229
+ const tokenRows = useMemo2(
3230
+ () => tab === 1 && !tableIsCursor ? sortRows(filterTokenRows(table ? [table.daily, table.weekly, table.monthly][view] : [], search), sort) : [],
3231
+ [tab, tableIsCursor, table, view, search, sort]
3232
+ );
3233
+ const cursorTableRows = useMemo2(
3234
+ () => tab === 1 && tableIsCursor ? sortCursorRows(filterCursorRows(cursorRows ?? [], search), sort) : [],
3235
+ [tab, tableIsCursor, cursorRows, search, sort]
3236
+ );
3237
+ useInput((input, key) => handleKey(input, key, {
3238
+ showPicker,
3239
+ pickerProviders,
3240
+ onboardCursor,
3241
+ setOnboardCursor,
3242
+ toggleOnboard,
3243
+ confirmOnboarding,
3244
+ exit,
3245
+ showSettings,
3246
+ accountForm,
3247
+ setAccountForm,
3248
+ commitAccountForm,
3249
+ cycleFormField,
3250
+ cycleProvider,
3251
+ cycleColor,
3252
+ isPrintable,
3253
+ insertText,
3254
+ tzEdit,
3255
+ setTzEdit,
3256
+ setTzError,
3257
+ updateConfig,
3258
+ setTzCaret,
3259
+ tzValueRef,
3260
+ tzCaretRef,
3261
+ tab,
3262
+ searchMode,
3263
+ setSearchMode,
3264
+ search,
3265
+ setSearch,
3266
+ setSearchCaret,
3267
+ searchValueRef,
3268
+ searchCaretRef,
3269
+ showLoader,
3270
+ configReady,
3271
+ toggleWeb,
3272
+ settingsCursor,
3273
+ settingsTab,
3274
+ setSettingsTab,
3275
+ setShowSettings,
3276
+ cfg,
3277
+ trackedAccountRows,
3278
+ moveAccount,
3279
+ setSettingsCursor,
3280
+ toggleProvider,
3281
+ openEditAccount,
3282
+ openConfigureAccount,
3283
+ deleteAccount,
3284
+ openAddAccount,
3285
+ cycleAccount,
3286
+ setTab,
3287
+ resetView,
3288
+ slots,
3289
+ dashPaginated,
3290
+ dashPageCount,
3291
+ setDashPage,
3292
+ cycleTableProvider,
3293
+ setExpanded,
3294
+ setSort,
3295
+ SORTS_FOR,
3296
+ tableIsCursor,
3297
+ setView,
3298
+ cursor,
3299
+ rowCountRef,
3300
+ rows,
3301
+ setCursor,
3302
+ clampRow
3303
+ }), { isActive: IS_TTY });
3304
+ if (error) return /* @__PURE__ */ jsx11(Box11, { padding: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "red", children: error }) });
3305
+ if (!config) return /* @__PURE__ */ jsx11(Box11, { padding: 1, children: /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "Loading..." }) });
3306
+ if (resizing) return /* @__PURE__ */ jsx11(ResizingView, { cols: live.cols, rows: live.rows });
3307
+ if (showPicker) {
3308
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, children: /* @__PURE__ */ jsx11(
3309
+ Onboarding,
3310
+ {
3311
+ items: onboardItems,
3312
+ cursor: onboardCursor,
3313
+ onToggle: toggleOnboard,
3314
+ onConfirm: confirmOnboarding,
3315
+ heading: needsOnboarding ? "Welcome to tokmon" : "New providers detected",
3316
+ subheading: needsOnboarding ? "Pick the tools you want to track. You can change this anytime in settings." : "tokmon found these installed since you last set up. Pick which to track."
3317
+ }
3318
+ ) });
3319
+ }
3320
+ if (showLoader) {
3321
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: /* @__PURE__ */ jsx11(LoadingView, { groups: allGroups, stats, cols, rows, readyInput: readyInputFor }) });
3322
+ }
3323
+ if (TOO_SMALL && !showSettings) {
3324
+ return /* @__PURE__ */ jsx11(TinyFallback, { groups, stats, rows, cols });
3325
+ }
3326
+ rowCountRef.current = tableIsCursor ? cursorTableRows.length : tokenRows.length;
3327
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: [
3328
+ /* @__PURE__ */ jsxs11(Box11, { justifyContent: "space-between", children: [
3329
+ /* @__PURE__ */ jsxs11(Box11, { children: [
3330
+ /* @__PURE__ */ jsxs11(Text11, { bold: true, color: "greenBright", children: [
3331
+ glyphs().dotSel,
3332
+ " tokmon"
3333
+ ] }),
3334
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
3335
+ " ",
3336
+ glyphs().middot,
3337
+ " every ",
3338
+ intervalLabel,
3339
+ "s"
3340
+ ] })
3341
+ ] }),
3342
+ /* @__PURE__ */ jsxs11(Box11, { children: [
3343
+ peak && /* @__PURE__ */ jsxs11(Fragment4, { children: [
3344
+ /* @__PURE__ */ jsx11(PeakBadge, { peak }),
3345
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
3346
+ " ",
3347
+ glyphs().middot,
3348
+ " "
3349
+ ] })
3350
+ ] }),
3351
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: time(updated, tz) })
3352
+ ] })
3353
+ ] }),
3354
+ degraded && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
3355
+ glyphs().warn,
3356
+ " degraded ",
3357
+ glyphs().middot,
3358
+ " background service unavailable, running in-process"
3359
+ ] }),
3360
+ connected && daemon.conn !== "live" && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
3361
+ glyphs().warn,
3362
+ " reconnecting ",
3363
+ glyphs().middot,
3364
+ " showing last known data"
3365
+ ] }),
3366
+ showSettings ? /* @__PURE__ */ jsx11(
3367
+ SettingsView,
3368
+ {
3369
+ config: cfg,
3370
+ cursor: settingsCursor,
3371
+ activeTab: settingsTab,
3372
+ tzEdit,
3373
+ tzCaret,
3374
+ tzError,
3375
+ resolvedTz: tz,
3376
+ accountForm,
3377
+ activeAccountId: cfg.activeAccountId,
3378
+ trackedAccounts: trackedAccountRows,
3379
+ accountIdentities
3380
+ }
3381
+ ) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
3382
+ /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, marginBottom: 1, children: [
3383
+ /* @__PURE__ */ jsx11(TabBar, { tabs: TABS, active: tab, onSelect: onTabSelect }),
3384
+ /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
3385
+ " Tab/",
3386
+ glyphs().arrowL,
3387
+ glyphs().arrowR
3388
+ ] })
3389
+ ] }),
3390
+ tab === 0 && /* @__PURE__ */ jsxs11(Fragment4, { children: [
3391
+ /* @__PURE__ */ jsx11(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage }),
3392
+ slots.length > 1 && /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
3393
+ /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "focus " }),
3394
+ /* @__PURE__ */ jsx11(
3395
+ AccountStrip,
3396
+ {
3397
+ slots,
3398
+ activeIdx: activeSlotIdx,
3399
+ onSelect: onStripSelect
3400
+ }
3401
+ )
3402
+ ] }),
3403
+ /* @__PURE__ */ jsx11(TotalsRow, { groups, stats, cols })
3404
+ ] }),
3405
+ tab === 1 && /* @__PURE__ */ jsxs11(Fragment4, { children: [
3406
+ tableProvs.length > 0 && /* @__PURE__ */ jsx11(TableProviderBar, { providers: tableProvs, active: effTableProvider, onSelect: onProviderSelect }),
3407
+ /* @__PURE__ */ jsx11(Box11, { height: 1 }),
3408
+ /* @__PURE__ */ jsx11(
3409
+ ControlBar,
3410
+ {
3411
+ views: VIEWS,
3412
+ period: view,
3413
+ sort: sortLabel(SORTS_FOR[sort % SORTS_FOR.length]),
3414
+ search,
3415
+ searchCaret,
3416
+ searching: searchMode,
3417
+ showPeriod: !tableIsCursor
3418
+ }
3419
+ ),
3420
+ /* @__PURE__ */ jsx11(Box11, { height: 1 }),
3421
+ !effTableProvider ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
3422
+ "No providers enabled ",
3423
+ glyphs().emDash,
3424
+ " press s to pick providers."
3425
+ ] }) : tableLoading && !table && !cursorRows ? /* @__PURE__ */ jsx11(Spinner, { label: "Loading history" }) : tableIsCursor ? /* @__PURE__ */ jsx11(
3426
+ CursorSpendTable,
3427
+ {
3428
+ rows: cursorTableRows,
3429
+ cursor,
3430
+ maxRows: Math.max(1, rows - 16),
3431
+ onRowClick: onRowClickCursor
3432
+ }
3433
+ ) : /* @__PURE__ */ jsx11(
3434
+ TokenTable,
3435
+ {
3436
+ rows: tokenRows,
3437
+ cursor,
3438
+ expanded,
3439
+ maxRows: Math.max(1, rows - 16),
3440
+ cols,
3441
+ onRowClick: onRowClickToken
3442
+ }
3443
+ )
3444
+ ] })
3445
+ ] }),
3446
+ (tab === 0 || showSettings) && /* @__PURE__ */ jsx11(Footer, { hasAccounts: slots.length > 1, paginated: tab === 0 && dashPaginated, cols })
3447
+ ] });
3448
+ }
3449
+
3450
+ // src/bootstrap-ink.tsx
3451
+ import { jsx as jsx12 } from "react/jsx-runtime";
3452
+ function enterAltScreen() {
3453
+ process.stdout.write("\x1B[?1049h\x1B[H");
3454
+ }
3455
+ function leaveAltScreen() {
3456
+ try {
3457
+ process.stdout.write("\x1B[?1049l");
3458
+ } catch {
3459
+ }
3460
+ }
3461
+ function setupInputModes() {
3462
+ process.stdout.write("\x1B[?2004h\x1B[?1004l");
3463
+ }
3464
+ function restoreInputModes() {
3465
+ try {
3466
+ process.stdout.write("\x1B[?2004l");
3467
+ } catch {
3468
+ }
3469
+ }
3470
+ async function bootstrapInk({ interval, config, daemon, mode }) {
3471
+ const isTTY = process.stdout.isTTY === true;
3472
+ const altScreen = config.clearScreen && isTTY;
3473
+ if (altScreen) enterAltScreen();
3474
+ if (isTTY) setupInputModes();
3475
+ process.once("exit", () => {
3476
+ if (isTTY) restoreInputModes();
3477
+ if (altScreen) leaveAltScreen();
3478
+ });
3479
+ const { waitUntilExit } = render(
3480
+ /* @__PURE__ */ jsx12(MouseProvider, { children: /* @__PURE__ */ jsx12(
3481
+ App,
3482
+ {
3483
+ interval,
3484
+ initialConfig: config,
3485
+ baseUrl: daemon.baseUrl,
3486
+ wsToken: daemon.wsToken,
3487
+ mode
3488
+ }
3489
+ ) })
3490
+ );
3491
+ await waitUntilExit();
3492
+ daemon.stop();
3493
+ if (isTTY) restoreInputModes();
3494
+ if (altScreen) leaveAltScreen();
3495
+ process.exit(typeof process.exitCode === "number" ? process.exitCode : 0);
3496
+ }
3497
+ export {
3498
+ bootstrapInk
3499
+ };