tokmon 0.22.7 → 0.23.1
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 +1 -0
- package/dist/{bootstrap-ink-OHPQZKGB.js → bootstrap-ink-UD3KDTOB.js} +1113 -818
- package/dist/{chunk-EMQL5P5J.js → chunk-AON6GE7D.js} +368 -280
- package/dist/{chunk-TNIADJ3W.js → chunk-DJPUYMZM.js} +49 -16
- package/dist/chunk-HXDAOOSI.js +154 -0
- package/dist/{chunk-I4USANOF.js → chunk-PGQK7MOE.js} +879 -391
- package/dist/chunk-RM3ZE44Z.js +193 -0
- package/dist/cli.js +93 -54
- package/dist/{config-WLRQHLVJ.js → config-5CNJN3P7.js} +1 -1
- package/dist/daemon-QGCI46US.js +193 -0
- package/dist/daemon-handle-4F4TXGXJ.js +163 -0
- package/dist/server-4CDZMLH6.js +10 -0
- package/dist/web/assets/Area-DGo9K-U3.js +2 -0
- package/dist/web/assets/analytics-BOhbhV5Z.js +2 -0
- package/dist/web/assets/breakdown-CO04nSN-.js +4 -0
- package/dist/web/assets/chart-bNxdfXxA.js +69 -0
- package/dist/web/assets/explore-sZnmjzAj.js +22 -0
- package/dist/web/assets/index-DSj-Bxov.css +1 -0
- package/dist/web/assets/index-DnDr1VUr.js +83 -0
- package/dist/web/assets/models-Cl6D8VVF.js +2 -0
- package/dist/web/assets/overview-C18nvaAK.js +2 -0
- package/dist/web/assets/panel-DTjMnHaz.js +1 -0
- package/dist/web/assets/primitives-Dj_9YxTF.js +1 -0
- package/dist/web/assets/settings-sheet-0BGb5Ahv.js +1 -0
- package/dist/web/assets/share-sheet-BHtt-OmT.js +2 -0
- package/dist/web/assets/timeline-DmzB8Z3W.js +1 -0
- package/dist/web/assets/use-dialog-trap-DeXFtxx8.js +1 -0
- package/dist/web/index.html +3 -2
- package/package.json +9 -5
- package/dist/daemon-IP3UCTPB.js +0 -213
- package/dist/daemon-handle-HLSKLMWU.js +0 -142
- package/dist/server-GA2FKYDQ.js +0 -9
- package/dist/web/assets/breakdown-DU2j1sQ8.js +0 -4
- package/dist/web/assets/index-BCaPGYQA.css +0 -1
- package/dist/web/assets/index-ygZyXDLT.js +0 -173
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/web/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
- package/dist/web/assets/timeline-CO2B2c-0.js +0 -1
|
@@ -19,7 +19,10 @@ import {
|
|
|
19
19
|
time,
|
|
20
20
|
tokens,
|
|
21
21
|
withTimeout
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-PGQK7MOE.js";
|
|
23
|
+
import {
|
|
24
|
+
glyphs
|
|
25
|
+
} from "./chunk-RF4GGQGM.js";
|
|
23
26
|
import {
|
|
24
27
|
COLOR_PALETTE,
|
|
25
28
|
DEFAULTS,
|
|
@@ -34,54 +37,17 @@ import {
|
|
|
34
37
|
sanitizeTyped,
|
|
35
38
|
saveConfigSync,
|
|
36
39
|
snapshotCacheFile
|
|
37
|
-
} from "./chunk-
|
|
38
|
-
import {
|
|
39
|
-
glyphs
|
|
40
|
-
} from "./chunk-RF4GGQGM.js";
|
|
40
|
+
} from "./chunk-DJPUYMZM.js";
|
|
41
41
|
|
|
42
42
|
// src/bootstrap-ink.tsx
|
|
43
43
|
import { render } from "ink";
|
|
44
44
|
import { MouseProvider } from "@zenobius/ink-mouse";
|
|
45
45
|
|
|
46
46
|
// src/app.tsx
|
|
47
|
-
import { useState as
|
|
48
|
-
import { Box as Box11, Text as
|
|
47
|
+
import { useState as useState10, useEffect as useEffect9, useCallback as useCallback7, useRef as useRef8, useMemo as useMemo2 } from "react";
|
|
48
|
+
import { Box as Box11, Text as Text12, useInput, useApp } from "ink";
|
|
49
49
|
import { useMouse } from "@zenobius/ink-mouse";
|
|
50
50
|
|
|
51
|
-
// src/config-sync.ts
|
|
52
|
-
function deepEqual(a, b) {
|
|
53
|
-
if (a === b) return true;
|
|
54
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
55
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
56
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
57
|
-
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
const ka = Object.keys(a);
|
|
61
|
-
const kb = Object.keys(b);
|
|
62
|
-
if (ka.length !== kb.length) return false;
|
|
63
|
-
for (const k of ka) {
|
|
64
|
-
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
65
|
-
if (!deepEqual(a[k], b[k])) return false;
|
|
66
|
-
}
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
function reconcileDaemonConfig(previous, daemonConfig, pendingLocalConfig) {
|
|
70
|
-
if (pendingLocalConfig) {
|
|
71
|
-
if (deepEqual(daemonConfig, pendingLocalConfig)) {
|
|
72
|
-
return { config: previous && deepEqual(previous, daemonConfig) ? previous : daemonConfig, pendingLocalConfig: null };
|
|
73
|
-
}
|
|
74
|
-
return { config: previous ?? pendingLocalConfig, pendingLocalConfig };
|
|
75
|
-
}
|
|
76
|
-
if (previous && deepEqual(previous, daemonConfig)) {
|
|
77
|
-
return { config: previous, pendingLocalConfig: null };
|
|
78
|
-
}
|
|
79
|
-
if (previous?.onboarded === true && daemonConfig.onboarded === false) {
|
|
80
|
-
return { config: previous, pendingLocalConfig: null };
|
|
81
|
-
}
|
|
82
|
-
return { config: daemonConfig, pendingLocalConfig: null };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
51
|
// src/ui/shared.tsx
|
|
86
52
|
import { appendFileSync } from "fs";
|
|
87
53
|
import { memo, useEffect, useRef, useState } from "react";
|
|
@@ -1541,7 +1507,7 @@ function osc8(text, url) {
|
|
|
1541
1507
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1542
1508
|
var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
1543
1509
|
const inner = cols - 4;
|
|
1544
|
-
const BASE = "by David Ilie (davidilie.com) \xB7 O=repo W=web s=settings q=quit".length;
|
|
1510
|
+
const BASE = "by David Ilie (davidilie.com) \xB7 r/R=refresh O=repo W=web s=settings q=quit".length;
|
|
1545
1511
|
const optHint = (glyphs().shift === "\u21E7" ? "\u2325" : "opt") + "-click links ";
|
|
1546
1512
|
const OPT = IS_APPLE_TERMINAL ? optHint.length : 0;
|
|
1547
1513
|
const JUMP = "0-9=jump a/A=cycle ".length;
|
|
@@ -1550,7 +1516,7 @@ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
|
1550
1516
|
const showJump = hasAccounts && inner >= BASE + (showOpt ? OPT : 0) + JUMP + (paginated ? PAGE : 0);
|
|
1551
1517
|
const showPage = paginated && inner >= BASE + (showOpt ? OPT : 0) + (showJump ? JUMP : 0) + PAGE;
|
|
1552
1518
|
if (inner < BASE) {
|
|
1553
|
-
return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexWrap: "nowrap", children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, wrap: "truncate-end", children: "O=repo W=web s=settings q=quit" }) });
|
|
1519
|
+
return /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexWrap: "nowrap", children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, wrap: "truncate-end", children: "r/R=refresh O=repo W=web s=settings q=quit" }) });
|
|
1554
1520
|
}
|
|
1555
1521
|
return /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexWrap: "nowrap", children: [
|
|
1556
1522
|
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "by " }),
|
|
@@ -1560,7 +1526,7 @@ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
|
1560
1526
|
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
1561
1527
|
") ",
|
|
1562
1528
|
glyphs().middot,
|
|
1563
|
-
" O=repo W=web s=settings "
|
|
1529
|
+
" r/R=refresh O=repo W=web s=settings "
|
|
1564
1530
|
] }),
|
|
1565
1531
|
showOpt && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: optHint }),
|
|
1566
1532
|
showJump && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "0-9=jump a/A=cycle " }),
|
|
@@ -1570,30 +1536,52 @@ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
|
1570
1536
|
});
|
|
1571
1537
|
|
|
1572
1538
|
// src/ui/tiny-fallback.tsx
|
|
1573
|
-
import { Box as Box10, Text as
|
|
1539
|
+
import { Box as Box10, Text as Text11 } from "ink";
|
|
1540
|
+
|
|
1541
|
+
// src/ui/refresh-status.tsx
|
|
1542
|
+
import { memo as memo7 } from "react";
|
|
1543
|
+
import { Text as Text10 } from "ink";
|
|
1574
1544
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1575
|
-
function
|
|
1545
|
+
var RefreshStatusLine = memo7(function RefreshStatusLine2({ status }) {
|
|
1546
|
+
if (status.phase === "idle") return null;
|
|
1547
|
+
if (status.phase === "refreshing") return /* @__PURE__ */ jsx10(Spinner, { label: `${status.message}${glyphs().ellipsis}` });
|
|
1548
|
+
if (status.phase === "success") return /* @__PURE__ */ jsxs10(Text10, { color: "green", children: [
|
|
1549
|
+
glyphs().check,
|
|
1550
|
+
" ",
|
|
1551
|
+
status.message
|
|
1552
|
+
] });
|
|
1553
|
+
return /* @__PURE__ */ jsxs10(Text10, { color: "red", children: [
|
|
1554
|
+
glyphs().warn,
|
|
1555
|
+
" ",
|
|
1556
|
+
status.message
|
|
1557
|
+
] });
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
// src/ui/tiny-fallback.tsx
|
|
1561
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1562
|
+
function TinyFallback({ groups, stats, rows, cols, refreshStatus }) {
|
|
1576
1563
|
const maxLines = Math.max(1, rows - 4);
|
|
1577
1564
|
const visible = groups.slice(0, maxLines);
|
|
1578
1565
|
const hidden = groups.length - visible.length;
|
|
1579
1566
|
const w = Math.max(8, cols - 2);
|
|
1580
|
-
return /* @__PURE__ */
|
|
1581
|
-
/* @__PURE__ */
|
|
1567
|
+
return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", paddingX: 1, height: rows, overflow: "hidden", children: [
|
|
1568
|
+
/* @__PURE__ */ jsxs11(Text11, { bold: true, color: "greenBright", children: [
|
|
1582
1569
|
glyphs().dotSel,
|
|
1583
1570
|
" tokmon"
|
|
1584
1571
|
] }),
|
|
1585
|
-
groups.length === 0 ? /* @__PURE__ */
|
|
1572
|
+
groups.length === 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
1586
1573
|
"No providers ",
|
|
1587
1574
|
glyphs().emDash,
|
|
1588
1575
|
" s=settings"
|
|
1589
|
-
] }) : visible.map((g) => /* @__PURE__ */
|
|
1590
|
-
hidden > 0 && /* @__PURE__ */
|
|
1576
|
+
] }) : visible.map((g) => /* @__PURE__ */ jsx11(TinyRow, { provider: g.provider, accounts: g.accounts, stats, width: w }, g.provider)),
|
|
1577
|
+
hidden > 0 && /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
1591
1578
|
"+",
|
|
1592
1579
|
hidden,
|
|
1593
1580
|
" more (enlarge terminal)"
|
|
1594
1581
|
] }),
|
|
1595
|
-
/* @__PURE__ */
|
|
1596
|
-
/* @__PURE__ */
|
|
1582
|
+
/* @__PURE__ */ jsx11(RefreshStatusLine, { status: refreshStatus }),
|
|
1583
|
+
/* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
|
|
1584
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: "R=refresh s=settings q=quit" })
|
|
1597
1585
|
] });
|
|
1598
1586
|
}
|
|
1599
1587
|
function TinyRow({ provider, accounts, stats, width }) {
|
|
@@ -1604,14 +1592,14 @@ function TinyRow({ provider, accounts, stats, width }) {
|
|
|
1604
1592
|
const pctMetric = billings.flatMap((b) => b?.metrics ?? []).find((m) => m.format.kind === "percent");
|
|
1605
1593
|
const detail = meta.hasUsage ? `${currency(todayCost)} today` : pctMetric ? `${Math.round(pctMetric.used)}%` : "billing";
|
|
1606
1594
|
const name = truncateName(meta.name, Math.max(4, width - 18));
|
|
1607
|
-
return /* @__PURE__ */
|
|
1608
|
-
/* @__PURE__ */
|
|
1595
|
+
return /* @__PURE__ */ jsxs11(Box10, { width, children: [
|
|
1596
|
+
/* @__PURE__ */ jsxs11(Text11, { color: meta.color, children: [
|
|
1609
1597
|
glyphs().dot,
|
|
1610
1598
|
" "
|
|
1611
1599
|
] }),
|
|
1612
|
-
/* @__PURE__ */
|
|
1613
|
-
/* @__PURE__ */
|
|
1614
|
-
/* @__PURE__ */
|
|
1600
|
+
/* @__PURE__ */ jsx11(Text11, { bold: true, color: meta.color, children: name }),
|
|
1601
|
+
/* @__PURE__ */ jsx11(Box10, { flexGrow: 1 }),
|
|
1602
|
+
/* @__PURE__ */ jsx11(Text11, { dimColor: true, children: detail })
|
|
1615
1603
|
] });
|
|
1616
1604
|
}
|
|
1617
1605
|
|
|
@@ -1635,6 +1623,8 @@ function toWsUrl(baseUrl, token) {
|
|
|
1635
1623
|
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
1636
1624
|
throw new Error(`unsupported daemon RPC protocol: ${url.protocol}`);
|
|
1637
1625
|
}
|
|
1626
|
+
url.hash = "";
|
|
1627
|
+
url.searchParams.delete("tokmonToken");
|
|
1638
1628
|
url.pathname = TOKMON_WS_PATH;
|
|
1639
1629
|
if (token) url.searchParams.set("wsToken", token);
|
|
1640
1630
|
return url.toString();
|
|
@@ -1821,12 +1811,12 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1821
1811
|
};
|
|
1822
1812
|
};
|
|
1823
1813
|
return {
|
|
1824
|
-
getConfig: () => run((client) => client[TOKMON_WS_METHODS.getConfig]({})),
|
|
1825
|
-
setConfig: (config) => run((client) => client[TOKMON_WS_METHODS.setConfig](config)),
|
|
1814
|
+
getConfig: () => run((client) => client[TOKMON_WS_METHODS.getConfig]({})).then((state) => state),
|
|
1815
|
+
setConfig: (config) => run((client) => client[TOKMON_WS_METHODS.setConfig](config)).then((state) => state),
|
|
1826
1816
|
refresh: (scope = "all") => run((client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
|
|
1827
1817
|
browseFs: (path) => run((client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
|
|
1828
|
-
subscribeSnapshot: (onSnapshot) => subscribe((client) => client[TOKMON_WS_METHODS.snapshot]({}), onSnapshot),
|
|
1829
|
-
subscribeConfig: (onConfig) => subscribe((client) => client[TOKMON_WS_METHODS.config]({}), onConfig),
|
|
1818
|
+
subscribeSnapshot: (onSnapshot) => subscribe((client) => client[TOKMON_WS_METHODS.snapshot]({}).pipe(Stream.map((value) => value)), onSnapshot),
|
|
1819
|
+
subscribeConfig: (onConfig) => subscribe((client) => client[TOKMON_WS_METHODS.config]({}).pipe(Stream.map((value) => value)), onConfig),
|
|
1830
1820
|
async close() {
|
|
1831
1821
|
if (closed) return;
|
|
1832
1822
|
closed = true;
|
|
@@ -1883,7 +1873,7 @@ function useDaemon(baseUrl, wsToken) {
|
|
|
1883
1873
|
snapshot,
|
|
1884
1874
|
conn,
|
|
1885
1875
|
config,
|
|
1886
|
-
setConfig: (next) => requireClient().setConfig(next),
|
|
1876
|
+
setConfig: (next, expectedRevision) => requireClient().setConfig({ config: next, expectedRevision }),
|
|
1887
1877
|
refresh: (scope) => requireClient().refresh(scope),
|
|
1888
1878
|
browse: (path) => requireClient().browseFs(path)
|
|
1889
1879
|
};
|
|
@@ -1950,19 +1940,6 @@ function upsert(prev, account, patch) {
|
|
|
1950
1940
|
next.set(account.id, { ...cur, account, ...patch });
|
|
1951
1941
|
return next;
|
|
1952
1942
|
}
|
|
1953
|
-
async function fetchScopeTable(scope, tz) {
|
|
1954
|
-
const tables = await Promise.all(scope.map(async (acc) => {
|
|
1955
|
-
const provider = PROVIDERS[acc.providerId];
|
|
1956
|
-
if (!provider.fetchTable) return null;
|
|
1957
|
-
try {
|
|
1958
|
-
return await provider.fetchTable(acc, tz);
|
|
1959
|
-
} catch {
|
|
1960
|
-
return null;
|
|
1961
|
-
}
|
|
1962
|
-
}));
|
|
1963
|
-
const valid = tables.filter((t) => t !== null);
|
|
1964
|
-
return coalesceTables(valid);
|
|
1965
|
-
}
|
|
1966
1943
|
function sortLabel(entry) {
|
|
1967
1944
|
if (entry.dir === "up") return `${entry.label} ${glyphs().arrowU}`;
|
|
1968
1945
|
if (entry.dir === "down") return `${entry.label} ${glyphs().arrowD}`;
|
|
@@ -2028,555 +2005,564 @@ function filterTokenRows(rows, q) {
|
|
|
2028
2005
|
return rows.filter((r) => r.label.toLowerCase().includes(s) || r.models.some((m) => m.toLowerCase().includes(s)));
|
|
2029
2006
|
}
|
|
2030
2007
|
|
|
2031
|
-
// src/ui/keybindings.ts
|
|
2032
|
-
function
|
|
2033
|
-
const {
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
searchMode,
|
|
2059
|
-
setSearchMode,
|
|
2060
|
-
search,
|
|
2061
|
-
setSearch,
|
|
2062
|
-
setSearchCaret,
|
|
2063
|
-
searchValueRef,
|
|
2064
|
-
searchCaretRef,
|
|
2065
|
-
showLoader,
|
|
2066
|
-
configReady,
|
|
2067
|
-
toggleWeb,
|
|
2068
|
-
settingsCursor,
|
|
2069
|
-
settingsTab,
|
|
2070
|
-
setSettingsTab,
|
|
2071
|
-
setShowSettings,
|
|
2072
|
-
cfg,
|
|
2073
|
-
trackedAccountRows,
|
|
2074
|
-
moveAccount,
|
|
2075
|
-
setSettingsCursor,
|
|
2076
|
-
toggleProvider,
|
|
2077
|
-
openEditAccount,
|
|
2078
|
-
openConfigureAccount,
|
|
2079
|
-
deleteAccount,
|
|
2080
|
-
openAddAccount,
|
|
2081
|
-
cycleAccount,
|
|
2082
|
-
setTab,
|
|
2083
|
-
resetView,
|
|
2084
|
-
slots,
|
|
2085
|
-
dashPaginated,
|
|
2086
|
-
dashPageCount,
|
|
2087
|
-
setDashPage,
|
|
2088
|
-
cycleTableProvider,
|
|
2089
|
-
setExpanded,
|
|
2090
|
-
setSort,
|
|
2091
|
-
SORTS_FOR,
|
|
2092
|
-
cycleTableModel: cycleTableModel2,
|
|
2093
|
-
setView,
|
|
2094
|
-
cursor,
|
|
2095
|
-
rowCountRef,
|
|
2096
|
-
rows,
|
|
2097
|
-
setCursor,
|
|
2098
|
-
clampRow
|
|
2099
|
-
} = ctx;
|
|
2100
|
-
if (showPicker) {
|
|
2101
|
-
if (input === "q") {
|
|
2102
|
-
exit();
|
|
2103
|
-
return;
|
|
2104
|
-
}
|
|
2105
|
-
const startIdx = pickerProviders.length;
|
|
2106
|
-
if (key.upArrow) {
|
|
2107
|
-
setOnboardCursor((c) => Math.max(0, c - 1));
|
|
2108
|
-
return;
|
|
2109
|
-
}
|
|
2110
|
-
if (key.downArrow) {
|
|
2111
|
-
setOnboardCursor((c) => Math.min(startIdx, c + 1));
|
|
2008
|
+
// src/ui/keybindings-editors.ts
|
|
2009
|
+
function handleAccountEditor(input, key, editor, text) {
|
|
2010
|
+
const { form, setForm, commit, cycleField, cycleProvider, cycleColor } = editor;
|
|
2011
|
+
if (!form) return;
|
|
2012
|
+
if (key.escape) {
|
|
2013
|
+
setForm(null);
|
|
2014
|
+
return;
|
|
2015
|
+
}
|
|
2016
|
+
if (key.ctrl && input === "s") {
|
|
2017
|
+
commit();
|
|
2018
|
+
return;
|
|
2019
|
+
}
|
|
2020
|
+
if (key.tab) {
|
|
2021
|
+
cycleField(key.shift ? -1 : 1);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
if (key.upArrow) {
|
|
2025
|
+
cycleField(-1);
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
if (key.downArrow) {
|
|
2029
|
+
cycleField(1);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
if (form.field === "provider") {
|
|
2033
|
+
if (key.leftArrow) {
|
|
2034
|
+
cycleProvider(-1);
|
|
2112
2035
|
return;
|
|
2113
2036
|
}
|
|
2114
|
-
if (
|
|
2115
|
-
|
|
2037
|
+
if (key.rightArrow) {
|
|
2038
|
+
cycleProvider(1);
|
|
2116
2039
|
return;
|
|
2117
2040
|
}
|
|
2118
2041
|
if (key.return) {
|
|
2119
|
-
|
|
2120
|
-
else toggleOnboard(onboardCursor);
|
|
2042
|
+
setForm((value) => value && { ...value, field: "name", caret: value.name.length });
|
|
2121
2043
|
return;
|
|
2122
2044
|
}
|
|
2123
2045
|
return;
|
|
2124
2046
|
}
|
|
2125
|
-
if (
|
|
2126
|
-
if (key.
|
|
2127
|
-
|
|
2128
|
-
return;
|
|
2129
|
-
}
|
|
2130
|
-
if (key.ctrl && input === "s") {
|
|
2131
|
-
commitAccountForm();
|
|
2047
|
+
if (form.field === "color") {
|
|
2048
|
+
if (key.leftArrow) {
|
|
2049
|
+
cycleColor(-1);
|
|
2132
2050
|
return;
|
|
2133
2051
|
}
|
|
2134
|
-
if (key.
|
|
2135
|
-
|
|
2052
|
+
if (key.rightArrow) {
|
|
2053
|
+
cycleColor(1);
|
|
2136
2054
|
return;
|
|
2137
2055
|
}
|
|
2138
|
-
if (key.
|
|
2139
|
-
|
|
2056
|
+
if (key.return) {
|
|
2057
|
+
commit();
|
|
2140
2058
|
return;
|
|
2141
2059
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
const field = form.field;
|
|
2063
|
+
if (key.leftArrow) {
|
|
2064
|
+
setForm((value) => value && { ...value, caret: clampCaret(value.caret - 1, value[field].length) });
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
if (key.rightArrow) {
|
|
2068
|
+
setForm((value) => value && { ...value, caret: clampCaret(value.caret + 1, value[field].length) });
|
|
2069
|
+
return;
|
|
2070
|
+
}
|
|
2071
|
+
if (key.ctrl && input === "a") {
|
|
2072
|
+
setForm((value) => value && { ...value, caret: 0 });
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
if (key.ctrl && input === "e") {
|
|
2076
|
+
setForm((value) => value && { ...value, caret: value[field].length });
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
if (key.return) {
|
|
2080
|
+
if (field === "name" && form.name.trim() === "") {
|
|
2081
|
+
setForm((value) => value && { ...value, error: "Name required", caret: value.name.length });
|
|
2144
2082
|
return;
|
|
2145
2083
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2084
|
+
setForm((value) => value && {
|
|
2085
|
+
...value,
|
|
2086
|
+
field: field === "name" ? "homeDir" : "color",
|
|
2087
|
+
caret: field === "name" ? value.homeDir.length : value.caret
|
|
2088
|
+
});
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
if (key.backspace || key.delete) {
|
|
2092
|
+
setForm((value) => {
|
|
2093
|
+
if (!value || value.field !== "name" && value.field !== "homeDir") return value;
|
|
2094
|
+
const result = spliceBackspace(value[value.field], value.caret);
|
|
2095
|
+
return { ...value, [value.field]: result.value, caret: result.caret, error: null };
|
|
2096
|
+
});
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
if (text.isPrintable(input, key)) {
|
|
2100
|
+
const clean = sanitizeTyped(input);
|
|
2101
|
+
if (clean) text.insert(clean);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
function handleTimezoneEditor(input, key, editor, text, updateConfig) {
|
|
2105
|
+
const { value, setValue, setError, setCaret, valueRef, caretRef } = editor;
|
|
2106
|
+
if (value === null) return;
|
|
2107
|
+
if (key.escape) {
|
|
2108
|
+
setValue(null);
|
|
2109
|
+
setError(null);
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
if (key.return) {
|
|
2113
|
+
const next = value.trim();
|
|
2114
|
+
if (next === "" || next.toLowerCase() === "system") {
|
|
2115
|
+
updateConfig((config) => ({ ...config, timezone: null }));
|
|
2116
|
+
setValue(null);
|
|
2117
|
+
setError(null);
|
|
2118
|
+
} else if (isValidTimezone(next)) {
|
|
2119
|
+
updateConfig((config) => ({ ...config, timezone: next }));
|
|
2120
|
+
setValue(null);
|
|
2121
|
+
setError(null);
|
|
2122
|
+
} else {
|
|
2123
|
+
setError(`Invalid: ${next}`);
|
|
2160
2124
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2125
|
+
return;
|
|
2126
|
+
}
|
|
2127
|
+
if (key.leftArrow) {
|
|
2128
|
+
setCaret((caret) => clampCaret(caret - 1, value.length));
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
if (key.rightArrow) {
|
|
2132
|
+
setCaret((caret) => clampCaret(caret + 1, value.length));
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
if (key.ctrl && input === "a") {
|
|
2136
|
+
setCaret(0);
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
if (key.ctrl && input === "e") {
|
|
2140
|
+
setCaret(value.length);
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
if (key.backspace || key.delete) {
|
|
2144
|
+
const result = spliceBackspace(valueRef.current, caretRef.current);
|
|
2145
|
+
valueRef.current = result.value;
|
|
2146
|
+
caretRef.current = result.caret;
|
|
2147
|
+
setValue(result.value);
|
|
2148
|
+
setCaret(result.caret);
|
|
2149
|
+
setError(null);
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
if (text.isPrintable(input, key)) {
|
|
2153
|
+
const clean = sanitizeTyped(input);
|
|
2154
|
+
if (clean) text.insert(clean);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
function handleSearchEditor(input, key, table, text) {
|
|
2158
|
+
const { search, setSearch, setSearchMode, setSearchCaret, searchValueRef, searchCaretRef } = table;
|
|
2159
|
+
if (key.return || key.escape) {
|
|
2160
|
+
setSearchMode(false);
|
|
2161
|
+
if (key.escape) {
|
|
2162
|
+
setSearch("");
|
|
2163
|
+
setSearchCaret(0);
|
|
2175
2164
|
}
|
|
2176
|
-
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
if (key.leftArrow) {
|
|
2168
|
+
setSearchCaret((caret) => clampCaret(caret - 1, search.length));
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
if (key.rightArrow) {
|
|
2172
|
+
setSearchCaret((caret) => clampCaret(caret + 1, search.length));
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
if (key.ctrl && input === "a") {
|
|
2176
|
+
setSearchCaret(0);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
if (key.ctrl && input === "e") {
|
|
2180
|
+
setSearchCaret(search.length);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
if (key.backspace || key.delete) {
|
|
2184
|
+
const result = spliceBackspace(searchValueRef.current, searchCaretRef.current);
|
|
2185
|
+
searchValueRef.current = result.value;
|
|
2186
|
+
searchCaretRef.current = result.caret;
|
|
2187
|
+
setSearch(result.value);
|
|
2188
|
+
setSearchCaret(result.caret);
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (text.isPrintable(input, key)) {
|
|
2192
|
+
const clean = sanitizeTyped(input);
|
|
2193
|
+
if (clean) text.insert(clean);
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// src/ui/keybindings-settings.ts
|
|
2198
|
+
function handleSettings(input, key, ctx) {
|
|
2199
|
+
const {
|
|
2200
|
+
settings,
|
|
2201
|
+
timezoneEditor,
|
|
2202
|
+
textInput,
|
|
2203
|
+
global: { config, updateConfig }
|
|
2204
|
+
} = ctx;
|
|
2205
|
+
const {
|
|
2206
|
+
cursor,
|
|
2207
|
+
tab,
|
|
2208
|
+
setShow,
|
|
2209
|
+
setTab,
|
|
2210
|
+
setCursor,
|
|
2211
|
+
trackedAccounts,
|
|
2212
|
+
moveAccount,
|
|
2213
|
+
toggleProvider,
|
|
2214
|
+
openEditAccount,
|
|
2215
|
+
openConfigureAccount,
|
|
2216
|
+
deleteAccount,
|
|
2217
|
+
openAddAccount
|
|
2218
|
+
} = settings;
|
|
2219
|
+
if (key.escape || input === "s") {
|
|
2220
|
+
setShow(false);
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
const switchTab = (direction) => {
|
|
2224
|
+
const index = SETTINGS_TABS.indexOf(tab);
|
|
2225
|
+
setTab(SETTINGS_TABS[(index + direction + SETTINGS_TABS.length) % SETTINGS_TABS.length]);
|
|
2226
|
+
setCursor(-1);
|
|
2227
|
+
timezoneEditor.setValue(null);
|
|
2228
|
+
timezoneEditor.setError(null);
|
|
2229
|
+
};
|
|
2230
|
+
const rowCount = tab === "general" ? GENERAL_ROWS : tab === "providers" ? PROVIDER_ORDER.length : trackedAccounts.length + 1;
|
|
2231
|
+
if (key.tab) {
|
|
2232
|
+
switchTab(key.shift ? -1 : 1);
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
if (input === "[") {
|
|
2236
|
+
switchTab(-1);
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
if (input === "]") {
|
|
2240
|
+
switchTab(1);
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
if (cursor < 0) {
|
|
2177
2244
|
if (key.leftArrow) {
|
|
2178
|
-
|
|
2245
|
+
switchTab(-1);
|
|
2179
2246
|
return;
|
|
2180
2247
|
}
|
|
2181
2248
|
if (key.rightArrow) {
|
|
2182
|
-
|
|
2183
|
-
return;
|
|
2184
|
-
}
|
|
2185
|
-
if (key.ctrl && input === "a") {
|
|
2186
|
-
setAccountForm((f) => f && { ...f, caret: 0 });
|
|
2249
|
+
switchTab(1);
|
|
2187
2250
|
return;
|
|
2188
2251
|
}
|
|
2189
|
-
if (key.
|
|
2190
|
-
|
|
2191
|
-
return;
|
|
2192
|
-
}
|
|
2193
|
-
if (key.return) {
|
|
2194
|
-
if (tf === "name" && accountForm.name.trim() === "") {
|
|
2195
|
-
setAccountForm((f) => f && { ...f, error: "Name required", caret: f.name.length });
|
|
2196
|
-
return;
|
|
2197
|
-
}
|
|
2198
|
-
setAccountForm((f) => f && { ...f, field: tf === "name" ? "homeDir" : "color", caret: tf === "name" ? f.homeDir.length : f.caret });
|
|
2252
|
+
if (key.downArrow || key.return) {
|
|
2253
|
+
setCursor(0);
|
|
2199
2254
|
return;
|
|
2200
2255
|
}
|
|
2201
|
-
if (key.
|
|
2202
|
-
|
|
2203
|
-
if (!f || f.field !== "name" && f.field !== "homeDir") return f;
|
|
2204
|
-
const r = spliceBackspace(f[f.field], f.caret);
|
|
2205
|
-
return { ...f, [f.field]: r.value, caret: r.caret, error: null };
|
|
2206
|
-
});
|
|
2256
|
+
if (key.upArrow) {
|
|
2257
|
+
setCursor(Math.max(0, rowCount - 1));
|
|
2207
2258
|
return;
|
|
2208
2259
|
}
|
|
2209
|
-
if (isPrintable(input, key)) {
|
|
2210
|
-
const clean = sanitizeTyped(input);
|
|
2211
|
-
if (clean) insertText(clean);
|
|
2212
|
-
}
|
|
2213
2260
|
return;
|
|
2214
2261
|
}
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2262
|
+
const selected = tab === "accounts" && cursor < trackedAccounts.length ? trackedAccounts[cursor] : null;
|
|
2263
|
+
if (selected?.source === "configured" && selected.explicitIndex !== void 0 && key.shift && (key.upArrow || key.downArrow)) {
|
|
2264
|
+
moveAccount(selected.explicitIndex, key.upArrow ? -1 : 1);
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
if (key.upArrow) {
|
|
2268
|
+
setCursor((current) => Math.max(-1, current - 1));
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
if (key.downArrow) {
|
|
2272
|
+
setCursor((current) => Math.min(rowCount - 1, current + 1));
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (tab === "general") {
|
|
2276
|
+
handleGeneralSetting(input, key, ctx);
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
if (tab === "providers") {
|
|
2280
|
+
if (cursor < PROVIDER_ORDER.length && (input === " " || key.return || key.leftArrow || key.rightArrow)) {
|
|
2281
|
+
toggleProvider(PROVIDER_ORDER[cursor]);
|
|
2220
2282
|
}
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
if (cursor < trackedAccounts.length) {
|
|
2286
|
+
const row = trackedAccounts[cursor];
|
|
2221
2287
|
if (key.return) {
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
setTzEdit(null);
|
|
2226
|
-
setTzError(null);
|
|
2227
|
-
} else if (isValidTimezone(val)) {
|
|
2228
|
-
updateConfig((c) => ({ ...c, timezone: val }));
|
|
2229
|
-
setTzEdit(null);
|
|
2230
|
-
setTzError(null);
|
|
2288
|
+
if (row.source === "configured") {
|
|
2289
|
+
const account = config.accounts.find((candidate) => candidate.id === row.explicitId);
|
|
2290
|
+
if (account) openEditAccount(account);
|
|
2231
2291
|
} else {
|
|
2232
|
-
|
|
2292
|
+
openConfigureAccount(row);
|
|
2233
2293
|
}
|
|
2234
2294
|
return;
|
|
2235
2295
|
}
|
|
2236
|
-
if (
|
|
2237
|
-
|
|
2238
|
-
return;
|
|
2239
|
-
}
|
|
2240
|
-
if (key.rightArrow) {
|
|
2241
|
-
setTzCaret((c) => clampCaret(c + 1, tzEdit.length));
|
|
2242
|
-
return;
|
|
2243
|
-
}
|
|
2244
|
-
if (key.ctrl && input === "a") {
|
|
2245
|
-
setTzCaret(0);
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
if (key.ctrl && input === "e") {
|
|
2249
|
-
setTzCaret(tzEdit.length);
|
|
2296
|
+
if (row.source === "configured" && row.explicitId && (input === "d" || input === "x")) {
|
|
2297
|
+
deleteAccount(row.explicitId);
|
|
2250
2298
|
return;
|
|
2251
2299
|
}
|
|
2252
|
-
if (
|
|
2253
|
-
|
|
2254
|
-
tzValueRef.current = r.value;
|
|
2255
|
-
tzCaretRef.current = r.caret;
|
|
2256
|
-
setTzEdit(r.value);
|
|
2257
|
-
setTzCaret(r.caret);
|
|
2258
|
-
setTzError(null);
|
|
2300
|
+
if (input === " ") {
|
|
2301
|
+
updateConfig((current) => ({ ...current, activeAccountId: row.id }));
|
|
2259
2302
|
return;
|
|
2260
2303
|
}
|
|
2261
|
-
if (isPrintable(input, key)) {
|
|
2262
|
-
const clean = sanitizeTyped(input);
|
|
2263
|
-
if (clean) insertText(clean);
|
|
2264
|
-
}
|
|
2265
2304
|
return;
|
|
2266
2305
|
}
|
|
2267
|
-
if (
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
}
|
|
2306
|
+
if (cursor === trackedAccounts.length && key.return) openAddAccount();
|
|
2307
|
+
function handleGeneralSetting(value, inputKey, context) {
|
|
2308
|
+
const index = context.settings.cursor;
|
|
2309
|
+
if (index === 0) {
|
|
2310
|
+
if (inputKey.leftArrow) updateConfig((current) => ({ ...current, interval: Math.max(1, current.interval - 1) }));
|
|
2311
|
+
if (inputKey.rightArrow) updateConfig((current) => ({ ...current, interval: current.interval + 1 }));
|
|
2274
2312
|
return;
|
|
2275
2313
|
}
|
|
2276
|
-
if (
|
|
2277
|
-
|
|
2314
|
+
if (index === 1) {
|
|
2315
|
+
if (inputKey.leftArrow) updateConfig((current) => ({ ...current, billingInterval: Math.max(1, current.billingInterval - 1) }));
|
|
2316
|
+
if (inputKey.rightArrow) updateConfig((current) => ({ ...current, billingInterval: current.billingInterval + 1 }));
|
|
2278
2317
|
return;
|
|
2279
2318
|
}
|
|
2280
|
-
if (
|
|
2281
|
-
|
|
2319
|
+
if (index === 2 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2320
|
+
updateConfig((current) => ({ ...current, clearScreen: !current.clearScreen }));
|
|
2282
2321
|
return;
|
|
2283
2322
|
}
|
|
2284
|
-
if (
|
|
2285
|
-
|
|
2323
|
+
if (index === 3 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2324
|
+
updateConfig((current) => ({ ...current, privacyMode: !current.privacyMode }));
|
|
2286
2325
|
return;
|
|
2287
2326
|
}
|
|
2288
|
-
if (
|
|
2289
|
-
|
|
2327
|
+
if (index === 4) {
|
|
2328
|
+
if (textInput.isPrintable(value, inputKey)) {
|
|
2329
|
+
const clean = sanitizeTyped(value);
|
|
2330
|
+
if (clean.length === 1) updateConfig((current) => ({ ...current, privacyToggleKey: clean }));
|
|
2331
|
+
}
|
|
2332
|
+
if (inputKey.backspace || inputKey.delete) updateConfig((current) => ({ ...current, privacyToggleKey: "p" }));
|
|
2290
2333
|
return;
|
|
2291
2334
|
}
|
|
2292
|
-
if (
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2335
|
+
if (index === 5) {
|
|
2336
|
+
if (inputKey.return) {
|
|
2337
|
+
const initial = config.timezone ?? "";
|
|
2338
|
+
timezoneEditor.setValue(initial);
|
|
2339
|
+
timezoneEditor.setCaret(initial.length);
|
|
2340
|
+
timezoneEditor.setError(null);
|
|
2341
|
+
}
|
|
2342
|
+
if (inputKey.leftArrow || inputKey.rightArrow) {
|
|
2343
|
+
updateConfig((current) => ({ ...current, timezone: current.timezone === null ? systemTimezone() : null }));
|
|
2344
|
+
}
|
|
2298
2345
|
return;
|
|
2299
2346
|
}
|
|
2300
|
-
if (
|
|
2301
|
-
|
|
2302
|
-
|
|
2347
|
+
if (index === 6 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2348
|
+
updateConfig((current) => ({ ...current, dashboardLayout: current.dashboardLayout === "grid" ? "single" : "grid" }));
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
if (index === 7 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2352
|
+
updateConfig((current) => ({ ...current, defaultFocus: current.defaultFocus === "all" ? "last" : "all" }));
|
|
2303
2353
|
}
|
|
2304
|
-
return;
|
|
2305
2354
|
}
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
// src/ui/keybindings-navigation.ts
|
|
2358
|
+
function handleGlobalCommand(input, ctx) {
|
|
2359
|
+
const { global, table, settings } = ctx;
|
|
2306
2360
|
if (input === "q") {
|
|
2307
|
-
exit();
|
|
2308
|
-
return;
|
|
2361
|
+
global.exit();
|
|
2362
|
+
return true;
|
|
2309
2363
|
}
|
|
2310
2364
|
if (input === "O") {
|
|
2311
2365
|
openUrl(REPO_URL);
|
|
2366
|
+
return true;
|
|
2367
|
+
}
|
|
2368
|
+
if (input === "W" || input === "w" && table.tab !== 1 && !settings.show) {
|
|
2369
|
+
if (!global.showLoader && global.configReady) void global.toggleWeb();
|
|
2370
|
+
return true;
|
|
2371
|
+
}
|
|
2372
|
+
return false;
|
|
2373
|
+
}
|
|
2374
|
+
function handleNavigation(input, key, ctx) {
|
|
2375
|
+
const { settings, table, dashboard, global } = ctx;
|
|
2376
|
+
const { config, updateConfig, resetView, slots } = global;
|
|
2377
|
+
if (input === config.privacyToggleKey) {
|
|
2378
|
+
updateConfig((current) => ({ ...current, privacyMode: !current.privacyMode }));
|
|
2312
2379
|
return;
|
|
2313
2380
|
}
|
|
2314
|
-
if (input === "
|
|
2315
|
-
|
|
2316
|
-
|
|
2381
|
+
if (input === "s") {
|
|
2382
|
+
settings.setTab("general");
|
|
2383
|
+
settings.setShow(true);
|
|
2384
|
+
settings.setCursor(-1);
|
|
2317
2385
|
return;
|
|
2318
2386
|
}
|
|
2319
|
-
if (
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2387
|
+
if (input === "a") {
|
|
2388
|
+
global.cycleAccount(1);
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
if (input === "A") {
|
|
2392
|
+
global.cycleAccount(-1);
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
2395
|
+
if (key.tab) {
|
|
2396
|
+
global.setTab((current) => (current + 1) % TABS.length);
|
|
2397
|
+
resetView();
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
if (input && /^[0-9]$/.test(input) && slots.length > 1) {
|
|
2401
|
+
const target = slots[parseInt(input, 10)];
|
|
2402
|
+
if (target) {
|
|
2403
|
+
updateConfig((current) => ({ ...current, activeAccountId: target.id }));
|
|
2404
|
+
resetView();
|
|
2335
2405
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
return;
|
|
2343
|
-
}
|
|
2344
|
-
if (settingsCursor < 0) {
|
|
2345
|
-
if (key.leftArrow) {
|
|
2346
|
-
switchSettingsTab(-1);
|
|
2347
|
-
return;
|
|
2348
|
-
}
|
|
2349
|
-
if (key.rightArrow) {
|
|
2350
|
-
switchSettingsTab(1);
|
|
2351
|
-
return;
|
|
2352
|
-
}
|
|
2353
|
-
if (key.downArrow || key.return) {
|
|
2354
|
-
setSettingsCursor(0);
|
|
2355
|
-
return;
|
|
2356
|
-
}
|
|
2357
|
-
if (key.upArrow) {
|
|
2358
|
-
setSettingsCursor(Math.max(0, rowCount - 1));
|
|
2359
|
-
return;
|
|
2360
|
-
}
|
|
2361
|
-
return;
|
|
2362
|
-
}
|
|
2363
|
-
const selectedAccountRow = settingsTab === "accounts" && settingsCursor >= 0 && settingsCursor < trackedAccountRows.length ? trackedAccountRows[settingsCursor] : null;
|
|
2364
|
-
if (selectedAccountRow?.source === "configured" && selectedAccountRow.explicitIndex !== void 0 && key.shift && (key.upArrow || key.downArrow)) {
|
|
2365
|
-
moveAccount(selectedAccountRow.explicitIndex, key.upArrow ? -1 : 1);
|
|
2366
|
-
return;
|
|
2367
|
-
}
|
|
2368
|
-
if (key.upArrow) {
|
|
2369
|
-
setSettingsCursor((c) => Math.max(-1, c - 1));
|
|
2370
|
-
return;
|
|
2371
|
-
}
|
|
2372
|
-
if (key.downArrow) {
|
|
2373
|
-
setSettingsCursor((c) => Math.min(rowCount - 1, c + 1));
|
|
2374
|
-
return;
|
|
2375
|
-
}
|
|
2376
|
-
if (settingsTab === "general") {
|
|
2377
|
-
if (settingsCursor === 0) {
|
|
2378
|
-
if (key.leftArrow) updateConfig((c) => ({ ...c, interval: Math.max(1, c.interval - 1) }));
|
|
2379
|
-
if (key.rightArrow) updateConfig((c) => ({ ...c, interval: c.interval + 1 }));
|
|
2380
|
-
return;
|
|
2381
|
-
}
|
|
2382
|
-
if (settingsCursor === 1) {
|
|
2383
|
-
if (key.leftArrow) updateConfig((c) => ({ ...c, billingInterval: Math.max(1, c.billingInterval - 1) }));
|
|
2384
|
-
if (key.rightArrow) updateConfig((c) => ({ ...c, billingInterval: c.billingInterval + 1 }));
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2387
|
-
if (settingsCursor === 2 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2388
|
-
updateConfig((c) => ({ ...c, clearScreen: !c.clearScreen }));
|
|
2389
|
-
return;
|
|
2390
|
-
}
|
|
2391
|
-
if (settingsCursor === 3 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2392
|
-
updateConfig((c) => ({ ...c, privacyMode: !c.privacyMode }));
|
|
2393
|
-
return;
|
|
2394
|
-
}
|
|
2395
|
-
if (settingsCursor === 4) {
|
|
2396
|
-
if (isPrintable(input, key)) {
|
|
2397
|
-
const clean = sanitizeTyped(input);
|
|
2398
|
-
if (clean.length === 1) updateConfig((c) => ({ ...c, privacyToggleKey: clean }));
|
|
2399
|
-
}
|
|
2400
|
-
if (key.backspace || key.delete) updateConfig((c) => ({ ...c, privacyToggleKey: "p" }));
|
|
2401
|
-
return;
|
|
2402
|
-
}
|
|
2403
|
-
if (settingsCursor === 5) {
|
|
2404
|
-
if (key.return) {
|
|
2405
|
-
const init = cfg.timezone ?? "";
|
|
2406
|
-
setTzEdit(init);
|
|
2407
|
-
setTzCaret(init.length);
|
|
2408
|
-
setTzError(null);
|
|
2409
|
-
}
|
|
2410
|
-
if (key.leftArrow || key.rightArrow) updateConfig((c) => ({ ...c, timezone: c.timezone === null ? systemTimezone() : null }));
|
|
2411
|
-
return;
|
|
2412
|
-
}
|
|
2413
|
-
if (settingsCursor === 6 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2414
|
-
updateConfig((c) => ({ ...c, dashboardLayout: c.dashboardLayout === "grid" ? "single" : "grid" }));
|
|
2415
|
-
return;
|
|
2416
|
-
}
|
|
2417
|
-
if (settingsCursor === 7 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2418
|
-
updateConfig((c) => ({ ...c, defaultFocus: c.defaultFocus === "all" ? "last" : "all" }));
|
|
2419
|
-
return;
|
|
2420
|
-
}
|
|
2421
|
-
return;
|
|
2422
|
-
}
|
|
2423
|
-
if (settingsTab === "providers") {
|
|
2424
|
-
if (settingsCursor >= 0 && settingsCursor < PROVIDER_ORDER.length && (input === " " || key.return || key.leftArrow || key.rightArrow)) {
|
|
2425
|
-
toggleProvider(PROVIDER_ORDER[settingsCursor]);
|
|
2426
|
-
}
|
|
2427
|
-
return;
|
|
2428
|
-
}
|
|
2429
|
-
if (settingsTab === "accounts" && settingsCursor >= 0 && settingsCursor < trackedAccountRows.length) {
|
|
2430
|
-
const row = trackedAccountRows[settingsCursor];
|
|
2431
|
-
if (key.return) {
|
|
2432
|
-
if (row.source === "configured") {
|
|
2433
|
-
const acc = cfg.accounts.find((a) => a.id === row.explicitId);
|
|
2434
|
-
if (acc) openEditAccount(acc);
|
|
2435
|
-
} else {
|
|
2436
|
-
openConfigureAccount(row);
|
|
2437
|
-
}
|
|
2438
|
-
return;
|
|
2439
|
-
}
|
|
2440
|
-
if (row.source === "configured" && row.explicitId && (input === "d" || input === "x")) {
|
|
2441
|
-
deleteAccount(row.explicitId);
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
if (table.tab === 0) {
|
|
2409
|
+
if (dashboard.paginated) {
|
|
2410
|
+
if (input === "]" || key.downArrow || key.pageDown) {
|
|
2411
|
+
dashboard.setPage((page) => Math.min(dashboard.pageCount - 1, page + 1));
|
|
2442
2412
|
return;
|
|
2443
2413
|
}
|
|
2444
|
-
if (input === "
|
|
2445
|
-
|
|
2414
|
+
if (input === "[" || key.upArrow || key.pageUp) {
|
|
2415
|
+
dashboard.setPage((page) => Math.max(0, page - 1));
|
|
2446
2416
|
return;
|
|
2447
2417
|
}
|
|
2448
|
-
return;
|
|
2449
2418
|
}
|
|
2450
|
-
if (
|
|
2451
|
-
|
|
2419
|
+
if (key.upArrow || key.downArrow || key.pageUp || key.pageDown || input === "G" || input === "g") return;
|
|
2420
|
+
}
|
|
2421
|
+
if (table.tab !== 1) {
|
|
2422
|
+
if (key.leftArrow || key.rightArrow) {
|
|
2423
|
+
global.setTab((current) => (current + 1) % TABS.length);
|
|
2424
|
+
resetView();
|
|
2452
2425
|
}
|
|
2453
2426
|
return;
|
|
2454
2427
|
}
|
|
2455
|
-
if (input ===
|
|
2456
|
-
|
|
2428
|
+
if (input === "p") {
|
|
2429
|
+
table.cycleProvider(1);
|
|
2457
2430
|
return;
|
|
2458
2431
|
}
|
|
2459
|
-
if (input === "
|
|
2460
|
-
|
|
2461
|
-
setShowSettings(true);
|
|
2462
|
-
setSettingsCursor(-1);
|
|
2432
|
+
if (input === "P") {
|
|
2433
|
+
table.cycleProvider(-1);
|
|
2463
2434
|
return;
|
|
2464
2435
|
}
|
|
2465
|
-
if (input === "
|
|
2466
|
-
|
|
2436
|
+
if (input === "/") {
|
|
2437
|
+
table.setSearchMode(true);
|
|
2438
|
+
table.setSearchCaret(table.search.length);
|
|
2467
2439
|
return;
|
|
2468
2440
|
}
|
|
2469
|
-
if (
|
|
2470
|
-
|
|
2441
|
+
if (key.escape) {
|
|
2442
|
+
if (table.search) {
|
|
2443
|
+
table.setSearch("");
|
|
2444
|
+
table.setSearchCaret(0);
|
|
2445
|
+
} else table.setExpanded(-1);
|
|
2471
2446
|
return;
|
|
2472
2447
|
}
|
|
2473
|
-
if (
|
|
2474
|
-
|
|
2448
|
+
if (input === "o") {
|
|
2449
|
+
table.setSort((sort) => (sort + 1) % table.sorts.length);
|
|
2475
2450
|
resetView();
|
|
2476
2451
|
return;
|
|
2477
2452
|
}
|
|
2478
|
-
if (input
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
updateConfig((c) => ({ ...c, activeAccountId: target.id }));
|
|
2482
|
-
resetView();
|
|
2483
|
-
}
|
|
2453
|
+
if (input === "d") {
|
|
2454
|
+
table.setView(0);
|
|
2455
|
+
resetView();
|
|
2484
2456
|
return;
|
|
2485
2457
|
}
|
|
2486
|
-
if (
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
return;
|
|
2491
|
-
}
|
|
2492
|
-
if (input === "[" || key.upArrow || key.pageUp) {
|
|
2493
|
-
setDashPage((p) => Math.max(0, p - 1));
|
|
2494
|
-
return;
|
|
2495
|
-
}
|
|
2496
|
-
}
|
|
2497
|
-
if (key.upArrow || key.downArrow || key.pageUp || key.pageDown || input === "G" || input === "g") return;
|
|
2458
|
+
if (input === "w") {
|
|
2459
|
+
table.setView(1);
|
|
2460
|
+
resetView();
|
|
2461
|
+
return;
|
|
2498
2462
|
}
|
|
2499
|
-
if (
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2463
|
+
if (input === "m") {
|
|
2464
|
+
table.cycleModel(1);
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
if (input === "M") {
|
|
2468
|
+
table.cycleModel(-1);
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
if (key.leftArrow) {
|
|
2472
|
+
table.setView((view) => (view - 1 + VIEWS.length) % VIEWS.length);
|
|
2473
|
+
resetView();
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (key.rightArrow) {
|
|
2477
|
+
table.setView((view) => (view + 1) % VIEWS.length);
|
|
2478
|
+
resetView();
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
if (key.return) {
|
|
2482
|
+
table.setExpanded((expanded) => expanded === table.cursor ? -1 : table.cursor);
|
|
2483
|
+
return;
|
|
2484
|
+
}
|
|
2485
|
+
if (key.upArrow) {
|
|
2486
|
+
table.setCursor((cursor) => Math.max(0, cursor - 1));
|
|
2487
|
+
return;
|
|
2488
|
+
}
|
|
2489
|
+
if (key.downArrow) {
|
|
2490
|
+
table.setCursor((cursor) => table.clampRow(cursor + 1));
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
if (key.pageDown || input === "G") {
|
|
2494
|
+
table.setCursor((cursor) => table.clampRow(input === "G" ? table.rowCountRef.current - 1 : cursor + Math.max(1, table.rows - 12)));
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
if (key.pageUp || input === "g") {
|
|
2498
|
+
table.setCursor((cursor) => input === "g" ? 0 : Math.max(0, cursor - Math.max(1, table.rows - 12)));
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// src/ui/keybindings.ts
|
|
2503
|
+
function isRefreshAllShortcut(input, mode) {
|
|
2504
|
+
return (input === "r" || input === "R") && !mode.unavailable && !mode.showPicker && !mode.editingAccount && !mode.editingTimezone && !mode.editingSearch;
|
|
2505
|
+
}
|
|
2506
|
+
function handleOnboarding(input, key, ctx) {
|
|
2507
|
+
const { onboarding, global } = ctx;
|
|
2508
|
+
if (input === "q") {
|
|
2509
|
+
global.exit();
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
const confirmIndex = onboarding.providers.length;
|
|
2513
|
+
if (key.upArrow) {
|
|
2514
|
+
onboarding.setCursor((cursor) => Math.max(0, cursor - 1));
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
if (key.downArrow) {
|
|
2518
|
+
onboarding.setCursor((cursor) => Math.min(confirmIndex, cursor + 1));
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
if (input === " ") {
|
|
2522
|
+
onboarding.toggle(onboarding.cursor);
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
if (key.return) {
|
|
2526
|
+
if (onboarding.cursor === confirmIndex) onboarding.confirm();
|
|
2527
|
+
else onboarding.toggle(onboarding.cursor);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
function handleKey(input, key, ctx) {
|
|
2531
|
+
const editingAccount = ctx.settings.show && ctx.accountEditor.form !== null;
|
|
2532
|
+
const editingTimezone = ctx.settings.show && ctx.timezoneEditor.value !== null;
|
|
2533
|
+
const editingSearch = ctx.table.tab === 1 && ctx.table.searchMode;
|
|
2534
|
+
if (isRefreshAllShortcut(input, {
|
|
2535
|
+
showPicker: ctx.onboarding.show,
|
|
2536
|
+
editingAccount,
|
|
2537
|
+
editingTimezone,
|
|
2538
|
+
editingSearch,
|
|
2539
|
+
unavailable: !ctx.global.configReady
|
|
2540
|
+
})) {
|
|
2541
|
+
ctx.global.refreshAll();
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
if (ctx.onboarding.show) {
|
|
2545
|
+
handleOnboarding(input, key, ctx);
|
|
2546
|
+
return;
|
|
2579
2547
|
}
|
|
2548
|
+
if (editingAccount) {
|
|
2549
|
+
handleAccountEditor(input, key, ctx.accountEditor, ctx.textInput);
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
if (editingTimezone) {
|
|
2553
|
+
handleTimezoneEditor(input, key, ctx.timezoneEditor, ctx.textInput, ctx.global.updateConfig);
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
if (editingSearch) {
|
|
2557
|
+
handleSearchEditor(input, key, ctx.table, ctx.textInput);
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
if (handleGlobalCommand(input, ctx)) return;
|
|
2561
|
+
if (ctx.settings.show) {
|
|
2562
|
+
handleSettings(input, key, ctx);
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
handleNavigation(input, key, ctx);
|
|
2580
2566
|
}
|
|
2581
2567
|
|
|
2582
2568
|
// src/ui/hooks/use-terminal-size.ts
|
|
@@ -2732,7 +2718,7 @@ function useLoader({ configReady, showPicker, accountsKey, allReady, tooSmall, s
|
|
|
2732
2718
|
}
|
|
2733
2719
|
|
|
2734
2720
|
// src/ui/hooks/use-degraded-polling.ts
|
|
2735
|
-
import {
|
|
2721
|
+
import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "react";
|
|
2736
2722
|
|
|
2737
2723
|
// src/client/seed-cache.ts
|
|
2738
2724
|
import { readFile } from "fs/promises";
|
|
@@ -2751,92 +2737,420 @@ async function loadSeedSnapshot() {
|
|
|
2751
2737
|
}
|
|
2752
2738
|
|
|
2753
2739
|
// src/ui/hooks/use-degraded-polling.ts
|
|
2754
|
-
function
|
|
2740
|
+
function failureMessage(scope, failures) {
|
|
2741
|
+
const detail = failures[0]?.reason;
|
|
2742
|
+
const suffix = detail instanceof Error && detail.message ? `: ${detail.message}` : "";
|
|
2743
|
+
return new Error(`${scope} refresh failed for ${failures.length} source${failures.length === 1 ? "" : "s"}${suffix}`);
|
|
2744
|
+
}
|
|
2745
|
+
function usageProviders(accounts) {
|
|
2746
|
+
return [...new Set(accounts.filter((account) => PROVIDERS[account.providerId].hasUsage).map((account) => account.providerId))];
|
|
2747
|
+
}
|
|
2748
|
+
function useDegradedPolling({
|
|
2749
|
+
degraded,
|
|
2750
|
+
configReady,
|
|
2751
|
+
showPicker,
|
|
2752
|
+
accountsKey,
|
|
2753
|
+
accountsRef,
|
|
2754
|
+
interval,
|
|
2755
|
+
billingMs,
|
|
2756
|
+
tz,
|
|
2757
|
+
activeTableProvider,
|
|
2758
|
+
tableVisible
|
|
2759
|
+
}) {
|
|
2755
2760
|
const [statsLocal, setStats] = useState6(/* @__PURE__ */ new Map());
|
|
2756
2761
|
const [peakLocal, setPeak] = useState6(null);
|
|
2757
|
-
const [error, setError] = useState6(null);
|
|
2758
2762
|
const [updatedLocal, setUpdated] = useState6(/* @__PURE__ */ new Date());
|
|
2763
|
+
const [tables, setTables] = useState6(/* @__PURE__ */ new Map());
|
|
2764
|
+
const [loadingTables, setLoadingTables] = useState6(/* @__PURE__ */ new Set());
|
|
2759
2765
|
const seededRef = useRef5(false);
|
|
2766
|
+
const epochRef = useRef5(0);
|
|
2767
|
+
const summaryFlightRef = useRef5(null);
|
|
2768
|
+
const billingFlightRef = useRef5(null);
|
|
2769
|
+
const peakFlightRef = useRef5(null);
|
|
2770
|
+
const tableFlightsRef = useRef5(/* @__PURE__ */ new Map());
|
|
2771
|
+
const allFlightRef = useRef5(null);
|
|
2772
|
+
useEffect6(() => {
|
|
2773
|
+
const epoch = ++epochRef.current;
|
|
2774
|
+
summaryFlightRef.current = null;
|
|
2775
|
+
billingFlightRef.current = null;
|
|
2776
|
+
peakFlightRef.current = null;
|
|
2777
|
+
tableFlightsRef.current.clear();
|
|
2778
|
+
allFlightRef.current = null;
|
|
2779
|
+
setStats(/* @__PURE__ */ new Map());
|
|
2780
|
+
setTables(/* @__PURE__ */ new Map());
|
|
2781
|
+
setLoadingTables(/* @__PURE__ */ new Set());
|
|
2782
|
+
return () => {
|
|
2783
|
+
if (epochRef.current === epoch) epochRef.current++;
|
|
2784
|
+
};
|
|
2785
|
+
}, [accountsKey, tz, degraded]);
|
|
2760
2786
|
useEffect6(() => {
|
|
2761
2787
|
if (!degraded) return;
|
|
2762
2788
|
if (seededRef.current || !configReady || showPicker || accountsRef.current.length === 0) return;
|
|
2763
2789
|
seededRef.current = true;
|
|
2764
|
-
|
|
2790
|
+
const epoch = epochRef.current;
|
|
2791
|
+
void loadSeedSnapshot().then((snap) => {
|
|
2792
|
+
if (epoch !== epochRef.current) return;
|
|
2765
2793
|
setStats((prev) => {
|
|
2766
2794
|
if (prev.size > 0) return prev;
|
|
2767
2795
|
const next = new Map(prev);
|
|
2768
2796
|
for (const acc of accountsRef.current) {
|
|
2769
|
-
const
|
|
2770
|
-
if (
|
|
2797
|
+
const cached = snap[acc.id];
|
|
2798
|
+
if (cached && (cached.dashboard || cached.billing)) {
|
|
2799
|
+
next.set(acc.id, {
|
|
2800
|
+
account: acc,
|
|
2801
|
+
dashboard: cached.dashboard ?? null,
|
|
2802
|
+
billing: cached.billing ?? null
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2771
2805
|
}
|
|
2772
2806
|
return next;
|
|
2773
2807
|
});
|
|
2774
2808
|
});
|
|
2775
|
-
}, [degraded, configReady, showPicker, accountsKey]);
|
|
2809
|
+
}, [degraded, configReady, showPicker, accountsKey, accountsRef]);
|
|
2810
|
+
const runSummary = useCallback3(() => {
|
|
2811
|
+
if (summaryFlightRef.current) return summaryFlightRef.current;
|
|
2812
|
+
const epoch = epochRef.current;
|
|
2813
|
+
const task = (async () => {
|
|
2814
|
+
const targets = accountsRef.current.filter((account) => {
|
|
2815
|
+
const provider = PROVIDERS[account.providerId];
|
|
2816
|
+
return provider.hasUsage && provider.fetchSummary;
|
|
2817
|
+
});
|
|
2818
|
+
const settled = await Promise.allSettled(targets.map(async (account) => {
|
|
2819
|
+
const fetchSummary = PROVIDERS[account.providerId].fetchSummary;
|
|
2820
|
+
if (!fetchSummary) return;
|
|
2821
|
+
const dashboard = await withTimeout(fetchSummary(account, tz));
|
|
2822
|
+
if (epoch === epochRef.current) setStats((prev) => upsert(prev, account, { dashboard }));
|
|
2823
|
+
}));
|
|
2824
|
+
if (epoch !== epochRef.current) return;
|
|
2825
|
+
setUpdated(/* @__PURE__ */ new Date());
|
|
2826
|
+
const failures = settled.filter((result) => result.status === "rejected");
|
|
2827
|
+
if (failures.length > 0) throw failureMessage("summary", failures);
|
|
2828
|
+
})();
|
|
2829
|
+
const owned = task.finally(() => {
|
|
2830
|
+
if (summaryFlightRef.current === owned) summaryFlightRef.current = null;
|
|
2831
|
+
});
|
|
2832
|
+
summaryFlightRef.current = owned;
|
|
2833
|
+
return owned;
|
|
2834
|
+
}, [accountsKey, accountsRef, tz]);
|
|
2835
|
+
const runBilling = useCallback3(() => {
|
|
2836
|
+
if (billingFlightRef.current) return billingFlightRef.current;
|
|
2837
|
+
const epoch = epochRef.current;
|
|
2838
|
+
const task = (async () => {
|
|
2839
|
+
const targets = accountsRef.current.filter((account) => {
|
|
2840
|
+
const provider = PROVIDERS[account.providerId];
|
|
2841
|
+
return provider.hasBilling && provider.fetchBilling;
|
|
2842
|
+
});
|
|
2843
|
+
const settled = await Promise.allSettled(targets.map(async (account) => {
|
|
2844
|
+
const fetchBilling = PROVIDERS[account.providerId].fetchBilling;
|
|
2845
|
+
if (!fetchBilling) return;
|
|
2846
|
+
const billing = await withTimeout(fetchBilling(account, tz));
|
|
2847
|
+
if (epoch === epochRef.current) setStats((prev) => upsert(prev, account, { billing }));
|
|
2848
|
+
}));
|
|
2849
|
+
if (epoch !== epochRef.current) return;
|
|
2850
|
+
const failures = settled.filter((result) => result.status === "rejected");
|
|
2851
|
+
if (failures.length > 0) throw failureMessage("billing", failures);
|
|
2852
|
+
})();
|
|
2853
|
+
const owned = task.finally(() => {
|
|
2854
|
+
if (billingFlightRef.current === owned) billingFlightRef.current = null;
|
|
2855
|
+
});
|
|
2856
|
+
billingFlightRef.current = owned;
|
|
2857
|
+
return owned;
|
|
2858
|
+
}, [accountsKey, accountsRef, tz]);
|
|
2859
|
+
const runPeak = useCallback3(() => {
|
|
2860
|
+
if (peakFlightRef.current) return peakFlightRef.current;
|
|
2861
|
+
if (!accountsRef.current.some((account) => account.providerId === "claude")) return Promise.resolve();
|
|
2862
|
+
const epoch = epochRef.current;
|
|
2863
|
+
const task = withTimeout(fetchPeak()).then((peak) => {
|
|
2864
|
+
if (epoch === epochRef.current && peak) setPeak(peak);
|
|
2865
|
+
});
|
|
2866
|
+
const owned = task.finally(() => {
|
|
2867
|
+
if (peakFlightRef.current === owned) peakFlightRef.current = null;
|
|
2868
|
+
});
|
|
2869
|
+
peakFlightRef.current = owned;
|
|
2870
|
+
return owned;
|
|
2871
|
+
}, [accountsKey, accountsRef]);
|
|
2872
|
+
const runTable = useCallback3((providerId) => {
|
|
2873
|
+
const existing = tableFlightsRef.current.get(providerId);
|
|
2874
|
+
if (existing) return existing;
|
|
2875
|
+
const epoch = epochRef.current;
|
|
2876
|
+
const targets = accountsRef.current.filter((account) => account.providerId === providerId);
|
|
2877
|
+
if (targets.length === 0 || !PROVIDERS[providerId].hasUsage) return Promise.resolve();
|
|
2878
|
+
setLoadingTables((prev) => new Set(prev).add(providerId));
|
|
2879
|
+
const task = (async () => {
|
|
2880
|
+
const settled = await Promise.allSettled(targets.map(async (account) => {
|
|
2881
|
+
const fetchTable = PROVIDERS[account.providerId].fetchTable;
|
|
2882
|
+
return fetchTable ? withTimeout(fetchTable(account, tz)) : null;
|
|
2883
|
+
}));
|
|
2884
|
+
if (epoch !== epochRef.current) return;
|
|
2885
|
+
const valid = settled.flatMap((result) => result.status === "fulfilled" && result.value ? [result.value] : []);
|
|
2886
|
+
if (valid.length > 0) {
|
|
2887
|
+
setTables((prev) => new Map(prev).set(providerId, coalesceTables(valid)));
|
|
2888
|
+
}
|
|
2889
|
+
const failures = settled.filter((result) => result.status === "rejected");
|
|
2890
|
+
if (failures.length > 0) throw failureMessage(`${PROVIDERS[providerId].name} history`, failures);
|
|
2891
|
+
})();
|
|
2892
|
+
const owned = task.finally(() => {
|
|
2893
|
+
if (tableFlightsRef.current.get(providerId) !== owned) return;
|
|
2894
|
+
tableFlightsRef.current.delete(providerId);
|
|
2895
|
+
setLoadingTables((prev) => {
|
|
2896
|
+
if (!prev.has(providerId)) return prev;
|
|
2897
|
+
const next = new Set(prev);
|
|
2898
|
+
next.delete(providerId);
|
|
2899
|
+
return next;
|
|
2900
|
+
});
|
|
2901
|
+
});
|
|
2902
|
+
tableFlightsRef.current.set(providerId, owned);
|
|
2903
|
+
return owned;
|
|
2904
|
+
}, [accountsKey, accountsRef, tz]);
|
|
2905
|
+
const refreshAll = useCallback3(() => {
|
|
2906
|
+
if (allFlightRef.current) return allFlightRef.current;
|
|
2907
|
+
const tasks = [runSummary(), runBilling(), runPeak()];
|
|
2908
|
+
for (const providerId of usageProviders(accountsRef.current)) tasks.push(runTable(providerId));
|
|
2909
|
+
const task = Promise.allSettled(tasks).then((settled) => {
|
|
2910
|
+
const failures = settled.filter((result) => result.status === "rejected");
|
|
2911
|
+
if (failures.length > 0) throw failureMessage("all-data", failures);
|
|
2912
|
+
setUpdated(/* @__PURE__ */ new Date());
|
|
2913
|
+
});
|
|
2914
|
+
const owned = task.finally(() => {
|
|
2915
|
+
if (allFlightRef.current === owned) allFlightRef.current = null;
|
|
2916
|
+
});
|
|
2917
|
+
allFlightRef.current = owned;
|
|
2918
|
+
return owned;
|
|
2919
|
+
}, [accountsRef, runBilling, runPeak, runSummary, runTable]);
|
|
2776
2920
|
useEffect6(() => {
|
|
2777
2921
|
if (!degraded || !configReady || showPicker) return;
|
|
2778
2922
|
let active = true;
|
|
2779
2923
|
let timer;
|
|
2780
|
-
const
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
if (!provider.hasUsage || !provider.fetchSummary) return;
|
|
2785
|
-
try {
|
|
2786
|
-
const dashboard = await withTimeout(provider.fetchSummary(acc, tz));
|
|
2787
|
-
if (active) setStats((prev) => upsert(prev, acc, { dashboard }));
|
|
2788
|
-
} catch {
|
|
2789
|
-
}
|
|
2790
|
-
}));
|
|
2791
|
-
if (active) {
|
|
2792
|
-
setError(null);
|
|
2793
|
-
setUpdated(/* @__PURE__ */ new Date());
|
|
2794
|
-
}
|
|
2795
|
-
} finally {
|
|
2796
|
-
if (active) timer = setTimeout(load, interval);
|
|
2797
|
-
}
|
|
2924
|
+
const loop = async () => {
|
|
2925
|
+
await runSummary().catch(() => {
|
|
2926
|
+
});
|
|
2927
|
+
if (active) timer = setTimeout(loop, interval);
|
|
2798
2928
|
};
|
|
2799
|
-
|
|
2929
|
+
void loop();
|
|
2800
2930
|
return () => {
|
|
2801
2931
|
active = false;
|
|
2802
|
-
clearTimeout(timer);
|
|
2932
|
+
if (timer) clearTimeout(timer);
|
|
2803
2933
|
};
|
|
2804
|
-
}, [degraded,
|
|
2934
|
+
}, [degraded, configReady, showPicker, interval, runSummary]);
|
|
2805
2935
|
useEffect6(() => {
|
|
2806
2936
|
if (!degraded || !configReady || showPicker) return;
|
|
2807
2937
|
let active = true;
|
|
2808
2938
|
let timer;
|
|
2809
|
-
const
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
}
|
|
2939
|
+
const loop = async () => {
|
|
2940
|
+
await Promise.allSettled([runBilling(), runPeak()]);
|
|
2941
|
+
if (active) timer = setTimeout(loop, billingMs);
|
|
2942
|
+
};
|
|
2943
|
+
void loop();
|
|
2944
|
+
return () => {
|
|
2945
|
+
active = false;
|
|
2946
|
+
if (timer) clearTimeout(timer);
|
|
2947
|
+
};
|
|
2948
|
+
}, [degraded, configReady, showPicker, billingMs, runBilling, runPeak]);
|
|
2949
|
+
useEffect6(() => {
|
|
2950
|
+
if (!degraded || !configReady || showPicker || !tableVisible || !activeTableProvider) return;
|
|
2951
|
+
let active = true;
|
|
2952
|
+
let timer;
|
|
2953
|
+
const loop = async () => {
|
|
2954
|
+
await runTable(activeTableProvider).catch(() => {
|
|
2955
|
+
});
|
|
2956
|
+
if (active) timer = setTimeout(loop, Math.max(interval, 1e4));
|
|
2826
2957
|
};
|
|
2827
|
-
|
|
2958
|
+
void loop();
|
|
2828
2959
|
return () => {
|
|
2829
2960
|
active = false;
|
|
2830
|
-
clearTimeout(timer);
|
|
2961
|
+
if (timer) clearTimeout(timer);
|
|
2962
|
+
};
|
|
2963
|
+
}, [degraded, configReady, showPicker, tableVisible, activeTableProvider, interval, runTable]);
|
|
2964
|
+
return {
|
|
2965
|
+
statsLocal,
|
|
2966
|
+
peakLocal,
|
|
2967
|
+
updatedLocal,
|
|
2968
|
+
tableLocal: activeTableProvider ? tables.get(activeTableProvider) ?? null : null,
|
|
2969
|
+
tableLoading: activeTableProvider ? loadingTables.has(activeTableProvider) : false,
|
|
2970
|
+
refreshAll
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
// src/ui/hooks/use-refresh-all.ts
|
|
2975
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef6, useState as useState7 } from "react";
|
|
2976
|
+
var IDLE = { phase: "idle", message: "" };
|
|
2977
|
+
var CONNECTED_REFRESH_TIMEOUT_MS = 10 * 6e4;
|
|
2978
|
+
var STATUS_TTL_MS = 4e3;
|
|
2979
|
+
function awaitRefreshCompletion(request, timeoutMs = CONNECTED_REFRESH_TIMEOUT_MS) {
|
|
2980
|
+
return new Promise((resolve, reject) => {
|
|
2981
|
+
const timer = setTimeout(() => {
|
|
2982
|
+
reject(new Error(`refresh is still running after ${Math.round(timeoutMs / 1e3)} seconds; it may finish in the background`));
|
|
2983
|
+
}, timeoutMs);
|
|
2984
|
+
timer.unref?.();
|
|
2985
|
+
Promise.resolve().then(request).then(
|
|
2986
|
+
() => {
|
|
2987
|
+
clearTimeout(timer);
|
|
2988
|
+
resolve();
|
|
2989
|
+
},
|
|
2990
|
+
(cause) => {
|
|
2991
|
+
clearTimeout(timer);
|
|
2992
|
+
reject(cause);
|
|
2993
|
+
}
|
|
2994
|
+
);
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
function errorText(cause) {
|
|
2998
|
+
return cause instanceof Error && cause.message ? cause.message : "refresh failed";
|
|
2999
|
+
}
|
|
3000
|
+
function useRefreshAll({
|
|
3001
|
+
connected,
|
|
3002
|
+
requestDaemonRefresh,
|
|
3003
|
+
requestDegradedRefresh
|
|
3004
|
+
}) {
|
|
3005
|
+
const [status, setStatus] = useState7(IDLE);
|
|
3006
|
+
const flightRef = useRef6(null);
|
|
3007
|
+
const clearTimerRef = useRef6(null);
|
|
3008
|
+
const mountedRef = useRef6(true);
|
|
3009
|
+
useEffect7(() => {
|
|
3010
|
+
mountedRef.current = true;
|
|
3011
|
+
return () => {
|
|
3012
|
+
mountedRef.current = false;
|
|
3013
|
+
if (clearTimerRef.current) clearTimeout(clearTimerRef.current);
|
|
2831
3014
|
};
|
|
2832
|
-
}, [
|
|
2833
|
-
|
|
3015
|
+
}, []);
|
|
3016
|
+
const refreshAll = useCallback4(() => {
|
|
3017
|
+
if (flightRef.current) return;
|
|
3018
|
+
if (clearTimerRef.current) {
|
|
3019
|
+
clearTimeout(clearTimerRef.current);
|
|
3020
|
+
clearTimerRef.current = null;
|
|
3021
|
+
}
|
|
3022
|
+
setStatus({ phase: "refreshing", message: "Refreshing all data" });
|
|
3023
|
+
const task = connected ? awaitRefreshCompletion(requestDaemonRefresh) : Promise.resolve().then(requestDegradedRefresh);
|
|
3024
|
+
const owned = task.then(() => {
|
|
3025
|
+
if (!mountedRef.current) return;
|
|
3026
|
+
setStatus({
|
|
3027
|
+
phase: "success",
|
|
3028
|
+
message: "All data refreshed"
|
|
3029
|
+
});
|
|
3030
|
+
}).catch((cause) => {
|
|
3031
|
+
if (!mountedRef.current) return;
|
|
3032
|
+
setStatus({ phase: "error", message: `Refresh failed: ${errorText(cause)}` });
|
|
3033
|
+
}).finally(() => {
|
|
3034
|
+
if (flightRef.current === owned) flightRef.current = null;
|
|
3035
|
+
if (!mountedRef.current) return;
|
|
3036
|
+
clearTimerRef.current = setTimeout(() => {
|
|
3037
|
+
clearTimerRef.current = null;
|
|
3038
|
+
if (mountedRef.current) setStatus(IDLE);
|
|
3039
|
+
}, STATUS_TTL_MS);
|
|
3040
|
+
clearTimerRef.current.unref?.();
|
|
3041
|
+
});
|
|
3042
|
+
flightRef.current = owned;
|
|
3043
|
+
}, [connected, requestDaemonRefresh, requestDegradedRefresh]);
|
|
3044
|
+
return { status, refreshAll };
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// src/ui/hooks/use-config-state.ts
|
|
3048
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef7, useState as useState8 } from "react";
|
|
3049
|
+
|
|
3050
|
+
// src/config-sync.ts
|
|
3051
|
+
function describeConfigUpdateFailure(cause) {
|
|
3052
|
+
if (cause && typeof cause === "object") {
|
|
3053
|
+
const failure = cause;
|
|
3054
|
+
if (failure.kind === "conflict") {
|
|
3055
|
+
const state = failure.state;
|
|
3056
|
+
const revision = state?.config?.revision;
|
|
3057
|
+
return {
|
|
3058
|
+
message: typeof revision === "number" ? `configuration changed elsewhere (daemon revision ${revision})` : "settings changed elsewhere; reload before saving",
|
|
3059
|
+
conflictState: state?.config && typeof revision === "number" ? state : null
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
if (failure.kind === "persistence" && typeof failure.message === "string") {
|
|
3063
|
+
return { message: failure.message, conflictState: null };
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return {
|
|
3067
|
+
message: cause instanceof Error ? cause.message : "config update failed",
|
|
3068
|
+
conflictState: null
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
// src/ui/hooks/use-config-state.ts
|
|
3073
|
+
function useConfigState({
|
|
3074
|
+
initialConfig,
|
|
3075
|
+
cliInterval,
|
|
3076
|
+
connected,
|
|
3077
|
+
daemon
|
|
3078
|
+
}) {
|
|
3079
|
+
const [config, setConfig] = useState8(
|
|
3080
|
+
() => initialConfig ? applyStartup(initialConfig, cliInterval) : null
|
|
3081
|
+
);
|
|
3082
|
+
const [configSaveError, setConfigSaveError] = useState8(null);
|
|
3083
|
+
const configRef = useRef7(config);
|
|
3084
|
+
const pendingRef = useRef7(null);
|
|
3085
|
+
const queuedRef = useRef7(null);
|
|
3086
|
+
configRef.current = config;
|
|
3087
|
+
useEffect8(() => {
|
|
3088
|
+
if (initialConfig) return;
|
|
3089
|
+
void loadConfig().then((next) => setConfig(applyStartup(next, cliInterval)));
|
|
3090
|
+
}, [initialConfig, cliInterval]);
|
|
3091
|
+
const drain = useCallback5(async (first, expectedRevision) => {
|
|
3092
|
+
let candidate = first;
|
|
3093
|
+
let revision = expectedRevision;
|
|
3094
|
+
try {
|
|
3095
|
+
for (; ; ) {
|
|
3096
|
+
const outgoing = { ...candidate, revision };
|
|
3097
|
+
pendingRef.current = { config: outgoing, expectedRevision: revision };
|
|
3098
|
+
const saved = await daemon.setConfig(outgoing, revision);
|
|
3099
|
+
const queued = queuedRef.current;
|
|
3100
|
+
queuedRef.current = null;
|
|
3101
|
+
if (queued) {
|
|
3102
|
+
candidate = queued;
|
|
3103
|
+
revision = saved.config.revision;
|
|
3104
|
+
continue;
|
|
3105
|
+
}
|
|
3106
|
+
pendingRef.current = null;
|
|
3107
|
+
configRef.current = saved.config;
|
|
3108
|
+
setConfig(saved.config);
|
|
3109
|
+
setConfigSaveError(null);
|
|
3110
|
+
return;
|
|
3111
|
+
}
|
|
3112
|
+
} catch (cause) {
|
|
3113
|
+
pendingRef.current = null;
|
|
3114
|
+
queuedRef.current = null;
|
|
3115
|
+
const failure = describeConfigUpdateFailure(cause);
|
|
3116
|
+
if (failure.conflictState) {
|
|
3117
|
+
configRef.current = failure.conflictState.config;
|
|
3118
|
+
setConfig(failure.conflictState.config);
|
|
3119
|
+
}
|
|
3120
|
+
setConfigSaveError(failure.message);
|
|
3121
|
+
}
|
|
3122
|
+
}, [daemon]);
|
|
3123
|
+
const updateConfig = useCallback5((fn) => {
|
|
3124
|
+
const current = configRef.current ?? DEFAULTS;
|
|
3125
|
+
const next = normalizeConfig({ ...fn(current), revision: current.revision });
|
|
3126
|
+
configRef.current = next;
|
|
3127
|
+
setConfig(next);
|
|
3128
|
+
setConfigSaveError(null);
|
|
3129
|
+
if (connected) {
|
|
3130
|
+
if (pendingRef.current) queuedRef.current = next;
|
|
3131
|
+
else void drain(next, current.revision);
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
try {
|
|
3135
|
+
saveConfigSync(next);
|
|
3136
|
+
} catch (cause) {
|
|
3137
|
+
setConfigSaveError(cause instanceof Error ? cause.message : "config could not be saved");
|
|
3138
|
+
}
|
|
3139
|
+
}, [connected, drain]);
|
|
3140
|
+
const daemonConfig = daemon.config?.config;
|
|
3141
|
+
useEffect8(() => {
|
|
3142
|
+
if (!connected || !daemonConfig || pendingRef.current) return;
|
|
3143
|
+
if (daemonConfig.revision < (configRef.current?.revision ?? 0)) return;
|
|
3144
|
+
configRef.current = daemonConfig;
|
|
3145
|
+
setConfig(daemonConfig);
|
|
3146
|
+
}, [connected, daemonConfig]);
|
|
3147
|
+
return { config, configSaveError, updateConfig };
|
|
2834
3148
|
}
|
|
2835
3149
|
|
|
2836
3150
|
// src/ui/hooks/use-account-form.ts
|
|
2837
|
-
import { useState as
|
|
3151
|
+
import { useState as useState9, useCallback as useCallback6 } from "react";
|
|
2838
3152
|
function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSettingsCursor }) {
|
|
2839
|
-
const [accountForm, setAccountForm] =
|
|
3153
|
+
const [accountForm, setAccountForm] = useState9(null);
|
|
2840
3154
|
function openAddAccount(defaults) {
|
|
2841
3155
|
const providerId = defaults?.providerId ?? (detected[0] ?? "claude");
|
|
2842
3156
|
setAccountForm({
|
|
@@ -2888,7 +3202,7 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
2888
3202
|
});
|
|
2889
3203
|
setAccountForm(null);
|
|
2890
3204
|
}
|
|
2891
|
-
const cycleFormField =
|
|
3205
|
+
const cycleFormField = useCallback6((dir) => {
|
|
2892
3206
|
setAccountForm((f) => {
|
|
2893
3207
|
if (!f) return f;
|
|
2894
3208
|
const i = FORM_FIELDS.indexOf(f.field);
|
|
@@ -2897,14 +3211,14 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
2897
3211
|
return { ...f, field: next, caret };
|
|
2898
3212
|
});
|
|
2899
3213
|
}, []);
|
|
2900
|
-
const cycleProvider =
|
|
3214
|
+
const cycleProvider = useCallback6((dir) => {
|
|
2901
3215
|
setAccountForm((f) => {
|
|
2902
3216
|
if (!f) return f;
|
|
2903
3217
|
const i = PROVIDER_ORDER.indexOf(f.providerId);
|
|
2904
3218
|
return { ...f, providerId: PROVIDER_ORDER[(i + dir + PROVIDER_ORDER.length) % PROVIDER_ORDER.length] };
|
|
2905
3219
|
});
|
|
2906
3220
|
}, []);
|
|
2907
|
-
const cycleColor =
|
|
3221
|
+
const cycleColor = useCallback6((dir) => {
|
|
2908
3222
|
setAccountForm((f) => {
|
|
2909
3223
|
if (!f) return f;
|
|
2910
3224
|
const i = COLOR_PALETTE.indexOf(f.color);
|
|
@@ -2945,39 +3259,42 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
2945
3259
|
}
|
|
2946
3260
|
|
|
2947
3261
|
// src/app.tsx
|
|
2948
|
-
import { Fragment as Fragment4, jsx as
|
|
3262
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2949
3263
|
function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = null, mode = "degraded" }) {
|
|
2950
3264
|
const connected = mode === "connected" && baseUrl !== null && wsToken !== null;
|
|
2951
3265
|
const degraded = !connected;
|
|
2952
3266
|
const daemon = useDaemon(connected ? baseUrl : null, connected ? wsToken : null);
|
|
2953
|
-
const
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
const [
|
|
2960
|
-
const [
|
|
2961
|
-
const [
|
|
2962
|
-
const [
|
|
2963
|
-
const [
|
|
2964
|
-
const [
|
|
2965
|
-
const [
|
|
2966
|
-
const [
|
|
2967
|
-
const [
|
|
2968
|
-
const [
|
|
2969
|
-
const [
|
|
2970
|
-
const [
|
|
2971
|
-
const [
|
|
2972
|
-
const [
|
|
2973
|
-
const [
|
|
2974
|
-
const [
|
|
2975
|
-
const [
|
|
3267
|
+
const { config, configSaveError, updateConfig } = useConfigState({
|
|
3268
|
+
initialConfig,
|
|
3269
|
+
cliInterval,
|
|
3270
|
+
connected,
|
|
3271
|
+
daemon
|
|
3272
|
+
});
|
|
3273
|
+
const [detected, setDetected] = useState10([]);
|
|
3274
|
+
const [tab, setTab] = useState10(0);
|
|
3275
|
+
const [view, setView] = useState10(0);
|
|
3276
|
+
const [cursor, setCursor] = useState10(0);
|
|
3277
|
+
const [expanded, setExpanded] = useState10(-1);
|
|
3278
|
+
const [sort, setSort] = useState10(1);
|
|
3279
|
+
const [tableProvider, setTableProvider] = useState10(null);
|
|
3280
|
+
const [tableModel, setTableModel] = useState10(null);
|
|
3281
|
+
const [search, setSearch] = useState10("");
|
|
3282
|
+
const [searchMode, setSearchMode] = useState10(false);
|
|
3283
|
+
const [showSettings, setShowSettings] = useState10(false);
|
|
3284
|
+
const [settingsTab, setSettingsTab] = useState10("general");
|
|
3285
|
+
const [settingsCursor, setSettingsCursor] = useState10(0);
|
|
3286
|
+
const [tzEdit, setTzEdit] = useState10(null);
|
|
3287
|
+
const [tzError, setTzError] = useState10(null);
|
|
3288
|
+
const [tzCaret, setTzCaret] = useState10(0);
|
|
3289
|
+
const [searchCaret, setSearchCaret] = useState10(0);
|
|
3290
|
+
const [onboardSel, setOnboardSel] = useState10(null);
|
|
3291
|
+
const [onboardCursor, setOnboardCursor] = useState10(0);
|
|
3292
|
+
const [dashPage, setDashPage] = useState10(0);
|
|
2976
3293
|
const { exit } = useApp();
|
|
2977
3294
|
const { cols, rows, resizing, live } = useTerminalSize();
|
|
2978
|
-
const webRef =
|
|
2979
|
-
const webStartingRef =
|
|
2980
|
-
|
|
3295
|
+
const webRef = useRef8(null);
|
|
3296
|
+
const webStartingRef = useRef8(false);
|
|
3297
|
+
useEffect9(() => () => {
|
|
2981
3298
|
void webRef.current?.stop();
|
|
2982
3299
|
}, []);
|
|
2983
3300
|
const cfg = config ?? DEFAULTS;
|
|
@@ -2988,22 +3305,38 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
2988
3305
|
const accounts = useMemo2(() => buildAccounts(cfg, detected), [cfg, detected]);
|
|
2989
3306
|
const trackedAccountRows = useMemo2(() => getTrackedAccountRows(cfg, detected, accounts), [cfg, detected, accounts]);
|
|
2990
3307
|
const settingsRowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "providers" ? PROVIDER_ORDER.length : trackedAccountRows.length + 1;
|
|
2991
|
-
const accountsRef =
|
|
3308
|
+
const accountsRef = useRef8([]);
|
|
2992
3309
|
accountsRef.current = accounts;
|
|
2993
|
-
const rowCountRef =
|
|
2994
|
-
const tabRef =
|
|
3310
|
+
const rowCountRef = useRef8(0);
|
|
3311
|
+
const tabRef = useRef8(0);
|
|
2995
3312
|
tabRef.current = tab;
|
|
2996
|
-
const dashPageCountRef =
|
|
2997
|
-
const
|
|
2998
|
-
const
|
|
2999
|
-
const
|
|
3000
|
-
const
|
|
3001
|
-
const searchCaretRef = useRef6(0);
|
|
3313
|
+
const dashPageCountRef = useRef8(1);
|
|
3314
|
+
const tzValueRef = useRef8("");
|
|
3315
|
+
const tzCaretRef = useRef8(0);
|
|
3316
|
+
const searchValueRef = useRef8("");
|
|
3317
|
+
const searchCaretRef = useRef8(0);
|
|
3002
3318
|
const accountsKey = useMemo2(() => accounts.map(acctKey).join("|"), [accounts]);
|
|
3319
|
+
const allGroups = useMemo2(() => accountsByProvider(accounts), [accounts]);
|
|
3320
|
+
const tableProvs = useMemo2(
|
|
3321
|
+
() => allGroups.map((g) => g.provider).filter((pid) => PROVIDERS[pid].hasUsage),
|
|
3322
|
+
[allGroups]
|
|
3323
|
+
);
|
|
3324
|
+
const effTableProvider = tableProvider && tableProvs.includes(tableProvider) ? tableProvider : tableProvs[0] ?? null;
|
|
3325
|
+
const tableAccounts = useMemo2(
|
|
3326
|
+
() => effTableProvider ? accounts.filter((a) => a.providerId === effTableProvider) : [],
|
|
3327
|
+
[accounts, effTableProvider]
|
|
3328
|
+
);
|
|
3003
3329
|
const needsOnboarding = configReady && !cfg.onboarded;
|
|
3004
3330
|
const newProviders = configReady && cfg.onboarded ? PROVIDER_ORDER.filter((p) => !cfg.knownProviders.includes(p) && detected.includes(p)) : [];
|
|
3005
3331
|
const showPicker = needsOnboarding || newProviders.length > 0;
|
|
3006
|
-
const {
|
|
3332
|
+
const {
|
|
3333
|
+
statsLocal,
|
|
3334
|
+
peakLocal,
|
|
3335
|
+
updatedLocal,
|
|
3336
|
+
tableLocal,
|
|
3337
|
+
tableLoading,
|
|
3338
|
+
refreshAll: refreshAllDegraded
|
|
3339
|
+
} = useDegradedPolling({
|
|
3007
3340
|
degraded,
|
|
3008
3341
|
configReady,
|
|
3009
3342
|
showPicker,
|
|
@@ -3011,9 +3344,22 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3011
3344
|
accountsRef,
|
|
3012
3345
|
interval,
|
|
3013
3346
|
billingMs,
|
|
3014
|
-
tz
|
|
3347
|
+
tz,
|
|
3348
|
+
activeTableProvider: effTableProvider,
|
|
3349
|
+
tableVisible: tab === 1
|
|
3015
3350
|
});
|
|
3016
3351
|
const snapshot = daemon.snapshot;
|
|
3352
|
+
const daemonRefreshRef = useRef8(daemon.refresh);
|
|
3353
|
+
const degradedRefreshRef = useRef8(refreshAllDegraded);
|
|
3354
|
+
daemonRefreshRef.current = daemon.refresh;
|
|
3355
|
+
degradedRefreshRef.current = refreshAllDegraded;
|
|
3356
|
+
const requestDaemonRefresh = useCallback7(() => daemonRefreshRef.current("all"), []);
|
|
3357
|
+
const requestDegradedRefresh = useCallback7(() => degradedRefreshRef.current(), []);
|
|
3358
|
+
const { status: refreshStatus, refreshAll } = useRefreshAll({
|
|
3359
|
+
connected,
|
|
3360
|
+
requestDaemonRefresh,
|
|
3361
|
+
requestDegradedRefresh
|
|
3362
|
+
});
|
|
3017
3363
|
const stats = useMemo2(
|
|
3018
3364
|
() => connected ? toStatsMap(snapshot, accounts) : statsLocal,
|
|
3019
3365
|
[connected, snapshot, accounts, statsLocal]
|
|
@@ -3038,7 +3384,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3038
3384
|
[connected, snapshot, updatedLocal]
|
|
3039
3385
|
);
|
|
3040
3386
|
const intervalLabel = connected && snapshot?.intervalMs ? Math.round(snapshot.intervalMs / 1e3) : cfg.interval;
|
|
3041
|
-
const readyInputFor =
|
|
3387
|
+
const readyInputFor = useCallback7((id) => {
|
|
3042
3388
|
if (connected) {
|
|
3043
3389
|
const wa = snapshot?.accounts.find((a) => a.id === id);
|
|
3044
3390
|
if (!wa) return void 0;
|
|
@@ -3055,15 +3401,10 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3055
3401
|
() => focusId === null ? accounts : accounts.filter((a) => a.id === focusId),
|
|
3056
3402
|
[accounts, focusId]
|
|
3057
3403
|
);
|
|
3058
|
-
const allGroups = useMemo2(() => accountsByProvider(accounts), [accounts]);
|
|
3059
3404
|
const groups = useMemo2(
|
|
3060
3405
|
() => focusId === null ? allGroups : accountsByProvider(visibleAccounts),
|
|
3061
3406
|
[allGroups, visibleAccounts, focusId]
|
|
3062
3407
|
);
|
|
3063
|
-
const tableProvs = useMemo2(
|
|
3064
|
-
() => allGroups.map((g) => g.provider).filter((pid) => PROVIDERS[pid].hasUsage),
|
|
3065
|
-
[allGroups]
|
|
3066
|
-
);
|
|
3067
3408
|
const TOO_SMALL = cols < 40 || rows < 12;
|
|
3068
3409
|
const allReady = accounts.length > 0 && accounts.every((a) => accountReady(readyInputFor(a.id), a.providerId));
|
|
3069
3410
|
const { gridBudget } = useMemo2(() => computeChrome(slots, cols, rows), [slots, cols, rows]);
|
|
@@ -3102,11 +3443,6 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3102
3443
|
}
|
|
3103
3444
|
};
|
|
3104
3445
|
const { handlePasteData, isPasteInput } = usePaste(insertText);
|
|
3105
|
-
const effTableProvider = tableProvider && tableProvs.includes(tableProvider) ? tableProvider : tableProvs[0] ?? null;
|
|
3106
|
-
const tableAccounts = useMemo2(
|
|
3107
|
-
() => effTableProvider ? accounts.filter((a) => a.providerId === effTableProvider) : [],
|
|
3108
|
-
[accounts, effTableProvider]
|
|
3109
|
-
);
|
|
3110
3446
|
const SORTS_FOR = SORTS;
|
|
3111
3447
|
const tableAccountIds = useMemo2(() => tableAccounts.map((a) => a.id), [tableAccounts]);
|
|
3112
3448
|
const table = useMemo2(
|
|
@@ -3131,67 +3467,36 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3131
3467
|
detected: detected.includes(pid),
|
|
3132
3468
|
enabled: onboardEnabled.includes(pid)
|
|
3133
3469
|
}));
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
detectProviders().then(setDetected);
|
|
3470
|
+
useEffect9(() => {
|
|
3471
|
+
void detectProviders().then(setDetected);
|
|
3137
3472
|
}, []);
|
|
3138
3473
|
const tableKey = useMemo2(
|
|
3139
3474
|
() => `${effTableProvider}|${tableAccounts.map(acctKey).join(",")}|${tz}`,
|
|
3140
3475
|
[effTableProvider, tableAccounts, tz]
|
|
3141
3476
|
);
|
|
3142
|
-
|
|
3143
|
-
setTable(null);
|
|
3477
|
+
useEffect9(() => {
|
|
3144
3478
|
setCursor(0);
|
|
3145
3479
|
setExpanded(-1);
|
|
3146
3480
|
setTableModel(null);
|
|
3147
3481
|
setSort(1);
|
|
3148
|
-
setTableLoading(false);
|
|
3149
3482
|
}, [tableKey]);
|
|
3150
|
-
|
|
3151
|
-
if (!degraded || tab !== 1 || !effTableProvider) return;
|
|
3152
|
-
let active = true;
|
|
3153
|
-
let timer;
|
|
3154
|
-
const fetchOnce = async () => {
|
|
3155
|
-
try {
|
|
3156
|
-
const r = await withTimeout(fetchScopeTable(tableAccounts, tz));
|
|
3157
|
-
if (active) setTable(r);
|
|
3158
|
-
} catch {
|
|
3159
|
-
}
|
|
3160
|
-
};
|
|
3161
|
-
const run = async () => {
|
|
3162
|
-
setTableLoading(true);
|
|
3163
|
-
await fetchOnce();
|
|
3164
|
-
if (!active) return;
|
|
3165
|
-
setTableLoading(false);
|
|
3166
|
-
const loop = async () => {
|
|
3167
|
-
await fetchOnce();
|
|
3168
|
-
if (active) timer = setTimeout(loop, Math.max(interval, 1e4));
|
|
3169
|
-
};
|
|
3170
|
-
timer = setTimeout(loop, Math.max(interval, 1e4));
|
|
3171
|
-
};
|
|
3172
|
-
run();
|
|
3173
|
-
return () => {
|
|
3174
|
-
active = false;
|
|
3175
|
-
clearTimeout(timer);
|
|
3176
|
-
};
|
|
3177
|
-
}, [degraded, tab, tableKey, interval]);
|
|
3178
|
-
useEffect7(() => {
|
|
3483
|
+
useEffect9(() => {
|
|
3179
3484
|
setCursor(0);
|
|
3180
3485
|
setExpanded(-1);
|
|
3181
3486
|
}, [search, tableModel]);
|
|
3182
|
-
|
|
3487
|
+
useEffect9(() => {
|
|
3183
3488
|
setDashPage((p) => Math.min(p, dashPageCount - 1));
|
|
3184
3489
|
}, [dashPageCount]);
|
|
3185
|
-
|
|
3490
|
+
useEffect9(() => {
|
|
3186
3491
|
setSettingsCursor((c) => Math.max(-1, Math.min(c, settingsRowCount - 1)));
|
|
3187
3492
|
}, [settingsRowCount]);
|
|
3188
|
-
const resetView =
|
|
3493
|
+
const resetView = useCallback7(() => {
|
|
3189
3494
|
setCursor(0);
|
|
3190
3495
|
setExpanded(-1);
|
|
3191
3496
|
}, []);
|
|
3192
3497
|
const clampRow = (n) => Math.max(0, Math.min(rowCountRef.current - 1, n));
|
|
3193
3498
|
const mouse = useMouse();
|
|
3194
|
-
|
|
3499
|
+
useEffect9(() => {
|
|
3195
3500
|
if (!IS_TTY) return;
|
|
3196
3501
|
mouse.enable();
|
|
3197
3502
|
if (process.stdout.isTTY) {
|
|
@@ -3219,31 +3524,6 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3219
3524
|
process.stdin.off("data", onData);
|
|
3220
3525
|
};
|
|
3221
3526
|
}, []);
|
|
3222
|
-
const updateConfig = useCallback4((fn) => {
|
|
3223
|
-
setConfig((prev) => {
|
|
3224
|
-
const next = normalizeConfig(fn(prev ?? DEFAULTS));
|
|
3225
|
-
pendingLocalConfigRef.current = connected ? next : null;
|
|
3226
|
-
saveConfigSync(next);
|
|
3227
|
-
if (connected) {
|
|
3228
|
-
void daemon.setConfig(next).then((saved) => {
|
|
3229
|
-
if (pendingLocalConfigRef.current && reconcileDaemonConfig(next, saved, pendingLocalConfigRef.current).pendingLocalConfig === null) {
|
|
3230
|
-
pendingLocalConfigRef.current = null;
|
|
3231
|
-
}
|
|
3232
|
-
}).catch(() => {
|
|
3233
|
-
});
|
|
3234
|
-
}
|
|
3235
|
-
return next;
|
|
3236
|
-
});
|
|
3237
|
-
}, [connected, daemon]);
|
|
3238
|
-
const daemonConfig = daemon.config;
|
|
3239
|
-
useEffect7(() => {
|
|
3240
|
-
if (!connected || !daemonConfig) return;
|
|
3241
|
-
setConfig((prev) => {
|
|
3242
|
-
const reconciled = reconcileDaemonConfig(prev, daemonConfig, pendingLocalConfigRef.current);
|
|
3243
|
-
pendingLocalConfigRef.current = reconciled.pendingLocalConfig;
|
|
3244
|
-
return reconciled.config;
|
|
3245
|
-
});
|
|
3246
|
-
}, [connected, daemonConfig]);
|
|
3247
3527
|
function toggleOnboard(i) {
|
|
3248
3528
|
if (i < 0 || i >= pickerProviders.length) return;
|
|
3249
3529
|
const pid = pickerProviders[i];
|
|
@@ -3280,13 +3560,13 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3280
3560
|
setOnboardSel(null);
|
|
3281
3561
|
setOnboardCursor(0);
|
|
3282
3562
|
}
|
|
3283
|
-
const cycleAccount =
|
|
3563
|
+
const cycleAccount = useCallback7((dir) => {
|
|
3284
3564
|
if (slots.length <= 1) return;
|
|
3285
3565
|
const next = (activeSlotIdx + dir + slots.length) % slots.length;
|
|
3286
3566
|
updateConfig((c) => ({ ...c, activeAccountId: slots[next].id }));
|
|
3287
3567
|
resetView();
|
|
3288
3568
|
}, [slots, activeSlotIdx, updateConfig, resetView]);
|
|
3289
|
-
const cycleTableProvider =
|
|
3569
|
+
const cycleTableProvider = useCallback7((dir) => {
|
|
3290
3570
|
if (tableProvs.length <= 1) return;
|
|
3291
3571
|
const cur = effTableProvider ? tableProvs.indexOf(effTableProvider) : 0;
|
|
3292
3572
|
const nextProv = tableProvs[(cur + dir + tableProvs.length) % tableProvs.length];
|
|
@@ -3318,30 +3598,30 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3318
3598
|
return;
|
|
3319
3599
|
}
|
|
3320
3600
|
if (webRef.current) {
|
|
3321
|
-
openUrl(webRef.current.
|
|
3601
|
+
openUrl(webRef.current.browserUrl);
|
|
3322
3602
|
return;
|
|
3323
3603
|
}
|
|
3324
3604
|
if (webStartingRef.current) return;
|
|
3325
3605
|
webStartingRef.current = true;
|
|
3326
3606
|
try {
|
|
3327
|
-
const { startWebServer } = await import("./server-
|
|
3607
|
+
const { startWebServer } = await import("./server-4CDZMLH6.js");
|
|
3328
3608
|
const ctrl = await startWebServer({ config: cfg, log: false });
|
|
3329
3609
|
webRef.current = ctrl;
|
|
3330
|
-
openUrl(ctrl.
|
|
3610
|
+
openUrl(ctrl.browserUrl);
|
|
3331
3611
|
} catch {
|
|
3332
3612
|
} finally {
|
|
3333
3613
|
webStartingRef.current = false;
|
|
3334
3614
|
}
|
|
3335
3615
|
}
|
|
3336
|
-
const onTabSelect =
|
|
3616
|
+
const onTabSelect = useCallback7((i) => {
|
|
3337
3617
|
setTab(i);
|
|
3338
3618
|
resetView();
|
|
3339
3619
|
}, [resetView]);
|
|
3340
|
-
const onStripSelect =
|
|
3620
|
+
const onStripSelect = useCallback7((i) => {
|
|
3341
3621
|
updateConfig((c) => ({ ...c, activeAccountId: slots[i].id }));
|
|
3342
3622
|
resetView();
|
|
3343
3623
|
}, [slots, updateConfig, resetView]);
|
|
3344
|
-
const onProviderSelect =
|
|
3624
|
+
const onProviderSelect = useCallback7((p) => {
|
|
3345
3625
|
setTableProvider(p);
|
|
3346
3626
|
setCursor(0);
|
|
3347
3627
|
setExpanded(-1);
|
|
@@ -3350,7 +3630,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3350
3630
|
setSearchCaret(0);
|
|
3351
3631
|
setSearchMode(false);
|
|
3352
3632
|
}, []);
|
|
3353
|
-
const onRowClickToken =
|
|
3633
|
+
const onRowClickToken = useCallback7((idx) => {
|
|
3354
3634
|
if (idx === cursor) setExpanded((e) => e === idx ? -1 : idx);
|
|
3355
3635
|
else setCursor(idx);
|
|
3356
3636
|
}, [cursor]);
|
|
@@ -3359,10 +3639,10 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3359
3639
|
[tab, table, view]
|
|
3360
3640
|
);
|
|
3361
3641
|
const tokenModelOptions = useMemo2(() => tableModelOptions(rawTokenRows), [rawTokenRows]);
|
|
3362
|
-
|
|
3642
|
+
useEffect9(() => {
|
|
3363
3643
|
if (tableModel && !tokenModelOptions.includes(tableModel)) setTableModel(null);
|
|
3364
3644
|
}, [tableModel, tokenModelOptions]);
|
|
3365
|
-
const cycleTableModelFilter =
|
|
3645
|
+
const cycleTableModelFilter = useCallback7((dir) => {
|
|
3366
3646
|
setTableModel((cur) => cycleTableModel(cur, tokenModelOptions, dir));
|
|
3367
3647
|
resetView();
|
|
3368
3648
|
}, [tokenModelOptions, resetView]);
|
|
@@ -3372,77 +3652,86 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3372
3652
|
[tab, rawTokenRows, activeTableModel, search, sort]
|
|
3373
3653
|
);
|
|
3374
3654
|
useInput((input, key) => handleKey(input, key, {
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3655
|
+
onboarding: {
|
|
3656
|
+
show: showPicker,
|
|
3657
|
+
providers: pickerProviders,
|
|
3658
|
+
cursor: onboardCursor,
|
|
3659
|
+
setCursor: setOnboardCursor,
|
|
3660
|
+
toggle: toggleOnboard,
|
|
3661
|
+
confirm: confirmOnboarding
|
|
3662
|
+
},
|
|
3663
|
+
accountEditor: {
|
|
3664
|
+
form: accountForm,
|
|
3665
|
+
setForm: setAccountForm,
|
|
3666
|
+
commit: commitAccountForm,
|
|
3667
|
+
cycleField: cycleFormField,
|
|
3668
|
+
cycleProvider,
|
|
3669
|
+
cycleColor
|
|
3670
|
+
},
|
|
3671
|
+
timezoneEditor: {
|
|
3672
|
+
value: tzEdit,
|
|
3673
|
+
setValue: setTzEdit,
|
|
3674
|
+
setError: setTzError,
|
|
3675
|
+
setCaret: setTzCaret,
|
|
3676
|
+
valueRef: tzValueRef,
|
|
3677
|
+
caretRef: tzCaretRef
|
|
3678
|
+
},
|
|
3679
|
+
textInput: { isPrintable, insert: insertText },
|
|
3680
|
+
settings: {
|
|
3681
|
+
show: showSettings,
|
|
3682
|
+
setShow: setShowSettings,
|
|
3683
|
+
cursor: settingsCursor,
|
|
3684
|
+
tab: settingsTab,
|
|
3685
|
+
setTab: setSettingsTab,
|
|
3686
|
+
setCursor: setSettingsCursor,
|
|
3687
|
+
trackedAccounts: trackedAccountRows,
|
|
3688
|
+
moveAccount,
|
|
3689
|
+
toggleProvider,
|
|
3690
|
+
openEditAccount,
|
|
3691
|
+
openConfigureAccount,
|
|
3692
|
+
deleteAccount,
|
|
3693
|
+
openAddAccount
|
|
3694
|
+
},
|
|
3695
|
+
table: {
|
|
3696
|
+
tab,
|
|
3697
|
+
searchMode,
|
|
3698
|
+
setSearchMode,
|
|
3699
|
+
search,
|
|
3700
|
+
setSearch,
|
|
3701
|
+
setSearchCaret,
|
|
3702
|
+
searchValueRef,
|
|
3703
|
+
searchCaretRef,
|
|
3704
|
+
cycleProvider: cycleTableProvider,
|
|
3705
|
+
setExpanded,
|
|
3706
|
+
setSort,
|
|
3707
|
+
sorts: SORTS_FOR,
|
|
3708
|
+
cycleModel: cycleTableModelFilter,
|
|
3709
|
+
setView,
|
|
3710
|
+
cursor,
|
|
3711
|
+
rowCountRef,
|
|
3712
|
+
rows,
|
|
3713
|
+
setCursor,
|
|
3714
|
+
clampRow
|
|
3715
|
+
},
|
|
3716
|
+
dashboard: { paginated: dashPaginated, pageCount: dashPageCount, setPage: setDashPage },
|
|
3717
|
+
global: {
|
|
3718
|
+
exit,
|
|
3719
|
+
showLoader,
|
|
3720
|
+
configReady,
|
|
3721
|
+
toggleWeb,
|
|
3722
|
+
config: cfg,
|
|
3723
|
+
updateConfig,
|
|
3724
|
+
cycleAccount,
|
|
3725
|
+
setTab,
|
|
3726
|
+
resetView,
|
|
3727
|
+
slots,
|
|
3728
|
+
refreshAll
|
|
3729
|
+
}
|
|
3440
3730
|
}), { isActive: IS_TTY });
|
|
3441
|
-
if (
|
|
3442
|
-
if (
|
|
3443
|
-
if (resizing) return /* @__PURE__ */ jsx11(ResizingView, { cols: live.cols, rows: live.rows });
|
|
3731
|
+
if (!config) return /* @__PURE__ */ jsx12(Box11, { padding: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Loading..." }) });
|
|
3732
|
+
if (resizing) return /* @__PURE__ */ jsx12(ResizingView, { cols: live.cols, rows: live.rows });
|
|
3444
3733
|
if (showPicker) {
|
|
3445
|
-
return /* @__PURE__ */
|
|
3734
|
+
return /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, children: /* @__PURE__ */ jsx12(
|
|
3446
3735
|
Onboarding,
|
|
3447
3736
|
{
|
|
3448
3737
|
items: onboardItems,
|
|
@@ -3455,20 +3744,20 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3455
3744
|
) });
|
|
3456
3745
|
}
|
|
3457
3746
|
if (showLoader) {
|
|
3458
|
-
return /* @__PURE__ */
|
|
3747
|
+
return /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: /* @__PURE__ */ jsx12(LoadingView, { groups: allGroups, stats, cols, rows, readyInput: readyInputFor, privacyMode: cfg.privacyMode }) });
|
|
3459
3748
|
}
|
|
3460
3749
|
if (TOO_SMALL && !showSettings) {
|
|
3461
|
-
return /* @__PURE__ */
|
|
3750
|
+
return /* @__PURE__ */ jsx12(TinyFallback, { groups, stats, rows, cols, refreshStatus });
|
|
3462
3751
|
}
|
|
3463
3752
|
rowCountRef.current = tokenRows.length;
|
|
3464
|
-
return /* @__PURE__ */
|
|
3465
|
-
/* @__PURE__ */
|
|
3466
|
-
/* @__PURE__ */
|
|
3467
|
-
/* @__PURE__ */
|
|
3753
|
+
return /* @__PURE__ */ jsxs12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: [
|
|
3754
|
+
/* @__PURE__ */ jsxs12(Box11, { justifyContent: "space-between", children: [
|
|
3755
|
+
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
3756
|
+
/* @__PURE__ */ jsxs12(Text12, { bold: true, color: "greenBright", children: [
|
|
3468
3757
|
glyphs().dotSel,
|
|
3469
3758
|
" tokmon"
|
|
3470
3759
|
] }),
|
|
3471
|
-
/* @__PURE__ */
|
|
3760
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3472
3761
|
" ",
|
|
3473
3762
|
glyphs().middot,
|
|
3474
3763
|
" every ",
|
|
@@ -3476,31 +3765,37 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3476
3765
|
"s"
|
|
3477
3766
|
] })
|
|
3478
3767
|
] }),
|
|
3479
|
-
/* @__PURE__ */
|
|
3480
|
-
peak && /* @__PURE__ */
|
|
3481
|
-
/* @__PURE__ */
|
|
3482
|
-
/* @__PURE__ */
|
|
3768
|
+
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
3769
|
+
peak && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3770
|
+
/* @__PURE__ */ jsx12(PeakBadge, { peak }),
|
|
3771
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3483
3772
|
" ",
|
|
3484
3773
|
glyphs().middot,
|
|
3485
3774
|
" "
|
|
3486
3775
|
] })
|
|
3487
3776
|
] }),
|
|
3488
|
-
/* @__PURE__ */
|
|
3777
|
+
/* @__PURE__ */ jsx12(Text12, { dimColor: true, children: time(updated, tz) })
|
|
3489
3778
|
] })
|
|
3490
3779
|
] }),
|
|
3491
|
-
degraded && /* @__PURE__ */
|
|
3780
|
+
degraded && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3492
3781
|
glyphs().warn,
|
|
3493
3782
|
" degraded ",
|
|
3494
3783
|
glyphs().middot,
|
|
3495
3784
|
" background service unavailable, running in-process"
|
|
3496
3785
|
] }),
|
|
3497
|
-
connected && daemon.conn !== "live" && /* @__PURE__ */
|
|
3786
|
+
connected && daemon.conn !== "live" && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3498
3787
|
glyphs().warn,
|
|
3499
3788
|
" reconnecting ",
|
|
3500
3789
|
glyphs().middot,
|
|
3501
3790
|
" showing last known data"
|
|
3502
3791
|
] }),
|
|
3503
|
-
|
|
3792
|
+
configSaveError && /* @__PURE__ */ jsxs12(Text12, { color: "red", children: [
|
|
3793
|
+
glyphs().warn,
|
|
3794
|
+
" ",
|
|
3795
|
+
configSaveError
|
|
3796
|
+
] }),
|
|
3797
|
+
/* @__PURE__ */ jsx12(RefreshStatusLine, { status: refreshStatus }),
|
|
3798
|
+
showSettings ? /* @__PURE__ */ jsx12(
|
|
3504
3799
|
SettingsView,
|
|
3505
3800
|
{
|
|
3506
3801
|
config: cfg,
|
|
@@ -3515,20 +3810,20 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3515
3810
|
trackedAccounts: trackedAccountRows,
|
|
3516
3811
|
accountIdentities
|
|
3517
3812
|
}
|
|
3518
|
-
) : /* @__PURE__ */
|
|
3519
|
-
/* @__PURE__ */
|
|
3520
|
-
/* @__PURE__ */
|
|
3521
|
-
/* @__PURE__ */
|
|
3813
|
+
) : /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3814
|
+
/* @__PURE__ */ jsxs12(Box11, { marginTop: 1, marginBottom: 1, children: [
|
|
3815
|
+
/* @__PURE__ */ jsx12(TabBar, { tabs: TABS, active: tab, onSelect: onTabSelect }),
|
|
3816
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3522
3817
|
" Tab/",
|
|
3523
3818
|
glyphs().arrowL,
|
|
3524
3819
|
glyphs().arrowR
|
|
3525
3820
|
] })
|
|
3526
3821
|
] }),
|
|
3527
|
-
tab === 0 && /* @__PURE__ */
|
|
3528
|
-
/* @__PURE__ */
|
|
3529
|
-
slots.length > 1 && /* @__PURE__ */
|
|
3530
|
-
/* @__PURE__ */
|
|
3531
|
-
/* @__PURE__ */
|
|
3822
|
+
tab === 0 && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3823
|
+
/* @__PURE__ */ jsx12(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage, privacyMode: cfg.privacyMode }),
|
|
3824
|
+
slots.length > 1 && /* @__PURE__ */ jsxs12(Box11, { marginTop: 1, children: [
|
|
3825
|
+
/* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "focus " }),
|
|
3826
|
+
/* @__PURE__ */ jsx12(
|
|
3532
3827
|
AccountStrip,
|
|
3533
3828
|
{
|
|
3534
3829
|
slots,
|
|
@@ -3537,12 +3832,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3537
3832
|
}
|
|
3538
3833
|
)
|
|
3539
3834
|
] }),
|
|
3540
|
-
/* @__PURE__ */
|
|
3835
|
+
/* @__PURE__ */ jsx12(TotalsRow, { groups, stats, cols })
|
|
3541
3836
|
] }),
|
|
3542
|
-
tab === 1 && /* @__PURE__ */
|
|
3543
|
-
tableProvs.length > 0 && /* @__PURE__ */
|
|
3544
|
-
/* @__PURE__ */
|
|
3545
|
-
/* @__PURE__ */
|
|
3837
|
+
tab === 1 && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3838
|
+
tableProvs.length > 0 && /* @__PURE__ */ jsx12(TableProviderBar, { providers: tableProvs, active: effTableProvider, onSelect: onProviderSelect }),
|
|
3839
|
+
/* @__PURE__ */ jsx12(Box11, { height: 1 }),
|
|
3840
|
+
/* @__PURE__ */ jsx12(
|
|
3546
3841
|
ControlBar,
|
|
3547
3842
|
{
|
|
3548
3843
|
views: VIEWS,
|
|
@@ -3556,12 +3851,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3556
3851
|
showModel: true
|
|
3557
3852
|
}
|
|
3558
3853
|
),
|
|
3559
|
-
/* @__PURE__ */
|
|
3560
|
-
!effTableProvider ? /* @__PURE__ */
|
|
3854
|
+
/* @__PURE__ */ jsx12(Box11, { height: 1 }),
|
|
3855
|
+
!effTableProvider ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3561
3856
|
"No providers enabled ",
|
|
3562
3857
|
glyphs().emDash,
|
|
3563
3858
|
" press s to pick providers."
|
|
3564
|
-
] }) : tableLoading && !table ? /* @__PURE__ */
|
|
3859
|
+
] }) : tableLoading && !table ? /* @__PURE__ */ jsx12(Spinner, { label: "Loading history" }) : /* @__PURE__ */ jsx12(
|
|
3565
3860
|
TokenTable,
|
|
3566
3861
|
{
|
|
3567
3862
|
rows: tokenRows,
|
|
@@ -3574,12 +3869,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3574
3869
|
)
|
|
3575
3870
|
] })
|
|
3576
3871
|
] }),
|
|
3577
|
-
(tab === 0 || showSettings) && /* @__PURE__ */
|
|
3872
|
+
(tab === 0 || showSettings) && /* @__PURE__ */ jsx12(Footer, { hasAccounts: slots.length > 1, paginated: tab === 0 && dashPaginated, cols })
|
|
3578
3873
|
] });
|
|
3579
3874
|
}
|
|
3580
3875
|
|
|
3581
3876
|
// src/bootstrap-ink.tsx
|
|
3582
|
-
import { jsx as
|
|
3877
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
3583
3878
|
function enterAltScreen() {
|
|
3584
3879
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
3585
3880
|
}
|
|
@@ -3608,7 +3903,7 @@ async function bootstrapInk({ interval, config, daemon, mode }) {
|
|
|
3608
3903
|
if (altScreen) leaveAltScreen();
|
|
3609
3904
|
});
|
|
3610
3905
|
const { waitUntilExit } = render(
|
|
3611
|
-
/* @__PURE__ */
|
|
3906
|
+
/* @__PURE__ */ jsx13(MouseProvider, { children: /* @__PURE__ */ jsx13(
|
|
3612
3907
|
App,
|
|
3613
3908
|
{
|
|
3614
3909
|
interval,
|