tokmon 0.28.4 → 0.28.6

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 (33) hide show
  1. package/dist/{bootstrap-ink-WG4WG3R2.js → bootstrap-ink-PYYVQAYP.js} +25 -9
  2. package/dist/{chunk-FLANGRI6.js → chunk-23NB5Z6V.js} +64 -9
  3. package/dist/{chunk-P57DVFNH.js → chunk-6E2OJCIQ.js} +4 -4
  4. package/dist/{chunk-TN6V7XSL.js → chunk-BZFDTIFV.js} +32 -10
  5. package/dist/{chunk-CFKUZCMB.js → chunk-DESEB73E.js} +7 -6
  6. package/dist/{chunk-5B3XCEMC.js → chunk-MMAKKTCU.js} +2 -1
  7. package/dist/{chunk-HNWEJ6MS.js → chunk-O7ORALKA.js} +2 -0
  8. package/dist/{chunk-7CH6S7BR.js → chunk-RY24UHMK.js} +2 -2
  9. package/dist/{chunk-JZSZEHVD.js → chunk-UON47FYF.js} +12 -4
  10. package/dist/{cli-command-I4GRR3MF.js → cli-command-MW6EBLNA.js} +17 -8
  11. package/dist/cli.js +6 -6
  12. package/dist/{config-3DGZ47F7.js → config-JEDXAKPZ.js} +1 -1
  13. package/dist/{daemon-ZXV3V5CS.js → daemon-XB6ZORTE.js} +6 -6
  14. package/dist/daemon-handle-4WDYZBVL.js +11 -0
  15. package/dist/server-LNFK5MRX.js +12 -0
  16. package/dist/web/assets/{Area-stRnp8Km.js → Area-BqyvRn2x.js} +1 -1
  17. package/dist/web/assets/{analytics-moZZt9L_.js → analytics-B6QwqU9g.js} +2 -2
  18. package/dist/web/assets/{breakdown-km9w7myU.js → breakdown-DYnVKScr.js} +1 -1
  19. package/dist/web/assets/{button-DrX04Cc2.js → button-CnT5g-Jl.js} +1 -1
  20. package/dist/web/assets/{chart-D0CtuL2k.js → chart-Duz9D-nH.js} +1 -1
  21. package/dist/web/assets/{explore-B11uNJV1.js → explore-P2EolJrt.js} +1 -1
  22. package/dist/web/assets/{index-RDhXsCNG.js → index-9hkGpa7j.js} +24 -24
  23. package/dist/web/assets/{models-DYbZnY34.js → models-8B4FBxr7.js} +2 -2
  24. package/dist/web/assets/{overview-CruICbfJ.js → overview-DXA_HAsw.js} +2 -2
  25. package/dist/web/assets/{panel-xckM3_GC.js → panel-gxLFBb8y.js} +1 -1
  26. package/dist/web/assets/settings-sheet-CJJl-3Gw.js +1 -0
  27. package/dist/web/assets/{share-sheet-weIdd4DW.js → share-sheet-CXdyA1ga.js} +1 -1
  28. package/dist/web/assets/{timeline-BHgniliZ.js → timeline-CzC85trW.js} +1 -1
  29. package/dist/web/index.html +1 -1
  30. package/package.json +1 -1
  31. package/dist/daemon-handle-G4OGPFR3.js +0 -11
  32. package/dist/server-EPSH52DR.js +0 -12
  33. package/dist/web/assets/settings-sheet-G-BTSIF6.js +0 -1
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createDaemonRpcClient
4
- } from "./chunk-7CH6S7BR.js";
4
+ } from "./chunk-RY24UHMK.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-TN6V7XSL.js";
14
+ } from "./chunk-BZFDTIFV.js";
15
15
  import {
16
16
  PROVIDERS,
17
17
  PROVIDER_ORDER,
@@ -28,8 +28,8 @@ import {
28
28
  time,
29
29
  tokens,
30
30
  withTimeout
31
- } from "./chunk-CFKUZCMB.js";
32
- import "./chunk-JZSZEHVD.js";
31
+ } from "./chunk-DESEB73E.js";
32
+ import "./chunk-UON47FYF.js";
33
33
  import {
34
34
  COLOR_PALETTE,
35
35
  DEFAULTS,
@@ -54,7 +54,7 @@ import {
54
54
  snapshotCacheFile,
55
55
  themePresetOption,
56
56
  toggleProviderSelection
57
- } from "./chunk-HNWEJ6MS.js";
57
+ } from "./chunk-O7ORALKA.js";
58
58
  import {
59
59
  glyphs
60
60
  } from "./chunk-RF4GGQGM.js";
@@ -655,6 +655,12 @@ function quotaViews(stats) {
655
655
  }
656
656
  function QuotaRow({ quota, color, barW, labelW, resetW, resetDisplay, tz }) {
657
657
  const theme = useTuiTheme();
658
+ if (quota.value?.kind === "money") {
659
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
660
+ /* @__PURE__ */ jsx3(Box2, { width: labelW, flexShrink: 0, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate", children: quota.label }) }),
661
+ /* @__PURE__ */ jsx3(Text2, { bold: true, color: theme.cost, wrap: "truncate-end", children: quota.valueText })
662
+ ] });
663
+ }
658
664
  if (quota.bounded && quota.usedPct !== null) {
659
665
  const used = quota.usedPct;
660
666
  const remaining = quota.remainingPct ?? 0;
@@ -1293,6 +1299,14 @@ var DESKTOP_FIXED_SETTINGS = [
1293
1299
  if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, showMenuBarText: !c.tray.showMenuBarText } }));
1294
1300
  }
1295
1301
  },
1302
+ {
1303
+ key: "menuBarContent",
1304
+ label: "Menu bar content",
1305
+ render: (rc) => caret(/* @__PURE__ */ jsx7(Text6, { bold: true, color: rc.theme.accent, children: rc.config.tray.menuBarValue === "todayTokens" ? "tokens today" : "usage" })),
1306
+ onAdjust: (input, key, ctx) => {
1307
+ if (isAdjustKey(input, key)) ctx.global.updateConfig((c) => ({ ...c, tray: { ...c.tray, menuBarValue: c.tray.menuBarValue === "usage" ? "todayTokens" : "usage" } }));
1308
+ }
1309
+ },
1296
1310
  {
1297
1311
  key: "summary",
1298
1312
  label: "Summary",
@@ -3622,7 +3636,7 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
3622
3636
 
3623
3637
  // src/app.tsx
3624
3638
  import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3625
- function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "degraded" }) {
3639
+ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "degraded", degradedMessage }) {
3626
3640
  const connected = mode === "connected" && baseUrl !== null;
3627
3641
  const degraded = !connected;
3628
3642
  const daemon = useDaemon(connected ? baseUrl : null);
@@ -3996,7 +4010,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
3996
4010
  if (webStartingRef.current) return;
3997
4011
  webStartingRef.current = true;
3998
4012
  try {
3999
- const { startWebServer } = await import("./server-EPSH52DR.js");
4013
+ const { startWebServer } = await import("./server-LNFK5MRX.js");
4000
4014
  const ctrl = await startWebServer({ config: cfg, log: false });
4001
4015
  webRef.current = ctrl;
4002
4016
  openUrl(ctrl.url);
@@ -4185,7 +4199,8 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
4185
4199
  glyphs().warn,
4186
4200
  " degraded ",
4187
4201
  glyphs().middot,
4188
- " background service unavailable, running in-process"
4202
+ " ",
4203
+ degradedMessage ?? "background service unavailable, running in-process"
4189
4204
  ] }),
4190
4205
  connected && daemon.conn !== "live" && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
4191
4206
  glyphs().warn,
@@ -4333,7 +4348,8 @@ async function bootstrapInk({ interval, config, daemon, mode }) {
4333
4348
  interval,
4334
4349
  initialConfig: config,
4335
4350
  baseUrl: daemon.baseUrl,
4336
- mode
4351
+ mode,
4352
+ degradedMessage: daemon.issue?.message
4337
4353
  }
4338
4354
  ) })
4339
4355
  );
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ isAlive,
4
+ probeHealth,
5
+ readForeignLock,
3
6
  readLock,
4
7
  retireIncompatibleCliOwner,
5
8
  verifyLock
6
- } from "./chunk-5B3XCEMC.js";
9
+ } from "./chunk-MMAKKTCU.js";
7
10
  import {
8
11
  daemonChannelFromWire,
9
12
  resolveDaemonChannel
10
13
  } from "./chunk-UYPDMVW5.js";
11
14
  import {
12
15
  TOKMON_PROTOCOL_VERSION
13
- } from "./chunk-JZSZEHVD.js";
16
+ } from "./chunk-UON47FYF.js";
14
17
 
15
18
  // src/client/daemon-handle.ts
16
19
  import { spawn } from "child_process";
@@ -51,6 +54,28 @@ async function attach(opts, protocolVersion) {
51
54
  const lock = await verifyLock(readLock(opts), protocolVersion);
52
55
  return lock ? connected(lock.url) : null;
53
56
  }
57
+ async function incompatibleDesktopIssue(opts, protocolVersion, timeoutMs) {
58
+ const owner = readForeignLock(opts);
59
+ if (!owner || owner.ownerKind !== "desktop" || owner.protocolVersion === void 0 || owner.protocolVersion === protocolVersion || !isAlive(owner.pid)) return null;
60
+ const verified = await probeHealth(owner.url, owner.wsToken, {
61
+ ownerKind: "desktop",
62
+ channel: owner.channel,
63
+ protocolVersion: owner.protocolVersion,
64
+ ...owner.version ? { version: owner.version } : {}
65
+ }, Math.min(500, timeoutMs));
66
+ if (!verified) return null;
67
+ const ownerIsOlder = owner.protocolVersion < protocolVersion;
68
+ const direction = ownerIsOlder ? "an older" : "a newer";
69
+ const ownerName = owner.version ? `Tokmon Desktop ${owner.version}` : "Tokmon Desktop";
70
+ const recovery = ownerIsOlder ? "Update and restart the desktop app, or quit it before running the CLI." : "Update the CLI, or quit the desktop app before running this version. With pnpm release-age filtering, use `pnpm --config.minimum-release-age=0 dlx tokmon@latest` once.";
71
+ return {
72
+ kind: "incompatible-desktop",
73
+ message: `${ownerName} is using ${direction} background service (protocol ${owner.protocolVersion}; this CLI needs ${protocolVersion}). ${recovery}`,
74
+ ownerVersion: owner.version ?? null,
75
+ ownerProtocolVersion: owner.protocolVersion,
76
+ clientProtocolVersion: protocolVersion
77
+ };
78
+ }
54
79
  async function attachOrSpawn(opts = {}) {
55
80
  const protocolVersion = TOKMON_PROTOCOL_VERSION;
56
81
  const timeoutMs = opts.timeoutMs ?? HANDSHAKE_TIMEOUT_MS;
@@ -59,6 +84,8 @@ async function attachOrSpawn(opts = {}) {
59
84
  const lockOpts = { cachePath: opts.cachePath, channel };
60
85
  const existing = await attach(lockOpts, protocolVersion);
61
86
  if (existing) return existing;
87
+ const desktopIssue = await incompatibleDesktopIssue(lockOpts, protocolVersion, timeoutMs);
88
+ if (desktopIssue) return degraded(desktopIssue);
62
89
  await retireIncompatibleCliOwner(lockOpts, protocolVersion, timeoutMs);
63
90
  const upgraded = await attach(lockOpts, protocolVersion);
64
91
  if (upgraded) return upgraded;
@@ -79,13 +106,19 @@ async function attachOrSpawn(opts = {}) {
79
106
  detached: process.platform !== "win32",
80
107
  env
81
108
  });
82
- } catch {
83
- resolve(degraded());
109
+ } catch (error) {
110
+ const detail = error instanceof Error && error.message ? `: ${error.message}` : "";
111
+ resolve(degraded({
112
+ kind: "spawn-failed",
113
+ message: `Could not start the Tokmon background service${detail}. Check Node permissions and retry; local-only mode is active.`
114
+ }));
84
115
  return;
85
116
  }
86
117
  let settled = false;
87
118
  let stdout = "";
88
119
  let pollTimer = null;
120
+ let startupError = null;
121
+ let startupExit = null;
89
122
  const finish = (handle) => {
90
123
  if (settled) return;
91
124
  settled = true;
@@ -102,7 +135,23 @@ async function attachOrSpawn(opts = {}) {
102
135
  return;
103
136
  }
104
137
  if (final) {
105
- finish(degraded());
138
+ if (startupError) {
139
+ finish(degraded({
140
+ kind: "spawn-failed",
141
+ message: `Could not start the Tokmon background service: ${startupError}. Check Node permissions and retry; local-only mode is active.`
142
+ }));
143
+ } else if (startupExit) {
144
+ const result = startupExit.signal ? `signal ${startupExit.signal}` : `exit code ${startupExit.code ?? "unknown"}`;
145
+ finish(degraded({
146
+ kind: "startup-exit",
147
+ message: `The Tokmon background service exited before it was ready (${result}). Update or reinstall Tokmon, then retry; local-only mode is active.`
148
+ }));
149
+ } else {
150
+ finish(degraded({
151
+ kind: "startup-timeout",
152
+ message: `The Tokmon background service did not become ready within ${Math.ceil(timeoutMs / 1e3)}s. Quit any stuck Tokmon process and retry; local-only mode is active.`
153
+ }));
154
+ }
106
155
  return;
107
156
  }
108
157
  if (!settled && !pollTimer) {
@@ -127,12 +176,18 @@ async function attachOrSpawn(opts = {}) {
127
176
  return;
128
177
  }
129
178
  });
130
- child.once("error", () => tryAttach());
131
- child.once("exit", () => tryAttach());
179
+ child.once("error", (error) => {
180
+ startupError = error.message || error.name;
181
+ tryAttach();
182
+ });
183
+ child.once("exit", (code, signal) => {
184
+ startupExit = { code, signal };
185
+ tryAttach();
186
+ });
132
187
  });
133
188
  }
134
- function degraded() {
135
- return { kind: "degraded", baseUrl: null, stop: () => {
189
+ function degraded(issue) {
190
+ return { kind: "degraded", baseUrl: null, ...issue ? { issue } : {}, stop: () => {
136
191
  } };
137
192
  }
138
193
 
@@ -10,13 +10,13 @@ import {
10
10
  deriveQuotaViews,
11
11
  fetchPeak,
12
12
  namedHex
13
- } from "./chunk-TN6V7XSL.js";
13
+ } from "./chunk-BZFDTIFV.js";
14
14
  import {
15
15
  PROVIDERS,
16
16
  detectProviders,
17
17
  resolveTimezone,
18
18
  withTimeout
19
- } from "./chunk-CFKUZCMB.js";
19
+ } from "./chunk-DESEB73E.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-JZSZEHVD.js";
29
+ } from "./chunk-UON47FYF.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-HNWEJ6MS.js";
37
+ } from "./chunk-O7ORALKA.js";
38
38
 
39
39
  // src/web/server.ts
40
40
  import { createServer } from "http";
@@ -5,13 +5,13 @@ import {
5
5
  identityFromIdToken,
6
6
  readClaudeIdentity,
7
7
  readJson
8
- } from "./chunk-CFKUZCMB.js";
8
+ } from "./chunk-DESEB73E.js";
9
9
  import {
10
10
  containsEmail,
11
11
  expandHome,
12
12
  redactEmail,
13
13
  slugify
14
- } from "./chunk-HNWEJ6MS.js";
14
+ } from "./chunk-O7ORALKA.js";
15
15
 
16
16
  // src/accounts.ts
17
17
  import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
@@ -281,15 +281,34 @@ function inferRole(metric) {
281
281
  if (metric.modelId || !metric.primary && metric.format.kind === "percent" && !["usage", "api", "auto"].includes(label)) return "model";
282
282
  return "other";
283
283
  }
284
- function valueText(metric, usedPct) {
284
+ function formatMoney(value, currency) {
285
+ try {
286
+ return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(value);
287
+ } catch {
288
+ return `$${value.toFixed(2)}`;
289
+ }
290
+ }
291
+ function moneyValue(metric) {
292
+ if (metric.format.kind !== "dollars" || !Number.isFinite(metric.used)) return void 0;
293
+ const limit = metric.limit != null && Number.isFinite(metric.limit) ? metric.limit : null;
294
+ return {
295
+ kind: "money",
296
+ used: metric.used,
297
+ limit,
298
+ remaining: limit === null ? null : limit - metric.used,
299
+ currency: metric.format.currency?.trim().toUpperCase() || "USD"
300
+ };
301
+ }
302
+ function valueText(metric, usedPct, value) {
303
+ if (value) {
304
+ if (value.remaining === null) return formatMoney(value.used, value.currency);
305
+ const used = `${formatMoney(value.used, value.currency)} used`;
306
+ return value.remaining >= 0 ? `${used} \xB7 ${formatMoney(value.remaining, value.currency)} left` : `${used} \xB7 ${formatMoney(-value.remaining, value.currency)} over`;
307
+ }
285
308
  if (usedPct !== null) return `${Math.round(usedPct)}% used`;
286
309
  if (!Number.isFinite(metric.used)) return "\u2014";
287
310
  if (metric.format.kind === "dollars") {
288
- try {
289
- return new Intl.NumberFormat("en-US", { style: "currency", currency: metric.format.currency ?? "USD", maximumFractionDigits: 2 }).format(metric.used);
290
- } catch {
291
- return `$${metric.used.toFixed(2)}`;
292
- }
311
+ return formatMoney(metric.used, metric.format.currency?.trim().toUpperCase() || "USD");
293
312
  }
294
313
  if (metric.format.kind === "count") {
295
314
  const suffix = metric.format.suffix?.trim();
@@ -303,6 +322,7 @@ function deriveQuotaView(metric, sourceIndex = 0) {
303
322
  const usedPct = metric.format.kind === "percent" ? metric.used : metric.limit != null && metric.limit > 0 ? metric.used / metric.limit * 100 : null;
304
323
  const finiteUsedPct = usedPct == null || !Number.isFinite(usedPct) ? null : clampPct(usedPct);
305
324
  const resets = metric.resetsAt ? Date.parse(metric.resetsAt) : Number.NaN;
325
+ const value = moneyValue(metric);
306
326
  return {
307
327
  key: metric.key ?? `${metric.label}:${sourceIndex}`,
308
328
  label: metric.label.replace(/\s*(limit|usage)$/i, "").trim() || metric.label,
@@ -315,7 +335,8 @@ function deriveQuotaView(metric, sourceIndex = 0) {
315
335
  primary: metric.primary === true,
316
336
  active: metric.active === true,
317
337
  displayOrder: sourceIndex,
318
- valueText: valueText(metric, finiteUsedPct)
338
+ valueText: valueText(metric, finiteUsedPct, value),
339
+ ...value ? { value } : {}
319
340
  };
320
341
  }
321
342
  function deriveQuotaViews(metrics) {
@@ -425,8 +446,9 @@ function cumulativeQuotaViews(accounts) {
425
446
  const remainingPct = rows.reduce((sum, quota) => sum + quota.remainingPct, 0) / rows.length;
426
447
  const usedPct = 100 - remainingPct;
427
448
  const resets = rows.map((quota) => quota.resetsAt).filter((value) => value !== null);
449
+ const { value: _value, ...firstWithoutValue } = first;
428
450
  return {
429
- ...first,
451
+ ...firstWithoutValue,
430
452
  usedPct,
431
453
  remainingPct,
432
454
  resetsAt: resets.length > 0 ? Math.min(...resets) : null,
@@ -5,7 +5,7 @@ import {
5
5
  envDir,
6
6
  expandHome,
7
7
  isValidTimezone
8
- } from "./chunk-HNWEJ6MS.js";
8
+ } from "./chunk-O7ORALKA.js";
9
9
 
10
10
  // src/async.ts
11
11
  var DEFAULT_TIMEOUT_MS = 3e4;
@@ -1706,7 +1706,6 @@ function identityFromIdToken(idToken) {
1706
1706
  // src/providers/codex/billing.ts
1707
1707
  var USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
1708
1708
  var RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
1709
- var CREDIT_USD_RATE = 0.04;
1710
1709
  function chatGptPlanLabel(planType) {
1711
1710
  if (typeof planType !== "string" || !planType.trim()) return null;
1712
1711
  const p = planType.trim().toLowerCase();
@@ -1830,11 +1829,13 @@ function appendWindowMetrics(metrics, rl, headerPct) {
1830
1829
  metrics.push(percentMetric(label, used, resetFrom2(item)));
1831
1830
  }
1832
1831
  }
1833
- function appendCredits(metrics, source) {
1832
+ function creditBalanceMetric(source) {
1834
1833
  const balance = numberValue(source?.credits?.balance ?? source?.credit_balance);
1835
- if (balance !== void 0 && balance >= 0) {
1836
- metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
1837
- }
1834
+ return balance !== void 0 && balance >= 0 ? { key: "credits", role: "unbounded", label: "Credits", used: balance, limit: null, format: { kind: "count", suffix: "available" } } : null;
1835
+ }
1836
+ function appendCredits(metrics, source) {
1837
+ const metric = creditBalanceMetric(source);
1838
+ if (metric) metrics.push(metric);
1838
1839
  }
1839
1840
  async function fetchResetCredits(headers) {
1840
1841
  try {
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-UYPDMVW5.js";
6
6
  import {
7
7
  cacheDir
8
- } from "./chunk-HNWEJ6MS.js";
8
+ } from "./chunk-O7ORALKA.js";
9
9
 
10
10
  // src/web/lockfile.ts
11
11
  import { closeSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
@@ -289,6 +289,7 @@ export {
289
289
  reclaimUnhealthyLock,
290
290
  reclaimAbandonedLock,
291
291
  isAlive,
292
+ probeHealth,
292
293
  verifyLock,
293
294
  retireIncompatibleCliOwner
294
295
  };
@@ -503,6 +503,7 @@ function moveProviderSelection(values, providerId, direction) {
503
503
  var DEFAULT_TRAY_CONFIG = {
504
504
  enabled: true,
505
505
  showMenuBarText: true,
506
+ menuBarValue: "usage",
506
507
  displayMetric: "smartHeadroom",
507
508
  pollIntervalSec: 30,
508
509
  activeTimeoutMin: 10,
@@ -821,6 +822,7 @@ function repairConfig(input) {
821
822
  const tray = {
822
823
  enabled: typeof rawTray.enabled === "boolean" ? rawTray.enabled : DEFAULT_TRAY_CONFIG.enabled,
823
824
  showMenuBarText: typeof rawTray.showMenuBarText === "boolean" ? rawTray.showMenuBarText : DEFAULT_TRAY_CONFIG.showMenuBarText,
825
+ menuBarValue: rawTray.menuBarValue === "todayTokens" || rawTray.menuBarValue === "usage" ? rawTray.menuBarValue : DEFAULT_TRAY_CONFIG.menuBarValue,
824
826
  displayMetric: rawTray.displayMetric === "tightestRemaining" || rawTray.displayMetric === "smartHeadroom" ? rawTray.displayMetric : DEFAULT_TRAY_CONFIG.displayMetric,
825
827
  pollIntervalSec: finiteInRange(rawTray.pollIntervalSec, DEFAULT_TRAY_CONFIG.pollIntervalSec, 1, 86400),
826
828
  activeTimeoutMin: finiteInRange(rawTray.activeTimeoutMin, DEFAULT_TRAY_CONFIG.activeTimeoutMin, 1, 1440),
@@ -4,10 +4,10 @@ import {
4
4
  TOKMON_WS_METHODS,
5
5
  TOKMON_WS_PATH,
6
6
  TokmonRpcGroup
7
- } from "./chunk-JZSZEHVD.js";
7
+ } from "./chunk-UON47FYF.js";
8
8
  import {
9
9
  normalizeConfig
10
- } from "./chunk-HNWEJ6MS.js";
10
+ } from "./chunk-O7ORALKA.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";
@@ -4,15 +4,15 @@ import {
4
4
  DESKTOP_GRAPH_RANGES,
5
5
  PROVIDER_IDS,
6
6
  THEME_PRESET_IDS
7
- } from "./chunk-HNWEJ6MS.js";
7
+ } from "./chunk-O7ORALKA.js";
8
8
 
9
9
  // src/rpc/contract.ts
10
10
  import { Schema } from "effect";
11
11
  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
- var TOKMON_PROTOCOL_VERSION = 3;
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"];
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"];
16
16
  var TOKMON_WS_METHODS = {
17
17
  getConfig: "tokmon.getConfig",
18
18
  setConfig: "tokmon.setConfig",
@@ -41,6 +41,7 @@ var AccountSchema = Schema.Struct({
41
41
  var TrayConfigSchema = Schema.Struct({
42
42
  enabled: Schema.Boolean,
43
43
  showMenuBarText: Schema.Boolean,
44
+ menuBarValue: Schema.optionalKey(Schema.Literals(["usage", "todayTokens"])),
44
45
  displayMetric: Schema.Literals(["smartHeadroom", "tightestRemaining"]),
45
46
  pollIntervalSec: Schema.Finite.check(Schema.isBetween({ minimum: 1, maximum: 86400 })),
46
47
  activeTimeoutMin: Schema.Finite.check(Schema.isBetween({ minimum: 1, maximum: 1440 })),
@@ -271,7 +272,14 @@ var QuotaViewSchema = Schema.Struct({
271
272
  primary: Schema.Boolean,
272
273
  active: Schema.Boolean,
273
274
  displayOrder: NonNegativeIntegerSchema,
274
- valueText: Schema.String
275
+ valueText: Schema.String,
276
+ value: Schema.optionalKey(Schema.Struct({
277
+ kind: Schema.Literal("money"),
278
+ used: Schema.Finite,
279
+ limit: Schema.NullOr(Schema.Finite),
280
+ remaining: Schema.NullOr(Schema.Finite),
281
+ currency: Schema.String
282
+ }))
275
283
  });
276
284
  var HeadroomFactorSchema = Schema.Struct({
277
285
  key: Schema.String,
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  attachOrSpawn
4
- } from "./chunk-FLANGRI6.js";
5
- import "./chunk-5B3XCEMC.js";
4
+ } from "./chunk-23NB5Z6V.js";
5
+ import "./chunk-MMAKKTCU.js";
6
6
  import "./chunk-UYPDMVW5.js";
7
7
  import {
8
8
  createDaemonRpcClient
9
- } from "./chunk-7CH6S7BR.js";
9
+ } from "./chunk-RY24UHMK.js";
10
10
  import {
11
11
  PROVIDERS,
12
12
  antigravityStateDb,
@@ -26,13 +26,13 @@ import {
26
26
  startOfMonth,
27
27
  startOfWeek,
28
28
  withTimeout
29
- } from "./chunk-CFKUZCMB.js";
30
- import "./chunk-JZSZEHVD.js";
29
+ } from "./chunk-DESEB73E.js";
30
+ import "./chunk-UON47FYF.js";
31
31
  import {
32
32
  DESKTOP_GRAPH_RANGES,
33
33
  PROVIDER_IDS,
34
34
  configLocation
35
- } from "./chunk-HNWEJ6MS.js";
35
+ } from "./chunk-O7ORALKA.js";
36
36
 
37
37
  // src/provider-locations.ts
38
38
  import { access } from "fs/promises";
@@ -356,6 +356,7 @@ Settings:
356
356
  privacy-key <char>
357
357
  menu-bar-pins <ids|none> Comma-separated provider ids; at most 2
358
358
  menu-bar-text <on|off>
359
+ menu-bar-value <usage|tokens-today>
359
360
  summary-mode <smart|tightest>
360
361
  expanded-providers <ids|none> Comma-separated provider ids
361
362
  active-window <minutes> 1..1440
@@ -457,7 +458,7 @@ var delay = (ms) => new Promise((resolve) => {
457
458
  });
458
459
  async function connectDaemonConfig(timeoutMs) {
459
460
  const handle = await attachOrSpawn({ timeoutMs });
460
- if (handle.kind !== "spawned" || !handle.baseUrl) throw new Error("tokmon daemon is unavailable");
461
+ if (handle.kind !== "spawned" || !handle.baseUrl) throw new Error(handle.issue?.message ?? "tokmon daemon is unavailable");
461
462
  return createDaemonRpcClient(handle.baseUrl, {
462
463
  transport: "node",
463
464
  reconnectAttempts: 2,
@@ -466,7 +467,7 @@ async function connectDaemonConfig(timeoutMs) {
466
467
  }
467
468
  async function fetchDaemonSnapshot(timeoutMs, refresh) {
468
469
  const handle = await attachOrSpawn({ timeoutMs });
469
- if (handle.kind !== "spawned" || !handle.baseUrl) throw new Error("tokmon daemon is unavailable");
470
+ if (handle.kind !== "spawned" || !handle.baseUrl) throw new Error(handle.issue?.message ?? "tokmon daemon is unavailable");
470
471
  const deadline = Date.now() + timeoutMs;
471
472
  if (refresh) {
472
473
  const client = createDaemonRpcClient(handle.baseUrl, {
@@ -514,6 +515,7 @@ var CONFIG_SETTINGS = [
514
515
  "privacy-key",
515
516
  "menu-bar-pins",
516
517
  "menu-bar-text",
518
+ "menu-bar-value",
517
519
  "summary-mode",
518
520
  "expanded-providers",
519
521
  "active-window",
@@ -529,6 +531,7 @@ function configReport(config) {
529
531
  privacyKey: config.privacyToggleKey,
530
532
  menuBarPins: [...config.tray.pinnedProviders],
531
533
  menuBarText: config.tray.showMenuBarText ? "on" : "off",
534
+ menuBarValue: config.tray.menuBarValue === "todayTokens" ? "tokens-today" : "usage",
532
535
  summaryMode: config.tray.displayMetric === "smartHeadroom" ? "smart" : "tightest",
533
536
  expandedProviders: [...config.desktop.expandedProviders],
534
537
  activeWindowMinutes: config.tray.activeTimeoutMin,
@@ -545,6 +548,7 @@ function formatConfigReport(report) {
545
548
  `privacy-key ${report.privacyKey}`,
546
549
  `menu-bar-pins ${list(report.menuBarPins)}`,
547
550
  `menu-bar-text ${report.menuBarText}`,
551
+ `menu-bar-value ${report.menuBarValue}`,
548
552
  `summary-mode ${report.summaryMode}`,
549
553
  `expanded-providers ${list(report.expandedProviders)}`,
550
554
  `active-window ${report.activeWindowMinutes}m`,
@@ -595,6 +599,11 @@ function settingMutation(setting, value) {
595
599
  const enabled2 = onOff(value, setting);
596
600
  return { mutate: (config) => ({ ...config, tray: { ...config.tray, showMenuBarText: enabled2 } }), display: value };
597
601
  }
602
+ if (setting === "menu-bar-value") {
603
+ if (value !== "usage" && value !== "tokens-today") throw new Error("menu-bar-value must be usage or tokens-today");
604
+ const menuBarValue = value === "tokens-today" ? "todayTokens" : "usage";
605
+ return { mutate: (config) => ({ ...config, tray: { ...config.tray, menuBarValue } }), display: value };
606
+ }
598
607
  if (setting === "summary-mode") {
599
608
  if (value !== "smart" && value !== "tightest") throw new Error("summary-mode must be smart or tightest");
600
609
  const displayMetric = value === "smart" ? "smartHeadroom" : "tightestRemaining";
package/dist/cli.js CHANGED
@@ -41,7 +41,7 @@ function validateServeArgs(serveArgs) {
41
41
  }
42
42
  async function main() {
43
43
  if (subcommand && ["usage", "models", "query", "providers", "snapshot", "config"].includes(subcommand)) {
44
- const { runQueryCommand } = await import("./cli-command-I4GRR3MF.js");
44
+ const { runQueryCommand } = await import("./cli-command-MW6EBLNA.js");
45
45
  try {
46
46
  const output = await runQueryCommand(
47
47
  subcommand,
@@ -60,14 +60,14 @@ Run tokmon ${subcommand} --help for usage.
60
60
  return;
61
61
  }
62
62
  if (subcommand === "__daemon") {
63
- const { runDaemon } = await import("./daemon-ZXV3V5CS.js");
63
+ const { runDaemon } = await import("./daemon-XB6ZORTE.js");
64
64
  await runDaemon(args.slice(1), { foreground: false });
65
65
  process.exitCode ??= 0;
66
66
  return;
67
67
  }
68
68
  if (subcommand === "serve" || subcommand === "web") {
69
69
  validateServeArgs(args.slice(1));
70
- const { runDaemon } = await import("./daemon-ZXV3V5CS.js");
70
+ const { runDaemon } = await import("./daemon-XB6ZORTE.js");
71
71
  await runDaemon(args.slice(1), { foreground: true });
72
72
  process.exitCode ??= 0;
73
73
  return;
@@ -115,9 +115,9 @@ Run tokmon ${subcommand} --help for usage.
115
115
  return;
116
116
  }
117
117
  }
118
- const { loadConfig } = await import("./config-3DGZ47F7.js");
118
+ const { loadConfig } = await import("./config-JEDXAKPZ.js");
119
119
  const { resolveGlyphs, setGlyphs } = await import("./glyphs-NKCSZLGO.js");
120
- const { attachOrSpawn } = await import("./daemon-handle-G4OGPFR3.js");
120
+ const { attachOrSpawn } = await import("./daemon-handle-4WDYZBVL.js");
121
121
  const config = await loadConfig();
122
122
  setGlyphs(resolveGlyphs({
123
123
  flag: asciiFlag,
@@ -128,7 +128,7 @@ Run tokmon ${subcommand} --help for usage.
128
128
  }));
129
129
  const daemon = await attachOrSpawn();
130
130
  const mode = daemon.kind === "spawned" ? "connected" : "degraded";
131
- const { bootstrapInk } = await import("./bootstrap-ink-WG4WG3R2.js");
131
+ const { bootstrapInk } = await import("./bootstrap-ink-PYYVQAYP.js");
132
132
  await bootstrapInk({ interval, config, daemon, mode });
133
133
  }
134
134
  void main().catch((error) => {
@@ -36,7 +36,7 @@ import {
36
36
  slugify,
37
37
  snapshotCacheFile,
38
38
  toggleProviderSelection
39
- } from "./chunk-HNWEJ6MS.js";
39
+ } from "./chunk-O7ORALKA.js";
40
40
  export {
41
41
  ACCENT_COLORS,
42
42
  COLOR_PALETTE,
@@ -12,25 +12,25 @@ import {
12
12
  unlinkLock,
13
13
  verifyLock,
14
14
  writeLock
15
- } from "./chunk-5B3XCEMC.js";
15
+ } from "./chunk-MMAKKTCU.js";
16
16
  import {
17
17
  appVersion,
18
18
  startWebServer
19
- } from "./chunk-P57DVFNH.js";
19
+ } from "./chunk-6E2OJCIQ.js";
20
20
  import {
21
21
  resolveDaemonChannel
22
22
  } from "./chunk-UYPDMVW5.js";
23
- import "./chunk-TN6V7XSL.js";
23
+ import "./chunk-BZFDTIFV.js";
24
24
  import {
25
25
  flushDisk
26
- } from "./chunk-CFKUZCMB.js";
26
+ } from "./chunk-DESEB73E.js";
27
27
  import {
28
28
  TOKMON_CAPABILITIES,
29
29
  TOKMON_PROTOCOL_VERSION
30
- } from "./chunk-JZSZEHVD.js";
30
+ } from "./chunk-UON47FYF.js";
31
31
  import {
32
32
  loadConfig
33
- } from "./chunk-HNWEJ6MS.js";
33
+ } from "./chunk-O7ORALKA.js";
34
34
 
35
35
  // src/web/daemon-controller.ts
36
36
  import { randomBytes } from "crypto";
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ attachOrSpawn
4
+ } from "./chunk-23NB5Z6V.js";
5
+ import "./chunk-MMAKKTCU.js";
6
+ import "./chunk-UYPDMVW5.js";
7
+ import "./chunk-UON47FYF.js";
8
+ import "./chunk-O7ORALKA.js";
9
+ export {
10
+ attachOrSpawn
11
+ };