tokmon 0.23.4 → 0.24.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 +23 -5
- package/dist/{bootstrap-ink-BIOLTP7F.js → bootstrap-ink-HL2BYNW3.js} +37 -292
- package/dist/{chunk-RM3ZE44Z.js → chunk-3TJVFKXV.js} +1 -1
- package/dist/chunk-42H7R376.js +298 -0
- package/dist/chunk-45ZP755H.js +275 -0
- package/dist/{chunk-DJPUYMZM.js → chunk-5CWOJMAH.js} +2 -0
- package/dist/{chunk-2AFQKH5O.js → chunk-O27I2XFN.js} +278 -539
- package/dist/{chunk-2K4L4QQK.js → chunk-REO4O5VE.js} +68 -79
- package/dist/{chunk-Q2DSDWO2.js → chunk-SMPY52EV.js} +1 -30
- package/dist/{daemon-handle-6F2IB6CU.js → chunk-W2WGBXZG.js} +8 -9
- package/dist/cli-command-TLGIGF63.js +526 -0
- package/dist/cli.js +30 -5
- package/dist/{config-5CNJN3P7.js → config-QC5QSP3G.js} +1 -1
- package/dist/{daemon-PP3QAT6E.js → daemon-F4EGY77J.js} +43 -17
- package/dist/daemon-handle-NLCLYWFU.js +10 -0
- package/dist/server-X63FGHIZ.js +11 -0
- package/dist/web/assets/{Area-BALPhJJp.js → Area-DCES17aZ.js} +1 -1
- package/dist/web/assets/{analytics-uHVeOhLh.js → analytics-DDW6zcnj.js} +2 -2
- package/dist/web/assets/{breakdown-CR5dNNSG.js → breakdown-D5LPPtqh.js} +1 -1
- package/dist/web/assets/{chart-DRkqw0fm.js → chart-bHY7MQV0.js} +1 -1
- package/dist/web/assets/{explore-C-bHaX-I.js → explore-DdWB-Pu7.js} +1 -1
- package/dist/web/assets/index-C9jxb2o3.js +83 -0
- package/dist/web/assets/index-m0KfWnN2.css +1 -0
- package/dist/web/assets/{models-Qc2daHCo.js → models-o1wJnG6w.js} +2 -2
- package/dist/web/assets/{overview-D4VG2awg.js → overview-wq2uAEk4.js} +2 -2
- package/dist/web/assets/{panel-C1CsBs1H.js → panel-DBnK1oy0.js} +1 -1
- package/dist/web/assets/{primitives-S4UDgnZq.js → primitives-CBmteetD.js} +1 -1
- package/dist/web/assets/settings-sheet-CZ6GEPl3.js +1 -0
- package/dist/web/assets/{share-sheet-BpZBju-k.js → share-sheet-DUee3FlK.js} +1 -1
- package/dist/web/assets/{timeline-Cp-fyeuK.js → timeline-BxHwOJA_.js} +1 -1
- package/dist/web/assets/{use-dialog-trap-DaKbCG1Z.js → use-dialog-trap-30EqxYF-.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/server-UHQEOVQH.js +0 -10
- package/dist/web/assets/index-C1jQdagn.js +0 -83
- package/dist/web/assets/index-DSj-Bxov.css +0 -1
- package/dist/web/assets/settings-sheet-DHi4Dt2z.js +0 -1
package/README.md
CHANGED
|
@@ -26,6 +26,22 @@ npm install -g tokmon
|
|
|
26
26
|
|
|
27
27
|
Then run `tokmon`. On first launch you'll pick which providers to track; press `q` to quit any time.
|
|
28
28
|
|
|
29
|
+
## CLI Data Queries
|
|
30
|
+
|
|
31
|
+
Scripts and coding agents can query the same daemon directly without opening the interactive dashboard:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
tokmon usage # current-month usage by provider/account/model
|
|
35
|
+
tokmon usage --period week --provider codex
|
|
36
|
+
tokmon usage --model opus --json # stable machine-readable schema
|
|
37
|
+
tokmon usage --period all --json --compact
|
|
38
|
+
tokmon providers --json # accounts plus local config/auth/usage paths
|
|
39
|
+
tokmon snapshot --refresh # complete raw daemon snapshot
|
|
40
|
+
tokmon config path # tokmon config file location
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`usage` refreshes local history by default; use `--cached` for the fastest cached answer or `--refresh` to refresh billing as well. JSON reports include `schemaVersion`, exact provider/account source IDs, per-model token and cost fields, and a `sources` collection that maps every model row to its provider home and discovered local paths. Run `tokmon usage --help` or `tokmon providers --help` for every filter and example.
|
|
44
|
+
|
|
29
45
|
## Providers
|
|
30
46
|
|
|
31
47
|
**Usage providers** — full cost & token history (Today / Week / Month, sparkline, per-model table):
|
|
@@ -81,7 +97,7 @@ For **Claude / Codex / Cursor / Grok** — Daily, Weekly, and Monthly breakdowns
|
|
|
81
97
|
|
|
82
98
|
## Web Dashboard
|
|
83
99
|
|
|
84
|
-
Prefer a browser? `tokmon serve` starts a local web dashboard with the same data as the TUI — charts, global filtering, and shareable images — in a terminal-styled UI. Press `w` (or `W`) inside the TUI to open its
|
|
100
|
+
Prefer a browser? `tokmon serve` starts a local web dashboard with the same data as the TUI — charts, global filtering, and shareable images — in a terminal-styled UI. Press `w` (or `W`) inside the TUI to open its ordinary local URL.
|
|
85
101
|
|
|
86
102
|
```bash
|
|
87
103
|
tokmon serve # opens http://127.0.0.1:4317 in your browser
|
|
@@ -89,9 +105,7 @@ tokmon serve --port 8080
|
|
|
89
105
|
tokmon serve --no-open # don't auto-open the browser
|
|
90
106
|
```
|
|
91
107
|
|
|
92
|
-
It binds to `127.0.0.1`
|
|
93
|
-
|
|
94
|
-
The URL opened by `W` contains a capability in its fragment. Fragments are not sent with normal page requests or referrers, but keeping it in the address makes the complete URL copyable into another local browser while that daemon is running. Treat that URL as private; after the daemon restarts, press `W` again for a fresh one.
|
|
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.
|
|
95
109
|
|
|
96
110
|
### Overview
|
|
97
111
|
|
|
@@ -188,6 +202,10 @@ Press `s` to open.
|
|
|
188
202
|
```
|
|
189
203
|
tokmon [options] Launch the terminal dashboard
|
|
190
204
|
tokmon serve [options] Launch the web dashboard (http://127.0.0.1:4317)
|
|
205
|
+
tokmon usage [options] Query usage by model (human or JSON)
|
|
206
|
+
tokmon providers [options] Show accounts and local provider paths
|
|
207
|
+
tokmon snapshot [options] Print the raw daemon snapshot as JSON
|
|
208
|
+
tokmon config [path] Print the tokmon config file location
|
|
191
209
|
|
|
192
210
|
Options:
|
|
193
211
|
-i, --interval <seconds> Refresh interval in seconds (default: from config, or 2)
|
|
@@ -224,7 +242,7 @@ tokmon runs entirely on your machine and reads everything **read-only**:
|
|
|
224
242
|
|
|
225
243
|
## How It Works
|
|
226
244
|
|
|
227
|
-
tokmon runs a small local **daemon** that does all the data collection. The terminal UI and the web dashboard are both thin clients of it, talking over a loopback-only
|
|
245
|
+
tokmon runs a small local **daemon** that does all the data collection. The terminal UI and the web dashboard are both thin clients of it, talking over a local WebSocket — loopback-only by default — so a single process does the work and the TUI and web always show the same numbers. The daemon starts automatically with the TUI (and standalone via `tokmon serve`), and idle-pauses when nothing is watching.
|
|
228
246
|
|
|
229
247
|
**Usage & cost**
|
|
230
248
|
- Parses each tool's local session logs — Claude / Codex / pi `JSONL`, Cursor / opencode `SQLite` — and aggregates cost and token usage per day, week, and month.
|
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
TOKMON_WS_PATH,
|
|
7
|
-
TokmonRpcGroup,
|
|
3
|
+
createDaemonRpcClient
|
|
4
|
+
} from "./chunk-45ZP755H.js";
|
|
5
|
+
import {
|
|
8
6
|
accountsByProvider,
|
|
9
7
|
buildAccounts,
|
|
8
|
+
fetchPeak,
|
|
9
|
+
modelColor
|
|
10
|
+
} from "./chunk-42H7R376.js";
|
|
11
|
+
import {
|
|
12
|
+
PROVIDERS,
|
|
13
|
+
PROVIDER_ORDER,
|
|
10
14
|
coalesceTables,
|
|
11
15
|
col,
|
|
12
16
|
currency,
|
|
13
17
|
detectProviders,
|
|
14
|
-
fetchPeak,
|
|
15
|
-
modelColor,
|
|
16
18
|
resolveTimezone,
|
|
17
19
|
shortDate,
|
|
18
20
|
systemTimezone,
|
|
19
21
|
time,
|
|
20
22
|
tokens,
|
|
21
23
|
withTimeout
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import {
|
|
24
|
-
glyphs
|
|
25
|
-
} from "./chunk-RF4GGQGM.js";
|
|
24
|
+
} from "./chunk-O27I2XFN.js";
|
|
26
25
|
import {
|
|
27
26
|
COLOR_PALETTE,
|
|
28
27
|
DEFAULTS,
|
|
@@ -37,7 +36,10 @@ import {
|
|
|
37
36
|
sanitizeTyped,
|
|
38
37
|
saveConfigSync,
|
|
39
38
|
snapshotCacheFile
|
|
40
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-5CWOJMAH.js";
|
|
40
|
+
import {
|
|
41
|
+
glyphs
|
|
42
|
+
} from "./chunk-RF4GGQGM.js";
|
|
41
43
|
|
|
42
44
|
// src/bootstrap-ink.tsx
|
|
43
45
|
import { render } from "ink";
|
|
@@ -530,7 +532,7 @@ import { Box as Box4, Text as Text4 } from "ink";
|
|
|
530
532
|
import { memo as memo3 } from "react";
|
|
531
533
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
532
534
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
533
|
-
var GENERAL_ROWS =
|
|
535
|
+
var GENERAL_ROWS = 9;
|
|
534
536
|
var SETTINGS_TABS = ["general", "providers", "accounts"];
|
|
535
537
|
var SETTINGS_TAB_LABELS = {
|
|
536
538
|
general: "General",
|
|
@@ -624,7 +626,9 @@ var SettingsView = memo3(function SettingsView2({
|
|
|
624
626
|
" ",
|
|
625
627
|
glyphs().caretR
|
|
626
628
|
] })
|
|
627
|
-
] })
|
|
629
|
+
] }),
|
|
630
|
+
/* @__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." })
|
|
628
632
|
] }),
|
|
629
633
|
activeTab === "providers" && /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
630
634
|
/* @__PURE__ */ jsx3(Text3, { bold: true, dimColor: true, children: "Providers" }),
|
|
@@ -1605,287 +1609,19 @@ function TinyRow({ provider, accounts, stats, width }) {
|
|
|
1605
1609
|
|
|
1606
1610
|
// src/client/use-daemon.ts
|
|
1607
1611
|
import { useEffect as useEffect3, useMemo, useRef as useRef2, useState as useState3 } from "react";
|
|
1608
|
-
|
|
1609
|
-
// src/client/daemon-rpc-client.ts
|
|
1610
|
-
import { Cause, Context, Duration, Effect, Exit, Fiber, Layer, ManagedRuntime, Schedule, Stream } from "effect";
|
|
1611
|
-
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
1612
|
-
import * as Socket from "effect/unstable/socket/Socket";
|
|
1613
|
-
var TokmonRpcClient = class extends Context.Service()(
|
|
1614
|
-
"tokmon/client/DaemonRpcClient/TokmonRpcClient"
|
|
1615
|
-
) {
|
|
1616
|
-
};
|
|
1617
|
-
var dynamicImport = new Function("specifier", "return import(specifier)");
|
|
1618
|
-
function toWsUrl(baseUrl, token) {
|
|
1619
|
-
const base = typeof window === "undefined" ? void 0 : window.location.origin;
|
|
1620
|
-
const url = new URL(baseUrl, base);
|
|
1621
|
-
if (url.protocol === "http:") url.protocol = "ws:";
|
|
1622
|
-
else if (url.protocol === "https:") url.protocol = "wss:";
|
|
1623
|
-
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
1624
|
-
throw new Error(`unsupported daemon RPC protocol: ${url.protocol}`);
|
|
1625
|
-
}
|
|
1626
|
-
url.hash = "";
|
|
1627
|
-
url.searchParams.delete("tokmonToken");
|
|
1628
|
-
url.pathname = TOKMON_WS_PATH;
|
|
1629
|
-
if (token) url.searchParams.set("wsToken", token);
|
|
1630
|
-
return url.toString();
|
|
1631
|
-
}
|
|
1632
|
-
function shouldUseNodeTransport(transport) {
|
|
1633
|
-
if (transport === "node") return true;
|
|
1634
|
-
if (transport === "browser") return false;
|
|
1635
|
-
return typeof window === "undefined";
|
|
1636
|
-
}
|
|
1637
|
-
async function socketLayerFor(url, transport) {
|
|
1638
|
-
if (shouldUseNodeTransport(transport)) {
|
|
1639
|
-
const NodeSocket = await dynamicImport("@effect/platform-node/NodeSocket");
|
|
1640
|
-
return NodeSocket.layerWebSocket(url);
|
|
1641
|
-
}
|
|
1642
|
-
return Socket.layerWebSocket(url).pipe(
|
|
1643
|
-
Layer.provide(Socket.layerWebSocketConstructorGlobal)
|
|
1644
|
-
);
|
|
1645
|
-
}
|
|
1646
|
-
function retryPolicy(options) {
|
|
1647
|
-
const baseDelay = options.reconnectBaseDelayMs ?? 250;
|
|
1648
|
-
const policy = Schedule.exponential(Duration.millis(baseDelay), 1.5).pipe(
|
|
1649
|
-
Schedule.either(Schedule.spaced(Duration.millis(2500)))
|
|
1650
|
-
);
|
|
1651
|
-
return typeof options.reconnectAttempts === "number" ? policy.pipe(Schedule.both(Schedule.recurs(options.reconnectAttempts))) : policy;
|
|
1652
|
-
}
|
|
1653
|
-
function createDaemonRpcClient(baseUrl, options = {}) {
|
|
1654
|
-
const url = toWsUrl(baseUrl, options.wsToken);
|
|
1655
|
-
const fibers = /* @__PURE__ */ new Set();
|
|
1656
|
-
let session = null;
|
|
1657
|
-
let sessionPromise = null;
|
|
1658
|
-
let closed = false;
|
|
1659
|
-
const setConn = (state, error) => {
|
|
1660
|
-
options.onConn?.(state, error);
|
|
1661
|
-
};
|
|
1662
|
-
const resetSession = (active) => {
|
|
1663
|
-
if (active !== void 0 && active !== null && active !== session) {
|
|
1664
|
-
void active.runtime.dispose().catch(() => {
|
|
1665
|
-
});
|
|
1666
|
-
return;
|
|
1667
|
-
}
|
|
1668
|
-
const dead = active ?? session;
|
|
1669
|
-
session = null;
|
|
1670
|
-
sessionPromise = null;
|
|
1671
|
-
if (dead) void dead.runtime.dispose().catch(() => {
|
|
1672
|
-
});
|
|
1673
|
-
};
|
|
1674
|
-
const makeProtocolLayer = async () => {
|
|
1675
|
-
const socketLayer = await socketLayerFor(url, options.transport);
|
|
1676
|
-
const connectionHooksLayer = Layer.succeed(
|
|
1677
|
-
RpcClient.ConnectionHooks,
|
|
1678
|
-
RpcClient.ConnectionHooks.of({
|
|
1679
|
-
onConnect: Effect.sync(() => {
|
|
1680
|
-
setConn("live");
|
|
1681
|
-
}),
|
|
1682
|
-
onDisconnect: Effect.sync(() => {
|
|
1683
|
-
if (!closed) {
|
|
1684
|
-
setConn("reconnecting");
|
|
1685
|
-
}
|
|
1686
|
-
})
|
|
1687
|
-
})
|
|
1688
|
-
);
|
|
1689
|
-
return Layer.effect(
|
|
1690
|
-
RpcClient.Protocol,
|
|
1691
|
-
RpcClient.makeProtocolSocket({
|
|
1692
|
-
retryPolicy: retryPolicy(options),
|
|
1693
|
-
retryTransientErrors: true
|
|
1694
|
-
})
|
|
1695
|
-
).pipe(
|
|
1696
|
-
Layer.provide(Layer.mergeAll(socketLayer, RpcSerialization.layerJson, connectionHooksLayer))
|
|
1697
|
-
);
|
|
1698
|
-
};
|
|
1699
|
-
const ensureSession = async () => {
|
|
1700
|
-
if (closed) throw new Error("daemon RPC client is closed");
|
|
1701
|
-
if (session) return session;
|
|
1702
|
-
if (sessionPromise) return sessionPromise;
|
|
1703
|
-
setConn("connecting");
|
|
1704
|
-
sessionPromise = (async () => {
|
|
1705
|
-
let runtime;
|
|
1706
|
-
try {
|
|
1707
|
-
const protocolLayer = await makeProtocolLayer();
|
|
1708
|
-
const clientLayer = Layer.effect(
|
|
1709
|
-
TokmonRpcClient,
|
|
1710
|
-
RpcClient.make(TokmonRpcGroup)
|
|
1711
|
-
).pipe(
|
|
1712
|
-
Layer.provide(protocolLayer)
|
|
1713
|
-
);
|
|
1714
|
-
runtime = ManagedRuntime.make(clientLayer);
|
|
1715
|
-
await runtime.runPromise(TokmonRpcClient.asEffect());
|
|
1716
|
-
if (closed) {
|
|
1717
|
-
await runtime.dispose();
|
|
1718
|
-
throw new Error("daemon RPC client is closed");
|
|
1719
|
-
}
|
|
1720
|
-
session = { runtime };
|
|
1721
|
-
return session;
|
|
1722
|
-
} catch (error) {
|
|
1723
|
-
sessionPromise = null;
|
|
1724
|
-
await runtime?.dispose().catch(() => {
|
|
1725
|
-
});
|
|
1726
|
-
if (!closed) setConn("error", error);
|
|
1727
|
-
throw error;
|
|
1728
|
-
}
|
|
1729
|
-
})();
|
|
1730
|
-
return sessionPromise;
|
|
1731
|
-
};
|
|
1732
|
-
const run = async (effectFor) => {
|
|
1733
|
-
const active = await ensureSession();
|
|
1734
|
-
try {
|
|
1735
|
-
return await active.runtime.runPromise(
|
|
1736
|
-
TokmonRpcClient.use((client) => effectFor(client))
|
|
1737
|
-
);
|
|
1738
|
-
} catch (error) {
|
|
1739
|
-
if (!closed) {
|
|
1740
|
-
resetSession(active);
|
|
1741
|
-
setConn("error", error);
|
|
1742
|
-
}
|
|
1743
|
-
throw error;
|
|
1744
|
-
}
|
|
1745
|
-
};
|
|
1746
|
-
const subscribe = (streamFor, onValue, staleAfterFor) => {
|
|
1747
|
-
if (closed) return () => {
|
|
1748
|
-
};
|
|
1749
|
-
let fiber = null;
|
|
1750
|
-
let unsubscribed = false;
|
|
1751
|
-
let retryTimer = null;
|
|
1752
|
-
let watchdogTimer = null;
|
|
1753
|
-
let lastValueAt = 0;
|
|
1754
|
-
let staleAfterMs = Number.POSITIVE_INFINITY;
|
|
1755
|
-
let watchdogRestart = false;
|
|
1756
|
-
const stopFiber = () => {
|
|
1757
|
-
if (!fiber) return;
|
|
1758
|
-
const current = fiber;
|
|
1759
|
-
fiber = null;
|
|
1760
|
-
fibers.delete(current);
|
|
1761
|
-
void (session?.runtime.runPromise(Fiber.interrupt(current)) ?? Effect.runPromise(Fiber.interrupt(current))).catch(() => {
|
|
1762
|
-
});
|
|
1763
|
-
};
|
|
1764
|
-
const scheduleRetry = () => {
|
|
1765
|
-
if (closed || unsubscribed || retryTimer) return;
|
|
1766
|
-
retryTimer = setTimeout(() => {
|
|
1767
|
-
retryTimer = null;
|
|
1768
|
-
start();
|
|
1769
|
-
}, options.reconnectBaseDelayMs ?? 250);
|
|
1770
|
-
retryTimer.unref?.();
|
|
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
|
-
}
|
|
1783
|
-
const start = () => {
|
|
1784
|
-
void (async () => {
|
|
1785
|
-
try {
|
|
1786
|
-
const active = await ensureSession();
|
|
1787
|
-
if (closed || unsubscribed) return;
|
|
1788
|
-
lastValueAt = Date.now();
|
|
1789
|
-
staleAfterMs = options.snapshotStaleFloorMs ?? 9e4;
|
|
1790
|
-
const currentFiber = active.runtime.runFork(
|
|
1791
|
-
TokmonRpcClient.use(
|
|
1792
|
-
(client) => streamFor(client).pipe(
|
|
1793
|
-
Stream.runForEach(
|
|
1794
|
-
(value) => Effect.sync(() => {
|
|
1795
|
-
lastValueAt = Date.now();
|
|
1796
|
-
if (staleAfterFor) staleAfterMs = staleAfterFor(value);
|
|
1797
|
-
try {
|
|
1798
|
-
onValue(value);
|
|
1799
|
-
} catch {
|
|
1800
|
-
}
|
|
1801
|
-
})
|
|
1802
|
-
)
|
|
1803
|
-
)
|
|
1804
|
-
)
|
|
1805
|
-
);
|
|
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();
|
|
1818
|
-
});
|
|
1819
|
-
} catch (error) {
|
|
1820
|
-
if (!closed && !unsubscribed) {
|
|
1821
|
-
resetSession();
|
|
1822
|
-
setConn("error", error);
|
|
1823
|
-
scheduleRetry();
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
})();
|
|
1827
|
-
};
|
|
1828
|
-
start();
|
|
1829
|
-
return () => {
|
|
1830
|
-
unsubscribed = true;
|
|
1831
|
-
if (retryTimer) {
|
|
1832
|
-
clearTimeout(retryTimer);
|
|
1833
|
-
retryTimer = null;
|
|
1834
|
-
}
|
|
1835
|
-
if (watchdogTimer) {
|
|
1836
|
-
clearInterval(watchdogTimer);
|
|
1837
|
-
watchdogTimer = null;
|
|
1838
|
-
}
|
|
1839
|
-
stopFiber();
|
|
1840
|
-
};
|
|
1841
|
-
};
|
|
1842
|
-
return {
|
|
1843
|
-
getConfig: () => run((client) => client[TOKMON_WS_METHODS.getConfig]({})).then((state) => state),
|
|
1844
|
-
setConfig: (config) => run((client) => client[TOKMON_WS_METHODS.setConfig](config)).then((state) => state),
|
|
1845
|
-
refresh: (scope = "all") => run((client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
|
|
1846
|
-
browseFs: (path) => run((client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
|
|
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
|
-
),
|
|
1852
|
-
subscribeConfig: (onConfig) => subscribe((client) => client[TOKMON_WS_METHODS.config]({}).pipe(Stream.map((value) => value)), onConfig),
|
|
1853
|
-
async close() {
|
|
1854
|
-
if (closed) return;
|
|
1855
|
-
closed = true;
|
|
1856
|
-
setConn("closed");
|
|
1857
|
-
const active = [...fibers];
|
|
1858
|
-
fibers.clear();
|
|
1859
|
-
const activeSession = session ?? await sessionPromise?.catch(() => null) ?? null;
|
|
1860
|
-
await Promise.all(active.map(
|
|
1861
|
-
(fiber) => (activeSession?.runtime.runPromise(Fiber.interrupt(fiber)) ?? Effect.runPromise(Fiber.interrupt(fiber))).catch(() => {
|
|
1862
|
-
})
|
|
1863
|
-
));
|
|
1864
|
-
session = null;
|
|
1865
|
-
sessionPromise = null;
|
|
1866
|
-
if (activeSession) {
|
|
1867
|
-
await activeSession.runtime.dispose().catch(() => {
|
|
1868
|
-
});
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
};
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
// src/client/use-daemon.ts
|
|
1875
|
-
function useDaemon(baseUrl, wsToken) {
|
|
1612
|
+
function useDaemon(baseUrl) {
|
|
1876
1613
|
const [snapshot, setSnapshot] = useState3(null);
|
|
1877
1614
|
const [conn, setConn] = useState3("connecting");
|
|
1878
1615
|
const [config, setConfigState] = useState3(null);
|
|
1879
1616
|
const client = useMemo(() => {
|
|
1880
|
-
if (!baseUrl
|
|
1617
|
+
if (!baseUrl) return null;
|
|
1881
1618
|
return createDaemonRpcClient(baseUrl, {
|
|
1882
1619
|
transport: "node",
|
|
1883
|
-
wsToken,
|
|
1884
1620
|
onConn: (state) => {
|
|
1885
1621
|
if (state !== "closed") setConn(state);
|
|
1886
1622
|
}
|
|
1887
1623
|
});
|
|
1888
|
-
}, [baseUrl
|
|
1624
|
+
}, [baseUrl]);
|
|
1889
1625
|
const clientRef = useRef2(client);
|
|
1890
1626
|
clientRef.current = client;
|
|
1891
1627
|
useEffect3(() => {
|
|
@@ -2383,6 +2119,10 @@ function handleSettings(input, key, ctx) {
|
|
|
2383
2119
|
}
|
|
2384
2120
|
if (index === 7 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2385
2121
|
updateConfig((current) => ({ ...current, defaultFocus: current.defaultFocus === "all" ? "last" : "all" }));
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
if (index === 8 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2125
|
+
updateConfig((current) => ({ ...current, allowNetworkAccess: !current.allowNetworkAccess }));
|
|
2386
2126
|
}
|
|
2387
2127
|
}
|
|
2388
2128
|
}
|
|
@@ -3304,10 +3044,10 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
3304
3044
|
|
|
3305
3045
|
// src/app.tsx
|
|
3306
3046
|
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3307
|
-
function App({ interval: cliInterval, initialConfig, baseUrl = null,
|
|
3308
|
-
const connected = mode === "connected" && baseUrl !== null
|
|
3047
|
+
function App({ interval: cliInterval, initialConfig, baseUrl = null, mode = "degraded" }) {
|
|
3048
|
+
const connected = mode === "connected" && baseUrl !== null;
|
|
3309
3049
|
const degraded = !connected;
|
|
3310
|
-
const daemon = useDaemon(connected ? baseUrl : null
|
|
3050
|
+
const daemon = useDaemon(connected ? baseUrl : null);
|
|
3311
3051
|
const { config, configSaveError, updateConfig } = useConfigState({
|
|
3312
3052
|
initialConfig,
|
|
3313
3053
|
cliInterval,
|
|
@@ -3643,16 +3383,16 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3643
3383
|
return;
|
|
3644
3384
|
}
|
|
3645
3385
|
if (webRef.current) {
|
|
3646
|
-
openUrl(webRef.current.
|
|
3386
|
+
openUrl(webRef.current.url);
|
|
3647
3387
|
return;
|
|
3648
3388
|
}
|
|
3649
3389
|
if (webStartingRef.current) return;
|
|
3650
3390
|
webStartingRef.current = true;
|
|
3651
3391
|
try {
|
|
3652
|
-
const { startWebServer } = await import("./server-
|
|
3392
|
+
const { startWebServer } = await import("./server-X63FGHIZ.js");
|
|
3653
3393
|
const ctrl = await startWebServer({ config: cfg, log: false });
|
|
3654
3394
|
webRef.current = ctrl;
|
|
3655
|
-
openUrl(ctrl.
|
|
3395
|
+
openUrl(ctrl.url);
|
|
3656
3396
|
} catch {
|
|
3657
3397
|
} finally {
|
|
3658
3398
|
webStartingRef.current = false;
|
|
@@ -3850,6 +3590,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3850
3590
|
glyphs().middot,
|
|
3851
3591
|
" showing last known data"
|
|
3852
3592
|
] }),
|
|
3593
|
+
cfg.allowNetworkAccess && /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
|
|
3594
|
+
glyphs().warn,
|
|
3595
|
+
" unsafe network access enabled ",
|
|
3596
|
+
glyphs().middot,
|
|
3597
|
+
" dashboard may be reachable from your LAN after daemon restart"
|
|
3598
|
+
] }),
|
|
3853
3599
|
configSaveError && /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
|
|
3854
3600
|
glyphs().warn,
|
|
3855
3601
|
" ",
|
|
@@ -3970,7 +3716,6 @@ async function bootstrapInk({ interval, config, daemon, mode }) {
|
|
|
3970
3716
|
interval,
|
|
3971
3717
|
initialConfig: config,
|
|
3972
3718
|
baseUrl: daemon.baseUrl,
|
|
3973
|
-
wsToken: daemon.wsToken,
|
|
3974
3719
|
mode
|
|
3975
3720
|
}
|
|
3976
3721
|
) })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
cacheDir
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5CWOJMAH.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";
|