tokmon 0.21.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,12 +13,13 @@ import {
13
13
  cursorModelSpend,
14
14
  detectProviders,
15
15
  fetchPeak,
16
+ modelColor,
16
17
  resolveTimezone,
17
18
  shortDate,
18
19
  systemTimezone,
19
20
  time,
20
21
  tokens
21
- } from "./chunk-YCIUMAKK.js";
22
+ } from "./chunk-FLLNU6BN.js";
22
23
  import {
23
24
  COLOR_PALETTE,
24
25
  DEFAULTS,
@@ -29,10 +30,11 @@ import {
29
30
  loadConfig,
30
31
  normalizeConfig,
31
32
  pickAccentColor,
33
+ redactEmail,
32
34
  sanitizeTyped,
33
35
  saveConfigSync,
34
36
  snapshotCacheFile
35
- } from "./chunk-7MFZMI5C.js";
37
+ } from "./chunk-E25RUTXB.js";
36
38
  import {
37
39
  glyphs
38
40
  } from "./chunk-RF4GGQGM.js";
@@ -322,7 +324,7 @@ function computeDashLayout(groups, stats, cols, budget, focusId, layoutPref) {
322
324
  }
323
325
  return chooseLayout(content, budget, groups.length, single, cols, heights);
324
326
  }
325
- var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget, focusId, layout, page = 0 }) {
327
+ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget, focusId, layout, page = 0, privacyMode: privacyMode2 = false }) {
326
328
  if (groups.length === 0) {
327
329
  return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
328
330
  "No providers enabled ",
@@ -337,7 +339,7 @@ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget,
337
339
  const pg = pageCount > 1 ? (page % pageCount + pageCount) % pageCount : 0;
338
340
  const visible = pageCount > 1 ? groups.slice(pg * cardsPerPage, pg * cardsPerPage + cardsPerPage) : groups;
339
341
  return /* @__PURE__ */ jsxs2(Box2, { height: budget, flexDirection: "column", overflow: "hidden", children: [
340
- /* @__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 }) }, g.provider)) }),
342
+ /* @__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: privacyMode2 }) }, g.provider)) }),
341
343
  pageCount > 1 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
342
344
  " ",
343
345
  glyphs().middot,
@@ -389,9 +391,9 @@ function ProviderCard({ provider, accounts, stats, width, variant }) {
389
391
  ] })),
390
392
  meta.hasBilling && showBars && /* @__PURE__ */ jsxs2(Fragment, { children: [
391
393
  meta.hasUsage && /* @__PURE__ */ jsx2(Rule, { inner }),
392
- /* @__PURE__ */ jsx2(LimitsBlock, { items, inner })
394
+ /* @__PURE__ */ jsx2(LimitsBlock, { items, inner, privacyMode })
393
395
  ] }),
394
- meta.hasBilling && !showBars && !meta.hasUsage && /* @__PURE__ */ jsx2(CompactBilling, { items }),
396
+ meta.hasBilling && !showBars && !meta.hasUsage && /* @__PURE__ */ jsx2(CompactBilling, { items, privacyMode }),
395
397
  hasSpark && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
396
398
  /* @__PURE__ */ jsx2(Rule, { inner }),
397
399
  /* @__PURE__ */ jsx2(SparkFooter, { series: agg.series, month: agg.month.cost, color: meta.color })
@@ -406,13 +408,13 @@ function ProviderCard({ provider, accounts, stats, width, variant }) {
406
408
  ] })
407
409
  ] });
408
410
  }
409
- function CompactBilling({ items }) {
411
+ function CompactBilling({ items, privacyMode: privacyMode2 }) {
410
412
  const billing = items.map((i) => i.s?.billing).find(Boolean);
411
413
  if (!billing) return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
412
414
  "Fetching",
413
415
  glyphs().ellipsis
414
416
  ] });
415
- if (billing.error) return /* @__PURE__ */ jsx2(Text2, { color: "red", children: billing.error });
417
+ if (billing.error) return /* @__PURE__ */ jsx2(Text2, { color: "red", children: privacyMode2 ? redactEmail(billing.error) : billing.error });
416
418
  const m = billing.metrics.find((x) => x.primary) ?? billing.metrics[0];
417
419
  if (!m) return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" });
418
420
  return /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: metricValueText(m) });
@@ -457,11 +459,12 @@ function KpiLine({ agg }) {
457
459
  ] })
458
460
  ] });
459
461
  }
460
- function accountTitle(account, billing) {
462
+ function accountTitle(account, billing, privacyMode2 = false) {
461
463
  const email = billing?.email;
462
- return email && !account.name.includes("@") ? `${account.name} ${email}` : account.name;
464
+ const title = email && !account.name.includes("@") ? `${account.name} ${email}` : account.name;
465
+ return privacyMode2 ? redactEmail(title) : title;
463
466
  }
464
- function LimitsBlock({ items, inner }) {
467
+ function LimitsBlock({ items, inner, privacyMode: privacyMode2 }) {
465
468
  const showName = items.length > 1;
466
469
  const labels = items.flatMap((i) => i.s?.billing?.metrics ?? []).map((m) => m.label.length);
467
470
  const labelW = Math.min(Math.max(7, ...labels) + 1, 14);
@@ -474,12 +477,12 @@ function LimitsBlock({ items, inner }) {
474
477
  glyphs().dot,
475
478
  " "
476
479
  ] }),
477
- /* @__PURE__ */ jsx2(Text2, { bold: true, children: truncateName(accountTitle(account, billing), Math.max(22, inner - 2)) })
480
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: truncateName(accountTitle(account, billing, privacyMode2), Math.max(22, inner - 2)) })
478
481
  ] }),
479
482
  !billing ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
480
483
  "Fetching",
481
484
  glyphs().ellipsis
482
- ] }) : billing.error ? /* @__PURE__ */ jsx2(Text2, { color: "red", wrap: "truncate-end", children: billing.error }) : billing.metrics.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" }) : billing.metrics.map((m, i) => /* @__PURE__ */ jsx2(MetricRow, { m, color: account.color, barW, labelW }, `${m.label}${i}`))
485
+ ] }) : billing.error ? /* @__PURE__ */ jsx2(Text2, { color: "red", wrap: "truncate-end", children: privacyMode2 ? 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 }, `${m.label}${i}`))
483
486
  ] }, account.id);
484
487
  }) });
485
488
  }
@@ -561,7 +564,7 @@ import { Box as Box4, Text as Text4 } from "ink";
561
564
  import { memo as memo3 } from "react";
562
565
  import { Box as Box3, Text as Text3 } from "ink";
563
566
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
564
- var GENERAL_ROWS = 6;
567
+ var GENERAL_ROWS = 7;
565
568
  var SETTINGS_TABS = ["general", "providers", "accounts"];
566
569
  var SETTINGS_TAB_LABELS = {
567
570
  general: "General",
@@ -623,16 +626,17 @@ var SettingsView = memo3(function SettingsView2({
623
626
  ] })
624
627
  ] }),
625
628
  /* @__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" }) }),
626
- /* @__PURE__ */ jsx3(Row, { cursor, idx: 3, label: "Timezone", children: editingTz ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
629
+ /* @__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" }) }),
630
+ /* @__PURE__ */ jsx3(Row, { cursor, idx: 4, label: "Timezone", children: editingTz ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
627
631
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "[" }),
628
632
  /* @__PURE__ */ jsx3(CaretText, { value: tzEdit ?? "", caret: tzCaret, color: "cyan" }),
629
633
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "]" })
630
634
  ] }) : /* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: tzDisplay }) }),
631
- cursor === 3 && tzError && /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
635
+ cursor === 4 && tzError && /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
632
636
  " ",
633
637
  tzError
634
638
  ] }),
635
- /* @__PURE__ */ jsxs3(Row, { cursor, idx: 4, label: "Dashboard", children: [
639
+ /* @__PURE__ */ jsxs3(Row, { cursor, idx: 5, label: "Dashboard", children: [
636
640
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
637
641
  glyphs().caretL,
638
642
  " "
@@ -643,7 +647,7 @@ var SettingsView = memo3(function SettingsView2({
643
647
  glyphs().caretR
644
648
  ] })
645
649
  ] }),
646
- /* @__PURE__ */ jsxs3(Row, { cursor, idx: 5, label: "Default focus", children: [
650
+ /* @__PURE__ */ jsxs3(Row, { cursor, idx: 6, label: "Default focus", children: [
647
651
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
648
652
  glyphs().caretL,
649
653
  " "
@@ -689,7 +693,8 @@ var SettingsView = memo3(function SettingsView2({
689
693
  const isActive = acc.id === activeAccountId;
690
694
  const provider = PROVIDERS[acc.providerId];
691
695
  const identity = accountIdentities.get(acc.id);
692
- const identityLabel = identity?.email || identity?.displayName || acc.name;
696
+ const rawIdentityLabel = identity?.email || identity?.displayName || acc.name;
697
+ const identityLabel = config.privacyMode ? redactEmail(rawIdentityLabel) : rawIdentityLabel;
693
698
  const plan = identity?.plan ?? null;
694
699
  return /* @__PURE__ */ jsxs3(Box3, { children: [
695
700
  /* @__PURE__ */ jsxs3(Text3, { color: selected ? "green" : void 0, children: [
@@ -1086,7 +1091,7 @@ var TableProviderBar = memo4(function TableProviderBar2({ providers, active, onS
1086
1091
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " p/P switch" })
1087
1092
  ] });
1088
1093
  });
1089
- var ControlBar = memo4(function ControlBar2({ views, period, sort, search, searchCaret, searching, showPeriod }) {
1094
+ var ControlBar = memo4(function ControlBar2({ views, period, sort, model, search, searchCaret, searching, showPeriod, showModel }) {
1090
1095
  return /* @__PURE__ */ jsxs4(Box4, { children: [
1091
1096
  showPeriod && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1092
1097
  views.map((v, i) => /* @__PURE__ */ jsx4(Box4, { marginRight: 2, children: i === period ? /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "cyan", children: [
@@ -1103,6 +1108,15 @@ var ControlBar = memo4(function ControlBar2({ views, period, sort, search, searc
1103
1108
  glyphs().middot,
1104
1109
  " "
1105
1110
  ] }),
1111
+ showModel && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1112
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "model " }),
1113
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: model ? modelColor(model) : "cyan", children: model ?? "all" }),
1114
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
1115
+ " m model ",
1116
+ glyphs().middot,
1117
+ " "
1118
+ ] })
1119
+ ] }),
1106
1120
  searching ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1107
1121
  /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "/" }),
1108
1122
  /* @__PURE__ */ jsx4(CaretText, { value: search, caret: searchCaret, color: "cyan" })
@@ -1370,18 +1384,18 @@ function groupTodayCost(items) {
1370
1384
  return sum + (d?.today.cost ?? 0);
1371
1385
  }, 0);
1372
1386
  }
1373
- function headlineFor(group, items) {
1387
+ function headlineFor(group, items, privacyMode2 = false) {
1374
1388
  const meta = PROVIDERS[group.provider];
1375
1389
  if (meta.hasUsage) return `${currency(groupTodayCost(items))} today`;
1376
1390
  const billing = items.map((s) => s?.billing).find(Boolean);
1377
1391
  if (!billing) return "no data";
1378
- if (billing.error) return billing.error;
1392
+ if (billing.error) return privacyMode2 ? redactEmail(billing.error) : billing.error;
1379
1393
  const m = billing.metrics[0];
1380
1394
  if (m) return metricValueText(m);
1381
1395
  return billing.plan ?? "no data";
1382
1396
  }
1383
1397
  var STAGGER_FRAMES = 2;
1384
- function LoadingView({ groups, stats, cols, rows, readyInput }) {
1398
+ function LoadingView({ groups, stats, cols, rows, readyInput, privacyMode: privacyMode2 = false }) {
1385
1399
  const resolveReady = readyInput ?? ((id) => statsReadyInput(stats.get(id)));
1386
1400
  const sp = glyphs().spinner;
1387
1401
  const [frame, setFrame] = useState2(0);
@@ -1449,7 +1463,7 @@ function LoadingView({ groups, stats, cols, rows, readyInput }) {
1449
1463
  sp[frame % sp.length],
1450
1464
  " "
1451
1465
  ] }),
1452
- errored ? /* @__PURE__ */ jsx6(Text6, { color: "red", children: headlineFor(g, items) }) : ready ? /* @__PURE__ */ jsx6(Text6, { children: headlineFor(g, items) }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1466
+ errored ? /* @__PURE__ */ jsx6(Text6, { color: "red", children: headlineFor(g, items, privacyMode2) }) : ready ? /* @__PURE__ */ jsx6(Text6, { children: headlineFor(g, items, privacyMode2) }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
1453
1467
  "loading",
1454
1468
  glyphs().ellipsis
1455
1469
  ] })
@@ -1478,8 +1492,9 @@ function LoadingView({ groups, stats, cols, rows, readyInput }) {
1478
1492
  }
1479
1493
 
1480
1494
  // src/ui/app-layout.logic.ts
1481
- function deriveSlots(accounts) {
1482
- return accounts.length > 1 ? [{ id: null, name: "All", color: "whiteBright" }, ...accounts.map((a) => ({ id: a.id, name: a.name, color: a.color }))] : accounts.map((a) => ({ id: a.id, name: a.name, color: a.color }));
1495
+ function deriveSlots(accounts, privacyMode2 = false) {
1496
+ const label = (name) => privacyMode2 ? redactEmail(name) : name;
1497
+ return accounts.length > 1 ? [{ id: null, name: "All", color: "whiteBright" }, ...accounts.map((a) => ({ id: a.id, name: label(a.name), color: a.color }))] : accounts.map((a) => ({ id: a.id, name: label(a.name), color: a.color }));
1483
1498
  }
1484
1499
  function findActiveSlot(slots, activeAccountId) {
1485
1500
  if (activeAccountId === null) return { activeSlotIdx: 0, focusId: slots[0]?.id ?? null };
@@ -2038,6 +2053,45 @@ function sortRows(rows, sortIdx) {
2038
2053
  return sorted;
2039
2054
  }
2040
2055
  }
2056
+ function tableModelOptions(rows) {
2057
+ const models = /* @__PURE__ */ new Set();
2058
+ for (const row of rows) for (const model of row.breakdown) models.add(model.name);
2059
+ return [...models].sort();
2060
+ }
2061
+ function cycleTableModel(current, models, dir) {
2062
+ if (models.length === 0) return null;
2063
+ const idx = current ? models.indexOf(current) : -1;
2064
+ if (dir === 1) {
2065
+ if (idx < 0) return models[0];
2066
+ return idx === models.length - 1 ? null : models[idx + 1];
2067
+ }
2068
+ if (idx < 0) return models[models.length - 1];
2069
+ return idx === 0 ? null : models[idx - 1];
2070
+ }
2071
+ function filterRowsByModel(rows, model) {
2072
+ if (!model) return rows;
2073
+ return rows.flatMap((row) => {
2074
+ const detail = row.breakdown.find((m) => m.name === model);
2075
+ if (!detail) return [];
2076
+ const input = detail.input;
2077
+ const output = detail.output;
2078
+ const cacheCreate = detail.cacheCreate;
2079
+ const cacheRead = detail.cacheRead;
2080
+ return [{
2081
+ label: row.label,
2082
+ models: [model],
2083
+ input,
2084
+ output,
2085
+ cacheCreate,
2086
+ cacheRead,
2087
+ cacheSavings: detail.cacheSavings,
2088
+ total: input + output + cacheCreate + cacheRead,
2089
+ cost: detail.cost,
2090
+ count: detail.count,
2091
+ breakdown: [{ ...detail }]
2092
+ }];
2093
+ });
2094
+ }
2041
2095
  function filterTokenRows(rows, q) {
2042
2096
  if (!q) return rows;
2043
2097
  const s = q.toLowerCase();
@@ -2122,6 +2176,7 @@ function handleKey(input, key, ctx) {
2122
2176
  setSort,
2123
2177
  SORTS_FOR,
2124
2178
  tableIsCursor,
2179
+ cycleTableModel: cycleTableModel2,
2125
2180
  setView,
2126
2181
  cursor,
2127
2182
  rowCountRef,
@@ -2420,7 +2475,11 @@ function handleKey(input, key, ctx) {
2420
2475
  updateConfig((c) => ({ ...c, clearScreen: !c.clearScreen }));
2421
2476
  return;
2422
2477
  }
2423
- if (settingsCursor === 3) {
2478
+ if (settingsCursor === 3 && (key.leftArrow || key.rightArrow || key.return)) {
2479
+ updateConfig((c) => ({ ...c, privacyMode: !c.privacyMode }));
2480
+ return;
2481
+ }
2482
+ if (settingsCursor === 4) {
2424
2483
  if (key.return) {
2425
2484
  const init = cfg.timezone ?? "";
2426
2485
  setTzEdit(init);
@@ -2430,11 +2489,11 @@ function handleKey(input, key, ctx) {
2430
2489
  if (key.leftArrow || key.rightArrow) updateConfig((c) => ({ ...c, timezone: c.timezone === null ? systemTimezone() : null }));
2431
2490
  return;
2432
2491
  }
2433
- if (settingsCursor === 4 && (key.leftArrow || key.rightArrow || key.return)) {
2492
+ if (settingsCursor === 5 && (key.leftArrow || key.rightArrow || key.return)) {
2434
2493
  updateConfig((c) => ({ ...c, dashboardLayout: c.dashboardLayout === "grid" ? "single" : "grid" }));
2435
2494
  return;
2436
2495
  }
2437
- if (settingsCursor === 5 && (key.leftArrow || key.rightArrow || key.return)) {
2496
+ if (settingsCursor === 6 && (key.leftArrow || key.rightArrow || key.return)) {
2438
2497
  updateConfig((c) => ({ ...c, defaultFocus: c.defaultFocus === "all" ? "last" : "all" }));
2439
2498
  return;
2440
2499
  }
@@ -2550,8 +2609,11 @@ function handleKey(input, key, ctx) {
2550
2609
  return;
2551
2610
  }
2552
2611
  if (input === "m") {
2553
- setView(2);
2554
- resetView();
2612
+ cycleTableModel2(1);
2613
+ return;
2614
+ }
2615
+ if (input === "M") {
2616
+ cycleTableModel2(-1);
2555
2617
  return;
2556
2618
  }
2557
2619
  if (key.leftArrow) {
@@ -2977,6 +3039,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
2977
3039
  const [expanded, setExpanded] = useState8(-1);
2978
3040
  const [sort, setSort] = useState8(1);
2979
3041
  const [tableProvider, setTableProvider] = useState8(null);
3042
+ const [tableModel, setTableModel] = useState8(null);
2980
3043
  const [search, setSearch] = useState8("");
2981
3044
  const [searchMode, setSearchMode] = useState8(false);
2982
3045
  const [cursorRowsLocal, setCursorRows] = useState8(null);
@@ -3063,7 +3126,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3063
3126
  }
3064
3127
  return statsReadyInput(statsLocal.get(id));
3065
3128
  }, [connected, snapshot, statsLocal]);
3066
- const slots = useMemo2(() => deriveSlots(accounts), [accounts]);
3129
+ const slots = useMemo2(() => deriveSlots(accounts, cfg.privacyMode), [accounts, cfg.privacyMode]);
3067
3130
  const { activeSlotIdx, focusId } = useMemo2(
3068
3131
  () => findActiveSlot(slots, cfg.activeAccountId),
3069
3132
  [slots, cfg.activeAccountId]
@@ -3163,6 +3226,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3163
3226
  setCursorRows(null);
3164
3227
  setCursor(0);
3165
3228
  setExpanded(-1);
3229
+ setTableModel(null);
3166
3230
  setSort(tableIsCursor ? 0 : 1);
3167
3231
  setTableLoading(false);
3168
3232
  }, [tableKey]);
@@ -3202,7 +3266,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3202
3266
  useEffect7(() => {
3203
3267
  setCursor(0);
3204
3268
  setExpanded(-1);
3205
- }, [search]);
3269
+ }, [search, tableModel]);
3206
3270
  useEffect7(() => {
3207
3271
  setDashPage((p) => Math.min(p, dashPageCount - 1));
3208
3272
  }, [dashPageCount]);
@@ -3319,6 +3383,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3319
3383
  setSort(nextIsCursor ? 0 : 1);
3320
3384
  setCursor(0);
3321
3385
  setExpanded(-1);
3386
+ setTableModel(null);
3322
3387
  setSearch("");
3323
3388
  setSearchCaret(0);
3324
3389
  setSearchMode(false);
@@ -3348,7 +3413,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3348
3413
  if (webStartingRef.current) return;
3349
3414
  webStartingRef.current = true;
3350
3415
  try {
3351
- const { startWebServer } = await import("./server-F7CUK4ZM.js");
3416
+ const { startWebServer } = await import("./server-P7T4ZMC6.js");
3352
3417
  const ctrl = await startWebServer({ config: cfg, log: false });
3353
3418
  webRef.current = ctrl;
3354
3419
  openUrl(ctrl.url);
@@ -3369,6 +3434,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3369
3434
  setTableProvider(p);
3370
3435
  setCursor(0);
3371
3436
  setExpanded(-1);
3437
+ setTableModel(null);
3372
3438
  setSearch("");
3373
3439
  setSearchCaret(0);
3374
3440
  setSearchMode(false);
@@ -3378,9 +3444,22 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3378
3444
  else setCursor(idx);
3379
3445
  }, [cursor]);
3380
3446
  const onRowClickCursor = useCallback4((idx) => setCursor(idx), []);
3447
+ const rawTokenRows = useMemo2(
3448
+ () => tab === 1 && !tableIsCursor ? table ? [table.daily, table.weekly, table.monthly][view] : [] : [],
3449
+ [tab, tableIsCursor, table, view]
3450
+ );
3451
+ const tokenModelOptions = useMemo2(() => tableModelOptions(rawTokenRows), [rawTokenRows]);
3452
+ useEffect7(() => {
3453
+ if (tableModel && !tokenModelOptions.includes(tableModel)) setTableModel(null);
3454
+ }, [tableModel, tokenModelOptions]);
3455
+ const cycleTableModelFilter = useCallback4((dir) => {
3456
+ setTableModel((cur) => cycleTableModel(cur, tokenModelOptions, dir));
3457
+ resetView();
3458
+ }, [tokenModelOptions, resetView]);
3459
+ const activeTableModel = tableModel && tokenModelOptions.includes(tableModel) ? tableModel : null;
3381
3460
  const tokenRows = useMemo2(
3382
- () => tab === 1 && !tableIsCursor ? sortRows(filterTokenRows(table ? [table.daily, table.weekly, table.monthly][view] : [], search), sort) : [],
3383
- [tab, tableIsCursor, table, view, search, sort]
3461
+ () => tab === 1 && !tableIsCursor ? sortRows(filterTokenRows(filterRowsByModel(rawTokenRows, activeTableModel), search), sort) : [],
3462
+ [tab, tableIsCursor, rawTokenRows, activeTableModel, search, sort]
3384
3463
  );
3385
3464
  const cursorTableRows = useMemo2(
3386
3465
  () => tab === 1 && tableIsCursor ? sortCursorRows(filterCursorRows(cursorRows ?? [], search), sort) : [],
@@ -3446,6 +3525,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3446
3525
  setSort,
3447
3526
  SORTS_FOR,
3448
3527
  tableIsCursor,
3528
+ cycleTableModel: cycleTableModelFilter,
3449
3529
  setView,
3450
3530
  cursor,
3451
3531
  rowCountRef,
@@ -3470,7 +3550,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3470
3550
  ) });
3471
3551
  }
3472
3552
  if (showLoader) {
3473
- return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: /* @__PURE__ */ jsx11(LoadingView, { groups: allGroups, stats, cols, rows, readyInput: readyInputFor }) });
3553
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: /* @__PURE__ */ jsx11(LoadingView, { groups: allGroups, stats, cols, rows, readyInput: readyInputFor, privacyMode: cfg.privacyMode }) });
3474
3554
  }
3475
3555
  if (TOO_SMALL && !showSettings) {
3476
3556
  return /* @__PURE__ */ jsx11(TinyFallback, { groups, stats, rows, cols });
@@ -3540,7 +3620,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3540
3620
  ] })
3541
3621
  ] }),
3542
3622
  tab === 0 && /* @__PURE__ */ jsxs11(Fragment4, { children: [
3543
- /* @__PURE__ */ jsx11(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage }),
3623
+ /* @__PURE__ */ jsx11(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage, privacyMode: cfg.privacyMode }),
3544
3624
  slots.length > 1 && /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
3545
3625
  /* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "focus " }),
3546
3626
  /* @__PURE__ */ jsx11(
@@ -3563,10 +3643,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
3563
3643
  views: VIEWS,
3564
3644
  period: view,
3565
3645
  sort: sortLabel(SORTS_FOR[sort % SORTS_FOR.length]),
3646
+ model: activeTableModel,
3566
3647
  search,
3567
3648
  searchCaret,
3568
3649
  searching: searchMode,
3569
- showPeriod: !tableIsCursor
3650
+ showPeriod: !tableIsCursor,
3651
+ showModel: !tableIsCursor
3570
3652
  }
3571
3653
  ),
3572
3654
  /* @__PURE__ */ jsx11(Box11, { height: 1 }),
@@ -14,6 +14,7 @@ var DEFAULTS = {
14
14
  interval: 2,
15
15
  billingInterval: 5,
16
16
  clearScreen: true,
17
+ privacyMode: true,
17
18
  timezone: null,
18
19
  accounts: [],
19
20
  activeAccountId: null,
@@ -48,6 +49,14 @@ var PROVIDER_META = {
48
49
  antigravity: { name: "Antigravity", color: "red" },
49
50
  gemini: { name: "Gemini", color: "greenBright" }
50
51
  };
52
+ var EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i;
53
+ var EMAIL_RE_GLOBAL = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
54
+ function containsEmail(value) {
55
+ return typeof value === "string" && EMAIL_RE.test(value);
56
+ }
57
+ function redactEmail(value) {
58
+ return value.replace(EMAIL_RE_GLOBAL, "[redacted]");
59
+ }
51
60
  function getTrackedAccountRows(config, trackedProviders = PROVIDER_ORDER.filter((pid) => !config.disabledProviders.includes(pid)), autoAccounts) {
52
61
  const tracked = new Set(trackedProviders);
53
62
  const configuredIds = /* @__PURE__ */ new Set();
@@ -128,6 +137,7 @@ function normalizeConfig(parsed) {
128
137
  interval: clampNum(parsed.interval, DEFAULTS.interval, 1),
129
138
  billingInterval: clampNum(parsed.billingInterval, DEFAULTS.billingInterval, 1),
130
139
  clearScreen: typeof parsed.clearScreen === "boolean" ? parsed.clearScreen : DEFAULTS.clearScreen,
140
+ privacyMode: typeof parsed.privacyMode === "boolean" ? parsed.privacyMode : DEFAULTS.privacyMode,
131
141
  timezone: typeof parsed.timezone === "string" && parsed.timezone.trim() && isValidTimezone(parsed.timezone.trim()) ? parsed.timezone.trim() : null,
132
142
  accounts,
133
143
  activeAccountId: typeof parsed.activeAccountId === "string" ? parsed.activeAccountId : null,
@@ -256,6 +266,8 @@ export {
256
266
  ACCENT_COLORS,
257
267
  COLOR_PALETTE,
258
268
  PROVIDER_META,
269
+ containsEmail,
270
+ redactEmail,
259
271
  getTrackedAccountRows,
260
272
  clampNum,
261
273
  isValidTimezone,