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