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.
Files changed (37) hide show
  1. package/README.md +23 -5
  2. package/dist/{bootstrap-ink-BIOLTP7F.js → bootstrap-ink-HL2BYNW3.js} +37 -292
  3. package/dist/{chunk-RM3ZE44Z.js → chunk-3TJVFKXV.js} +1 -1
  4. package/dist/chunk-42H7R376.js +298 -0
  5. package/dist/chunk-45ZP755H.js +275 -0
  6. package/dist/{chunk-DJPUYMZM.js → chunk-5CWOJMAH.js} +2 -0
  7. package/dist/{chunk-2AFQKH5O.js → chunk-O27I2XFN.js} +278 -539
  8. package/dist/{chunk-2K4L4QQK.js → chunk-REO4O5VE.js} +68 -79
  9. package/dist/{chunk-Q2DSDWO2.js → chunk-SMPY52EV.js} +1 -30
  10. package/dist/{daemon-handle-6F2IB6CU.js → chunk-W2WGBXZG.js} +8 -9
  11. package/dist/cli-command-TLGIGF63.js +526 -0
  12. package/dist/cli.js +30 -5
  13. package/dist/{config-5CNJN3P7.js → config-QC5QSP3G.js} +1 -1
  14. package/dist/{daemon-PP3QAT6E.js → daemon-F4EGY77J.js} +43 -17
  15. package/dist/daemon-handle-NLCLYWFU.js +10 -0
  16. package/dist/server-X63FGHIZ.js +11 -0
  17. package/dist/web/assets/{Area-BALPhJJp.js → Area-DCES17aZ.js} +1 -1
  18. package/dist/web/assets/{analytics-uHVeOhLh.js → analytics-DDW6zcnj.js} +2 -2
  19. package/dist/web/assets/{breakdown-CR5dNNSG.js → breakdown-D5LPPtqh.js} +1 -1
  20. package/dist/web/assets/{chart-DRkqw0fm.js → chart-bHY7MQV0.js} +1 -1
  21. package/dist/web/assets/{explore-C-bHaX-I.js → explore-DdWB-Pu7.js} +1 -1
  22. package/dist/web/assets/index-C9jxb2o3.js +83 -0
  23. package/dist/web/assets/index-m0KfWnN2.css +1 -0
  24. package/dist/web/assets/{models-Qc2daHCo.js → models-o1wJnG6w.js} +2 -2
  25. package/dist/web/assets/{overview-D4VG2awg.js → overview-wq2uAEk4.js} +2 -2
  26. package/dist/web/assets/{panel-C1CsBs1H.js → panel-DBnK1oy0.js} +1 -1
  27. package/dist/web/assets/{primitives-S4UDgnZq.js → primitives-CBmteetD.js} +1 -1
  28. package/dist/web/assets/settings-sheet-CZ6GEPl3.js +1 -0
  29. package/dist/web/assets/{share-sheet-BpZBju-k.js → share-sheet-DUee3FlK.js} +1 -1
  30. package/dist/web/assets/{timeline-Cp-fyeuK.js → timeline-BxHwOJA_.js} +1 -1
  31. package/dist/web/assets/{use-dialog-trap-DaKbCG1Z.js → use-dialog-trap-30EqxYF-.js} +1 -1
  32. package/dist/web/index.html +2 -2
  33. package/package.json +1 -1
  34. package/dist/server-UHQEOVQH.js +0 -10
  35. package/dist/web/assets/index-C1jQdagn.js +0 -83
  36. package/dist/web/assets/index-DSj-Bxov.css +0 -1
  37. 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 authenticated local URL.
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` only and reads the same data read-only — nothing leaves your machine. It renders instantly from a cached snapshot, then streams live updates over an authenticated WebSocket, 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.
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 WebSocket — 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.
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
- PROVIDERS,
4
- PROVIDER_ORDER,
5
- TOKMON_WS_METHODS,
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-2AFQKH5O.js";
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-DJPUYMZM.js";
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 = 8;
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 || !wsToken) return null;
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, wsToken]);
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, wsToken = null, mode = "degraded" }) {
3308
- const connected = mode === "connected" && baseUrl !== null && wsToken !== 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, connected ? wsToken : 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.browserUrl);
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-UHQEOVQH.js");
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.browserUrl);
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-DJPUYMZM.js";
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";