tokmon 0.28.1 → 0.28.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +156 -234
  2. package/THIRD_PARTY_NOTICES.md +62 -0
  3. package/dist/{bootstrap-ink-L7QSJBMS.js → bootstrap-ink-WG4WG3R2.js} +1579 -1135
  4. package/dist/chunk-7CH6S7BR.js +447 -0
  5. package/dist/{chunk-CVKCVHS7.js → chunk-CFKUZCMB.js} +70 -310
  6. package/dist/chunk-HNWEJ6MS.js +1094 -0
  7. package/dist/chunk-JZSZEHVD.js +406 -0
  8. package/dist/{chunk-S33XIUAW.js → chunk-P57DVFNH.js} +277 -49
  9. package/dist/{chunk-4HU4EI5T.js → chunk-P5JN5DDX.js} +25 -46
  10. package/dist/chunk-TN6V7XSL.js +512 -0
  11. package/dist/chunk-USKOQIE3.js +278 -0
  12. package/dist/chunk-UYPDMVW5.js +21 -0
  13. package/dist/{cli-command-75LP4IDS.js → cli-command-MD3RJRP4.js} +249 -15
  14. package/dist/cli.js +6 -6
  15. package/dist/{config-HXFJTNLM.js → config-3DGZ47F7.js} +23 -3
  16. package/dist/daemon-EMOA36WB.js +377 -0
  17. package/dist/daemon-handle-JHE5MKMI.js +11 -0
  18. package/dist/server-EPSH52DR.js +12 -0
  19. package/dist/web/assets/{Area-CMXvOw33.js → Area-stRnp8Km.js} +1 -1
  20. package/dist/web/assets/analytics-moZZt9L_.js +2 -0
  21. package/dist/web/assets/breakdown-km9w7myU.js +4 -0
  22. package/dist/web/assets/button-DrX04Cc2.js +1 -0
  23. package/dist/web/assets/{chart-cZoLgpmG.js → chart-D0CtuL2k.js} +1 -1
  24. package/dist/web/assets/explore-B11uNJV1.js +22 -0
  25. package/dist/web/assets/index-RDhXsCNG.js +83 -0
  26. package/dist/web/assets/index-ZmOS8p9O.css +1 -0
  27. package/dist/web/assets/models-DYbZnY34.js +2 -0
  28. package/dist/web/assets/overview-CruICbfJ.js +2 -0
  29. package/dist/web/assets/panel-xckM3_GC.js +1 -0
  30. package/dist/web/assets/settings-sheet-G-BTSIF6.js +1 -0
  31. package/dist/web/assets/share-sheet-weIdd4DW.js +2 -0
  32. package/dist/web/assets/timeline-BHgniliZ.js +1 -0
  33. package/dist/web/index.html +30 -4
  34. package/package.json +13 -4
  35. package/site/THIRD_PARTY_NOTICES.md +83 -0
  36. package/site/public/fonts/OFL.txt +93 -0
  37. package/dist/chunk-3RTWFGGD.js +0 -275
  38. package/dist/chunk-FMP3P2WV.js +0 -299
  39. package/dist/chunk-HP5UZCXP.js +0 -436
  40. package/dist/chunk-SMPY52EV.js +0 -125
  41. package/dist/chunk-XDA5RJST.js +0 -193
  42. package/dist/daemon-CL4FJW26.js +0 -219
  43. package/dist/daemon-handle-QQLJE46Y.js +0 -10
  44. package/dist/server-EUO7CWYH.js +0 -11
  45. package/dist/web/assets/analytics-DqdZXOJL.js +0 -2
  46. package/dist/web/assets/breakdown-kOSSJ3mI.js +0 -4
  47. package/dist/web/assets/explore-vcsM8K9I.js +0 -22
  48. package/dist/web/assets/index-BErCXT7f.css +0 -1
  49. package/dist/web/assets/index-DH3dOnbg.js +0 -83
  50. package/dist/web/assets/models-B5ikm83_.js +0 -2
  51. package/dist/web/assets/overview-DD1kP8CP.js +0 -2
  52. package/dist/web/assets/panel-DTQBwExW.js +0 -1
  53. package/dist/web/assets/primitives-C36qJlA2.js +0 -1
  54. package/dist/web/assets/settings-sheet-_i2zTQjf.js +0 -1
  55. package/dist/web/assets/share-sheet-BLHmrFbm.js +0 -2
  56. package/dist/web/assets/timeline-B4uG-Emo.js +0 -1
  57. package/dist/web/assets/use-dialog-trap-bfaXEMz9.js +0 -1
@@ -1,13 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createDaemonRpcClient
4
- } from "./chunk-3RTWFGGD.js";
4
+ } from "./chunk-7CH6S7BR.js";
5
5
  import {
6
+ accountIdentityText,
6
7
  accountsByProvider,
7
8
  buildAccounts,
8
9
  fetchPeak,
9
- modelColor
10
- } from "./chunk-FMP3P2WV.js";
10
+ modelColor,
11
+ resolveQuotaViews,
12
+ severity,
13
+ usageFromHeadroom
14
+ } from "./chunk-TN6V7XSL.js";
11
15
  import {
12
16
  PROVIDERS,
13
17
  PROVIDER_ORDER,
@@ -24,23 +28,33 @@ import {
24
28
  time,
25
29
  tokens,
26
30
  withTimeout
27
- } from "./chunk-CVKCVHS7.js";
31
+ } from "./chunk-CFKUZCMB.js";
32
+ import "./chunk-JZSZEHVD.js";
28
33
  import {
29
34
  COLOR_PALETTE,
30
35
  DEFAULTS,
36
+ DESKTOP_GRAPH_RANGES,
37
+ THEME_PRESET_IDS,
31
38
  configLocation,
32
39
  generateAccountId,
33
40
  getTrackedAccountRows,
41
+ isDarkOnlyThemePreset,
34
42
  isValidTimezone,
35
43
  loadConfig,
36
44
  normalizeAllowedHost,
37
45
  normalizeConfig,
38
46
  pickAccentColor,
47
+ providerDetectionEnabled,
39
48
  redactEmail,
49
+ resolveTerminalTheme,
40
50
  sanitizeTyped,
41
51
  saveConfigSync,
42
- snapshotCacheFile
43
- } from "./chunk-HP5UZCXP.js";
52
+ setDetectedAccountExcluded,
53
+ setProviderDetectionEnabled,
54
+ snapshotCacheFile,
55
+ themePresetOption,
56
+ toggleProviderSelection
57
+ } from "./chunk-HNWEJ6MS.js";
44
58
  import {
45
59
  glyphs
46
60
  } from "./chunk-RF4GGQGM.js";
@@ -50,7 +64,7 @@ import { render } from "ink";
50
64
  import { MouseProvider } from "@zenobius/ink-mouse";
51
65
 
52
66
  // src/app.tsx
53
- import { useState as useState10, useEffect as useEffect9, useCallback as useCallback7, useRef as useRef8, useMemo as useMemo2 } from "react";
67
+ import { useState as useState10, useEffect as useEffect9, useCallback as useCallback7, useRef as useRef8, useMemo as useMemo3 } from "react";
54
68
  import { Box as Box11, Text as Text12, useInput, useApp } from "ink";
55
69
  import { useMouse } from "@zenobius/ink-mouse";
56
70
 
@@ -59,7 +73,55 @@ import { appendFileSync } from "fs";
59
73
  import { memo, useEffect, useRef, useState } from "react";
60
74
  import { Box, Text } from "ink";
61
75
  import { useOnMouseClick } from "@zenobius/ink-mouse";
62
- import { jsx, jsxs } from "react/jsx-runtime";
76
+
77
+ // src/ui/theme.tsx
78
+ import { createContext, useContext, useMemo } from "react";
79
+ import { jsx } from "react/jsx-runtime";
80
+ var ANSI_THEME = {
81
+ accent: "greenBright",
82
+ cost: "yellow",
83
+ positive: "green",
84
+ ok: "green",
85
+ warn: "yellow",
86
+ crit: "red",
87
+ unknown: void 0
88
+ };
89
+ var NO_COLOR_THEME = {
90
+ accent: void 0,
91
+ cost: void 0,
92
+ positive: void 0,
93
+ ok: void 0,
94
+ warn: void 0,
95
+ crit: void 0,
96
+ unknown: void 0
97
+ };
98
+ var TuiThemeContext = createContext(ANSI_THEME);
99
+ function fromTokens(tokens2) {
100
+ return {
101
+ accent: tokens2.accent,
102
+ cost: tokens2.cost,
103
+ positive: tokens2.positive,
104
+ ok: tokens2.ok,
105
+ warn: tokens2.warn,
106
+ crit: tokens2.crit,
107
+ unknown: tokens2.unknown
108
+ };
109
+ }
110
+ function resolveTuiTheme(appearance, noColor = process.env.NO_COLOR !== void 0) {
111
+ if (noColor || appearance.terminal === "off") return NO_COLOR_THEME;
112
+ const resolved = resolveTerminalTheme(appearance);
113
+ return resolved ? fromTokens(resolved.tokens) : ANSI_THEME;
114
+ }
115
+ function TuiThemeProvider({ appearance, children }) {
116
+ const value = useMemo(() => resolveTuiTheme(appearance), [appearance]);
117
+ return /* @__PURE__ */ jsx(TuiThemeContext.Provider, { value, children });
118
+ }
119
+ function useTuiTheme() {
120
+ return useContext(TuiThemeContext);
121
+ }
122
+
123
+ // src/ui/shared.tsx
124
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
63
125
  function truncateName(s, n) {
64
126
  const ell = glyphs().ellipsis;
65
127
  return s.length > n ? s.slice(0, n - ell.length) + ell : s;
@@ -69,7 +131,7 @@ function ClickableBox({ onClick, children, ...props }) {
69
131
  useOnMouseClick(ref, (clicked) => {
70
132
  if (clicked) onClick();
71
133
  });
72
- return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
134
+ return /* @__PURE__ */ jsx2(Box, { ref, ...props, children });
73
135
  }
74
136
  var SGR_PRESS = /\x1b\[<(\d+);(\d+);(\d+)M/g;
75
137
  var linkHits = /* @__PURE__ */ new Set();
@@ -137,9 +199,10 @@ function LinkBox({ onClick, children, ...props }) {
137
199
  linkHits.delete(hit);
138
200
  };
139
201
  }, []);
140
- return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
202
+ return /* @__PURE__ */ jsx2(Box, { ref, ...props, children });
141
203
  }
142
204
  function Spinner({ label }) {
205
+ const theme = useTuiTheme();
143
206
  const frames = glyphs().spinner;
144
207
  const [i, setI] = useState(0);
145
208
  useEffect(() => {
@@ -147,15 +210,15 @@ function Spinner({ label }) {
147
210
  return () => clearInterval(id);
148
211
  }, []);
149
212
  return /* @__PURE__ */ jsxs(Box, { children: [
150
- /* @__PURE__ */ jsxs(Text, { color: "green", children: [
213
+ /* @__PURE__ */ jsxs(Text, { color: theme.accent, children: [
151
214
  frames[i],
152
215
  " "
153
216
  ] }),
154
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: label })
217
+ /* @__PURE__ */ jsx2(Text, { dimColor: true, children: label })
155
218
  ] });
156
219
  }
157
220
  var TabBar = memo(function TabBar2({ tabs, active, onSelect }) {
158
- 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: [
221
+ return /* @__PURE__ */ jsx2(Box, { children: tabs.map((t, i) => /* @__PURE__ */ jsx2(ClickableBox, { onClick: () => onSelect(i), marginRight: 1, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, inverse: true, children: [
159
222
  " ",
160
223
  t,
161
224
  " "
@@ -166,14 +229,15 @@ var TabBar = memo(function TabBar2({ tabs, active, onSelect }) {
166
229
  ] }) }, t)) });
167
230
  });
168
231
  var PeakBadge = memo(function PeakBadge2({ peak, resetDisplay = "relative", tz }) {
169
- const color = peak.state === "peak" ? "red" : "green";
232
+ const theme = useTuiTheme();
233
+ const color = peak.state === "peak" ? theme.crit : theme.ok;
170
234
  const changesAt = peak.changesAt ?? (peak.minutesUntilChange !== null ? new Date(Date.now() + peak.minutesUntilChange * 6e4).toISOString() : null);
171
235
  return /* @__PURE__ */ jsxs(Box, { children: [
172
236
  /* @__PURE__ */ jsxs(Text, { color, children: [
173
237
  glyphs().dot,
174
238
  " "
175
239
  ] }),
176
- /* @__PURE__ */ jsx(Text, { bold: true, color, children: peak.label }),
240
+ /* @__PURE__ */ jsx2(Text, { bold: true, color, children: peak.label }),
177
241
  changesAt && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
178
242
  " (",
179
243
  formatResetAt(changesAt, resetDisplay, Date.now(), tz),
@@ -198,8 +262,8 @@ function sparkline(values) {
198
262
  function Bar({ pct, color, width = 24 }) {
199
263
  const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
200
264
  return /* @__PURE__ */ jsxs(Text, { children: [
201
- /* @__PURE__ */ jsx(Text, { color, children: glyphs().barFull.repeat(filled) }),
202
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: glyphs().barEmpty.repeat(width - filled) })
265
+ /* @__PURE__ */ jsx2(Text, { color, children: glyphs().barFull.repeat(filled) }),
266
+ /* @__PURE__ */ jsx2(Text, { dimColor: true, children: glyphs().barEmpty.repeat(width - filled) })
203
267
  ] });
204
268
  }
205
269
  function metricValueText(m) {
@@ -215,6 +279,20 @@ function metricValueText(m) {
215
279
  }
216
280
  return `${Math.round(m.used)}%`;
217
281
  }
282
+ function CaretText({ value, caret: caret2, color }) {
283
+ const c = Math.max(0, Math.min(caret2, value.length));
284
+ if (c >= value.length) {
285
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
286
+ /* @__PURE__ */ jsx2(Text, { bold: true, color, children: value }),
287
+ /* @__PURE__ */ jsx2(Text, { color, children: glyphs().vbar })
288
+ ] });
289
+ }
290
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
291
+ /* @__PURE__ */ jsx2(Text, { bold: true, color, children: value.slice(0, c) }),
292
+ /* @__PURE__ */ jsx2(Text, { inverse: true, color, children: value[c] }),
293
+ /* @__PURE__ */ jsx2(Text, { bold: true, color, children: value.slice(c + 1) })
294
+ ] });
295
+ }
218
296
 
219
297
  // src/ui/dashboard.tsx
220
298
  import { memo as memo2 } from "react";
@@ -243,8 +321,58 @@ function planDisplay(rawPlans) {
243
321
  return { mode: "perRow", count: plans.length };
244
322
  }
245
323
 
324
+ // src/dashboard-data.ts
325
+ var emptyUsageSummary = () => ({
326
+ cost: 0,
327
+ tokens: 0,
328
+ input: 0,
329
+ cacheRead: 0,
330
+ cacheSavings: 0
331
+ });
332
+ function addUsageSummary(target, source) {
333
+ target.cost += source.cost;
334
+ target.tokens += source.tokens;
335
+ target.input += source.input;
336
+ target.cacheRead += source.cacheRead;
337
+ target.cacheSavings += source.cacheSavings;
338
+ }
339
+ function aggregateDashboardData(snapshots) {
340
+ const dashboards = snapshots.filter((value) => value != null);
341
+ if (dashboards.length === 0) return null;
342
+ const result = {
343
+ today: emptyUsageSummary(),
344
+ week: emptyUsageSummary(),
345
+ month: emptyUsageSummary(),
346
+ burnRate: 0,
347
+ series: Array.from({ length: Math.max(...dashboards.map((value) => value.series.length)) }, () => 0),
348
+ lastActivityAt: null
349
+ };
350
+ for (const dashboard of dashboards) {
351
+ addUsageSummary(result.today, dashboard.today);
352
+ addUsageSummary(result.week, dashboard.week);
353
+ addUsageSummary(result.month, dashboard.month);
354
+ result.burnRate += dashboard.burnRate;
355
+ if (dashboard.lastActivityAt !== null && (result.lastActivityAt === null || dashboard.lastActivityAt > result.lastActivityAt)) {
356
+ result.lastActivityAt = dashboard.lastActivityAt;
357
+ }
358
+ const offset = result.series.length - dashboard.series.length;
359
+ dashboard.series.forEach((value, index) => {
360
+ result.series[offset + index] += value;
361
+ });
362
+ }
363
+ return result;
364
+ }
365
+ function cachedTokenPercentage(summary) {
366
+ if (!Number.isFinite(summary.tokens) || !Number.isFinite(summary.cacheRead) || summary.tokens <= 0) return 0;
367
+ return Math.round(Math.min(100, Math.max(0, summary.cacheRead / summary.tokens * 100)));
368
+ }
369
+
246
370
  // src/ui/dashboard.tsx
247
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
371
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
372
+ function severityColor(remaining, crit, warn, base) {
373
+ const level = severity(remaining);
374
+ return level === "crit" ? crit : level === "warn" ? warn : base;
375
+ }
248
376
  var GAP = 2;
249
377
  var MIN_CARD = 56;
250
378
  var MIN_CARD_DENSE = 50;
@@ -262,8 +390,8 @@ function estimateCardHeights(groups, stats) {
262
390
  const multi = g.accounts.length > 1;
263
391
  g.accounts.forEach((a, i) => {
264
392
  const s = stats.get(a.id);
265
- const staleRow = s?.billing && !s.billing.error && s.billing.metrics.length > 0 && billingStaleLabel(s.billing.asOfMs ?? s.billingUpdatedAt, Date.now()) ? 1 : 0;
266
- const metricRows = (s?.billing?.metrics.length || 1) + staleRow;
393
+ const staleRow = s?.billing && !s.billing.error && quotaViews(s).length > 0 && billingStaleLabel(s.billing.asOfMs ?? s.billingUpdatedAt, Date.now()) ? 1 : 0;
394
+ const metricRows = (quotaViews(s).length || 1) + staleRow;
267
395
  h += metricRows + (multi ? 1 : 0) + (multi && i > 0 ? 1 : 0);
268
396
  });
269
397
  }
@@ -318,7 +446,7 @@ function computeDashLayout(groups, stats, cols, budget, focusId, layoutPref) {
318
446
  }
319
447
  return chooseLayout(content, budget, groups.length, single, cols, heights);
320
448
  }
321
- var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget, focusId, layout, page = 0, privacyMode = false, resetDisplay = "relative", tz }) {
449
+ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget, computed, page = 0, privacyMode = false, resetDisplay = "relative", tz }) {
322
450
  if (groups.length === 0) {
323
451
  return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
324
452
  "No providers enabled ",
@@ -327,13 +455,13 @@ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget,
327
455
  ] });
328
456
  }
329
457
  const content = Math.max(30, cols - 4);
330
- const { ncols, variant, cardsPerPage, pageCount } = computeDashLayout(groups, stats, cols, budget, focusId, layout);
458
+ const { ncols, variant, cardsPerPage, pageCount } = computed;
331
459
  let cardW = ncols <= 1 ? content : Math.floor((content - GAP * (ncols - 1)) / ncols);
332
460
  if (ncols === 1 && cardW > MAX_SINGLE_CARD) cardW = MAX_SINGLE_CARD;
333
461
  const pg = pageCount > 1 ? (page % pageCount + pageCount) % pageCount : 0;
334
462
  const visible = pageCount > 1 ? groups.slice(pg * cardsPerPage, pg * cardsPerPage + cardsPerPage) : groups;
335
463
  return /* @__PURE__ */ jsxs2(Box2, { height: budget, flexDirection: "column", overflow: "hidden", children: [
336
- /* @__PURE__ */ jsx2(Box2, { width: content, flexWrap: "wrap", columnGap: GAP, rowGap: 1, alignItems: "flex-start", children: visible.map((g) => /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, children: /* @__PURE__ */ jsx2(ProviderCard, { provider: g.provider, accounts: g.accounts, stats, width: cardW, variant, privacyMode, resetDisplay, tz }) }, g.provider)) }),
464
+ /* @__PURE__ */ jsx3(Box2, { width: content, flexWrap: "wrap", columnGap: GAP, rowGap: 1, alignItems: "flex-start", children: visible.map((g) => /* @__PURE__ */ jsx3(Box2, { flexShrink: 0, children: /* @__PURE__ */ jsx3(ProviderCard, { provider: g.provider, accounts: g.accounts, stats, width: cardW, variant, privacyMode, resetDisplay, tz }) }, g.provider)) }),
337
465
  pageCount > 1 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
338
466
  " ",
339
467
  glyphs().middot,
@@ -350,127 +478,143 @@ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget,
350
478
  ] });
351
479
  });
352
480
  function ProviderCard({ provider, accounts, stats, width, variant, privacyMode = false, resetDisplay, tz }) {
481
+ const theme = useTuiTheme();
353
482
  const meta = PROVIDERS[provider];
354
483
  const items = accounts.map((a) => ({ account: a, s: stats.get(a.id) }));
355
- const dashboards = items.map((i) => i.s?.dashboard).filter((d) => !!d);
356
- const agg = meta.hasUsage && dashboards.length > 0 ? aggregate(dashboards) : null;
484
+ const agg = meta.hasUsage ? aggregateDashboardData(items.map((i) => i.s?.dashboard)) : null;
357
485
  const planView = planDisplay(items.map((i) => i.s?.billing?.plan));
358
486
  const activity = items.map((i) => i.s?.billing?.activity).find(Boolean) ?? null;
359
487
  const inner = width - 4;
360
488
  const hasSpark = !!agg && agg.series.some((v) => v > 0);
361
489
  const showBars = variant !== "mini";
362
490
  const showSpark = variant === "full";
491
+ const headroom = items.find((item) => item.s?.providerHeadroom)?.s?.providerHeadroom;
363
492
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width, borderStyle: glyphs().border, borderColor: meta.color, paddingX: 1, children: [
364
493
  /* @__PURE__ */ jsxs2(Box2, { children: [
365
494
  /* @__PURE__ */ jsxs2(Text2, { color: meta.color, children: [
366
495
  glyphs().dot,
367
496
  " "
368
497
  ] }),
369
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: meta.color, children: meta.name }),
370
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
371
- planView.mode === "header" && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: planView.plan }),
498
+ /* @__PURE__ */ jsx3(Text2, { bold: true, color: meta.color, children: meta.name }),
499
+ /* @__PURE__ */ jsx3(Box2, { flexGrow: 1 }),
500
+ planView.mode === "header" && /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: planView.plan }),
372
501
  planView.mode === "perRow" && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
373
502
  planView.count,
374
503
  " accounts"
375
504
  ] })
376
505
  ] }),
377
- meta.hasUsage && (agg ? /* @__PURE__ */ jsxs2(Fragment, { children: [
378
- /* @__PURE__ */ jsx2(Box2, { height: 1 }),
379
- /* @__PURE__ */ jsx2(SummaryRow, { label: "Today", s: agg.today }),
380
- /* @__PURE__ */ jsx2(SummaryRow, { label: "This Week", s: agg.week }),
381
- /* @__PURE__ */ jsx2(SummaryRow, { label: "This Month", s: agg.month }),
382
- /* @__PURE__ */ jsx2(KpiLine, { agg })
383
- ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
384
- /* @__PURE__ */ jsx2(Box2, { height: 1 }),
506
+ headroom?.value != null && /* @__PURE__ */ jsxs2(Box2, { children: [
507
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "Usage " }),
508
+ /* @__PURE__ */ jsxs2(Text2, { bold: true, color: severityColor(headroom.value, theme.crit, theme.warn, meta.color), children: [
509
+ Math.round(usageFromHeadroom(headroom.value)),
510
+ "%"
511
+ ] })
512
+ ] }),
513
+ meta.hasUsage && (agg ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
514
+ /* @__PURE__ */ jsx3(Box2, { height: 1 }),
515
+ /* @__PURE__ */ jsx3(SummaryRow, { label: "Today", s: agg.today }),
516
+ /* @__PURE__ */ jsx3(SummaryRow, { label: "This Week", s: agg.week }),
517
+ /* @__PURE__ */ jsx3(SummaryRow, { label: "This Month", s: agg.month }),
518
+ /* @__PURE__ */ jsx3(KpiLine, { agg })
519
+ ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
520
+ /* @__PURE__ */ jsx3(Box2, { height: 1 }),
385
521
  /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
386
522
  "Fetching usage",
387
523
  glyphs().ellipsis
388
524
  ] })
389
525
  ] })),
390
- meta.hasBilling && showBars && /* @__PURE__ */ jsxs2(Fragment, { children: [
391
- meta.hasUsage && /* @__PURE__ */ jsx2(Rule, { inner }),
392
- /* @__PURE__ */ jsx2(LimitsBlock, { items, inner, showRowPlans: planView.mode === "perRow", privacyMode, resetDisplay, tz })
526
+ meta.hasBilling && showBars && /* @__PURE__ */ jsxs2(Fragment2, { children: [
527
+ meta.hasUsage && /* @__PURE__ */ jsx3(Rule, { inner }),
528
+ /* @__PURE__ */ jsx3(LimitsBlock, { items, inner, showRowPlans: planView.mode === "perRow", privacyMode, resetDisplay, tz, providerName: meta.name })
393
529
  ] }),
394
- meta.hasBilling && !showBars && !meta.hasUsage && /* @__PURE__ */ jsx2(CompactBilling, { items, privacyMode }),
395
- hasSpark && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
396
- /* @__PURE__ */ jsx2(Rule, { inner }),
397
- /* @__PURE__ */ jsx2(SparkFooter, { series: agg.series, month: agg.month.cost, color: meta.color })
530
+ meta.hasBilling && !showBars && !meta.hasUsage && /* @__PURE__ */ jsx3(CompactBilling, { items, privacyMode }),
531
+ hasSpark && showSpark && /* @__PURE__ */ jsxs2(Fragment2, { children: [
532
+ /* @__PURE__ */ jsx3(Rule, { inner }),
533
+ /* @__PURE__ */ jsx3(SparkFooter, { series: agg.series, month: agg.month.cost, color: meta.color })
398
534
  ] }),
399
- !meta.hasUsage && activity && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
400
- /* @__PURE__ */ jsx2(Rule, { inner }),
535
+ !meta.hasUsage && activity && showSpark && /* @__PURE__ */ jsxs2(Fragment2, { children: [
536
+ /* @__PURE__ */ jsx3(Rule, { inner }),
401
537
  /* @__PURE__ */ jsxs2(Box2, { children: [
402
- /* @__PURE__ */ jsx2(Box2, { width: 4, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "14d" }) }),
403
- /* @__PURE__ */ jsx2(Text2, { color: meta.color, children: sparkline(activity.series) }),
404
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: activity.summary }) })
538
+ /* @__PURE__ */ jsx3(Box2, { width: 4, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "14d" }) }),
539
+ /* @__PURE__ */ jsx3(Text2, { color: meta.color, children: sparkline(activity.series.slice(-14)) }),
540
+ /* @__PURE__ */ jsx3(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: activity.summary }) })
405
541
  ] })
406
542
  ] })
407
543
  ] });
408
544
  }
409
545
  function CompactBilling({ items, privacyMode }) {
410
- const billing = items.map((i) => i.s?.billing).find(Boolean);
546
+ const theme = useTuiTheme();
547
+ const stats = items.map((item) => item.s).find((value) => value?.billing);
548
+ const billing = stats?.billing;
411
549
  if (!billing) return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
412
550
  "Fetching",
413
551
  glyphs().ellipsis
414
552
  ] });
415
- if (billing.error) return /* @__PURE__ */ jsx2(Text2, { color: "red", children: privacyMode ? redactEmail(billing.error) : billing.error });
416
- const m = billing.metrics.find((x) => x.primary) ?? billing.metrics[0];
417
- if (!m) return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" });
418
- return /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: metricValueText(m) });
553
+ if (billing.error) return /* @__PURE__ */ jsx3(Text2, { color: theme.crit, children: privacyMode ? redactEmail(billing.error) : billing.error });
554
+ const quotas = quotaViews(stats);
555
+ const quota = quotas.find((value) => value.primary) ?? quotas[0];
556
+ if (!quota) return /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "No data" });
557
+ return /* @__PURE__ */ jsx3(Text2, { bold: true, color: theme.cost, children: quota.valueText });
419
558
  }
420
559
  function Rule({ inner }) {
421
- return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: glyphs().rule.repeat(Math.max(0, inner)) });
560
+ return /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: glyphs().rule.repeat(Math.max(0, inner)) });
422
561
  }
423
562
  function SummaryRow({ label, s }) {
424
- const cachedPct = s.tokens > 0 ? Math.round(s.cacheRead / s.tokens * 100) : 0;
563
+ const theme = useTuiTheme();
564
+ const cachedPct = cachedTokenPercentage(s);
425
565
  return /* @__PURE__ */ jsxs2(Box2, { children: [
426
- /* @__PURE__ */ jsx2(Box2, { width: 11, flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: label }) }),
427
- /* @__PURE__ */ jsx2(Box2, { width: 11, flexShrink: 0, justifyContent: "flex-end", children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", wrap: "truncate", children: currency(s.cost) }) }),
428
- /* @__PURE__ */ jsx2(Box2, { width: 13, flexShrink: 0, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate", children: [
566
+ /* @__PURE__ */ jsx3(Box2, { width: 11, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate", children: label }) }),
567
+ /* @__PURE__ */ jsx3(Box2, { width: 11, flexShrink: 0, justifyContent: "flex-end", children: /* @__PURE__ */ jsx3(Text2, { bold: true, color: theme.cost, wrap: "truncate", children: currency(s.cost) }) }),
568
+ /* @__PURE__ */ jsx3(Box2, { width: 13, flexShrink: 0, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate", children: [
429
569
  tokens(s.tokens),
430
570
  " tok"
431
571
  ] }) }),
432
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: cachedPct > 0 ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate", children: [
572
+ /* @__PURE__ */ jsx3(Box2, { flexGrow: 1, justifyContent: "flex-end", children: cachedPct > 0 ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate", children: [
433
573
  cachedPct,
434
574
  "% cached"
435
- ] }) : /* @__PURE__ */ jsx2(Text2, { children: " " }) })
575
+ ] }) : /* @__PURE__ */ jsx3(Text2, { children: " " }) })
436
576
  ] });
437
577
  }
438
578
  function KpiLine({ agg }) {
579
+ const theme = useTuiTheme();
439
580
  const hasBurn = agg.burnRate > 0;
440
581
  const hasSaved = agg.month.cacheSavings > 0;
441
582
  if (!hasBurn && !hasSaved) return null;
442
583
  return /* @__PURE__ */ jsxs2(Box2, { children: [
443
- hasBurn && /* @__PURE__ */ jsxs2(Fragment, { children: [
444
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Burn " }),
445
- /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
584
+ hasBurn && /* @__PURE__ */ jsxs2(Fragment2, { children: [
585
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "Burn " }),
586
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.crit, children: [
446
587
  currency(agg.burnRate),
447
588
  "/hr"
448
589
  ] })
449
590
  ] }),
450
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
451
- hasSaved && /* @__PURE__ */ jsxs2(Fragment, { children: [
452
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "Cache saved " }),
453
- /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
591
+ /* @__PURE__ */ jsx3(Box2, { flexGrow: 1 }),
592
+ hasSaved && /* @__PURE__ */ jsxs2(Fragment2, { children: [
593
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "Cache saved " }),
594
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.positive, children: [
454
595
  currency(agg.month.cacheSavings),
455
596
  "/mo"
456
597
  ] })
457
598
  ] })
458
599
  ] });
459
600
  }
460
- function accountTitle(account, billing, privacyMode = false) {
601
+ function accountTitle(account, billing, privacyMode = false, identity, providerName = "") {
602
+ if (identity) return accountIdentityText({ identity, name: account.name }, providerName);
461
603
  const email = billing?.email;
462
604
  const title = email && !account.name.includes("@") ? `${account.name} ${email}` : account.name;
463
605
  return privacyMode ? redactEmail(title) : title;
464
606
  }
465
- function LimitsBlock({ items, inner, showRowPlans, privacyMode, resetDisplay, tz }) {
607
+ function LimitsBlock({ items, inner, showRowPlans, privacyMode, resetDisplay, tz, providerName }) {
608
+ const theme = useTuiTheme();
466
609
  const showName = items.length > 1;
467
- const labels = items.flatMap((i) => i.s?.billing?.metrics ?? []).map((m) => m.label.length);
610
+ const labels = items.flatMap((i) => quotaViews(i.s)).map((quota) => quota.label.length);
468
611
  const labelW = Math.min(Math.max(7, ...labels) + 1, 14);
469
612
  const resetW = resetDisplay === "absolute" ? 17 : 8;
470
613
  const barW = Math.max(10, Math.min(46, inner - labelW - resetW - 5));
471
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map(({ account, s }, idx) => {
614
+ return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: items.map(({ account, s }, idx) => {
472
615
  const billing = s?.billing;
473
- const staleLabel = billing && !billing.error && billing.metrics.length > 0 ? billingStaleLabel(billing.asOfMs ?? s?.billingUpdatedAt, Date.now()) : null;
616
+ const quotas = quotaViews(s);
617
+ const staleLabel = billing && !billing.error && quotas.length > 0 ? billingStaleLabel(billing.asOfMs ?? s?.billingUpdatedAt, Date.now()) : null;
474
618
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: showName && idx > 0 ? 1 : 0, children: [
475
619
  showName && (() => {
476
620
  const rowPlan = showRowPlans ? normalizePlan(billing?.plan) : null;
@@ -483,18 +627,18 @@ function LimitsBlock({ items, inner, showRowPlans, privacyMode, resetDisplay, tz
483
627
  glyphs().dot,
484
628
  " "
485
629
  ] }),
486
- /* @__PURE__ */ jsx2(Text2, { bold: true, children: truncateName(accountTitle(account, billing, privacyMode), nameBudget) }),
487
- shownPlan && /* @__PURE__ */ jsxs2(Fragment, { children: [
488
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
489
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: shownPlan })
630
+ /* @__PURE__ */ jsx3(Text2, { bold: true, children: truncateName(accountTitle(account, billing, privacyMode, s?.identity, providerName), nameBudget) }),
631
+ shownPlan && /* @__PURE__ */ jsxs2(Fragment2, { children: [
632
+ /* @__PURE__ */ jsx3(Box2, { flexGrow: 1 }),
633
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: shownPlan })
490
634
  ] })
491
635
  ] });
492
636
  })(),
493
637
  !billing ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
494
638
  "Fetching",
495
639
  glyphs().ellipsis
496
- ] }) : billing.error ? /* @__PURE__ */ jsx2(Text2, { color: "red", wrap: "truncate-end", children: privacyMode ? redactEmail(billing.error) : 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, labelW, resetW, resetDisplay, tz }, `${m.label}${i}`)),
497
- staleLabel && /* @__PURE__ */ jsxs2(Text2, { color: "yellow", dimColor: true, children: [
640
+ ] }) : billing.error ? /* @__PURE__ */ jsx3(Text2, { color: theme.crit, wrap: "truncate-end", children: privacyMode ? redactEmail(billing.error) : billing.error }) : quotas.length === 0 ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "No data" }) : quotas.map((quota) => /* @__PURE__ */ jsx3(QuotaRow, { quota, color: account.color, barW, labelW, resetW, resetDisplay, tz }, quota.key)),
641
+ staleLabel && /* @__PURE__ */ jsxs2(Text2, { color: theme.warn, dimColor: true, children: [
498
642
  glyphs().warn,
499
643
  " ",
500
644
  staleLabel,
@@ -506,71 +650,45 @@ function LimitsBlock({ items, inner, showRowPlans, privacyMode, resetDisplay, tz
506
650
  ] }, account.id);
507
651
  }) });
508
652
  }
509
- function MetricRow({ m, color, barW, labelW, resetW, resetDisplay, tz }) {
510
- if (m.format.kind === "percent") {
511
- const barColor = m.used >= 90 ? "red" : m.used >= 75 ? "yellow" : color;
653
+ function quotaViews(stats) {
654
+ return resolveQuotaViews({ quotas: stats?.quotas, metrics: stats?.billing?.metrics });
655
+ }
656
+ function QuotaRow({ quota, color, barW, labelW, resetW, resetDisplay, tz }) {
657
+ const theme = useTuiTheme();
658
+ if (quota.bounded && quota.usedPct !== null) {
659
+ const used = quota.usedPct;
660
+ const remaining = quota.remainingPct ?? 0;
661
+ const barColor = severityColor(remaining, theme.crit, theme.warn, color);
512
662
  return /* @__PURE__ */ jsxs2(Box2, { children: [
513
- /* @__PURE__ */ jsx2(Box2, { width: labelW, flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: m.label }) }),
514
- /* @__PURE__ */ jsx2(Bar, { pct: m.used, color: barColor, width: barW }),
515
- /* @__PURE__ */ jsx2(Box2, { width: 5, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { bold: true, children: [
516
- Math.round(m.used),
663
+ /* @__PURE__ */ jsx3(Box2, { width: labelW, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate", children: quota.label }) }),
664
+ /* @__PURE__ */ jsx3(Bar, { pct: used, color: barColor, width: barW }),
665
+ /* @__PURE__ */ jsx3(Box2, { width: 5, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { bold: true, children: [
666
+ Math.round(used),
517
667
  "%"
518
668
  ] }) }),
519
- /* @__PURE__ */ jsx2(Box2, { width: resetW, justifyContent: "flex-end", children: m.resetsAt ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: resetAt(m.resetsAt, resetDisplay, Date.now(), tz) }) : /* @__PURE__ */ jsx2(Text2, { children: " " }) })
669
+ /* @__PURE__ */ jsx3(Box2, { width: resetW, justifyContent: "flex-end", children: quota.resetsAt ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: resetAt(new Date(quota.resetsAt).toISOString(), resetDisplay, Date.now(), tz) }) : /* @__PURE__ */ jsx3(Text2, { children: " " }) })
520
670
  ] });
521
671
  }
522
672
  return /* @__PURE__ */ jsxs2(Box2, { children: [
523
- /* @__PURE__ */ jsx2(Box2, { width: labelW, flexShrink: 0, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: m.label }) }),
524
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: metricValueText(m) })
673
+ /* @__PURE__ */ jsx3(Box2, { width: labelW, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate", children: quota.label }) }),
674
+ /* @__PURE__ */ jsx3(Text2, { bold: true, color: theme.cost, children: quota.valueText })
525
675
  ] });
526
676
  }
527
677
  function SparkFooter({ series, month, color }) {
528
678
  return /* @__PURE__ */ jsxs2(Box2, { children: [
529
- /* @__PURE__ */ jsx2(Box2, { width: 4, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "14d" }) }),
530
- /* @__PURE__ */ jsx2(Text2, { color, children: sparkline(series) }),
531
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
679
+ /* @__PURE__ */ jsx3(Box2, { width: 4, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "14d" }) }),
680
+ /* @__PURE__ */ jsx3(Text2, { color, children: sparkline(series.slice(-14)) }),
681
+ /* @__PURE__ */ jsx3(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
532
682
  currency(month),
533
683
  " mo"
534
684
  ] }) })
535
685
  ] });
536
686
  }
537
- function aggregate(list) {
538
- const zero = () => ({ cost: 0, tokens: 0, input: 0, cacheRead: 0, cacheSavings: 0 });
539
- const z = { today: zero(), week: zero(), month: zero(), burnRate: 0, series: [] };
540
- for (const d of list) {
541
- for (const k of ["today", "week", "month"]) {
542
- z[k].cost += d[k].cost;
543
- z[k].tokens += d[k].tokens;
544
- z[k].input += d[k].input ?? 0;
545
- z[k].cacheRead += d[k].cacheRead;
546
- z[k].cacheSavings += d[k].cacheSavings;
547
- }
548
- z.burnRate += d.burnRate;
549
- d.series.forEach((v, i) => {
550
- z.series[i] = (z.series[i] ?? 0) + v;
551
- });
552
- }
553
- return z;
554
- }
555
687
  var TotalsRow = memo2(function TotalsRow2({ groups, stats, cols }) {
556
- const zero = () => ({ cost: 0, tokens: 0, input: 0, cacheRead: 0, cacheSavings: 0 });
557
- const t = zero(), w = zero(), m = zero();
558
- for (const g of groups) {
559
- if (!PROVIDERS[g.provider].hasUsage) continue;
560
- for (const a of g.accounts) {
561
- const d = stats.get(a.id)?.dashboard;
562
- if (!d) continue;
563
- t.cost += d.today.cost;
564
- t.tokens += d.today.tokens;
565
- t.input += d.today.input ?? 0;
566
- w.cost += d.week.cost;
567
- w.tokens += d.week.tokens;
568
- w.input += d.week.input ?? 0;
569
- m.cost += d.month.cost;
570
- m.tokens += d.month.tokens;
571
- m.input += d.month.input ?? 0;
572
- }
573
- }
688
+ const aggregate = aggregateDashboardData(groups.flatMap((group) => PROVIDERS[group.provider].hasUsage ? group.accounts.map((account) => stats.get(account.id)?.dashboard) : []));
689
+ const t = aggregate?.today ?? { cost: 0, tokens: 0, input: 0, cacheRead: 0, cacheSavings: 0 };
690
+ const w = aggregate?.week ?? { cost: 0, tokens: 0, input: 0, cacheRead: 0, cacheSavings: 0 };
691
+ const m = aggregate?.month ?? { cost: 0, tokens: 0, input: 0, cacheRead: 0, cacheSavings: 0 };
574
692
  const inner = cols - 4;
575
693
  const dot = glyphs().middot;
576
694
  const monthNonCached = m.input > 0 ? `, ${tokens(m.input)} non-cached` : "";
@@ -579,920 +697,1219 @@ var TotalsRow = memo2(function TotalsRow2({ groups, stats, cols }) {
579
697
  const noTok = `${glyphs().dotAll} Today ${currency(t.cost)} ${dot} Week ${currency(w.cost)} ${dot} Month ${currency(m.cost)}${m.input > 0 ? ` (${tokens(m.input)} non-cached)` : ""}`;
580
698
  const tight = `${glyphs().dotAll} ${currency(t.cost)} ${dot} ${currency(w.cost)} ${dot} ${currency(m.cost)}`;
581
699
  const text = full.length <= inner ? full : monthOnly.length <= inner ? monthOnly : noTok.length <= inner ? noTok : tight;
582
- return /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: text }) });
700
+ return /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: text }) });
583
701
  });
584
702
 
585
703
  // src/ui/table.tsx
586
- import { memo as memo4 } from "react";
587
- import { Box as Box4, Text as Text4 } from "ink";
588
-
589
- // src/ui/settings.tsx
590
704
  import { memo as memo3 } from "react";
591
705
  import { Box as Box3, Text as Text3 } from "ink";
592
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
593
- var GENERAL_ROWS = 11;
594
- var SETTINGS_TABS = ["general", "providers", "accounts"];
595
- var SETTINGS_TAB_LABELS = {
596
- general: "General",
597
- providers: "Providers",
598
- accounts: "Accounts"
599
- };
600
- var FORM_FIELDS = ["provider", "name", "homeDir", "color"];
601
- var SettingsView = memo3(function SettingsView2({
602
- config,
603
- cursor,
604
- activeTab,
605
- tzEdit,
606
- tzCaret,
607
- tzError,
608
- allowedHostsEdit,
609
- allowedHostsCaret,
610
- allowedHostsError,
611
- resolvedTz,
612
- accountForm,
613
- activeAccountId,
614
- trackedAccounts,
615
- accountIdentities
616
- }) {
617
- if (accountForm) return /* @__PURE__ */ jsx3(AccountFormView, { form: accountForm, accounts: config.accounts });
618
- const editingTz = tzEdit !== null;
619
- const editingAllowedHosts = allowedHostsEdit !== null;
620
- const tzDisplay = config.timezone === null ? `System (${resolvedTz})` : config.timezone;
621
- const tabFocused = cursor < 0;
622
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
623
- /* @__PURE__ */ jsx3(Text3, { bold: true, children: "Settings" }),
624
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: configLocation() }),
625
- /* @__PURE__ */ jsx3(Box3, { height: 1 }),
626
- /* @__PURE__ */ jsx3(SettingsTabBar, { active: activeTab, focused: tabFocused }),
627
- /* @__PURE__ */ jsx3(Box3, { height: 1 }),
628
- activeTab === "general" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
629
- /* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "General" }),
630
- /* @__PURE__ */ jsxs3(Row, { cursor, idx: 0, label: "Refresh interval", children: [
631
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
632
- glyphs().caretL,
633
- " "
634
- ] }),
635
- /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "yellow", children: [
636
- config.interval,
637
- "s"
638
- ] }),
639
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
640
- " ",
641
- glyphs().caretR
642
- ] })
643
- ] }),
644
- /* @__PURE__ */ jsxs3(Row, { cursor, idx: 1, label: "Billing poll", children: [
645
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
646
- glyphs().caretL,
647
- " "
648
- ] }),
649
- /* @__PURE__ */ jsxs3(Text3, { bold: true, color: "yellow", children: [
650
- config.billingInterval,
651
- "m"
652
- ] }),
653
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
654
- " ",
655
- glyphs().caretR
656
- ] })
657
- ] }),
658
- /* @__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" }) }),
659
- /* @__PURE__ */ jsx3(Row, { cursor, idx: 3, label: "Privacy mode", children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: config.privacyMode ? "green" : "red", children: config.privacyMode ? "on" : "off" }) }),
660
- /* @__PURE__ */ jsx3(Row, { cursor, idx: 4, label: "Privacy key", children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.privacyToggleKey === " " ? "space" : config.privacyToggleKey }) }),
661
- /* @__PURE__ */ jsx3(Row, { cursor, idx: 5, label: "Timezone", children: editingTz ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
662
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[" }),
663
- /* @__PURE__ */ jsx3(CaretText, { value: tzEdit ?? "", caret: tzCaret, color: "cyan" }),
664
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "]" })
665
- ] }) : /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: tzDisplay }) }),
666
- cursor === 5 && tzError && /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
667
- " ",
668
- tzError
669
- ] }),
670
- /* @__PURE__ */ jsxs3(Row, { cursor, idx: 6, label: "Dashboard", children: [
671
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
672
- glyphs().caretL,
673
- " "
674
- ] }),
675
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.dashboardLayout === "grid" ? "grid (all)" : "single (cycle)" }),
676
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
677
- " ",
678
- glyphs().caretR
679
- ] })
680
- ] }),
681
- /* @__PURE__ */ jsxs3(Row, { cursor, idx: 7, label: "Default focus", children: [
682
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
683
- glyphs().caretL,
684
- " "
685
- ] }),
686
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.defaultFocus === "all" ? "All" : "Last account" }),
687
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
688
- " ",
689
- glyphs().caretR
690
- ] })
691
- ] }),
692
- /* @__PURE__ */ jsx3(Row, { cursor, idx: 8, label: "Network access", children: /* @__PURE__ */ jsx3(Text3, { bold: true, color: config.allowNetworkAccess ? "red" : "green", children: config.allowNetworkAccess ? "LAN (unsafe)" : "local only" }) }),
693
- /* @__PURE__ */ jsx3(Row, { cursor, idx: 9, label: "Allowed hosts", children: editingAllowedHosts ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
694
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[" }),
695
- /* @__PURE__ */ jsx3(CaretText, { value: allowedHostsEdit ?? "", caret: allowedHostsCaret, color: "cyan" }),
696
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "]" })
697
- ] }) : /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.allowedHosts.join(", ") || "none" }) }),
698
- cursor === 9 && allowedHostsError && /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
706
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
707
+ var MONTHS = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
708
+ var TableProviderBar = memo3(function TableProviderBar2({ providers, active, onSelect }) {
709
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
710
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "provider " }),
711
+ providers.map((p) => {
712
+ const meta = PROVIDERS[p];
713
+ return /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onSelect(p), marginRight: 1, children: p === active ? /* @__PURE__ */ jsxs3(Text3, { bold: true, color: meta.color, inverse: true, children: [
714
+ " ",
715
+ meta.name,
716
+ " "
717
+ ] }) : /* @__PURE__ */ jsxs3(Text3, { color: meta.color, dimColor: true, children: [
718
+ " ",
719
+ meta.name,
720
+ " "
721
+ ] }) }, p);
722
+ }),
723
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: " p/P switch" })
724
+ ] });
725
+ });
726
+ var ControlBar = memo3(function ControlBar2({ views, period, sort, model, search, searchCaret, searching, showPeriod, showModel }) {
727
+ const theme = useTuiTheme();
728
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
729
+ showPeriod && /* @__PURE__ */ jsxs3(Fragment3, { children: [
730
+ views.map((v, i) => /* @__PURE__ */ jsx4(Box3, { marginRight: 2, children: i === period ? /* @__PURE__ */ jsxs3(Text3, { bold: true, color: theme.accent, children: [
731
+ "[",
732
+ v,
733
+ "]"
734
+ ] }) : /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: v }) }, v)),
735
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: " " })
736
+ ] }),
737
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "sort " }),
738
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.accent, children: sort }),
739
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
740
+ " o cycle ",
741
+ glyphs().middot,
742
+ " "
743
+ ] }),
744
+ showModel && /* @__PURE__ */ jsxs3(Fragment3, { children: [
745
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "model " }),
746
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: model ? modelColor(model) : theme.accent, children: model ?? "all" }),
747
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
748
+ " m model ",
749
+ glyphs().middot,
750
+ " "
751
+ ] })
752
+ ] }),
753
+ searching ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
754
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "/" }),
755
+ /* @__PURE__ */ jsx4(CaretText, { value: search, caret: searchCaret, color: theme.accent })
756
+ ] }) : search ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
757
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "filter " }),
758
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.positive, children: search }),
759
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
760
+ " (/ edit ",
761
+ glyphs().middot,
762
+ " esc clear)"
763
+ ] })
764
+ ] }) : /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "/ filter" })
765
+ ] });
766
+ });
767
+ var TokenTable = memo3(function TokenTable2({ rows, cursor, expanded, maxRows, cols, onRowClick }) {
768
+ const theme = useTuiTheme();
769
+ if (rows.length === 0) return /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: "No usage in this period (or filtered out)." });
770
+ const wide = cols > 90;
771
+ 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 };
772
+ const fixed = base.label + base.input + base.output + base.cc + base.cr + base.total + base.cost;
773
+ const available = cols - fixed - 6;
774
+ const W = { ...base, models: Math.max(wide ? 22 : 14, available) };
775
+ const lineW = W.label + W.models + W.input + W.output + W.cc + W.cr + W.total + W.cost;
776
+ const totals = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, cost: 0 };
777
+ for (const r of rows) {
778
+ totals.input += r.input;
779
+ totals.output += r.output;
780
+ totals.cacheCreate += r.cacheCreate;
781
+ totals.cacheRead += r.cacheRead;
782
+ totals.cost += r.cost;
783
+ }
784
+ const clampedCursor = Math.min(cursor, rows.length - 1);
785
+ const scrollStart = Math.max(0, Math.min(clampedCursor - Math.floor(maxRows / 2), rows.length - maxRows));
786
+ const visible = rows.slice(scrollStart, scrollStart + maxRows);
787
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
788
+ /* @__PURE__ */ jsxs3(Text3, { children: [
789
+ /* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
699
790
  " ",
700
- allowedHostsError
791
+ col("Date", W.label, "left")
701
792
  ] }),
702
- config.allowNetworkAccess && /* @__PURE__ */ jsx3(Text3, { color: "red", children: " Warning: dashboard data and settings will be exposed to your local network after daemon restart." }),
703
- /* @__PURE__ */ jsxs3(Row, { cursor, idx: 10, label: "Reset times", children: [
704
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
705
- glyphs().caretL,
706
- " "
707
- ] }),
708
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.resetDisplay === "relative" ? "time remaining" : "exact date/time" }),
709
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
710
- " ",
711
- glyphs().caretR
712
- ] })
713
- ] })
793
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: col("Models", W.models, "left") }),
794
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: col("Input", W.input) }),
795
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: col("Output", W.output) }),
796
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: col(wide ? "Cache Create" : "CchCrt", W.cc) }),
797
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: col(wide ? "Cache Read" : "CchRd", W.cr) }),
798
+ W.total > 0 && /* @__PURE__ */ jsx4(Text3, { bold: true, children: col("Total", W.total) }),
799
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: col("Cost", W.cost) })
714
800
  ] }),
715
- activeTab === "providers" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
716
- /* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "Providers" }),
717
- PROVIDER_ORDER.map((pid, i) => {
718
- const selected = cursor === i;
719
- const enabled = !config.disabledProviders.includes(pid);
720
- const p = PROVIDERS[pid];
721
- return /* @__PURE__ */ jsxs3(Box3, { children: [
722
- /* @__PURE__ */ jsxs3(Text3, { color: selected ? "green" : void 0, children: [
723
- selected ? glyphs().caretR : " ",
724
- " "
725
- ] }),
726
- /* @__PURE__ */ jsx3(Text3, { bold: enabled, color: enabled ? p.color : void 0, dimColor: !enabled, children: enabled ? `[${glyphs().check}]` : "[ ]" }),
727
- /* @__PURE__ */ jsxs3(Text3, { color: p.color, children: [
728
- " ",
729
- glyphs().dot,
730
- " "
801
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
802
+ visible.map((r, vi) => {
803
+ const idx = scrollStart + vi;
804
+ const selected = idx === clampedCursor;
805
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
806
+ /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs3(Text3, { inverse: selected, children: [
807
+ /* @__PURE__ */ jsxs3(Text3, { color: selected ? void 0 : theme.accent, children: [
808
+ selected ? `${glyphs().caretR} ` : " ",
809
+ col(fmtLabel(r.label), W.label, "left")
731
810
  ] }),
732
- /* @__PURE__ */ jsx3(Box3, { width: 9, children: /* @__PURE__ */ jsx3(Text3, { bold: selected, children: p.name }) }),
733
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: enabled ? "tracking" : "off" })
734
- ] }, pid);
735
- })
736
- ] }),
737
- activeTab === "accounts" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
738
- /* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "Accounts" }),
739
- trackedAccounts.length === 0 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
740
- " none tracked ",
741
- glyphs().emDash,
742
- " enable a provider or add an account"
811
+ /* @__PURE__ */ jsx4(Text3, { dimColor: !selected, children: col(r.models.join(", "), W.models, "left") }),
812
+ /* @__PURE__ */ jsx4(Text3, { children: col(tokens(r.input), W.input) }),
813
+ /* @__PURE__ */ jsx4(Text3, { children: col(tokens(r.output), W.output) }),
814
+ /* @__PURE__ */ jsx4(Text3, { children: col(tokens(r.cacheCreate), W.cc) }),
815
+ /* @__PURE__ */ jsx4(Text3, { children: col(tokens(r.cacheRead), W.cr) }),
816
+ W.total > 0 && /* @__PURE__ */ jsx4(Text3, { children: col(tokens(r.total), W.total) }),
817
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: selected ? void 0 : theme.cost, children: col(currency(r.cost), W.cost) })
818
+ ] }) }),
819
+ idx === expanded && /* @__PURE__ */ jsx4(RowDetail, { row: r, indent: W.label + 2 })
820
+ ] }, r.label);
821
+ }),
822
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
823
+ /* @__PURE__ */ jsxs3(Text3, { children: [
824
+ /* @__PURE__ */ jsxs3(Text3, { bold: true, color: theme.accent, children: [
825
+ " ",
826
+ col("Total", W.label, "left")
743
827
  ] }),
744
- trackedAccounts.map((acc, i) => {
745
- const selected = cursor === i;
746
- const isActive = acc.id === activeAccountId;
747
- const provider = PROVIDERS[acc.providerId];
748
- const identity = accountIdentities.get(acc.id);
749
- const rawIdentityLabel = identity?.email || identity?.displayName || acc.name;
750
- const identityLabel = config.privacyMode ? redactEmail(rawIdentityLabel) : rawIdentityLabel;
751
- const plan = identity?.plan ?? null;
752
- return /* @__PURE__ */ jsxs3(Box3, { children: [
753
- /* @__PURE__ */ jsxs3(Text3, { color: selected ? "green" : void 0, children: [
754
- selected ? glyphs().caretR : " ",
755
- " "
756
- ] }),
757
- /* @__PURE__ */ jsxs3(Text3, { color: acc.color || provider.color, children: [
758
- isActive ? glyphs().dot : glyphs().radioOff,
759
- " "
760
- ] }),
761
- /* @__PURE__ */ jsx3(Box3, { width: 28, children: /* @__PURE__ */ jsx3(Text3, { bold: true, children: truncateName(identityLabel, 27) }) }),
762
- /* @__PURE__ */ jsx3(Box3, { width: 9, children: /* @__PURE__ */ jsx3(Text3, { color: provider.color, children: provider.name }) }),
763
- /* @__PURE__ */ jsx3(Box3, { width: 18, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: plan ? truncateName(plan, 17) : "" }) }),
764
- /* @__PURE__ */ jsx3(Box3, { width: 12, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: acc.source === "auto" ? "auto tracking" : "configured" }) }),
765
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: truncateName(acc.homeDir, 24) })
766
- ] }, `${acc.source}:${acc.id}`);
767
- }),
768
- /* @__PURE__ */ jsxs3(Box3, { children: [
769
- /* @__PURE__ */ jsxs3(Text3, { color: cursor === trackedAccounts.length ? "green" : void 0, children: [
770
- cursor === trackedAccounts.length ? glyphs().caretR : " ",
771
- " "
772
- ] }),
773
- /* @__PURE__ */ jsx3(Text3, { color: "greenBright", children: "+ " }),
774
- /* @__PURE__ */ jsx3(Text3, { children: "Add account" })
775
- ] })
828
+ /* @__PURE__ */ jsx4(Text3, { children: col("", W.models, "left") }),
829
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.cost, children: col(tokens(totals.input), W.input) }),
830
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.cost, children: col(tokens(totals.output), W.output) }),
831
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.cost, children: col(tokens(totals.cacheCreate), W.cc) }),
832
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.cost, children: col(tokens(totals.cacheRead), W.cr) }),
833
+ W.total > 0 && /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.cost, children: col(tokens(totals.input + totals.output + totals.cacheCreate + totals.cacheRead), W.total) }),
834
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.cost, children: col(currency(totals.cost), W.cost) })
776
835
  ] }),
777
- /* @__PURE__ */ jsx3(Box3, { height: 1 }),
778
- tabFocused ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
779
- glyphs().arrowL,
780
- glyphs().arrowR,
781
- "/tab switch section ",
782
- glyphs().middot,
783
- " ",
784
- glyphs().arrowD,
785
- " rows ",
786
- glyphs().middot,
787
- " s/Esc close"
788
- ] }) : editingTz ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
789
- "type IANA name (e.g. Europe/London) ",
790
- glyphs().middot,
791
- " empty = System ",
792
- glyphs().middot,
793
- " Enter save ",
794
- glyphs().middot,
795
- " Esc cancel"
796
- ] }) : editingAllowedHosts ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
797
- "comma-separated exact DNS names ",
798
- glyphs().middot,
799
- " Enter save ",
800
- glyphs().middot,
801
- " Esc cancel"
802
- ] }) : activeTab === "providers" ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
836
+ /* @__PURE__ */ jsx4(Box3, { height: 1 }),
837
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
803
838
  glyphs().arrowU,
804
839
  glyphs().arrowD,
805
- " select ",
806
- glyphs().middot,
807
- " space toggle provider ",
808
- glyphs().middot,
809
- " s/Esc close"
810
- ] }) : activeTab === "accounts" && cursor >= 0 && cursor < trackedAccounts.length ? trackedAccounts[cursor]?.source === "auto" ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
811
- glyphs().arrowU,
812
- glyphs().arrowD,
813
- " select ",
840
+ " navigate ",
814
841
  glyphs().middot,
815
- " Enter configure ",
842
+ " Enter detail ",
816
843
  glyphs().middot,
817
- " space activate ",
844
+ " o sort ",
818
845
  glyphs().middot,
819
- " s/Esc close"
820
- ] }) : /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
821
- glyphs().arrowU,
822
- glyphs().arrowD,
823
- " select ",
846
+ " g/G top/bottom ",
824
847
  glyphs().middot,
825
848
  " ",
826
- glyphs().shift,
827
- glyphs().arrowU,
828
- glyphs().arrowD,
829
- " reorder ",
830
- glyphs().middot,
831
- " Enter edit ",
832
- glyphs().middot,
833
- " space activate ",
834
- glyphs().middot,
835
- " d delete ",
836
- glyphs().middot,
837
- " s/Esc close"
838
- ] }) : activeTab === "accounts" && cursor === trackedAccounts.length ? /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
839
- glyphs().arrowU,
840
- glyphs().arrowD,
841
- " select ",
842
- glyphs().middot,
843
- " Enter add account ",
844
- glyphs().middot,
845
- " s/Esc close"
846
- ] }) : /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
847
- glyphs().arrowU,
848
- glyphs().arrowD,
849
- " select ",
850
- glyphs().arrowL,
851
- glyphs().arrowR,
852
- " adjust Enter edit tab switch section s/Esc close"
849
+ clampedCursor + 1,
850
+ "/",
851
+ rows.length
853
852
  ] })
854
853
  ] });
855
854
  });
856
- function SettingsTabBar({ active, focused }) {
857
- return /* @__PURE__ */ jsxs3(Box3, { children: [
858
- /* @__PURE__ */ jsxs3(Text3, { color: focused ? "green" : void 0, children: [
859
- focused ? glyphs().caretR : " ",
855
+ function RowDetail({ row, indent }) {
856
+ const theme = useTuiTheme();
857
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", paddingLeft: indent, children: row.breakdown.map((m, i) => /* @__PURE__ */ jsxs3(Text3, { children: [
858
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
859
+ i === row.breakdown.length - 1 ? glyphs().treeEnd : glyphs().treeMid,
860
860
  " "
861
861
  ] }),
862
- SETTINGS_TABS.map((tab, i) => {
863
- const selected = tab === active;
864
- return /* @__PURE__ */ jsxs3(Box3, { marginRight: 1, children: [
865
- selected ? /* @__PURE__ */ jsxs3(Text3, { bold: true, inverse: true, children: [
866
- " ",
867
- SETTINGS_TAB_LABELS[tab],
862
+ /* @__PURE__ */ jsx4(Text3, { bold: true, children: col(m.name, 16, "left") }),
863
+ /* @__PURE__ */ jsxs3(Text3, { children: [
864
+ col(tokens(m.input), 8),
865
+ " in "
866
+ ] }),
867
+ /* @__PURE__ */ jsxs3(Text3, { children: [
868
+ col(tokens(m.output), 8),
869
+ " out "
870
+ ] }),
871
+ /* @__PURE__ */ jsxs3(Text3, { children: [
872
+ col(tokens(m.cacheCreate), 8),
873
+ " cc "
874
+ ] }),
875
+ /* @__PURE__ */ jsxs3(Text3, { children: [
876
+ col(tokens(m.cacheRead), 9),
877
+ " cr "
878
+ ] }),
879
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.cost, children: currency(m.cost) })
880
+ ] }, m.name)) });
881
+ }
882
+ function fmtLabel(label) {
883
+ if (label.length === 7 && label[4] === "-") {
884
+ return `${MONTHS[Number(label.slice(5, 7))]} '${label.slice(2, 4)}`;
885
+ }
886
+ return shortDate(label);
887
+ }
888
+
889
+ // src/ui/onboarding.tsx
890
+ import { Box as Box4, Text as Text4 } from "ink";
891
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
892
+ 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." }) {
893
+ const theme = useTuiTheme();
894
+ const anyEnabled = items.some((it) => it.enabled);
895
+ const startIdx = items.length;
896
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
897
+ /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsx5(Text4, { bold: true, color: theme.accent, children: heading }) }),
898
+ /* @__PURE__ */ jsx5(Text4, { dimColor: true, children: subheading }),
899
+ /* @__PURE__ */ jsx5(Box4, { height: 1 }),
900
+ items.map((it, i) => {
901
+ const selected = cursor === i;
902
+ const box = it.enabled ? `[${glyphs().check}]` : "[ ]";
903
+ return /* @__PURE__ */ jsxs4(ClickableBox, { onClick: () => onToggle(i), children: [
904
+ /* @__PURE__ */ jsxs4(Text4, { color: selected ? theme.accent : void 0, children: [
905
+ selected ? glyphs().caretR : " ",
868
906
  " "
869
- ] }) : /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
907
+ ] }),
908
+ /* @__PURE__ */ jsx5(Text4, { bold: it.enabled, color: it.enabled ? it.color : void 0, dimColor: !it.enabled, children: box }),
909
+ /* @__PURE__ */ jsxs4(Text4, { color: it.color, children: [
870
910
  " ",
871
- SETTINGS_TAB_LABELS[tab],
911
+ glyphs().dot,
872
912
  " "
873
913
  ] }),
874
- i < SETTINGS_TABS.length - 1 && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " " })
875
- ] }, tab);
876
- })
914
+ /* @__PURE__ */ jsx5(Box4, { width: 13, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text4, { bold: selected, dimColor: !it.detected && !it.enabled, wrap: "truncate", children: it.name }) }),
915
+ it.detected ? /* @__PURE__ */ jsx5(Text4, { color: theme.ok, dimColor: true, children: "installed" }) : it.enabled ? /* @__PURE__ */ jsx5(Text4, { color: theme.warn, dimColor: true, children: "manual" }) : /* @__PURE__ */ jsx5(Text4, { dimColor: true, children: "not found" })
916
+ ] }, it.id);
917
+ }),
918
+ /* @__PURE__ */ jsx5(Box4, { height: 1 }),
919
+ /* @__PURE__ */ jsxs4(ClickableBox, { onClick: onConfirm, children: [
920
+ /* @__PURE__ */ jsxs4(Text4, { color: cursor === startIdx ? theme.accent : void 0, children: [
921
+ cursor === startIdx ? glyphs().caretR : " ",
922
+ " "
923
+ ] }),
924
+ /* @__PURE__ */ jsx5(Text4, { bold: true, color: anyEnabled ? theme.accent : void 0, dimColor: !anyEnabled, children: anyEnabled ? `${glyphs().play} Start tokmon` : `${glyphs().play} Start (nothing selected)` })
925
+ ] }),
926
+ /* @__PURE__ */ jsx5(Box4, { height: 1 }),
927
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
928
+ glyphs().arrowU,
929
+ glyphs().arrowD,
930
+ " move ",
931
+ glyphs().middot,
932
+ " space toggle ",
933
+ glyphs().middot,
934
+ " enter start ",
935
+ glyphs().middot,
936
+ " q quit"
937
+ ] })
877
938
  ] });
878
939
  }
879
- function CaretText({ value, caret, color }) {
880
- const c = Math.max(0, Math.min(caret, value.length));
881
- if (c >= value.length) {
882
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
883
- /* @__PURE__ */ jsx3(Text3, { bold: true, color, children: value }),
884
- /* @__PURE__ */ jsx3(Text3, { color, children: glyphs().vbar })
885
- ] });
886
- }
887
- return /* @__PURE__ */ jsxs3(Fragment2, { children: [
888
- /* @__PURE__ */ jsx3(Text3, { bold: true, color, children: value.slice(0, c) }),
889
- /* @__PURE__ */ jsx3(Text3, { inverse: true, color, children: value[c] }),
890
- /* @__PURE__ */ jsx3(Text3, { bold: true, color, children: value.slice(c + 1) })
891
- ] });
940
+
941
+ // src/ui/loading.tsx
942
+ import { useState as useState2, useEffect as useEffect2 } from "react";
943
+ import { Box as Box5, Text as Text5 } from "ink";
944
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
945
+ function accountReady(s, providerId) {
946
+ if (!s) return false;
947
+ const p = PROVIDERS[providerId];
948
+ if (p.hasBilling && (s.billingState === "error" || s.billing?.error)) return true;
949
+ if (p.hasUsage && s.summaryState !== "ready" && s.summaryState !== "error") return false;
950
+ if (p.hasBilling && s.billingState !== "ready" && s.billingState !== "error") return false;
951
+ return true;
892
952
  }
893
- function Row({ cursor, idx, label, children }) {
894
- return /* @__PURE__ */ jsxs3(Box3, { children: [
895
- /* @__PURE__ */ jsxs3(Text3, { color: cursor === idx ? "green" : void 0, children: [
896
- cursor === idx ? glyphs().caretR : " ",
897
- " "
898
- ] }),
899
- /* @__PURE__ */ jsx3(Box3, { width: 20, children: /* @__PURE__ */ jsx3(Text3, { children: label }) }),
900
- children
901
- ] });
953
+ function statsReadyInput(s) {
954
+ if (!s) return void 0;
955
+ return {
956
+ summaryState: s.dashboard ? "ready" : "pending",
957
+ billingState: s.billing?.error ? "error" : s.billing ? "ready" : "pending",
958
+ billing: s.billing
959
+ };
902
960
  }
903
- function AccountFormView({ form, accounts }) {
904
- const previewId = form.mode === "add" ? generateAccountId(form.name || "account", accounts) : form.editingId ?? "";
905
- const accent = form.color;
906
- const stepIndex = { provider: 1, name: 2, homeDir: 3, color: 4 };
907
- const step = stepIndex[form.field];
908
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
909
- /* @__PURE__ */ jsxs3(Box3, { children: [
910
- /* @__PURE__ */ jsx3(Text3, { color: accent, bold: true, children: glyphs().vbar }),
911
- /* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
912
- " ",
913
- form.mode === "add" ? "NEW ACCOUNT" : "EDIT ACCOUNT"
961
+ function groupTodayCost(items) {
962
+ return items.reduce((sum, s) => {
963
+ const d = s?.dashboard;
964
+ return sum + (d?.today.cost ?? 0);
965
+ }, 0);
966
+ }
967
+ function headlineFor(group, items, privacyMode = false) {
968
+ const meta = PROVIDERS[group.provider];
969
+ if (meta.hasUsage) return `${currency(groupTodayCost(items))} today`;
970
+ const billing = items.map((s) => s?.billing).find(Boolean);
971
+ if (!billing) return "no data";
972
+ if (billing.error) return privacyMode ? redactEmail(billing.error) : billing.error;
973
+ const m = billing.metrics[0];
974
+ if (m) return metricValueText(m);
975
+ return billing.plan ?? "no data";
976
+ }
977
+ var STAGGER_FRAMES = 2;
978
+ function LoadingView({ groups, stats, cols, rows, readyInput, privacyMode = false }) {
979
+ const theme = useTuiTheme();
980
+ const resolveReady = readyInput ?? ((id) => statsReadyInput(stats.get(id)));
981
+ const sp = glyphs().spinner;
982
+ const [frame, setFrame] = useState2(0);
983
+ useEffect2(() => {
984
+ const id = setInterval(() => setFrame((f) => f + 1), 80);
985
+ return () => clearInterval(id);
986
+ }, []);
987
+ const nameW = Math.min(13, groups.reduce((w, g) => Math.max(w, PROVIDERS[g.provider].name.length), 0));
988
+ const readyCount = groups.filter((g) => g.accounts.every((a) => accountReady(resolveReady(a.id), g.provider))).length;
989
+ const maxRows = Math.max(1, rows - 7);
990
+ const visible = groups.slice(0, maxRows);
991
+ const hidden = groups.length - visible.length;
992
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
993
+ /* @__PURE__ */ jsxs5(Text5, { bold: true, color: theme.accent, children: [
994
+ glyphs().dotSel,
995
+ " tokmon"
996
+ ] }),
997
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
998
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
999
+ "Detecting installed tools",
1000
+ glyphs().ellipsis
914
1001
  ] }),
915
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
916
- " step ",
917
- step,
918
- " of 4"
1002
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1003
+ " ",
1004
+ groups.length,
1005
+ " found"
919
1006
  ] })
920
1007
  ] }),
921
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Stepper, { active: form.field, accent }) }),
922
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", borderStyle: glyphs().border, borderColor: accent, paddingX: 2, paddingY: 1, children: [
923
- /* @__PURE__ */ jsx3(ProviderField, { value: form.providerId, focused: form.field === "provider" }),
924
- /* @__PURE__ */ jsx3(Box3, { height: 1 }),
925
- /* @__PURE__ */ jsx3(
926
- FormField,
927
- {
928
- label: "Name",
929
- hint: "display name for this account",
930
- value: form.name,
931
- focused: form.field === "name",
932
- caret: form.caret,
933
- accent,
934
- placeholder: "e.g. Work, Personal"
935
- }
936
- ),
937
- /* @__PURE__ */ jsx3(Box3, { height: 1 }),
938
- /* @__PURE__ */ jsx3(
939
- FormField,
940
- {
941
- label: "Home directory",
942
- hint: `path containing the tool's data dir ${glyphs().middot} ~ for default`,
943
- value: form.homeDir,
944
- focused: form.field === "homeDir",
945
- caret: form.caret,
946
- accent,
947
- placeholder: "~/work",
948
- mono: true
1008
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
1009
+ visible.map((g, i) => {
1010
+ const meta = PROVIDERS[g.provider];
1011
+ const items = g.accounts.map((a) => stats.get(a.id));
1012
+ const ready = g.accounts.every((a) => accountReady(resolveReady(a.id), g.provider));
1013
+ const errored = items.some((s) => !!s?.billing?.error);
1014
+ const revealed = frame >= i * STAGGER_FRAMES;
1015
+ const name = truncateName(meta.name, nameW);
1016
+ const namePad = " ".repeat(Math.max(0, nameW - name.length));
1017
+ if (!revealed) {
1018
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
1019
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1020
+ glyphs().dot,
1021
+ " "
1022
+ ] }),
1023
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1024
+ name,
1025
+ namePad
1026
+ ] })
1027
+ ] }, g.provider);
949
1028
  }
950
- ),
951
- /* @__PURE__ */ jsx3(Box3, { height: 1 }),
952
- /* @__PURE__ */ jsx3(ColorField, { value: form.color, focused: form.field === "color" }),
953
- /* @__PURE__ */ jsx3(Box3, { height: 1 }),
954
- /* @__PURE__ */ jsxs3(Box3, { children: [
955
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
956
- "id ",
957
- glyphs().boxMark,
958
- " "
959
- ] }),
960
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: accent, children: previewId || "account" }),
961
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
962
- " ",
963
- glyphs().boxMark,
964
- " auto-generated from name"
965
- ] })
1029
+ return /* @__PURE__ */ jsxs5(Box5, { children: [
1030
+ /* @__PURE__ */ jsxs5(Text5, { color: meta.color, children: [
1031
+ glyphs().dot,
1032
+ " "
1033
+ ] }),
1034
+ /* @__PURE__ */ jsx6(Text5, { bold: true, color: meta.color, children: name }),
1035
+ /* @__PURE__ */ jsx6(Text5, { children: namePad }),
1036
+ /* @__PURE__ */ jsx6(Text5, { children: " " }),
1037
+ errored ? /* @__PURE__ */ jsxs5(Text5, { color: theme.crit, children: [
1038
+ glyphs().warn,
1039
+ " "
1040
+ ] }) : ready ? /* @__PURE__ */ jsxs5(Text5, { color: theme.ok, children: [
1041
+ glyphs().check,
1042
+ " "
1043
+ ] }) : /* @__PURE__ */ jsxs5(Text5, { color: theme.accent, children: [
1044
+ sp[frame % sp.length],
1045
+ " "
1046
+ ] }),
1047
+ errored ? /* @__PURE__ */ jsx6(Text5, { color: theme.crit, children: headlineFor(g, items, privacyMode) }) : ready ? /* @__PURE__ */ jsx6(Text5, { children: headlineFor(g, items, privacyMode) }) : /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1048
+ "loading",
1049
+ glyphs().ellipsis
1050
+ ] })
1051
+ ] }, g.provider);
1052
+ }),
1053
+ hidden > 0 && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1054
+ "+",
1055
+ hidden,
1056
+ " more"
966
1057
  ] })
967
1058
  ] }),
968
- form.error && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
969
- glyphs().warn,
970
- " ",
971
- form.error
972
- ] }) }),
973
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
974
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
975
- "tab/",
976
- glyphs().arrowU,
977
- glyphs().arrowD,
978
- " "
979
- ] }),
980
- /* @__PURE__ */ jsx3(Text3, { children: "switch field" }),
981
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
982
- " ",
983
- glyphs().middot,
984
- " "
985
- ] }),
986
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "enter " }),
987
- /* @__PURE__ */ jsx3(Text3, { children: form.field === "color" ? "save" : "next" }),
988
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
989
- " ",
990
- glyphs().middot,
991
- " "
992
- ] }),
993
- form.field === "color" || form.field === "provider" ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
994
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
995
- glyphs().arrowL,
996
- glyphs().arrowR,
997
- " "
998
- ] }),
999
- /* @__PURE__ */ jsx3(Text3, { children: form.field === "provider" ? "pick provider" : "pick color" }),
1000
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1001
- " ",
1002
- glyphs().middot,
1003
- " "
1004
- ] })
1005
- ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1006
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1007
- glyphs().arrowL,
1008
- glyphs().arrowR,
1009
- " "
1010
- ] }),
1011
- /* @__PURE__ */ jsx3(Text3, { children: "move caret" }),
1012
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1013
- " ",
1014
- glyphs().middot,
1015
- " "
1016
- ] })
1059
+ /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
1060
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1061
+ "loading ",
1062
+ readyCount,
1063
+ " / ",
1064
+ groups.length
1017
1065
  ] }),
1018
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "ctrl+s " }),
1019
- /* @__PURE__ */ jsx3(Text3, { children: "save" }),
1020
- /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1066
+ /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1021
1067
  " ",
1022
1068
  glyphs().middot,
1023
- " "
1024
- ] }),
1025
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "esc " }),
1026
- /* @__PURE__ */ jsx3(Text3, { children: "cancel" })
1069
+ " W opens the web dashboard once loaded"
1070
+ ] })
1027
1071
  ] })
1028
1072
  ] });
1029
1073
  }
1030
- function Stepper({ active, accent }) {
1031
- const steps = [
1032
- { id: "provider", label: "Provider" },
1033
- { id: "name", label: "Name" },
1034
- { id: "homeDir", label: "Home" },
1035
- { id: "color", label: "Color" }
1036
- ];
1037
- const activeIdx = steps.findIndex((s) => s.id === active);
1038
- return /* @__PURE__ */ jsx3(Box3, { children: steps.map((s, i) => {
1039
- const done = i < activeIdx;
1040
- const cur = i === activeIdx;
1041
- const dot = done ? glyphs().dot : cur ? glyphs().dotSel : glyphs().radioOff;
1042
- return /* @__PURE__ */ jsxs3(Box3, { children: [
1043
- /* @__PURE__ */ jsxs3(Text3, { color: cur || done ? accent : void 0, dimColor: !cur && !done, children: [
1044
- dot,
1045
- " "
1046
- ] }),
1047
- /* @__PURE__ */ jsx3(Text3, { bold: cur, color: cur ? accent : void 0, dimColor: !cur, children: s.label }),
1048
- i < steps.length - 1 && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1049
- " ",
1050
- glyphs().rule,
1051
- " "
1052
- ] })
1053
- ] }, s.id);
1054
- }) });
1055
- }
1056
- function ProviderField({ value, focused }) {
1057
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1058
- /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: focused ? PROVIDERS[value].color : void 0, bold: focused, dimColor: !focused, children: [
1059
- focused ? glyphs().caretR : " ",
1060
- " Provider"
1061
- ] }) }),
1062
- /* @__PURE__ */ jsxs3(Box3, { children: [
1063
- /* @__PURE__ */ jsxs3(Text3, { children: [
1064
- " ",
1065
- focused ? glyphs().vbar : " ",
1066
- " "
1067
- ] }),
1068
- PROVIDER_ORDER.map((pid) => {
1069
- const selected = pid === value;
1070
- const p = PROVIDERS[pid];
1071
- return /* @__PURE__ */ jsx3(Box3, { marginRight: 2, children: selected ? /* @__PURE__ */ jsxs3(Text3, { bold: true, color: p.color, children: [
1072
- "[",
1073
- p.name,
1074
- "]"
1075
- ] }) : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: p.name }) }, pid);
1076
- })
1077
- ] }),
1078
- /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " which tool this account tracks" }) })
1079
- ] });
1080
- }
1081
- function FormField({ label, hint, value, focused, caret, accent, placeholder, mono }) {
1082
- const isPlaceholder = value === "";
1083
- const display = isPlaceholder ? placeholder : value;
1084
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1085
- /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: focused ? accent : void 0, bold: focused, dimColor: !focused, children: [
1086
- focused ? glyphs().caretR : " ",
1087
- " ",
1088
- label
1089
- ] }) }),
1090
- /* @__PURE__ */ jsxs3(Box3, { children: [
1091
- /* @__PURE__ */ jsxs3(Text3, { color: focused ? accent : void 0, children: [
1092
- " ",
1093
- focused ? glyphs().vbar : " ",
1094
- " "
1095
- ] }),
1096
- focused ? isPlaceholder ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1097
- /* @__PURE__ */ jsx3(Text3, { color: accent, children: glyphs().vbar }),
1098
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, italic: mono, children: placeholder })
1099
- ] }) : /* @__PURE__ */ jsx3(CaretText, { value, caret: caret ?? value.length, color: accent }) : /* @__PURE__ */ jsx3(Text3, { dimColor: isPlaceholder, italic: mono && isPlaceholder, children: display })
1100
- ] }),
1101
- /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1102
- " ",
1103
- hint
1104
- ] }) })
1105
- ] });
1074
+
1075
+ // src/ui/settings.tsx
1076
+ import { Fragment as Fragment4, memo as memo4 } from "react";
1077
+ import { Box as Box6, Text as Text6 } from "ink";
1078
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1079
+ var caret = (value) => /* @__PURE__ */ jsxs6(Fragment5, { children: [
1080
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1081
+ glyphs().caretL,
1082
+ " "
1083
+ ] }),
1084
+ value,
1085
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1086
+ " ",
1087
+ glyphs().caretR
1088
+ ] })
1089
+ ] });
1090
+ var toggleText = (on, theme, onLabel, offLabel, offColor) => /* @__PURE__ */ jsx7(Text6, { bold: true, color: on ? theme.ok : offColor ?? theme.crit, children: on ? onLabel : offLabel });
1091
+ function cycleNumber(values, current, direction) {
1092
+ const exact = values.indexOf(current);
1093
+ const index = exact >= 0 ? exact : values.reduce((best, value, candidate) => Math.abs(value - current) < Math.abs(values[best] - current) ? candidate : best, 0);
1094
+ return values[(index + direction + values.length) % values.length];
1106
1095
  }
1107
- function ColorField({ value, focused }) {
1108
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1109
- /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: focused ? value : void 0, bold: focused, dimColor: !focused, children: [
1110
- focused ? glyphs().caretR : " ",
1111
- " Accent color"
1112
- ] }) }),
1113
- /* @__PURE__ */ jsxs3(Box3, { children: [
1114
- /* @__PURE__ */ jsxs3(Text3, { children: [
1096
+ var step = (key) => key.leftArrow ? -1 : 1;
1097
+ var isToggleKey = (key) => key.leftArrow || key.rightArrow || key.return;
1098
+ var isAdjustKey = (input, key) => input === " " || key.leftArrow || key.rightArrow || key.return;
1099
+ var GENERAL_SETTINGS = [
1100
+ {
1101
+ key: "refreshInterval",
1102
+ label: "Refresh interval",
1103
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1104
+ rc.config.interval,
1105
+ "s"
1106
+ ] })),
1107
+ onAdjust: (_input, key, ctx) => {
1108
+ if (key.leftArrow) ctx.global.updateConfig((c) => ({ ...c, interval: Math.max(1, c.interval - 1) }));
1109
+ if (key.rightArrow) ctx.global.updateConfig((c) => ({ ...c, interval: c.interval + 1 }));
1110
+ }
1111
+ },
1112
+ {
1113
+ key: "billingPoll",
1114
+ label: "Billing poll",
1115
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1116
+ rc.config.billingInterval,
1117
+ "m"
1118
+ ] })),
1119
+ onAdjust: (_input, key, ctx) => {
1120
+ if (key.leftArrow) ctx.global.updateConfig((c) => ({ ...c, billingInterval: Math.max(1, c.billingInterval - 1) }));
1121
+ if (key.rightArrow) ctx.global.updateConfig((c) => ({ ...c, billingInterval: c.billingInterval + 1 }));
1122
+ }
1123
+ },
1124
+ {
1125
+ key: "clearScreen",
1126
+ label: "Clear screen",
1127
+ render: (rc) => toggleText(rc.config.clearScreen, rc.theme, "on", "off"),
1128
+ onAdjust: (_input, key, ctx) => {
1129
+ if (isToggleKey(key)) ctx.global.updateConfig((c) => ({ ...c, clearScreen: !c.clearScreen }));
1130
+ }
1131
+ },
1132
+ {
1133
+ key: "privacyMode",
1134
+ label: "Privacy mode",
1135
+ render: (rc) => toggleText(rc.config.privacyMode, rc.theme, "on", "off"),
1136
+ onAdjust: (_input, key, ctx) => {
1137
+ if (isToggleKey(key)) ctx.global.updateConfig((c) => ({ ...c, privacyMode: !c.privacyMode }));
1138
+ }
1139
+ },
1140
+ {
1141
+ key: "privacyKey",
1142
+ label: "Privacy key",
1143
+ render: (rc) => /* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.cost, children: rc.config.privacyToggleKey === " " ? "space" : rc.config.privacyToggleKey }),
1144
+ onAdjust: (input, key, ctx) => {
1145
+ if (ctx.textInput.isPrintable(input, key)) {
1146
+ const clean = sanitizeTyped(input);
1147
+ if (clean.length === 1) ctx.global.updateConfig((c) => ({ ...c, privacyToggleKey: clean }));
1148
+ }
1149
+ if (key.backspace || key.delete) ctx.global.updateConfig((c) => ({ ...c, privacyToggleKey: "p" }));
1150
+ }
1151
+ },
1152
+ {
1153
+ key: "timezone",
1154
+ label: "Timezone",
1155
+ render: (rc) => rc.tzEdit !== null ? /* @__PURE__ */ jsxs6(Fragment5, { children: [
1156
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "[" }),
1157
+ /* @__PURE__ */ jsx7(CaretText, { value: rc.tzEdit ?? "", caret: rc.tzCaret, color: rc.theme.accent }),
1158
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "]" })
1159
+ ] }) : /* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.cost, children: rc.tzDisplay }),
1160
+ below: (rc, focused) => focused && rc.tzError ? /* @__PURE__ */ jsxs6(Text6, { color: rc.theme.crit, children: [
1161
+ " ",
1162
+ rc.tzError
1163
+ ] }) : null,
1164
+ onAdjust: (_input, key, ctx) => {
1165
+ if (key.return) {
1166
+ const initial = ctx.global.config.timezone ?? "";
1167
+ ctx.timezoneEditor.setValue(initial);
1168
+ ctx.timezoneEditor.setCaret(initial.length);
1169
+ ctx.timezoneEditor.setError(null);
1170
+ }
1171
+ if (key.leftArrow || key.rightArrow) {
1172
+ ctx.global.updateConfig((c) => ({ ...c, timezone: c.timezone === null ? systemTimezone() : null }));
1173
+ }
1174
+ }
1175
+ },
1176
+ {
1177
+ key: "dashboard",
1178
+ label: "Dashboard",
1179
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.cost, children: rc.config.dashboardLayout === "grid" ? "grid (all)" : "single (cycle)" })),
1180
+ onAdjust: (_input, key, ctx) => {
1181
+ if (isToggleKey(key)) ctx.global.updateConfig((c) => ({ ...c, dashboardLayout: c.dashboardLayout === "grid" ? "single" : "grid" }));
1182
+ }
1183
+ },
1184
+ {
1185
+ key: "defaultFocus",
1186
+ label: "Default focus",
1187
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.cost, children: rc.config.defaultFocus === "all" ? "All" : "Last account" })),
1188
+ onAdjust: (_input, key, ctx) => {
1189
+ if (isToggleKey(key)) ctx.global.updateConfig((c) => ({ ...c, defaultFocus: c.defaultFocus === "all" ? "last" : "all" }));
1190
+ }
1191
+ },
1192
+ {
1193
+ key: "networkAccess",
1194
+ label: "Network access",
1195
+ render: (rc) => /* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.config.allowNetworkAccess ? rc.theme.crit : rc.theme.ok, children: rc.config.allowNetworkAccess ? "LAN (unsafe)" : "local only" }),
1196
+ onAdjust: (_input, key, ctx) => {
1197
+ if (isToggleKey(key)) ctx.global.updateConfig((c) => ({ ...c, allowNetworkAccess: !c.allowNetworkAccess }));
1198
+ }
1199
+ },
1200
+ {
1201
+ key: "allowedHosts",
1202
+ label: "Allowed hosts",
1203
+ render: (rc) => rc.allowedHostsEdit !== null ? /* @__PURE__ */ jsxs6(Fragment5, { children: [
1204
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "[" }),
1205
+ /* @__PURE__ */ jsx7(CaretText, { value: rc.allowedHostsEdit ?? "", caret: rc.allowedHostsCaret, color: rc.theme.accent }),
1206
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "]" })
1207
+ ] }) : /* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.cost, children: rc.config.allowedHosts.join(", ") || "none" }),
1208
+ below: (rc, focused) => /* @__PURE__ */ jsxs6(Fragment5, { children: [
1209
+ focused && rc.allowedHostsError ? /* @__PURE__ */ jsxs6(Text6, { color: rc.theme.crit, children: [
1115
1210
  " ",
1116
- focused ? glyphs().vbar : " ",
1117
- " "
1118
- ] }),
1119
- COLOR_PALETTE.map((c) => /* @__PURE__ */ jsx3(Box3, { marginRight: 1, children: c === value ? /* @__PURE__ */ jsxs3(Text3, { bold: true, color: c, children: [
1120
- "[",
1121
- glyphs().dot,
1122
- "]"
1123
- ] }) : /* @__PURE__ */ jsxs3(Text3, { color: c, dimColor: !focused, children: [
1124
- " ",
1125
- glyphs().dot
1126
- ] }) }, c))
1211
+ rc.allowedHostsError
1212
+ ] }) : null,
1213
+ rc.config.allowNetworkAccess ? /* @__PURE__ */ jsx7(Text6, { color: rc.theme.crit, children: " Warning: dashboard data and settings will be exposed to your local network after daemon restart." }) : null
1127
1214
  ] }),
1128
- /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " shows on dashboard, account strip, borders" }) })
1129
- ] });
1130
- }
1131
-
1132
- // src/ui/table.tsx
1133
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1134
- var MONTHS = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1135
- var TableProviderBar = memo4(function TableProviderBar2({ providers, active, onSelect }) {
1136
- return /* @__PURE__ */ jsxs4(Box4, { children: [
1137
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "provider " }),
1138
- providers.map((p) => {
1139
- const meta = PROVIDERS[p];
1140
- return /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onSelect(p), marginRight: 1, children: p === active ? /* @__PURE__ */ jsxs4(Text4, { bold: true, color: meta.color, inverse: true, children: [
1141
- " ",
1142
- meta.name,
1143
- " "
1144
- ] }) : /* @__PURE__ */ jsxs4(Text4, { color: meta.color, dimColor: true, children: [
1145
- " ",
1146
- meta.name,
1147
- " "
1148
- ] }) }, p);
1149
- }),
1150
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " p/P switch" })
1151
- ] });
1152
- });
1153
- var ControlBar = memo4(function ControlBar2({ views, period, sort, model, search, searchCaret, searching, showPeriod, showModel }) {
1154
- return /* @__PURE__ */ jsxs4(Box4, { children: [
1155
- showPeriod && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1156
- views.map((v, i) => /* @__PURE__ */ jsx4(Box4, { marginRight: 2, children: i === period ? /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
1157
- "[",
1158
- v,
1159
- "]"
1160
- ] }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: v }) }, v)),
1161
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " " })
1162
- ] }),
1163
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "sort " }),
1164
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "magenta", children: sort }),
1165
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1166
- " o cycle ",
1167
- glyphs().middot,
1168
- " "
1215
+ onAdjust: (_input, key, ctx) => {
1216
+ if (key.return) {
1217
+ const initial = ctx.global.config.allowedHosts.join(", ");
1218
+ ctx.allowedHostsEditor.setValue(initial);
1219
+ ctx.allowedHostsEditor.setCaret(initial.length);
1220
+ ctx.allowedHostsEditor.setError(null);
1221
+ }
1222
+ }
1223
+ },
1224
+ {
1225
+ key: "resetTimes",
1226
+ label: "Reset times",
1227
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.cost, children: rc.config.resetDisplay === "relative" ? "time remaining" : "exact date/time" })),
1228
+ onAdjust: (_input, key, ctx) => {
1229
+ if (isToggleKey(key)) ctx.global.updateConfig((c) => ({ ...c, resetDisplay: c.resetDisplay === "relative" ? "absolute" : "relative" }));
1230
+ }
1231
+ }
1232
+ ];
1233
+ var THEME_SETTINGS = [
1234
+ {
1235
+ key: "preset",
1236
+ label: "Preset",
1237
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.accent, children: themePresetLabel(rc.config.appearance.preset) })),
1238
+ onAdjust: (_input, key, ctx) => {
1239
+ if (!isToggleKey(key)) return;
1240
+ const direction = step(key);
1241
+ ctx.global.updateConfig((current) => {
1242
+ const choices = THEME_PRESET_IDS.filter((preset2) => preset2 !== "custom" || current.appearance.custom);
1243
+ const currentIndex = Math.max(0, choices.findIndex((v) => v === current.appearance.preset));
1244
+ const preset = choices[(currentIndex + direction + choices.length) % choices.length];
1245
+ return { ...current, appearance: { ...current.appearance, preset, ...isDarkOnlyThemePreset(preset) ? { mode: "dark" } : {} } };
1246
+ });
1247
+ }
1248
+ },
1249
+ {
1250
+ key: "appAppearance",
1251
+ label: "App appearance",
1252
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.cost, children: appearanceModeLabel(rc.config.appearance.mode) })),
1253
+ onAdjust: (_input, key, ctx) => {
1254
+ if (!isToggleKey(key)) return;
1255
+ const direction = step(key);
1256
+ ctx.global.updateConfig((current) => {
1257
+ if (isDarkOnlyThemePreset(current.appearance.preset)) return current;
1258
+ const choices = ["auto", "light", "dark"];
1259
+ const currentIndex = choices.indexOf(current.appearance.mode);
1260
+ return { ...current, appearance: { ...current.appearance, mode: choices[(currentIndex + direction + choices.length) % choices.length] } };
1261
+ });
1262
+ }
1263
+ },
1264
+ {
1265
+ key: "terminalColors",
1266
+ label: "Terminal colors",
1267
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.accent, children: terminalModeLabel(rc.config.appearance.terminal) })),
1268
+ onAdjust: (_input, key, ctx) => {
1269
+ if (!isToggleKey(key)) return;
1270
+ const direction = step(key);
1271
+ ctx.global.updateConfig((current) => {
1272
+ const choices = ["ansi", "dark", "light", "off"];
1273
+ const currentIndex = choices.indexOf(current.appearance.terminal);
1274
+ return { ...current, appearance: { ...current.appearance, terminal: choices[(currentIndex + direction + choices.length) % choices.length] } };
1275
+ });
1276
+ }
1277
+ }
1278
+ ];
1279
+ var DESKTOP_FIXED_SETTINGS = [
1280
+ {
1281
+ key: "trayApp",
1282
+ label: "Tray app",
1283
+ render: (rc) => toggleText(rc.config.tray.enabled, rc.theme, "on", "off"),
1284
+ onAdjust: (input, key, ctx) => {
1285
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, enabled: !c.tray.enabled } }));
1286
+ }
1287
+ },
1288
+ {
1289
+ key: "menuBarValues",
1290
+ label: "Menu bar values",
1291
+ render: (rc) => toggleText(rc.config.tray.showMenuBarText, rc.theme, "shown", "icon only", rc.theme.unknown),
1292
+ onAdjust: (input, key, ctx) => {
1293
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, showMenuBarText: !c.tray.showMenuBarText } }));
1294
+ }
1295
+ },
1296
+ {
1297
+ key: "summary",
1298
+ label: "Summary",
1299
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.accent, children: rc.config.tray.displayMetric === "smartHeadroom" ? "smart usage" : "tightest quota" })),
1300
+ onAdjust: (input, key, ctx) => {
1301
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, displayMetric: c.tray.displayMetric === "smartHeadroom" ? "tightestRemaining" : "smartHeadroom" } }));
1302
+ }
1303
+ },
1304
+ {
1305
+ key: "trayRefresh",
1306
+ label: "Tray refresh",
1307
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1308
+ rc.config.tray.pollIntervalSec,
1309
+ "s"
1310
+ ] })),
1311
+ onAdjust: (input, key, ctx) => {
1312
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, pollIntervalSec: cycleNumber([15, 30, 60, 120], c.tray.pollIntervalSec, step(key)) } }));
1313
+ }
1314
+ },
1315
+ {
1316
+ key: "activeWindow",
1317
+ label: "Active window",
1318
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1319
+ rc.config.tray.activeTimeoutMin,
1320
+ "m"
1321
+ ] })),
1322
+ onAdjust: (input, key, ctx) => {
1323
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, activeTimeoutMin: cycleNumber([5, 10, 15, 30], c.tray.activeTimeoutMin, step(key)) } }));
1324
+ }
1325
+ },
1326
+ {
1327
+ key: "graphRange",
1328
+ label: "Graph range",
1329
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1330
+ rc.config.desktop.graphRangeDays,
1331
+ " days"
1332
+ ] })),
1333
+ onAdjust: (input, key, ctx) => {
1334
+ if (!isAdjustKey(input, key)) return;
1335
+ ctx.global.updateConfig((c) => ({
1336
+ ...c,
1337
+ desktop: { ...c.desktop, graphRangeDays: cycleNumber(DESKTOP_GRAPH_RANGES, c.desktop.graphRangeDays, step(key)) }
1338
+ }));
1339
+ }
1340
+ },
1341
+ {
1342
+ key: "launchAtLogin",
1343
+ label: "Launch at login",
1344
+ render: (rc) => toggleText(rc.config.tray.launchAtLogin, rc.theme, "on", "off", rc.theme.unknown),
1345
+ onAdjust: (input, key, ctx) => {
1346
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, launchAtLogin: !c.tray.launchAtLogin } }));
1347
+ }
1348
+ }
1349
+ ];
1350
+ var GENERAL_ROWS = GENERAL_SETTINGS.length;
1351
+ var THEME_ROWS = THEME_SETTINGS.length;
1352
+ var DESKTOP_FIXED_ROWS = DESKTOP_FIXED_SETTINGS.length;
1353
+ var SETTINGS_TABS = ["general", "theme", "desktop", "providers", "accounts"];
1354
+ var SETTINGS_TAB_LABELS = {
1355
+ general: "General",
1356
+ theme: "Theme",
1357
+ desktop: "Desktop App",
1358
+ providers: "Providers",
1359
+ accounts: "Accounts"
1360
+ };
1361
+ var FORM_FIELDS = ["provider", "name", "homeDir", "color"];
1362
+ var SettingsView = memo4(function SettingsView2({
1363
+ config,
1364
+ cursor,
1365
+ activeTab,
1366
+ tzEdit,
1367
+ tzCaret,
1368
+ tzError,
1369
+ allowedHostsEdit,
1370
+ allowedHostsCaret,
1371
+ allowedHostsError,
1372
+ resolvedTz,
1373
+ accountForm,
1374
+ activeAccountId,
1375
+ trackedAccounts,
1376
+ accountIdentities
1377
+ }) {
1378
+ const theme = useTuiTheme();
1379
+ if (accountForm) return /* @__PURE__ */ jsx7(AccountFormView, { form: accountForm, accounts: config.accounts });
1380
+ const editingTz = tzEdit !== null;
1381
+ const editingAllowedHosts = allowedHostsEdit !== null;
1382
+ const tzDisplay = config.timezone === null ? `System (${resolvedTz})` : config.timezone;
1383
+ const tabFocused = cursor < 0;
1384
+ const rc = {
1385
+ config,
1386
+ theme,
1387
+ tzEdit,
1388
+ tzCaret,
1389
+ tzError,
1390
+ tzDisplay,
1391
+ allowedHostsEdit,
1392
+ allowedHostsCaret,
1393
+ allowedHostsError
1394
+ };
1395
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
1396
+ /* @__PURE__ */ jsx7(Text6, { bold: true, children: "Settings" }),
1397
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: configLocation() }),
1398
+ /* @__PURE__ */ jsx7(Box6, { height: 1 }),
1399
+ /* @__PURE__ */ jsx7(SettingsTabBar, { active: activeTab, focused: tabFocused }),
1400
+ /* @__PURE__ */ jsx7(Box6, { height: 1 }),
1401
+ activeTab === "general" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1402
+ /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "General" }),
1403
+ GENERAL_SETTINGS.map((row, i) => /* @__PURE__ */ jsxs6(Fragment4, { children: [
1404
+ /* @__PURE__ */ jsx7(Row, { cursor, idx: i, label: row.label, children: row.render(rc) }),
1405
+ row.below?.(rc, cursor === i)
1406
+ ] }, row.key))
1169
1407
  ] }),
1170
- showModel && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1171
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "model " }),
1172
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: model ? modelColor(model) : "cyan", children: model ?? "all" }),
1173
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1174
- " m model ",
1175
- glyphs().middot,
1176
- " "
1408
+ activeTab === "theme" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1409
+ /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "Theme" }),
1410
+ THEME_SETTINGS.map((row, i) => /* @__PURE__ */ jsxs6(Fragment4, { children: [
1411
+ /* @__PURE__ */ jsx7(Row, { cursor, idx: i, label: row.label, children: row.render(rc) }),
1412
+ row.below?.(rc, cursor === i)
1413
+ ] }, row.key)),
1414
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1415
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Auto follows the operating system in the web and desktop apps." }),
1416
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Terminal colors never repaint your terminal background." }),
1417
+ config.appearance.preset === "custom" && /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Custom colors are edited in the web dashboard and shared here." })
1177
1418
  ] })
1178
1419
  ] }),
1179
- searching ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1180
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "/" }),
1181
- /* @__PURE__ */ jsx4(CaretText, { value: search, caret: searchCaret, color: "cyan" })
1182
- ] }) : search ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1183
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "filter " }),
1184
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: search }),
1185
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1186
- " (/ edit ",
1187
- glyphs().middot,
1188
- " esc clear)"
1189
- ] })
1190
- ] }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "/ filter" })
1191
- ] });
1192
- });
1193
- var TokenTable = memo4(function TokenTable2({ rows, cursor, expanded, maxRows, cols, onRowClick }) {
1194
- if (rows.length === 0) return /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No usage in this period (or filtered out)." });
1195
- const wide = cols > 90;
1196
- 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 };
1197
- const fixed = base.label + base.input + base.output + base.cc + base.cr + base.total + base.cost;
1198
- const available = cols - fixed - 6;
1199
- const W = { ...base, models: Math.max(wide ? 22 : 14, available) };
1200
- const lineW = W.label + W.models + W.input + W.output + W.cc + W.cr + W.total + W.cost;
1201
- const totals = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, cost: 0 };
1202
- for (const r of rows) {
1203
- totals.input += r.input;
1204
- totals.output += r.output;
1205
- totals.cacheCreate += r.cacheCreate;
1206
- totals.cacheRead += r.cacheRead;
1207
- totals.cost += r.cost;
1208
- }
1209
- const clampedCursor = Math.min(cursor, rows.length - 1);
1210
- const scrollStart = Math.max(0, Math.min(clampedCursor - Math.floor(maxRows / 2), rows.length - maxRows));
1211
- const visible = rows.slice(scrollStart, scrollStart + maxRows);
1212
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1213
- /* @__PURE__ */ jsxs4(Text4, { children: [
1214
- /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
1215
- " ",
1216
- col("Date", W.label, "left")
1420
+ activeTab === "desktop" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1421
+ /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "Desktop App" }),
1422
+ DESKTOP_FIXED_SETTINGS.map((row, i) => /* @__PURE__ */ jsxs6(Fragment4, { children: [
1423
+ /* @__PURE__ */ jsx7(Row, { cursor, idx: i, label: row.label, children: row.render(rc) }),
1424
+ row.below?.(rc, cursor === i)
1425
+ ] }, row.key)),
1426
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1427
+ /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "Menu bar pins" }),
1428
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " up to 2 providers, in order" })
1217
1429
  ] }),
1218
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Models", W.models, "left") }),
1219
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Input", W.input) }),
1220
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Output", W.output) }),
1221
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: col(wide ? "Cache Create" : "CchCrt", W.cc) }),
1222
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: col(wide ? "Cache Read" : "CchRd", W.cr) }),
1223
- W.total > 0 && /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Total", W.total) }),
1224
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Cost", W.cost) })
1430
+ PROVIDER_ORDER.map((pid, i) => {
1431
+ const idx = DESKTOP_FIXED_ROWS + i;
1432
+ const selected = cursor === idx;
1433
+ const pinned = config.tray.pinnedProviders.includes(pid);
1434
+ const position = config.tray.pinnedProviders.indexOf(pid);
1435
+ const meta = PROVIDERS[pid];
1436
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1437
+ /* @__PURE__ */ jsxs6(Text6, { color: selected ? theme.accent : void 0, children: [
1438
+ selected ? glyphs().caretR : " ",
1439
+ " "
1440
+ ] }),
1441
+ /* @__PURE__ */ jsxs6(Box6, { width: 20, children: [
1442
+ /* @__PURE__ */ jsxs6(Text6, { color: meta.color, children: [
1443
+ glyphs().dot,
1444
+ " "
1445
+ ] }),
1446
+ /* @__PURE__ */ jsx7(Text6, { bold: pinned, children: meta.name })
1447
+ ] }),
1448
+ /* @__PURE__ */ jsx7(Text6, { color: pinned ? theme.ok : void 0, dimColor: !pinned, children: pinned ? `[${position + 1}] pinned` : "[ ] not pinned" })
1449
+ ] }, pid);
1450
+ })
1225
1451
  ] }),
1226
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
1227
- visible.map((r, vi) => {
1228
- const idx = scrollStart + vi;
1229
- const selected = idx === clampedCursor;
1230
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
1231
- /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs4(Text4, { inverse: selected, children: [
1232
- /* @__PURE__ */ jsxs4(Text4, { color: selected ? void 0 : "cyan", children: [
1233
- selected ? `${glyphs().caretR} ` : " ",
1234
- col(fmtLabel(r.label), W.label, "left")
1452
+ activeTab === "providers" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1453
+ /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "Providers" }),
1454
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1455
+ /* @__PURE__ */ jsxs6(Text6, { color: cursor === 0 ? theme.accent : void 0, children: [
1456
+ cursor === 0 ? glyphs().caretR : " ",
1457
+ " "
1458
+ ] }),
1459
+ /* @__PURE__ */ jsx7(Box6, { width: 20, children: /* @__PURE__ */ jsx7(Text6, { bold: true, children: "Discover accounts" }) }),
1460
+ toggleText(config.accountDetection.enabled, theme, "on", "off")
1461
+ ] }),
1462
+ PROVIDER_ORDER.map((pid, i) => {
1463
+ const selected = cursor === i + 1;
1464
+ const enabled = !config.disabledProviders.includes(pid);
1465
+ const discovery = providerDetectionEnabled(config.accountDetection, pid);
1466
+ const p = PROVIDERS[pid];
1467
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1468
+ /* @__PURE__ */ jsxs6(Text6, { color: selected ? theme.accent : void 0, children: [
1469
+ selected ? glyphs().caretR : " ",
1470
+ " "
1235
1471
  ] }),
1236
- /* @__PURE__ */ jsx4(Text4, { dimColor: !selected, children: col(r.models.join(", "), W.models, "left") }),
1237
- /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.input), W.input) }),
1238
- /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.output), W.output) }),
1239
- /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.cacheCreate), W.cc) }),
1240
- /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.cacheRead), W.cr) }),
1241
- W.total > 0 && /* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.total), W.total) }),
1242
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: selected ? void 0 : "yellow", children: col(currency(r.cost), W.cost) })
1243
- ] }) }),
1244
- idx === expanded && /* @__PURE__ */ jsx4(RowDetail, { row: r, indent: W.label + 2 })
1245
- ] }, r.label);
1246
- }),
1247
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
1248
- /* @__PURE__ */ jsxs4(Text4, { children: [
1249
- /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "greenBright", children: [
1250
- " ",
1251
- col("Total", W.label, "left")
1472
+ /* @__PURE__ */ jsxs6(Text6, { color: p.color, children: [
1473
+ " ",
1474
+ glyphs().dot,
1475
+ " "
1476
+ ] }),
1477
+ /* @__PURE__ */ jsx7(Box6, { width: 16, children: /* @__PURE__ */ jsx7(Text6, { bold: selected, children: p.name }) }),
1478
+ /* @__PURE__ */ jsx7(Box6, { width: 18, children: /* @__PURE__ */ jsx7(Text6, { color: enabled ? theme.ok : void 0, dimColor: !enabled, children: enabled ? `[${glyphs().check}] tracking` : "[ ] tracking" }) }),
1479
+ /* @__PURE__ */ jsx7(Text6, { color: discovery ? theme.ok : void 0, dimColor: !discovery, children: discovery ? `[${glyphs().check}] auto-detect` : "[ ] auto-detect" })
1480
+ ] }, pid);
1481
+ })
1482
+ ] }),
1483
+ activeTab === "accounts" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1484
+ /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "Accounts" }),
1485
+ trackedAccounts.length === 0 && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1486
+ " none tracked ",
1487
+ glyphs().emDash,
1488
+ " enable a provider or add an account"
1252
1489
  ] }),
1253
- /* @__PURE__ */ jsx4(Text4, { children: col("", W.models, "left") }),
1254
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.input), W.input) }),
1255
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.output), W.output) }),
1256
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.cacheCreate), W.cc) }),
1257
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.cacheRead), W.cr) }),
1258
- W.total > 0 && /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totals.input + totals.output + totals.cacheCreate + totals.cacheRead), W.total) }),
1259
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellowBright", children: col(currency(totals.cost), W.cost) })
1490
+ trackedAccounts.map((acc, i) => {
1491
+ const selected = cursor === i;
1492
+ const isActive = acc.id === activeAccountId;
1493
+ const provider = PROVIDERS[acc.providerId];
1494
+ const identity = accountIdentities.get(acc.id);
1495
+ const rawIdentityLabel = identity?.email || identity?.displayName || acc.name;
1496
+ const identityLabel = config.privacyMode ? redactEmail(rawIdentityLabel) : rawIdentityLabel;
1497
+ const plan = identity?.plan ?? null;
1498
+ const ignored = acc.source === "ignored";
1499
+ const sourceLabel = acc.source === "auto" ? "auto tracking" : ignored ? "ignored" : "configured";
1500
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1501
+ /* @__PURE__ */ jsxs6(Text6, { color: selected ? theme.accent : void 0, children: [
1502
+ selected ? glyphs().caretR : " ",
1503
+ " "
1504
+ ] }),
1505
+ /* @__PURE__ */ jsxs6(Text6, { color: ignored ? theme.unknown : acc.color || provider.color, children: [
1506
+ ignored ? glyphs().warn : isActive ? glyphs().dot : glyphs().radioOff,
1507
+ " "
1508
+ ] }),
1509
+ /* @__PURE__ */ jsx7(Box6, { width: 28, children: /* @__PURE__ */ jsx7(Text6, { bold: !ignored, dimColor: ignored, children: truncateName(identityLabel, 27) }) }),
1510
+ /* @__PURE__ */ jsx7(Box6, { width: 9, children: /* @__PURE__ */ jsx7(Text6, { color: provider.color, children: provider.name }) }),
1511
+ /* @__PURE__ */ jsx7(Box6, { width: 18, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: plan ? truncateName(plan, 17) : "" }) }),
1512
+ /* @__PURE__ */ jsx7(Box6, { width: 14, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: sourceLabel }) }),
1513
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: config.privacyMode ? "[path hidden]" : truncateName(acc.homeDir, 24) })
1514
+ ] }, `${acc.source}:${acc.id}`);
1515
+ }),
1516
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1517
+ /* @__PURE__ */ jsxs6(Text6, { color: cursor === trackedAccounts.length ? theme.accent : void 0, children: [
1518
+ cursor === trackedAccounts.length ? glyphs().caretR : " ",
1519
+ " "
1520
+ ] }),
1521
+ /* @__PURE__ */ jsx7(Text6, { color: theme.accent, children: "+ " }),
1522
+ /* @__PURE__ */ jsx7(Text6, { children: "Add account" })
1523
+ ] })
1260
1524
  ] }),
1261
- /* @__PURE__ */ jsx4(Box4, { height: 1 }),
1262
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1525
+ /* @__PURE__ */ jsx7(Box6, { height: 1 }),
1526
+ tabFocused ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1527
+ glyphs().arrowL,
1528
+ glyphs().arrowR,
1529
+ "/tab switch section ",
1530
+ glyphs().middot,
1531
+ " ",
1532
+ glyphs().arrowD,
1533
+ " rows ",
1534
+ glyphs().middot,
1535
+ " s/Esc close"
1536
+ ] }) : editingTz ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1537
+ "type IANA name (e.g. Europe/London) ",
1538
+ glyphs().middot,
1539
+ " empty = System ",
1540
+ glyphs().middot,
1541
+ " Enter save ",
1542
+ glyphs().middot,
1543
+ " Esc cancel"
1544
+ ] }) : editingAllowedHosts ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1545
+ "comma-separated exact DNS names ",
1546
+ glyphs().middot,
1547
+ " Enter save ",
1548
+ glyphs().middot,
1549
+ " Esc cancel"
1550
+ ] }) : activeTab === "theme" ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1263
1551
  glyphs().arrowU,
1264
1552
  glyphs().arrowD,
1265
- " navigate ",
1553
+ " select ",
1266
1554
  glyphs().middot,
1267
- " Enter detail ",
1555
+ " ",
1556
+ glyphs().arrowL,
1557
+ glyphs().arrowR,
1558
+ " adjust ",
1268
1559
  glyphs().middot,
1269
- " o sort ",
1560
+ " s/Esc close"
1561
+ ] }) : activeTab === "desktop" ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1562
+ glyphs().arrowU,
1563
+ glyphs().arrowD,
1564
+ " select ",
1270
1565
  glyphs().middot,
1271
- " g/G top/bottom ",
1566
+ " ",
1567
+ glyphs().arrowL,
1568
+ glyphs().arrowR,
1569
+ "/space adjust ",
1570
+ glyphs().middot,
1571
+ " pins are capped at 2 ",
1572
+ glyphs().middot,
1573
+ " s/Esc close"
1574
+ ] }) : activeTab === "providers" ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1575
+ glyphs().arrowU,
1576
+ glyphs().arrowD,
1577
+ " select ",
1578
+ glyphs().middot,
1579
+ " space tracking/global discovery ",
1580
+ glyphs().middot,
1581
+ " a per-provider auto-detect ",
1582
+ glyphs().middot,
1583
+ " s/Esc close"
1584
+ ] }) : activeTab === "accounts" && cursor >= 0 && cursor < trackedAccounts.length ? trackedAccounts[cursor]?.source === "auto" ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1585
+ glyphs().arrowU,
1586
+ glyphs().arrowD,
1587
+ " select ",
1588
+ glyphs().middot,
1589
+ " Enter configure ",
1590
+ glyphs().middot,
1591
+ " space activate ",
1592
+ glyphs().middot,
1593
+ " x ignore ",
1594
+ glyphs().middot,
1595
+ " s/Esc close"
1596
+ ] }) : trackedAccounts[cursor]?.source === "ignored" ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1597
+ glyphs().arrowU,
1598
+ glyphs().arrowD,
1599
+ " select ",
1600
+ glyphs().middot,
1601
+ " Enter/x turn on ",
1602
+ glyphs().middot,
1603
+ " s/Esc close"
1604
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1605
+ glyphs().arrowU,
1606
+ glyphs().arrowD,
1607
+ " select ",
1272
1608
  glyphs().middot,
1273
1609
  " ",
1274
- clampedCursor + 1,
1275
- "/",
1276
- rows.length
1277
- ] })
1278
- ] });
1279
- });
1280
- function RowDetail({ row, indent }) {
1281
- return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", paddingLeft: indent, children: row.breakdown.map((m, i) => /* @__PURE__ */ jsxs4(Text4, { children: [
1282
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1283
- i === row.breakdown.length - 1 ? glyphs().treeEnd : glyphs().treeMid,
1284
- " "
1285
- ] }),
1286
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: col(m.name, 16, "left") }),
1287
- /* @__PURE__ */ jsxs4(Text4, { children: [
1288
- col(tokens(m.input), 8),
1289
- " in "
1290
- ] }),
1291
- /* @__PURE__ */ jsxs4(Text4, { children: [
1292
- col(tokens(m.output), 8),
1293
- " out "
1294
- ] }),
1295
- /* @__PURE__ */ jsxs4(Text4, { children: [
1296
- col(tokens(m.cacheCreate), 8),
1297
- " cc "
1298
- ] }),
1299
- /* @__PURE__ */ jsxs4(Text4, { children: [
1300
- col(tokens(m.cacheRead), 9),
1301
- " cr "
1302
- ] }),
1303
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: currency(m.cost) })
1304
- ] }, m.name)) });
1305
- }
1306
- function fmtLabel(label) {
1307
- if (label.length === 7 && label[4] === "-") {
1308
- return `${MONTHS[Number(label.slice(5, 7))]} '${label.slice(2, 4)}`;
1309
- }
1310
- return shortDate(label);
1311
- }
1312
-
1313
- // src/ui/onboarding.tsx
1314
- import { Box as Box5, Text as Text5 } from "ink";
1315
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1316
- 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." }) {
1317
- const anyEnabled = items.some((it) => it.enabled);
1318
- const startIdx = items.length;
1319
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginTop: 1, children: [
1320
- /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { bold: true, color: "greenBright", children: heading }) }),
1321
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: subheading }),
1322
- /* @__PURE__ */ jsx5(Box5, { height: 1 }),
1323
- items.map((it, i) => {
1324
- const selected = cursor === i;
1325
- const box = it.enabled ? `[${glyphs().check}]` : "[ ]";
1326
- return /* @__PURE__ */ jsxs5(ClickableBox, { onClick: () => onToggle(i), children: [
1327
- /* @__PURE__ */ jsxs5(Text5, { color: selected ? "green" : void 0, children: [
1328
- selected ? glyphs().caretR : " ",
1329
- " "
1330
- ] }),
1331
- /* @__PURE__ */ jsx5(Text5, { bold: it.enabled, color: it.enabled ? it.color : void 0, dimColor: !it.enabled, children: box }),
1332
- /* @__PURE__ */ jsxs5(Text5, { color: it.color, children: [
1333
- " ",
1334
- glyphs().dot,
1335
- " "
1336
- ] }),
1337
- /* @__PURE__ */ jsx5(Box5, { width: 13, flexShrink: 0, children: /* @__PURE__ */ jsx5(Text5, { bold: selected, dimColor: !it.detected && !it.enabled, wrap: "truncate", children: it.name }) }),
1338
- 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" })
1339
- ] }, it.id);
1340
- }),
1341
- /* @__PURE__ */ jsx5(Box5, { height: 1 }),
1342
- /* @__PURE__ */ jsxs5(ClickableBox, { onClick: onConfirm, children: [
1343
- /* @__PURE__ */ jsxs5(Text5, { color: cursor === startIdx ? "green" : void 0, children: [
1344
- cursor === startIdx ? glyphs().caretR : " ",
1345
- " "
1346
- ] }),
1347
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: anyEnabled ? "greenBright" : void 0, dimColor: !anyEnabled, children: anyEnabled ? `${glyphs().play} Start tokmon` : `${glyphs().play} Start (nothing selected)` })
1348
- ] }),
1349
- /* @__PURE__ */ jsx5(Box5, { height: 1 }),
1350
- /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
1610
+ glyphs().shift,
1351
1611
  glyphs().arrowU,
1352
1612
  glyphs().arrowD,
1353
- " move ",
1613
+ " reorder ",
1354
1614
  glyphs().middot,
1355
- " space toggle ",
1615
+ " Enter edit ",
1356
1616
  glyphs().middot,
1357
- " enter start ",
1617
+ " space activate ",
1358
1618
  glyphs().middot,
1359
- " q quit"
1619
+ " d delete ",
1620
+ glyphs().middot,
1621
+ " s/Esc close"
1622
+ ] }) : activeTab === "accounts" && cursor === trackedAccounts.length ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1623
+ glyphs().arrowU,
1624
+ glyphs().arrowD,
1625
+ " select ",
1626
+ glyphs().middot,
1627
+ " Enter add account ",
1628
+ glyphs().middot,
1629
+ " s/Esc close"
1630
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1631
+ glyphs().arrowU,
1632
+ glyphs().arrowD,
1633
+ " select ",
1634
+ glyphs().arrowL,
1635
+ glyphs().arrowR,
1636
+ " adjust Enter edit tab switch section s/Esc close"
1360
1637
  ] })
1361
1638
  ] });
1639
+ });
1640
+ function themePresetLabel(preset) {
1641
+ return themePresetOption(preset).name;
1362
1642
  }
1363
-
1364
- // src/ui/loading.tsx
1365
- import { useState as useState2, useEffect as useEffect2 } from "react";
1366
- import { Box as Box6, Text as Text6 } from "ink";
1367
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1368
- function accountReady(s, providerId) {
1369
- if (!s) return false;
1370
- const p = PROVIDERS[providerId];
1371
- if (p.hasBilling && (s.billingState === "error" || s.billing?.error)) return true;
1372
- if (p.hasUsage && s.summaryState !== "ready" && s.summaryState !== "error") return false;
1373
- if (p.hasBilling && s.billingState !== "ready" && s.billingState !== "error") return false;
1374
- return true;
1643
+ function appearanceModeLabel(mode) {
1644
+ return mode === "auto" ? "Auto (system)" : mode === "light" ? "Light" : "Dark";
1375
1645
  }
1376
- function statsReadyInput(s) {
1377
- if (!s) return void 0;
1378
- return {
1379
- summaryState: s.dashboard ? "ready" : "pending",
1380
- billingState: s.billing?.error ? "error" : s.billing ? "ready" : "pending",
1381
- billing: s.billing
1382
- };
1646
+ function terminalModeLabel(mode) {
1647
+ return mode === "ansi" ? "Terminal managed" : mode === "off" ? "No color" : mode === "light" ? "Light terminal" : "Dark terminal";
1383
1648
  }
1384
- function groupTodayCost(items) {
1385
- return items.reduce((sum, s) => {
1386
- const d = s?.dashboard;
1387
- return sum + (d?.today.cost ?? 0);
1388
- }, 0);
1389
- }
1390
- function headlineFor(group, items, privacyMode = false) {
1391
- const meta = PROVIDERS[group.provider];
1392
- if (meta.hasUsage) return `${currency(groupTodayCost(items))} today`;
1393
- const billing = items.map((s) => s?.billing).find(Boolean);
1394
- if (!billing) return "no data";
1395
- if (billing.error) return privacyMode ? redactEmail(billing.error) : billing.error;
1396
- const m = billing.metrics[0];
1397
- if (m) return metricValueText(m);
1398
- return billing.plan ?? "no data";
1649
+ function SettingsTabBar({ active, focused }) {
1650
+ const theme = useTuiTheme();
1651
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1652
+ /* @__PURE__ */ jsxs6(Text6, { color: focused ? theme.accent : void 0, children: [
1653
+ focused ? glyphs().caretR : " ",
1654
+ " "
1655
+ ] }),
1656
+ SETTINGS_TABS.map((tab, i) => {
1657
+ const selected = tab === active;
1658
+ return /* @__PURE__ */ jsxs6(Box6, { marginRight: 1, children: [
1659
+ selected ? /* @__PURE__ */ jsxs6(Text6, { bold: true, inverse: true, children: [
1660
+ " ",
1661
+ SETTINGS_TAB_LABELS[tab],
1662
+ " "
1663
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1664
+ " ",
1665
+ SETTINGS_TAB_LABELS[tab],
1666
+ " "
1667
+ ] }),
1668
+ i < SETTINGS_TABS.length - 1 && /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " " })
1669
+ ] }, tab);
1670
+ })
1671
+ ] });
1399
1672
  }
1400
- var STAGGER_FRAMES = 2;
1401
- function LoadingView({ groups, stats, cols, rows, readyInput, privacyMode = false }) {
1402
- const resolveReady = readyInput ?? ((id) => statsReadyInput(stats.get(id)));
1403
- const sp = glyphs().spinner;
1404
- const [frame, setFrame] = useState2(0);
1405
- useEffect2(() => {
1406
- const id = setInterval(() => setFrame((f) => f + 1), 80);
1407
- return () => clearInterval(id);
1408
- }, []);
1409
- const nameW = Math.min(13, groups.reduce((w, g) => Math.max(w, PROVIDERS[g.provider].name.length), 0));
1410
- const readyCount = groups.filter((g) => g.accounts.every((a) => accountReady(resolveReady(a.id), g.provider))).length;
1411
- const maxRows = Math.max(1, rows - 7);
1412
- const visible = groups.slice(0, maxRows);
1413
- const hidden = groups.length - visible.length;
1414
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1415
- /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "greenBright", children: [
1416
- glyphs().dotSel,
1417
- " tokmon"
1673
+ function Row({ cursor, idx, label, children }) {
1674
+ const theme = useTuiTheme();
1675
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1676
+ /* @__PURE__ */ jsxs6(Text6, { color: cursor === idx ? theme.accent : void 0, children: [
1677
+ cursor === idx ? glyphs().caretR : " ",
1678
+ " "
1418
1679
  ] }),
1419
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1420
- /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1421
- "Detecting installed tools",
1422
- glyphs().ellipsis
1680
+ /* @__PURE__ */ jsx7(Box6, { width: 20, children: /* @__PURE__ */ jsx7(Text6, { children: label }) }),
1681
+ children
1682
+ ] });
1683
+ }
1684
+ function AccountFormView({ form, accounts }) {
1685
+ const theme = useTuiTheme();
1686
+ const previewId = form.mode === "add" ? generateAccountId(form.name || "account", accounts) : form.editingId ?? "";
1687
+ const accent = form.color;
1688
+ const stepIndex = { provider: 1, name: 2, homeDir: 3, color: 4 };
1689
+ const step2 = stepIndex[form.field];
1690
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
1691
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1692
+ /* @__PURE__ */ jsx7(Text6, { color: accent, bold: true, children: glyphs().vbar }),
1693
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
1694
+ " ",
1695
+ form.mode === "add" ? "NEW ACCOUNT" : "EDIT ACCOUNT"
1423
1696
  ] }),
1424
1697
  /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1425
- " ",
1426
- groups.length,
1427
- " found"
1698
+ " step ",
1699
+ step2,
1700
+ " of 4"
1428
1701
  ] })
1429
1702
  ] }),
1430
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1431
- visible.map((g, i) => {
1432
- const meta = PROVIDERS[g.provider];
1433
- const items = g.accounts.map((a) => stats.get(a.id));
1434
- const ready = g.accounts.every((a) => accountReady(resolveReady(a.id), g.provider));
1435
- const errored = items.some((s) => !!s?.billing?.error);
1436
- const revealed = frame >= i * STAGGER_FRAMES;
1437
- const name = truncateName(meta.name, nameW);
1438
- const namePad = " ".repeat(Math.max(0, nameW - name.length));
1439
- if (!revealed) {
1440
- return /* @__PURE__ */ jsxs6(Box6, { children: [
1441
- /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1442
- glyphs().dot,
1443
- " "
1444
- ] }),
1445
- /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1446
- name,
1447
- namePad
1448
- ] })
1449
- ] }, g.provider);
1703
+ /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx7(Stepper, { active: form.field, accent }) }),
1704
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", borderStyle: glyphs().border, borderColor: accent, paddingX: 2, paddingY: 1, children: [
1705
+ /* @__PURE__ */ jsx7(ProviderField, { value: form.providerId, focused: form.field === "provider" }),
1706
+ /* @__PURE__ */ jsx7(Box6, { height: 1 }),
1707
+ /* @__PURE__ */ jsx7(
1708
+ FormField,
1709
+ {
1710
+ label: "Name",
1711
+ hint: "display name for this account",
1712
+ value: form.name,
1713
+ focused: form.field === "name",
1714
+ caret: form.caret,
1715
+ accent,
1716
+ placeholder: "e.g. Work, Personal"
1450
1717
  }
1451
- return /* @__PURE__ */ jsxs6(Box6, { children: [
1452
- /* @__PURE__ */ jsxs6(Text6, { color: meta.color, children: [
1453
- glyphs().dot,
1454
- " "
1455
- ] }),
1456
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: meta.color, children: name }),
1457
- /* @__PURE__ */ jsx6(Text6, { children: namePad }),
1458
- /* @__PURE__ */ jsx6(Text6, { children: " " }),
1459
- errored ? /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
1460
- glyphs().warn,
1461
- " "
1462
- ] }) : ready ? /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
1463
- glyphs().check,
1464
- " "
1465
- ] }) : /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
1466
- sp[frame % sp.length],
1467
- " "
1468
- ] }),
1469
- errored ? /* @__PURE__ */ jsx6(Text6, { color: "red", children: headlineFor(g, items, privacyMode) }) : ready ? /* @__PURE__ */ jsx6(Text6, { children: headlineFor(g, items, privacyMode) }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1470
- "loading",
1471
- glyphs().ellipsis
1472
- ] })
1473
- ] }, g.provider);
1474
- }),
1475
- hidden > 0 && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1476
- "+",
1477
- hidden,
1478
- " more"
1718
+ ),
1719
+ /* @__PURE__ */ jsx7(Box6, { height: 1 }),
1720
+ /* @__PURE__ */ jsx7(
1721
+ FormField,
1722
+ {
1723
+ label: "Home directory",
1724
+ hint: `path containing the tool's data dir ${glyphs().middot} ~ for default`,
1725
+ value: form.homeDir,
1726
+ focused: form.field === "homeDir",
1727
+ caret: form.caret,
1728
+ accent,
1729
+ placeholder: "~/work",
1730
+ mono: true
1731
+ }
1732
+ ),
1733
+ /* @__PURE__ */ jsx7(Box6, { height: 1 }),
1734
+ /* @__PURE__ */ jsx7(ColorField, { value: form.color, focused: form.field === "color" }),
1735
+ /* @__PURE__ */ jsx7(Box6, { height: 1 }),
1736
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1737
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1738
+ "id ",
1739
+ glyphs().boxMark,
1740
+ " "
1741
+ ] }),
1742
+ /* @__PURE__ */ jsx7(Text6, { bold: true, color: accent, children: previewId || "account" }),
1743
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1744
+ " ",
1745
+ glyphs().boxMark,
1746
+ " auto-generated from name"
1747
+ ] })
1479
1748
  ] })
1480
1749
  ] }),
1750
+ form.error && /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: theme.crit, children: [
1751
+ glyphs().warn,
1752
+ " ",
1753
+ form.error
1754
+ ] }) }),
1481
1755
  /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1482
1756
  /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1483
- "loading ",
1484
- readyCount,
1485
- " / ",
1486
- groups.length
1757
+ "tab/",
1758
+ glyphs().arrowU,
1759
+ glyphs().arrowD,
1760
+ " "
1487
1761
  ] }),
1762
+ /* @__PURE__ */ jsx7(Text6, { children: "switch field" }),
1488
1763
  /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1489
1764
  " ",
1490
1765
  glyphs().middot,
1491
- " W opens the web dashboard once loaded"
1492
- ] })
1766
+ " "
1767
+ ] }),
1768
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "enter " }),
1769
+ /* @__PURE__ */ jsx7(Text6, { children: form.field === "color" ? "save" : "next" }),
1770
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1771
+ " ",
1772
+ glyphs().middot,
1773
+ " "
1774
+ ] }),
1775
+ form.field === "color" || form.field === "provider" ? /* @__PURE__ */ jsxs6(Fragment5, { children: [
1776
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1777
+ glyphs().arrowL,
1778
+ glyphs().arrowR,
1779
+ " "
1780
+ ] }),
1781
+ /* @__PURE__ */ jsx7(Text6, { children: form.field === "provider" ? "pick provider" : "pick color" }),
1782
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1783
+ " ",
1784
+ glyphs().middot,
1785
+ " "
1786
+ ] })
1787
+ ] }) : /* @__PURE__ */ jsxs6(Fragment5, { children: [
1788
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1789
+ glyphs().arrowL,
1790
+ glyphs().arrowR,
1791
+ " "
1792
+ ] }),
1793
+ /* @__PURE__ */ jsx7(Text6, { children: "move caret" }),
1794
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1795
+ " ",
1796
+ glyphs().middot,
1797
+ " "
1798
+ ] })
1799
+ ] }),
1800
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "ctrl+s " }),
1801
+ /* @__PURE__ */ jsx7(Text6, { children: "save" }),
1802
+ /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1803
+ " ",
1804
+ glyphs().middot,
1805
+ " "
1806
+ ] }),
1807
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "esc " }),
1808
+ /* @__PURE__ */ jsx7(Text6, { children: "cancel" })
1493
1809
  ] })
1494
1810
  ] });
1495
1811
  }
1812
+ function Stepper({ active, accent }) {
1813
+ const steps = [
1814
+ { id: "provider", label: "Provider" },
1815
+ { id: "name", label: "Name" },
1816
+ { id: "homeDir", label: "Home" },
1817
+ { id: "color", label: "Color" }
1818
+ ];
1819
+ const activeIdx = steps.findIndex((s) => s.id === active);
1820
+ return /* @__PURE__ */ jsx7(Box6, { children: steps.map((s, i) => {
1821
+ const done = i < activeIdx;
1822
+ const cur = i === activeIdx;
1823
+ const dot = done ? glyphs().dot : cur ? glyphs().dotSel : glyphs().radioOff;
1824
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1825
+ /* @__PURE__ */ jsxs6(Text6, { color: cur || done ? accent : void 0, dimColor: !cur && !done, children: [
1826
+ dot,
1827
+ " "
1828
+ ] }),
1829
+ /* @__PURE__ */ jsx7(Text6, { bold: cur, color: cur ? accent : void 0, dimColor: !cur, children: s.label }),
1830
+ i < steps.length - 1 && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1831
+ " ",
1832
+ glyphs().rule,
1833
+ " "
1834
+ ] })
1835
+ ] }, s.id);
1836
+ }) });
1837
+ }
1838
+ function ProviderField({ value, focused }) {
1839
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1840
+ /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: focused ? PROVIDERS[value].color : void 0, bold: focused, dimColor: !focused, children: [
1841
+ focused ? glyphs().caretR : " ",
1842
+ " Provider"
1843
+ ] }) }),
1844
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1845
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1846
+ " ",
1847
+ focused ? glyphs().vbar : " ",
1848
+ " "
1849
+ ] }),
1850
+ PROVIDER_ORDER.map((pid) => {
1851
+ const selected = pid === value;
1852
+ const p = PROVIDERS[pid];
1853
+ return /* @__PURE__ */ jsx7(Box6, { marginRight: 2, children: selected ? /* @__PURE__ */ jsxs6(Text6, { bold: true, color: p.color, children: [
1854
+ "[",
1855
+ p.name,
1856
+ "]"
1857
+ ] }) : /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: p.name }) }, pid);
1858
+ })
1859
+ ] }),
1860
+ /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " which tool this account tracks" }) })
1861
+ ] });
1862
+ }
1863
+ function FormField({ label, hint, value, focused, caret: caret2, accent, placeholder, mono }) {
1864
+ const isPlaceholder = value === "";
1865
+ const display = isPlaceholder ? placeholder : value;
1866
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1867
+ /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: focused ? accent : void 0, bold: focused, dimColor: !focused, children: [
1868
+ focused ? glyphs().caretR : " ",
1869
+ " ",
1870
+ label
1871
+ ] }) }),
1872
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1873
+ /* @__PURE__ */ jsxs6(Text6, { color: focused ? accent : void 0, children: [
1874
+ " ",
1875
+ focused ? glyphs().vbar : " ",
1876
+ " "
1877
+ ] }),
1878
+ focused ? isPlaceholder ? /* @__PURE__ */ jsxs6(Fragment5, { children: [
1879
+ /* @__PURE__ */ jsx7(Text6, { color: accent, children: glyphs().vbar }),
1880
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, italic: mono, children: placeholder })
1881
+ ] }) : /* @__PURE__ */ jsx7(CaretText, { value, caret: caret2 ?? value.length, color: accent }) : /* @__PURE__ */ jsx7(Text6, { dimColor: isPlaceholder, italic: mono && isPlaceholder, children: display })
1882
+ ] }),
1883
+ /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1884
+ " ",
1885
+ hint
1886
+ ] }) })
1887
+ ] });
1888
+ }
1889
+ function ColorField({ value, focused }) {
1890
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1891
+ /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: focused ? value : void 0, bold: focused, dimColor: !focused, children: [
1892
+ focused ? glyphs().caretR : " ",
1893
+ " Accent color"
1894
+ ] }) }),
1895
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1896
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1897
+ " ",
1898
+ focused ? glyphs().vbar : " ",
1899
+ " "
1900
+ ] }),
1901
+ COLOR_PALETTE.map((c) => /* @__PURE__ */ jsx7(Box6, { marginRight: 1, children: c === value ? /* @__PURE__ */ jsxs6(Text6, { bold: true, color: c, children: [
1902
+ "[",
1903
+ glyphs().dot,
1904
+ "]"
1905
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { color: c, dimColor: !focused, children: [
1906
+ " ",
1907
+ glyphs().dot
1908
+ ] }) }, c))
1909
+ ] }),
1910
+ /* @__PURE__ */ jsx7(Box6, { children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " shows on dashboard, account strip, borders" }) })
1911
+ ] });
1912
+ }
1496
1913
 
1497
1914
  // src/ui/app-layout.logic.ts
1498
1915
  function deriveSlots(accounts, privacyMode = false) {
@@ -1518,32 +1935,33 @@ function computeChrome(slots, cols, rows) {
1518
1935
 
1519
1936
  // src/ui/resizing.tsx
1520
1937
  import { Box as Box7, Text as Text7 } from "ink";
1521
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1938
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1522
1939
  function ResizingView({ cols, rows }) {
1523
- return /* @__PURE__ */ jsx7(Box7, { width: cols, height: rows, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1940
+ const theme = useTuiTheme();
1941
+ return /* @__PURE__ */ jsx8(Box7, { width: cols, height: rows, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1524
1942
  glyphs().dotSel,
1525
1943
  " resizing\u2026 ",
1526
- /* @__PURE__ */ jsx7(Text7, { color: "greenBright", children: cols }),
1944
+ /* @__PURE__ */ jsx8(Text7, { color: theme.accent, children: cols }),
1527
1945
  "\xD7",
1528
- /* @__PURE__ */ jsx7(Text7, { color: "greenBright", children: rows })
1946
+ /* @__PURE__ */ jsx8(Text7, { color: theme.accent, children: rows })
1529
1947
  ] }) });
1530
1948
  }
1531
1949
 
1532
1950
  // src/ui/account-strip.tsx
1533
1951
  import { memo as memo5 } from "react";
1534
1952
  import { Box as Box8, Text as Text8 } from "ink";
1535
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1953
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1536
1954
  var AccountStrip = memo5(function AccountStrip2({ slots, activeIdx, onSelect }) {
1537
- return /* @__PURE__ */ jsx8(Box8, { flexWrap: "wrap", children: slots.map((s, i) => {
1955
+ return /* @__PURE__ */ jsx9(Box8, { flexWrap: "wrap", children: slots.map((s, i) => {
1538
1956
  const active = i === activeIdx;
1539
1957
  const dot = s.id === null ? glyphs().dotAll : glyphs().dot;
1540
1958
  const label = truncateName(s.name, 16);
1541
1959
  return /* @__PURE__ */ jsxs8(ClickableBox, { onClick: () => onSelect(i), marginRight: 2, children: [
1542
- /* @__PURE__ */ jsx8(Text8, { dimColor: !active, children: i }),
1543
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
1544
- /* @__PURE__ */ jsx8(Text8, { color: s.color, bold: active, dimColor: !active, children: dot }),
1545
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
1546
- active ? /* @__PURE__ */ jsx8(Text8, { bold: true, color: s.color, children: label }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: label })
1960
+ /* @__PURE__ */ jsx9(Text8, { dimColor: !active, children: i }),
1961
+ /* @__PURE__ */ jsx9(Text8, { children: " " }),
1962
+ /* @__PURE__ */ jsx9(Text8, { color: s.color, bold: active, dimColor: !active, children: dot }),
1963
+ /* @__PURE__ */ jsx9(Text8, { children: " " }),
1964
+ active ? /* @__PURE__ */ jsx9(Text8, { bold: true, color: s.color, children: label }) : /* @__PURE__ */ jsx9(Text8, { dimColor: true, children: label })
1547
1965
  ] }, s.id ?? "__all__");
1548
1966
  }) });
1549
1967
  });
@@ -1596,7 +2014,7 @@ function osc8(text, url) {
1596
2014
  }
1597
2015
 
1598
2016
  // src/ui/footer.tsx
1599
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2017
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1600
2018
  var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
1601
2019
  const inner = cols - 4;
1602
2020
  const BASE = "by David Ilie (davidilie.com) \xB7 r/R=refresh O=repo W=web s=settings q=quit".length;
@@ -1608,22 +2026,22 @@ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
1608
2026
  const showJump = hasAccounts && inner >= BASE + (showOpt ? OPT : 0) + JUMP + (paginated ? PAGE : 0);
1609
2027
  const showPage = paginated && inner >= BASE + (showOpt ? OPT : 0) + (showJump ? JUMP : 0) + PAGE;
1610
2028
  if (inner < BASE) {
1611
- return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexWrap: "nowrap", children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, wrap: "truncate-end", children: "r/R=refresh O=repo W=web s=settings q=quit" }) });
2029
+ return /* @__PURE__ */ jsx10(Box9, { marginTop: 1, flexWrap: "nowrap", children: /* @__PURE__ */ jsx10(Text9, { dimColor: true, wrap: "truncate-end", children: "r/R=refresh O=repo W=web s=settings q=quit" }) });
1612
2030
  }
1613
2031
  return /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexWrap: "nowrap", children: [
1614
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "by " }),
1615
- /* @__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" }) }) }),
1616
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: " (" }),
1617
- /* @__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" }) }) }),
2032
+ /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "by " }),
2033
+ /* @__PURE__ */ jsx10(LinkBox, { onClick: () => openUrl(REPO_URL), children: /* @__PURE__ */ jsx10(Transform, { transform: (s) => osc8(s, REPO_URL), children: /* @__PURE__ */ jsx10(Text9, { underline: true, children: "David Ilie" }) }) }),
2034
+ /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: " (" }),
2035
+ /* @__PURE__ */ jsx10(LinkBox, { onClick: () => openUrl(SITE_URL), children: /* @__PURE__ */ jsx10(Transform, { transform: (s) => osc8(s, SITE_URL), children: /* @__PURE__ */ jsx10(Text9, { color: "cyan", underline: true, children: "davidilie.com" }) }) }),
1618
2036
  /* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
1619
2037
  ") ",
1620
2038
  glyphs().middot,
1621
2039
  " r/R=refresh O=repo W=web s=settings "
1622
2040
  ] }),
1623
- showOpt && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: optHint }),
1624
- showJump && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "0-9=jump a/A=cycle " }),
1625
- showPage && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "scroll=page " }),
1626
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "q=quit" })
2041
+ showOpt && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: optHint }),
2042
+ showJump && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "0-9=jump a/A=cycle " }),
2043
+ showPage && /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "scroll=page " }),
2044
+ /* @__PURE__ */ jsx10(Text9, { dimColor: true, children: "q=quit" })
1627
2045
  ] });
1628
2046
  });
1629
2047
 
@@ -1633,16 +2051,17 @@ import { Box as Box10, Text as Text11 } from "ink";
1633
2051
  // src/ui/refresh-status.tsx
1634
2052
  import { memo as memo7 } from "react";
1635
2053
  import { Text as Text10 } from "ink";
1636
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2054
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1637
2055
  var RefreshStatusLine = memo7(function RefreshStatusLine2({ status }) {
2056
+ const theme = useTuiTheme();
1638
2057
  if (status.phase === "idle") return null;
1639
- if (status.phase === "refreshing") return /* @__PURE__ */ jsx10(Spinner, { label: `${status.message}${glyphs().ellipsis}` });
1640
- if (status.phase === "success") return /* @__PURE__ */ jsxs10(Text10, { color: "green", children: [
2058
+ if (status.phase === "refreshing") return /* @__PURE__ */ jsx11(Spinner, { label: `${status.message}${glyphs().ellipsis}` });
2059
+ if (status.phase === "success") return /* @__PURE__ */ jsxs10(Text10, { color: theme.ok, children: [
1641
2060
  glyphs().check,
1642
2061
  " ",
1643
2062
  status.message
1644
2063
  ] });
1645
- return /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
2064
+ return /* @__PURE__ */ jsxs10(Text10, { color: theme.crit, children: [
1646
2065
  glyphs().warn,
1647
2066
  " ",
1648
2067
  status.message
@@ -1650,14 +2069,15 @@ var RefreshStatusLine = memo7(function RefreshStatusLine2({ status }) {
1650
2069
  });
1651
2070
 
1652
2071
  // src/ui/tiny-fallback.tsx
1653
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
2072
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1654
2073
  function TinyFallback({ groups, stats, rows, cols, refreshStatus }) {
2074
+ const theme = useTuiTheme();
1655
2075
  const maxLines = Math.max(1, rows - 4);
1656
2076
  const visible = groups.slice(0, maxLines);
1657
2077
  const hidden = groups.length - visible.length;
1658
2078
  const w = Math.max(8, cols - 2);
1659
2079
  return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", paddingX: 1, height: rows, overflow: "hidden", children: [
1660
- /* @__PURE__ */ jsxs11(Text11, { bold: true, color: "greenBright", children: [
2080
+ /* @__PURE__ */ jsxs11(Text11, { bold: true, color: theme.accent, children: [
1661
2081
  glyphs().dotSel,
1662
2082
  " tokmon"
1663
2083
  ] }),
@@ -1665,15 +2085,15 @@ function TinyFallback({ groups, stats, rows, cols, refreshStatus }) {
1665
2085
  "No providers ",
1666
2086
  glyphs().emDash,
1667
2087
  " s=settings"
1668
- ] }) : visible.map((g) => /* @__PURE__ */ jsx11(TinyRow, { provider: g.provider, accounts: g.accounts, stats, width: w }, g.provider)),
2088
+ ] }) : visible.map((g) => /* @__PURE__ */ jsx12(TinyRow, { provider: g.provider, accounts: g.accounts, stats, width: w }, g.provider)),
1669
2089
  hidden > 0 && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
1670
2090
  "+",
1671
2091
  hidden,
1672
2092
  " more (enlarge terminal)"
1673
2093
  ] }),
1674
- /* @__PURE__ */ jsx11(RefreshStatusLine, { status: refreshStatus }),
1675
- /* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
1676
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "R=refresh s=settings q=quit" })
2094
+ /* @__PURE__ */ jsx12(RefreshStatusLine, { status: refreshStatus }),
2095
+ /* @__PURE__ */ jsx12(Box10, { flexGrow: 1 }),
2096
+ /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: "R=refresh s=settings q=quit" })
1677
2097
  ] });
1678
2098
  }
1679
2099
  function TinyRow({ provider, accounts, stats, width }) {
@@ -1689,19 +2109,19 @@ function TinyRow({ provider, accounts, stats, width }) {
1689
2109
  glyphs().dot,
1690
2110
  " "
1691
2111
  ] }),
1692
- /* @__PURE__ */ jsx11(Text11, { bold: true, color: meta.color, children: name }),
1693
- /* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
1694
- /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: detail })
2112
+ /* @__PURE__ */ jsx12(Text11, { bold: true, color: meta.color, children: name }),
2113
+ /* @__PURE__ */ jsx12(Box10, { flexGrow: 1 }),
2114
+ /* @__PURE__ */ jsx12(Text11, { dimColor: true, children: detail })
1695
2115
  ] });
1696
2116
  }
1697
2117
 
1698
2118
  // src/client/use-daemon.ts
1699
- import { useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState3 } from "react";
2119
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
1700
2120
  function useDaemon(baseUrl) {
1701
2121
  const [snapshot, setSnapshot] = useState3(null);
1702
2122
  const [conn, setConn] = useState3("connecting");
1703
2123
  const [config, setConfigState] = useState3(null);
1704
- const client = useMemo(() => {
2124
+ const client = useMemo2(() => {
1705
2125
  if (!baseUrl) return null;
1706
2126
  return createDaemonRpcClient(baseUrl, {
1707
2127
  transport: "node",
@@ -1744,6 +2164,7 @@ function indexById(snapshot) {
1744
2164
  }
1745
2165
  function toStatsMap(snapshot, accounts) {
1746
2166
  const byId = indexById(snapshot);
2167
+ const providerHeadroom = new Map(snapshot?.providers.map((provider) => [provider.id, provider.headroom]) ?? []);
1747
2168
  const out = /* @__PURE__ */ new Map();
1748
2169
  for (const account of accounts) {
1749
2170
  const wa = byId.get(account.id);
@@ -1751,7 +2172,11 @@ function toStatsMap(snapshot, accounts) {
1751
2172
  account,
1752
2173
  dashboard: wa?.dashboard ?? null,
1753
2174
  billing: wa?.billing ?? null,
1754
- billingUpdatedAt: wa?.billingUpdatedAt ?? null
2175
+ billingUpdatedAt: wa?.billingUpdatedAt ?? null,
2176
+ identity: wa?.identity,
2177
+ quotas: wa?.quotas,
2178
+ headroom: wa?.headroom,
2179
+ providerHeadroom: providerHeadroom.get(account.providerId)
1755
2180
  });
1756
2181
  }
1757
2182
  return out;
@@ -1777,13 +2202,13 @@ var SORTS = [
1777
2202
  { label: "cost", dir: "down" }
1778
2203
  ];
1779
2204
  var acctKey = (a) => `${a.id}:${a.homeDir ?? ""}`;
1780
- var clampCaret = (caret, len) => Math.max(0, Math.min(caret, len));
1781
- function spliceInsert(value, caret, text) {
1782
- const c = clampCaret(caret, value.length);
2205
+ var clampCaret = (caret2, len) => Math.max(0, Math.min(caret2, len));
2206
+ function spliceInsert(value, caret2, text) {
2207
+ const c = clampCaret(caret2, value.length);
1783
2208
  return { value: value.slice(0, c) + text + value.slice(c), caret: c + text.length };
1784
2209
  }
1785
- function spliceBackspace(value, caret) {
1786
- const c = clampCaret(caret, value.length);
2210
+ function spliceBackspace(value, caret2) {
2211
+ const c = clampCaret(caret2, value.length);
1787
2212
  if (c === 0) return { value, caret: 0 };
1788
2213
  return { value: value.slice(0, c - 1) + value.slice(c), caret: c - 1 };
1789
2214
  }
@@ -1983,11 +2408,11 @@ function handleTimezoneEditor(input, key, editor, text, updateConfig) {
1983
2408
  return;
1984
2409
  }
1985
2410
  if (key.leftArrow) {
1986
- setCaret((caret) => clampCaret(caret - 1, value.length));
2411
+ setCaret((caret2) => clampCaret(caret2 - 1, value.length));
1987
2412
  return;
1988
2413
  }
1989
2414
  if (key.rightArrow) {
1990
- setCaret((caret) => clampCaret(caret + 1, value.length));
2415
+ setCaret((caret2) => clampCaret(caret2 + 1, value.length));
1991
2416
  return;
1992
2417
  }
1993
2418
  if (key.ctrl && input === "a") {
@@ -2033,11 +2458,11 @@ function handleAllowedHostsEditor(input, key, editor, text, updateConfig) {
2033
2458
  return;
2034
2459
  }
2035
2460
  if (key.leftArrow) {
2036
- setCaret((caret) => clampCaret(caret - 1, value.length));
2461
+ setCaret((caret2) => clampCaret(caret2 - 1, value.length));
2037
2462
  return;
2038
2463
  }
2039
2464
  if (key.rightArrow) {
2040
- setCaret((caret) => clampCaret(caret + 1, value.length));
2465
+ setCaret((caret2) => clampCaret(caret2 + 1, value.length));
2041
2466
  return;
2042
2467
  }
2043
2468
  if (key.ctrl && input === "a") {
@@ -2073,11 +2498,11 @@ function handleSearchEditor(input, key, table, text) {
2073
2498
  return;
2074
2499
  }
2075
2500
  if (key.leftArrow) {
2076
- setSearchCaret((caret) => clampCaret(caret - 1, search.length));
2501
+ setSearchCaret((caret2) => clampCaret(caret2 - 1, search.length));
2077
2502
  return;
2078
2503
  }
2079
2504
  if (key.rightArrow) {
2080
- setSearchCaret((caret) => clampCaret(caret + 1, search.length));
2505
+ setSearchCaret((caret2) => clampCaret(caret2 + 1, search.length));
2081
2506
  return;
2082
2507
  }
2083
2508
  if (key.ctrl && input === "a") {
@@ -2137,7 +2562,7 @@ function handleSettings(input, key, ctx) {
2137
2562
  ctx.allowedHostsEditor.setValue(null);
2138
2563
  ctx.allowedHostsEditor.setError(null);
2139
2564
  };
2140
- const rowCount = tab === "general" ? GENERAL_ROWS : tab === "providers" ? PROVIDER_ORDER.length : trackedAccounts.length + 1;
2565
+ const rowCount = tab === "general" ? GENERAL_ROWS : tab === "theme" ? THEME_ROWS : tab === "desktop" ? DESKTOP_FIXED_ROWS + PROVIDER_ORDER.length : tab === "providers" ? PROVIDER_ORDER.length + 1 : trackedAccounts.length + 1;
2141
2566
  if (key.tab) {
2142
2567
  switchTab(key.shift ? -1 : 1);
2143
2568
  return;
@@ -2183,17 +2608,64 @@ function handleSettings(input, key, ctx) {
2183
2608
  return;
2184
2609
  }
2185
2610
  if (tab === "general") {
2186
- handleGeneralSetting(input, key, ctx);
2611
+ GENERAL_SETTINGS[cursor]?.onAdjust(input, key, ctx);
2612
+ return;
2613
+ }
2614
+ if (tab === "theme") {
2615
+ THEME_SETTINGS[cursor]?.onAdjust(input, key, ctx);
2616
+ return;
2617
+ }
2618
+ if (tab === "desktop") {
2619
+ if (cursor < DESKTOP_FIXED_ROWS) {
2620
+ DESKTOP_FIXED_SETTINGS[cursor]?.onAdjust(input, key, ctx);
2621
+ return;
2622
+ }
2623
+ const provider = PROVIDER_ORDER[cursor - DESKTOP_FIXED_ROWS];
2624
+ if (provider && (input === " " || key.leftArrow || key.rightArrow || key.return)) {
2625
+ updateConfig((current) => ({
2626
+ ...current,
2627
+ tray: {
2628
+ ...current.tray,
2629
+ pinnedProviders: toggleProviderSelection(current.tray.pinnedProviders, provider, new Set(PROVIDER_ORDER), 2)
2630
+ }
2631
+ }));
2632
+ }
2187
2633
  return;
2188
2634
  }
2189
2635
  if (tab === "providers") {
2190
- if (cursor < PROVIDER_ORDER.length && (input === " " || key.return || key.leftArrow || key.rightArrow)) {
2191
- toggleProvider(PROVIDER_ORDER[cursor]);
2636
+ if (cursor === 0 && (input === " " || key.return || key.leftArrow || key.rightArrow)) {
2637
+ updateConfig((current) => ({
2638
+ ...current,
2639
+ accountDetection: { ...current.accountDetection, enabled: !current.accountDetection.enabled }
2640
+ }));
2641
+ return;
2642
+ }
2643
+ const provider = PROVIDER_ORDER[cursor - 1];
2644
+ if (provider && input === "a") {
2645
+ updateConfig((current) => ({
2646
+ ...current,
2647
+ accountDetection: setProviderDetectionEnabled(
2648
+ current.accountDetection,
2649
+ provider,
2650
+ !providerDetectionEnabled(current.accountDetection, provider)
2651
+ )
2652
+ }));
2653
+ return;
2654
+ }
2655
+ if (provider && (input === " " || key.return || key.leftArrow || key.rightArrow)) {
2656
+ toggleProvider(provider);
2192
2657
  }
2193
2658
  return;
2194
2659
  }
2195
2660
  if (cursor < trackedAccounts.length) {
2196
2661
  const row = trackedAccounts[cursor];
2662
+ if (row.source === "ignored" && (key.return || input === "x")) {
2663
+ if (row.excludedRef) updateConfig((current) => ({
2664
+ ...current,
2665
+ accountDetection: setDetectedAccountExcluded(current.accountDetection, row.excludedRef, false)
2666
+ }));
2667
+ return;
2668
+ }
2197
2669
  if (key.return) {
2198
2670
  if (row.source === "configured") {
2199
2671
  const account = config.accounts.find((candidate) => candidate.id === row.explicitId);
@@ -2207,6 +2679,17 @@ function handleSettings(input, key, ctx) {
2207
2679
  deleteAccount(row.explicitId);
2208
2680
  return;
2209
2681
  }
2682
+ if (row.source === "auto" && input === "x") {
2683
+ updateConfig((current) => ({
2684
+ ...current,
2685
+ activeAccountId: current.activeAccountId === row.id ? null : current.activeAccountId,
2686
+ accountDetection: setDetectedAccountExcluded(current.accountDetection, {
2687
+ providerId: row.providerId,
2688
+ homeDir: row.homeDir
2689
+ }, true)
2690
+ }));
2691
+ return;
2692
+ }
2210
2693
  if (input === " ") {
2211
2694
  updateConfig((current) => ({ ...current, activeAccountId: row.id }));
2212
2695
  return;
@@ -2214,69 +2697,6 @@ function handleSettings(input, key, ctx) {
2214
2697
  return;
2215
2698
  }
2216
2699
  if (cursor === trackedAccounts.length && key.return) openAddAccount();
2217
- function handleGeneralSetting(value, inputKey, context) {
2218
- const index = context.settings.cursor;
2219
- if (index === 0) {
2220
- if (inputKey.leftArrow) updateConfig((current) => ({ ...current, interval: Math.max(1, current.interval - 1) }));
2221
- if (inputKey.rightArrow) updateConfig((current) => ({ ...current, interval: current.interval + 1 }));
2222
- return;
2223
- }
2224
- if (index === 1) {
2225
- if (inputKey.leftArrow) updateConfig((current) => ({ ...current, billingInterval: Math.max(1, current.billingInterval - 1) }));
2226
- if (inputKey.rightArrow) updateConfig((current) => ({ ...current, billingInterval: current.billingInterval + 1 }));
2227
- return;
2228
- }
2229
- if (index === 2 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
2230
- updateConfig((current) => ({ ...current, clearScreen: !current.clearScreen }));
2231
- return;
2232
- }
2233
- if (index === 3 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
2234
- updateConfig((current) => ({ ...current, privacyMode: !current.privacyMode }));
2235
- return;
2236
- }
2237
- if (index === 4) {
2238
- if (textInput.isPrintable(value, inputKey)) {
2239
- const clean = sanitizeTyped(value);
2240
- if (clean.length === 1) updateConfig((current) => ({ ...current, privacyToggleKey: clean }));
2241
- }
2242
- if (inputKey.backspace || inputKey.delete) updateConfig((current) => ({ ...current, privacyToggleKey: "p" }));
2243
- return;
2244
- }
2245
- if (index === 5) {
2246
- if (inputKey.return) {
2247
- const initial = config.timezone ?? "";
2248
- timezoneEditor.setValue(initial);
2249
- timezoneEditor.setCaret(initial.length);
2250
- timezoneEditor.setError(null);
2251
- }
2252
- if (inputKey.leftArrow || inputKey.rightArrow) {
2253
- updateConfig((current) => ({ ...current, timezone: current.timezone === null ? systemTimezone() : null }));
2254
- }
2255
- return;
2256
- }
2257
- if (index === 6 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
2258
- updateConfig((current) => ({ ...current, dashboardLayout: current.dashboardLayout === "grid" ? "single" : "grid" }));
2259
- return;
2260
- }
2261
- if (index === 7 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
2262
- updateConfig((current) => ({ ...current, defaultFocus: current.defaultFocus === "all" ? "last" : "all" }));
2263
- return;
2264
- }
2265
- if (index === 8 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
2266
- updateConfig((current) => ({ ...current, allowNetworkAccess: !current.allowNetworkAccess }));
2267
- return;
2268
- }
2269
- if (index === 9 && inputKey.return) {
2270
- const initial = config.allowedHosts.join(", ");
2271
- context.allowedHostsEditor.setValue(initial);
2272
- context.allowedHostsEditor.setCaret(initial.length);
2273
- context.allowedHostsEditor.setError(null);
2274
- return;
2275
- }
2276
- if (index === 10 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
2277
- updateConfig((current) => ({ ...current, resetDisplay: current.resetDisplay === "relative" ? "absolute" : "relative" }));
2278
- }
2279
- }
2280
2700
  }
2281
2701
 
2282
2702
  // src/ui/keybindings-navigation.ts
@@ -2345,7 +2765,8 @@ function handleNavigation(input, key, ctx) {
2345
2765
  }
2346
2766
  if (table.tab !== 1) {
2347
2767
  if (key.leftArrow || key.rightArrow) {
2348
- global.setTab((current) => (current + 1) % TABS.length);
2768
+ const step2 = key.leftArrow ? -1 : 1;
2769
+ global.setTab((current) => (current + step2 + TABS.length) % TABS.length);
2349
2770
  resetView();
2350
2771
  }
2351
2772
  return;
@@ -3148,8 +3569,8 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
3148
3569
  if (!f) return f;
3149
3570
  const i = FORM_FIELDS.indexOf(f.field);
3150
3571
  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 };
3572
+ const caret2 = next === "name" ? f.name.length : next === "homeDir" ? f.homeDir.length : f.caret;
3573
+ return { ...f, field: next, caret: caret2 };
3153
3574
  });
3154
3575
  }, []);
3155
3576
  const cycleProvider = useCallback6((dir) => {
@@ -3200,7 +3621,7 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
3200
3621
  }
3201
3622
 
3202
3623
  // src/app.tsx
3203
- import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
3624
+ import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3204
3625
  function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "degraded" }) {
3205
3626
  const connected = mode === "connected" && baseUrl !== null;
3206
3627
  const degraded = !connected;
@@ -3246,9 +3667,24 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3246
3667
  const billingMs = cfg.billingInterval * 6e4;
3247
3668
  const tz = resolveTimezone(cfg.timezone);
3248
3669
  const configReady = config !== null;
3249
- const accounts = useMemo2(() => buildAccounts(cfg, detected), [cfg, detected]);
3250
- const trackedAccountRows = useMemo2(() => getTrackedAccountRows(cfg, detected, accounts), [cfg, detected, accounts]);
3251
- const settingsRowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "providers" ? PROVIDER_ORDER.length : trackedAccountRows.length + 1;
3670
+ const snapshot = daemon.snapshot;
3671
+ const daemonDetected = useMemo3(
3672
+ () => [...new Set((snapshot?.accounts ?? []).map((account) => account.providerId))],
3673
+ [snapshot]
3674
+ );
3675
+ const detectedProviders = connected ? daemonDetected : detected;
3676
+ const accounts = useMemo3(() => connected ? (snapshot?.accounts ?? []).map((account) => ({
3677
+ id: account.id,
3678
+ providerId: account.providerId,
3679
+ name: account.name,
3680
+ color: account.color,
3681
+ homeDir: account.homeDir ?? void 0
3682
+ })) : buildAccounts(cfg, detectedProviders), [cfg, connected, detectedProviders, snapshot]);
3683
+ const trackedAccountRows = useMemo3(
3684
+ () => getTrackedAccountRows(cfg, detectedProviders, accounts),
3685
+ [cfg, detectedProviders, accounts]
3686
+ );
3687
+ const settingsRowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "theme" ? THEME_ROWS : settingsTab === "desktop" ? DESKTOP_FIXED_ROWS + PROVIDER_ORDER.length : settingsTab === "providers" ? PROVIDER_ORDER.length + 1 : trackedAccountRows.length + 1;
3252
3688
  const accountsRef = useRef8([]);
3253
3689
  accountsRef.current = accounts;
3254
3690
  const rowCountRef = useRef8(0);
@@ -3261,19 +3697,19 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3261
3697
  const allowedHostsCaretRef = useRef8(0);
3262
3698
  const searchValueRef = useRef8("");
3263
3699
  const searchCaretRef = useRef8(0);
3264
- const accountsKey = useMemo2(() => accounts.map(acctKey).join("|"), [accounts]);
3265
- const allGroups = useMemo2(() => accountsByProvider(accounts), [accounts]);
3266
- const tableProvs = useMemo2(
3700
+ const accountsKey = useMemo3(() => accounts.map(acctKey).join("|"), [accounts]);
3701
+ const allGroups = useMemo3(() => accountsByProvider(accounts), [accounts]);
3702
+ const tableProvs = useMemo3(
3267
3703
  () => allGroups.map((g) => g.provider).filter((pid) => PROVIDERS[pid].hasUsage),
3268
3704
  [allGroups]
3269
3705
  );
3270
3706
  const effTableProvider = tableProvider && tableProvs.includes(tableProvider) ? tableProvider : tableProvs[0] ?? null;
3271
- const tableAccounts = useMemo2(
3707
+ const tableAccounts = useMemo3(
3272
3708
  () => effTableProvider ? accounts.filter((a) => a.providerId === effTableProvider) : [],
3273
3709
  [accounts, effTableProvider]
3274
3710
  );
3275
3711
  const needsOnboarding = configReady && !cfg.onboarded;
3276
- const newProviders = configReady && cfg.onboarded ? PROVIDER_ORDER.filter((p) => !cfg.knownProviders.includes(p) && detected.includes(p)) : [];
3712
+ const newProviders = configReady && cfg.onboarded ? PROVIDER_ORDER.filter((p) => !cfg.knownProviders.includes(p) && detectedProviders.includes(p)) : [];
3277
3713
  const showPicker = needsOnboarding || newProviders.length > 0;
3278
3714
  const {
3279
3715
  statsLocal,
@@ -3294,7 +3730,6 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3294
3730
  activeTableProvider: effTableProvider,
3295
3731
  tableVisible: tab === 1
3296
3732
  });
3297
- const snapshot = daemon.snapshot;
3298
3733
  const daemonRefreshRef = useRef8(daemon.refresh);
3299
3734
  const degradedRefreshRef = useRef8(refreshAllDegraded);
3300
3735
  daemonRefreshRef.current = daemon.refresh;
@@ -3306,11 +3741,11 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3306
3741
  requestDaemonRefresh,
3307
3742
  requestDegradedRefresh
3308
3743
  });
3309
- const stats = useMemo2(
3744
+ const stats = useMemo3(
3310
3745
  () => connected ? toStatsMap(snapshot, accounts) : statsLocal,
3311
3746
  [connected, snapshot, accounts, statsLocal]
3312
3747
  );
3313
- const accountIdentities = useMemo2(() => {
3748
+ const accountIdentities = useMemo3(() => {
3314
3749
  const out = /* @__PURE__ */ new Map();
3315
3750
  for (const [id, stat] of stats) {
3316
3751
  const billing = stat.billing;
@@ -3325,7 +3760,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3325
3760
  }, [stats]);
3326
3761
  const showPeak = accounts.some((a) => a.providerId === "claude");
3327
3762
  const peak = connected ? showPeak ? snapshot?.peak ?? null : null : peakLocal;
3328
- const updated = useMemo2(
3763
+ const updated = useMemo3(
3329
3764
  () => connected ? new Date(snapshot?.generatedAt ?? Date.now()) : updatedLocal,
3330
3765
  [connected, snapshot, updatedLocal]
3331
3766
  );
@@ -3339,23 +3774,23 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3339
3774
  }
3340
3775
  return statsReadyInput(statsLocal.get(id));
3341
3776
  }, [connected, snapshot, statsLocal]);
3342
- const slots = useMemo2(() => deriveSlots(accounts, cfg.privacyMode), [accounts, cfg.privacyMode]);
3343
- const { activeSlotIdx, focusId } = useMemo2(
3777
+ const slots = useMemo3(() => deriveSlots(accounts, cfg.privacyMode), [accounts, cfg.privacyMode]);
3778
+ const { activeSlotIdx, focusId } = useMemo3(
3344
3779
  () => findActiveSlot(slots, cfg.activeAccountId),
3345
3780
  [slots, cfg.activeAccountId]
3346
3781
  );
3347
- const visibleAccounts = useMemo2(
3782
+ const visibleAccounts = useMemo3(
3348
3783
  () => focusId === null ? accounts : accounts.filter((a) => a.id === focusId),
3349
3784
  [accounts, focusId]
3350
3785
  );
3351
- const groups = useMemo2(
3786
+ const groups = useMemo3(
3352
3787
  () => focusId === null ? allGroups : accountsByProvider(visibleAccounts),
3353
3788
  [allGroups, visibleAccounts, focusId]
3354
3789
  );
3355
3790
  const TOO_SMALL = cols < 40 || rows < 12;
3356
3791
  const allReady = accounts.length > 0 && accounts.every((a) => accountReady(readyInputFor(a.id), a.providerId));
3357
- const { gridBudget } = useMemo2(() => computeChrome(slots, cols, rows), [slots, cols, rows]);
3358
- const dashLayout = useMemo2(
3792
+ const { gridBudget } = useMemo3(() => computeChrome(slots, cols, rows), [slots, cols, rows]);
3793
+ const dashLayout = useMemo3(
3359
3794
  () => computeDashLayout(groups, stats, cols, gridBudget, focusId, cfg.dashboardLayout),
3360
3795
  [groups, stats, cols, gridBudget, focusId, cfg.dashboardLayout]
3361
3796
  );
@@ -3400,8 +3835,8 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3400
3835
  };
3401
3836
  const { handlePasteData, isPasteInput } = usePaste(insertText);
3402
3837
  const SORTS_FOR = SORTS;
3403
- const tableAccountIds = useMemo2(() => tableAccounts.map((a) => a.id), [tableAccounts]);
3404
- const table = useMemo2(
3838
+ const tableAccountIds = useMemo3(() => tableAccounts.map((a) => a.id), [tableAccounts]);
3839
+ const table = useMemo3(
3405
3840
  () => connected ? pickTable(snapshot, tableAccountIds) : tableLocal,
3406
3841
  [connected, snapshot, tableAccountIds, tableLocal]
3407
3842
  );
@@ -3415,18 +3850,19 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3415
3850
  accountsCount: accounts.length
3416
3851
  });
3417
3852
  const pickerProviders = needsOnboarding ? PROVIDER_ORDER : newProviders;
3418
- const onboardEnabled = onboardSel ?? detected;
3853
+ const onboardEnabled = onboardSel ?? detectedProviders;
3419
3854
  const onboardItems = pickerProviders.map((pid) => ({
3420
3855
  id: pid,
3421
3856
  name: PROVIDERS[pid].name,
3422
3857
  color: PROVIDERS[pid].color,
3423
- detected: detected.includes(pid),
3858
+ detected: detectedProviders.includes(pid),
3424
3859
  enabled: onboardEnabled.includes(pid)
3425
3860
  }));
3426
3861
  useEffect9(() => {
3862
+ if (connected) return;
3427
3863
  void detectProviders().then(setDetected);
3428
- }, []);
3429
- const tableKey = useMemo2(
3864
+ }, [connected]);
3865
+ const tableKey = useMemo3(
3430
3866
  () => `${effTableProvider}|${tableAccounts.map(acctKey).join(",")}|${tz}`,
3431
3867
  [effTableProvider, tableAccounts, tz]
3432
3868
  );
@@ -3484,7 +3920,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3484
3920
  if (i < 0 || i >= pickerProviders.length) return;
3485
3921
  const pid = pickerProviders[i];
3486
3922
  setOnboardSel((prev) => {
3487
- const base = prev ?? detected;
3923
+ const base = prev ?? detectedProviders;
3488
3924
  return base.includes(pid) ? base.filter((p) => p !== pid) : [...base, pid];
3489
3925
  });
3490
3926
  }
@@ -3547,7 +3983,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3547
3983
  cycleColor,
3548
3984
  deleteAccount,
3549
3985
  moveAccount
3550
- } = useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSettingsCursor });
3986
+ } = useAccountForm({ cfg, detected: detectedProviders, updateConfig, trackedAccountRows, setSettingsCursor });
3551
3987
  async function toggleWeb() {
3552
3988
  if (connected) {
3553
3989
  if (baseUrl) openUrl(baseUrl);
@@ -3560,7 +3996,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3560
3996
  if (webStartingRef.current) return;
3561
3997
  webStartingRef.current = true;
3562
3998
  try {
3563
- const { startWebServer } = await import("./server-EUO7CWYH.js");
3999
+ const { startWebServer } = await import("./server-EPSH52DR.js");
3564
4000
  const ctrl = await startWebServer({ config: cfg, log: false });
3565
4001
  webRef.current = ctrl;
3566
4002
  openUrl(ctrl.url);
@@ -3590,11 +4026,11 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3590
4026
  if (idx === cursor) setExpanded((e) => e === idx ? -1 : idx);
3591
4027
  else setCursor(idx);
3592
4028
  }, [cursor]);
3593
- const rawTokenRows = useMemo2(
4029
+ const rawTokenRows = useMemo3(
3594
4030
  () => tab === 1 ? table ? [table.daily, table.weekly, table.monthly][view] : [] : [],
3595
4031
  [tab, table, view]
3596
4032
  );
3597
- const tokenModelOptions = useMemo2(() => tableModelOptions(rawTokenRows), [rawTokenRows]);
4033
+ const tokenModelOptions = useMemo3(() => tableModelOptions(rawTokenRows), [rawTokenRows]);
3598
4034
  useEffect9(() => {
3599
4035
  if (tableModel && !tokenModelOptions.includes(tableModel)) setTableModel(null);
3600
4036
  }, [tableModel, tokenModelOptions]);
@@ -3603,7 +4039,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3603
4039
  resetView();
3604
4040
  }, [tokenModelOptions, resetView]);
3605
4041
  const activeTableModel = tableModel && tokenModelOptions.includes(tableModel) ? tableModel : null;
3606
- const tokenRows = useMemo2(
4042
+ const tokenRows = useMemo3(
3607
4043
  () => tab === 1 ? sortRows(filterTokenRows(filterRowsByModel(rawTokenRows, activeTableModel), search), sort) : [],
3608
4044
  [tab, rawTokenRows, activeTableModel, search, sort]
3609
4045
  );
@@ -3695,10 +4131,10 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3695
4131
  }
3696
4132
  });
3697
4133
  }, { isActive: IS_TTY });
3698
- if (!config) return /* @__PURE__ */ jsx12(Box11, { padding: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Loading..." }) });
3699
- if (resizing) return /* @__PURE__ */ jsx12(ResizingView, { cols: live.cols, rows: live.rows });
4134
+ if (!config) return /* @__PURE__ */ jsx13(Box11, { padding: 1, children: /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "Loading..." }) });
4135
+ if (resizing) return /* @__PURE__ */ jsx13(TuiThemeProvider, { appearance: cfg.appearance, children: /* @__PURE__ */ jsx13(ResizingView, { cols: live.cols, rows: live.rows }) });
3700
4136
  if (showPicker) {
3701
- return /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, children: /* @__PURE__ */ jsx12(
4137
+ return /* @__PURE__ */ jsx13(TuiThemeProvider, { appearance: cfg.appearance, children: /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, children: /* @__PURE__ */ jsx13(
3702
4138
  Onboarding,
3703
4139
  {
3704
4140
  items: onboardItems,
@@ -3708,22 +4144,19 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3708
4144
  heading: needsOnboarding ? "Welcome to tokmon" : "New providers detected",
3709
4145
  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."
3710
4146
  }
3711
- ) });
4147
+ ) }) });
3712
4148
  }
3713
4149
  if (showLoader) {
3714
- return /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: /* @__PURE__ */ jsx12(LoadingView, { groups: allGroups, stats, cols, rows, readyInput: readyInputFor, privacyMode: cfg.privacyMode }) });
4150
+ return /* @__PURE__ */ jsx13(TuiThemeProvider, { appearance: cfg.appearance, children: /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: /* @__PURE__ */ jsx13(LoadingView, { groups: allGroups, stats, cols, rows, readyInput: readyInputFor, privacyMode: cfg.privacyMode }) }) });
3715
4151
  }
3716
4152
  if (TOO_SMALL && !showSettings) {
3717
- return /* @__PURE__ */ jsx12(TinyFallback, { groups, stats, rows, cols, refreshStatus });
4153
+ return /* @__PURE__ */ jsx13(TuiThemeProvider, { appearance: cfg.appearance, children: /* @__PURE__ */ jsx13(TinyFallback, { groups, stats, rows, cols, refreshStatus }) });
3718
4154
  }
3719
4155
  rowCountRef.current = tokenRows.length;
3720
- return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: [
4156
+ return /* @__PURE__ */ jsx13(TuiThemeProvider, { appearance: cfg.appearance, children: /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: [
3721
4157
  /* @__PURE__ */ jsxs12(Box11, { justifyContent: "space-between", children: [
3722
4158
  /* @__PURE__ */ jsxs12(Box11, { children: [
3723
- /* @__PURE__ */ jsxs12(Text12, { bold: true, color: "greenBright", children: [
3724
- glyphs().dotSel,
3725
- " tokmon"
3726
- ] }),
4159
+ /* @__PURE__ */ jsx13(ThemedBrand, {}),
3727
4160
  /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3728
4161
  " ",
3729
4162
  glyphs().middot,
@@ -3737,15 +4170,15 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3737
4170
  ] })
3738
4171
  ] }),
3739
4172
  /* @__PURE__ */ jsxs12(Box11, { children: [
3740
- peak && /* @__PURE__ */ jsxs12(Fragment4, { children: [
3741
- /* @__PURE__ */ jsx12(PeakBadge, { peak, resetDisplay: cfg.resetDisplay, tz }),
4173
+ peak && /* @__PURE__ */ jsxs12(Fragment6, { children: [
4174
+ /* @__PURE__ */ jsx13(PeakBadge, { peak, resetDisplay: cfg.resetDisplay, tz }),
3742
4175
  /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3743
4176
  " ",
3744
4177
  glyphs().middot,
3745
4178
  " "
3746
4179
  ] })
3747
4180
  ] }),
3748
- /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: time(updated, tz) })
4181
+ /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: time(updated, tz) })
3749
4182
  ] })
3750
4183
  ] }),
3751
4184
  degraded && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
@@ -3760,19 +4193,19 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3760
4193
  glyphs().middot,
3761
4194
  " showing last known data"
3762
4195
  ] }),
3763
- cfg.allowNetworkAccess && /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
4196
+ cfg.allowNetworkAccess && /* @__PURE__ */ jsxs12(ThemedError, { children: [
3764
4197
  glyphs().warn,
3765
4198
  " unsafe network access enabled ",
3766
4199
  glyphs().middot,
3767
4200
  " dashboard may be reachable from your LAN after daemon restart"
3768
4201
  ] }),
3769
- configSaveError && /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
4202
+ configSaveError && /* @__PURE__ */ jsxs12(ThemedError, { children: [
3770
4203
  glyphs().warn,
3771
4204
  " ",
3772
4205
  configSaveError
3773
4206
  ] }),
3774
- /* @__PURE__ */ jsx12(RefreshStatusLine, { status: refreshStatus }),
3775
- showSettings ? /* @__PURE__ */ jsx12(
4207
+ /* @__PURE__ */ jsx13(RefreshStatusLine, { status: refreshStatus }),
4208
+ showSettings ? /* @__PURE__ */ jsx13(
3776
4209
  SettingsView,
3777
4210
  {
3778
4211
  config: cfg,
@@ -3790,20 +4223,20 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3790
4223
  trackedAccounts: trackedAccountRows,
3791
4224
  accountIdentities
3792
4225
  }
3793
- ) : /* @__PURE__ */ jsxs12(Fragment4, { children: [
4226
+ ) : /* @__PURE__ */ jsxs12(Fragment6, { children: [
3794
4227
  /* @__PURE__ */ jsxs12(Box11, { marginTop: 1, marginBottom: 1, children: [
3795
- /* @__PURE__ */ jsx12(TabBar, { tabs: TABS, active: tab, onSelect: onTabSelect }),
4228
+ /* @__PURE__ */ jsx13(TabBar, { tabs: TABS, active: tab, onSelect: onTabSelect }),
3796
4229
  /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3797
4230
  " Tab/",
3798
4231
  glyphs().arrowL,
3799
4232
  glyphs().arrowR
3800
4233
  ] })
3801
4234
  ] }),
3802
- tab === 0 && /* @__PURE__ */ jsxs12(Fragment4, { children: [
3803
- /* @__PURE__ */ jsx12(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage, privacyMode: cfg.privacyMode, resetDisplay: cfg.resetDisplay, tz }),
4235
+ tab === 0 && /* @__PURE__ */ jsxs12(Fragment6, { children: [
4236
+ /* @__PURE__ */ jsx13(DashboardView, { groups, stats, cols, budget: gridBudget, computed: dashLayout, page: dashPage, privacyMode: cfg.privacyMode, resetDisplay: cfg.resetDisplay, tz }),
3804
4237
  slots.length > 1 && /* @__PURE__ */ jsxs12(Box11, { marginTop: 1, children: [
3805
- /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "focus " }),
3806
- /* @__PURE__ */ jsx12(
4238
+ /* @__PURE__ */ jsx13(Text12, { dimColor: true, children: "focus " }),
4239
+ /* @__PURE__ */ jsx13(
3807
4240
  AccountStrip,
3808
4241
  {
3809
4242
  slots,
@@ -3812,12 +4245,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3812
4245
  }
3813
4246
  )
3814
4247
  ] }),
3815
- /* @__PURE__ */ jsx12(TotalsRow, { groups, stats, cols })
4248
+ /* @__PURE__ */ jsx13(TotalsRow, { groups, stats, cols })
3816
4249
  ] }),
3817
- tab === 1 && /* @__PURE__ */ jsxs12(Fragment4, { children: [
3818
- tableProvs.length > 0 && /* @__PURE__ */ jsx12(TableProviderBar, { providers: tableProvs, active: effTableProvider, onSelect: onProviderSelect }),
3819
- /* @__PURE__ */ jsx12(Box11, { height: 1 }),
3820
- /* @__PURE__ */ jsx12(
4250
+ tab === 1 && /* @__PURE__ */ jsxs12(Fragment6, { children: [
4251
+ tableProvs.length > 0 && /* @__PURE__ */ jsx13(TableProviderBar, { providers: tableProvs, active: effTableProvider, onSelect: onProviderSelect }),
4252
+ /* @__PURE__ */ jsx13(Box11, { height: 1 }),
4253
+ /* @__PURE__ */ jsx13(
3821
4254
  ControlBar,
3822
4255
  {
3823
4256
  views: VIEWS,
@@ -3831,12 +4264,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3831
4264
  showModel: true
3832
4265
  }
3833
4266
  ),
3834
- /* @__PURE__ */ jsx12(Box11, { height: 1 }),
4267
+ /* @__PURE__ */ jsx13(Box11, { height: 1 }),
3835
4268
  !effTableProvider ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
3836
4269
  "No providers enabled ",
3837
4270
  glyphs().emDash,
3838
4271
  " press s to pick providers."
3839
- ] }) : tableLoading && !table ? /* @__PURE__ */ jsx12(Spinner, { label: "Loading history" }) : /* @__PURE__ */ jsx12(
4272
+ ] }) : tableLoading && !table ? /* @__PURE__ */ jsx13(Spinner, { label: "Loading history" }) : /* @__PURE__ */ jsx13(
3840
4273
  TokenTable,
3841
4274
  {
3842
4275
  rows: tokenRows,
@@ -3849,12 +4282,23 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3849
4282
  )
3850
4283
  ] })
3851
4284
  ] }),
3852
- (tab === 0 || showSettings) && /* @__PURE__ */ jsx12(Footer, { hasAccounts: slots.length > 1, paginated: tab === 0 && dashPaginated, cols })
4285
+ (tab === 0 || showSettings) && /* @__PURE__ */ jsx13(Footer, { hasAccounts: slots.length > 1, paginated: tab === 0 && dashPaginated, cols })
4286
+ ] }) });
4287
+ }
4288
+ function ThemedBrand() {
4289
+ const theme = useTuiTheme();
4290
+ return /* @__PURE__ */ jsxs12(Text12, { bold: true, color: theme.accent, children: [
4291
+ glyphs().dotSel,
4292
+ " tokmon"
3853
4293
  ] });
3854
4294
  }
4295
+ function ThemedError({ children }) {
4296
+ const theme = useTuiTheme();
4297
+ return /* @__PURE__ */ jsx13(Text12, { color: theme.crit, children });
4298
+ }
3855
4299
 
3856
4300
  // src/bootstrap-ink.tsx
3857
- import { jsx as jsx13 } from "react/jsx-runtime";
4301
+ import { jsx as jsx14 } from "react/jsx-runtime";
3858
4302
  function enterAltScreen() {
3859
4303
  process.stdout.write("\x1B[?1049h\x1B[H");
3860
4304
  }
@@ -3883,7 +4327,7 @@ async function bootstrapInk({ interval, config, daemon, mode }) {
3883
4327
  if (altScreen) leaveAltScreen();
3884
4328
  });
3885
4329
  const { waitUntilExit } = render(
3886
- /* @__PURE__ */ jsx13(MouseProvider, { children: /* @__PURE__ */ jsx13(
4330
+ /* @__PURE__ */ jsx14(MouseProvider, { children: /* @__PURE__ */ jsx14(
3887
4331
  App,
3888
4332
  {
3889
4333
  interval,