tokmon 0.22.6 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{bootstrap-ink-CHJWH5XC.js → bootstrap-ink-UD3KDTOB.js} +1113 -934
- package/dist/{chunk-FO7IUYCM.js → chunk-AON6GE7D.js} +368 -280
- package/dist/{chunk-QTHCHB7S.js → chunk-DJPUYMZM.js} +52 -18
- package/dist/chunk-HV6AYQDB.js +192 -0
- package/dist/chunk-HXDAOOSI.js +154 -0
- package/dist/{chunk-AQNFQRWV.js → chunk-PGQK7MOE.js} +2583 -1711
- package/dist/cli.js +93 -54
- package/dist/{config-64N7KBZ7.js → config-5CNJN3P7.js} +1 -1
- package/dist/daemon-AW3YHFSJ.js +193 -0
- package/dist/daemon-handle-EYAXNNKZ.js +126 -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 +10 -6
- package/dist/daemon-ATXHNYTK.js +0 -213
- package/dist/daemon-handle-HLSKLMWU.js +0 -142
- package/dist/server-56C7MMEZ.js +0 -9
- package/dist/web/assets/breakdown-Bj0BAVYf.js +0 -4
- package/dist/web/assets/index-BCaPGYQA.css +0 -1
- package/dist/web/assets/index-Dziiggw_.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-BQpqTU-a.js +0 -1
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
coalesceTables,
|
|
11
11
|
col,
|
|
12
12
|
currency,
|
|
13
|
-
cursorModelSpend,
|
|
14
13
|
detectProviders,
|
|
15
14
|
fetchPeak,
|
|
16
15
|
modelColor,
|
|
@@ -20,7 +19,10 @@ import {
|
|
|
20
19
|
time,
|
|
21
20
|
tokens,
|
|
22
21
|
withTimeout
|
|
23
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-PGQK7MOE.js";
|
|
23
|
+
import {
|
|
24
|
+
glyphs
|
|
25
|
+
} from "./chunk-RF4GGQGM.js";
|
|
24
26
|
import {
|
|
25
27
|
COLOR_PALETTE,
|
|
26
28
|
DEFAULTS,
|
|
@@ -35,54 +37,17 @@ import {
|
|
|
35
37
|
sanitizeTyped,
|
|
36
38
|
saveConfigSync,
|
|
37
39
|
snapshotCacheFile
|
|
38
|
-
} from "./chunk-
|
|
39
|
-
import {
|
|
40
|
-
glyphs
|
|
41
|
-
} from "./chunk-RF4GGQGM.js";
|
|
40
|
+
} from "./chunk-DJPUYMZM.js";
|
|
42
41
|
|
|
43
42
|
// src/bootstrap-ink.tsx
|
|
44
43
|
import { render } from "ink";
|
|
45
44
|
import { MouseProvider } from "@zenobius/ink-mouse";
|
|
46
45
|
|
|
47
46
|
// src/app.tsx
|
|
48
|
-
import { useState as
|
|
49
|
-
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";
|
|
50
49
|
import { useMouse } from "@zenobius/ink-mouse";
|
|
51
50
|
|
|
52
|
-
// src/config-sync.ts
|
|
53
|
-
function deepEqual(a, b) {
|
|
54
|
-
if (a === b) return true;
|
|
55
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
56
|
-
if (Array.isArray(a) || Array.isArray(b)) {
|
|
57
|
-
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
58
|
-
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
const ka = Object.keys(a);
|
|
62
|
-
const kb = Object.keys(b);
|
|
63
|
-
if (ka.length !== kb.length) return false;
|
|
64
|
-
for (const k of ka) {
|
|
65
|
-
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
66
|
-
if (!deepEqual(a[k], b[k])) return false;
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
function reconcileDaemonConfig(previous, daemonConfig, pendingLocalConfig) {
|
|
71
|
-
if (pendingLocalConfig) {
|
|
72
|
-
if (deepEqual(daemonConfig, pendingLocalConfig)) {
|
|
73
|
-
return { config: previous && deepEqual(previous, daemonConfig) ? previous : daemonConfig, pendingLocalConfig: null };
|
|
74
|
-
}
|
|
75
|
-
return { config: previous ?? pendingLocalConfig, pendingLocalConfig };
|
|
76
|
-
}
|
|
77
|
-
if (previous && deepEqual(previous, daemonConfig)) {
|
|
78
|
-
return { config: previous, pendingLocalConfig: null };
|
|
79
|
-
}
|
|
80
|
-
if (previous?.onboarded === true && daemonConfig.onboarded === false) {
|
|
81
|
-
return { config: previous, pendingLocalConfig: null };
|
|
82
|
-
}
|
|
83
|
-
return { config: daemonConfig, pendingLocalConfig: null };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
51
|
// src/ui/shared.tsx
|
|
87
52
|
import { appendFileSync } from "fs";
|
|
88
53
|
import { memo, useEffect, useRef, useState } from "react";
|
|
@@ -1246,62 +1211,6 @@ function RowDetail({ row, indent }) {
|
|
|
1246
1211
|
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: currency(m.cost) })
|
|
1247
1212
|
] }, m.name)) });
|
|
1248
1213
|
}
|
|
1249
|
-
var CursorSpendTable = memo4(function CursorSpendTable2({ rows, cursor, maxRows, onRowClick }) {
|
|
1250
|
-
if (rows.length === 0) return /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No Cursor spend recorded locally." });
|
|
1251
|
-
const total = rows.reduce((a, r) => a + r.usd, 0);
|
|
1252
|
-
const totalAmt = rows.reduce((a, r) => a + r.requests, 0);
|
|
1253
|
-
const clamped = Math.min(cursor, rows.length - 1);
|
|
1254
|
-
const scrollStart = Math.max(0, Math.min(clamped - Math.floor(maxRows / 2), rows.length - maxRows));
|
|
1255
|
-
const visible = rows.slice(scrollStart, scrollStart + maxRows);
|
|
1256
|
-
const W = { model: 34, cost: 12, amount: 12, share: 8 };
|
|
1257
|
-
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
1258
|
-
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1259
|
-
/* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
|
|
1260
|
-
" ",
|
|
1261
|
-
col("Model", W.model, "left")
|
|
1262
|
-
] }),
|
|
1263
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Cost", W.cost) }),
|
|
1264
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Amount", W.amount) }),
|
|
1265
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, children: col("Share", W.share) })
|
|
1266
|
-
] }),
|
|
1267
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(W.model + W.cost + W.amount + W.share + 2) }),
|
|
1268
|
-
visible.map((r, vi) => {
|
|
1269
|
-
const idx = scrollStart + vi;
|
|
1270
|
-
const selected = idx === clamped;
|
|
1271
|
-
const share = total > 0 ? r.usd / total * 100 : 0;
|
|
1272
|
-
return /* @__PURE__ */ jsx4(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs4(Text4, { inverse: selected, children: [
|
|
1273
|
-
/* @__PURE__ */ jsxs4(Text4, { color: selected ? void 0 : "magenta", children: [
|
|
1274
|
-
selected ? `${glyphs().caretR} ` : " ",
|
|
1275
|
-
col(r.name, W.model, "left")
|
|
1276
|
-
] }),
|
|
1277
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: selected ? void 0 : "yellow", children: col(currency(r.usd), W.cost) }),
|
|
1278
|
-
/* @__PURE__ */ jsx4(Text4, { children: col(tokens(r.requests), W.amount) }),
|
|
1279
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: col(share.toFixed(1) + "%", W.share) })
|
|
1280
|
-
] }) }, r.name);
|
|
1281
|
-
}),
|
|
1282
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: glyphs().rule.repeat(W.model + W.cost + W.amount + W.share + 2) }),
|
|
1283
|
-
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1284
|
-
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: "greenBright", children: [
|
|
1285
|
-
" ",
|
|
1286
|
-
col("Total", W.model, "left")
|
|
1287
|
-
] }),
|
|
1288
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellowBright", children: col(currency(total), W.cost) }),
|
|
1289
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "yellow", children: col(tokens(totalAmt), W.amount) }),
|
|
1290
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: col("100%", W.share) })
|
|
1291
|
-
] }),
|
|
1292
|
-
/* @__PURE__ */ jsx4(Box4, { height: 1 }),
|
|
1293
|
-
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1294
|
-
"local spend by model (composerData) ",
|
|
1295
|
-
glyphs().middot,
|
|
1296
|
-
" est. API-equivalent ",
|
|
1297
|
-
glyphs().middot,
|
|
1298
|
-
" ",
|
|
1299
|
-
clamped + 1,
|
|
1300
|
-
"/",
|
|
1301
|
-
rows.length
|
|
1302
|
-
] })
|
|
1303
|
-
] });
|
|
1304
|
-
});
|
|
1305
1214
|
function fmtLabel(label) {
|
|
1306
1215
|
if (label.length === 7 && label[4] === "-") {
|
|
1307
1216
|
return `${MONTHS[Number(label.slice(5, 7))]} '${label.slice(2, 4)}`;
|
|
@@ -1598,7 +1507,7 @@ function osc8(text, url) {
|
|
|
1598
1507
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1599
1508
|
var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
1600
1509
|
const inner = cols - 4;
|
|
1601
|
-
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;
|
|
1602
1511
|
const optHint = (glyphs().shift === "\u21E7" ? "\u2325" : "opt") + "-click links ";
|
|
1603
1512
|
const OPT = IS_APPLE_TERMINAL ? optHint.length : 0;
|
|
1604
1513
|
const JUMP = "0-9=jump a/A=cycle ".length;
|
|
@@ -1607,7 +1516,7 @@ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
|
1607
1516
|
const showJump = hasAccounts && inner >= BASE + (showOpt ? OPT : 0) + JUMP + (paginated ? PAGE : 0);
|
|
1608
1517
|
const showPage = paginated && inner >= BASE + (showOpt ? OPT : 0) + (showJump ? JUMP : 0) + PAGE;
|
|
1609
1518
|
if (inner < BASE) {
|
|
1610
|
-
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" }) });
|
|
1611
1520
|
}
|
|
1612
1521
|
return /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexWrap: "nowrap", children: [
|
|
1613
1522
|
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "by " }),
|
|
@@ -1617,7 +1526,7 @@ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
|
1617
1526
|
/* @__PURE__ */ jsxs9(Text9, { dimColor: true, children: [
|
|
1618
1527
|
") ",
|
|
1619
1528
|
glyphs().middot,
|
|
1620
|
-
" O=repo W=web s=settings "
|
|
1529
|
+
" r/R=refresh O=repo W=web s=settings "
|
|
1621
1530
|
] }),
|
|
1622
1531
|
showOpt && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: optHint }),
|
|
1623
1532
|
showJump && /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "0-9=jump a/A=cycle " }),
|
|
@@ -1627,30 +1536,52 @@ var Footer = memo6(function Footer2({ hasAccounts, paginated, cols }) {
|
|
|
1627
1536
|
});
|
|
1628
1537
|
|
|
1629
1538
|
// src/ui/tiny-fallback.tsx
|
|
1630
|
-
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";
|
|
1631
1544
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1632
|
-
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 }) {
|
|
1633
1563
|
const maxLines = Math.max(1, rows - 4);
|
|
1634
1564
|
const visible = groups.slice(0, maxLines);
|
|
1635
1565
|
const hidden = groups.length - visible.length;
|
|
1636
1566
|
const w = Math.max(8, cols - 2);
|
|
1637
|
-
return /* @__PURE__ */
|
|
1638
|
-
/* @__PURE__ */
|
|
1567
|
+
return /* @__PURE__ */ jsxs11(Box10, { flexDirection: "column", paddingX: 1, height: rows, overflow: "hidden", children: [
|
|
1568
|
+
/* @__PURE__ */ jsxs11(Text11, { bold: true, color: "greenBright", children: [
|
|
1639
1569
|
glyphs().dotSel,
|
|
1640
1570
|
" tokmon"
|
|
1641
1571
|
] }),
|
|
1642
|
-
groups.length === 0 ? /* @__PURE__ */
|
|
1572
|
+
groups.length === 0 ? /* @__PURE__ */ jsxs11(Text11, { dimColor: true, children: [
|
|
1643
1573
|
"No providers ",
|
|
1644
1574
|
glyphs().emDash,
|
|
1645
1575
|
" s=settings"
|
|
1646
|
-
] }) : visible.map((g) => /* @__PURE__ */
|
|
1647
|
-
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: [
|
|
1648
1578
|
"+",
|
|
1649
1579
|
hidden,
|
|
1650
1580
|
" more (enlarge terminal)"
|
|
1651
1581
|
] }),
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__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" })
|
|
1654
1585
|
] });
|
|
1655
1586
|
}
|
|
1656
1587
|
function TinyRow({ provider, accounts, stats, width }) {
|
|
@@ -1661,14 +1592,14 @@ function TinyRow({ provider, accounts, stats, width }) {
|
|
|
1661
1592
|
const pctMetric = billings.flatMap((b) => b?.metrics ?? []).find((m) => m.format.kind === "percent");
|
|
1662
1593
|
const detail = meta.hasUsage ? `${currency(todayCost)} today` : pctMetric ? `${Math.round(pctMetric.used)}%` : "billing";
|
|
1663
1594
|
const name = truncateName(meta.name, Math.max(4, width - 18));
|
|
1664
|
-
return /* @__PURE__ */
|
|
1665
|
-
/* @__PURE__ */
|
|
1595
|
+
return /* @__PURE__ */ jsxs11(Box10, { width, children: [
|
|
1596
|
+
/* @__PURE__ */ jsxs11(Text11, { color: meta.color, children: [
|
|
1666
1597
|
glyphs().dot,
|
|
1667
1598
|
" "
|
|
1668
1599
|
] }),
|
|
1669
|
-
/* @__PURE__ */
|
|
1670
|
-
/* @__PURE__ */
|
|
1671
|
-
/* @__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 })
|
|
1672
1603
|
] });
|
|
1673
1604
|
}
|
|
1674
1605
|
|
|
@@ -1692,6 +1623,8 @@ function toWsUrl(baseUrl, token) {
|
|
|
1692
1623
|
if (url.protocol !== "ws:" && url.protocol !== "wss:") {
|
|
1693
1624
|
throw new Error(`unsupported daemon RPC protocol: ${url.protocol}`);
|
|
1694
1625
|
}
|
|
1626
|
+
url.hash = "";
|
|
1627
|
+
url.searchParams.delete("tokmonToken");
|
|
1695
1628
|
url.pathname = TOKMON_WS_PATH;
|
|
1696
1629
|
if (token) url.searchParams.set("wsToken", token);
|
|
1697
1630
|
return url.toString();
|
|
@@ -1878,12 +1811,12 @@ function createDaemonRpcClient(baseUrl, options = {}) {
|
|
|
1878
1811
|
};
|
|
1879
1812
|
};
|
|
1880
1813
|
return {
|
|
1881
|
-
getConfig: () => run((client) => client[TOKMON_WS_METHODS.getConfig]({})),
|
|
1882
|
-
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),
|
|
1883
1816
|
refresh: (scope = "all") => run((client) => client[TOKMON_WS_METHODS.refresh]({ scope })),
|
|
1884
1817
|
browseFs: (path) => run((client) => client[TOKMON_WS_METHODS.browseFs]({ path })),
|
|
1885
|
-
subscribeSnapshot: (onSnapshot) => subscribe((client) => client[TOKMON_WS_METHODS.snapshot]({}), onSnapshot),
|
|
1886
|
-
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),
|
|
1887
1820
|
async close() {
|
|
1888
1821
|
if (closed) return;
|
|
1889
1822
|
closed = true;
|
|
@@ -1940,7 +1873,7 @@ function useDaemon(baseUrl, wsToken) {
|
|
|
1940
1873
|
snapshot,
|
|
1941
1874
|
conn,
|
|
1942
1875
|
config,
|
|
1943
|
-
setConfig: (next) => requireClient().setConfig(next),
|
|
1876
|
+
setConfig: (next, expectedRevision) => requireClient().setConfig({ config: next, expectedRevision }),
|
|
1944
1877
|
refresh: (scope) => requireClient().refresh(scope),
|
|
1945
1878
|
browse: (path) => requireClient().browseFs(path)
|
|
1946
1879
|
};
|
|
@@ -1965,15 +1898,6 @@ function toStatsMap(snapshot, accounts) {
|
|
|
1965
1898
|
}
|
|
1966
1899
|
return out;
|
|
1967
1900
|
}
|
|
1968
|
-
function toCursorRows(snapshot, accountId) {
|
|
1969
|
-
if (!snapshot || !accountId) return null;
|
|
1970
|
-
const wa = snapshot.accounts.find((a) => a.id === accountId);
|
|
1971
|
-
if (!wa) return null;
|
|
1972
|
-
if (wa.billingState === "pending") return null;
|
|
1973
|
-
const spend = wa.billing?.modelSpend;
|
|
1974
|
-
if (!spend) return [];
|
|
1975
|
-
return spend.map((m) => ({ name: m.name, usd: m.usd, requests: m.requests }));
|
|
1976
|
-
}
|
|
1977
1901
|
function pickTable(snapshot, accountIds) {
|
|
1978
1902
|
if (!snapshot) return null;
|
|
1979
1903
|
const byId = indexById(snapshot);
|
|
@@ -1994,11 +1918,6 @@ var SORTS = [
|
|
|
1994
1918
|
{ label: "cost", dir: "up" },
|
|
1995
1919
|
{ label: "cost", dir: "down" }
|
|
1996
1920
|
];
|
|
1997
|
-
var CURSOR_SORTS = [
|
|
1998
|
-
{ label: "cost", dir: "down" },
|
|
1999
|
-
{ label: "amount", dir: "down" },
|
|
2000
|
-
{ label: "model", dir: null }
|
|
2001
|
-
];
|
|
2002
1921
|
var acctKey = (a) => `${a.id}:${a.homeDir ?? ""}`;
|
|
2003
1922
|
var clampCaret = (caret, len) => Math.max(0, Math.min(caret, len));
|
|
2004
1923
|
function spliceInsert(value, caret, text) {
|
|
@@ -2021,19 +1940,6 @@ function upsert(prev, account, patch) {
|
|
|
2021
1940
|
next.set(account.id, { ...cur, account, ...patch });
|
|
2022
1941
|
return next;
|
|
2023
1942
|
}
|
|
2024
|
-
async function fetchScopeTable(scope, tz) {
|
|
2025
|
-
const tables = await Promise.all(scope.map(async (acc) => {
|
|
2026
|
-
const provider = PROVIDERS[acc.providerId];
|
|
2027
|
-
if (!provider.fetchTable) return null;
|
|
2028
|
-
try {
|
|
2029
|
-
return await provider.fetchTable(acc, tz);
|
|
2030
|
-
} catch {
|
|
2031
|
-
return null;
|
|
2032
|
-
}
|
|
2033
|
-
}));
|
|
2034
|
-
const valid = tables.filter((t) => t !== null);
|
|
2035
|
-
return coalesceTables(valid);
|
|
2036
|
-
}
|
|
2037
1943
|
function sortLabel(entry) {
|
|
2038
1944
|
if (entry.dir === "up") return `${entry.label} ${glyphs().arrowU}`;
|
|
2039
1945
|
if (entry.dir === "down") return `${entry.label} ${glyphs().arrowD}`;
|
|
@@ -2098,579 +2004,567 @@ function filterTokenRows(rows, q) {
|
|
|
2098
2004
|
const s = q.toLowerCase();
|
|
2099
2005
|
return rows.filter((r) => r.label.toLowerCase().includes(s) || r.models.some((m) => m.toLowerCase().includes(s)));
|
|
2100
2006
|
}
|
|
2101
|
-
function filterCursorRows(rows, q) {
|
|
2102
|
-
if (!q) return rows;
|
|
2103
|
-
const s = q.toLowerCase();
|
|
2104
|
-
return rows.filter((r) => r.name.toLowerCase().includes(s));
|
|
2105
|
-
}
|
|
2106
|
-
function sortCursorRows(rows, sortIdx) {
|
|
2107
|
-
const out = [...rows];
|
|
2108
|
-
switch (sortIdx % CURSOR_SORTS.length) {
|
|
2109
|
-
case 1:
|
|
2110
|
-
return out.sort((a, b) => b.requests - a.requests);
|
|
2111
|
-
case 2:
|
|
2112
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
2113
|
-
default:
|
|
2114
|
-
return out.sort((a, b) => b.usd - a.usd);
|
|
2115
|
-
}
|
|
2116
|
-
}
|
|
2117
2007
|
|
|
2118
|
-
// src/ui/keybindings.ts
|
|
2119
|
-
function
|
|
2120
|
-
const {
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
searchMode,
|
|
2146
|
-
setSearchMode,
|
|
2147
|
-
search,
|
|
2148
|
-
setSearch,
|
|
2149
|
-
setSearchCaret,
|
|
2150
|
-
searchValueRef,
|
|
2151
|
-
searchCaretRef,
|
|
2152
|
-
showLoader,
|
|
2153
|
-
configReady,
|
|
2154
|
-
toggleWeb,
|
|
2155
|
-
settingsCursor,
|
|
2156
|
-
settingsTab,
|
|
2157
|
-
setSettingsTab,
|
|
2158
|
-
setShowSettings,
|
|
2159
|
-
cfg,
|
|
2160
|
-
trackedAccountRows,
|
|
2161
|
-
moveAccount,
|
|
2162
|
-
setSettingsCursor,
|
|
2163
|
-
toggleProvider,
|
|
2164
|
-
openEditAccount,
|
|
2165
|
-
openConfigureAccount,
|
|
2166
|
-
deleteAccount,
|
|
2167
|
-
openAddAccount,
|
|
2168
|
-
cycleAccount,
|
|
2169
|
-
setTab,
|
|
2170
|
-
resetView,
|
|
2171
|
-
slots,
|
|
2172
|
-
dashPaginated,
|
|
2173
|
-
dashPageCount,
|
|
2174
|
-
setDashPage,
|
|
2175
|
-
cycleTableProvider,
|
|
2176
|
-
setExpanded,
|
|
2177
|
-
setSort,
|
|
2178
|
-
SORTS_FOR,
|
|
2179
|
-
tableIsCursor,
|
|
2180
|
-
cycleTableModel: cycleTableModel2,
|
|
2181
|
-
setView,
|
|
2182
|
-
cursor,
|
|
2183
|
-
rowCountRef,
|
|
2184
|
-
rows,
|
|
2185
|
-
setCursor,
|
|
2186
|
-
clampRow
|
|
2187
|
-
} = ctx;
|
|
2188
|
-
if (showPicker) {
|
|
2189
|
-
if (input === "q") {
|
|
2190
|
-
exit();
|
|
2191
|
-
return;
|
|
2192
|
-
}
|
|
2193
|
-
const startIdx = pickerProviders.length;
|
|
2194
|
-
if (key.upArrow) {
|
|
2195
|
-
setOnboardCursor((c) => Math.max(0, c - 1));
|
|
2196
|
-
return;
|
|
2197
|
-
}
|
|
2198
|
-
if (key.downArrow) {
|
|
2199
|
-
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);
|
|
2200
2035
|
return;
|
|
2201
2036
|
}
|
|
2202
|
-
if (
|
|
2203
|
-
|
|
2037
|
+
if (key.rightArrow) {
|
|
2038
|
+
cycleProvider(1);
|
|
2204
2039
|
return;
|
|
2205
2040
|
}
|
|
2206
2041
|
if (key.return) {
|
|
2207
|
-
|
|
2208
|
-
else toggleOnboard(onboardCursor);
|
|
2042
|
+
setForm((value) => value && { ...value, field: "name", caret: value.name.length });
|
|
2209
2043
|
return;
|
|
2210
2044
|
}
|
|
2211
2045
|
return;
|
|
2212
2046
|
}
|
|
2213
|
-
if (
|
|
2214
|
-
if (key.
|
|
2215
|
-
|
|
2216
|
-
return;
|
|
2217
|
-
}
|
|
2218
|
-
if (key.ctrl && input === "s") {
|
|
2219
|
-
commitAccountForm();
|
|
2047
|
+
if (form.field === "color") {
|
|
2048
|
+
if (key.leftArrow) {
|
|
2049
|
+
cycleColor(-1);
|
|
2220
2050
|
return;
|
|
2221
2051
|
}
|
|
2222
|
-
if (key.
|
|
2223
|
-
|
|
2052
|
+
if (key.rightArrow) {
|
|
2053
|
+
cycleColor(1);
|
|
2224
2054
|
return;
|
|
2225
2055
|
}
|
|
2226
|
-
if (key.
|
|
2227
|
-
|
|
2056
|
+
if (key.return) {
|
|
2057
|
+
commit();
|
|
2228
2058
|
return;
|
|
2229
2059
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
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 });
|
|
2232
2082
|
return;
|
|
2233
2083
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
if (
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
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}`);
|
|
2248
2124
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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);
|
|
2263
2164
|
}
|
|
2264
|
-
|
|
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) {
|
|
2265
2244
|
if (key.leftArrow) {
|
|
2266
|
-
|
|
2245
|
+
switchTab(-1);
|
|
2267
2246
|
return;
|
|
2268
2247
|
}
|
|
2269
2248
|
if (key.rightArrow) {
|
|
2270
|
-
|
|
2271
|
-
return;
|
|
2272
|
-
}
|
|
2273
|
-
if (key.ctrl && input === "a") {
|
|
2274
|
-
setAccountForm((f) => f && { ...f, caret: 0 });
|
|
2249
|
+
switchTab(1);
|
|
2275
2250
|
return;
|
|
2276
2251
|
}
|
|
2277
|
-
if (key.
|
|
2278
|
-
|
|
2252
|
+
if (key.downArrow || key.return) {
|
|
2253
|
+
setCursor(0);
|
|
2279
2254
|
return;
|
|
2280
2255
|
}
|
|
2281
|
-
if (key.
|
|
2282
|
-
|
|
2283
|
-
setAccountForm((f) => f && { ...f, error: "Name required", caret: f.name.length });
|
|
2284
|
-
return;
|
|
2285
|
-
}
|
|
2286
|
-
setAccountForm((f) => f && { ...f, field: tf === "name" ? "homeDir" : "color", caret: tf === "name" ? f.homeDir.length : f.caret });
|
|
2287
|
-
return;
|
|
2288
|
-
}
|
|
2289
|
-
if (key.backspace || key.delete) {
|
|
2290
|
-
setAccountForm((f) => {
|
|
2291
|
-
if (!f || f.field !== "name" && f.field !== "homeDir") return f;
|
|
2292
|
-
const r = spliceBackspace(f[f.field], f.caret);
|
|
2293
|
-
return { ...f, [f.field]: r.value, caret: r.caret, error: null };
|
|
2294
|
-
});
|
|
2256
|
+
if (key.upArrow) {
|
|
2257
|
+
setCursor(Math.max(0, rowCount - 1));
|
|
2295
2258
|
return;
|
|
2296
2259
|
}
|
|
2297
|
-
if (isPrintable(input, key)) {
|
|
2298
|
-
const clean = sanitizeTyped(input);
|
|
2299
|
-
if (clean) insertText(clean);
|
|
2300
|
-
}
|
|
2301
2260
|
return;
|
|
2302
2261
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
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]);
|
|
2308
2282
|
}
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
if (cursor < trackedAccounts.length) {
|
|
2286
|
+
const row = trackedAccounts[cursor];
|
|
2309
2287
|
if (key.return) {
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
setTzEdit(null);
|
|
2314
|
-
setTzError(null);
|
|
2315
|
-
} else if (isValidTimezone(val)) {
|
|
2316
|
-
updateConfig((c) => ({ ...c, timezone: val }));
|
|
2317
|
-
setTzEdit(null);
|
|
2318
|
-
setTzError(null);
|
|
2288
|
+
if (row.source === "configured") {
|
|
2289
|
+
const account = config.accounts.find((candidate) => candidate.id === row.explicitId);
|
|
2290
|
+
if (account) openEditAccount(account);
|
|
2319
2291
|
} else {
|
|
2320
|
-
|
|
2292
|
+
openConfigureAccount(row);
|
|
2321
2293
|
}
|
|
2322
2294
|
return;
|
|
2323
2295
|
}
|
|
2324
|
-
if (
|
|
2325
|
-
|
|
2296
|
+
if (row.source === "configured" && row.explicitId && (input === "d" || input === "x")) {
|
|
2297
|
+
deleteAccount(row.explicitId);
|
|
2326
2298
|
return;
|
|
2327
2299
|
}
|
|
2328
|
-
if (
|
|
2329
|
-
|
|
2300
|
+
if (input === " ") {
|
|
2301
|
+
updateConfig((current) => ({ ...current, activeAccountId: row.id }));
|
|
2330
2302
|
return;
|
|
2331
2303
|
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
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 }));
|
|
2334
2312
|
return;
|
|
2335
2313
|
}
|
|
2336
|
-
if (
|
|
2337
|
-
|
|
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 }));
|
|
2338
2317
|
return;
|
|
2339
2318
|
}
|
|
2340
|
-
if (
|
|
2341
|
-
|
|
2342
|
-
tzValueRef.current = r.value;
|
|
2343
|
-
tzCaretRef.current = r.caret;
|
|
2344
|
-
setTzEdit(r.value);
|
|
2345
|
-
setTzCaret(r.caret);
|
|
2346
|
-
setTzError(null);
|
|
2319
|
+
if (index === 2 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2320
|
+
updateConfig((current) => ({ ...current, clearScreen: !current.clearScreen }));
|
|
2347
2321
|
return;
|
|
2348
2322
|
}
|
|
2349
|
-
if (
|
|
2350
|
-
|
|
2351
|
-
if (clean) insertText(clean);
|
|
2352
|
-
}
|
|
2353
|
-
return;
|
|
2354
|
-
}
|
|
2355
|
-
if (tab === 1 && searchMode) {
|
|
2356
|
-
if (key.return || key.escape) {
|
|
2357
|
-
setSearchMode(false);
|
|
2358
|
-
if (key.escape) {
|
|
2359
|
-
setSearch("");
|
|
2360
|
-
setSearchCaret(0);
|
|
2361
|
-
}
|
|
2323
|
+
if (index === 3 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2324
|
+
updateConfig((current) => ({ ...current, privacyMode: !current.privacyMode }));
|
|
2362
2325
|
return;
|
|
2363
2326
|
}
|
|
2364
|
-
if (
|
|
2365
|
-
|
|
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" }));
|
|
2366
2333
|
return;
|
|
2367
2334
|
}
|
|
2368
|
-
if (
|
|
2369
|
-
|
|
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
|
+
}
|
|
2370
2345
|
return;
|
|
2371
2346
|
}
|
|
2372
|
-
if (
|
|
2373
|
-
|
|
2347
|
+
if (index === 6 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2348
|
+
updateConfig((current) => ({ ...current, dashboardLayout: current.dashboardLayout === "grid" ? "single" : "grid" }));
|
|
2374
2349
|
return;
|
|
2375
2350
|
}
|
|
2376
|
-
if (
|
|
2377
|
-
|
|
2378
|
-
return;
|
|
2351
|
+
if (index === 7 && (inputKey.leftArrow || inputKey.rightArrow || inputKey.return)) {
|
|
2352
|
+
updateConfig((current) => ({ ...current, defaultFocus: current.defaultFocus === "all" ? "last" : "all" }));
|
|
2379
2353
|
}
|
|
2380
|
-
if (key.backspace || key.delete) {
|
|
2381
|
-
const r = spliceBackspace(searchValueRef.current, searchCaretRef.current);
|
|
2382
|
-
searchValueRef.current = r.value;
|
|
2383
|
-
searchCaretRef.current = r.caret;
|
|
2384
|
-
setSearch(r.value);
|
|
2385
|
-
setSearchCaret(r.caret);
|
|
2386
|
-
return;
|
|
2387
|
-
}
|
|
2388
|
-
if (isPrintable(input, key)) {
|
|
2389
|
-
const clean = sanitizeTyped(input);
|
|
2390
|
-
if (clean) insertText(clean);
|
|
2391
|
-
}
|
|
2392
|
-
return;
|
|
2393
2354
|
}
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
// src/ui/keybindings-navigation.ts
|
|
2358
|
+
function handleGlobalCommand(input, ctx) {
|
|
2359
|
+
const { global, table, settings } = ctx;
|
|
2394
2360
|
if (input === "q") {
|
|
2395
|
-
exit();
|
|
2396
|
-
return;
|
|
2361
|
+
global.exit();
|
|
2362
|
+
return true;
|
|
2397
2363
|
}
|
|
2398
2364
|
if (input === "O") {
|
|
2399
2365
|
openUrl(REPO_URL);
|
|
2400
|
-
return;
|
|
2401
|
-
}
|
|
2402
|
-
if (input === "W" || input === "w" && tab !== 1 && !showSettings) {
|
|
2403
|
-
if (showLoader || !configReady) return;
|
|
2404
|
-
void toggleWeb();
|
|
2405
|
-
return;
|
|
2366
|
+
return true;
|
|
2406
2367
|
}
|
|
2407
|
-
if (
|
|
2408
|
-
if (
|
|
2409
|
-
|
|
2410
|
-
return;
|
|
2411
|
-
}
|
|
2412
|
-
const switchSettingsTab = (dir) => {
|
|
2413
|
-
const idx = SETTINGS_TABS.indexOf(settingsTab);
|
|
2414
|
-
setSettingsTab(SETTINGS_TABS[(idx + dir + SETTINGS_TABS.length) % SETTINGS_TABS.length]);
|
|
2415
|
-
setSettingsCursor(-1);
|
|
2416
|
-
setTzEdit(null);
|
|
2417
|
-
setTzError(null);
|
|
2418
|
-
};
|
|
2419
|
-
const rowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "providers" ? PROVIDER_ORDER.length : trackedAccountRows.length + 1;
|
|
2420
|
-
if (key.tab) {
|
|
2421
|
-
switchSettingsTab(key.shift ? -1 : 1);
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
if (input === "[") {
|
|
2425
|
-
switchSettingsTab(-1);
|
|
2426
|
-
return;
|
|
2427
|
-
}
|
|
2428
|
-
if (input === "]") {
|
|
2429
|
-
switchSettingsTab(1);
|
|
2430
|
-
return;
|
|
2431
|
-
}
|
|
2432
|
-
if (settingsCursor < 0) {
|
|
2433
|
-
if (key.leftArrow) {
|
|
2434
|
-
switchSettingsTab(-1);
|
|
2435
|
-
return;
|
|
2436
|
-
}
|
|
2437
|
-
if (key.rightArrow) {
|
|
2438
|
-
switchSettingsTab(1);
|
|
2439
|
-
return;
|
|
2440
|
-
}
|
|
2441
|
-
if (key.downArrow || key.return) {
|
|
2442
|
-
setSettingsCursor(0);
|
|
2443
|
-
return;
|
|
2444
|
-
}
|
|
2445
|
-
if (key.upArrow) {
|
|
2446
|
-
setSettingsCursor(Math.max(0, rowCount - 1));
|
|
2447
|
-
return;
|
|
2448
|
-
}
|
|
2449
|
-
return;
|
|
2450
|
-
}
|
|
2451
|
-
const selectedAccountRow = settingsTab === "accounts" && settingsCursor >= 0 && settingsCursor < trackedAccountRows.length ? trackedAccountRows[settingsCursor] : null;
|
|
2452
|
-
if (selectedAccountRow?.source === "configured" && selectedAccountRow.explicitIndex !== void 0 && key.shift && (key.upArrow || key.downArrow)) {
|
|
2453
|
-
moveAccount(selectedAccountRow.explicitIndex, key.upArrow ? -1 : 1);
|
|
2454
|
-
return;
|
|
2455
|
-
}
|
|
2456
|
-
if (key.upArrow) {
|
|
2457
|
-
setSettingsCursor((c) => Math.max(-1, c - 1));
|
|
2458
|
-
return;
|
|
2459
|
-
}
|
|
2460
|
-
if (key.downArrow) {
|
|
2461
|
-
setSettingsCursor((c) => Math.min(rowCount - 1, c + 1));
|
|
2462
|
-
return;
|
|
2463
|
-
}
|
|
2464
|
-
if (settingsTab === "general") {
|
|
2465
|
-
if (settingsCursor === 0) {
|
|
2466
|
-
if (key.leftArrow) updateConfig((c) => ({ ...c, interval: Math.max(1, c.interval - 1) }));
|
|
2467
|
-
if (key.rightArrow) updateConfig((c) => ({ ...c, interval: c.interval + 1 }));
|
|
2468
|
-
return;
|
|
2469
|
-
}
|
|
2470
|
-
if (settingsCursor === 1) {
|
|
2471
|
-
if (key.leftArrow) updateConfig((c) => ({ ...c, billingInterval: Math.max(1, c.billingInterval - 1) }));
|
|
2472
|
-
if (key.rightArrow) updateConfig((c) => ({ ...c, billingInterval: c.billingInterval + 1 }));
|
|
2473
|
-
return;
|
|
2474
|
-
}
|
|
2475
|
-
if (settingsCursor === 2 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2476
|
-
updateConfig((c) => ({ ...c, clearScreen: !c.clearScreen }));
|
|
2477
|
-
return;
|
|
2478
|
-
}
|
|
2479
|
-
if (settingsCursor === 3 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2480
|
-
updateConfig((c) => ({ ...c, privacyMode: !c.privacyMode }));
|
|
2481
|
-
return;
|
|
2482
|
-
}
|
|
2483
|
-
if (settingsCursor === 4) {
|
|
2484
|
-
if (isPrintable(input, key)) {
|
|
2485
|
-
const clean = sanitizeTyped(input);
|
|
2486
|
-
if (clean.length === 1) updateConfig((c) => ({ ...c, privacyToggleKey: clean }));
|
|
2487
|
-
}
|
|
2488
|
-
if (key.backspace || key.delete) updateConfig((c) => ({ ...c, privacyToggleKey: "p" }));
|
|
2489
|
-
return;
|
|
2490
|
-
}
|
|
2491
|
-
if (settingsCursor === 5) {
|
|
2492
|
-
if (key.return) {
|
|
2493
|
-
const init = cfg.timezone ?? "";
|
|
2494
|
-
setTzEdit(init);
|
|
2495
|
-
setTzCaret(init.length);
|
|
2496
|
-
setTzError(null);
|
|
2497
|
-
}
|
|
2498
|
-
if (key.leftArrow || key.rightArrow) updateConfig((c) => ({ ...c, timezone: c.timezone === null ? systemTimezone() : null }));
|
|
2499
|
-
return;
|
|
2500
|
-
}
|
|
2501
|
-
if (settingsCursor === 6 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2502
|
-
updateConfig((c) => ({ ...c, dashboardLayout: c.dashboardLayout === "grid" ? "single" : "grid" }));
|
|
2503
|
-
return;
|
|
2504
|
-
}
|
|
2505
|
-
if (settingsCursor === 7 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
2506
|
-
updateConfig((c) => ({ ...c, defaultFocus: c.defaultFocus === "all" ? "last" : "all" }));
|
|
2507
|
-
return;
|
|
2508
|
-
}
|
|
2509
|
-
return;
|
|
2510
|
-
}
|
|
2511
|
-
if (settingsTab === "providers") {
|
|
2512
|
-
if (settingsCursor >= 0 && settingsCursor < PROVIDER_ORDER.length && (input === " " || key.return || key.leftArrow || key.rightArrow)) {
|
|
2513
|
-
toggleProvider(PROVIDER_ORDER[settingsCursor]);
|
|
2514
|
-
}
|
|
2515
|
-
return;
|
|
2516
|
-
}
|
|
2517
|
-
if (settingsTab === "accounts" && settingsCursor >= 0 && settingsCursor < trackedAccountRows.length) {
|
|
2518
|
-
const row = trackedAccountRows[settingsCursor];
|
|
2519
|
-
if (key.return) {
|
|
2520
|
-
if (row.source === "configured") {
|
|
2521
|
-
const acc = cfg.accounts.find((a) => a.id === row.explicitId);
|
|
2522
|
-
if (acc) openEditAccount(acc);
|
|
2523
|
-
} else {
|
|
2524
|
-
openConfigureAccount(row);
|
|
2525
|
-
}
|
|
2526
|
-
return;
|
|
2527
|
-
}
|
|
2528
|
-
if (row.source === "configured" && row.explicitId && (input === "d" || input === "x")) {
|
|
2529
|
-
deleteAccount(row.explicitId);
|
|
2530
|
-
return;
|
|
2531
|
-
}
|
|
2532
|
-
if (input === " ") {
|
|
2533
|
-
updateConfig((c) => ({ ...c, activeAccountId: row.id }));
|
|
2534
|
-
return;
|
|
2535
|
-
}
|
|
2536
|
-
return;
|
|
2537
|
-
}
|
|
2538
|
-
if (settingsTab === "accounts" && settingsCursor === trackedAccountRows.length && key.return) {
|
|
2539
|
-
openAddAccount();
|
|
2540
|
-
}
|
|
2541
|
-
return;
|
|
2368
|
+
if (input === "W" || input === "w" && table.tab !== 1 && !settings.show) {
|
|
2369
|
+
if (!global.showLoader && global.configReady) void global.toggleWeb();
|
|
2370
|
+
return true;
|
|
2542
2371
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
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 }));
|
|
2545
2379
|
return;
|
|
2546
2380
|
}
|
|
2547
2381
|
if (input === "s") {
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2382
|
+
settings.setTab("general");
|
|
2383
|
+
settings.setShow(true);
|
|
2384
|
+
settings.setCursor(-1);
|
|
2551
2385
|
return;
|
|
2552
2386
|
}
|
|
2553
2387
|
if (input === "a") {
|
|
2554
|
-
cycleAccount(1);
|
|
2388
|
+
global.cycleAccount(1);
|
|
2555
2389
|
return;
|
|
2556
2390
|
}
|
|
2557
2391
|
if (input === "A") {
|
|
2558
|
-
cycleAccount(-1);
|
|
2392
|
+
global.cycleAccount(-1);
|
|
2559
2393
|
return;
|
|
2560
2394
|
}
|
|
2561
2395
|
if (key.tab) {
|
|
2562
|
-
setTab((
|
|
2396
|
+
global.setTab((current) => (current + 1) % TABS.length);
|
|
2563
2397
|
resetView();
|
|
2564
2398
|
return;
|
|
2565
2399
|
}
|
|
2566
2400
|
if (input && /^[0-9]$/.test(input) && slots.length > 1) {
|
|
2567
2401
|
const target = slots[parseInt(input, 10)];
|
|
2568
2402
|
if (target) {
|
|
2569
|
-
updateConfig((
|
|
2403
|
+
updateConfig((current) => ({ ...current, activeAccountId: target.id }));
|
|
2570
2404
|
resetView();
|
|
2571
2405
|
}
|
|
2572
2406
|
return;
|
|
2573
2407
|
}
|
|
2574
|
-
if (tab === 0) {
|
|
2575
|
-
if (
|
|
2408
|
+
if (table.tab === 0) {
|
|
2409
|
+
if (dashboard.paginated) {
|
|
2576
2410
|
if (input === "]" || key.downArrow || key.pageDown) {
|
|
2577
|
-
|
|
2411
|
+
dashboard.setPage((page) => Math.min(dashboard.pageCount - 1, page + 1));
|
|
2578
2412
|
return;
|
|
2579
2413
|
}
|
|
2580
2414
|
if (input === "[" || key.upArrow || key.pageUp) {
|
|
2581
|
-
|
|
2415
|
+
dashboard.setPage((page) => Math.max(0, page - 1));
|
|
2582
2416
|
return;
|
|
2583
2417
|
}
|
|
2584
2418
|
}
|
|
2585
2419
|
if (key.upArrow || key.downArrow || key.pageUp || key.pageDown || input === "G" || input === "g") return;
|
|
2586
2420
|
}
|
|
2587
|
-
if (tab
|
|
2588
|
-
if (input === "p") {
|
|
2589
|
-
cycleTableProvider(1);
|
|
2590
|
-
return;
|
|
2591
|
-
}
|
|
2592
|
-
if (input === "P") {
|
|
2593
|
-
cycleTableProvider(-1);
|
|
2594
|
-
return;
|
|
2595
|
-
}
|
|
2596
|
-
if (input === "/") {
|
|
2597
|
-
setSearchMode(true);
|
|
2598
|
-
setSearchCaret(search.length);
|
|
2599
|
-
return;
|
|
2600
|
-
}
|
|
2601
|
-
if (key.escape) {
|
|
2602
|
-
if (search) {
|
|
2603
|
-
setSearch("");
|
|
2604
|
-
setSearchCaret(0);
|
|
2605
|
-
} else setExpanded(-1);
|
|
2606
|
-
return;
|
|
2607
|
-
}
|
|
2608
|
-
if (input === "o") {
|
|
2609
|
-
setSort((s) => (s + 1) % SORTS_FOR.length);
|
|
2610
|
-
resetView();
|
|
2611
|
-
return;
|
|
2612
|
-
}
|
|
2613
|
-
if (!tableIsCursor) {
|
|
2614
|
-
if (input === "d") {
|
|
2615
|
-
setView(0);
|
|
2616
|
-
resetView();
|
|
2617
|
-
return;
|
|
2618
|
-
}
|
|
2619
|
-
if (input === "w") {
|
|
2620
|
-
setView(1);
|
|
2621
|
-
resetView();
|
|
2622
|
-
return;
|
|
2623
|
-
}
|
|
2624
|
-
if (input === "m") {
|
|
2625
|
-
cycleTableModel2(1);
|
|
2626
|
-
return;
|
|
2627
|
-
}
|
|
2628
|
-
if (input === "M") {
|
|
2629
|
-
cycleTableModel2(-1);
|
|
2630
|
-
return;
|
|
2631
|
-
}
|
|
2632
|
-
if (key.leftArrow) {
|
|
2633
|
-
setView((v) => (v - 1 + VIEWS.length) % VIEWS.length);
|
|
2634
|
-
resetView();
|
|
2635
|
-
return;
|
|
2636
|
-
}
|
|
2637
|
-
if (key.rightArrow) {
|
|
2638
|
-
setView((v) => (v + 1) % VIEWS.length);
|
|
2639
|
-
resetView();
|
|
2640
|
-
return;
|
|
2641
|
-
}
|
|
2642
|
-
if (key.return) {
|
|
2643
|
-
setExpanded((e) => e === cursor ? -1 : cursor);
|
|
2644
|
-
return;
|
|
2645
|
-
}
|
|
2646
|
-
}
|
|
2647
|
-
} else {
|
|
2421
|
+
if (table.tab !== 1) {
|
|
2648
2422
|
if (key.leftArrow || key.rightArrow) {
|
|
2649
|
-
setTab((
|
|
2423
|
+
global.setTab((current) => (current + 1) % TABS.length);
|
|
2650
2424
|
resetView();
|
|
2651
|
-
return;
|
|
2652
2425
|
}
|
|
2426
|
+
return;
|
|
2653
2427
|
}
|
|
2654
|
-
if (
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2428
|
+
if (input === "p") {
|
|
2429
|
+
table.cycleProvider(1);
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
if (input === "P") {
|
|
2433
|
+
table.cycleProvider(-1);
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
if (input === "/") {
|
|
2437
|
+
table.setSearchMode(true);
|
|
2438
|
+
table.setSearchCaret(table.search.length);
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
if (key.escape) {
|
|
2442
|
+
if (table.search) {
|
|
2443
|
+
table.setSearch("");
|
|
2444
|
+
table.setSearchCaret(0);
|
|
2445
|
+
} else table.setExpanded(-1);
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
if (input === "o") {
|
|
2449
|
+
table.setSort((sort) => (sort + 1) % table.sorts.length);
|
|
2450
|
+
resetView();
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
if (input === "d") {
|
|
2454
|
+
table.setView(0);
|
|
2455
|
+
resetView();
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
if (input === "w") {
|
|
2459
|
+
table.setView(1);
|
|
2460
|
+
resetView();
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
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)));
|
|
2671
2499
|
}
|
|
2672
2500
|
}
|
|
2673
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;
|
|
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);
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2674
2568
|
// src/ui/hooks/use-terminal-size.ts
|
|
2675
2569
|
import { useState as useState4, useEffect as useEffect4 } from "react";
|
|
2676
2570
|
import { useStdout } from "ink";
|
|
@@ -2824,7 +2718,7 @@ function useLoader({ configReady, showPicker, accountsKey, allReady, tooSmall, s
|
|
|
2824
2718
|
}
|
|
2825
2719
|
|
|
2826
2720
|
// src/ui/hooks/use-degraded-polling.ts
|
|
2827
|
-
import {
|
|
2721
|
+
import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "react";
|
|
2828
2722
|
|
|
2829
2723
|
// src/client/seed-cache.ts
|
|
2830
2724
|
import { readFile } from "fs/promises";
|
|
@@ -2843,92 +2737,420 @@ async function loadSeedSnapshot() {
|
|
|
2843
2737
|
}
|
|
2844
2738
|
|
|
2845
2739
|
// src/ui/hooks/use-degraded-polling.ts
|
|
2846
|
-
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
|
+
}) {
|
|
2847
2760
|
const [statsLocal, setStats] = useState6(/* @__PURE__ */ new Map());
|
|
2848
2761
|
const [peakLocal, setPeak] = useState6(null);
|
|
2849
|
-
const [error, setError] = useState6(null);
|
|
2850
2762
|
const [updatedLocal, setUpdated] = useState6(/* @__PURE__ */ new Date());
|
|
2763
|
+
const [tables, setTables] = useState6(/* @__PURE__ */ new Map());
|
|
2764
|
+
const [loadingTables, setLoadingTables] = useState6(/* @__PURE__ */ new Set());
|
|
2851
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]);
|
|
2852
2786
|
useEffect6(() => {
|
|
2853
2787
|
if (!degraded) return;
|
|
2854
2788
|
if (seededRef.current || !configReady || showPicker || accountsRef.current.length === 0) return;
|
|
2855
2789
|
seededRef.current = true;
|
|
2856
|
-
|
|
2790
|
+
const epoch = epochRef.current;
|
|
2791
|
+
void loadSeedSnapshot().then((snap) => {
|
|
2792
|
+
if (epoch !== epochRef.current) return;
|
|
2857
2793
|
setStats((prev) => {
|
|
2858
2794
|
if (prev.size > 0) return prev;
|
|
2859
2795
|
const next = new Map(prev);
|
|
2860
2796
|
for (const acc of accountsRef.current) {
|
|
2861
|
-
const
|
|
2862
|
-
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
|
+
}
|
|
2863
2805
|
}
|
|
2864
2806
|
return next;
|
|
2865
2807
|
});
|
|
2866
2808
|
});
|
|
2867
|
-
}, [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]);
|
|
2868
2920
|
useEffect6(() => {
|
|
2869
2921
|
if (!degraded || !configReady || showPicker) return;
|
|
2870
2922
|
let active = true;
|
|
2871
2923
|
let timer;
|
|
2872
|
-
const
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
if (!provider.hasUsage || !provider.fetchSummary) return;
|
|
2877
|
-
try {
|
|
2878
|
-
const dashboard = await withTimeout(provider.fetchSummary(acc, tz));
|
|
2879
|
-
if (active) setStats((prev) => upsert(prev, acc, { dashboard }));
|
|
2880
|
-
} catch {
|
|
2881
|
-
}
|
|
2882
|
-
}));
|
|
2883
|
-
if (active) {
|
|
2884
|
-
setError(null);
|
|
2885
|
-
setUpdated(/* @__PURE__ */ new Date());
|
|
2886
|
-
}
|
|
2887
|
-
} finally {
|
|
2888
|
-
if (active) timer = setTimeout(load, interval);
|
|
2889
|
-
}
|
|
2924
|
+
const loop = async () => {
|
|
2925
|
+
await runSummary().catch(() => {
|
|
2926
|
+
});
|
|
2927
|
+
if (active) timer = setTimeout(loop, interval);
|
|
2890
2928
|
};
|
|
2891
|
-
|
|
2929
|
+
void loop();
|
|
2892
2930
|
return () => {
|
|
2893
2931
|
active = false;
|
|
2894
|
-
clearTimeout(timer);
|
|
2932
|
+
if (timer) clearTimeout(timer);
|
|
2895
2933
|
};
|
|
2896
|
-
}, [degraded,
|
|
2934
|
+
}, [degraded, configReady, showPicker, interval, runSummary]);
|
|
2897
2935
|
useEffect6(() => {
|
|
2898
2936
|
if (!degraded || !configReady || showPicker) return;
|
|
2899
2937
|
let active = true;
|
|
2900
2938
|
let timer;
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
await Promise.all(accountsRef.current.map(async (acc) => {
|
|
2905
|
-
const provider = PROVIDERS[acc.providerId];
|
|
2906
|
-
if (!provider.hasBilling || !provider.fetchBilling) return;
|
|
2907
|
-
try {
|
|
2908
|
-
const billing = await withTimeout(provider.fetchBilling(acc, tz));
|
|
2909
|
-
if (active) setStats((prev) => upsert(prev, acc, { billing }));
|
|
2910
|
-
} catch {
|
|
2911
|
-
}
|
|
2912
|
-
}));
|
|
2913
|
-
const p = await peakP;
|
|
2914
|
-
if (active && p) setPeak(p);
|
|
2915
|
-
} finally {
|
|
2916
|
-
if (active) timer = setTimeout(load, billingMs);
|
|
2917
|
-
}
|
|
2939
|
+
const loop = async () => {
|
|
2940
|
+
await Promise.allSettled([runBilling(), runPeak()]);
|
|
2941
|
+
if (active) timer = setTimeout(loop, billingMs);
|
|
2918
2942
|
};
|
|
2919
|
-
|
|
2943
|
+
void loop();
|
|
2920
2944
|
return () => {
|
|
2921
2945
|
active = false;
|
|
2922
|
-
clearTimeout(timer);
|
|
2946
|
+
if (timer) clearTimeout(timer);
|
|
2923
2947
|
};
|
|
2924
|
-
}, [degraded,
|
|
2925
|
-
|
|
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));
|
|
2957
|
+
};
|
|
2958
|
+
void loop();
|
|
2959
|
+
return () => {
|
|
2960
|
+
active = false;
|
|
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);
|
|
3014
|
+
};
|
|
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 };
|
|
2926
3148
|
}
|
|
2927
3149
|
|
|
2928
3150
|
// src/ui/hooks/use-account-form.ts
|
|
2929
|
-
import { useState as
|
|
3151
|
+
import { useState as useState9, useCallback as useCallback6 } from "react";
|
|
2930
3152
|
function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSettingsCursor }) {
|
|
2931
|
-
const [accountForm, setAccountForm] =
|
|
3153
|
+
const [accountForm, setAccountForm] = useState9(null);
|
|
2932
3154
|
function openAddAccount(defaults) {
|
|
2933
3155
|
const providerId = defaults?.providerId ?? (detected[0] ?? "claude");
|
|
2934
3156
|
setAccountForm({
|
|
@@ -2980,7 +3202,7 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
2980
3202
|
});
|
|
2981
3203
|
setAccountForm(null);
|
|
2982
3204
|
}
|
|
2983
|
-
const cycleFormField =
|
|
3205
|
+
const cycleFormField = useCallback6((dir) => {
|
|
2984
3206
|
setAccountForm((f) => {
|
|
2985
3207
|
if (!f) return f;
|
|
2986
3208
|
const i = FORM_FIELDS.indexOf(f.field);
|
|
@@ -2989,14 +3211,14 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
2989
3211
|
return { ...f, field: next, caret };
|
|
2990
3212
|
});
|
|
2991
3213
|
}, []);
|
|
2992
|
-
const cycleProvider =
|
|
3214
|
+
const cycleProvider = useCallback6((dir) => {
|
|
2993
3215
|
setAccountForm((f) => {
|
|
2994
3216
|
if (!f) return f;
|
|
2995
3217
|
const i = PROVIDER_ORDER.indexOf(f.providerId);
|
|
2996
3218
|
return { ...f, providerId: PROVIDER_ORDER[(i + dir + PROVIDER_ORDER.length) % PROVIDER_ORDER.length] };
|
|
2997
3219
|
});
|
|
2998
3220
|
}, []);
|
|
2999
|
-
const cycleColor =
|
|
3221
|
+
const cycleColor = useCallback6((dir) => {
|
|
3000
3222
|
setAccountForm((f) => {
|
|
3001
3223
|
if (!f) return f;
|
|
3002
3224
|
const i = COLOR_PALETTE.indexOf(f.color);
|
|
@@ -3037,40 +3259,42 @@ function useAccountForm({ cfg, detected, updateConfig, trackedAccountRows, setSe
|
|
|
3037
3259
|
}
|
|
3038
3260
|
|
|
3039
3261
|
// src/app.tsx
|
|
3040
|
-
import { Fragment as Fragment4, jsx as
|
|
3262
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3041
3263
|
function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = null, mode = "degraded" }) {
|
|
3042
3264
|
const connected = mode === "connected" && baseUrl !== null && wsToken !== null;
|
|
3043
3265
|
const degraded = !connected;
|
|
3044
3266
|
const daemon = useDaemon(connected ? baseUrl : null, connected ? wsToken : null);
|
|
3045
|
-
const
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
const [
|
|
3052
|
-
const [
|
|
3053
|
-
const [
|
|
3054
|
-
const [
|
|
3055
|
-
const [
|
|
3056
|
-
const [
|
|
3057
|
-
const [
|
|
3058
|
-
const [
|
|
3059
|
-
const [
|
|
3060
|
-
const [
|
|
3061
|
-
const [
|
|
3062
|
-
const [
|
|
3063
|
-
const [
|
|
3064
|
-
const [
|
|
3065
|
-
const [
|
|
3066
|
-
const [
|
|
3067
|
-
const [
|
|
3068
|
-
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);
|
|
3069
3293
|
const { exit } = useApp();
|
|
3070
3294
|
const { cols, rows, resizing, live } = useTerminalSize();
|
|
3071
|
-
const webRef =
|
|
3072
|
-
const webStartingRef =
|
|
3073
|
-
|
|
3295
|
+
const webRef = useRef8(null);
|
|
3296
|
+
const webStartingRef = useRef8(false);
|
|
3297
|
+
useEffect9(() => () => {
|
|
3074
3298
|
void webRef.current?.stop();
|
|
3075
3299
|
}, []);
|
|
3076
3300
|
const cfg = config ?? DEFAULTS;
|
|
@@ -3081,22 +3305,38 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3081
3305
|
const accounts = useMemo2(() => buildAccounts(cfg, detected), [cfg, detected]);
|
|
3082
3306
|
const trackedAccountRows = useMemo2(() => getTrackedAccountRows(cfg, detected, accounts), [cfg, detected, accounts]);
|
|
3083
3307
|
const settingsRowCount = settingsTab === "general" ? GENERAL_ROWS : settingsTab === "providers" ? PROVIDER_ORDER.length : trackedAccountRows.length + 1;
|
|
3084
|
-
const accountsRef =
|
|
3308
|
+
const accountsRef = useRef8([]);
|
|
3085
3309
|
accountsRef.current = accounts;
|
|
3086
|
-
const rowCountRef =
|
|
3087
|
-
const tabRef =
|
|
3310
|
+
const rowCountRef = useRef8(0);
|
|
3311
|
+
const tabRef = useRef8(0);
|
|
3088
3312
|
tabRef.current = tab;
|
|
3089
|
-
const dashPageCountRef =
|
|
3090
|
-
const
|
|
3091
|
-
const
|
|
3092
|
-
const
|
|
3093
|
-
const
|
|
3094
|
-
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);
|
|
3095
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
|
+
);
|
|
3096
3329
|
const needsOnboarding = configReady && !cfg.onboarded;
|
|
3097
3330
|
const newProviders = configReady && cfg.onboarded ? PROVIDER_ORDER.filter((p) => !cfg.knownProviders.includes(p) && detected.includes(p)) : [];
|
|
3098
3331
|
const showPicker = needsOnboarding || newProviders.length > 0;
|
|
3099
|
-
const {
|
|
3332
|
+
const {
|
|
3333
|
+
statsLocal,
|
|
3334
|
+
peakLocal,
|
|
3335
|
+
updatedLocal,
|
|
3336
|
+
tableLocal,
|
|
3337
|
+
tableLoading,
|
|
3338
|
+
refreshAll: refreshAllDegraded
|
|
3339
|
+
} = useDegradedPolling({
|
|
3100
3340
|
degraded,
|
|
3101
3341
|
configReady,
|
|
3102
3342
|
showPicker,
|
|
@@ -3104,9 +3344,22 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3104
3344
|
accountsRef,
|
|
3105
3345
|
interval,
|
|
3106
3346
|
billingMs,
|
|
3107
|
-
tz
|
|
3347
|
+
tz,
|
|
3348
|
+
activeTableProvider: effTableProvider,
|
|
3349
|
+
tableVisible: tab === 1
|
|
3108
3350
|
});
|
|
3109
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
|
+
});
|
|
3110
3363
|
const stats = useMemo2(
|
|
3111
3364
|
() => connected ? toStatsMap(snapshot, accounts) : statsLocal,
|
|
3112
3365
|
[connected, snapshot, accounts, statsLocal]
|
|
@@ -3131,7 +3384,7 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3131
3384
|
[connected, snapshot, updatedLocal]
|
|
3132
3385
|
);
|
|
3133
3386
|
const intervalLabel = connected && snapshot?.intervalMs ? Math.round(snapshot.intervalMs / 1e3) : cfg.interval;
|
|
3134
|
-
const readyInputFor =
|
|
3387
|
+
const readyInputFor = useCallback7((id) => {
|
|
3135
3388
|
if (connected) {
|
|
3136
3389
|
const wa = snapshot?.accounts.find((a) => a.id === id);
|
|
3137
3390
|
if (!wa) return void 0;
|
|
@@ -3148,12 +3401,10 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3148
3401
|
() => focusId === null ? accounts : accounts.filter((a) => a.id === focusId),
|
|
3149
3402
|
[accounts, focusId]
|
|
3150
3403
|
);
|
|
3151
|
-
const allGroups = useMemo2(() => accountsByProvider(accounts), [accounts]);
|
|
3152
3404
|
const groups = useMemo2(
|
|
3153
3405
|
() => focusId === null ? allGroups : accountsByProvider(visibleAccounts),
|
|
3154
3406
|
[allGroups, visibleAccounts, focusId]
|
|
3155
3407
|
);
|
|
3156
|
-
const tableProvs = useMemo2(() => allGroups.map((g) => g.provider), [allGroups]);
|
|
3157
3408
|
const TOO_SMALL = cols < 40 || rows < 12;
|
|
3158
3409
|
const allReady = accounts.length > 0 && accounts.every((a) => accountReady(readyInputFor(a.id), a.providerId));
|
|
3159
3410
|
const { gridBudget } = useMemo2(() => computeChrome(slots, cols, rows), [slots, cols, rows]);
|
|
@@ -3192,22 +3443,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3192
3443
|
}
|
|
3193
3444
|
};
|
|
3194
3445
|
const { handlePasteData, isPasteInput } = usePaste(insertText);
|
|
3195
|
-
const
|
|
3196
|
-
const tableIsCursor = !!effTableProvider && !PROVIDERS[effTableProvider].hasUsage;
|
|
3197
|
-
const tableAccounts = useMemo2(
|
|
3198
|
-
() => effTableProvider ? accounts.filter((a) => a.providerId === effTableProvider) : [],
|
|
3199
|
-
[accounts, effTableProvider]
|
|
3200
|
-
);
|
|
3201
|
-
const SORTS_FOR = tableIsCursor ? CURSOR_SORTS : SORTS;
|
|
3446
|
+
const SORTS_FOR = SORTS;
|
|
3202
3447
|
const tableAccountIds = useMemo2(() => tableAccounts.map((a) => a.id), [tableAccounts]);
|
|
3203
3448
|
const table = useMemo2(
|
|
3204
3449
|
() => connected ? pickTable(snapshot, tableAccountIds) : tableLocal,
|
|
3205
3450
|
[connected, snapshot, tableAccountIds, tableLocal]
|
|
3206
3451
|
);
|
|
3207
|
-
const cursorRows = useMemo2(
|
|
3208
|
-
() => connected ? toCursorRows(snapshot, tableAccounts[0]?.id) : cursorRowsLocal,
|
|
3209
|
-
[connected, snapshot, tableAccounts, cursorRowsLocal]
|
|
3210
|
-
);
|
|
3211
3452
|
const { showLoader } = useLoader({
|
|
3212
3453
|
configReady,
|
|
3213
3454
|
showPicker,
|
|
@@ -3226,73 +3467,36 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3226
3467
|
detected: detected.includes(pid),
|
|
3227
3468
|
enabled: onboardEnabled.includes(pid)
|
|
3228
3469
|
}));
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
detectProviders().then(setDetected);
|
|
3470
|
+
useEffect9(() => {
|
|
3471
|
+
void detectProviders().then(setDetected);
|
|
3232
3472
|
}, []);
|
|
3233
3473
|
const tableKey = useMemo2(
|
|
3234
3474
|
() => `${effTableProvider}|${tableAccounts.map(acctKey).join(",")}|${tz}`,
|
|
3235
3475
|
[effTableProvider, tableAccounts, tz]
|
|
3236
3476
|
);
|
|
3237
|
-
|
|
3238
|
-
setTable(null);
|
|
3239
|
-
setCursorRows(null);
|
|
3477
|
+
useEffect9(() => {
|
|
3240
3478
|
setCursor(0);
|
|
3241
3479
|
setExpanded(-1);
|
|
3242
3480
|
setTableModel(null);
|
|
3243
|
-
setSort(
|
|
3244
|
-
setTableLoading(false);
|
|
3481
|
+
setSort(1);
|
|
3245
3482
|
}, [tableKey]);
|
|
3246
|
-
|
|
3247
|
-
if (!degraded || tab !== 1 || !effTableProvider) return;
|
|
3248
|
-
let active = true;
|
|
3249
|
-
let timer;
|
|
3250
|
-
const fetchOnce = async () => {
|
|
3251
|
-
try {
|
|
3252
|
-
if (tableIsCursor) {
|
|
3253
|
-
const s = await withTimeout(cursorModelSpend(tableAccounts[0]?.homeDir));
|
|
3254
|
-
if (active) setCursorRows(s?.models ?? []);
|
|
3255
|
-
} else {
|
|
3256
|
-
const r = await withTimeout(fetchScopeTable(tableAccounts, tz));
|
|
3257
|
-
if (active) setTable(r);
|
|
3258
|
-
}
|
|
3259
|
-
} catch {
|
|
3260
|
-
}
|
|
3261
|
-
};
|
|
3262
|
-
const run = async () => {
|
|
3263
|
-
setTableLoading(true);
|
|
3264
|
-
await fetchOnce();
|
|
3265
|
-
if (!active) return;
|
|
3266
|
-
setTableLoading(false);
|
|
3267
|
-
const loop = async () => {
|
|
3268
|
-
await fetchOnce();
|
|
3269
|
-
if (active) timer = setTimeout(loop, Math.max(interval, 1e4));
|
|
3270
|
-
};
|
|
3271
|
-
timer = setTimeout(loop, Math.max(interval, 1e4));
|
|
3272
|
-
};
|
|
3273
|
-
run();
|
|
3274
|
-
return () => {
|
|
3275
|
-
active = false;
|
|
3276
|
-
clearTimeout(timer);
|
|
3277
|
-
};
|
|
3278
|
-
}, [degraded, tab, tableKey, interval]);
|
|
3279
|
-
useEffect7(() => {
|
|
3483
|
+
useEffect9(() => {
|
|
3280
3484
|
setCursor(0);
|
|
3281
3485
|
setExpanded(-1);
|
|
3282
3486
|
}, [search, tableModel]);
|
|
3283
|
-
|
|
3487
|
+
useEffect9(() => {
|
|
3284
3488
|
setDashPage((p) => Math.min(p, dashPageCount - 1));
|
|
3285
3489
|
}, [dashPageCount]);
|
|
3286
|
-
|
|
3490
|
+
useEffect9(() => {
|
|
3287
3491
|
setSettingsCursor((c) => Math.max(-1, Math.min(c, settingsRowCount - 1)));
|
|
3288
3492
|
}, [settingsRowCount]);
|
|
3289
|
-
const resetView =
|
|
3493
|
+
const resetView = useCallback7(() => {
|
|
3290
3494
|
setCursor(0);
|
|
3291
3495
|
setExpanded(-1);
|
|
3292
3496
|
}, []);
|
|
3293
3497
|
const clampRow = (n) => Math.max(0, Math.min(rowCountRef.current - 1, n));
|
|
3294
3498
|
const mouse = useMouse();
|
|
3295
|
-
|
|
3499
|
+
useEffect9(() => {
|
|
3296
3500
|
if (!IS_TTY) return;
|
|
3297
3501
|
mouse.enable();
|
|
3298
3502
|
if (process.stdout.isTTY) {
|
|
@@ -3320,31 +3524,6 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3320
3524
|
process.stdin.off("data", onData);
|
|
3321
3525
|
};
|
|
3322
3526
|
}, []);
|
|
3323
|
-
const updateConfig = useCallback4((fn) => {
|
|
3324
|
-
setConfig((prev) => {
|
|
3325
|
-
const next = normalizeConfig(fn(prev ?? DEFAULTS));
|
|
3326
|
-
pendingLocalConfigRef.current = connected ? next : null;
|
|
3327
|
-
saveConfigSync(next);
|
|
3328
|
-
if (connected) {
|
|
3329
|
-
void daemon.setConfig(next).then((saved) => {
|
|
3330
|
-
if (pendingLocalConfigRef.current && reconcileDaemonConfig(next, saved, pendingLocalConfigRef.current).pendingLocalConfig === null) {
|
|
3331
|
-
pendingLocalConfigRef.current = null;
|
|
3332
|
-
}
|
|
3333
|
-
}).catch(() => {
|
|
3334
|
-
});
|
|
3335
|
-
}
|
|
3336
|
-
return next;
|
|
3337
|
-
});
|
|
3338
|
-
}, [connected, daemon]);
|
|
3339
|
-
const daemonConfig = daemon.config;
|
|
3340
|
-
useEffect7(() => {
|
|
3341
|
-
if (!connected || !daemonConfig) return;
|
|
3342
|
-
setConfig((prev) => {
|
|
3343
|
-
const reconciled = reconcileDaemonConfig(prev, daemonConfig, pendingLocalConfigRef.current);
|
|
3344
|
-
pendingLocalConfigRef.current = reconciled.pendingLocalConfig;
|
|
3345
|
-
return reconciled.config;
|
|
3346
|
-
});
|
|
3347
|
-
}, [connected, daemonConfig]);
|
|
3348
3527
|
function toggleOnboard(i) {
|
|
3349
3528
|
if (i < 0 || i >= pickerProviders.length) return;
|
|
3350
3529
|
const pid = pickerProviders[i];
|
|
@@ -3381,19 +3560,18 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3381
3560
|
setOnboardSel(null);
|
|
3382
3561
|
setOnboardCursor(0);
|
|
3383
3562
|
}
|
|
3384
|
-
const cycleAccount =
|
|
3563
|
+
const cycleAccount = useCallback7((dir) => {
|
|
3385
3564
|
if (slots.length <= 1) return;
|
|
3386
3565
|
const next = (activeSlotIdx + dir + slots.length) % slots.length;
|
|
3387
3566
|
updateConfig((c) => ({ ...c, activeAccountId: slots[next].id }));
|
|
3388
3567
|
resetView();
|
|
3389
3568
|
}, [slots, activeSlotIdx, updateConfig, resetView]);
|
|
3390
|
-
const cycleTableProvider =
|
|
3569
|
+
const cycleTableProvider = useCallback7((dir) => {
|
|
3391
3570
|
if (tableProvs.length <= 1) return;
|
|
3392
3571
|
const cur = effTableProvider ? tableProvs.indexOf(effTableProvider) : 0;
|
|
3393
3572
|
const nextProv = tableProvs[(cur + dir + tableProvs.length) % tableProvs.length];
|
|
3394
3573
|
setTableProvider(nextProv);
|
|
3395
|
-
|
|
3396
|
-
setSort(nextIsCursor ? 0 : 1);
|
|
3574
|
+
setSort(1);
|
|
3397
3575
|
setCursor(0);
|
|
3398
3576
|
setExpanded(-1);
|
|
3399
3577
|
setTableModel(null);
|
|
@@ -3420,30 +3598,30 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3420
3598
|
return;
|
|
3421
3599
|
}
|
|
3422
3600
|
if (webRef.current) {
|
|
3423
|
-
openUrl(webRef.current.
|
|
3601
|
+
openUrl(webRef.current.browserUrl);
|
|
3424
3602
|
return;
|
|
3425
3603
|
}
|
|
3426
3604
|
if (webStartingRef.current) return;
|
|
3427
3605
|
webStartingRef.current = true;
|
|
3428
3606
|
try {
|
|
3429
|
-
const { startWebServer } = await import("./server-
|
|
3607
|
+
const { startWebServer } = await import("./server-4CDZMLH6.js");
|
|
3430
3608
|
const ctrl = await startWebServer({ config: cfg, log: false });
|
|
3431
3609
|
webRef.current = ctrl;
|
|
3432
|
-
openUrl(ctrl.
|
|
3610
|
+
openUrl(ctrl.browserUrl);
|
|
3433
3611
|
} catch {
|
|
3434
3612
|
} finally {
|
|
3435
3613
|
webStartingRef.current = false;
|
|
3436
3614
|
}
|
|
3437
3615
|
}
|
|
3438
|
-
const onTabSelect =
|
|
3616
|
+
const onTabSelect = useCallback7((i) => {
|
|
3439
3617
|
setTab(i);
|
|
3440
3618
|
resetView();
|
|
3441
3619
|
}, [resetView]);
|
|
3442
|
-
const onStripSelect =
|
|
3620
|
+
const onStripSelect = useCallback7((i) => {
|
|
3443
3621
|
updateConfig((c) => ({ ...c, activeAccountId: slots[i].id }));
|
|
3444
3622
|
resetView();
|
|
3445
3623
|
}, [slots, updateConfig, resetView]);
|
|
3446
|
-
const onProviderSelect =
|
|
3624
|
+
const onProviderSelect = useCallback7((p) => {
|
|
3447
3625
|
setTableProvider(p);
|
|
3448
3626
|
setCursor(0);
|
|
3449
3627
|
setExpanded(-1);
|
|
@@ -3452,105 +3630,108 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3452
3630
|
setSearchCaret(0);
|
|
3453
3631
|
setSearchMode(false);
|
|
3454
3632
|
}, []);
|
|
3455
|
-
const onRowClickToken =
|
|
3633
|
+
const onRowClickToken = useCallback7((idx) => {
|
|
3456
3634
|
if (idx === cursor) setExpanded((e) => e === idx ? -1 : idx);
|
|
3457
3635
|
else setCursor(idx);
|
|
3458
3636
|
}, [cursor]);
|
|
3459
|
-
const onRowClickCursor = useCallback4((idx) => setCursor(idx), []);
|
|
3460
3637
|
const rawTokenRows = useMemo2(
|
|
3461
|
-
() => tab === 1
|
|
3462
|
-
[tab,
|
|
3638
|
+
() => tab === 1 ? table ? [table.daily, table.weekly, table.monthly][view] : [] : [],
|
|
3639
|
+
[tab, table, view]
|
|
3463
3640
|
);
|
|
3464
3641
|
const tokenModelOptions = useMemo2(() => tableModelOptions(rawTokenRows), [rawTokenRows]);
|
|
3465
|
-
|
|
3642
|
+
useEffect9(() => {
|
|
3466
3643
|
if (tableModel && !tokenModelOptions.includes(tableModel)) setTableModel(null);
|
|
3467
3644
|
}, [tableModel, tokenModelOptions]);
|
|
3468
|
-
const cycleTableModelFilter =
|
|
3645
|
+
const cycleTableModelFilter = useCallback7((dir) => {
|
|
3469
3646
|
setTableModel((cur) => cycleTableModel(cur, tokenModelOptions, dir));
|
|
3470
3647
|
resetView();
|
|
3471
3648
|
}, [tokenModelOptions, resetView]);
|
|
3472
3649
|
const activeTableModel = tableModel && tokenModelOptions.includes(tableModel) ? tableModel : null;
|
|
3473
3650
|
const tokenRows = useMemo2(
|
|
3474
|
-
() => tab === 1
|
|
3475
|
-
[tab,
|
|
3476
|
-
);
|
|
3477
|
-
const cursorTableRows = useMemo2(
|
|
3478
|
-
() => tab === 1 && tableIsCursor ? sortCursorRows(filterCursorRows(cursorRows ?? [], search), sort) : [],
|
|
3479
|
-
[tab, tableIsCursor, cursorRows, search, sort]
|
|
3651
|
+
() => tab === 1 ? sortRows(filterTokenRows(filterRowsByModel(rawTokenRows, activeTableModel), search), sort) : [],
|
|
3652
|
+
[tab, rawTokenRows, activeTableModel, search, sort]
|
|
3480
3653
|
);
|
|
3481
3654
|
useInput((input, key) => handleKey(input, key, {
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
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
|
+
}
|
|
3548
3730
|
}), { isActive: IS_TTY });
|
|
3549
|
-
if (
|
|
3550
|
-
if (
|
|
3551
|
-
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 });
|
|
3552
3733
|
if (showPicker) {
|
|
3553
|
-
return /* @__PURE__ */
|
|
3734
|
+
return /* @__PURE__ */ jsx12(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, children: /* @__PURE__ */ jsx12(
|
|
3554
3735
|
Onboarding,
|
|
3555
3736
|
{
|
|
3556
3737
|
items: onboardItems,
|
|
@@ -3563,20 +3744,20 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3563
3744
|
) });
|
|
3564
3745
|
}
|
|
3565
3746
|
if (showLoader) {
|
|
3566
|
-
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 }) });
|
|
3567
3748
|
}
|
|
3568
3749
|
if (TOO_SMALL && !showSettings) {
|
|
3569
|
-
return /* @__PURE__ */
|
|
3750
|
+
return /* @__PURE__ */ jsx12(TinyFallback, { groups, stats, rows, cols, refreshStatus });
|
|
3570
3751
|
}
|
|
3571
|
-
rowCountRef.current =
|
|
3572
|
-
return /* @__PURE__ */
|
|
3573
|
-
/* @__PURE__ */
|
|
3574
|
-
/* @__PURE__ */
|
|
3575
|
-
/* @__PURE__ */
|
|
3752
|
+
rowCountRef.current = tokenRows.length;
|
|
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: [
|
|
3576
3757
|
glyphs().dotSel,
|
|
3577
3758
|
" tokmon"
|
|
3578
3759
|
] }),
|
|
3579
|
-
/* @__PURE__ */
|
|
3760
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3580
3761
|
" ",
|
|
3581
3762
|
glyphs().middot,
|
|
3582
3763
|
" every ",
|
|
@@ -3584,31 +3765,37 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3584
3765
|
"s"
|
|
3585
3766
|
] })
|
|
3586
3767
|
] }),
|
|
3587
|
-
/* @__PURE__ */
|
|
3588
|
-
peak && /* @__PURE__ */
|
|
3589
|
-
/* @__PURE__ */
|
|
3590
|
-
/* @__PURE__ */
|
|
3768
|
+
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
3769
|
+
peak && /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
3770
|
+
/* @__PURE__ */ jsx12(PeakBadge, { peak }),
|
|
3771
|
+
/* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3591
3772
|
" ",
|
|
3592
3773
|
glyphs().middot,
|
|
3593
3774
|
" "
|
|
3594
3775
|
] })
|
|
3595
3776
|
] }),
|
|
3596
|
-
/* @__PURE__ */
|
|
3777
|
+
/* @__PURE__ */ jsx12(Text12, { dimColor: true, children: time(updated, tz) })
|
|
3597
3778
|
] })
|
|
3598
3779
|
] }),
|
|
3599
|
-
degraded && /* @__PURE__ */
|
|
3780
|
+
degraded && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3600
3781
|
glyphs().warn,
|
|
3601
3782
|
" degraded ",
|
|
3602
3783
|
glyphs().middot,
|
|
3603
3784
|
" background service unavailable, running in-process"
|
|
3604
3785
|
] }),
|
|
3605
|
-
connected && daemon.conn !== "live" && /* @__PURE__ */
|
|
3786
|
+
connected && daemon.conn !== "live" && /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3606
3787
|
glyphs().warn,
|
|
3607
3788
|
" reconnecting ",
|
|
3608
3789
|
glyphs().middot,
|
|
3609
3790
|
" showing last known data"
|
|
3610
3791
|
] }),
|
|
3611
|
-
|
|
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(
|
|
3612
3799
|
SettingsView,
|
|
3613
3800
|
{
|
|
3614
3801
|
config: cfg,
|
|
@@ -3623,20 +3810,20 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3623
3810
|
trackedAccounts: trackedAccountRows,
|
|
3624
3811
|
accountIdentities
|
|
3625
3812
|
}
|
|
3626
|
-
) : /* @__PURE__ */
|
|
3627
|
-
/* @__PURE__ */
|
|
3628
|
-
/* @__PURE__ */
|
|
3629
|
-
/* @__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: [
|
|
3630
3817
|
" Tab/",
|
|
3631
3818
|
glyphs().arrowL,
|
|
3632
3819
|
glyphs().arrowR
|
|
3633
3820
|
] })
|
|
3634
3821
|
] }),
|
|
3635
|
-
tab === 0 && /* @__PURE__ */
|
|
3636
|
-
/* @__PURE__ */
|
|
3637
|
-
slots.length > 1 && /* @__PURE__ */
|
|
3638
|
-
/* @__PURE__ */
|
|
3639
|
-
/* @__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(
|
|
3640
3827
|
AccountStrip,
|
|
3641
3828
|
{
|
|
3642
3829
|
slots,
|
|
@@ -3645,12 +3832,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3645
3832
|
}
|
|
3646
3833
|
)
|
|
3647
3834
|
] }),
|
|
3648
|
-
/* @__PURE__ */
|
|
3835
|
+
/* @__PURE__ */ jsx12(TotalsRow, { groups, stats, cols })
|
|
3649
3836
|
] }),
|
|
3650
|
-
tab === 1 && /* @__PURE__ */
|
|
3651
|
-
tableProvs.length > 0 && /* @__PURE__ */
|
|
3652
|
-
/* @__PURE__ */
|
|
3653
|
-
/* @__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(
|
|
3654
3841
|
ControlBar,
|
|
3655
3842
|
{
|
|
3656
3843
|
views: VIEWS,
|
|
@@ -3660,24 +3847,16 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3660
3847
|
search,
|
|
3661
3848
|
searchCaret,
|
|
3662
3849
|
searching: searchMode,
|
|
3663
|
-
showPeriod:
|
|
3664
|
-
showModel:
|
|
3850
|
+
showPeriod: true,
|
|
3851
|
+
showModel: true
|
|
3665
3852
|
}
|
|
3666
3853
|
),
|
|
3667
|
-
/* @__PURE__ */
|
|
3668
|
-
!effTableProvider ? /* @__PURE__ */
|
|
3854
|
+
/* @__PURE__ */ jsx12(Box11, { height: 1 }),
|
|
3855
|
+
!effTableProvider ? /* @__PURE__ */ jsxs12(Text12, { dimColor: true, children: [
|
|
3669
3856
|
"No providers enabled ",
|
|
3670
3857
|
glyphs().emDash,
|
|
3671
3858
|
" press s to pick providers."
|
|
3672
|
-
] }) : tableLoading && !table
|
|
3673
|
-
CursorSpendTable,
|
|
3674
|
-
{
|
|
3675
|
-
rows: cursorTableRows,
|
|
3676
|
-
cursor,
|
|
3677
|
-
maxRows: Math.max(1, rows - 16),
|
|
3678
|
-
onRowClick: onRowClickCursor
|
|
3679
|
-
}
|
|
3680
|
-
) : /* @__PURE__ */ jsx11(
|
|
3859
|
+
] }) : tableLoading && !table ? /* @__PURE__ */ jsx12(Spinner, { label: "Loading history" }) : /* @__PURE__ */ jsx12(
|
|
3681
3860
|
TokenTable,
|
|
3682
3861
|
{
|
|
3683
3862
|
rows: tokenRows,
|
|
@@ -3690,12 +3869,12 @@ function App({ interval: cliInterval, initialConfig, baseUrl = null, wsToken = n
|
|
|
3690
3869
|
)
|
|
3691
3870
|
] })
|
|
3692
3871
|
] }),
|
|
3693
|
-
(tab === 0 || showSettings) && /* @__PURE__ */
|
|
3872
|
+
(tab === 0 || showSettings) && /* @__PURE__ */ jsx12(Footer, { hasAccounts: slots.length > 1, paginated: tab === 0 && dashPaginated, cols })
|
|
3694
3873
|
] });
|
|
3695
3874
|
}
|
|
3696
3875
|
|
|
3697
3876
|
// src/bootstrap-ink.tsx
|
|
3698
|
-
import { jsx as
|
|
3877
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
3699
3878
|
function enterAltScreen() {
|
|
3700
3879
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
3701
3880
|
}
|
|
@@ -3724,7 +3903,7 @@ async function bootstrapInk({ interval, config, daemon, mode }) {
|
|
|
3724
3903
|
if (altScreen) leaveAltScreen();
|
|
3725
3904
|
});
|
|
3726
3905
|
const { waitUntilExit } = render(
|
|
3727
|
-
/* @__PURE__ */
|
|
3906
|
+
/* @__PURE__ */ jsx13(MouseProvider, { children: /* @__PURE__ */ jsx13(
|
|
3728
3907
|
App,
|
|
3729
3908
|
{
|
|
3730
3909
|
interval,
|