tokmon 0.24.0 → 0.25.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.
- package/README.md +5 -3
- package/dist/{bootstrap-ink-HL2BYNW3.js → bootstrap-ink-O55MPGXJ.js} +42 -39
- package/dist/{chunk-42H7R376.js → chunk-45YYP2FJ.js} +4 -3
- package/dist/{chunk-W2WGBXZG.js → chunk-AMD4PXDG.js} +1 -1
- package/dist/{chunk-5CWOJMAH.js → chunk-E2YXYU73.js} +2 -0
- package/dist/{chunk-O27I2XFN.js → chunk-INBZLHJQ.js} +34 -15
- package/dist/{chunk-REO4O5VE.js → chunk-IOIAXKW2.js} +33 -13
- package/dist/{chunk-3TJVFKXV.js → chunk-QM5E5RJZ.js} +1 -1
- package/dist/{chunk-45ZP755H.js → chunk-QUGH2XA6.js} +1 -1
- package/dist/{cli-command-TLGIGF63.js → cli-command-YQ5T4R73.js} +5 -5
- package/dist/cli.js +6 -6
- package/dist/{config-QC5QSP3G.js → config-2PXUENQL.js} +1 -1
- package/dist/{daemon-F4EGY77J.js → daemon-QBOXJFRA.js} +5 -5
- package/dist/{daemon-handle-NLCLYWFU.js → daemon-handle-2GVZT55B.js} +3 -3
- package/dist/server-23ERUOJ7.js +11 -0
- package/dist/web/assets/{Area-DCES17aZ.js → Area-DbVPPS7M.js} +1 -1
- package/dist/web/assets/{analytics-DDW6zcnj.js → analytics-DS5p9Duq.js} +2 -2
- package/dist/web/assets/{breakdown-D5LPPtqh.js → breakdown-zYF0TXN5.js} +1 -1
- package/dist/web/assets/{chart-bHY7MQV0.js → chart-BZHliIXk.js} +1 -1
- package/dist/web/assets/{explore-DdWB-Pu7.js → explore-DYv8bb2r.js} +1 -1
- package/dist/web/assets/{index-C9jxb2o3.js → index-BO8m4dq1.js} +24 -24
- package/dist/web/assets/index-NnKaHxPO.css +1 -0
- package/dist/web/assets/{models-o1wJnG6w.js → models-P3OwakgO.js} +2 -2
- package/dist/web/assets/overview-5WswYjWK.js +2 -0
- package/dist/web/assets/{panel-DBnK1oy0.js → panel-CpwRWsNg.js} +1 -1
- package/dist/web/assets/{primitives-CBmteetD.js → primitives-Do0zWLZ4.js} +1 -1
- package/dist/web/assets/settings-sheet-uD6-A3tV.js +1 -0
- package/dist/web/assets/{share-sheet-DUee3FlK.js → share-sheet-CimegH2K.js} +2 -2
- package/dist/web/assets/{timeline-BxHwOJA_.js → timeline-Ch94Yv4z.js} +1 -1
- package/dist/web/assets/{use-dialog-trap-30EqxYF-.js → use-dialog-trap-LxEFLutK.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/server-X63FGHIZ.js +0 -11
- package/dist/web/assets/index-m0KfWnN2.css +0 -1
- package/dist/web/assets/overview-wq2uAEk4.js +0 -2
- package/dist/web/assets/settings-sheet-CZ6GEPl3.js +0 -1
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ A responsive grid of provider cards (or one card at a time — see **Dashboard l
|
|
|
76
76
|
- **Today / This Week / This Month** — cost and token summaries
|
|
77
77
|
- **Burn rate** — current $/hr
|
|
78
78
|
- **Cache saved** — what caching has saved you
|
|
79
|
-
- **Rate limits** — live utilization bars with reset countdowns
|
|
79
|
+
- **Rate limits** — live utilization bars with reset countdowns or exact reset dates
|
|
80
80
|
- **Sparkline** — recent daily activity
|
|
81
81
|
|
|
82
82
|
The grid reflows to fit your terminal — more columns when it's wide, compacting cards when it's short. With more providers than fit on screen, it splits into **pages**; **scroll** (mouse wheel) to move between them (or `↑`/`↓` / `[` `]`). When you track more than one account, a focus strip lets you view **All** together or zoom into a single account.
|
|
@@ -105,7 +105,7 @@ tokmon serve --port 8080
|
|
|
105
105
|
tokmon serve --no-open # don't auto-open the browser
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
It binds to `127.0.0.1` by default and reads the same local daemon state — nothing leaves your machine. The browser connects directly over a same-origin WebSocket; dashboard URLs have no token or login step. Optional LAN access can be enabled in settings, with an explicit unsafe-access warning, and takes effect after the daemon restarts. The dashboard renders instantly from a cached snapshot, then streams live updates and goes idle when no tab is open. Filter by provider, model, account, and period, flip between dark and light, and export any panel — or a summary card — as a PNG with the **Share** button.
|
|
108
|
+
It binds to `127.0.0.1` by default and reads the same local daemon state — nothing leaves your machine. The browser connects directly over a same-origin WebSocket; dashboard URLs have no token or login step. Optional LAN access can be enabled in settings, with an explicit unsafe-access warning, and takes effect after the daemon restarts. The dashboard renders instantly from a cached snapshot, then streams live updates and goes idle when no tab is open. Press `R` or use the visible **Refresh** control to update it. Filter by provider, model, account, and period, flip between dark and light, and export any panel — or a summary card — as a PNG with the **Share** button.
|
|
109
109
|
|
|
110
110
|
### Overview
|
|
111
111
|
|
|
@@ -192,6 +192,8 @@ Press `s` to open.
|
|
|
192
192
|
- **Dashboard layout** — `grid` (all providers at once) or `single` (one at a time)
|
|
193
193
|
- **Default focus** — start on `all`, or remember your `last` focused account
|
|
194
194
|
- **ASCII glyphs** — `auto` (detect), `on` (force ASCII), or `off` (force Unicode)
|
|
195
|
+
- **Network access** — loopback-only by default; optional LAN access is explicitly marked unsafe
|
|
196
|
+
- **Reset times** — show time remaining or the exact reset date/time in the configured timezone
|
|
195
197
|
|
|
196
198
|
**Providers** — toggle each provider on or off.
|
|
197
199
|
|
|
@@ -253,7 +255,7 @@ tokmon runs a small local **daemon** that does all the data collection. The term
|
|
|
253
255
|
- Each enabled provider is detected automatically, and its real account identity — email and plan — is read from local auth (e.g. Claude `~/.claude.json`, the Codex `id_token`, Cursor's state DB). Extra accounts, like additional Claude homes, are auto-discovered too.
|
|
254
256
|
|
|
255
257
|
**Limits & billing**
|
|
256
|
-
- Rate limits and remaining spend/quota come from each provider's own official API
|
|
258
|
+
- Rate limits and remaining spend/quota come from each provider's own official API. Tokmon refreshes them on the configured billing interval; terminal focus and extra dashboard viewers reuse fresh data instead of issuing extra provider requests.
|
|
257
259
|
|
|
258
260
|
**Responsiveness**
|
|
259
261
|
- Dashboard summaries and table history load independently and refresh on separate intervals, so the UI stays responsive even on large histories.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createDaemonRpcClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QUGH2XA6.js";
|
|
5
5
|
import {
|
|
6
6
|
accountsByProvider,
|
|
7
7
|
buildAccounts,
|
|
8
8
|
fetchPeak,
|
|
9
9
|
modelColor
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-45YYP2FJ.js";
|
|
11
11
|
import {
|
|
12
12
|
PROVIDERS,
|
|
13
13
|
PROVIDER_ORDER,
|
|
@@ -15,13 +15,15 @@ import {
|
|
|
15
15
|
col,
|
|
16
16
|
currency,
|
|
17
17
|
detectProviders,
|
|
18
|
+
formatResetAt,
|
|
19
|
+
resetAt,
|
|
18
20
|
resolveTimezone,
|
|
19
21
|
shortDate,
|
|
20
22
|
systemTimezone,
|
|
21
23
|
time,
|
|
22
24
|
tokens,
|
|
23
25
|
withTimeout
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-INBZLHJQ.js";
|
|
25
27
|
import {
|
|
26
28
|
COLOR_PALETTE,
|
|
27
29
|
DEFAULTS,
|
|
@@ -36,7 +38,7 @@ import {
|
|
|
36
38
|
sanitizeTyped,
|
|
37
39
|
saveConfigSync,
|
|
38
40
|
snapshotCacheFile
|
|
39
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-E2YXYU73.js";
|
|
40
42
|
import {
|
|
41
43
|
glyphs
|
|
42
44
|
} from "./chunk-RF4GGQGM.js";
|
|
@@ -161,27 +163,22 @@ var TabBar = memo(function TabBar2({ tabs, active, onSelect }) {
|
|
|
161
163
|
" "
|
|
162
164
|
] }) }, t)) });
|
|
163
165
|
});
|
|
164
|
-
var PeakBadge = memo(function PeakBadge2({ peak }) {
|
|
166
|
+
var PeakBadge = memo(function PeakBadge2({ peak, resetDisplay = "relative", tz }) {
|
|
165
167
|
const color = peak.state === "peak" ? "red" : "green";
|
|
168
|
+
const changesAt = peak.changesAt ?? (peak.minutesUntilChange !== null ? new Date(Date.now() + peak.minutesUntilChange * 6e4).toISOString() : null);
|
|
166
169
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
167
170
|
/* @__PURE__ */ jsxs(Text, { color, children: [
|
|
168
171
|
glyphs().dot,
|
|
169
172
|
" "
|
|
170
173
|
] }),
|
|
171
174
|
/* @__PURE__ */ jsx(Text, { bold: true, color, children: peak.label }),
|
|
172
|
-
|
|
175
|
+
changesAt && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
173
176
|
" (",
|
|
174
|
-
|
|
177
|
+
formatResetAt(changesAt, resetDisplay, Date.now(), tz),
|
|
175
178
|
")"
|
|
176
179
|
] })
|
|
177
180
|
] });
|
|
178
181
|
});
|
|
179
|
-
function fmtMinutes(mins) {
|
|
180
|
-
if (mins < 60) return `${mins}m`;
|
|
181
|
-
const h = Math.floor(mins / 60);
|
|
182
|
-
const m = mins % 60;
|
|
183
|
-
return m === 0 ? `${h}h` : `${h}h ${m}m`;
|
|
184
|
-
}
|
|
185
182
|
function currencySymbol(cur) {
|
|
186
183
|
return cur === "EUR" ? glyphs().eur : cur === "GBP" ? glyphs().gbp : "$";
|
|
187
184
|
}
|
|
@@ -292,7 +289,7 @@ function computeDashLayout(groups, stats, cols, budget, focusId, layoutPref) {
|
|
|
292
289
|
}
|
|
293
290
|
return chooseLayout(content, budget, groups.length, single, cols, heights);
|
|
294
291
|
}
|
|
295
|
-
var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget, focusId, layout, page = 0, privacyMode = false }) {
|
|
292
|
+
var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget, focusId, layout, page = 0, privacyMode = false, resetDisplay = "relative", tz }) {
|
|
296
293
|
if (groups.length === 0) {
|
|
297
294
|
return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
298
295
|
"No providers enabled ",
|
|
@@ -307,7 +304,7 @@ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget,
|
|
|
307
304
|
const pg = pageCount > 1 ? (page % pageCount + pageCount) % pageCount : 0;
|
|
308
305
|
const visible = pageCount > 1 ? groups.slice(pg * cardsPerPage, pg * cardsPerPage + cardsPerPage) : groups;
|
|
309
306
|
return /* @__PURE__ */ jsxs2(Box2, { height: budget, flexDirection: "column", overflow: "hidden", children: [
|
|
310
|
-
/* @__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 }) }, g.provider)) }),
|
|
307
|
+
/* @__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)) }),
|
|
311
308
|
pageCount > 1 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
312
309
|
" ",
|
|
313
310
|
glyphs().middot,
|
|
@@ -323,7 +320,7 @@ var DashboardView = memo2(function DashboardView2({ groups, stats, cols, budget,
|
|
|
323
320
|
] })
|
|
324
321
|
] });
|
|
325
322
|
});
|
|
326
|
-
function ProviderCard({ provider, accounts, stats, width, variant, privacyMode = false }) {
|
|
323
|
+
function ProviderCard({ provider, accounts, stats, width, variant, privacyMode = false, resetDisplay, tz }) {
|
|
327
324
|
const meta = PROVIDERS[provider];
|
|
328
325
|
const items = accounts.map((a) => ({ account: a, s: stats.get(a.id) }));
|
|
329
326
|
const dashboards = items.map((i) => i.s?.dashboard).filter((d) => !!d);
|
|
@@ -359,7 +356,7 @@ function ProviderCard({ provider, accounts, stats, width, variant, privacyMode =
|
|
|
359
356
|
] })),
|
|
360
357
|
meta.hasBilling && showBars && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
361
358
|
meta.hasUsage && /* @__PURE__ */ jsx2(Rule, { inner }),
|
|
362
|
-
/* @__PURE__ */ jsx2(LimitsBlock, { items, inner, privacyMode })
|
|
359
|
+
/* @__PURE__ */ jsx2(LimitsBlock, { items, inner, privacyMode, resetDisplay, tz })
|
|
363
360
|
] }),
|
|
364
361
|
meta.hasBilling && !showBars && !meta.hasUsage && /* @__PURE__ */ jsx2(CompactBilling, { items, privacyMode }),
|
|
365
362
|
hasSpark && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
@@ -432,11 +429,12 @@ function accountTitle(account, billing, privacyMode = false) {
|
|
|
432
429
|
const title = email && !account.name.includes("@") ? `${account.name} ${email}` : account.name;
|
|
433
430
|
return privacyMode ? redactEmail(title) : title;
|
|
434
431
|
}
|
|
435
|
-
function LimitsBlock({ items, inner, privacyMode }) {
|
|
432
|
+
function LimitsBlock({ items, inner, privacyMode, resetDisplay, tz }) {
|
|
436
433
|
const showName = items.length > 1;
|
|
437
434
|
const labels = items.flatMap((i) => i.s?.billing?.metrics ?? []).map((m) => m.label.length);
|
|
438
435
|
const labelW = Math.min(Math.max(7, ...labels) + 1, 14);
|
|
439
|
-
const
|
|
436
|
+
const resetW = resetDisplay === "absolute" ? 17 : 8;
|
|
437
|
+
const barW = Math.max(10, Math.min(46, inner - labelW - resetW - 5));
|
|
440
438
|
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: items.map(({ account, s }, idx) => {
|
|
441
439
|
const billing = s?.billing;
|
|
442
440
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: showName && idx > 0 ? 1 : 0, children: [
|
|
@@ -450,11 +448,11 @@ function LimitsBlock({ items, inner, privacyMode }) {
|
|
|
450
448
|
!billing ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
451
449
|
"Fetching",
|
|
452
450
|
glyphs().ellipsis
|
|
453
|
-
] }) : 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 }, `${m.label}${i}`))
|
|
451
|
+
] }) : 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}`))
|
|
454
452
|
] }, account.id);
|
|
455
453
|
}) });
|
|
456
454
|
}
|
|
457
|
-
function MetricRow({ m, color, barW, labelW }) {
|
|
455
|
+
function MetricRow({ m, color, barW, labelW, resetW, resetDisplay, tz }) {
|
|
458
456
|
if (m.format.kind === "percent") {
|
|
459
457
|
const barColor = m.used >= 90 ? "red" : m.used >= 75 ? "yellow" : color;
|
|
460
458
|
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
@@ -464,7 +462,7 @@ function MetricRow({ m, color, barW, labelW }) {
|
|
|
464
462
|
Math.round(m.used),
|
|
465
463
|
"%"
|
|
466
464
|
] }) }),
|
|
467
|
-
/* @__PURE__ */ jsx2(Box2, { width:
|
|
465
|
+
/* @__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: " " }) })
|
|
468
466
|
] });
|
|
469
467
|
}
|
|
470
468
|
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
@@ -532,7 +530,7 @@ import { Box as Box4, Text as Text4 } from "ink";
|
|
|
532
530
|
import { memo as memo3 } from "react";
|
|
533
531
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
534
532
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
535
|
-
var GENERAL_ROWS =
|
|
533
|
+
var GENERAL_ROWS = 10;
|
|
536
534
|
var SETTINGS_TABS = ["general", "providers", "accounts"];
|
|
537
535
|
var SETTINGS_TAB_LABELS = {
|
|
538
536
|
general: "General",
|
|
@@ -628,7 +626,18 @@ var SettingsView = memo3(function SettingsView2({
|
|
|
628
626
|
] })
|
|
629
627
|
] }),
|
|
630
628
|
/* @__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" }) }),
|
|
631
|
-
config.allowNetworkAccess && /* @__PURE__ */ jsx3(Text3, { color: "red", children: " Warning: dashboard data and settings will be exposed to your local network after daemon restart." })
|
|
629
|
+
config.allowNetworkAccess && /* @__PURE__ */ jsx3(Text3, { color: "red", children: " Warning: dashboard data and settings will be exposed to your local network after daemon restart." }),
|
|
630
|
+
/* @__PURE__ */ jsxs3(Row, { cursor, idx: 9, label: "Reset times", children: [
|
|
631
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
632
|
+
glyphs().caretL,
|
|
633
|
+
" "
|
|
634
|
+
] }),
|
|
635
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: config.resetDisplay === "relative" ? "time remaining" : "exact date/time" }),
|
|
636
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
637
|
+
" ",
|
|
638
|
+
glyphs().caretR
|
|
639
|
+
] })
|
|
640
|
+
] })
|
|
632
641
|
] }),
|
|
633
642
|
activeTab === "providers" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
634
643
|
/* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "Providers" }),
|
|
@@ -2123,6 +2132,10 @@ function handleSettings(input, key, ctx) {
|
|
|
2123
2132
|
}
|
|
2124
2133
|
if (index === 8 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2125
2134
|
updateConfig((current) => ({ ...current, allowNetworkAccess: !current.allowNetworkAccess }));
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
if (index === 9 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2138
|
+
updateConfig((current) => ({ ...current, resetDisplay: current.resetDisplay === "relative" ? "absolute" : "relative" }));
|
|
2126
2139
|
}
|
|
2127
2140
|
}
|
|
2128
2141
|
}
|
|
@@ -2278,10 +2291,9 @@ function terminalFocusEvent(input) {
|
|
|
2278
2291
|
if (input === "[O") return "out";
|
|
2279
2292
|
return null;
|
|
2280
2293
|
}
|
|
2281
|
-
function handleTerminalFocusInput(input
|
|
2294
|
+
function handleTerminalFocusInput(input) {
|
|
2282
2295
|
const event = terminalFocusEvent(input);
|
|
2283
2296
|
if (!event) return false;
|
|
2284
|
-
if (event === "in") onFocusIn();
|
|
2285
2297
|
return true;
|
|
2286
2298
|
}
|
|
2287
2299
|
function isRefreshAllShortcut(input, mode) {
|
|
@@ -3389,7 +3401,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3389
3401
|
if (webStartingRef.current) return;
|
|
3390
3402
|
webStartingRef.current = true;
|
|
3391
3403
|
try {
|
|
3392
|
-
const { startWebServer } = await import("./server-
|
|
3404
|
+
const { startWebServer } = await import("./server-23ERUOJ7.js");
|
|
3393
3405
|
const ctrl = await startWebServer({ config: cfg, log: false });
|
|
3394
3406
|
webRef.current = ctrl;
|
|
3395
3407
|
openUrl(ctrl.url);
|
|
@@ -3436,17 +3448,8 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3436
3448
|
() => tab === 1 ? sortRows(filterTokenRows(filterRowsByModel(rawTokenRows, activeTableModel), search), sort) : [],
|
|
3437
3449
|
[tab, rawTokenRows, activeTableModel, search, sort]
|
|
3438
3450
|
);
|
|
3439
|
-
const lastFocusRefreshRef = useRef8(Date.now());
|
|
3440
|
-
const refreshOnFocus = useCallback7(() => {
|
|
3441
|
-
const now = Date.now();
|
|
3442
|
-
if (now - lastFocusRefreshRef.current < 3e4) return;
|
|
3443
|
-
lastFocusRefreshRef.current = now;
|
|
3444
|
-
const task = connected ? daemonRefreshRef.current("billing") : degradedRefreshRef.current();
|
|
3445
|
-
void task.catch(() => {
|
|
3446
|
-
});
|
|
3447
|
-
}, [connected]);
|
|
3448
3451
|
useInput((input, key) => {
|
|
3449
|
-
if (handleTerminalFocusInput(input
|
|
3452
|
+
if (handleTerminalFocusInput(input)) return;
|
|
3450
3453
|
handleKey(input, key, {
|
|
3451
3454
|
onboarding: {
|
|
3452
3455
|
show: showPicker,
|
|
@@ -3568,7 +3571,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3568
3571
|
] }),
|
|
3569
3572
|
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
3570
3573
|
peak && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3571
|
-
/* @__PURE__ */ jsx12(PeakBadge, { peak }),
|
|
3574
|
+
/* @__PURE__ */ jsx12(PeakBadge, { peak, resetDisplay: cfg.resetDisplay, tz }),
|
|
3572
3575
|
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3573
3576
|
" ",
|
|
3574
3577
|
glyphs().middot,
|
|
@@ -3627,7 +3630,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "deg
|
|
|
3627
3630
|
] })
|
|
3628
3631
|
] }),
|
|
3629
3632
|
tab === 0 && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3630
|
-
/* @__PURE__ */ jsx12(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage, privacyMode: cfg.privacyMode }),
|
|
3633
|
+
/* @__PURE__ */ jsx12(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage, privacyMode: cfg.privacyMode, resetDisplay: cfg.resetDisplay, tz }),
|
|
3631
3634
|
slots.length > 1 && /* @__PURE__ */ jsxs12(Box11, { marginTop: 1, children: [
|
|
3632
3635
|
/* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "focus " }),
|
|
3633
3636
|
/* @__PURE__ */ jsx12(
|
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
identityFromIdToken,
|
|
6
6
|
readClaudeIdentity,
|
|
7
7
|
readJson
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-INBZLHJQ.js";
|
|
9
9
|
import {
|
|
10
10
|
expandHome,
|
|
11
11
|
slugify
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-E2YXYU73.js";
|
|
13
13
|
|
|
14
14
|
// src/accounts.ts
|
|
15
15
|
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
@@ -281,7 +281,8 @@ async function fetchPeak() {
|
|
|
281
281
|
return {
|
|
282
282
|
state,
|
|
283
283
|
label: state === "peak" ? "Peak" : state === "weekend" ? "Weekend" : "Off-Peak",
|
|
284
|
-
minutesUntilChange
|
|
284
|
+
minutesUntilChange,
|
|
285
|
+
changesAt: minutesUntilChange !== null ? new Date(Date.now() + minutesUntilChange * 6e4).toISOString() : null
|
|
285
286
|
};
|
|
286
287
|
} catch {
|
|
287
288
|
return null;
|
|
@@ -26,6 +26,7 @@ var DEFAULTS = {
|
|
|
26
26
|
defaultFocus: "all",
|
|
27
27
|
ascii: "auto",
|
|
28
28
|
allowNetworkAccess: false,
|
|
29
|
+
resetDisplay: "relative",
|
|
29
30
|
knownProviders: []
|
|
30
31
|
};
|
|
31
32
|
var LEGACY_KNOWN = ["claude", "codex", "cursor"];
|
|
@@ -216,6 +217,7 @@ function repairConfig(input) {
|
|
|
216
217
|
defaultFocus: parsed.defaultFocus === "last" ? "last" : "all",
|
|
217
218
|
ascii: parsed.ascii === "on" ? "on" : parsed.ascii === "off" ? "off" : "auto",
|
|
218
219
|
allowNetworkAccess: parsed.allowNetworkAccess === true,
|
|
220
|
+
resetDisplay: parsed.resetDisplay === "absolute" ? "absolute" : "relative",
|
|
219
221
|
knownProviders
|
|
220
222
|
};
|
|
221
223
|
for (const key of Object.keys(DEFAULTS)) {
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
envDir,
|
|
6
6
|
expandHome,
|
|
7
7
|
isValidTimezone
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-E2YXYU73.js";
|
|
9
9
|
|
|
10
10
|
// src/async.ts
|
|
11
11
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -786,6 +786,7 @@ var ConfigSchema = Schema.Struct({
|
|
|
786
786
|
defaultFocus: Schema.Literals(["all", "last"]),
|
|
787
787
|
ascii: Schema.Literals(["auto", "on", "off"]),
|
|
788
788
|
allowNetworkAccess: Schema.Boolean,
|
|
789
|
+
resetDisplay: Schema.Literals(["relative", "absolute"]),
|
|
789
790
|
knownProviders: Schema.Array(ProviderIdSchema)
|
|
790
791
|
});
|
|
791
792
|
var ProtocolInfoSchema = Schema.Struct({
|
|
@@ -930,7 +931,8 @@ var WebSnapshotSchema = Schema.Struct({
|
|
|
930
931
|
label: Schema.String,
|
|
931
932
|
// The upstream clock is only normalized to a finite number; do not make
|
|
932
933
|
// the wire contract narrower than the producer's declared type.
|
|
933
|
-
minutesUntilChange: Schema.NullOr(Schema.Finite)
|
|
934
|
+
minutesUntilChange: Schema.NullOr(Schema.Finite),
|
|
935
|
+
changesAt: Schema.optionalKey(Schema.NullOr(Schema.String))
|
|
934
936
|
}))
|
|
935
937
|
});
|
|
936
938
|
var EmptyPayloadSchema = Schema.Struct({});
|
|
@@ -1017,6 +1019,18 @@ function formatResetIn(iso, now = Date.now()) {
|
|
|
1017
1019
|
const hrs = hours % 24;
|
|
1018
1020
|
return `${days}d ${hrs}h`;
|
|
1019
1021
|
}
|
|
1022
|
+
function formatResetAt(iso, display, now = Date.now(), tz) {
|
|
1023
|
+
const timestamp = Date.parse(iso);
|
|
1024
|
+
if (!Number.isFinite(timestamp)) return iso;
|
|
1025
|
+
if (display === "relative") return formatResetIn(iso, now);
|
|
1026
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
1027
|
+
month: "short",
|
|
1028
|
+
day: "numeric",
|
|
1029
|
+
hour: "numeric",
|
|
1030
|
+
minute: "2-digit",
|
|
1031
|
+
timeZone: tz
|
|
1032
|
+
}).format(new Date(timestamp));
|
|
1033
|
+
}
|
|
1020
1034
|
|
|
1021
1035
|
// src/format.ts
|
|
1022
1036
|
var currency = formatCurrency;
|
|
@@ -1030,7 +1044,7 @@ function col(s, w, align = "right") {
|
|
|
1030
1044
|
const spaces = " ".repeat(w - s.length);
|
|
1031
1045
|
return align === "right" ? spaces + s : s + spaces;
|
|
1032
1046
|
}
|
|
1033
|
-
var
|
|
1047
|
+
var resetAt = formatResetAt;
|
|
1034
1048
|
|
|
1035
1049
|
// src/providers/claude/usage.ts
|
|
1036
1050
|
import { stat as fsStat, access } from "fs/promises";
|
|
@@ -1432,11 +1446,13 @@ function boolValue(value) {
|
|
|
1432
1446
|
return false;
|
|
1433
1447
|
}
|
|
1434
1448
|
function resetFrom(value) {
|
|
1435
|
-
if (typeof value === "string" && value.trim())
|
|
1449
|
+
if (typeof value === "string" && value.trim()) {
|
|
1450
|
+
const timestamp = Date.parse(value);
|
|
1451
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
1452
|
+
}
|
|
1436
1453
|
const n = numberValue(value);
|
|
1437
1454
|
if (n === void 0) return null;
|
|
1438
|
-
|
|
1439
|
-
return iso ? resetIn(iso) : null;
|
|
1455
|
+
return msToIso(Math.abs(n) < 1e10 ? n * 1e3 : n);
|
|
1440
1456
|
}
|
|
1441
1457
|
function usageMetric(label, window, primary) {
|
|
1442
1458
|
const used = numberValue(window?.utilization);
|
|
@@ -1989,8 +2005,7 @@ function resetDateMs(window) {
|
|
|
1989
2005
|
}
|
|
1990
2006
|
function resetFrom2(window) {
|
|
1991
2007
|
const ms = resetDateMs(window);
|
|
1992
|
-
|
|
1993
|
-
return iso ? resetIn(iso) : null;
|
|
2008
|
+
return ms === null ? null : msToIso(ms);
|
|
1994
2009
|
}
|
|
1995
2010
|
function windowSeconds(window) {
|
|
1996
2011
|
const seconds = numberValue(window?.limit_window_seconds ?? window?.period_seconds ?? window?.window_seconds);
|
|
@@ -2569,7 +2584,7 @@ async function cursorBillingCore(account) {
|
|
|
2569
2584
|
const rawEnd = usage.billingCycleEnd;
|
|
2570
2585
|
const endMs = timestampMs(rawEnd);
|
|
2571
2586
|
const iso = endMs === null ? null : msToIso(endMs);
|
|
2572
|
-
const resets = iso
|
|
2587
|
+
const resets = iso;
|
|
2573
2588
|
const limit = numberValue(pu.limit);
|
|
2574
2589
|
const planUsedCents = numberValue(pu.totalSpend) ?? (limit !== void 0 && numberValue(pu.remaining) !== void 0 ? limit - numberValue(pu.remaining) : void 0);
|
|
2575
2590
|
const computedPercent = limit !== void 0 && limit > 0 && planUsedCents !== void 0 ? planUsedCents / limit * 100 : void 0;
|
|
@@ -3118,7 +3133,9 @@ function redactToken(token) {
|
|
|
3118
3133
|
return token.length <= 4 ? "****" : `****${token.slice(-4)}`;
|
|
3119
3134
|
}
|
|
3120
3135
|
function resetDate(value) {
|
|
3121
|
-
|
|
3136
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
3137
|
+
const timestamp = Date.parse(value);
|
|
3138
|
+
return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : null;
|
|
3122
3139
|
}
|
|
3123
3140
|
function boolValue2(value) {
|
|
3124
3141
|
if (typeof value === "boolean") return value;
|
|
@@ -3625,7 +3642,7 @@ function cloudCodeBucketsToMetrics(buckets, options = {}) {
|
|
|
3625
3642
|
used: Math.round((1 - clamped) * 100),
|
|
3626
3643
|
limit: 100,
|
|
3627
3644
|
format: { kind: "percent" },
|
|
3628
|
-
resetsAt: bucket.resetTime && Number.isFinite(Date.parse(bucket.resetTime)) ?
|
|
3645
|
+
resetsAt: bucket.resetTime && Number.isFinite(Date.parse(bucket.resetTime)) ? new Date(bucket.resetTime).toISOString() : null,
|
|
3629
3646
|
primary: i === 0
|
|
3630
3647
|
};
|
|
3631
3648
|
});
|
|
@@ -4556,15 +4573,17 @@ export {
|
|
|
4556
4573
|
flushDisk,
|
|
4557
4574
|
coalesceTables,
|
|
4558
4575
|
claudeConfigDirs,
|
|
4576
|
+
readJson,
|
|
4577
|
+
readClaudeIdentity,
|
|
4578
|
+
codexHomes,
|
|
4579
|
+
identityFromIdToken,
|
|
4580
|
+
formatResetAt,
|
|
4559
4581
|
currency,
|
|
4560
4582
|
tokens,
|
|
4561
4583
|
time,
|
|
4562
4584
|
shortDate,
|
|
4563
4585
|
col,
|
|
4564
|
-
|
|
4565
|
-
readClaudeIdentity,
|
|
4566
|
-
codexHomes,
|
|
4567
|
-
identityFromIdToken,
|
|
4586
|
+
resetAt,
|
|
4568
4587
|
cursorStateDb,
|
|
4569
4588
|
piSessionsDir,
|
|
4570
4589
|
opencodeDbPaths,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
colorHex,
|
|
12
12
|
fetchPeak,
|
|
13
13
|
namedHex
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-45YYP2FJ.js";
|
|
15
15
|
import {
|
|
16
16
|
PROVIDERS,
|
|
17
17
|
TOKMON_CAPABILITIES,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
detectProviders,
|
|
23
23
|
resolveTimezone,
|
|
24
24
|
withTimeout
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-INBZLHJQ.js";
|
|
26
26
|
import {
|
|
27
27
|
cacheDir,
|
|
28
28
|
expandHome,
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
normalizeConfig,
|
|
31
31
|
saveConfig,
|
|
32
32
|
snapshotCacheFile
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-E2YXYU73.js";
|
|
34
34
|
|
|
35
35
|
// src/web/server.ts
|
|
36
36
|
import { createServer } from "http";
|
|
@@ -189,7 +189,7 @@ function deferred() {
|
|
|
189
189
|
});
|
|
190
190
|
return { promise, resolve, reject };
|
|
191
191
|
}
|
|
192
|
-
function createRefreshQueue(perform, skipAutomatic = () => false) {
|
|
192
|
+
function createRefreshQueue(perform, skipAutomatic = () => false, options = {}) {
|
|
193
193
|
let activePass = null;
|
|
194
194
|
let activeResult = null;
|
|
195
195
|
let queued = null;
|
|
@@ -226,6 +226,7 @@ function createRefreshQueue(perform, skipAutomatic = () => false) {
|
|
|
226
226
|
if (stopped || !force && skipAutomatic()) return Promise.resolve();
|
|
227
227
|
if (!activePass) return launch();
|
|
228
228
|
if (!force) return activeResult.promise;
|
|
229
|
+
if (options.forceWhileActive === "join") return activeResult.promise;
|
|
229
230
|
if (!queued) queued = deferred();
|
|
230
231
|
return queued.promise;
|
|
231
232
|
};
|
|
@@ -252,10 +253,21 @@ var IDLE_PAUSE_MS = 6e4;
|
|
|
252
253
|
var SNAPSHOT_CACHE_THROTTLE_MS = 2e4;
|
|
253
254
|
var REVEAL_THROTTLE_MS = 500;
|
|
254
255
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
255
|
-
var
|
|
256
|
-
function billingNeedsCatchUp(accounts, updatedAt, now = Date.now(), maxAgeMs =
|
|
256
|
+
var DEFAULT_BILLING_STALE_MS = 5 * 6e4;
|
|
257
|
+
function billingNeedsCatchUp(accounts, updatedAt, now = Date.now(), maxAgeMs = DEFAULT_BILLING_STALE_MS) {
|
|
257
258
|
return accounts.some(({ account }) => now - (updatedAt.get(account.id) ?? 0) >= maxAgeMs);
|
|
258
259
|
}
|
|
260
|
+
async function forEachProviderSequentially(values, task) {
|
|
261
|
+
const groups = /* @__PURE__ */ new Map();
|
|
262
|
+
for (const value of values) {
|
|
263
|
+
const group = groups.get(value.account.providerId) ?? [];
|
|
264
|
+
group.push(value);
|
|
265
|
+
groups.set(value.account.providerId, group);
|
|
266
|
+
}
|
|
267
|
+
await Promise.all([...groups.values()].map(async (group) => {
|
|
268
|
+
for (const value of group) await task(value);
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
259
271
|
function runInBackground(task) {
|
|
260
272
|
void task.catch(() => {
|
|
261
273
|
});
|
|
@@ -405,7 +417,14 @@ function createDataEngine(opts) {
|
|
|
405
417
|
}
|
|
406
418
|
reveal();
|
|
407
419
|
};
|
|
408
|
-
if (opts2.concurrent) {
|
|
420
|
+
if (opts2.concurrent === "provider") {
|
|
421
|
+
await forEachProviderSequentially(opts2.accounts(), async (r) => {
|
|
422
|
+
const result = await fetchOne(r);
|
|
423
|
+
if (stopped || epoch !== configEpoch) return;
|
|
424
|
+
applyResult(result);
|
|
425
|
+
});
|
|
426
|
+
if (stopped || epoch !== configEpoch) return;
|
|
427
|
+
} else if (opts2.concurrent) {
|
|
409
428
|
await Promise.all(opts2.accounts().map(async (r) => {
|
|
410
429
|
const result = await fetchOne(r);
|
|
411
430
|
if (stopped || epoch !== configEpoch) return;
|
|
@@ -423,7 +442,8 @@ function createDataEngine(opts) {
|
|
|
423
442
|
rebuild();
|
|
424
443
|
throwIfRefreshFailures(opts2.scope, failures);
|
|
425
444
|
},
|
|
426
|
-
idle
|
|
445
|
+
idle,
|
|
446
|
+
{ forceWhileActive: opts2.forceWhileActive }
|
|
427
447
|
);
|
|
428
448
|
const refreshSummary = makeRefreshLoop({
|
|
429
449
|
scope: "summary",
|
|
@@ -454,8 +474,10 @@ function createDataEngine(opts) {
|
|
|
454
474
|
},
|
|
455
475
|
state: billingState,
|
|
456
476
|
updatedAt: billingUpdatedAt,
|
|
457
|
-
concurrent:
|
|
477
|
+
concurrent: "provider",
|
|
478
|
+
forceWhileActive: "join"
|
|
458
479
|
});
|
|
480
|
+
const refreshBillingIfStale = () => billingNeedsCatchUp(billingAccounts, billingUpdatedAt, Date.now(), billingIntervalMs) ? refreshBilling.run(true) : Promise.resolve();
|
|
459
481
|
const refreshPeak = createRefreshQueue(
|
|
460
482
|
async () => {
|
|
461
483
|
if (stopped || !hasClaude) return;
|
|
@@ -549,7 +571,7 @@ function createDataEngine(opts) {
|
|
|
549
571
|
rebuild();
|
|
550
572
|
runInBackground(refreshSummary.run(true));
|
|
551
573
|
runInBackground(refreshTable.run(true));
|
|
552
|
-
runInBackground(
|
|
574
|
+
runInBackground(refreshBillingIfStale());
|
|
553
575
|
if (hasClaude) runInBackground(refreshPeak.run(true));
|
|
554
576
|
startTimers();
|
|
555
577
|
},
|
|
@@ -576,9 +598,7 @@ function createDataEngine(opts) {
|
|
|
576
598
|
runInBackground(refreshSummary.run(true));
|
|
577
599
|
runInBackground(refreshTable.run(true));
|
|
578
600
|
}
|
|
579
|
-
|
|
580
|
-
runInBackground(refreshBilling.run());
|
|
581
|
-
}
|
|
601
|
+
runInBackground(refreshBillingIfStale());
|
|
582
602
|
return () => {
|
|
583
603
|
snapshotSubscribers.delete(onSnapshot);
|
|
584
604
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
cacheDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-E2YXYU73.js";
|
|
5
5
|
|
|
6
6
|
// src/web/lockfile.ts
|
|
7
7
|
import { closeSync, fchmodSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
TOKMON_WS_METHODS,
|
|
4
4
|
TOKMON_WS_PATH,
|
|
5
5
|
TokmonRpcGroup
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-INBZLHJQ.js";
|
|
7
7
|
|
|
8
8
|
// src/client/daemon-rpc-client.ts
|
|
9
9
|
import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
attachOrSpawn
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-AMD4PXDG.js";
|
|
5
|
+
import "./chunk-QM5E5RJZ.js";
|
|
6
6
|
import "./chunk-SMPY52EV.js";
|
|
7
7
|
import {
|
|
8
8
|
createDaemonRpcClient
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QUGH2XA6.js";
|
|
10
10
|
import {
|
|
11
11
|
PROVIDERS,
|
|
12
12
|
antigravityStateDb,
|
|
@@ -26,11 +26,11 @@ import {
|
|
|
26
26
|
startOfMonth,
|
|
27
27
|
startOfWeek,
|
|
28
28
|
withTimeout
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-INBZLHJQ.js";
|
|
30
30
|
import {
|
|
31
31
|
PROVIDER_IDS,
|
|
32
32
|
configLocation
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-E2YXYU73.js";
|
|
34
34
|
|
|
35
35
|
// src/provider-locations.ts
|
|
36
36
|
import { access } from "fs/promises";
|