tokmon 0.23.1 → 0.23.2
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 +2 -1
- package/dist/{bootstrap-ink-UD3KDTOB.js → bootstrap-ink-MBN74IBQ.js} +161 -100
- package/dist/{chunk-PGQK7MOE.js → chunk-2AFQKH5O.js} +5 -1
- package/dist/{chunk-AON6GE7D.js → chunk-VVJF5H4D.js} +69 -20
- package/dist/cli.js +3 -3
- package/dist/{daemon-QGCI46US.js → daemon-F556JO7S.js} +2 -2
- package/dist/{server-4CDZMLH6.js → server-GBB3CR7Y.js} +2 -2
- package/dist/web/assets/{Area-DGo9K-U3.js → Area-C-Zb5hGY.js} +1 -1
- package/dist/web/assets/{analytics-BOhbhV5Z.js → analytics-kEeE-ogR.js} +2 -2
- package/dist/web/assets/{breakdown-CO04nSN-.js → breakdown-BavE2T8a.js} +1 -1
- package/dist/web/assets/{chart-bNxdfXxA.js → chart-QgM4f2g9.js} +1 -1
- package/dist/web/assets/{explore-sZnmjzAj.js → explore-Buh9XEPv.js} +1 -1
- package/dist/web/assets/index-KD8LkaQs.js +83 -0
- package/dist/web/assets/{models-Cl6D8VVF.js → models-BRG0KFKx.js} +2 -2
- package/dist/web/assets/{overview-C18nvaAK.js → overview-JbfGEJ5u.js} +2 -2
- package/dist/web/assets/{panel-DTjMnHaz.js → panel-OcuPqv_k.js} +1 -1
- package/dist/web/assets/{primitives-Dj_9YxTF.js → primitives-BUmlNFB0.js} +1 -1
- package/dist/web/assets/{settings-sheet-0BGb5Ahv.js → settings-sheet-DVstI_qi.js} +1 -1
- package/dist/web/assets/{share-sheet-BHtt-OmT.js → share-sheet-qAODWbhh.js} +1 -1
- package/dist/web/assets/{timeline-DmzB8Z3W.js → timeline-DoJ5qjNr.js} +1 -1
- package/dist/web/assets/{use-dialog-trap-DeXFtxx8.js → use-dialog-trap-9cAy7Dfu.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
- package/dist/web/assets/index-DnDr1VUr.js +0 -83
package/README.md
CHANGED
|
@@ -233,10 +233,11 @@ tokmon runs a small local **daemon** that does all the data collection. The term
|
|
|
233
233
|
- 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.
|
|
234
234
|
|
|
235
235
|
**Limits & billing**
|
|
236
|
-
- Rate limits and remaining spend/quota come from each provider's own official API, refreshed on the billing poll interval.
|
|
236
|
+
- Rate limits and remaining spend/quota come from each provider's own official API, refreshed on the billing poll interval and when the terminal regains focus. Opening a dashboard also catches up stale limits immediately.
|
|
237
237
|
|
|
238
238
|
**Responsiveness**
|
|
239
239
|
- Dashboard summaries and table history load independently and refresh on separate intervals, so the UI stays responsive even on large histories.
|
|
240
|
+
- The TUI shows the usage and limits cadences separately and reconnects automatically if a suspended terminal leaves its live stream stale.
|
|
240
241
|
|
|
241
242
|
Cross-platform: macOS, Linux, Windows. Everything is local and read-only — see [Privacy](#privacy).
|
|
242
243
|
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
time,
|
|
20
20
|
tokens,
|
|
21
21
|
withTimeout
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-2AFQKH5O.js";
|
|
23
23
|
import {
|
|
24
24
|
glyphs
|
|
25
25
|
} from "./chunk-RF4GGQGM.js";
|
|
@@ -1607,7 +1607,7 @@ function TinyRow({ provider, accounts, stats, width }) {
|
|
|
1607
1607
|
import { useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState3 } from "react";
|
|
1608
1608
|
|
|
1609
1609
|
// src/client/daemon-rpc-client.ts
|
|
1610
|
-
import { Cause, Context, Duration, Effect, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
|
|
1610
|
+
import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
|
|
1611
1611
|
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
1612
1612
|
import * as Socket from "effect/unstable/socket/Socket";
|
|
1613
1613
|
var TokmonRpcClient = class extends Context.Service()(
|
|
@@ -1660,8 +1660,13 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1660
1660
|
options.onConn?.(state, error);
|
|
1661
1661
|
};
|
|
1662
1662
|
const resetSession = (active) => {
|
|
1663
|
+
if (active !== void 0 && active !== null && active !== session) {
|
|
1664
|
+
void active.runtime.dispose().catch(() => {
|
|
1665
|
+
});
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1663
1668
|
const dead = active ?? session;
|
|
1664
|
-
|
|
1669
|
+
session = null;
|
|
1665
1670
|
sessionPromise = null;
|
|
1666
1671
|
if (dead) void dead.runtime.dispose().catch(() => {
|
|
1667
1672
|
});
|
|
@@ -1738,12 +1743,16 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1738
1743
|
throw error;
|
|
1739
1744
|
}
|
|
1740
1745
|
};
|
|
1741
|
-
const subscribe = (streamFor, onValue) => {
|
|
1746
|
+
const subscribe = (streamFor, onValue, staleAfterFor) => {
|
|
1742
1747
|
if (closed) return () => {
|
|
1743
1748
|
};
|
|
1744
1749
|
let fiber = null;
|
|
1745
1750
|
let unsubscribed = false;
|
|
1746
1751
|
let retryTimer = null;
|
|
1752
|
+
let watchdogTimer = null;
|
|
1753
|
+
let lastValueAt = 0;
|
|
1754
|
+
let staleAfterMs = Number.POSITIVE_INFINITY;
|
|
1755
|
+
let watchdogRestart = false;
|
|
1747
1756
|
const stopFiber = () => {
|
|
1748
1757
|
if (!fiber) return;
|
|
1749
1758
|
const current = fiber;
|
|
@@ -1760,16 +1769,31 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1760
1769
|
}, options.reconnectBaseDelayMs ?? 250);
|
|
1761
1770
|
retryTimer.unref?.();
|
|
1762
1771
|
};
|
|
1772
|
+
if (staleAfterFor) {
|
|
1773
|
+
const checkEveryMs = Math.min(5e3, Math.max(10, (options.snapshotStaleFloorMs ?? 9e4) / 2));
|
|
1774
|
+
watchdogTimer = setInterval(() => {
|
|
1775
|
+
if (!fiber || lastValueAt === 0 || Date.now() - lastValueAt <= staleAfterMs) return;
|
|
1776
|
+
watchdogRestart = true;
|
|
1777
|
+
lastValueAt = Date.now();
|
|
1778
|
+
setConn("reconnecting");
|
|
1779
|
+
stopFiber();
|
|
1780
|
+
}, checkEveryMs);
|
|
1781
|
+
watchdogTimer.unref?.();
|
|
1782
|
+
}
|
|
1763
1783
|
const start = () => {
|
|
1764
1784
|
void (async () => {
|
|
1765
1785
|
try {
|
|
1766
1786
|
const active = await ensureSession();
|
|
1767
1787
|
if (closed || unsubscribed) return;
|
|
1768
|
-
|
|
1788
|
+
lastValueAt = Date.now();
|
|
1789
|
+
staleAfterMs = options.snapshotStaleFloorMs ?? 9e4;
|
|
1790
|
+
const currentFiber = active.runtime.runFork(
|
|
1769
1791
|
TokmonRpcClient.use(
|
|
1770
1792
|
(client) => streamFor(client).pipe(
|
|
1771
1793
|
Stream.runForEach(
|
|
1772
1794
|
(value) => Effect.sync(() => {
|
|
1795
|
+
lastValueAt = Date.now();
|
|
1796
|
+
if (staleAfterFor) staleAfterMs = staleAfterFor(value);
|
|
1773
1797
|
try {
|
|
1774
1798
|
onValue(value);
|
|
1775
1799
|
} catch {
|
|
@@ -1777,19 +1801,20 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1777
1801
|
})
|
|
1778
1802
|
)
|
|
1779
1803
|
)
|
|
1780
|
-
)
|
|
1781
|
-
(cause) => Effect.sync(() => {
|
|
1782
|
-
if (!closed && !unsubscribed) {
|
|
1783
|
-
resetSession(active);
|
|
1784
|
-
setConn("error", Cause.squash(cause));
|
|
1785
|
-
scheduleRetry();
|
|
1786
|
-
}
|
|
1787
|
-
})
|
|
1788
|
-
))
|
|
1804
|
+
)
|
|
1789
1805
|
);
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1806
|
+
fiber = currentFiber;
|
|
1807
|
+
fibers.add(currentFiber);
|
|
1808
|
+
currentFiber.addObserver((exit) => {
|
|
1809
|
+
fibers.delete(currentFiber);
|
|
1810
|
+
if (fiber === currentFiber) fiber = null;
|
|
1811
|
+
if (closed || unsubscribed) return;
|
|
1812
|
+
const stale = watchdogRestart;
|
|
1813
|
+
watchdogRestart = false;
|
|
1814
|
+
resetSession(active);
|
|
1815
|
+
if (stale || Exit.isSuccess(exit)) setConn("reconnecting");
|
|
1816
|
+
else setConn("error", Cause.squash(exit.cause));
|
|
1817
|
+
scheduleRetry();
|
|
1793
1818
|
});
|
|
1794
1819
|
} catch (error) {
|
|
1795
1820
|
if (!closed && !unsubscribed) {
|
|
@@ -1807,6 +1832,10 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1807
1832
|
clearTimeout(retryTimer);
|
|
1808
1833
|
retryTimer = null;
|
|
1809
1834
|
}
|
|
1835
|
+
if (watchdogTimer) {
|
|
1836
|
+
clearInterval(watchdogTimer);
|
|
1837
|
+
watchdogTimer = null;
|
|
1838
|
+
}
|
|
1810
1839
|
stopFiber();
|
|
1811
1840
|
};
|
|
1812
1841
|
};
|
|
@@ -1815,7 +1844,11 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1815
1844
|
setConfig: (config) => run((client) => client[TOKMON_WS_METHODS.setConfig](config)).then((state) => state),
|
|
1816
1845
|
refresh: (scope = "all") => run((client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
|
|
1817
1846
|
browseFs: (path) => run((client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
|
|
1818
|
-
subscribeSnapshot: (onSnapshot) => subscribe(
|
|
1847
|
+
subscribeSnapshot: (onSnapshot) => subscribe(
|
|
1848
|
+
(client) => client[TOKMON_WS_METHODS.snapshot]({}).pipe(Stream.map((value) => value)),
|
|
1849
|
+
onSnapshot,
|
|
1850
|
+
(snapshot) => Math.max(options.snapshotStaleFloorMs ?? 9e4, snapshot.intervalMs * 3)
|
|
1851
|
+
),
|
|
1819
1852
|
subscribeConfig: (onConfig) => subscribe((client) => client[TOKMON_WS_METHODS.config]({}).pipe(Stream.map((value) => value)), onConfig),
|
|
1820
1853
|
async close() {
|
|
1821
1854
|
if (closed) return;
|
|
@@ -2500,6 +2533,17 @@ function handleNavigation(input, key, ctx) {
|
|
|
2500
2533
|
}
|
|
2501
2534
|
|
|
2502
2535
|
// src/ui/keybindings.ts
|
|
2536
|
+
function terminalFocusEvent(input) {
|
|
2537
|
+
if (input === "[I") return "in";
|
|
2538
|
+
if (input === "[O") return "out";
|
|
2539
|
+
return null;
|
|
2540
|
+
}
|
|
2541
|
+
function handleTerminalFocusInput(input, onFocusIn) {
|
|
2542
|
+
const event = terminalFocusEvent(input);
|
|
2543
|
+
if (!event) return false;
|
|
2544
|
+
if (event === "in") onFocusIn();
|
|
2545
|
+
return true;
|
|
2546
|
+
}
|
|
2503
2547
|
function isRefreshAllShortcut(input, mode) {
|
|
2504
2548
|
return (input === "r" || input === "R") && !mode.unavailable && !mode.showPicker && !mode.editingAccount && !mode.editingTimezone && !mode.editingSearch;
|
|
2505
2549
|
}
|
|
@@ -3384,6 +3428,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3384
3428
|
[connected, snapshot, updatedLocal]
|
|
3385
3429
|
);
|
|
3386
3430
|
const intervalLabel = connected && snapshot?.intervalMs ? Math.round(snapshot.intervalMs / 1e3) : cfg.interval;
|
|
3431
|
+
const billingIntervalLabel = connected && snapshot?.billingIntervalMs ? Math.max(1, Math.round(snapshot.billingIntervalMs / 6e4)) : cfg.billingInterval;
|
|
3387
3432
|
const readyInputFor = useCallback7((id) => {
|
|
3388
3433
|
if (connected) {
|
|
3389
3434
|
const wa = snapshot?.accounts.find((a) => a.id === id);
|
|
@@ -3604,7 +3649,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3604
3649
|
if (webStartingRef.current) return;
|
|
3605
3650
|
webStartingRef.current = true;
|
|
3606
3651
|
try {
|
|
3607
|
-
const { startWebServer } = await import("./server-
|
|
3652
|
+
const { startWebServer } = await import("./server-GBB3CR7Y.js");
|
|
3608
3653
|
const ctrl = await startWebServer({ config: cfg, log: false });
|
|
3609
3654
|
webRef.current = ctrl;
|
|
3610
3655
|
openUrl(ctrl.browserUrl);
|
|
@@ -3651,83 +3696,95 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3651
3696
|
() => tab === 1 ? sortRows(filterTokenRows(filterRowsByModel(rawTokenRows, activeTableModel), search), sort) : [],
|
|
3652
3697
|
[tab, rawTokenRows, activeTableModel, search, sort]
|
|
3653
3698
|
);
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3699
|
+
const lastFocusRefreshRef = useRef8(Date.now());
|
|
3700
|
+
const refreshOnFocus = useCallback7(() => {
|
|
3701
|
+
const now = Date.now();
|
|
3702
|
+
if (now - lastFocusRefreshRef.current < 3e4) return;
|
|
3703
|
+
lastFocusRefreshRef.current = now;
|
|
3704
|
+
const task = connected ? daemonRefreshRef.current("billing") : degradedRefreshRef.current();
|
|
3705
|
+
void task.catch(() => {
|
|
3706
|
+
});
|
|
3707
|
+
}, [connected]);
|
|
3708
|
+
useInput((input, key) => {
|
|
3709
|
+
if (handleTerminalFocusInput(input, refreshOnFocus)) return;
|
|
3710
|
+
handleKey(input, key, {
|
|
3711
|
+
onboarding: {
|
|
3712
|
+
show: showPicker,
|
|
3713
|
+
providers: pickerProviders,
|
|
3714
|
+
cursor: onboardCursor,
|
|
3715
|
+
setCursor: setOnboardCursor,
|
|
3716
|
+
toggle: toggleOnboard,
|
|
3717
|
+
confirm: confirmOnboarding
|
|
3718
|
+
},
|
|
3719
|
+
accountEditor: {
|
|
3720
|
+
form: accountForm,
|
|
3721
|
+
setForm: setAccountForm,
|
|
3722
|
+
commit: commitAccountForm,
|
|
3723
|
+
cycleField: cycleFormField,
|
|
3724
|
+
cycleProvider,
|
|
3725
|
+
cycleColor
|
|
3726
|
+
},
|
|
3727
|
+
timezoneEditor: {
|
|
3728
|
+
value: tzEdit,
|
|
3729
|
+
setValue: setTzEdit,
|
|
3730
|
+
setError: setTzError,
|
|
3731
|
+
setCaret: setTzCaret,
|
|
3732
|
+
valueRef: tzValueRef,
|
|
3733
|
+
caretRef: tzCaretRef
|
|
3734
|
+
},
|
|
3735
|
+
textInput: { isPrintable, insert: insertText },
|
|
3736
|
+
settings: {
|
|
3737
|
+
show: showSettings,
|
|
3738
|
+
setShow: setShowSettings,
|
|
3739
|
+
cursor: settingsCursor,
|
|
3740
|
+
tab: settingsTab,
|
|
3741
|
+
setTab: setSettingsTab,
|
|
3742
|
+
setCursor: setSettingsCursor,
|
|
3743
|
+
trackedAccounts: trackedAccountRows,
|
|
3744
|
+
moveAccount,
|
|
3745
|
+
toggleProvider,
|
|
3746
|
+
openEditAccount,
|
|
3747
|
+
openConfigureAccount,
|
|
3748
|
+
deleteAccount,
|
|
3749
|
+
openAddAccount
|
|
3750
|
+
},
|
|
3751
|
+
table: {
|
|
3752
|
+
tab,
|
|
3753
|
+
searchMode,
|
|
3754
|
+
setSearchMode,
|
|
3755
|
+
search,
|
|
3756
|
+
setSearch,
|
|
3757
|
+
setSearchCaret,
|
|
3758
|
+
searchValueRef,
|
|
3759
|
+
searchCaretRef,
|
|
3760
|
+
cycleProvider: cycleTableProvider,
|
|
3761
|
+
setExpanded,
|
|
3762
|
+
setSort,
|
|
3763
|
+
sorts: SORTS_FOR,
|
|
3764
|
+
cycleModel: cycleTableModelFilter,
|
|
3765
|
+
setView,
|
|
3766
|
+
cursor,
|
|
3767
|
+
rowCountRef,
|
|
3768
|
+
rows,
|
|
3769
|
+
setCursor,
|
|
3770
|
+
clampRow
|
|
3771
|
+
},
|
|
3772
|
+
dashboard: { paginated: dashPaginated, pageCount: dashPageCount, setPage: setDashPage },
|
|
3773
|
+
global: {
|
|
3774
|
+
exit,
|
|
3775
|
+
showLoader,
|
|
3776
|
+
configReady,
|
|
3777
|
+
toggleWeb,
|
|
3778
|
+
config: cfg,
|
|
3779
|
+
updateConfig,
|
|
3780
|
+
cycleAccount,
|
|
3781
|
+
setTab,
|
|
3782
|
+
resetView,
|
|
3783
|
+
slots,
|
|
3784
|
+
refreshAll
|
|
3785
|
+
}
|
|
3786
|
+
});
|
|
3787
|
+
}, { isActive: IS_TTY });
|
|
3731
3788
|
if (!config) return /* @__PURE__ */ jsx12(Box11, { padding: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Loading..." }) });
|
|
3732
3789
|
if (resizing) return /* @__PURE__ */ jsx12(ResizingView, { cols: live.cols, rows: live.rows });
|
|
3733
3790
|
if (showPicker) {
|
|
@@ -3760,9 +3817,13 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3760
3817
|
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3761
3818
|
" ",
|
|
3762
3819
|
glyphs().middot,
|
|
3763
|
-
"
|
|
3820
|
+
" usage ",
|
|
3764
3821
|
intervalLabel,
|
|
3765
|
-
"s"
|
|
3822
|
+
"s ",
|
|
3823
|
+
glyphs().middot,
|
|
3824
|
+
" limits ",
|
|
3825
|
+
billingIntervalLabel,
|
|
3826
|
+
"m"
|
|
3766
3827
|
] })
|
|
3767
3828
|
] }),
|
|
3768
3829
|
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
@@ -3885,11 +3946,11 @@ function leaveAltScreen() {
|
|
|
3885
3946
|
}
|
|
3886
3947
|
}
|
|
3887
3948
|
function setupInputModes() {
|
|
3888
|
-
process.stdout.write("\x1B[?2004h\x1B[?
|
|
3949
|
+
process.stdout.write("\x1B[?2004h\x1B[?1004h");
|
|
3889
3950
|
}
|
|
3890
3951
|
function restoreInputModes() {
|
|
3891
3952
|
try {
|
|
3892
|
-
process.stdout.write("\x1B[?2004l");
|
|
3953
|
+
process.stdout.write("\x1B[?1004l\x1B[?2004l");
|
|
3893
3954
|
} catch {
|
|
3894
3955
|
}
|
|
3895
3956
|
}
|
|
@@ -4752,13 +4752,17 @@ var WebAccountSchema = Schema.Struct({
|
|
|
4752
4752
|
billing: Schema.NullOr(BillingResultSchema),
|
|
4753
4753
|
summaryState: Schema.Literals(["pending", "ready", "error"]),
|
|
4754
4754
|
billingState: Schema.Literals(["pending", "ready", "error"]),
|
|
4755
|
-
tableState: Schema.Literals(["pending", "ready", "error"])
|
|
4755
|
+
tableState: Schema.Literals(["pending", "ready", "error"]),
|
|
4756
|
+
summaryUpdatedAt: Schema.optionalKey(Schema.NullOr(NonNegativeIntegerSchema)),
|
|
4757
|
+
billingUpdatedAt: Schema.optionalKey(Schema.NullOr(NonNegativeIntegerSchema)),
|
|
4758
|
+
tableUpdatedAt: Schema.optionalKey(Schema.NullOr(NonNegativeIntegerSchema))
|
|
4756
4759
|
});
|
|
4757
4760
|
var WebSnapshotSchema = Schema.Struct({
|
|
4758
4761
|
version: Schema.String,
|
|
4759
4762
|
generatedAt: NonNegativeIntegerSchema,
|
|
4760
4763
|
tz: Schema.String,
|
|
4761
4764
|
intervalMs: PositiveFiniteSchema,
|
|
4765
|
+
billingIntervalMs: Schema.optionalKey(PositiveFiniteSchema),
|
|
4762
4766
|
providers: Schema.Array(Schema.Struct({
|
|
4763
4767
|
id: ProviderIdSchema,
|
|
4764
4768
|
name: Schema.String,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
namedHex,
|
|
22
22
|
resolveTimezone,
|
|
23
23
|
withTimeout
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-2AFQKH5O.js";
|
|
25
25
|
import {
|
|
26
26
|
cacheDir,
|
|
27
27
|
expandHome,
|
|
@@ -84,7 +84,10 @@ function assembleSnapshot(opts) {
|
|
|
84
84
|
billing,
|
|
85
85
|
summaryState: opts.summaryState?.get(r.account.id) ?? "pending",
|
|
86
86
|
billingState: opts.billingState?.get(r.account.id) ?? "pending",
|
|
87
|
-
tableState: opts.tableState?.get(r.account.id) ?? "pending"
|
|
87
|
+
tableState: opts.tableState?.get(r.account.id) ?? "pending",
|
|
88
|
+
summaryUpdatedAt: opts.summaryUpdatedAt?.get(r.account.id) ?? null,
|
|
89
|
+
billingUpdatedAt: opts.billingUpdatedAt?.get(r.account.id) ?? null,
|
|
90
|
+
tableUpdatedAt: opts.tableUpdatedAt?.get(r.account.id) ?? null
|
|
88
91
|
};
|
|
89
92
|
});
|
|
90
93
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -103,6 +106,7 @@ function assembleSnapshot(opts) {
|
|
|
103
106
|
generatedAt: Date.now(),
|
|
104
107
|
tz: opts.tz,
|
|
105
108
|
intervalMs: opts.intervalMs,
|
|
109
|
+
billingIntervalMs: opts.billingIntervalMs,
|
|
106
110
|
providers,
|
|
107
111
|
accounts,
|
|
108
112
|
seeded: opts.seeded ?? false,
|
|
@@ -247,6 +251,10 @@ var IDLE_PAUSE_MS = 6e4;
|
|
|
247
251
|
var SNAPSHOT_CACHE_THROTTLE_MS = 2e4;
|
|
248
252
|
var REVEAL_THROTTLE_MS = 500;
|
|
249
253
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
254
|
+
var ATTACH_BILLING_MAX_AGE_MS = 3e4;
|
|
255
|
+
function billingNeedsCatchUp(accounts, updatedAt, now = Date.now(), maxAgeMs = ATTACH_BILLING_MAX_AGE_MS) {
|
|
256
|
+
return accounts.some(({ account }) => now - (updatedAt.get(account.id) ?? 0) >= maxAgeMs);
|
|
257
|
+
}
|
|
250
258
|
function runInBackground(task) {
|
|
251
259
|
void task.catch(() => {
|
|
252
260
|
});
|
|
@@ -268,6 +276,9 @@ function createDataEngine(opts) {
|
|
|
268
276
|
const summaryState = /* @__PURE__ */ new Map();
|
|
269
277
|
const billingState = /* @__PURE__ */ new Map();
|
|
270
278
|
const tableState = /* @__PURE__ */ new Map();
|
|
279
|
+
const summaryUpdatedAt = /* @__PURE__ */ new Map();
|
|
280
|
+
const billingUpdatedAt = /* @__PURE__ */ new Map();
|
|
281
|
+
const tableUpdatedAt = /* @__PURE__ */ new Map();
|
|
271
282
|
let peak = null;
|
|
272
283
|
let seeded = false;
|
|
273
284
|
let current = null;
|
|
@@ -296,12 +307,16 @@ function createDataEngine(opts) {
|
|
|
296
307
|
version,
|
|
297
308
|
tz,
|
|
298
309
|
intervalMs: summaryIntervalMs,
|
|
310
|
+
billingIntervalMs,
|
|
299
311
|
resolved,
|
|
300
312
|
usage,
|
|
301
313
|
billing,
|
|
302
314
|
summaryState,
|
|
303
315
|
billingState,
|
|
304
316
|
tableState,
|
|
317
|
+
summaryUpdatedAt,
|
|
318
|
+
billingUpdatedAt,
|
|
319
|
+
tableUpdatedAt,
|
|
305
320
|
seeded,
|
|
306
321
|
peak
|
|
307
322
|
});
|
|
@@ -312,12 +327,19 @@ function createDataEngine(opts) {
|
|
|
312
327
|
for (const a of cached.accounts) {
|
|
313
328
|
if (a.dashboard || a.table) {
|
|
314
329
|
usage.set(a.id, { dashboard: a.dashboard, table: a.table });
|
|
315
|
-
if (a.dashboard)
|
|
316
|
-
|
|
330
|
+
if (a.dashboard) {
|
|
331
|
+
summaryState.set(a.id, "ready");
|
|
332
|
+
if (typeof a.summaryUpdatedAt === "number") summaryUpdatedAt.set(a.id, a.summaryUpdatedAt);
|
|
333
|
+
}
|
|
334
|
+
if (a.table) {
|
|
335
|
+
tableState.set(a.id, "ready");
|
|
336
|
+
if (typeof a.tableUpdatedAt === "number") tableUpdatedAt.set(a.id, a.tableUpdatedAt);
|
|
337
|
+
}
|
|
317
338
|
}
|
|
318
339
|
if (a.billing) {
|
|
319
340
|
billing.set(a.id, a.billing);
|
|
320
341
|
billingState.set(a.id, "ready");
|
|
342
|
+
if (typeof a.billingUpdatedAt === "number") billingUpdatedAt.set(a.id, a.billingUpdatedAt);
|
|
321
343
|
}
|
|
322
344
|
}
|
|
323
345
|
seeded = true;
|
|
@@ -362,23 +384,40 @@ function createDataEngine(opts) {
|
|
|
362
384
|
async () => {
|
|
363
385
|
if (stopped) return;
|
|
364
386
|
const epoch = configEpoch;
|
|
365
|
-
const
|
|
366
|
-
for (const r of opts2.accounts()) {
|
|
367
|
-
if (stopped) return;
|
|
368
|
-
let value = null;
|
|
369
|
-
let ok = true;
|
|
387
|
+
const fetchOne = async (r) => {
|
|
370
388
|
try {
|
|
371
|
-
value
|
|
389
|
+
return { r, ok: true, value: await withTimeout(opts2.fetch(r), FETCH_TIMEOUT_MS) };
|
|
372
390
|
} catch (cause) {
|
|
373
|
-
ok
|
|
374
|
-
|
|
391
|
+
return { r, ok: false, cause };
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
const failures = [];
|
|
395
|
+
const applyResult = (result) => {
|
|
396
|
+
const id = result.r.account.id;
|
|
397
|
+
if (result.ok) {
|
|
398
|
+
opts2.apply(id, result.value);
|
|
399
|
+
opts2.state.set(id, "ready");
|
|
400
|
+
opts2.updatedAt.set(id, Date.now());
|
|
401
|
+
} else {
|
|
402
|
+
opts2.state.set(id, "error");
|
|
403
|
+
failures.push(result.cause);
|
|
375
404
|
}
|
|
376
|
-
if (stopped || epoch !== configEpoch) return;
|
|
377
|
-
if (ok) {
|
|
378
|
-
opts2.apply(r.account.id, value);
|
|
379
|
-
opts2.state.set(r.account.id, "ready");
|
|
380
|
-
} else opts2.state.set(r.account.id, "error");
|
|
381
405
|
reveal();
|
|
406
|
+
};
|
|
407
|
+
if (opts2.concurrent) {
|
|
408
|
+
await Promise.all(opts2.accounts().map(async (r) => {
|
|
409
|
+
const result = await fetchOne(r);
|
|
410
|
+
if (stopped || epoch !== configEpoch) return;
|
|
411
|
+
applyResult(result);
|
|
412
|
+
}));
|
|
413
|
+
if (stopped || epoch !== configEpoch) return;
|
|
414
|
+
} else {
|
|
415
|
+
for (const r of opts2.accounts()) {
|
|
416
|
+
if (stopped) return;
|
|
417
|
+
const result = await fetchOne(r);
|
|
418
|
+
if (stopped || epoch !== configEpoch) return;
|
|
419
|
+
applyResult(result);
|
|
420
|
+
}
|
|
382
421
|
}
|
|
383
422
|
rebuild();
|
|
384
423
|
throwIfRefreshFailures(opts2.scope, failures);
|
|
@@ -392,7 +431,8 @@ function createDataEngine(opts) {
|
|
|
392
431
|
apply: (id, dashboard) => {
|
|
393
432
|
usageEntry(id).dashboard = dashboard;
|
|
394
433
|
},
|
|
395
|
-
state: summaryState
|
|
434
|
+
state: summaryState,
|
|
435
|
+
updatedAt: summaryUpdatedAt
|
|
396
436
|
});
|
|
397
437
|
const refreshTable = makeRefreshLoop({
|
|
398
438
|
scope: "history",
|
|
@@ -401,7 +441,8 @@ function createDataEngine(opts) {
|
|
|
401
441
|
apply: (id, table) => {
|
|
402
442
|
usageEntry(id).table = table;
|
|
403
443
|
},
|
|
404
|
-
state: tableState
|
|
444
|
+
state: tableState,
|
|
445
|
+
updatedAt: tableUpdatedAt
|
|
405
446
|
});
|
|
406
447
|
const refreshBilling = makeRefreshLoop({
|
|
407
448
|
scope: "billing",
|
|
@@ -410,7 +451,9 @@ function createDataEngine(opts) {
|
|
|
410
451
|
apply: (id, result) => {
|
|
411
452
|
billing.set(id, result);
|
|
412
453
|
},
|
|
413
|
-
state: billingState
|
|
454
|
+
state: billingState,
|
|
455
|
+
updatedAt: billingUpdatedAt,
|
|
456
|
+
concurrent: true
|
|
414
457
|
});
|
|
415
458
|
const refreshPeak = createRefreshQueue(
|
|
416
459
|
async () => {
|
|
@@ -499,6 +542,9 @@ function createDataEngine(opts) {
|
|
|
499
542
|
for (const map of [summaryState, billingState, tableState]) {
|
|
500
543
|
for (const id of [...map.keys()]) if (!survivors.has(id)) map.delete(id);
|
|
501
544
|
}
|
|
545
|
+
for (const map of [summaryUpdatedAt, billingUpdatedAt, tableUpdatedAt]) {
|
|
546
|
+
for (const id of [...map.keys()]) if (!survivors.has(id)) map.delete(id);
|
|
547
|
+
}
|
|
502
548
|
rebuild();
|
|
503
549
|
runInBackground(refreshSummary.run(true));
|
|
504
550
|
runInBackground(refreshTable.run(true));
|
|
@@ -529,6 +575,9 @@ function createDataEngine(opts) {
|
|
|
529
575
|
runInBackground(refreshSummary.run(true));
|
|
530
576
|
runInBackground(refreshTable.run(true));
|
|
531
577
|
}
|
|
578
|
+
if (billingNeedsCatchUp(billingAccounts, billingUpdatedAt)) {
|
|
579
|
+
runInBackground(refreshBilling.run());
|
|
580
|
+
}
|
|
532
581
|
return () => {
|
|
533
582
|
snapshotSubscribers.delete(onSnapshot);
|
|
534
583
|
};
|
package/dist/cli.js
CHANGED
|
@@ -41,14 +41,14 @@ function validateServeArgs(serveArgs) {
|
|
|
41
41
|
}
|
|
42
42
|
async function main() {
|
|
43
43
|
if (subcommand === "__daemon") {
|
|
44
|
-
const { runDaemon } = await import("./daemon-
|
|
44
|
+
const { runDaemon } = await import("./daemon-F556JO7S.js");
|
|
45
45
|
await runDaemon(args.slice(1), { foreground: false });
|
|
46
46
|
process.exitCode ??= 0;
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
if (subcommand === "serve" || subcommand === "web") {
|
|
50
50
|
validateServeArgs(args.slice(1));
|
|
51
|
-
const { runDaemon } = await import("./daemon-
|
|
51
|
+
const { runDaemon } = await import("./daemon-F556JO7S.js");
|
|
52
52
|
await runDaemon(args.slice(1), { foreground: true });
|
|
53
53
|
process.exitCode ??= 0;
|
|
54
54
|
return;
|
|
@@ -103,7 +103,7 @@ async function main() {
|
|
|
103
103
|
}));
|
|
104
104
|
const daemon = await attachOrSpawn();
|
|
105
105
|
const mode = daemon.kind === "spawned" ? "connected" : "degraded";
|
|
106
|
-
const { bootstrapInk } = await import("./bootstrap-ink-
|
|
106
|
+
const { bootstrapInk } = await import("./bootstrap-ink-MBN74IBQ.js");
|
|
107
107
|
await bootstrapInk({ interval, config, daemon, mode });
|
|
108
108
|
}
|
|
109
109
|
void main().catch((error) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startWebServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VVJF5H4D.js";
|
|
5
5
|
import {
|
|
6
6
|
acquireLock,
|
|
7
7
|
isAlive,
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "./chunk-HXDAOOSI.js";
|
|
20
20
|
import {
|
|
21
21
|
flushDisk
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-2AFQKH5O.js";
|
|
23
23
|
import {
|
|
24
24
|
loadConfig
|
|
25
25
|
} from "./chunk-DJPUYMZM.js";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startWebServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VVJF5H4D.js";
|
|
5
5
|
import "./chunk-HXDAOOSI.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-2AFQKH5O.js";
|
|
7
7
|
import "./chunk-DJPUYMZM.js";
|
|
8
8
|
export {
|
|
9
9
|
startWebServer
|