tokmon 0.28.8 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +9 -5
  2. package/dist/{bootstrap-ink-PYYVQAYP.js → bootstrap-ink-MPWICEIJ.js} +136 -58
  3. package/dist/{chunk-BZFDTIFV.js → chunk-3D6TO5TS.js} +2 -2
  4. package/dist/{chunk-6E2OJCIQ.js → chunk-5Q5HUOP4.js} +11 -4
  5. package/dist/{chunk-RY24UHMK.js → chunk-AUUBRLBG.js} +2 -2
  6. package/dist/{chunk-MIXBF6QU.js → chunk-BLUQGJKM.js} +2 -2
  7. package/dist/{chunk-UON47FYF.js → chunk-BYLWQ46U.js} +19 -2
  8. package/dist/{chunk-O7ORALKA.js → chunk-G2A3DVPU.js} +80 -1
  9. package/dist/{chunk-DESEB73E.js → chunk-WPZTPMBX.js} +1 -1
  10. package/dist/{chunk-WHOIUSJM.js → chunk-YWDMEU3L.js} +1 -1
  11. package/dist/{cli-command-XPFM6LUP.js → cli-command-WL2K5WWR.js} +145 -13
  12. package/dist/cli.js +6 -6
  13. package/dist/{config-JEDXAKPZ.js → config-B7QJQTU2.js} +7 -1
  14. package/dist/{daemon-KFMXYG7V.js → daemon-A3WGDRFT.js} +6 -6
  15. package/dist/daemon-handle-4E7RSSDB.js +11 -0
  16. package/dist/server-P6BRXBZ5.js +12 -0
  17. package/dist/web/assets/{Area-BqyvRn2x.js → Area-CEpf7_6d.js} +1 -1
  18. package/dist/web/assets/{analytics-B6QwqU9g.js → analytics-CdjcYtmK.js} +2 -2
  19. package/dist/web/assets/{breakdown-DYnVKScr.js → breakdown-DM6g0evr.js} +1 -1
  20. package/dist/web/assets/{button-CnT5g-Jl.js → button-Cu4co7ZR.js} +1 -1
  21. package/dist/web/assets/{chart-Duz9D-nH.js → chart-YlnVH1If.js} +1 -1
  22. package/dist/web/assets/{explore-P2EolJrt.js → explore-kLpZWR-_.js} +1 -1
  23. package/dist/web/assets/index-Dd37pBKy.js +83 -0
  24. package/dist/web/assets/index-PpTp4nyP.css +1 -0
  25. package/dist/web/assets/{models-8B4FBxr7.js → models-XKfoWTDv.js} +2 -2
  26. package/dist/web/assets/{overview-DXA_HAsw.js → overview-DC-WVKZ6.js} +2 -2
  27. package/dist/web/assets/{panel-gxLFBb8y.js → panel-B_U9oe1x.js} +1 -1
  28. package/dist/web/assets/settings-sheet-B7BuJ3j8.js +1 -0
  29. package/dist/web/assets/{share-sheet-CXdyA1ga.js → share-sheet-BaEXe1m9.js} +1 -1
  30. package/dist/web/assets/{timeline-CzC85trW.js → timeline-ar9mdhcb.js} +1 -1
  31. package/dist/web/index.html +2 -2
  32. package/package.json +1 -1
  33. package/dist/daemon-handle-6RYUJPC2.js +0 -11
  34. package/dist/server-LNFK5MRX.js +0 -12
  35. package/dist/web/assets/index-9hkGpa7j.js +0 -83
  36. package/dist/web/assets/index-ZmOS8p9O.css +0 -1
  37. package/dist/web/assets/settings-sheet-CJJl-3Gw.js +0 -1
package/README.md CHANGED
@@ -60,11 +60,15 @@ signed and notarized. Windows builds are currently unsigned.
60
60
  | --- | --- | --- |
61
61
  | Terminal | A fast overview while coding | `tokmon` |
62
62
  | Web dashboard | Charts, filtering, all-time history, and exports | `tokmon serve` |
63
- | Desktop | Pinned quota rings and quick account details | Install from Releases |
64
-
65
- The desktop app can pin two providers in the macOS menu bar. Clicking it opens
66
- a compact view of every provider and account, including usage, reset times,
67
- token totals, spend, cache savings, and recent history.
63
+ | Desktop | A customisable menu-bar summary and quick account details | Install from Releases |
64
+
65
+ Pin up to two providers directly from their desktop cards, then decide exactly
66
+ what the macOS menu bar shows: provider marks, usage or today's tokens, optional
67
+ progress lines, and comfortable, compact, or tight spacing. Auto layout adapts
68
+ to smaller displays; Custom mode exposes half-point edge and gap controls.
69
+ Clicking the item opens a compact view of every enabled provider and account,
70
+ including usage, reset times, token totals, spend, cache savings, and recent
71
+ history.
68
72
 
69
73
  ![Tokmon desktop quota popover](assets/desktop/tray-popover.png)
70
74
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createDaemonRpcClient
4
- } from "./chunk-RY24UHMK.js";
4
+ } from "./chunk-AUUBRLBG.js";
5
5
  import {
6
6
  accountIdentityText,
7
7
  accountsByProvider,
@@ -11,7 +11,7 @@ import {
11
11
  resolveQuotaViews,
12
12
  severity,
13
13
  usageFromHeadroom
14
- } from "./chunk-BZFDTIFV.js";
14
+ } from "./chunk-3D6TO5TS.js";
15
15
  import {
16
16
  PROVIDERS,
17
17
  PROVIDER_ORDER,
@@ -28,11 +28,12 @@ import {
28
28
  time,
29
29
  tokens,
30
30
  withTimeout
31
- } from "./chunk-DESEB73E.js";
32
- import "./chunk-UON47FYF.js";
31
+ } from "./chunk-WPZTPMBX.js";
32
+ import "./chunk-BYLWQ46U.js";
33
33
  import {
34
34
  COLOR_PALETTE,
35
35
  DEFAULTS,
36
+ DEFAULT_MENU_BAR_CONFIG,
36
37
  DESKTOP_GRAPH_RANGES,
37
38
  THEME_PRESET_IDS,
38
39
  configLocation,
@@ -51,10 +52,10 @@ import {
51
52
  saveConfigSync,
52
53
  setDetectedAccountExcluded,
53
54
  setProviderDetectionEnabled,
55
+ setProviderTrackingEnabled,
54
56
  snapshotCacheFile,
55
- themePresetOption,
56
- toggleProviderSelection
57
- } from "./chunk-O7ORALKA.js";
57
+ themePresetOption
58
+ } from "./chunk-G2A3DVPU.js";
58
59
  import {
59
60
  glyphs
60
61
  } from "./chunk-RF4GGQGM.js";
@@ -1102,6 +1103,34 @@ function cycleNumber(values, current, direction) {
1102
1103
  var step = (key) => key.leftArrow ? -1 : 1;
1103
1104
  var isToggleKey = (key) => key.leftArrow || key.rightArrow || key.return;
1104
1105
  var isAdjustKey = (input, key) => input === " " || key.leftArrow || key.rightArrow || key.return;
1106
+ function toggleMenuBarPresentationElement(config, element) {
1107
+ const elements = config.tray.menuBar.elements;
1108
+ if (elements[element] && Object.values(elements).filter(Boolean).length === 1) return config;
1109
+ const nextElements = { ...elements, [element]: !elements[element] };
1110
+ return {
1111
+ ...config,
1112
+ tray: {
1113
+ ...config.tray,
1114
+ showMenuBarText: nextElements.value,
1115
+ menuBar: { ...config.tray.menuBar, elements: nextElements }
1116
+ }
1117
+ };
1118
+ }
1119
+ function adjustMenuBarSpacing(config, field, direction, max) {
1120
+ const current = config.tray.menuBar.customSpacing[field];
1121
+ const value = Math.min(max, Math.max(0, Math.round((current + direction * 0.5) * 2) / 2));
1122
+ return {
1123
+ ...config,
1124
+ tray: {
1125
+ ...config.tray,
1126
+ menuBar: {
1127
+ ...config.tray.menuBar,
1128
+ mode: "custom",
1129
+ customSpacing: { ...config.tray.menuBar.customSpacing, [field]: value }
1130
+ }
1131
+ }
1132
+ };
1133
+ }
1105
1134
  var GENERAL_SETTINGS = [
1106
1135
  {
1107
1136
  key: "refreshInterval",
@@ -1292,11 +1321,35 @@ var DESKTOP_FIXED_SETTINGS = [
1292
1321
  }
1293
1322
  },
1294
1323
  {
1295
- key: "menuBarValues",
1296
- label: "Menu bar values",
1297
- render: (rc) => toggleText(rc.config.tray.showMenuBarText, rc.theme, "shown", "icon only", rc.theme.unknown),
1324
+ key: "menuBarMode",
1325
+ label: "Menu bar layout",
1326
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.accent, children: rc.config.tray.menuBar.mode })),
1327
+ onAdjust: (input, key, ctx) => {
1328
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, menuBar: { ...c.tray.menuBar, mode: c.tray.menuBar.mode === "auto" ? "custom" : "auto" } } }));
1329
+ }
1330
+ },
1331
+ {
1332
+ key: "menuBarMark",
1333
+ label: "Provider mark",
1334
+ render: (rc) => toggleText(rc.config.tray.menuBar.elements.providerMark, rc.theme, "shown", "hidden", rc.theme.unknown),
1335
+ onAdjust: (input, key, ctx) => {
1336
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => toggleMenuBarPresentationElement(c, "providerMark"));
1337
+ }
1338
+ },
1339
+ {
1340
+ key: "menuBarValue",
1341
+ label: "Value",
1342
+ render: (rc) => toggleText(rc.config.tray.menuBar.elements.value, rc.theme, "shown", "hidden", rc.theme.unknown),
1343
+ onAdjust: (input, key, ctx) => {
1344
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => toggleMenuBarPresentationElement(c, "value"));
1345
+ }
1346
+ },
1347
+ {
1348
+ key: "menuBarProgress",
1349
+ label: "Progress",
1350
+ render: (rc) => toggleText(rc.config.tray.menuBar.elements.progress, rc.theme, "shown", "hidden", rc.theme.unknown),
1298
1351
  onAdjust: (input, key, ctx) => {
1299
- if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, showMenuBarText: !c.tray.showMenuBarText } }));
1352
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => toggleMenuBarPresentationElement(c, "progress"));
1300
1353
  }
1301
1354
  },
1302
1355
  {
@@ -1307,6 +1360,68 @@ var DESKTOP_FIXED_SETTINGS = [
1307
1360
  if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, menuBarValue: c.tray.menuBarValue === "usage" ? "todayTokens" : "usage" } }));
1308
1361
  }
1309
1362
  },
1363
+ {
1364
+ key: "menuBarDensity",
1365
+ label: "Density",
1366
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.accent, children: rc.config.tray.menuBar.density })),
1367
+ onAdjust: (input, key, ctx) => {
1368
+ if (!isAdjustKey(input, key)) return;
1369
+ const choices = ["comfortable", "compact", "tight"];
1370
+ ctx.global.updateConfig((c) => {
1371
+ const current = choices.indexOf(c.tray.menuBar.density);
1372
+ return { ...c, tray: { ...c.tray, menuBar: { ...c.tray.menuBar, density: choices[(current + step(key) + choices.length) % choices.length] } } };
1373
+ });
1374
+ }
1375
+ },
1376
+ {
1377
+ key: "menuBarEdgePadding",
1378
+ label: "Edge padding",
1379
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1380
+ rc.config.tray.menuBar.customSpacing.edgePaddingPt.toFixed(1),
1381
+ "pt"
1382
+ ] })),
1383
+ onAdjust: (input, key, ctx) => {
1384
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => adjustMenuBarSpacing(c, "edgePaddingPt", step(key), 6));
1385
+ }
1386
+ },
1387
+ {
1388
+ key: "menuBarMarkValueGap",
1389
+ label: "Mark/value gap",
1390
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1391
+ rc.config.tray.menuBar.customSpacing.markValueGapPt.toFixed(1),
1392
+ "pt"
1393
+ ] })),
1394
+ onAdjust: (input, key, ctx) => {
1395
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => adjustMenuBarSpacing(c, "markValueGapPt", step(key), 8));
1396
+ }
1397
+ },
1398
+ {
1399
+ key: "menuBarProviderGap",
1400
+ label: "Provider gap",
1401
+ render: (rc) => caret(/* @__PURE__ */ jsxs6(Text6, { bold: true, color: rc.theme.cost, children: [
1402
+ rc.config.tray.menuBar.customSpacing.providerGapPt.toFixed(1),
1403
+ "pt"
1404
+ ] })),
1405
+ onAdjust: (input, key, ctx) => {
1406
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => adjustMenuBarSpacing(c, "providerGapPt", step(key), 16));
1407
+ }
1408
+ },
1409
+ {
1410
+ key: "menuBarReset",
1411
+ label: "Reset presentation",
1412
+ render: (rc) => /* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.unknown, children: "press enter" }),
1413
+ onAdjust: (input, key, ctx) => {
1414
+ if (input !== " " && !key.return) return;
1415
+ ctx.global.updateConfig((c) => {
1416
+ const menuBar = {
1417
+ ...DEFAULT_MENU_BAR_CONFIG,
1418
+ elements: { ...DEFAULT_MENU_BAR_CONFIG.elements },
1419
+ customSpacing: { ...DEFAULT_MENU_BAR_CONFIG.customSpacing }
1420
+ };
1421
+ return { ...c, tray: { ...c.tray, menuBar, showMenuBarText: menuBar.elements.value } };
1422
+ });
1423
+ }
1424
+ },
1310
1425
  {
1311
1426
  key: "summary",
1312
1427
  label: "Summary",
@@ -1437,31 +1552,11 @@ var SettingsView = memo4(function SettingsView2({
1437
1552
  /* @__PURE__ */ jsx7(Row, { cursor, idx: i, label: row.label, children: row.render(rc) }),
1438
1553
  row.below?.(rc, cursor === i)
1439
1554
  ] }, row.key)),
1440
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
1441
- /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "Menu bar pins" }),
1442
- /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " up to 2 providers, in order" })
1443
- ] }),
1444
- PROVIDER_ORDER.map((pid, i) => {
1445
- const idx = DESKTOP_FIXED_ROWS + i;
1446
- const selected = cursor === idx;
1447
- const pinned = config.tray.pinnedProviders.includes(pid);
1448
- const position = config.tray.pinnedProviders.indexOf(pid);
1449
- const meta = PROVIDERS[pid];
1450
- return /* @__PURE__ */ jsxs6(Box6, { children: [
1451
- /* @__PURE__ */ jsxs6(Text6, { color: selected ? theme.accent : void 0, children: [
1452
- selected ? glyphs().caretR : " ",
1453
- " "
1454
- ] }),
1455
- /* @__PURE__ */ jsxs6(Box6, { width: 20, children: [
1456
- /* @__PURE__ */ jsxs6(Text6, { color: meta.color, children: [
1457
- glyphs().dot,
1458
- " "
1459
- ] }),
1460
- /* @__PURE__ */ jsx7(Text6, { bold: pinned, children: meta.name })
1461
- ] }),
1462
- /* @__PURE__ */ jsx7(Text6, { color: pinned ? theme.ok : void 0, dimColor: !pinned, children: pinned ? `[${position + 1}] pinned` : "[ ] not pinned" })
1463
- ] }, pid);
1464
- })
1555
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
1556
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Menu-bar presentation applies to the macOS desktop app." }),
1557
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Pin providers from their cards in the desktop overview." }),
1558
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: " Adjusting a spacing value switches the layout to custom." })
1559
+ ] })
1465
1560
  ] }),
1466
1561
  activeTab === "providers" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1467
1562
  /* @__PURE__ */ jsx7(Text6, { bold: true, dimColor: true, children: "Providers" }),
@@ -2576,7 +2671,7 @@ function handleSettings(input, key, ctx) {
2576
2671
  ctx.allowedHostsEditor.setValue(null);
2577
2672
  ctx.allowedHostsEditor.setError(null);
2578
2673
  };
2579
- 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;
2674
+ const rowCount = tab === "general" ? GENERAL_ROWS : tab === "theme" ? THEME_ROWS : tab === "desktop" ? DESKTOP_FIXED_ROWS : tab === "providers" ? PROVIDER_ORDER.length + 1 : trackedAccounts.length + 1;
2580
2675
  if (key.tab) {
2581
2676
  switchTab(key.shift ? -1 : 1);
2582
2677
  return;
@@ -2630,20 +2725,7 @@ function handleSettings(input, key, ctx) {
2630
2725
  return;
2631
2726
  }
2632
2727
  if (tab === "desktop") {
2633
- if (cursor < DESKTOP_FIXED_ROWS) {
2634
- DESKTOP_FIXED_SETTINGS[cursor]?.onAdjust(input, key, ctx);
2635
- return;
2636
- }
2637
- const provider = PROVIDER_ORDER[cursor - DESKTOP_FIXED_ROWS];
2638
- if (provider && (input === " " || key.leftArrow || key.rightArrow || key.return)) {
2639
- updateConfig((current) => ({
2640
- ...current,
2641
- tray: {
2642
- ...current.tray,
2643
- pinnedProviders: toggleProviderSelection(current.tray.pinnedProviders, provider, new Set(PROVIDER_ORDER), 2)
2644
- }
2645
- }));
2646
- }
2728
+ DESKTOP_FIXED_SETTINGS[cursor]?.onAdjust(input, key, ctx);
2647
2729
  return;
2648
2730
  }
2649
2731
  if (tab === "providers") {
@@ -3698,7 +3780,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3698
3780
  () => getTrackedAccountRows(cfg, detectedProviders, accounts),
3699
3781
  [cfg, detectedProviders, accounts]
3700
3782
  );
3701
- 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;
3783
+ const settingsRowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "theme" ? THEME_ROWS : settingsTab === "desktop" ? DESKTOP_FIXED_ROWS : settingsTab === "providers" ? PROVIDER_ORDER.length + 1 : trackedAccountRows.length + 1;
3702
3784
  const accountsRef = useRef8([]);
3703
3785
  accountsRef.current = accounts;
3704
3786
  const rowCountRef = useRef8(0);
@@ -3939,11 +4021,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3939
4021
  });
3940
4022
  }
3941
4023
  function toggleProvider(pid) {
3942
- updateConfig((c) => ({
3943
- ...c,
3944
- knownProviders: c.knownProviders.includes(pid) ? c.knownProviders : [...c.knownProviders, pid],
3945
- disabledProviders: c.disabledProviders.includes(pid) ? c.disabledProviders.filter((p) => p !== pid) : [...c.disabledProviders, pid]
3946
- }));
4024
+ updateConfig((c) => setProviderTrackingEnabled(c, pid, c.disabledProviders.includes(pid)));
3947
4025
  }
3948
4026
  function confirmOnboarding() {
3949
4027
  const enabled = onboardEnabled;
@@ -4010,7 +4088,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
4010
4088
  if (webStartingRef.current) return;
4011
4089
  webStartingRef.current = true;
4012
4090
  try {
4013
- const { startWebServer } = await import("./server-LNFK5MRX.js");
4091
+ const { startWebServer } = await import("./server-P6BRXBZ5.js");
4014
4092
  const ctrl = await startWebServer({ config: cfg, log: false });
4015
4093
  webRef.current = ctrl;
4016
4094
  openUrl(ctrl.url);
@@ -5,13 +5,13 @@ import {
5
5
  identityFromIdToken,
6
6
  readClaudeIdentity,
7
7
  readJson
8
- } from "./chunk-DESEB73E.js";
8
+ } from "./chunk-WPZTPMBX.js";
9
9
  import {
10
10
  containsEmail,
11
11
  expandHome,
12
12
  redactEmail,
13
13
  slugify
14
- } from "./chunk-O7ORALKA.js";
14
+ } from "./chunk-G2A3DVPU.js";
15
15
 
16
16
  // src/accounts.ts
17
17
  import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
@@ -10,13 +10,13 @@ import {
10
10
  deriveQuotaViews,
11
11
  fetchPeak,
12
12
  namedHex
13
- } from "./chunk-BZFDTIFV.js";
13
+ } from "./chunk-3D6TO5TS.js";
14
14
  import {
15
15
  PROVIDERS,
16
16
  detectProviders,
17
17
  resolveTimezone,
18
18
  withTimeout
19
- } from "./chunk-DESEB73E.js";
19
+ } from "./chunk-WPZTPMBX.js";
20
20
  import {
21
21
  ConfigPersistenceFailure,
22
22
  ConfigUpdateConflictFailure,
@@ -26,7 +26,7 @@ import {
26
26
  TOKMON_WS_METHODS,
27
27
  TOKMON_WS_PATH,
28
28
  TokmonRpcGroup
29
- } from "./chunk-UON47FYF.js";
29
+ } from "./chunk-BYLWQ46U.js";
30
30
  import {
31
31
  cacheDir,
32
32
  expandHome,
@@ -34,7 +34,7 @@ import {
34
34
  normalizeConfig,
35
35
  saveConfig,
36
36
  snapshotCacheFile
37
- } from "./chunk-O7ORALKA.js";
37
+ } from "./chunk-G2A3DVPU.js";
38
38
 
39
39
  // src/web/server.ts
40
40
  import { createServer } from "http";
@@ -845,6 +845,13 @@ function mergeCapabilityFields(incomingConfig, current) {
845
845
  tray: hasOwn(incoming, "tray") && incomingTray ? {
846
846
  ...current.tray,
847
847
  ...incomingTray,
848
+ menuBar: hasOwn(incomingTray, "menuBar") ? incomingTray.menuBar : hasOwn(incomingTray, "showMenuBarText") && incomingTray.showMenuBarText !== current.tray.showMenuBarText ? {
849
+ ...current.tray.menuBar,
850
+ elements: {
851
+ ...current.tray.menuBar.elements,
852
+ value: incomingTray.showMenuBarText
853
+ }
854
+ } : current.tray.menuBar,
848
855
  pinnedProviders: hasOwn(incomingTray, "pinnedProviders") ? incomingTray.pinnedProviders : current.tray.pinnedProviders
849
856
  } : current.tray,
850
857
  desktop: hasOwn(incoming, "desktop") && incomingDesktop ? {
@@ -4,10 +4,10 @@ import {
4
4
  TOKMON_WS_METHODS,
5
5
  TOKMON_WS_PATH,
6
6
  TokmonRpcGroup
7
- } from "./chunk-UON47FYF.js";
7
+ } from "./chunk-BYLWQ46U.js";
8
8
  import {
9
9
  normalizeConfig
10
- } from "./chunk-O7ORALKA.js";
10
+ } from "./chunk-G2A3DVPU.js";
11
11
 
12
12
  // src/client/daemon-rpc-client.ts
13
13
  import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Schema, Stream } from "effect";
@@ -8,14 +8,14 @@ import {
8
8
  readLock,
9
9
  retireIncompatibleCliOwner,
10
10
  verifyLock
11
- } from "./chunk-WHOIUSJM.js";
11
+ } from "./chunk-YWDMEU3L.js";
12
12
  import {
13
13
  daemonChannelFromWire,
14
14
  resolveDaemonChannel
15
15
  } from "./chunk-UYPDMVW5.js";
16
16
  import {
17
17
  TOKMON_PROTOCOL_VERSION
18
- } from "./chunk-UON47FYF.js";
18
+ } from "./chunk-BYLWQ46U.js";
19
19
 
20
20
  // src/client/daemon-handle.ts
21
21
  import { spawn } from "child_process";
@@ -4,7 +4,7 @@ import {
4
4
  DESKTOP_GRAPH_RANGES,
5
5
  PROVIDER_IDS,
6
6
  THEME_PRESET_IDS
7
- } from "./chunk-O7ORALKA.js";
7
+ } from "./chunk-G2A3DVPU.js";
8
8
 
9
9
  // src/rpc/contract.ts
10
10
  import { Schema } from "effect";
@@ -12,7 +12,7 @@ import * as Rpc from "effect/unstable/rpc/Rpc";
12
12
  import * as RpcGroup from "effect/unstable/rpc/RpcGroup";
13
13
  var TOKMON_WS_PATH = "/ws";
14
14
  var TOKMON_PROTOCOL_VERSION = 4;
15
- var TOKMON_CAPABILITIES = ["config-cas", "config-revision", "allowed-hosts", "tray-config", "usage-activity", "tray-pins", "provider-pins", "desktop-disclosure", "desktop-graph-range", "provider-headroom", "canonical-identity", "appearance-v1", "theme-engine", "account-detection-v1", "menu-bar-today-tokens"];
15
+ var TOKMON_CAPABILITIES = ["config-cas", "config-revision", "allowed-hosts", "tray-config", "usage-activity", "tray-pins", "provider-pins", "desktop-disclosure", "desktop-graph-range", "provider-headroom", "canonical-identity", "appearance-v1", "theme-engine", "account-detection-v1", "menu-bar-today-tokens", "menu-bar-builder-v1"];
16
16
  var TOKMON_WS_METHODS = {
17
17
  getConfig: "tokmon.getConfig",
18
18
  setConfig: "tokmon.setConfig",
@@ -40,6 +40,23 @@ var AccountSchema = Schema.Struct({
40
40
  });
41
41
  var TrayConfigSchema = Schema.Struct({
42
42
  enabled: Schema.Boolean,
43
+ // Additive under `menu-bar-builder-v1`; old peers omit it and repair from
44
+ // `showMenuBarText`, while the daemon's CAS boundary preserves newer state.
45
+ menuBar: Schema.optionalKey(Schema.Struct({
46
+ version: Schema.Literal(1),
47
+ mode: Schema.Literals(["auto", "custom"]),
48
+ elements: Schema.Struct({
49
+ providerMark: Schema.Boolean,
50
+ value: Schema.Boolean,
51
+ progress: Schema.Boolean
52
+ }),
53
+ density: Schema.Literals(["comfortable", "compact", "tight"]),
54
+ customSpacing: Schema.Struct({
55
+ edgePaddingPt: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 6 })).check(Schema.isMultipleOf(0.5)),
56
+ markValueGapPt: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 8 })).check(Schema.isMultipleOf(0.5)),
57
+ providerGapPt: Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 16 })).check(Schema.isMultipleOf(0.5))
58
+ })
59
+ })),
43
60
  showMenuBarText: Schema.Boolean,
44
61
  menuBarValue: Schema.optionalKey(Schema.Literals(["usage", "todayTokens"])),
45
62
  displayMetric: Schema.Literals(["smartHeadroom", "tightestRemaining"]),
@@ -500,8 +500,24 @@ function moveProviderSelection(values, providerId, direction) {
500
500
  [next[index], next[target]] = [next[target], next[index]];
501
501
  return next;
502
502
  }
503
+ var DEFAULT_MENU_BAR_CONFIG = {
504
+ version: 1,
505
+ mode: "auto",
506
+ elements: {
507
+ providerMark: true,
508
+ value: true,
509
+ progress: false
510
+ },
511
+ density: "comfortable",
512
+ customSpacing: {
513
+ edgePaddingPt: 1,
514
+ markValueGapPt: 3,
515
+ providerGapPt: 8
516
+ }
517
+ };
503
518
  var DEFAULT_TRAY_CONFIG = {
504
519
  enabled: true,
520
+ menuBar: DEFAULT_MENU_BAR_CONFIG,
505
521
  showMenuBarText: true,
506
522
  menuBarValue: "usage",
507
523
  displayMetric: "smartHeadroom",
@@ -670,6 +686,14 @@ function getTrackedAccountRows(config, trackedProviders = PROVIDER_ORDER.filter(
670
686
  function providerDetectionEnabled(config, providerId) {
671
687
  return config.enabled && !config.disabledProviders.includes(providerId);
672
688
  }
689
+ function setProviderTrackingEnabled(config, providerId, enabled) {
690
+ const disabledProviders = config.disabledProviders.filter((id) => id !== providerId);
691
+ return {
692
+ ...config,
693
+ knownProviders: config.knownProviders.includes(providerId) ? config.knownProviders : [...config.knownProviders, providerId],
694
+ disabledProviders: enabled ? disabledProviders : [...disabledProviders, providerId]
695
+ };
696
+ }
673
697
  function setProviderDetectionEnabled(config, providerId, enabled) {
674
698
  const without = config.disabledProviders.filter((id) => id !== providerId);
675
699
  return {
@@ -692,6 +716,10 @@ function clampNum(v, fallback, min) {
692
716
  function finiteInRange(v, fallback, min, max) {
693
717
  return typeof v === "number" && Number.isFinite(v) && v >= min && v <= max ? v : fallback;
694
718
  }
719
+ function halfPointInRange(v, fallback, min, max) {
720
+ if (typeof v !== "number" || !Number.isFinite(v)) return fallback;
721
+ return Math.min(max, Math.max(min, Math.round(v * 2) / 2));
722
+ }
695
723
  function isValidTimezone(tz) {
696
724
  try {
697
725
  new Intl.DateTimeFormat("en-CA", { timeZone: tz });
@@ -713,6 +741,50 @@ function sameJson(a, b) {
713
741
  return false;
714
742
  }
715
743
  }
744
+ function repairMenuBarConfig(input, legacyShowMenuBarText = DEFAULT_TRAY_CONFIG.showMenuBarText) {
745
+ if (!isRecord2(input)) {
746
+ return {
747
+ ...DEFAULT_MENU_BAR_CONFIG,
748
+ elements: {
749
+ ...DEFAULT_MENU_BAR_CONFIG.elements,
750
+ value: legacyShowMenuBarText
751
+ },
752
+ customSpacing: { ...DEFAULT_MENU_BAR_CONFIG.customSpacing }
753
+ };
754
+ }
755
+ const elements = isRecord2(input.elements) ? input.elements : {};
756
+ const spacing = isRecord2(input.customSpacing) ? input.customSpacing : {};
757
+ return {
758
+ version: 1,
759
+ mode: input.mode === "custom" ? "custom" : "auto",
760
+ elements: {
761
+ providerMark: typeof elements.providerMark === "boolean" ? elements.providerMark : DEFAULT_MENU_BAR_CONFIG.elements.providerMark,
762
+ value: typeof elements.value === "boolean" ? elements.value : DEFAULT_MENU_BAR_CONFIG.elements.value,
763
+ progress: typeof elements.progress === "boolean" ? elements.progress : DEFAULT_MENU_BAR_CONFIG.elements.progress
764
+ },
765
+ density: input.density === "compact" || input.density === "tight" ? input.density : DEFAULT_MENU_BAR_CONFIG.density,
766
+ customSpacing: {
767
+ edgePaddingPt: halfPointInRange(
768
+ spacing.edgePaddingPt,
769
+ DEFAULT_MENU_BAR_CONFIG.customSpacing.edgePaddingPt,
770
+ 0,
771
+ 6
772
+ ),
773
+ markValueGapPt: halfPointInRange(
774
+ spacing.markValueGapPt,
775
+ DEFAULT_MENU_BAR_CONFIG.customSpacing.markValueGapPt,
776
+ 0,
777
+ 8
778
+ ),
779
+ providerGapPt: halfPointInRange(
780
+ spacing.providerGapPt,
781
+ DEFAULT_MENU_BAR_CONFIG.customSpacing.providerGapPt,
782
+ 0,
783
+ 16
784
+ )
785
+ }
786
+ };
787
+ }
716
788
  function normalizeAllowedHost(value) {
717
789
  if (typeof value !== "string") return null;
718
790
  const candidate = value.trim();
@@ -819,9 +891,13 @@ function repairConfig(input) {
819
891
  }
820
892
  const rawTray = isRecord2(parsed.tray) ? parsed.tray : {};
821
893
  if (parsed.tray !== void 0 && !isRecord2(parsed.tray)) reasons.push("tray was not an object");
894
+ const legacyShowMenuBarText = typeof rawTray.showMenuBarText === "boolean" ? rawTray.showMenuBarText : DEFAULT_TRAY_CONFIG.showMenuBarText;
895
+ const menuBar = repairMenuBarConfig(rawTray.menuBar, legacyShowMenuBarText);
822
896
  const tray = {
823
897
  enabled: typeof rawTray.enabled === "boolean" ? rawTray.enabled : DEFAULT_TRAY_CONFIG.enabled,
824
- showMenuBarText: typeof rawTray.showMenuBarText === "boolean" ? rawTray.showMenuBarText : DEFAULT_TRAY_CONFIG.showMenuBarText,
898
+ menuBar,
899
+ // Keep the legacy field canonical for older desktop and CLI clients.
900
+ showMenuBarText: menuBar.elements.value,
825
901
  menuBarValue: rawTray.menuBarValue === "todayTokens" || rawTray.menuBarValue === "usage" ? rawTray.menuBarValue : DEFAULT_TRAY_CONFIG.menuBarValue,
826
902
  displayMetric: rawTray.displayMetric === "tightestRemaining" || rawTray.displayMetric === "smartHeadroom" ? rawTray.displayMetric : DEFAULT_TRAY_CONFIG.displayMetric,
827
903
  pollIntervalSec: finiteInRange(rawTray.pollIntervalSec, DEFAULT_TRAY_CONFIG.pollIntervalSec, 1, 86400),
@@ -1062,6 +1138,7 @@ export {
1062
1138
  cleanProviderSelection,
1063
1139
  toggleProviderSelection,
1064
1140
  moveProviderSelection,
1141
+ DEFAULT_MENU_BAR_CONFIG,
1065
1142
  DEFAULT_TRAY_CONFIG,
1066
1143
  DEFAULT_ACCOUNT_DETECTION_CONFIG,
1067
1144
  DEFAULTS,
@@ -1072,10 +1149,12 @@ export {
1072
1149
  redactEmail,
1073
1150
  getTrackedAccountRows,
1074
1151
  providerDetectionEnabled,
1152
+ setProviderTrackingEnabled,
1075
1153
  setProviderDetectionEnabled,
1076
1154
  setDetectedAccountExcluded,
1077
1155
  clampNum,
1078
1156
  isValidTimezone,
1157
+ repairMenuBarConfig,
1079
1158
  normalizeAllowedHost,
1080
1159
  normalizeAllowedHosts,
1081
1160
  repairConfig,
@@ -5,7 +5,7 @@ import {
5
5
  envDir,
6
6
  expandHome,
7
7
  isValidTimezone
8
- } from "./chunk-O7ORALKA.js";
8
+ } from "./chunk-G2A3DVPU.js";
9
9
 
10
10
  // src/async.ts
11
11
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-UYPDMVW5.js";
6
6
  import {
7
7
  cacheDir
8
- } from "./chunk-O7ORALKA.js";
8
+ } from "./chunk-G2A3DVPU.js";
9
9
 
10
10
  // src/web/daemon-compatibility.ts
11
11
  function classifyDaemonCompatibility(owner, clientProtocolVersion) {