tokmon 0.13.0 → 0.14.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 +31 -13
- package/dist/cli.js +2001 -392
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.tsx
|
|
4
|
+
import { EventEmitter } from "events";
|
|
4
5
|
import { render } from "ink";
|
|
5
6
|
import { MouseProvider } from "@zenobius/ink-mouse";
|
|
6
7
|
|
|
@@ -22,8 +23,11 @@ var DEFAULTS = {
|
|
|
22
23
|
disabledProviders: [],
|
|
23
24
|
onboarded: false,
|
|
24
25
|
dashboardLayout: "grid",
|
|
25
|
-
defaultFocus: "all"
|
|
26
|
+
defaultFocus: "all",
|
|
27
|
+
ascii: "auto",
|
|
28
|
+
knownProviders: []
|
|
26
29
|
};
|
|
30
|
+
var LEGACY_KNOWN = ["claude", "codex", "cursor"];
|
|
27
31
|
var ACCENT_COLORS = ["cyan", "magenta", "green", "yellow", "blue", "red"];
|
|
28
32
|
function configDir() {
|
|
29
33
|
if (process.platform === "win32") {
|
|
@@ -43,7 +47,7 @@ function cacheDir() {
|
|
|
43
47
|
}
|
|
44
48
|
return join(envDir("XDG_CACHE_HOME") ?? join(homedir(), ".cache"), "tokmon");
|
|
45
49
|
}
|
|
46
|
-
var PROVIDER_IDS = ["claude", "codex", "cursor"];
|
|
50
|
+
var PROVIDER_IDS = ["claude", "codex", "cursor", "pi", "opencode", "copilot", "antigravity", "gemini"];
|
|
47
51
|
function clampNum(v, fallback, min) {
|
|
48
52
|
return typeof v === "number" && Number.isFinite(v) && v >= min ? v : fallback;
|
|
49
53
|
}
|
|
@@ -84,7 +88,9 @@ async function loadConfig() {
|
|
|
84
88
|
// provider picker once, so existing users can opt into Codex/Cursor.
|
|
85
89
|
onboarded: parsed.onboarded === true,
|
|
86
90
|
dashboardLayout: parsed.dashboardLayout === "single" ? "single" : "grid",
|
|
87
|
-
defaultFocus: parsed.defaultFocus === "last" ? "last" : "all"
|
|
91
|
+
defaultFocus: parsed.defaultFocus === "last" ? "last" : "all",
|
|
92
|
+
ascii: parsed.ascii === "on" ? "on" : parsed.ascii === "off" ? "off" : "auto",
|
|
93
|
+
knownProviders: Array.isArray(parsed.knownProviders) ? parsed.knownProviders.filter((p) => PROVIDER_IDS.includes(p)) : parsed.onboarded === true ? [...LEGACY_KNOWN] : []
|
|
88
94
|
};
|
|
89
95
|
} catch {
|
|
90
96
|
return { ...DEFAULTS };
|
|
@@ -505,9 +511,105 @@ function mergeTables(list) {
|
|
|
505
511
|
};
|
|
506
512
|
}
|
|
507
513
|
|
|
514
|
+
// src/glyphs.ts
|
|
515
|
+
var GLYPHS_UNICODE = {
|
|
516
|
+
spark: ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"],
|
|
517
|
+
barFull: "\u2501",
|
|
518
|
+
barEmpty: "\u2500",
|
|
519
|
+
rule: "\u2500",
|
|
520
|
+
spinner: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
|
|
521
|
+
dot: "\u25CF",
|
|
522
|
+
dotSel: "\u25C9",
|
|
523
|
+
radioOff: "\u25CB",
|
|
524
|
+
dotAll: "\u2726",
|
|
525
|
+
caretR: "\u25B8",
|
|
526
|
+
caretL: "\u25C2",
|
|
527
|
+
play: "\u25B6",
|
|
528
|
+
arrowU: "\u2191",
|
|
529
|
+
arrowD: "\u2193",
|
|
530
|
+
arrowL: "\u2190",
|
|
531
|
+
arrowR: "\u2192",
|
|
532
|
+
shift: "\u21E7",
|
|
533
|
+
vbar: "\u258C",
|
|
534
|
+
treeMid: "\u251C\u2500",
|
|
535
|
+
treeEnd: "\u2514\u2500",
|
|
536
|
+
boxMark: "\u2502",
|
|
537
|
+
check: "\u2713",
|
|
538
|
+
warn: "\u26A0",
|
|
539
|
+
ellipsis: "\u2026",
|
|
540
|
+
middot: "\xB7",
|
|
541
|
+
emDash: "\u2014",
|
|
542
|
+
eur: "\u20AC",
|
|
543
|
+
gbp: "\xA3",
|
|
544
|
+
border: "round"
|
|
545
|
+
};
|
|
546
|
+
var GLYPHS_ASCII = {
|
|
547
|
+
spark: [".", ":", "-", "=", "+", "*", "#", "@"],
|
|
548
|
+
barFull: "#",
|
|
549
|
+
barEmpty: "-",
|
|
550
|
+
rule: "-",
|
|
551
|
+
spinner: ["|", "/", "-", "\\"],
|
|
552
|
+
dot: "*",
|
|
553
|
+
dotSel: "*",
|
|
554
|
+
radioOff: "o",
|
|
555
|
+
dotAll: "+",
|
|
556
|
+
caretR: ">",
|
|
557
|
+
caretL: "<",
|
|
558
|
+
play: ">",
|
|
559
|
+
arrowU: "^",
|
|
560
|
+
arrowD: "v",
|
|
561
|
+
arrowL: "<",
|
|
562
|
+
arrowR: ">",
|
|
563
|
+
shift: "^",
|
|
564
|
+
vbar: "|",
|
|
565
|
+
treeMid: "+-",
|
|
566
|
+
treeEnd: "`-",
|
|
567
|
+
boxMark: "|",
|
|
568
|
+
check: "x",
|
|
569
|
+
warn: "!",
|
|
570
|
+
ellipsis: "...",
|
|
571
|
+
middot: "-",
|
|
572
|
+
emDash: "-",
|
|
573
|
+
eur: "EUR",
|
|
574
|
+
gbp: "GBP",
|
|
575
|
+
border: "classic"
|
|
576
|
+
};
|
|
577
|
+
function detectUnicode(env, isTTY, platform) {
|
|
578
|
+
if (!isTTY) return false;
|
|
579
|
+
if (env.TERM === "dumb") return false;
|
|
580
|
+
if (platform === "win32") {
|
|
581
|
+
return Boolean(env.WT_SESSION || env.ConEmuANSI === "ON" || env.TERM_PROGRAM === "vscode" || /xterm/i.test(env.TERM ?? ""));
|
|
582
|
+
}
|
|
583
|
+
const loc = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
584
|
+
if (loc && /\.(iso|latin|ascii|cp\d|koi|gbk|big5)/i.test(loc)) return false;
|
|
585
|
+
if (/^(C|POSIX)$/i.test(loc)) return false;
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
function resolveGlyphs(opts) {
|
|
589
|
+
let ascii;
|
|
590
|
+
if (opts.flag === "on") ascii = true;
|
|
591
|
+
else if (opts.flag === "off") ascii = false;
|
|
592
|
+
else {
|
|
593
|
+
const e = (opts.env.TOKMON_ASCII ?? "").toLowerCase();
|
|
594
|
+
if (/^(1|true|on|yes)$/.test(e)) ascii = true;
|
|
595
|
+
else if (/^(0|false|off|no)$/.test(e)) ascii = false;
|
|
596
|
+
else if (opts.config === "on") ascii = true;
|
|
597
|
+
else if (opts.config === "off") ascii = false;
|
|
598
|
+
else ascii = !detectUnicode(opts.env, opts.isTTY, opts.platform);
|
|
599
|
+
}
|
|
600
|
+
return ascii ? GLYPHS_ASCII : GLYPHS_UNICODE;
|
|
601
|
+
}
|
|
602
|
+
var active = GLYPHS_UNICODE;
|
|
603
|
+
function setGlyphs(set) {
|
|
604
|
+
active = set;
|
|
605
|
+
}
|
|
606
|
+
function glyphs() {
|
|
607
|
+
return active;
|
|
608
|
+
}
|
|
609
|
+
|
|
508
610
|
// src/app.tsx
|
|
509
|
-
import { useState as
|
|
510
|
-
import { Box as
|
|
611
|
+
import { useState as useState3, useEffect as useEffect3, useCallback, useRef as useRef2, useMemo } from "react";
|
|
612
|
+
import { Box as Box7, Text as Text7, useInput, useStdout, useApp } from "ink";
|
|
511
613
|
import { useMouse } from "@zenobius/ink-mouse";
|
|
512
614
|
|
|
513
615
|
// src/http.ts
|
|
@@ -746,12 +848,26 @@ function resetIn(iso) {
|
|
|
746
848
|
|
|
747
849
|
// src/providers/claude/billing.ts
|
|
748
850
|
var execFile = promisify(execFileCb);
|
|
851
|
+
function parseAuth(raw) {
|
|
852
|
+
try {
|
|
853
|
+
const creds = JSON.parse(raw);
|
|
854
|
+
const o = creds?.claudeAiOauth ?? creds;
|
|
855
|
+
const token = o?.accessToken;
|
|
856
|
+
if (typeof token !== "string" || !token) return null;
|
|
857
|
+
return {
|
|
858
|
+
token,
|
|
859
|
+
subscriptionType: typeof o.subscriptionType === "string" ? o.subscriptionType : void 0,
|
|
860
|
+
rateLimitTier: typeof o.rateLimitTier === "string" ? o.rateLimitTier : void 0
|
|
861
|
+
};
|
|
862
|
+
} catch {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
749
866
|
async function readCredentialsFile(homeDir) {
|
|
750
867
|
for (const dir of claudeConfigDirs(homeDir)) {
|
|
751
868
|
try {
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
if (token) return token;
|
|
869
|
+
const auth = parseAuth(await readFile3(join4(dir, ".credentials.json"), "utf-8"));
|
|
870
|
+
if (auth) return auth;
|
|
755
871
|
} catch {
|
|
756
872
|
}
|
|
757
873
|
}
|
|
@@ -765,38 +881,45 @@ async function readMacKeychain() {
|
|
|
765
881
|
"Claude Code-credentials",
|
|
766
882
|
"-w"
|
|
767
883
|
], { timeout: 5e3 });
|
|
768
|
-
|
|
769
|
-
return creds?.claudeAiOauth?.accessToken ?? creds?.accessToken ?? null;
|
|
884
|
+
return parseAuth(stdout.trim());
|
|
770
885
|
} catch {
|
|
771
886
|
return null;
|
|
772
887
|
}
|
|
773
888
|
}
|
|
774
|
-
async function
|
|
889
|
+
async function getAuth(homeDir) {
|
|
775
890
|
const isDefault = !homeDir || homeDir === homedir3();
|
|
776
891
|
if (isDefault && process.platform === "darwin") {
|
|
777
|
-
const
|
|
778
|
-
if (
|
|
892
|
+
const auth = await readMacKeychain();
|
|
893
|
+
if (auth) return auth;
|
|
779
894
|
}
|
|
780
895
|
return readCredentialsFile(homeDir);
|
|
781
896
|
}
|
|
897
|
+
function planLabel(auth) {
|
|
898
|
+
const sub = auth.subscriptionType;
|
|
899
|
+
if (!sub) return null;
|
|
900
|
+
const base = sub.charAt(0).toUpperCase() + sub.slice(1);
|
|
901
|
+
const tier = (auth.rateLimitTier ?? "").match(/(\d+)x/);
|
|
902
|
+
return tier ? `${base} ${tier[1]}x` : base;
|
|
903
|
+
}
|
|
782
904
|
var pct = (used, resets, primary) => ({ label: "", used, limit: 100, format: { kind: "percent" }, resetsAt: resets ?? null, primary });
|
|
783
905
|
async function claudeBilling(account) {
|
|
784
|
-
const
|
|
785
|
-
if (!
|
|
906
|
+
const auth = await getAuth(account.homeDir);
|
|
907
|
+
if (!auth) return { plan: null, metrics: [], error: "No OAuth token \u2014 run claude and log in" };
|
|
908
|
+
const plan = planLabel(auth);
|
|
786
909
|
try {
|
|
787
910
|
const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
788
911
|
headers: {
|
|
789
|
-
"Authorization": `Bearer ${token}`,
|
|
912
|
+
"Authorization": `Bearer ${auth.token}`,
|
|
790
913
|
"anthropic-beta": "oauth-2025-04-20",
|
|
791
914
|
"User-Agent": "tokmon"
|
|
792
915
|
},
|
|
793
916
|
signal: AbortSignal.timeout(1e4)
|
|
794
917
|
});
|
|
795
|
-
if (res.status === 429) return { plan
|
|
796
|
-
if (res.status === 401) return { plan
|
|
797
|
-
if (!res.ok) return { plan
|
|
918
|
+
if (res.status === 429) return { plan, metrics: [], error: "Rate limited \u2014 retrying next poll" };
|
|
919
|
+
if (res.status === 401) return { plan, metrics: [], error: "Token expired \u2014 restart Claude Code" };
|
|
920
|
+
if (!res.ok) return { plan, metrics: [], error: `API ${res.status}` };
|
|
798
921
|
const data = await readJson(res);
|
|
799
|
-
if (!data) return { plan
|
|
922
|
+
if (!data) return { plan, metrics: [], error: "Unexpected API response" };
|
|
800
923
|
const metrics = [];
|
|
801
924
|
if (data.five_hour) {
|
|
802
925
|
metrics.push({ ...pct(data.five_hour.utilization, resetIn(data.five_hour.resets_at), true), label: "5h" });
|
|
@@ -815,9 +938,9 @@ async function claudeBilling(account) {
|
|
|
815
938
|
format: { kind: "dollars", currency: data.extra_usage.currency ?? "USD" }
|
|
816
939
|
});
|
|
817
940
|
}
|
|
818
|
-
return { plan
|
|
941
|
+
return { plan, metrics, error: null };
|
|
819
942
|
} catch {
|
|
820
|
-
return { plan
|
|
943
|
+
return { plan, metrics: [], error: "Network error" };
|
|
821
944
|
}
|
|
822
945
|
}
|
|
823
946
|
|
|
@@ -1017,7 +1140,7 @@ async function readKeychainAuth() {
|
|
|
1017
1140
|
return null;
|
|
1018
1141
|
}
|
|
1019
1142
|
}
|
|
1020
|
-
async function
|
|
1143
|
+
async function getAuth2(homeDir) {
|
|
1021
1144
|
for (const home of codexHomes(homeDir)) {
|
|
1022
1145
|
const auth = await readAuthFile(home);
|
|
1023
1146
|
if (auth) return auth;
|
|
@@ -1025,7 +1148,7 @@ async function getAuth(homeDir) {
|
|
|
1025
1148
|
if (process.platform === "darwin") return readKeychainAuth();
|
|
1026
1149
|
return null;
|
|
1027
1150
|
}
|
|
1028
|
-
function
|
|
1151
|
+
function planLabel2(planType) {
|
|
1029
1152
|
if (typeof planType !== "string" || !planType.trim()) return null;
|
|
1030
1153
|
const p = planType.trim().toLowerCase();
|
|
1031
1154
|
if (p === "prolite") return "Pro 5x";
|
|
@@ -1077,7 +1200,7 @@ async function liveBilling(auth) {
|
|
|
1077
1200
|
metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
|
|
1078
1201
|
}
|
|
1079
1202
|
if (metrics.length === 0) return null;
|
|
1080
|
-
return { plan:
|
|
1203
|
+
return { plan: planLabel2(data.plan_type), metrics, error: null };
|
|
1081
1204
|
} catch {
|
|
1082
1205
|
return null;
|
|
1083
1206
|
}
|
|
@@ -1134,10 +1257,10 @@ async function snapshotBilling(homeDir) {
|
|
|
1134
1257
|
metrics.push({ label: "Credits", used: balance * CREDIT_USD_RATE, limit: null, format: { kind: "dollars" } });
|
|
1135
1258
|
}
|
|
1136
1259
|
if (metrics.length === 0) return null;
|
|
1137
|
-
return { plan:
|
|
1260
|
+
return { plan: planLabel2(last.plan_type), metrics, error: null };
|
|
1138
1261
|
}
|
|
1139
1262
|
async function codexBilling(account) {
|
|
1140
|
-
const auth = await
|
|
1263
|
+
const auth = await getAuth2(account.homeDir);
|
|
1141
1264
|
if (auth) {
|
|
1142
1265
|
const live = await liveBilling(auth);
|
|
1143
1266
|
if (live) return live;
|
|
@@ -1177,29 +1300,75 @@ import { homedir as homedir5 } from "os";
|
|
|
1177
1300
|
import { execFile as execFileCb3 } from "child_process";
|
|
1178
1301
|
import { promisify as promisify3 } from "util";
|
|
1179
1302
|
var execFile3 = promisify3(execFileCb3);
|
|
1180
|
-
|
|
1303
|
+
var nativeDb;
|
|
1304
|
+
async function getNativeDb() {
|
|
1305
|
+
if (nativeDb !== void 0) return nativeDb;
|
|
1306
|
+
try {
|
|
1307
|
+
nativeDb = (await import("sqlite")).DatabaseSync;
|
|
1308
|
+
} catch {
|
|
1309
|
+
nativeDb = null;
|
|
1310
|
+
}
|
|
1311
|
+
return nativeDb;
|
|
1312
|
+
}
|
|
1313
|
+
function classify(msg) {
|
|
1314
|
+
if (/unable to open|no such file|cannot open|ENOENT/i.test(msg)) return "missing";
|
|
1315
|
+
if (/database is (locked|busy)|readonly/i.test(msg)) return "locked";
|
|
1316
|
+
if (/no such (function|table|column)|unknown option/i.test(msg)) return "old";
|
|
1317
|
+
return "error";
|
|
1318
|
+
}
|
|
1319
|
+
async function runSqlite(db, sql, params = []) {
|
|
1320
|
+
const DB = await getNativeDb();
|
|
1321
|
+
if (DB) {
|
|
1322
|
+
let handle;
|
|
1323
|
+
try {
|
|
1324
|
+
handle = new DB(db, { readOnly: true, timeout: 1500 });
|
|
1325
|
+
const rows = handle.prepare(sql).all(...params);
|
|
1326
|
+
return { status: "ok", rows };
|
|
1327
|
+
} catch {
|
|
1328
|
+
} finally {
|
|
1329
|
+
try {
|
|
1330
|
+
handle?.close();
|
|
1331
|
+
} catch {
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
return runSqliteCli(db, sql, params);
|
|
1336
|
+
}
|
|
1337
|
+
function inlineParams(sql, params) {
|
|
1338
|
+
let i = 0;
|
|
1339
|
+
return sql.replace(/\?/g, () => {
|
|
1340
|
+
const p = params[i++];
|
|
1341
|
+
return typeof p === "number" ? String(p) : `'${String(p).replace(/'/g, "''")}'`;
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
async function runSqliteCli(db, sql, params) {
|
|
1181
1345
|
try {
|
|
1182
1346
|
const { stdout } = await execFile3(
|
|
1183
1347
|
"sqlite3",
|
|
1184
|
-
|
|
1348
|
+
// -json yields row objects; `.timeout` sets the busy handler silently
|
|
1349
|
+
// (a `-cmd 'PRAGMA busy_timeout=…'` would print its result row first).
|
|
1350
|
+
["-readonly", "-json", "-cmd", ".timeout 1500", db, inlineParams(sql, params)],
|
|
1185
1351
|
{ timeout: 1e4, maxBuffer: 8 << 20 }
|
|
1186
1352
|
);
|
|
1187
|
-
|
|
1353
|
+
const text = stdout.trim();
|
|
1354
|
+
if (!text) return { status: "ok", rows: [] };
|
|
1355
|
+
try {
|
|
1356
|
+
return { status: "ok", rows: JSON.parse(text) };
|
|
1357
|
+
} catch {
|
|
1358
|
+
return { status: "error", rows: [] };
|
|
1359
|
+
}
|
|
1188
1360
|
} catch (e) {
|
|
1189
1361
|
const err = e;
|
|
1190
|
-
if (err?.code === "ENOENT") return { status: "missing",
|
|
1191
|
-
|
|
1192
|
-
if (/database is (locked|busy)/i.test(msg)) return { status: "locked", stdout: "" };
|
|
1193
|
-
if (/no such function|unknown option|no such table/i.test(msg)) return { status: "old", stdout: "" };
|
|
1194
|
-
return { status: "error", stdout: "" };
|
|
1362
|
+
if (err?.code === "ENOENT") return { status: "missing", rows: [] };
|
|
1363
|
+
return { status: classify(String(err?.stderr ?? err?.message ?? "")), rows: [] };
|
|
1195
1364
|
}
|
|
1196
1365
|
}
|
|
1197
1366
|
function sqliteStatusMessage(status) {
|
|
1198
1367
|
switch (status) {
|
|
1199
1368
|
case "missing":
|
|
1200
|
-
return "
|
|
1369
|
+
return "Cursor data not found \u2014 open Cursor";
|
|
1201
1370
|
case "old":
|
|
1202
|
-
return "
|
|
1371
|
+
return "Cursor DB unreadable";
|
|
1203
1372
|
case "locked":
|
|
1204
1373
|
return "Cursor DB busy \u2014 retrying next poll";
|
|
1205
1374
|
default:
|
|
@@ -1222,17 +1391,14 @@ async function cursorActivity(homeDir) {
|
|
|
1222
1391
|
const now = Date.now();
|
|
1223
1392
|
const res = await runSqlite(
|
|
1224
1393
|
db,
|
|
1225
|
-
`SELECT date(createdAt/1000,'unixepoch','localtime') d, count(*) c FROM ai_code_hashes WHERE source!='human' AND createdAt >= ${Math.floor(now - 30 * DAY_MS2)} GROUP BY d;`
|
|
1394
|
+
`SELECT date(createdAt/1000,'unixepoch','localtime') AS d, count(*) AS c FROM ai_code_hashes WHERE source!='human' AND createdAt >= ${Math.floor(now - 30 * DAY_MS2)} GROUP BY d;`
|
|
1226
1395
|
);
|
|
1227
1396
|
if (res.status !== "ok") return null;
|
|
1228
|
-
const daily = res.stdout.trim();
|
|
1229
1397
|
const byDay = /* @__PURE__ */ new Map();
|
|
1230
1398
|
let month = 0;
|
|
1231
|
-
for (const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
const n = Number(c) || 0;
|
|
1235
|
-
byDay.set(d, n);
|
|
1399
|
+
for (const row of res.rows) {
|
|
1400
|
+
const n = Number(row.c) || 0;
|
|
1401
|
+
byDay.set(String(row.d), n);
|
|
1236
1402
|
month += n;
|
|
1237
1403
|
}
|
|
1238
1404
|
const series = [];
|
|
@@ -1247,17 +1413,15 @@ async function cursorActivity(homeDir) {
|
|
|
1247
1413
|
// src/providers/cursor/composer.ts
|
|
1248
1414
|
async function cursorModelSpend(homeDir) {
|
|
1249
1415
|
const db = cursorStateDb(homeDir);
|
|
1250
|
-
const sql = "SELECT mk.key, sum(json_extract(mk.value,'$.costInCents')), sum(json_extract(mk.value,'$.amount')) FROM cursorDiskKV c, json_each(c.value,'$.usageData') mk WHERE c.key LIKE 'composerData:%' AND json_valid(c.value) AND json_type(c.value,'$.usageData')='object' GROUP BY mk.key ORDER BY
|
|
1251
|
-
const res = await runSqlite(db, sql
|
|
1416
|
+
const sql = "SELECT mk.key AS name, sum(json_extract(mk.value,'$.costInCents')) AS cents, sum(json_extract(mk.value,'$.amount')) AS amt FROM cursorDiskKV c, json_each(c.value,'$.usageData') mk WHERE c.key LIKE 'composerData:%' AND json_valid(c.value) AND json_type(c.value,'$.usageData')='object' GROUP BY mk.key ORDER BY cents DESC;";
|
|
1417
|
+
const res = await runSqlite(db, sql);
|
|
1252
1418
|
if (res.status !== "ok") return null;
|
|
1253
1419
|
const models = [];
|
|
1254
1420
|
let total = 0;
|
|
1255
|
-
for (const
|
|
1256
|
-
|
|
1257
|
-
const [name, cents, amt] = line.split(" ");
|
|
1258
|
-
const usd = (Number(cents) || 0) / 100;
|
|
1421
|
+
for (const row of res.rows) {
|
|
1422
|
+
const usd = (Number(row.cents) || 0) / 100;
|
|
1259
1423
|
if (usd <= 0) continue;
|
|
1260
|
-
models.push({ name, usd, requests: Number(amt) || 0 });
|
|
1424
|
+
models.push({ name: String(row.name ?? ""), usd, requests: Number(row.amt) || 0 });
|
|
1261
1425
|
total += usd;
|
|
1262
1426
|
}
|
|
1263
1427
|
if (total <= 0) return null;
|
|
@@ -1290,9 +1454,9 @@ async function detectCursor(homeDir) {
|
|
|
1290
1454
|
}
|
|
1291
1455
|
}
|
|
1292
1456
|
async function readState(db, key) {
|
|
1293
|
-
const
|
|
1294
|
-
const
|
|
1295
|
-
return { value:
|
|
1457
|
+
const r = await runSqlite(db, "SELECT value FROM ItemTable WHERE key=? LIMIT 1;", [key]);
|
|
1458
|
+
const raw = r.status === "ok" ? r.rows[0]?.value : void 0;
|
|
1459
|
+
return { value: typeof raw === "string" && raw.trim() ? raw.trim() : null, status: r.status };
|
|
1296
1460
|
}
|
|
1297
1461
|
async function connectPost(url, token) {
|
|
1298
1462
|
try {
|
|
@@ -1414,28 +1578,846 @@ var cursorProvider = {
|
|
|
1414
1578
|
fetchBilling: (account) => cursorBilling(account)
|
|
1415
1579
|
};
|
|
1416
1580
|
|
|
1581
|
+
// src/providers/pi/usage.ts
|
|
1582
|
+
import { readdir as readdir4, stat as fsStat4, access as access4 } from "fs/promises";
|
|
1583
|
+
import { createReadStream as createReadStream4 } from "fs";
|
|
1584
|
+
import { createInterface as createInterface4 } from "readline";
|
|
1585
|
+
import { join as join9 } from "path";
|
|
1586
|
+
import { homedir as homedir7 } from "os";
|
|
1587
|
+
function piSessionsDir(homeDir) {
|
|
1588
|
+
return join9(homeDir ?? homedir7(), ".pi", "agent", "sessions");
|
|
1589
|
+
}
|
|
1590
|
+
async function detectPi(homeDir) {
|
|
1591
|
+
try {
|
|
1592
|
+
await access4(piSessionsDir(homeDir));
|
|
1593
|
+
return true;
|
|
1594
|
+
} catch {
|
|
1595
|
+
return false;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
function pos(v) {
|
|
1599
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
1600
|
+
}
|
|
1601
|
+
async function parseFile3(path) {
|
|
1602
|
+
const entries = [];
|
|
1603
|
+
const rl = createInterface4({ input: createReadStream4(path), crlfDelay: Infinity });
|
|
1604
|
+
for await (const rawLine of rl) {
|
|
1605
|
+
if (!rawLine.includes('"usage"')) continue;
|
|
1606
|
+
try {
|
|
1607
|
+
const line = rawLine.charCodeAt(0) === 65279 ? rawLine.slice(1) : rawLine;
|
|
1608
|
+
const obj = JSON.parse(line);
|
|
1609
|
+
if (obj?.type !== "message") continue;
|
|
1610
|
+
const msg = obj.message;
|
|
1611
|
+
if (msg?.role !== "assistant" || !msg?.usage) continue;
|
|
1612
|
+
const u = msg.usage;
|
|
1613
|
+
const ts = new Date(obj.timestamp ?? msg.timestamp ?? 0).getTime();
|
|
1614
|
+
if (!Number.isFinite(ts)) continue;
|
|
1615
|
+
const input = safeNum(u.input);
|
|
1616
|
+
const output = safeNum(u.output);
|
|
1617
|
+
const cacheRead = safeNum(u.cacheRead);
|
|
1618
|
+
const cacheCreate = safeNum(u.cacheWrite);
|
|
1619
|
+
if (input + output + cacheRead + cacheCreate === 0) continue;
|
|
1620
|
+
const c = u.cost ?? {};
|
|
1621
|
+
const costInput = pos(c.input);
|
|
1622
|
+
const cacheSavings = input > 0 && cacheRead > 0 ? Math.max(0, cacheRead * (costInput / input) - pos(c.cacheRead)) : 0;
|
|
1623
|
+
entries.push({
|
|
1624
|
+
ts,
|
|
1625
|
+
model: typeof msg.model === "string" && msg.model ? msg.model : "unknown",
|
|
1626
|
+
cost: pos(c.total),
|
|
1627
|
+
input,
|
|
1628
|
+
output,
|
|
1629
|
+
cacheCreate,
|
|
1630
|
+
cacheRead,
|
|
1631
|
+
cacheSavings
|
|
1632
|
+
});
|
|
1633
|
+
} catch {
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
return entries;
|
|
1637
|
+
}
|
|
1638
|
+
async function loadEntries3(since, homeDir) {
|
|
1639
|
+
const dir = piSessionsDir(homeDir);
|
|
1640
|
+
const files = [];
|
|
1641
|
+
const seenIno = /* @__PURE__ */ new Set();
|
|
1642
|
+
let listing;
|
|
1643
|
+
try {
|
|
1644
|
+
listing = await readdir4(dir, { recursive: true });
|
|
1645
|
+
} catch {
|
|
1646
|
+
return [];
|
|
1647
|
+
}
|
|
1648
|
+
for (const f of listing) {
|
|
1649
|
+
if (!f.endsWith(".jsonl")) continue;
|
|
1650
|
+
const path = join9(dir, f);
|
|
1651
|
+
try {
|
|
1652
|
+
const s = await fsStat4(path);
|
|
1653
|
+
if (s.mtimeMs < since) continue;
|
|
1654
|
+
if (s.ino && process.platform !== "win32") {
|
|
1655
|
+
const idn = `${s.dev}:${s.ino}`;
|
|
1656
|
+
if (seenIno.has(idn)) continue;
|
|
1657
|
+
seenIno.add(idn);
|
|
1658
|
+
}
|
|
1659
|
+
files.push({ path, mtimeMs: s.mtimeMs, size: s.size });
|
|
1660
|
+
} catch {
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
return loadCachedEntries(files, parseFile3, since);
|
|
1664
|
+
}
|
|
1665
|
+
async function piDashboard(tz, homeDir) {
|
|
1666
|
+
const now = Date.now();
|
|
1667
|
+
const since = Math.min(startOfMonth(now, tz), startOfWeek(now, tz), now - SPARK_DAYS * 864e5);
|
|
1668
|
+
return summarize(await loadEntries3(since, homeDir), tz);
|
|
1669
|
+
}
|
|
1670
|
+
async function piTable(tz, homeDir) {
|
|
1671
|
+
return tabulate(await loadEntries3(monthsAgoStart(Date.now(), 6, tz), homeDir), tz);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
// src/providers/pi/index.ts
|
|
1675
|
+
var piProvider = {
|
|
1676
|
+
id: "pi",
|
|
1677
|
+
name: "Pi",
|
|
1678
|
+
color: "blue",
|
|
1679
|
+
hasUsage: true,
|
|
1680
|
+
hasBilling: false,
|
|
1681
|
+
detect: (homeDir) => detectPi(homeDir),
|
|
1682
|
+
fetchSummary: (account, tz) => piDashboard(tz, account.homeDir),
|
|
1683
|
+
fetchTable: (account, tz) => piTable(tz, account.homeDir)
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
// src/providers/opencode/usage.ts
|
|
1687
|
+
import { access as access5 } from "fs/promises";
|
|
1688
|
+
import { join as join10 } from "path";
|
|
1689
|
+
import { homedir as homedir8 } from "os";
|
|
1690
|
+
function opencodeDbPaths(homeDir) {
|
|
1691
|
+
const base = homeDir ?? homedir8();
|
|
1692
|
+
const paths = [];
|
|
1693
|
+
if (!homeDir && process.env.XDG_DATA_HOME) paths.push(join10(process.env.XDG_DATA_HOME, "opencode", "opencode.db"));
|
|
1694
|
+
paths.push(join10(base, ".local", "share", "opencode", "opencode.db"));
|
|
1695
|
+
if (process.platform === "darwin") paths.push(join10(base, "Library", "Application Support", "opencode", "opencode.db"));
|
|
1696
|
+
if (process.platform === "win32") {
|
|
1697
|
+
const lad = homeDir ? join10(homeDir, "AppData", "Local") : process.env.LOCALAPPDATA;
|
|
1698
|
+
if (lad) paths.push(join10(lad, "opencode", "opencode.db"));
|
|
1699
|
+
}
|
|
1700
|
+
return [...new Set(paths)];
|
|
1701
|
+
}
|
|
1702
|
+
async function findDb(homeDir) {
|
|
1703
|
+
for (const p of opencodeDbPaths(homeDir)) {
|
|
1704
|
+
try {
|
|
1705
|
+
await access5(p);
|
|
1706
|
+
return p;
|
|
1707
|
+
} catch {
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
async function detectOpencode(homeDir) {
|
|
1713
|
+
return await findDb(homeDir) !== null;
|
|
1714
|
+
}
|
|
1715
|
+
var pos2 = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
1716
|
+
async function loadEntries4(since, homeDir) {
|
|
1717
|
+
const db = await findDb(homeDir);
|
|
1718
|
+
if (!db) return [];
|
|
1719
|
+
const sql = "SELECT time_created AS ts, json_extract(data,'$.modelID') AS model, json_extract(data,'$.cost') AS cost, json_extract(data,'$.tokens.input') AS input, json_extract(data,'$.tokens.output') AS output, json_extract(data,'$.tokens.reasoning') AS reasoning, json_extract(data,'$.tokens.cache.read') AS cacheRead, json_extract(data,'$.tokens.cache.write') AS cacheWrite FROM message WHERE json_valid(data) AND json_extract(data,'$.role')='assistant' AND json_type(data,'$.tokens')='object' AND time_created >= ?;";
|
|
1720
|
+
const res = await runSqlite(db, sql, [Math.floor(since)]);
|
|
1721
|
+
if (res.status !== "ok") return [];
|
|
1722
|
+
const entries = [];
|
|
1723
|
+
for (const row of res.rows) {
|
|
1724
|
+
const ts = pos2(row.ts);
|
|
1725
|
+
if (!ts) continue;
|
|
1726
|
+
const input = pos2(row.input);
|
|
1727
|
+
const output = pos2(row.output);
|
|
1728
|
+
const cacheRead = pos2(row.cacheRead);
|
|
1729
|
+
const cacheCreate = pos2(row.cacheWrite);
|
|
1730
|
+
if (input + output + cacheRead + cacheCreate === 0) continue;
|
|
1731
|
+
entries.push({
|
|
1732
|
+
ts,
|
|
1733
|
+
model: typeof row.model === "string" && row.model ? row.model : "unknown",
|
|
1734
|
+
cost: pos2(row.cost),
|
|
1735
|
+
input,
|
|
1736
|
+
output,
|
|
1737
|
+
cacheCreate,
|
|
1738
|
+
cacheRead,
|
|
1739
|
+
cacheSavings: 0
|
|
1740
|
+
// opencode stores only a total cost, no per-component split to derive savings
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
return entries;
|
|
1744
|
+
}
|
|
1745
|
+
async function opencodeDashboard(tz, homeDir) {
|
|
1746
|
+
const now = Date.now();
|
|
1747
|
+
const since = Math.min(startOfMonth(now, tz), startOfWeek(now, tz), now - SPARK_DAYS * 864e5);
|
|
1748
|
+
return summarize(await loadEntries4(since, homeDir), tz);
|
|
1749
|
+
}
|
|
1750
|
+
async function opencodeTable(tz, homeDir) {
|
|
1751
|
+
return tabulate(await loadEntries4(monthsAgoStart(Date.now(), 6, tz), homeDir), tz);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// src/providers/opencode/index.ts
|
|
1755
|
+
var opencodeProvider = {
|
|
1756
|
+
id: "opencode",
|
|
1757
|
+
name: "opencode",
|
|
1758
|
+
color: "yellow",
|
|
1759
|
+
hasUsage: true,
|
|
1760
|
+
hasBilling: false,
|
|
1761
|
+
detect: (homeDir) => detectOpencode(homeDir),
|
|
1762
|
+
fetchSummary: (account, tz) => opencodeDashboard(tz, account.homeDir),
|
|
1763
|
+
fetchTable: (account, tz) => opencodeTable(tz, account.homeDir)
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
// src/providers/copilot/billing.ts
|
|
1767
|
+
import { execFile as execFileCb4 } from "child_process";
|
|
1768
|
+
import { access as access6, readFile as readFile5, readdir as readdir5 } from "fs/promises";
|
|
1769
|
+
import { join as join11 } from "path";
|
|
1770
|
+
import { homedir as homedir9 } from "os";
|
|
1771
|
+
import { promisify as promisify4 } from "util";
|
|
1772
|
+
var execFile4 = promisify4(execFileCb4);
|
|
1773
|
+
var USAGE_URL3 = "https://api.github.com/copilot_internal/user";
|
|
1774
|
+
var GH_KEYCHAIN_SERVICE = "gh:github.com";
|
|
1775
|
+
var GO_KEYRING_PREFIX = "go-keyring-base64:";
|
|
1776
|
+
function ghConfigDir(homeDir) {
|
|
1777
|
+
if (!homeDir) {
|
|
1778
|
+
const explicit = process.env.GH_CONFIG_DIR;
|
|
1779
|
+
if (explicit && explicit.trim()) return explicit.trim();
|
|
1780
|
+
if (process.platform === "win32") {
|
|
1781
|
+
return join11(envDir("APPDATA") ?? join11(homedir9(), "AppData", "Roaming"), "GitHub CLI");
|
|
1782
|
+
}
|
|
1783
|
+
const xdg = envDir("XDG_CONFIG_HOME");
|
|
1784
|
+
return xdg ? join11(xdg, "gh") : join11(homedir9(), ".config", "gh");
|
|
1785
|
+
}
|
|
1786
|
+
return process.platform === "win32" ? join11(homeDir, "AppData", "Roaming", "GitHub CLI") : join11(homeDir, ".config", "gh");
|
|
1787
|
+
}
|
|
1788
|
+
function ghHostsPath(homeDir) {
|
|
1789
|
+
return join11(ghConfigDir(homeDir), "hosts.yml");
|
|
1790
|
+
}
|
|
1791
|
+
async function detectCopilot(homeDir) {
|
|
1792
|
+
try {
|
|
1793
|
+
await access6(ghHostsPath(homeDir));
|
|
1794
|
+
return true;
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
try {
|
|
1798
|
+
await execFile4("gh", ["--version"], { timeout: 3e3 });
|
|
1799
|
+
return true;
|
|
1800
|
+
} catch {
|
|
1801
|
+
return false;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
function unquoteYamlValue(value) {
|
|
1805
|
+
const trimmed = value.trim();
|
|
1806
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
1807
|
+
return trimmed.slice(1, -1);
|
|
1808
|
+
}
|
|
1809
|
+
return trimmed;
|
|
1810
|
+
}
|
|
1811
|
+
function tokenFromHostsYaml(raw) {
|
|
1812
|
+
const lines = raw.split(/\r?\n/);
|
|
1813
|
+
let inGithub = false;
|
|
1814
|
+
let githubIndent = -1;
|
|
1815
|
+
for (const line of lines) {
|
|
1816
|
+
const match = line.match(/^(\s*)([^:#][^:]*):\s*(.*)$/);
|
|
1817
|
+
if (!match) continue;
|
|
1818
|
+
const indent = match[1].length;
|
|
1819
|
+
const key = match[2].trim();
|
|
1820
|
+
const value = match[3].trim();
|
|
1821
|
+
if (indent === 0) {
|
|
1822
|
+
inGithub = key === "github.com";
|
|
1823
|
+
githubIndent = inGithub ? indent : -1;
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
if (inGithub && indent > githubIndent && key === "oauth_token" && value) {
|
|
1827
|
+
return unquoteYamlValue(value);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return null;
|
|
1831
|
+
}
|
|
1832
|
+
async function loadTokenFromHosts(homeDir) {
|
|
1833
|
+
try {
|
|
1834
|
+
const token = tokenFromHostsYaml(await readFile5(ghHostsPath(homeDir), "utf-8"));
|
|
1835
|
+
return token ? { token, source: "gh-hosts" } : null;
|
|
1836
|
+
} catch {
|
|
1837
|
+
return null;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
async function readMacKeychainService(service) {
|
|
1841
|
+
if (process.platform !== "darwin") return null;
|
|
1842
|
+
try {
|
|
1843
|
+
const { stdout } = await execFile4("security", [
|
|
1844
|
+
"find-generic-password",
|
|
1845
|
+
"-s",
|
|
1846
|
+
service,
|
|
1847
|
+
"-w"
|
|
1848
|
+
], { timeout: 5e3 });
|
|
1849
|
+
const raw = stdout.trim();
|
|
1850
|
+
if (!raw) return null;
|
|
1851
|
+
if (raw.startsWith(GO_KEYRING_PREFIX)) {
|
|
1852
|
+
return Buffer.from(raw.slice(GO_KEYRING_PREFIX.length), "base64").toString("utf-8");
|
|
1853
|
+
}
|
|
1854
|
+
return raw;
|
|
1855
|
+
} catch {
|
|
1856
|
+
return null;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
async function loadTokenFromGhKeychain() {
|
|
1860
|
+
const token = await readMacKeychainService(GH_KEYCHAIN_SERVICE);
|
|
1861
|
+
return token ? { token, source: "gh-keychain" } : null;
|
|
1862
|
+
}
|
|
1863
|
+
function vscodeUserDir(homeDir) {
|
|
1864
|
+
const home = homeDir ?? homedir9();
|
|
1865
|
+
if (process.platform === "darwin") return join11(home, "Library", "Application Support", "Code", "User");
|
|
1866
|
+
if (process.platform === "win32") return join11(home, "AppData", "Roaming", "Code", "User");
|
|
1867
|
+
return join11(home, ".config", "Code", "User");
|
|
1868
|
+
}
|
|
1869
|
+
function tokenFromText(raw) {
|
|
1870
|
+
const patterns = [
|
|
1871
|
+
/github\.com[^A-Za-z0-9_]+oauth_token[^A-Za-z0-9_]+([A-Za-z0-9_]{20,})/i,
|
|
1872
|
+
/github\.com[^A-Za-z0-9_]+(gh[opusr]_[A-Za-z0-9_]{20,})/i,
|
|
1873
|
+
/\b(gh[opusr]_[A-Za-z0-9_]{20,})\b/
|
|
1874
|
+
];
|
|
1875
|
+
for (const pattern of patterns) {
|
|
1876
|
+
const token = raw.match(pattern)?.[1];
|
|
1877
|
+
if (token) return token;
|
|
1878
|
+
}
|
|
1879
|
+
return null;
|
|
1880
|
+
}
|
|
1881
|
+
async function loadTokenFromVsCode(homeDir) {
|
|
1882
|
+
const userDir = vscodeUserDir(homeDir);
|
|
1883
|
+
const candidates = [
|
|
1884
|
+
join11(userDir, "globalStorage", "github.copilot-chat", "auth.json"),
|
|
1885
|
+
join11(userDir, "globalStorage", "github.copilot", "auth.json"),
|
|
1886
|
+
join11(userDir, "globalStorage", "state.vscdb")
|
|
1887
|
+
];
|
|
1888
|
+
try {
|
|
1889
|
+
for (const dirent of await readdir5(join11(userDir, "globalStorage"), { withFileTypes: true })) {
|
|
1890
|
+
if (dirent.isDirectory() && dirent.name.toLowerCase().includes("github")) {
|
|
1891
|
+
candidates.push(join11(userDir, "globalStorage", dirent.name, "auth.json"));
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
} catch {
|
|
1895
|
+
}
|
|
1896
|
+
for (const path of candidates) {
|
|
1897
|
+
try {
|
|
1898
|
+
const token = tokenFromText(await readFile5(path, "utf-8"));
|
|
1899
|
+
if (token) return { token, source: "vscode" };
|
|
1900
|
+
} catch {
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
return null;
|
|
1904
|
+
}
|
|
1905
|
+
async function loadToken(homeDir) {
|
|
1906
|
+
return await loadTokenFromHosts(homeDir) || await loadTokenFromGhKeychain() || await loadTokenFromVsCode(homeDir);
|
|
1907
|
+
}
|
|
1908
|
+
function redactToken(token) {
|
|
1909
|
+
return token.length <= 4 ? "****" : `****${token.slice(-4)}`;
|
|
1910
|
+
}
|
|
1911
|
+
function resetDate(value) {
|
|
1912
|
+
return typeof value === "string" && value.trim() ? resetIn(value) : null;
|
|
1913
|
+
}
|
|
1914
|
+
function percentMetric2(label, snapshot, reset, primary) {
|
|
1915
|
+
if (!snapshot || typeof snapshot.percent_remaining !== "number") return null;
|
|
1916
|
+
const used = Math.min(100, Math.max(0, 100 - snapshot.percent_remaining));
|
|
1917
|
+
return { label, used, limit: 100, format: { kind: "percent" }, resetsAt: reset, primary };
|
|
1918
|
+
}
|
|
1919
|
+
function countMetric(label, remaining, total, reset) {
|
|
1920
|
+
if (typeof remaining !== "number" || typeof total !== "number" || total <= 0) return null;
|
|
1921
|
+
return {
|
|
1922
|
+
label,
|
|
1923
|
+
used: Math.max(0, total - remaining),
|
|
1924
|
+
limit: total,
|
|
1925
|
+
format: { kind: "count" },
|
|
1926
|
+
resetsAt: reset
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
async function fetchUsage(token) {
|
|
1930
|
+
try {
|
|
1931
|
+
const res = await fetch(USAGE_URL3, {
|
|
1932
|
+
headers: {
|
|
1933
|
+
"Authorization": `token ${token}`,
|
|
1934
|
+
"Accept": "application/json",
|
|
1935
|
+
"Editor-Version": "vscode/1.96.2",
|
|
1936
|
+
"Editor-Plugin-Version": "copilot-chat/0.26.7",
|
|
1937
|
+
"User-Agent": "GitHubCopilotChat/0.26.7",
|
|
1938
|
+
"X-Github-Api-Version": "2025-04-01"
|
|
1939
|
+
},
|
|
1940
|
+
signal: AbortSignal.timeout(1e4)
|
|
1941
|
+
});
|
|
1942
|
+
if (!res.ok) return { data: null, status: res.status };
|
|
1943
|
+
return { data: await readJson(res), status: res.status };
|
|
1944
|
+
} catch {
|
|
1945
|
+
return { data: null, status: null };
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
async function copilotBilling(account) {
|
|
1949
|
+
const cred = await loadToken(account.homeDir);
|
|
1950
|
+
if (!cred) return { plan: null, metrics: [], error: "Not logged in \u2014 run gh auth login" };
|
|
1951
|
+
const { data, status } = await fetchUsage(cred.token);
|
|
1952
|
+
if (!data) {
|
|
1953
|
+
if (status === 401 || status === 403) {
|
|
1954
|
+
return { plan: null, metrics: [], error: `Token invalid (${redactToken(cred.token)}) \u2014 run gh auth login` };
|
|
1955
|
+
}
|
|
1956
|
+
if (status) return { plan: null, metrics: [], error: `Copilot API ${status}` };
|
|
1957
|
+
return { plan: null, metrics: [], error: "Network error" };
|
|
1958
|
+
}
|
|
1959
|
+
const plan = typeof data.copilot_plan === "string" && data.copilot_plan.trim() ? data.copilot_plan : null;
|
|
1960
|
+
const metrics = [];
|
|
1961
|
+
const quotaReset = resetDate(data.quota_reset_date);
|
|
1962
|
+
const snapshots = data.quota_snapshots;
|
|
1963
|
+
const premium = percentMetric2("Premium", snapshots?.premium_interactions, quotaReset, true);
|
|
1964
|
+
if (premium) metrics.push(premium);
|
|
1965
|
+
const chat = percentMetric2("Chat", snapshots?.chat, quotaReset);
|
|
1966
|
+
if (chat) metrics.push(chat);
|
|
1967
|
+
if (data.limited_user_quotas && data.monthly_quotas) {
|
|
1968
|
+
const reset = resetDate(data.limited_user_reset_date);
|
|
1969
|
+
const limitedChat = countMetric("Chat", data.limited_user_quotas.chat, data.monthly_quotas.chat, reset);
|
|
1970
|
+
if (limitedChat) metrics.push(limitedChat);
|
|
1971
|
+
const completions = countMetric("Completions", data.limited_user_quotas.completions, data.monthly_quotas.completions, reset);
|
|
1972
|
+
if (completions) metrics.push(completions);
|
|
1973
|
+
}
|
|
1974
|
+
if (metrics.length === 0) return { plan, metrics: [], error: "No usage data" };
|
|
1975
|
+
return { plan, metrics, error: null };
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// src/providers/copilot/index.ts
|
|
1979
|
+
var copilotProvider = {
|
|
1980
|
+
id: "copilot",
|
|
1981
|
+
name: "Copilot",
|
|
1982
|
+
color: "white",
|
|
1983
|
+
hasUsage: false,
|
|
1984
|
+
hasBilling: true,
|
|
1985
|
+
detect: (homeDir) => detectCopilot(homeDir),
|
|
1986
|
+
fetchBilling: (account) => copilotBilling(account)
|
|
1987
|
+
};
|
|
1988
|
+
|
|
1989
|
+
// src/providers/antigravity/billing.ts
|
|
1990
|
+
import { access as access7, readdir as readdir6 } from "fs/promises";
|
|
1991
|
+
import { join as join12 } from "path";
|
|
1992
|
+
import { homedir as homedir10 } from "os";
|
|
1993
|
+
|
|
1994
|
+
// src/providers/cloud-code.ts
|
|
1995
|
+
var CLOUD_CODE_URLS = [
|
|
1996
|
+
"https://daily-cloudcode-pa.googleapis.com",
|
|
1997
|
+
"https://cloudcode-pa.googleapis.com"
|
|
1998
|
+
];
|
|
1999
|
+
var LOAD_CODE_ASSIST_PATH = "/v1internal:loadCodeAssist";
|
|
2000
|
+
var FETCH_MODELS_PATH = "/v1internal:fetchAvailableModels";
|
|
2001
|
+
var RETRIEVE_QUOTA_PATH = "/v1internal:retrieveUserQuota";
|
|
2002
|
+
var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
|
|
2003
|
+
var GOOGLE_CLIENT_ID = process.env.TOKMON_GOOGLE_CLIENT_ID ?? "";
|
|
2004
|
+
var GOOGLE_CLIENT_SECRET = process.env.TOKMON_GOOGLE_CLIENT_SECRET ?? "";
|
|
2005
|
+
var OAUTH_TOKEN_KEY = "antigravityUnifiedStateSync.oauthToken";
|
|
2006
|
+
var OAUTH_TOKEN_SENTINEL = "oauthTokenInfoSentinelKey";
|
|
2007
|
+
var CC_MODEL_BLACKLIST = {
|
|
2008
|
+
MODEL_CHAT_20706: true,
|
|
2009
|
+
MODEL_CHAT_23310: true,
|
|
2010
|
+
MODEL_GOOGLE_GEMINI_2_5_FLASH: true,
|
|
2011
|
+
MODEL_GOOGLE_GEMINI_2_5_FLASH_THINKING: true,
|
|
2012
|
+
MODEL_GOOGLE_GEMINI_2_5_FLASH_LITE: true,
|
|
2013
|
+
MODEL_GOOGLE_GEMINI_2_5_PRO: true,
|
|
2014
|
+
MODEL_PLACEHOLDER_M19: true,
|
|
2015
|
+
MODEL_PLACEHOLDER_M9: true,
|
|
2016
|
+
MODEL_PLACEHOLDER_M12: true
|
|
2017
|
+
};
|
|
2018
|
+
function readVarint(bytes, start) {
|
|
2019
|
+
let value = 0;
|
|
2020
|
+
let shift = 0;
|
|
2021
|
+
let pos3 = start;
|
|
2022
|
+
while (pos3 < bytes.length) {
|
|
2023
|
+
const b = bytes[pos3++];
|
|
2024
|
+
value += (b & 127) * Math.pow(2, shift);
|
|
2025
|
+
if ((b & 128) === 0) return { value, pos: pos3 };
|
|
2026
|
+
shift += 7;
|
|
2027
|
+
}
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
function readFields(bytes) {
|
|
2031
|
+
const fields = {};
|
|
2032
|
+
let pos3 = 0;
|
|
2033
|
+
while (pos3 < bytes.length) {
|
|
2034
|
+
const tag = readVarint(bytes, pos3);
|
|
2035
|
+
if (!tag) break;
|
|
2036
|
+
pos3 = tag.pos;
|
|
2037
|
+
const fieldNum = Math.floor(tag.value / 8);
|
|
2038
|
+
const wireType = tag.value % 8;
|
|
2039
|
+
if (wireType === 0) {
|
|
2040
|
+
const val = readVarint(bytes, pos3);
|
|
2041
|
+
if (!val) break;
|
|
2042
|
+
fields[fieldNum] = { type: 0, value: val.value };
|
|
2043
|
+
pos3 = val.pos;
|
|
2044
|
+
} else if (wireType === 1) {
|
|
2045
|
+
if (pos3 + 8 > bytes.length) break;
|
|
2046
|
+
pos3 += 8;
|
|
2047
|
+
} else if (wireType === 2) {
|
|
2048
|
+
const len = readVarint(bytes, pos3);
|
|
2049
|
+
if (!len) break;
|
|
2050
|
+
pos3 = len.pos;
|
|
2051
|
+
if (pos3 + len.value > bytes.length) break;
|
|
2052
|
+
fields[fieldNum] = { type: 2, data: bytes.slice(pos3, pos3 + len.value) };
|
|
2053
|
+
pos3 += len.value;
|
|
2054
|
+
} else if (wireType === 5) {
|
|
2055
|
+
if (pos3 + 4 > bytes.length) break;
|
|
2056
|
+
pos3 += 4;
|
|
2057
|
+
} else {
|
|
2058
|
+
break;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return fields;
|
|
2062
|
+
}
|
|
2063
|
+
function utf8(data) {
|
|
2064
|
+
return Buffer.from(data).toString("utf8");
|
|
2065
|
+
}
|
|
2066
|
+
function decodeBase64(text) {
|
|
2067
|
+
try {
|
|
2068
|
+
return Buffer.from(text, "base64");
|
|
2069
|
+
} catch {
|
|
2070
|
+
return null;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
function unwrapKeyringBase64(raw) {
|
|
2074
|
+
const text = raw.trim();
|
|
2075
|
+
if (!text.startsWith("go-keyring-base64:")) return text;
|
|
2076
|
+
const decoded = decodeBase64(text.slice("go-keyring-base64:".length));
|
|
2077
|
+
return decoded ? utf8(decoded).trim() : text;
|
|
2078
|
+
}
|
|
2079
|
+
function unwrapOAuthSentinel(base64Text) {
|
|
2080
|
+
const outerBytes = decodeBase64(unwrapKeyringBase64(base64Text));
|
|
2081
|
+
if (!outerBytes) return null;
|
|
2082
|
+
const outer = readFields(outerBytes);
|
|
2083
|
+
if (outer[1]?.type !== 2 || !outer[1].data) return null;
|
|
2084
|
+
const wrapper = readFields(outer[1].data);
|
|
2085
|
+
const sentinel = wrapper[1]?.type === 2 && wrapper[1].data ? utf8(wrapper[1].data) : null;
|
|
2086
|
+
const payload = wrapper[2]?.type === 2 ? wrapper[2].data : null;
|
|
2087
|
+
if (sentinel !== OAUTH_TOKEN_SENTINEL || !payload) return null;
|
|
2088
|
+
const payloadFields = readFields(payload);
|
|
2089
|
+
if (payloadFields[1]?.type !== 2 || !payloadFields[1].data) return null;
|
|
2090
|
+
const innerText = utf8(payloadFields[1].data).trim();
|
|
2091
|
+
return innerText ? decodeBase64(innerText) : null;
|
|
2092
|
+
}
|
|
2093
|
+
async function readAntigravityOAuthToken(db) {
|
|
2094
|
+
const r = await runSqlite(db, "SELECT value FROM ItemTable WHERE key=? LIMIT 1;", [OAUTH_TOKEN_KEY]);
|
|
2095
|
+
if (r.status !== "ok") return { token: null, status: r.status };
|
|
2096
|
+
const raw = r.rows[0]?.value;
|
|
2097
|
+
if (typeof raw !== "string" || !raw.trim()) return { token: null, status: "ok" };
|
|
2098
|
+
const inner = unwrapOAuthSentinel(raw);
|
|
2099
|
+
if (!inner) return { token: null, status: "ok" };
|
|
2100
|
+
const fields = readFields(inner);
|
|
2101
|
+
const accessToken = fields[1]?.type === 2 && fields[1].data ? utf8(fields[1].data) : null;
|
|
2102
|
+
const refreshToken = fields[3]?.type === 2 && fields[3].data ? utf8(fields[3].data) : null;
|
|
2103
|
+
let expirySeconds = null;
|
|
2104
|
+
if (fields[4]?.type === 2 && fields[4].data) {
|
|
2105
|
+
const ts = readFields(fields[4].data);
|
|
2106
|
+
expirySeconds = ts[1]?.type === 0 && typeof ts[1].value === "number" ? ts[1].value : null;
|
|
2107
|
+
}
|
|
2108
|
+
if (!accessToken && !refreshToken) return { token: null, status: "ok" };
|
|
2109
|
+
return { token: { accessToken, refreshToken, expirySeconds }, status: "ok" };
|
|
2110
|
+
}
|
|
2111
|
+
function redact(token) {
|
|
2112
|
+
if (!token) return "none";
|
|
2113
|
+
return `...${token.slice(-4)}`;
|
|
2114
|
+
}
|
|
2115
|
+
async function refreshAccessToken(refreshToken) {
|
|
2116
|
+
if (!refreshToken || !GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) return null;
|
|
2117
|
+
const body = new URLSearchParams({
|
|
2118
|
+
client_id: GOOGLE_CLIENT_ID,
|
|
2119
|
+
client_secret: GOOGLE_CLIENT_SECRET,
|
|
2120
|
+
refresh_token: refreshToken,
|
|
2121
|
+
grant_type: "refresh_token"
|
|
2122
|
+
});
|
|
2123
|
+
try {
|
|
2124
|
+
const res = await fetch(GOOGLE_OAUTH_URL, {
|
|
2125
|
+
method: "POST",
|
|
2126
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
2127
|
+
body,
|
|
2128
|
+
signal: AbortSignal.timeout(15e3)
|
|
2129
|
+
});
|
|
2130
|
+
if (!res.ok) return null;
|
|
2131
|
+
const json = await readJson(res);
|
|
2132
|
+
return typeof json?.access_token === "string" && json.access_token.trim() ? json.access_token.trim() : null;
|
|
2133
|
+
} catch {
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
async function requestCloudCodeJson(path, token, body) {
|
|
2138
|
+
for (const base of CLOUD_CODE_URLS) {
|
|
2139
|
+
try {
|
|
2140
|
+
const res = await fetch(`${base}${path}`, {
|
|
2141
|
+
method: "POST",
|
|
2142
|
+
headers: {
|
|
2143
|
+
Accept: "application/json",
|
|
2144
|
+
"Content-Type": "application/json",
|
|
2145
|
+
Authorization: `Bearer ${token}`,
|
|
2146
|
+
"User-Agent": "agy"
|
|
2147
|
+
},
|
|
2148
|
+
body: JSON.stringify(body ?? {}),
|
|
2149
|
+
signal: AbortSignal.timeout(15e3)
|
|
2150
|
+
});
|
|
2151
|
+
if (res.status === 401 || res.status === 403) return { _authFailed: true };
|
|
2152
|
+
if (!res.ok) continue;
|
|
2153
|
+
const json = await readJson(res);
|
|
2154
|
+
if (json && typeof json === "object") return json;
|
|
2155
|
+
} catch {
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
return null;
|
|
2159
|
+
}
|
|
2160
|
+
function readPlan(loadData) {
|
|
2161
|
+
const paid = typeof loadData?.paidTier?.name === "string" ? loadData.paidTier.name.trim() : "";
|
|
2162
|
+
if (paid) return paid;
|
|
2163
|
+
const current = typeof loadData?.currentTier?.name === "string" ? loadData.currentTier.name.trim() : "";
|
|
2164
|
+
return current || null;
|
|
2165
|
+
}
|
|
2166
|
+
function parseBuckets(data) {
|
|
2167
|
+
if (!Array.isArray(data?.buckets)) return [];
|
|
2168
|
+
return data.buckets.flatMap((bucket) => {
|
|
2169
|
+
const modelId = typeof bucket?.modelId === "string" ? bucket.modelId.trim() : "";
|
|
2170
|
+
if (!modelId) return [];
|
|
2171
|
+
return [{
|
|
2172
|
+
modelId,
|
|
2173
|
+
remainingFraction: typeof bucket.remainingFraction === "number" ? bucket.remainingFraction : 0,
|
|
2174
|
+
resetTime: typeof bucket.resetTime === "string" ? bucket.resetTime : void 0
|
|
2175
|
+
}];
|
|
2176
|
+
});
|
|
2177
|
+
}
|
|
2178
|
+
function parseModelBuckets(data) {
|
|
2179
|
+
const models = data?.models;
|
|
2180
|
+
if (!models || typeof models !== "object") return [];
|
|
2181
|
+
return Object.keys(models).flatMap((key) => {
|
|
2182
|
+
const model = models[key];
|
|
2183
|
+
if (!model || typeof model !== "object" || model.isInternal) return [];
|
|
2184
|
+
const modelId = typeof model.model === "string" && model.model.trim() ? model.model.trim() : key;
|
|
2185
|
+
if (CC_MODEL_BLACKLIST[modelId]) return [];
|
|
2186
|
+
const displayName = typeof model.displayName === "string" && model.displayName.trim() || typeof model.label === "string" && model.label.trim() || "";
|
|
2187
|
+
if (!displayName) return [];
|
|
2188
|
+
const quotaInfo = model.quotaInfo;
|
|
2189
|
+
return [{
|
|
2190
|
+
modelId: displayName,
|
|
2191
|
+
remainingFraction: typeof quotaInfo?.remainingFraction === "number" ? quotaInfo.remainingFraction : 0,
|
|
2192
|
+
resetTime: typeof quotaInfo?.resetTime === "string" ? quotaInfo.resetTime : void 0
|
|
2193
|
+
}];
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
async function fetchWithAccessToken(accessToken) {
|
|
2197
|
+
const loadData = await requestCloudCodeJson(LOAD_CODE_ASSIST_PATH, accessToken, {});
|
|
2198
|
+
if (!loadData) return { ok: false, plan: null, error: "Cloud Code API error" };
|
|
2199
|
+
if ("_authFailed" in loadData) return { ok: false, plan: null, error: "Token expired" };
|
|
2200
|
+
const plan = readPlan(loadData);
|
|
2201
|
+
const project = typeof loadData.cloudaicompanionProject === "string" && loadData.cloudaicompanionProject.trim() ? loadData.cloudaicompanionProject.trim() : null;
|
|
2202
|
+
let quotaData = project ? await requestCloudCodeJson(RETRIEVE_QUOTA_PATH, accessToken, { project }) : null;
|
|
2203
|
+
if (!quotaData || "_authFailed" in quotaData) {
|
|
2204
|
+
quotaData = await requestCloudCodeJson(RETRIEVE_QUOTA_PATH, accessToken, {});
|
|
2205
|
+
}
|
|
2206
|
+
if (!quotaData) return { ok: false, plan, error: "Cloud Code quota unavailable" };
|
|
2207
|
+
if ("_authFailed" in quotaData) return { ok: false, plan, error: "Token expired" };
|
|
2208
|
+
let buckets = parseBuckets(quotaData);
|
|
2209
|
+
if (buckets.length === 0) {
|
|
2210
|
+
const modelData = await requestCloudCodeJson(FETCH_MODELS_PATH, accessToken, {});
|
|
2211
|
+
if (modelData && !("_authFailed" in modelData)) buckets = parseModelBuckets(modelData);
|
|
2212
|
+
if (modelData && "_authFailed" in modelData) return { ok: false, plan, error: "Token expired" };
|
|
2213
|
+
}
|
|
2214
|
+
if (buckets.length === 0) return { ok: false, plan, error: "No quota data" };
|
|
2215
|
+
return { ok: true, plan, buckets };
|
|
2216
|
+
}
|
|
2217
|
+
async function fetchCloudCodeQuota(token, expiredMessage = "Token expired") {
|
|
2218
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
2219
|
+
let accessToken = token.accessToken?.trim() || null;
|
|
2220
|
+
if (accessToken && token.expirySeconds && token.expirySeconds <= nowSec) {
|
|
2221
|
+
accessToken = await refreshAccessToken(token.refreshToken);
|
|
2222
|
+
if (!accessToken) return { ok: false, plan: null, error: expiredMessage };
|
|
2223
|
+
}
|
|
2224
|
+
if (!accessToken) {
|
|
2225
|
+
accessToken = await refreshAccessToken(token.refreshToken);
|
|
2226
|
+
if (!accessToken) return { ok: false, plan: null, error: `Missing credentials (${redact(token.accessToken)})` };
|
|
2227
|
+
}
|
|
2228
|
+
const result = await fetchWithAccessToken(accessToken);
|
|
2229
|
+
if (result.ok || result.error !== "Token expired") return result;
|
|
2230
|
+
const refreshed = await refreshAccessToken(token.refreshToken);
|
|
2231
|
+
if (!refreshed) return { ok: false, plan: result.plan, error: expiredMessage };
|
|
2232
|
+
return fetchWithAccessToken(refreshed);
|
|
2233
|
+
}
|
|
2234
|
+
function normalizeLabel(label) {
|
|
2235
|
+
return label.replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
2236
|
+
}
|
|
2237
|
+
function poolLabel(label) {
|
|
2238
|
+
const lower = normalizeLabel(label).toLowerCase();
|
|
2239
|
+
if (lower.includes("gemini") && lower.includes("pro")) return "Gemini Pro";
|
|
2240
|
+
if (lower.includes("gemini") && lower.includes("flash")) return "Gemini Flash";
|
|
2241
|
+
return "Claude";
|
|
2242
|
+
}
|
|
2243
|
+
function sortKey(label) {
|
|
2244
|
+
const lower = label.toLowerCase();
|
|
2245
|
+
if (lower.includes("gemini") && lower.includes("pro")) return `0a_${label}`;
|
|
2246
|
+
if (lower.includes("gemini")) return `0b_${label}`;
|
|
2247
|
+
if (lower.includes("claude") && lower.includes("opus")) return `1a_${label}`;
|
|
2248
|
+
if (lower.includes("claude")) return `1b_${label}`;
|
|
2249
|
+
return `2_${label}`;
|
|
2250
|
+
}
|
|
2251
|
+
function cloudCodeBucketsToMetrics(buckets) {
|
|
2252
|
+
const pooled = /* @__PURE__ */ new Map();
|
|
2253
|
+
for (const bucket of buckets) {
|
|
2254
|
+
const label = poolLabel(bucket.modelId);
|
|
2255
|
+
const existing = pooled.get(label);
|
|
2256
|
+
if (!existing || bucket.remainingFraction < existing.remainingFraction) {
|
|
2257
|
+
pooled.set(label, { ...bucket, modelId: label });
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return [...pooled.values()].sort((a, b) => sortKey(a.modelId).localeCompare(sortKey(b.modelId))).map((bucket, i) => {
|
|
2261
|
+
const clamped = Math.max(0, Math.min(1, bucket.remainingFraction));
|
|
2262
|
+
return {
|
|
2263
|
+
label: bucket.modelId,
|
|
2264
|
+
used: Math.round((1 - clamped) * 100),
|
|
2265
|
+
limit: 100,
|
|
2266
|
+
format: { kind: "percent" },
|
|
2267
|
+
resetsAt: bucket.resetTime ? resetIn(bucket.resetTime) : null,
|
|
2268
|
+
primary: i === 0
|
|
2269
|
+
};
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
function cloudCodeSqliteError(status) {
|
|
2273
|
+
return status === "ok" ? "Not signed in \u2014 open Antigravity" : sqliteStatusMessage(status).replace(/Cursor/g, "Antigravity");
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
// src/providers/antigravity/billing.ts
|
|
2277
|
+
async function exists(path) {
|
|
2278
|
+
try {
|
|
2279
|
+
await access7(path);
|
|
2280
|
+
return true;
|
|
2281
|
+
} catch {
|
|
2282
|
+
return false;
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
async function firstExisting(paths) {
|
|
2286
|
+
for (const path of paths) {
|
|
2287
|
+
if (await exists(path)) return path;
|
|
2288
|
+
}
|
|
2289
|
+
return paths[0];
|
|
2290
|
+
}
|
|
2291
|
+
async function antigravityStateDb(homeDir) {
|
|
2292
|
+
const base = homeDir ?? homedir10();
|
|
2293
|
+
const tail = ["User", "globalStorage", "state.vscdb"];
|
|
2294
|
+
if (process.platform === "darwin") {
|
|
2295
|
+
const support = join12(base, "Library", "Application Support");
|
|
2296
|
+
const exact = [
|
|
2297
|
+
join12(support, "Antigravity IDE", ...tail),
|
|
2298
|
+
join12(support, "Antigravity", ...tail)
|
|
2299
|
+
];
|
|
2300
|
+
try {
|
|
2301
|
+
const entries = await readdir6(support, { withFileTypes: true });
|
|
2302
|
+
const matches = entries.filter((e) => e.isDirectory() && e.name.includes("Antigravity")).map((e) => join12(support, e.name, ...tail));
|
|
2303
|
+
return firstExisting([...exact, ...matches]);
|
|
2304
|
+
} catch {
|
|
2305
|
+
return firstExisting(exact);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
if (process.platform === "win32") {
|
|
2309
|
+
const roaming = homeDir ? join12(homeDir, "AppData", "Roaming") : envDir("APPDATA") ?? join12(base, "AppData", "Roaming");
|
|
2310
|
+
return firstExisting([
|
|
2311
|
+
join12(roaming, "Antigravity IDE", ...tail),
|
|
2312
|
+
join12(roaming, "Antigravity", ...tail)
|
|
2313
|
+
]);
|
|
2314
|
+
}
|
|
2315
|
+
const cfg = homeDir ? join12(homeDir, ".config") : envDir("XDG_CONFIG_HOME") ?? join12(base, ".config");
|
|
2316
|
+
return firstExisting([
|
|
2317
|
+
join12(cfg, "Antigravity IDE", ...tail),
|
|
2318
|
+
join12(cfg, "Antigravity", ...tail)
|
|
2319
|
+
]);
|
|
2320
|
+
}
|
|
2321
|
+
async function detectAntigravity(homeDir) {
|
|
2322
|
+
return exists(await antigravityStateDb(homeDir));
|
|
2323
|
+
}
|
|
2324
|
+
async function antigravityBilling(account) {
|
|
2325
|
+
try {
|
|
2326
|
+
const db = await antigravityStateDb(account.homeDir);
|
|
2327
|
+
const { token, status } = await readAntigravityOAuthToken(db);
|
|
2328
|
+
if (!token) return { plan: null, metrics: [], error: cloudCodeSqliteError(status) };
|
|
2329
|
+
const quota = await fetchCloudCodeQuota(token, "Token expired \u2014 open Antigravity");
|
|
2330
|
+
if (!quota.ok) return { plan: quota.plan, metrics: [], error: quota.error };
|
|
2331
|
+
return { plan: quota.plan, metrics: cloudCodeBucketsToMetrics(quota.buckets), error: null };
|
|
2332
|
+
} catch {
|
|
2333
|
+
return { plan: null, metrics: [], error: "Antigravity billing unavailable" };
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
// src/providers/antigravity/index.ts
|
|
2338
|
+
var antigravityProvider = {
|
|
2339
|
+
id: "antigravity",
|
|
2340
|
+
name: "Antigravity",
|
|
2341
|
+
color: "red",
|
|
2342
|
+
hasUsage: false,
|
|
2343
|
+
hasBilling: true,
|
|
2344
|
+
detect: (homeDir) => detectAntigravity(homeDir),
|
|
2345
|
+
fetchBilling: (account) => antigravityBilling(account)
|
|
2346
|
+
};
|
|
2347
|
+
|
|
2348
|
+
// src/providers/gemini/billing.ts
|
|
2349
|
+
import { access as access8, readFile as readFile6 } from "fs/promises";
|
|
2350
|
+
import { join as join13 } from "path";
|
|
2351
|
+
import { homedir as homedir11 } from "os";
|
|
2352
|
+
function geminiCredsPath(homeDir) {
|
|
2353
|
+
return join13(homeDir ?? homedir11(), ".gemini", "oauth_creds.json");
|
|
2354
|
+
}
|
|
2355
|
+
async function detectGemini(homeDir) {
|
|
2356
|
+
try {
|
|
2357
|
+
await access8(geminiCredsPath(homeDir));
|
|
2358
|
+
return true;
|
|
2359
|
+
} catch {
|
|
2360
|
+
return false;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
async function readGeminiCreds(path) {
|
|
2364
|
+
try {
|
|
2365
|
+
return JSON.parse(await readFile6(path, "utf8"));
|
|
2366
|
+
} catch {
|
|
2367
|
+
return null;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
async function geminiBilling(account) {
|
|
2371
|
+
try {
|
|
2372
|
+
const creds = await readGeminiCreds(geminiCredsPath(account.homeDir));
|
|
2373
|
+
const accessToken = typeof creds?.access_token === "string" ? creds.access_token.trim() : "";
|
|
2374
|
+
const refreshToken = typeof creds?.refresh_token === "string" ? creds.refresh_token.trim() : null;
|
|
2375
|
+
if (!creds || !accessToken && !refreshToken) return { plan: null, metrics: [], error: "Not signed in \u2014 run gemini" };
|
|
2376
|
+
const quota = await fetchCloudCodeQuota({
|
|
2377
|
+
accessToken,
|
|
2378
|
+
refreshToken,
|
|
2379
|
+
expirySeconds: typeof creds.expiry_date === "number" ? Math.floor(creds.expiry_date / 1e3) : null
|
|
2380
|
+
}, "Token expired \u2014 run gemini");
|
|
2381
|
+
if (!quota.ok) return { plan: quota.plan, metrics: [], error: quota.error };
|
|
2382
|
+
return { plan: quota.plan, metrics: cloudCodeBucketsToMetrics(quota.buckets), error: null };
|
|
2383
|
+
} catch {
|
|
2384
|
+
return { plan: null, metrics: [], error: "Gemini billing unavailable" };
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
// src/providers/gemini/index.ts
|
|
2389
|
+
var geminiProvider = {
|
|
2390
|
+
id: "gemini",
|
|
2391
|
+
name: "Gemini",
|
|
2392
|
+
color: "greenBright",
|
|
2393
|
+
hasUsage: false,
|
|
2394
|
+
hasBilling: true,
|
|
2395
|
+
detect: (homeDir) => detectGemini(homeDir),
|
|
2396
|
+
fetchBilling: (account) => geminiBilling(account)
|
|
2397
|
+
};
|
|
2398
|
+
|
|
1417
2399
|
// src/providers/detect.ts
|
|
1418
2400
|
import { accessSync, constants } from "fs";
|
|
1419
|
-
import { join as
|
|
1420
|
-
import { homedir as
|
|
2401
|
+
import { join as join14, delimiter, isAbsolute as isAbsolute3 } from "path";
|
|
2402
|
+
import { homedir as homedir12 } from "os";
|
|
1421
2403
|
function searchDirs() {
|
|
1422
|
-
const home =
|
|
2404
|
+
const home = homedir12();
|
|
1423
2405
|
const fromEnv = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
1424
2406
|
const extra = process.platform === "win32" ? [
|
|
1425
|
-
process.env.APPDATA &&
|
|
1426
|
-
process.env.LOCALAPPDATA &&
|
|
1427
|
-
|
|
2407
|
+
process.env.APPDATA && join14(process.env.APPDATA, "npm"),
|
|
2408
|
+
process.env.LOCALAPPDATA && join14(process.env.LOCALAPPDATA, "pnpm"),
|
|
2409
|
+
join14(home, "scoop", "shims")
|
|
1428
2410
|
] : [
|
|
1429
2411
|
"/opt/homebrew/bin",
|
|
1430
2412
|
"/usr/local/bin",
|
|
1431
2413
|
"/usr/bin",
|
|
1432
2414
|
"/bin",
|
|
1433
2415
|
"/opt/local/bin",
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
2416
|
+
join14(home, ".local", "bin"),
|
|
2417
|
+
join14(home, "bin"),
|
|
2418
|
+
join14(home, ".npm-global", "bin"),
|
|
2419
|
+
join14(home, ".bun", "bin"),
|
|
2420
|
+
join14(home, ".local", "share", "pnpm")
|
|
1439
2421
|
];
|
|
1440
2422
|
return [.../* @__PURE__ */ new Set([...fromEnv, ...extra.filter((d) => !!d)])];
|
|
1441
2423
|
}
|
|
@@ -1452,7 +2434,7 @@ function onPath(names) {
|
|
|
1452
2434
|
for (const dir of searchDirs()) {
|
|
1453
2435
|
for (const n of names) {
|
|
1454
2436
|
for (const e of exts) {
|
|
1455
|
-
if (isExec(
|
|
2437
|
+
if (isExec(join14(dir, n + e))) return true;
|
|
1456
2438
|
}
|
|
1457
2439
|
}
|
|
1458
2440
|
}
|
|
@@ -1462,7 +2444,7 @@ function anyExists(paths) {
|
|
|
1462
2444
|
return paths.some((p) => !!p && isExec(p));
|
|
1463
2445
|
}
|
|
1464
2446
|
function installSignals(id) {
|
|
1465
|
-
const home =
|
|
2447
|
+
const home = homedir12();
|
|
1466
2448
|
const pf = process.env.ProgramFiles;
|
|
1467
2449
|
const pf86 = process.env["ProgramFiles(x86)"];
|
|
1468
2450
|
const lad = process.env.LOCALAPPDATA;
|
|
@@ -1470,8 +2452,8 @@ function installSignals(id) {
|
|
|
1470
2452
|
case "claude":
|
|
1471
2453
|
return onPath(["claude"]) || anyExists([
|
|
1472
2454
|
"/Applications/Claude.app",
|
|
1473
|
-
|
|
1474
|
-
lad &&
|
|
2455
|
+
join14(home, "Applications", "Claude.app"),
|
|
2456
|
+
lad && join14(lad, "Programs", "claude", "Claude.exe")
|
|
1475
2457
|
]);
|
|
1476
2458
|
case "codex": {
|
|
1477
2459
|
const bin = process.env.CODEX_BIN;
|
|
@@ -1481,25 +2463,44 @@ function installSignals(id) {
|
|
|
1481
2463
|
case "cursor":
|
|
1482
2464
|
return onPath(["cursor", "cursor-agent"]) || anyExists([
|
|
1483
2465
|
"/Applications/Cursor.app",
|
|
1484
|
-
|
|
1485
|
-
lad &&
|
|
1486
|
-
pf &&
|
|
1487
|
-
pf86 &&
|
|
2466
|
+
join14(home, "Applications", "Cursor.app"),
|
|
2467
|
+
lad && join14(lad, "Programs", "cursor", "Cursor.exe"),
|
|
2468
|
+
pf && join14(pf, "Cursor", "Cursor.exe"),
|
|
2469
|
+
pf86 && join14(pf86, "Cursor", "Cursor.exe"),
|
|
1488
2470
|
"/opt/Cursor/cursor",
|
|
1489
2471
|
"/usr/share/cursor/cursor",
|
|
1490
2472
|
"/usr/bin/cursor"
|
|
1491
2473
|
]);
|
|
2474
|
+
case "pi":
|
|
2475
|
+
return onPath(["pi"]);
|
|
2476
|
+
case "opencode":
|
|
2477
|
+
return onPath(["opencode"]);
|
|
2478
|
+
case "copilot":
|
|
2479
|
+
return onPath(["gh"]);
|
|
2480
|
+
case "antigravity":
|
|
2481
|
+
return onPath(["antigravity"]) || anyExists([
|
|
2482
|
+
"/Applications/Antigravity.app",
|
|
2483
|
+
join14(home, "Applications", "Antigravity.app"),
|
|
2484
|
+
lad && join14(lad, "Programs", "Antigravity", "Antigravity.exe")
|
|
2485
|
+
]);
|
|
2486
|
+
case "gemini":
|
|
2487
|
+
return onPath(["gemini"]);
|
|
1492
2488
|
default:
|
|
1493
2489
|
return false;
|
|
1494
2490
|
}
|
|
1495
2491
|
}
|
|
1496
2492
|
|
|
1497
2493
|
// src/providers/index.ts
|
|
1498
|
-
var PROVIDER_ORDER = ["claude", "codex", "cursor"];
|
|
2494
|
+
var PROVIDER_ORDER = ["claude", "codex", "cursor", "copilot", "pi", "opencode", "antigravity", "gemini"];
|
|
1499
2495
|
var PROVIDERS = {
|
|
1500
2496
|
claude: claudeProvider,
|
|
1501
2497
|
codex: codexProvider,
|
|
1502
|
-
cursor: cursorProvider
|
|
2498
|
+
cursor: cursorProvider,
|
|
2499
|
+
pi: piProvider,
|
|
2500
|
+
opencode: opencodeProvider,
|
|
2501
|
+
copilot: copilotProvider,
|
|
2502
|
+
antigravity: antigravityProvider,
|
|
2503
|
+
gemini: geminiProvider
|
|
1503
2504
|
};
|
|
1504
2505
|
var ALL_PROVIDERS = PROVIDER_ORDER.map((id) => PROVIDERS[id]);
|
|
1505
2506
|
async function detectProviders() {
|
|
@@ -1548,14 +2549,46 @@ function accountsByProvider(accounts) {
|
|
|
1548
2549
|
return groups;
|
|
1549
2550
|
}
|
|
1550
2551
|
|
|
2552
|
+
// src/snapshot.ts
|
|
2553
|
+
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir3, rename as rename3 } from "fs/promises";
|
|
2554
|
+
import { join as join15 } from "path";
|
|
2555
|
+
function snapshotFile() {
|
|
2556
|
+
return join15(cacheDir(), "dashboard-snapshot.json");
|
|
2557
|
+
}
|
|
2558
|
+
async function loadSnapshot() {
|
|
2559
|
+
try {
|
|
2560
|
+
const obj = JSON.parse(await readFile7(snapshotFile(), "utf-8"));
|
|
2561
|
+
return obj && typeof obj === "object" ? obj : {};
|
|
2562
|
+
} catch {
|
|
2563
|
+
return {};
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
var saveQueue2 = Promise.resolve();
|
|
2567
|
+
function saveSnapshot(stats) {
|
|
2568
|
+
const obj = {};
|
|
2569
|
+
for (const [id, s] of stats) {
|
|
2570
|
+
if (s.dashboard || s.billing) obj[id] = { dashboard: s.dashboard ?? null, billing: s.billing ?? null };
|
|
2571
|
+
}
|
|
2572
|
+
saveQueue2 = saveQueue2.then(async () => {
|
|
2573
|
+
try {
|
|
2574
|
+
const dir = cacheDir();
|
|
2575
|
+
await mkdir3(dir, { recursive: true });
|
|
2576
|
+
const tmp = join15(dir, `dashboard-snapshot.json.${process.pid}.tmp`);
|
|
2577
|
+
await writeFile3(tmp, JSON.stringify(obj));
|
|
2578
|
+
await rename3(tmp, snapshotFile());
|
|
2579
|
+
} catch {
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
|
|
1551
2584
|
// src/ui/shared.tsx
|
|
1552
2585
|
import { useState, useEffect, useRef } from "react";
|
|
1553
2586
|
import { Box, Text } from "ink";
|
|
1554
2587
|
import { useOnMouseClick } from "@zenobius/ink-mouse";
|
|
1555
2588
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1556
|
-
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1557
2589
|
function truncateName(s, n) {
|
|
1558
|
-
|
|
2590
|
+
const ell = glyphs().ellipsis;
|
|
2591
|
+
return s.length > n ? s.slice(0, n - ell.length) + ell : s;
|
|
1559
2592
|
}
|
|
1560
2593
|
function ClickableBox({ onClick, children, ...props }) {
|
|
1561
2594
|
const ref = useRef(null);
|
|
@@ -1565,21 +2598,22 @@ function ClickableBox({ onClick, children, ...props }) {
|
|
|
1565
2598
|
return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
|
|
1566
2599
|
}
|
|
1567
2600
|
function Spinner({ label }) {
|
|
2601
|
+
const frames = glyphs().spinner;
|
|
1568
2602
|
const [i, setI] = useState(0);
|
|
1569
2603
|
useEffect(() => {
|
|
1570
|
-
const id = setInterval(() => setI((n) => (n + 1) %
|
|
2604
|
+
const id = setInterval(() => setI((n) => (n + 1) % frames.length), 80);
|
|
1571
2605
|
return () => clearInterval(id);
|
|
1572
2606
|
}, []);
|
|
1573
2607
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
1574
2608
|
/* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
1575
|
-
|
|
2609
|
+
frames[i],
|
|
1576
2610
|
" "
|
|
1577
2611
|
] }),
|
|
1578
2612
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: label })
|
|
1579
2613
|
] });
|
|
1580
2614
|
}
|
|
1581
|
-
function TabBar({ tabs, active, onSelect }) {
|
|
1582
|
-
return /* @__PURE__ */ jsx(Box, { children: tabs.map((t, i) => /* @__PURE__ */ jsx(ClickableBox, { onClick: () => onSelect(i), marginRight: 1, children: i ===
|
|
2615
|
+
function TabBar({ tabs, active: active2, onSelect }) {
|
|
2616
|
+
return /* @__PURE__ */ jsx(Box, { children: tabs.map((t, i) => /* @__PURE__ */ jsx(ClickableBox, { onClick: () => onSelect(i), marginRight: 1, children: i === active2 ? /* @__PURE__ */ jsxs(Text, { bold: true, inverse: true, children: [
|
|
1583
2617
|
" ",
|
|
1584
2618
|
t,
|
|
1585
2619
|
" "
|
|
@@ -1592,7 +2626,10 @@ function TabBar({ tabs, active, onSelect }) {
|
|
|
1592
2626
|
function PeakBadge({ peak }) {
|
|
1593
2627
|
const color = peak.state === "peak" ? "red" : "green";
|
|
1594
2628
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
1595
|
-
/* @__PURE__ */
|
|
2629
|
+
/* @__PURE__ */ jsxs(Text, { color, children: [
|
|
2630
|
+
glyphs().dot,
|
|
2631
|
+
" "
|
|
2632
|
+
] }),
|
|
1596
2633
|
/* @__PURE__ */ jsx(Text, { bold: true, color, children: peak.label }),
|
|
1597
2634
|
peak.minutesUntilChange !== null && peak.minutesUntilChange > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
1598
2635
|
" (",
|
|
@@ -1608,24 +2645,24 @@ function fmtMinutes(mins) {
|
|
|
1608
2645
|
return m === 0 ? `${h}h` : `${h}h ${m}m`;
|
|
1609
2646
|
}
|
|
1610
2647
|
function currencySymbol(cur) {
|
|
1611
|
-
return cur === "EUR" ?
|
|
2648
|
+
return cur === "EUR" ? glyphs().eur : cur === "GBP" ? glyphs().gbp : "$";
|
|
1612
2649
|
}
|
|
1613
|
-
var SPARK = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
|
|
1614
2650
|
function sparkline(values) {
|
|
1615
2651
|
if (values.length === 0) return "";
|
|
2652
|
+
const spark = glyphs().spark;
|
|
1616
2653
|
const max = Math.max(...values);
|
|
1617
|
-
if (max <= 0) return
|
|
2654
|
+
if (max <= 0) return spark[0].repeat(values.length);
|
|
1618
2655
|
return values.map((v) => {
|
|
1619
|
-
if (v <= 0) return
|
|
1620
|
-
const idx = Math.min(
|
|
1621
|
-
return
|
|
2656
|
+
if (v <= 0) return spark[0];
|
|
2657
|
+
const idx = Math.min(spark.length - 1, 1 + Math.round(v / max * (spark.length - 2)));
|
|
2658
|
+
return spark[idx];
|
|
1622
2659
|
}).join("");
|
|
1623
2660
|
}
|
|
1624
2661
|
function Bar({ pct: pct2, color, width = 24 }) {
|
|
1625
2662
|
const filled = Math.max(0, Math.min(width, Math.round(pct2 / 100 * width)));
|
|
1626
2663
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
1627
|
-
/* @__PURE__ */ jsx(Text, { color, children:
|
|
1628
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children:
|
|
2664
|
+
/* @__PURE__ */ jsx(Text, { color, children: glyphs().barFull.repeat(filled) }),
|
|
2665
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: glyphs().barEmpty.repeat(width - filled) })
|
|
1629
2666
|
] });
|
|
1630
2667
|
}
|
|
1631
2668
|
function metricValueText(m) {
|
|
@@ -1647,20 +2684,76 @@ import { Box as Box2, Text as Text2 } from "ink";
|
|
|
1647
2684
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1648
2685
|
var GAP = 2;
|
|
1649
2686
|
var MIN_CARD = 56;
|
|
1650
|
-
|
|
2687
|
+
var MIN_CARD_DENSE = 44;
|
|
2688
|
+
var CARD_H = { full: 14, compact: 12, mini: 8 };
|
|
2689
|
+
var VARIANT_ORDER = ["full", "compact", "mini"];
|
|
2690
|
+
var INDICATOR_ROWS = 1;
|
|
2691
|
+
var MAX_SINGLE_CARD = Math.round(MIN_CARD * 1.6);
|
|
2692
|
+
function chooseLayout(content, budget, n, single, cols) {
|
|
2693
|
+
if (n <= 0) return { ncols: 1, variant: "mini", cardsPerPage: 1, pageCount: 1 };
|
|
2694
|
+
const gridHeight = (rows, H2) => rows * H2 + Math.max(0, rows - 1);
|
|
2695
|
+
const colCap = single ? 1 : cols >= 3 * MIN_CARD_DENSE + 2 * GAP ? 3 : cols >= 2 * MIN_CARD + GAP ? 2 : 1;
|
|
2696
|
+
const maxCols = Math.max(1, Math.min(colCap, n));
|
|
2697
|
+
const cardWidthAt = (nc) => nc <= 1 ? content : Math.floor((content - GAP * (nc - 1)) / nc);
|
|
2698
|
+
const minWidthAt = (nc) => nc >= 3 ? MIN_CARD_DENSE : MIN_CARD;
|
|
2699
|
+
for (const variant of VARIANT_ORDER) {
|
|
2700
|
+
for (let nc = maxCols; nc >= 1; nc--) {
|
|
2701
|
+
if (nc > 1 && cardWidthAt(nc) < minWidthAt(nc)) continue;
|
|
2702
|
+
const rows = Math.ceil(n / nc);
|
|
2703
|
+
if (gridHeight(rows, CARD_H[variant]) <= budget) {
|
|
2704
|
+
return { ncols: nc, variant, cardsPerPage: n, pageCount: 1 };
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
let ncols = 1;
|
|
2709
|
+
for (let nc = maxCols; nc >= 1; nc--) {
|
|
2710
|
+
if (nc === 1 || cardWidthAt(nc) >= minWidthAt(nc)) {
|
|
2711
|
+
ncols = nc;
|
|
2712
|
+
break;
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
const H = CARD_H.mini;
|
|
2716
|
+
const fitBudget = budget - INDICATOR_ROWS;
|
|
2717
|
+
const rowsThatFit = Math.max(1, Math.floor((fitBudget + 1) / (H + 1)));
|
|
2718
|
+
const cardsPerPage = Math.max(1, rowsThatFit * ncols);
|
|
2719
|
+
const pageCount = Math.max(1, Math.ceil(n / cardsPerPage));
|
|
2720
|
+
return { ncols, variant: "mini", cardsPerPage, pageCount };
|
|
2721
|
+
}
|
|
2722
|
+
function DashboardView({ groups, stats, cols, budget, focusId, layout, page = 0 }) {
|
|
1651
2723
|
if (groups.length === 0) {
|
|
1652
|
-
return /* @__PURE__ */
|
|
2724
|
+
return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
2725
|
+
"No providers enabled ",
|
|
2726
|
+
glyphs().emDash,
|
|
2727
|
+
" press s to pick providers."
|
|
2728
|
+
] });
|
|
1653
2729
|
}
|
|
1654
2730
|
let shown = groups;
|
|
1655
2731
|
if (layout === "single" && focusId === null) shown = groups.slice(0, 1);
|
|
1656
|
-
const content = Math.max(MIN_CARD, cols - 4);
|
|
1657
2732
|
const single = focusId !== null || layout === "single";
|
|
1658
|
-
const
|
|
1659
|
-
const ncols
|
|
1660
|
-
|
|
1661
|
-
|
|
2733
|
+
const content = Math.max(MIN_CARD, cols - 4);
|
|
2734
|
+
const { ncols, variant, cardsPerPage, pageCount } = chooseLayout(content, budget, shown.length, single, cols);
|
|
2735
|
+
let cardW = ncols <= 1 ? content : Math.floor((content - GAP * (ncols - 1)) / ncols);
|
|
2736
|
+
if (ncols === 1 && cardW > MAX_SINGLE_CARD) cardW = MAX_SINGLE_CARD;
|
|
2737
|
+
const pg = pageCount > 1 ? (page % pageCount + pageCount) % pageCount : 0;
|
|
2738
|
+
const visible = pageCount > 1 ? shown.slice(pg * cardsPerPage, pg * cardsPerPage + cardsPerPage) : shown;
|
|
2739
|
+
return /* @__PURE__ */ jsxs2(Box2, { height: budget, flexDirection: "column", overflow: "hidden", children: [
|
|
2740
|
+
/* @__PURE__ */ jsx2(Box2, { width: content, flexWrap: "wrap", columnGap: GAP, rowGap: 1, children: visible.map((g) => /* @__PURE__ */ jsx2(Box2, { flexShrink: 0, children: /* @__PURE__ */ jsx2(ProviderCard, { provider: g.provider, accounts: g.accounts, stats, width: cardW, variant }) }, g.provider)) }),
|
|
2741
|
+
pageCount > 1 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
2742
|
+
" ",
|
|
2743
|
+
glyphs().middot,
|
|
2744
|
+
" page ",
|
|
2745
|
+
pg + 1,
|
|
2746
|
+
"/",
|
|
2747
|
+
pageCount,
|
|
2748
|
+
" ",
|
|
2749
|
+
glyphs().middot,
|
|
2750
|
+
" scroll ",
|
|
2751
|
+
glyphs().arrowU,
|
|
2752
|
+
glyphs().arrowD
|
|
2753
|
+
] })
|
|
2754
|
+
] });
|
|
1662
2755
|
}
|
|
1663
|
-
function ProviderCard({ provider, accounts, stats, width }) {
|
|
2756
|
+
function ProviderCard({ provider, accounts, stats, width, variant }) {
|
|
1664
2757
|
const meta = PROVIDERS[provider];
|
|
1665
2758
|
const items = accounts.map((a) => ({ account: a, s: stats.get(a.id) }));
|
|
1666
2759
|
const dashboards = items.map((i) => i.s?.dashboard).filter((d) => !!d);
|
|
@@ -1670,9 +2763,14 @@ function ProviderCard({ provider, accounts, stats, width }) {
|
|
|
1670
2763
|
const inner = width - 4;
|
|
1671
2764
|
const barW = Math.max(10, Math.min(46, inner - 20));
|
|
1672
2765
|
const hasSpark = !!agg && agg.series.some((v) => v > 0);
|
|
1673
|
-
|
|
2766
|
+
const showBars = variant !== "mini";
|
|
2767
|
+
const showSpark = variant === "full";
|
|
2768
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width, borderStyle: glyphs().border, borderColor: meta.color, paddingX: 1, children: [
|
|
1674
2769
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1675
|
-
/* @__PURE__ */
|
|
2770
|
+
/* @__PURE__ */ jsxs2(Text2, { color: meta.color, children: [
|
|
2771
|
+
glyphs().dot,
|
|
2772
|
+
" "
|
|
2773
|
+
] }),
|
|
1676
2774
|
/* @__PURE__ */ jsx2(Text2, { bold: true, color: meta.color, children: meta.name }),
|
|
1677
2775
|
/* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
|
|
1678
2776
|
plan && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: plan })
|
|
@@ -1685,17 +2783,21 @@ function ProviderCard({ provider, accounts, stats, width }) {
|
|
|
1685
2783
|
/* @__PURE__ */ jsx2(KpiLine, { agg })
|
|
1686
2784
|
] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1687
2785
|
/* @__PURE__ */ jsx2(Box2, { height: 1 }),
|
|
1688
|
-
/* @__PURE__ */
|
|
2786
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
2787
|
+
"Fetching usage",
|
|
2788
|
+
glyphs().ellipsis
|
|
2789
|
+
] })
|
|
1689
2790
|
] })),
|
|
1690
|
-
meta.hasBilling && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2791
|
+
meta.hasBilling && showBars && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1691
2792
|
meta.hasUsage && /* @__PURE__ */ jsx2(Rule, { inner }),
|
|
1692
2793
|
/* @__PURE__ */ jsx2(LimitsBlock, { items, barW })
|
|
1693
2794
|
] }),
|
|
1694
|
-
|
|
2795
|
+
meta.hasBilling && !showBars && !meta.hasUsage && /* @__PURE__ */ jsx2(CompactBilling, { items }),
|
|
2796
|
+
hasSpark && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1695
2797
|
/* @__PURE__ */ jsx2(Rule, { inner }),
|
|
1696
2798
|
/* @__PURE__ */ jsx2(SparkFooter, { series: agg.series, month: agg.month.cost, color: meta.color })
|
|
1697
2799
|
] }),
|
|
1698
|
-
!meta.hasUsage && activity && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2800
|
+
!meta.hasUsage && activity && showSpark && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1699
2801
|
/* @__PURE__ */ jsx2(Rule, { inner }),
|
|
1700
2802
|
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1701
2803
|
/* @__PURE__ */ jsx2(Box2, { width: 4, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "14d" }) }),
|
|
@@ -1703,14 +2805,29 @@ function ProviderCard({ provider, accounts, stats, width }) {
|
|
|
1703
2805
|
/* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: activity.summary }) })
|
|
1704
2806
|
] })
|
|
1705
2807
|
] }),
|
|
1706
|
-
!meta.hasUsage && !activity && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
2808
|
+
!meta.hasUsage && !activity && variant === "full" && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1707
2809
|
/* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
|
|
1708
|
-
/* @__PURE__ */
|
|
2810
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
2811
|
+
"Billing only ",
|
|
2812
|
+
glyphs().emDash,
|
|
2813
|
+
" no local history"
|
|
2814
|
+
] })
|
|
1709
2815
|
] })
|
|
1710
2816
|
] });
|
|
1711
2817
|
}
|
|
2818
|
+
function CompactBilling({ items }) {
|
|
2819
|
+
const billing = items.map((i) => i.s?.billing).find(Boolean);
|
|
2820
|
+
if (!billing) return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
2821
|
+
"Fetching",
|
|
2822
|
+
glyphs().ellipsis
|
|
2823
|
+
] });
|
|
2824
|
+
if (billing.error) return /* @__PURE__ */ jsx2(Text2, { color: "red", children: billing.error });
|
|
2825
|
+
const m = billing.metrics[0];
|
|
2826
|
+
if (!m) return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" });
|
|
2827
|
+
return /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: metricValueText(m) });
|
|
2828
|
+
}
|
|
1712
2829
|
function Rule({ inner }) {
|
|
1713
|
-
return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children:
|
|
2830
|
+
return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: glyphs().rule.repeat(Math.max(0, inner)) });
|
|
1714
2831
|
}
|
|
1715
2832
|
function SummaryRow({ label, s }) {
|
|
1716
2833
|
const cachedPct = s.tokens > 0 ? Math.round(s.cacheRead / s.tokens * 100) : 0;
|
|
@@ -1755,10 +2872,16 @@ function LimitsBlock({ items, barW }) {
|
|
|
1755
2872
|
const billing = s?.billing;
|
|
1756
2873
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: showName && idx > 0 ? 1 : 0, children: [
|
|
1757
2874
|
showName && /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1758
|
-
/* @__PURE__ */
|
|
2875
|
+
/* @__PURE__ */ jsxs2(Text2, { color: account.color, children: [
|
|
2876
|
+
glyphs().dot,
|
|
2877
|
+
" "
|
|
2878
|
+
] }),
|
|
1759
2879
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: truncateName(account.name, 22) })
|
|
1760
2880
|
] }),
|
|
1761
|
-
!billing ? /* @__PURE__ */
|
|
2881
|
+
!billing ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
2882
|
+
"Fetching",
|
|
2883
|
+
glyphs().ellipsis
|
|
2884
|
+
] }) : billing.error ? /* @__PURE__ */ jsx2(Text2, { color: "red", children: billing.error }) : billing.metrics.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No data" }) : billing.metrics.map((m, i) => /* @__PURE__ */ jsx2(MetricRow, { m, color: account.color, barW }, `${m.label}${i}`))
|
|
1762
2885
|
] }, account.id);
|
|
1763
2886
|
}) });
|
|
1764
2887
|
}
|
|
@@ -1812,12 +2935,12 @@ function aggregate(list) {
|
|
|
1812
2935
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
1813
2936
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1814
2937
|
var MONTHS = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
1815
|
-
function TableProviderBar({ providers, active, onSelect }) {
|
|
2938
|
+
function TableProviderBar({ providers, active: active2, onSelect }) {
|
|
1816
2939
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1817
2940
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "provider " }),
|
|
1818
2941
|
providers.map((p) => {
|
|
1819
2942
|
const meta = PROVIDERS[p];
|
|
1820
|
-
return /* @__PURE__ */ jsx3(ClickableBox, { onClick: () => onSelect(p), marginRight: 1, children: p ===
|
|
2943
|
+
return /* @__PURE__ */ jsx3(ClickableBox, { onClick: () => onSelect(p), marginRight: 1, children: p === active2 ? /* @__PURE__ */ jsxs3(Text3, { bold: true, color: meta.color, inverse: true, children: [
|
|
1821
2944
|
" ",
|
|
1822
2945
|
meta.name,
|
|
1823
2946
|
" "
|
|
@@ -1842,15 +2965,23 @@ function ControlBar({ views, period, sort, search, searching, showPeriod }) {
|
|
|
1842
2965
|
] }),
|
|
1843
2966
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "sort " }),
|
|
1844
2967
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "magenta", children: sort }),
|
|
1845
|
-
/* @__PURE__ */
|
|
2968
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
2969
|
+
" o cycle ",
|
|
2970
|
+
glyphs().middot,
|
|
2971
|
+
" "
|
|
2972
|
+
] }),
|
|
1846
2973
|
searching ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
1847
2974
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "/" }),
|
|
1848
2975
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: search }),
|
|
1849
|
-
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children:
|
|
2976
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: glyphs().vbar })
|
|
1850
2977
|
] }) : search ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
1851
2978
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "filter " }),
|
|
1852
2979
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "green", children: search }),
|
|
1853
|
-
/* @__PURE__ */
|
|
2980
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
2981
|
+
" (/ edit ",
|
|
2982
|
+
glyphs().middot,
|
|
2983
|
+
" esc clear)"
|
|
2984
|
+
] })
|
|
1854
2985
|
] }) : /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "/ filter" })
|
|
1855
2986
|
] });
|
|
1856
2987
|
}
|
|
@@ -1887,14 +3018,14 @@ function TokenTable({ rows, cursor, expanded, maxRows, cols, onRowClick }) {
|
|
|
1887
3018
|
W.total > 0 && /* @__PURE__ */ jsx3(Text3, { bold: true, children: col("Total", W.total) }),
|
|
1888
3019
|
/* @__PURE__ */ jsx3(Text3, { bold: true, children: col("Cost", W.cost) })
|
|
1889
3020
|
] }),
|
|
1890
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children:
|
|
3021
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
|
|
1891
3022
|
visible.map((r, vi) => {
|
|
1892
3023
|
const idx = scrollStart + vi;
|
|
1893
3024
|
const selected = idx === clampedCursor;
|
|
1894
3025
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
1895
3026
|
/* @__PURE__ */ jsx3(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs3(Text3, { inverse: selected, children: [
|
|
1896
3027
|
/* @__PURE__ */ jsxs3(Text3, { color: selected ? void 0 : "cyan", children: [
|
|
1897
|
-
selected ?
|
|
3028
|
+
selected ? `${glyphs().caretR} ` : " ",
|
|
1898
3029
|
col(fmtLabel(r.label), W.label, "left")
|
|
1899
3030
|
] }),
|
|
1900
3031
|
/* @__PURE__ */ jsx3(Text3, { dimColor: !selected, children: col(r.models.join(", "), W.models, "left") }),
|
|
@@ -1908,7 +3039,7 @@ function TokenTable({ rows, cursor, expanded, maxRows, cols, onRowClick }) {
|
|
|
1908
3039
|
idx === expanded && /* @__PURE__ */ jsx3(RowDetail, { row: r, indent: W.label + 2 })
|
|
1909
3040
|
] }, r.label);
|
|
1910
3041
|
}),
|
|
1911
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children:
|
|
3042
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: glyphs().rule.repeat(lineW + 2) }),
|
|
1912
3043
|
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1913
3044
|
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: "greenBright", children: [
|
|
1914
3045
|
" ",
|
|
@@ -1924,7 +3055,17 @@ function TokenTable({ rows, cursor, expanded, maxRows, cols, onRowClick }) {
|
|
|
1924
3055
|
] }),
|
|
1925
3056
|
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
1926
3057
|
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1927
|
-
|
|
3058
|
+
glyphs().arrowU,
|
|
3059
|
+
glyphs().arrowD,
|
|
3060
|
+
" navigate ",
|
|
3061
|
+
glyphs().middot,
|
|
3062
|
+
" Enter detail ",
|
|
3063
|
+
glyphs().middot,
|
|
3064
|
+
" o sort ",
|
|
3065
|
+
glyphs().middot,
|
|
3066
|
+
" g/G top/bottom ",
|
|
3067
|
+
glyphs().middot,
|
|
3068
|
+
" ",
|
|
1928
3069
|
clampedCursor + 1,
|
|
1929
3070
|
"/",
|
|
1930
3071
|
rows.length
|
|
@@ -1934,7 +3075,7 @@ function TokenTable({ rows, cursor, expanded, maxRows, cols, onRowClick }) {
|
|
|
1934
3075
|
function RowDetail({ row, indent }) {
|
|
1935
3076
|
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", paddingLeft: indent, children: row.breakdown.map((m, i) => /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1936
3077
|
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1937
|
-
i === row.breakdown.length - 1 ?
|
|
3078
|
+
i === row.breakdown.length - 1 ? glyphs().treeEnd : glyphs().treeMid,
|
|
1938
3079
|
" "
|
|
1939
3080
|
] }),
|
|
1940
3081
|
/* @__PURE__ */ jsx3(Text3, { bold: true, children: col(m.name, 16, "left") }),
|
|
@@ -1975,14 +3116,14 @@ function CursorSpendTable({ rows, cursor, maxRows, onRowClick }) {
|
|
|
1975
3116
|
/* @__PURE__ */ jsx3(Text3, { bold: true, children: col("Amount", W.amount) }),
|
|
1976
3117
|
/* @__PURE__ */ jsx3(Text3, { bold: true, children: col("Share", W.share) })
|
|
1977
3118
|
] }),
|
|
1978
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children:
|
|
3119
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: glyphs().rule.repeat(W.model + W.cost + W.amount + W.share + 2) }),
|
|
1979
3120
|
visible.map((r, vi) => {
|
|
1980
3121
|
const idx = scrollStart + vi;
|
|
1981
3122
|
const selected = idx === clamped;
|
|
1982
3123
|
const share = total > 0 ? r.usd / total * 100 : 0;
|
|
1983
3124
|
return /* @__PURE__ */ jsx3(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs3(Text3, { inverse: selected, children: [
|
|
1984
3125
|
/* @__PURE__ */ jsxs3(Text3, { color: selected ? void 0 : "magenta", children: [
|
|
1985
|
-
selected ?
|
|
3126
|
+
selected ? `${glyphs().caretR} ` : " ",
|
|
1986
3127
|
col(r.name, W.model, "left")
|
|
1987
3128
|
] }),
|
|
1988
3129
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: selected ? void 0 : "yellow", children: col(currency(r.usd), W.cost) }),
|
|
@@ -1990,7 +3131,7 @@ function CursorSpendTable({ rows, cursor, maxRows, onRowClick }) {
|
|
|
1990
3131
|
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: col(share.toFixed(1) + "%", W.share) })
|
|
1991
3132
|
] }) }, r.name);
|
|
1992
3133
|
}),
|
|
1993
|
-
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children:
|
|
3134
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: glyphs().rule.repeat(W.model + W.cost + W.amount + W.share + 2) }),
|
|
1994
3135
|
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1995
3136
|
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: "greenBright", children: [
|
|
1996
3137
|
" ",
|
|
@@ -2002,7 +3143,11 @@ function CursorSpendTable({ rows, cursor, maxRows, onRowClick }) {
|
|
|
2002
3143
|
] }),
|
|
2003
3144
|
/* @__PURE__ */ jsx3(Box3, { height: 1 }),
|
|
2004
3145
|
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
2005
|
-
"local spend by model (composerData)
|
|
3146
|
+
"local spend by model (composerData) ",
|
|
3147
|
+
glyphs().middot,
|
|
3148
|
+
" est. API-equivalent ",
|
|
3149
|
+
glyphs().middot,
|
|
3150
|
+
" ",
|
|
2006
3151
|
clamped + 1,
|
|
2007
3152
|
"/",
|
|
2008
3153
|
rows.length
|
|
@@ -2019,43 +3164,174 @@ function fmtLabel(label) {
|
|
|
2019
3164
|
// src/ui/onboarding.tsx
|
|
2020
3165
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
2021
3166
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2022
|
-
function Onboarding({ items, cursor, onToggle, onConfirm }) {
|
|
3167
|
+
function Onboarding({ items, cursor, onToggle, onConfirm, heading = "Welcome to tokmon", subheading = "Pick the tools you want to track. You can change this anytime in settings." }) {
|
|
2023
3168
|
const anyEnabled = items.some((it) => it.enabled);
|
|
2024
3169
|
const startIdx = items.length;
|
|
2025
3170
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, children: [
|
|
2026
|
-
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "greenBright", children:
|
|
2027
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children:
|
|
3171
|
+
/* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "greenBright", children: heading }) }),
|
|
3172
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: subheading }),
|
|
2028
3173
|
/* @__PURE__ */ jsx4(Box4, { height: 1 }),
|
|
2029
3174
|
items.map((it, i) => {
|
|
2030
3175
|
const selected = cursor === i;
|
|
2031
|
-
const box = it.enabled ?
|
|
3176
|
+
const box = it.enabled ? `[${glyphs().check}]` : "[ ]";
|
|
2032
3177
|
return /* @__PURE__ */ jsxs4(ClickableBox, { onClick: () => onToggle(i), children: [
|
|
2033
3178
|
/* @__PURE__ */ jsxs4(Text4, { color: selected ? "green" : void 0, children: [
|
|
2034
|
-
selected ?
|
|
3179
|
+
selected ? glyphs().caretR : " ",
|
|
2035
3180
|
" "
|
|
2036
3181
|
] }),
|
|
2037
3182
|
/* @__PURE__ */ jsx4(Text4, { bold: it.enabled, color: it.enabled ? it.color : void 0, dimColor: !it.enabled, children: box }),
|
|
2038
|
-
/* @__PURE__ */
|
|
2039
|
-
|
|
3183
|
+
/* @__PURE__ */ jsxs4(Text4, { color: it.color, children: [
|
|
3184
|
+
" ",
|
|
3185
|
+
glyphs().dot,
|
|
3186
|
+
" "
|
|
3187
|
+
] }),
|
|
3188
|
+
/* @__PURE__ */ jsx4(Box4, { width: 13, flexShrink: 0, children: /* @__PURE__ */ jsx4(Text4, { bold: selected, dimColor: !it.detected && !it.enabled, wrap: "truncate", children: it.name }) }),
|
|
2040
3189
|
it.detected ? /* @__PURE__ */ jsx4(Text4, { color: "green", dimColor: true, children: "installed" }) : it.enabled ? /* @__PURE__ */ jsx4(Text4, { color: "yellow", dimColor: true, children: "manual" }) : /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "not found" })
|
|
2041
3190
|
] }, it.id);
|
|
2042
3191
|
}),
|
|
2043
3192
|
/* @__PURE__ */ jsx4(Box4, { height: 1 }),
|
|
2044
3193
|
/* @__PURE__ */ jsxs4(ClickableBox, { onClick: onConfirm, children: [
|
|
2045
3194
|
/* @__PURE__ */ jsxs4(Text4, { color: cursor === startIdx ? "green" : void 0, children: [
|
|
2046
|
-
cursor === startIdx ?
|
|
3195
|
+
cursor === startIdx ? glyphs().caretR : " ",
|
|
2047
3196
|
" "
|
|
2048
3197
|
] }),
|
|
2049
|
-
/* @__PURE__ */ jsx4(Text4, { bold: true, color: anyEnabled ? "greenBright" : void 0, dimColor: !anyEnabled, children: anyEnabled ?
|
|
3198
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: anyEnabled ? "greenBright" : void 0, dimColor: !anyEnabled, children: anyEnabled ? `${glyphs().play} Start tokmon` : `${glyphs().play} Start (nothing selected)` })
|
|
2050
3199
|
] }),
|
|
2051
3200
|
/* @__PURE__ */ jsx4(Box4, { height: 1 }),
|
|
2052
|
-
/* @__PURE__ */
|
|
3201
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
3202
|
+
glyphs().arrowU,
|
|
3203
|
+
glyphs().arrowD,
|
|
3204
|
+
" move ",
|
|
3205
|
+
glyphs().middot,
|
|
3206
|
+
" space toggle ",
|
|
3207
|
+
glyphs().middot,
|
|
3208
|
+
" enter start ",
|
|
3209
|
+
glyphs().middot,
|
|
3210
|
+
" q quit"
|
|
3211
|
+
] })
|
|
2053
3212
|
] });
|
|
2054
3213
|
}
|
|
2055
3214
|
|
|
2056
|
-
// src/ui/
|
|
3215
|
+
// src/ui/loading.tsx
|
|
3216
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
2057
3217
|
import { Box as Box5, Text as Text5 } from "ink";
|
|
2058
|
-
import {
|
|
3218
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3219
|
+
function accountReady(s, providerId) {
|
|
3220
|
+
if (!s) return false;
|
|
3221
|
+
const p = PROVIDERS[providerId];
|
|
3222
|
+
if (p.hasBilling && s.billing?.error) return true;
|
|
3223
|
+
if (p.hasUsage && !s.dashboard) return false;
|
|
3224
|
+
if (p.hasBilling && !s.billing) return false;
|
|
3225
|
+
return true;
|
|
3226
|
+
}
|
|
3227
|
+
function groupTodayCost(items) {
|
|
3228
|
+
return items.reduce((sum, s) => {
|
|
3229
|
+
const d = s?.dashboard;
|
|
3230
|
+
return sum + (d?.today.cost ?? 0);
|
|
3231
|
+
}, 0);
|
|
3232
|
+
}
|
|
3233
|
+
function headlineFor(group, items) {
|
|
3234
|
+
const meta = PROVIDERS[group.provider];
|
|
3235
|
+
if (meta.hasUsage) return `${currency(groupTodayCost(items))} today`;
|
|
3236
|
+
const billing = items.map((s) => s?.billing).find(Boolean);
|
|
3237
|
+
if (!billing) return "no data";
|
|
3238
|
+
if (billing.error) return billing.error;
|
|
3239
|
+
const m = billing.metrics[0];
|
|
3240
|
+
if (m) return metricValueText(m);
|
|
3241
|
+
return billing.plan ?? "no data";
|
|
3242
|
+
}
|
|
3243
|
+
var STAGGER_FRAMES = 2;
|
|
3244
|
+
function LoadingView({ groups, stats, cols, rows }) {
|
|
3245
|
+
const sp = glyphs().spinner;
|
|
3246
|
+
const [frame, setFrame] = useState2(0);
|
|
3247
|
+
useEffect2(() => {
|
|
3248
|
+
const id = setInterval(() => setFrame((f) => f + 1), 80);
|
|
3249
|
+
return () => clearInterval(id);
|
|
3250
|
+
}, []);
|
|
3251
|
+
const nameW = Math.min(13, groups.reduce((w, g) => Math.max(w, PROVIDERS[g.provider].name.length), 0));
|
|
3252
|
+
const readyCount = groups.filter((g) => g.accounts.every((a) => accountReady(stats.get(a.id), g.provider))).length;
|
|
3253
|
+
const maxRows = Math.max(1, rows - 7);
|
|
3254
|
+
const visible = groups.slice(0, maxRows);
|
|
3255
|
+
const hidden = groups.length - visible.length;
|
|
3256
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
3257
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: true, color: "greenBright", children: [
|
|
3258
|
+
glyphs().dotSel,
|
|
3259
|
+
" tokmon"
|
|
3260
|
+
] }),
|
|
3261
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
|
|
3262
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
3263
|
+
"Detecting installed tools",
|
|
3264
|
+
glyphs().ellipsis
|
|
3265
|
+
] }),
|
|
3266
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
3267
|
+
" ",
|
|
3268
|
+
groups.length,
|
|
3269
|
+
" found"
|
|
3270
|
+
] })
|
|
3271
|
+
] }),
|
|
3272
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, flexDirection: "column", children: [
|
|
3273
|
+
visible.map((g, i) => {
|
|
3274
|
+
const meta = PROVIDERS[g.provider];
|
|
3275
|
+
const items = g.accounts.map((a) => stats.get(a.id));
|
|
3276
|
+
const ready = g.accounts.every((a) => accountReady(stats.get(a.id), g.provider));
|
|
3277
|
+
const errored = items.some((s) => !!s?.billing?.error);
|
|
3278
|
+
const revealed = frame >= i * STAGGER_FRAMES;
|
|
3279
|
+
const name = truncateName(meta.name, nameW);
|
|
3280
|
+
const namePad = " ".repeat(Math.max(0, nameW - name.length));
|
|
3281
|
+
if (!revealed) {
|
|
3282
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
3283
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
3284
|
+
glyphs().dot,
|
|
3285
|
+
" "
|
|
3286
|
+
] }),
|
|
3287
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
3288
|
+
name,
|
|
3289
|
+
namePad
|
|
3290
|
+
] })
|
|
3291
|
+
] }, g.provider);
|
|
3292
|
+
}
|
|
3293
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
3294
|
+
/* @__PURE__ */ jsxs5(Text5, { color: meta.color, children: [
|
|
3295
|
+
glyphs().dot,
|
|
3296
|
+
" "
|
|
3297
|
+
] }),
|
|
3298
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: meta.color, children: name }),
|
|
3299
|
+
/* @__PURE__ */ jsx5(Text5, { children: namePad }),
|
|
3300
|
+
/* @__PURE__ */ jsx5(Text5, { children: " " }),
|
|
3301
|
+
errored ? /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
|
|
3302
|
+
glyphs().warn,
|
|
3303
|
+
" "
|
|
3304
|
+
] }) : ready ? /* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
|
|
3305
|
+
glyphs().check,
|
|
3306
|
+
" "
|
|
3307
|
+
] }) : /* @__PURE__ */ jsxs5(Text5, { color: "green", children: [
|
|
3308
|
+
sp[frame % sp.length],
|
|
3309
|
+
" "
|
|
3310
|
+
] }),
|
|
3311
|
+
errored ? /* @__PURE__ */ jsx5(Text5, { color: "red", children: headlineFor(g, items) }) : ready ? /* @__PURE__ */ jsx5(Text5, { children: headlineFor(g, items) }) : /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
3312
|
+
"loading",
|
|
3313
|
+
glyphs().ellipsis
|
|
3314
|
+
] })
|
|
3315
|
+
] }, g.provider);
|
|
3316
|
+
}),
|
|
3317
|
+
hidden > 0 && /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
3318
|
+
"+",
|
|
3319
|
+
hidden,
|
|
3320
|
+
" more"
|
|
3321
|
+
] })
|
|
3322
|
+
] }),
|
|
3323
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
3324
|
+
"loading ",
|
|
3325
|
+
readyCount,
|
|
3326
|
+
" / ",
|
|
3327
|
+
groups.length
|
|
3328
|
+
] }) })
|
|
3329
|
+
] });
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
// src/ui/settings.tsx
|
|
3333
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
3334
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2059
3335
|
var GENERAL_ROWS = 6;
|
|
2060
3336
|
var PROVIDER_ROWS_START = GENERAL_ROWS;
|
|
2061
3337
|
var ACCOUNT_ROWS_START = GENERAL_ROWS + PROVIDER_ORDER.length;
|
|
@@ -2080,134 +3356,191 @@ function SettingsView({
|
|
|
2080
3356
|
accountForm,
|
|
2081
3357
|
activeAccountId
|
|
2082
3358
|
}) {
|
|
2083
|
-
if (accountForm) return /* @__PURE__ */
|
|
3359
|
+
if (accountForm) return /* @__PURE__ */ jsx6(AccountFormView, { form: accountForm, accounts: config2.accounts });
|
|
2084
3360
|
const editingTz = tzEdit !== null;
|
|
2085
3361
|
const tzDisplay = config2.timezone === null ? `System (${resolvedTz})` : config2.timezone;
|
|
2086
|
-
return /* @__PURE__ */
|
|
2087
|
-
/* @__PURE__ */
|
|
2088
|
-
/* @__PURE__ */
|
|
2089
|
-
/* @__PURE__ */
|
|
2090
|
-
/* @__PURE__ */
|
|
2091
|
-
/* @__PURE__ */
|
|
2092
|
-
/* @__PURE__ */
|
|
2093
|
-
|
|
3362
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
|
|
3363
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Settings" }),
|
|
3364
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: configLocation() }),
|
|
3365
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3366
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "General" }),
|
|
3367
|
+
/* @__PURE__ */ jsxs6(Row, { cursor, idx: 0, label: "Refresh interval", children: [
|
|
3368
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3369
|
+
glyphs().caretL,
|
|
2094
3370
|
" "
|
|
2095
3371
|
] }),
|
|
2096
|
-
/* @__PURE__ */
|
|
3372
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: "yellow", children: [
|
|
2097
3373
|
config2.interval,
|
|
2098
3374
|
"s"
|
|
2099
3375
|
] }),
|
|
2100
|
-
/* @__PURE__ */
|
|
3376
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2101
3377
|
" ",
|
|
2102
|
-
|
|
3378
|
+
glyphs().caretR
|
|
2103
3379
|
] })
|
|
2104
3380
|
] }),
|
|
2105
|
-
/* @__PURE__ */
|
|
2106
|
-
/* @__PURE__ */
|
|
2107
|
-
|
|
3381
|
+
/* @__PURE__ */ jsxs6(Row, { cursor, idx: 1, label: "Billing poll", children: [
|
|
3382
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3383
|
+
glyphs().caretL,
|
|
2108
3384
|
" "
|
|
2109
3385
|
] }),
|
|
2110
|
-
/* @__PURE__ */
|
|
3386
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, color: "yellow", children: [
|
|
2111
3387
|
config2.billingInterval,
|
|
2112
3388
|
"m"
|
|
2113
3389
|
] }),
|
|
2114
|
-
/* @__PURE__ */
|
|
3390
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2115
3391
|
" ",
|
|
2116
|
-
|
|
3392
|
+
glyphs().caretR
|
|
2117
3393
|
] })
|
|
2118
3394
|
] }),
|
|
2119
|
-
/* @__PURE__ */
|
|
2120
|
-
/* @__PURE__ */
|
|
2121
|
-
/* @__PURE__ */
|
|
2122
|
-
/* @__PURE__ */
|
|
2123
|
-
/* @__PURE__ */
|
|
2124
|
-
/* @__PURE__ */
|
|
2125
|
-
] }) : /* @__PURE__ */
|
|
2126
|
-
cursor === 3 && tzError && /* @__PURE__ */
|
|
3395
|
+
/* @__PURE__ */ jsx6(Row, { cursor, idx: 2, label: "Clear screen", children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: config2.clearScreen ? "green" : "red", children: config2.clearScreen ? "on" : "off" }) }),
|
|
3396
|
+
/* @__PURE__ */ jsx6(Row, { cursor, idx: 3, label: "Timezone", children: editingTz ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3397
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "[" }),
|
|
3398
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "cyan", children: tzEdit }),
|
|
3399
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "_" }),
|
|
3400
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "]" })
|
|
3401
|
+
] }) : /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: tzDisplay }) }),
|
|
3402
|
+
cursor === 3 && tzError && /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
2127
3403
|
" ",
|
|
2128
3404
|
tzError
|
|
2129
3405
|
] }),
|
|
2130
|
-
/* @__PURE__ */
|
|
2131
|
-
/* @__PURE__ */
|
|
2132
|
-
|
|
3406
|
+
/* @__PURE__ */ jsxs6(Row, { cursor, idx: 4, label: "Dashboard", children: [
|
|
3407
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3408
|
+
glyphs().caretL,
|
|
2133
3409
|
" "
|
|
2134
3410
|
] }),
|
|
2135
|
-
/* @__PURE__ */
|
|
2136
|
-
/* @__PURE__ */
|
|
3411
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: config2.dashboardLayout === "grid" ? "grid (all)" : "single (cycle)" }),
|
|
3412
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2137
3413
|
" ",
|
|
2138
|
-
|
|
3414
|
+
glyphs().caretR
|
|
2139
3415
|
] })
|
|
2140
3416
|
] }),
|
|
2141
|
-
/* @__PURE__ */
|
|
2142
|
-
/* @__PURE__ */
|
|
2143
|
-
|
|
3417
|
+
/* @__PURE__ */ jsxs6(Row, { cursor, idx: 5, label: "Default focus", children: [
|
|
3418
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3419
|
+
glyphs().caretL,
|
|
2144
3420
|
" "
|
|
2145
3421
|
] }),
|
|
2146
|
-
/* @__PURE__ */
|
|
2147
|
-
/* @__PURE__ */
|
|
3422
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: config2.defaultFocus === "all" ? "All" : "Last account" }),
|
|
3423
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2148
3424
|
" ",
|
|
2149
|
-
|
|
3425
|
+
glyphs().caretR
|
|
2150
3426
|
] })
|
|
2151
3427
|
] }),
|
|
2152
|
-
/* @__PURE__ */
|
|
2153
|
-
/* @__PURE__ */
|
|
3428
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3429
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Providers" }),
|
|
2154
3430
|
PROVIDER_ORDER.map((pid, i) => {
|
|
2155
3431
|
const idx = PROVIDER_ROWS_START + i;
|
|
2156
3432
|
const selected = cursor === idx;
|
|
2157
3433
|
const enabled = !config2.disabledProviders.includes(pid);
|
|
2158
3434
|
const p = PROVIDERS[pid];
|
|
2159
|
-
return /* @__PURE__ */
|
|
2160
|
-
/* @__PURE__ */
|
|
2161
|
-
selected ?
|
|
3435
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3436
|
+
/* @__PURE__ */ jsxs6(Text6, { color: selected ? "green" : void 0, children: [
|
|
3437
|
+
selected ? glyphs().caretR : " ",
|
|
2162
3438
|
" "
|
|
2163
3439
|
] }),
|
|
2164
|
-
/* @__PURE__ */
|
|
2165
|
-
/* @__PURE__ */
|
|
2166
|
-
|
|
2167
|
-
|
|
3440
|
+
/* @__PURE__ */ jsx6(Text6, { bold: enabled, color: enabled ? p.color : void 0, dimColor: !enabled, children: enabled ? `[${glyphs().check}]` : "[ ]" }),
|
|
3441
|
+
/* @__PURE__ */ jsxs6(Text6, { color: p.color, children: [
|
|
3442
|
+
" ",
|
|
3443
|
+
glyphs().dot,
|
|
3444
|
+
" "
|
|
3445
|
+
] }),
|
|
3446
|
+
/* @__PURE__ */ jsx6(Box6, { width: 9, children: /* @__PURE__ */ jsx6(Text6, { bold: selected, children: p.name }) }),
|
|
3447
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: enabled ? "tracking" : "off" })
|
|
2168
3448
|
] }, pid);
|
|
2169
3449
|
}),
|
|
2170
|
-
/* @__PURE__ */
|
|
2171
|
-
/* @__PURE__ */
|
|
2172
|
-
config2.accounts.length === 0 && /* @__PURE__ */
|
|
3450
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3451
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, dimColor: true, children: "Accounts" }),
|
|
3452
|
+
config2.accounts.length === 0 && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3453
|
+
" none configured ",
|
|
3454
|
+
glyphs().emDash,
|
|
3455
|
+
" enabled providers track automatically"
|
|
3456
|
+
] }),
|
|
2173
3457
|
config2.accounts.map((acc, i) => {
|
|
2174
3458
|
const idx = ACCOUNT_ROWS_START + i;
|
|
2175
3459
|
const selected = cursor === idx;
|
|
2176
3460
|
const isActive = acc.id === activeAccountId;
|
|
2177
3461
|
const provider = PROVIDERS[acc.providerId];
|
|
2178
|
-
return /* @__PURE__ */
|
|
2179
|
-
/* @__PURE__ */
|
|
2180
|
-
selected ?
|
|
3462
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3463
|
+
/* @__PURE__ */ jsxs6(Text6, { color: selected ? "green" : void 0, children: [
|
|
3464
|
+
selected ? glyphs().caretR : " ",
|
|
2181
3465
|
" "
|
|
2182
3466
|
] }),
|
|
2183
|
-
/* @__PURE__ */
|
|
2184
|
-
isActive ?
|
|
3467
|
+
/* @__PURE__ */ jsxs6(Text6, { color: acc.color || provider.color, children: [
|
|
3468
|
+
isActive ? glyphs().dot : glyphs().radioOff,
|
|
2185
3469
|
" "
|
|
2186
3470
|
] }),
|
|
2187
|
-
/* @__PURE__ */
|
|
2188
|
-
/* @__PURE__ */
|
|
2189
|
-
/* @__PURE__ */
|
|
3471
|
+
/* @__PURE__ */ jsx6(Box6, { width: 16, children: /* @__PURE__ */ jsx6(Text6, { bold: true, children: truncateName(acc.name, 15) }) }),
|
|
3472
|
+
/* @__PURE__ */ jsx6(Box6, { width: 9, children: /* @__PURE__ */ jsx6(Text6, { color: provider.color, children: provider.name }) }),
|
|
3473
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: truncateName(acc.homeDir, 24) })
|
|
2190
3474
|
] }, acc.id);
|
|
2191
3475
|
}),
|
|
2192
|
-
/* @__PURE__ */
|
|
2193
|
-
/* @__PURE__ */
|
|
2194
|
-
cursor === ACCOUNT_ROWS_START + config2.accounts.length ?
|
|
3476
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3477
|
+
/* @__PURE__ */ jsxs6(Text6, { color: cursor === ACCOUNT_ROWS_START + config2.accounts.length ? "green" : void 0, children: [
|
|
3478
|
+
cursor === ACCOUNT_ROWS_START + config2.accounts.length ? glyphs().caretR : " ",
|
|
2195
3479
|
" "
|
|
2196
3480
|
] }),
|
|
2197
|
-
/* @__PURE__ */
|
|
2198
|
-
/* @__PURE__ */
|
|
3481
|
+
/* @__PURE__ */ jsx6(Text6, { color: "greenBright", children: "+ " }),
|
|
3482
|
+
/* @__PURE__ */ jsx6(Text6, { children: "Add account" })
|
|
2199
3483
|
] }),
|
|
2200
|
-
/* @__PURE__ */
|
|
2201
|
-
editingTz ? /* @__PURE__ */
|
|
3484
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3485
|
+
editingTz ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3486
|
+
"type IANA name (e.g. Europe/London) ",
|
|
3487
|
+
glyphs().middot,
|
|
3488
|
+
" empty = System ",
|
|
3489
|
+
glyphs().middot,
|
|
3490
|
+
" Enter save ",
|
|
3491
|
+
glyphs().middot,
|
|
3492
|
+
" Esc cancel"
|
|
3493
|
+
] }) : cursor >= PROVIDER_ROWS_START && cursor < ACCOUNT_ROWS_START ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3494
|
+
glyphs().arrowU,
|
|
3495
|
+
glyphs().arrowD,
|
|
3496
|
+
" select ",
|
|
3497
|
+
glyphs().middot,
|
|
3498
|
+
" space toggle provider ",
|
|
3499
|
+
glyphs().middot,
|
|
3500
|
+
" s/Esc close"
|
|
3501
|
+
] }) : cursor >= ACCOUNT_ROWS_START && cursor < ACCOUNT_ROWS_START + config2.accounts.length ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3502
|
+
glyphs().arrowU,
|
|
3503
|
+
glyphs().arrowD,
|
|
3504
|
+
" select ",
|
|
3505
|
+
glyphs().middot,
|
|
3506
|
+
" ",
|
|
3507
|
+
glyphs().shift,
|
|
3508
|
+
glyphs().arrowU,
|
|
3509
|
+
glyphs().arrowD,
|
|
3510
|
+
" reorder ",
|
|
3511
|
+
glyphs().middot,
|
|
3512
|
+
" Enter edit ",
|
|
3513
|
+
glyphs().middot,
|
|
3514
|
+
" space activate ",
|
|
3515
|
+
glyphs().middot,
|
|
3516
|
+
" d delete ",
|
|
3517
|
+
glyphs().middot,
|
|
3518
|
+
" s/Esc close"
|
|
3519
|
+
] }) : cursor === ACCOUNT_ROWS_START + config2.accounts.length ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3520
|
+
glyphs().arrowU,
|
|
3521
|
+
glyphs().arrowD,
|
|
3522
|
+
" select ",
|
|
3523
|
+
glyphs().middot,
|
|
3524
|
+
" Enter add account ",
|
|
3525
|
+
glyphs().middot,
|
|
3526
|
+
" s/Esc close"
|
|
3527
|
+
] }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3528
|
+
glyphs().arrowU,
|
|
3529
|
+
glyphs().arrowD,
|
|
3530
|
+
" select ",
|
|
3531
|
+
glyphs().arrowL,
|
|
3532
|
+
glyphs().arrowR,
|
|
3533
|
+
" adjust Enter edit s/Esc close"
|
|
3534
|
+
] })
|
|
2202
3535
|
] });
|
|
2203
3536
|
}
|
|
2204
3537
|
function Row({ cursor, idx, label, children }) {
|
|
2205
|
-
return /* @__PURE__ */
|
|
2206
|
-
/* @__PURE__ */
|
|
2207
|
-
cursor === idx ?
|
|
3538
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3539
|
+
/* @__PURE__ */ jsxs6(Text6, { color: cursor === idx ? "green" : void 0, children: [
|
|
3540
|
+
cursor === idx ? glyphs().caretR : " ",
|
|
2208
3541
|
" "
|
|
2209
3542
|
] }),
|
|
2210
|
-
/* @__PURE__ */
|
|
3543
|
+
/* @__PURE__ */ jsx6(Box6, { width: 20, children: /* @__PURE__ */ jsx6(Text6, { children: label }) }),
|
|
2211
3544
|
children
|
|
2212
3545
|
] });
|
|
2213
3546
|
}
|
|
@@ -2216,24 +3549,24 @@ function AccountFormView({ form, accounts }) {
|
|
|
2216
3549
|
const accent = form.color;
|
|
2217
3550
|
const stepIndex = { provider: 1, name: 2, homeDir: 3, color: 4 };
|
|
2218
3551
|
const step = stepIndex[form.field];
|
|
2219
|
-
return /* @__PURE__ */
|
|
2220
|
-
/* @__PURE__ */
|
|
2221
|
-
/* @__PURE__ */
|
|
2222
|
-
/* @__PURE__ */
|
|
3552
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginTop: 1, children: [
|
|
3553
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3554
|
+
/* @__PURE__ */ jsx6(Text6, { color: accent, bold: true, children: glyphs().vbar }),
|
|
3555
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
|
|
2223
3556
|
" ",
|
|
2224
3557
|
form.mode === "add" ? "NEW ACCOUNT" : "EDIT ACCOUNT"
|
|
2225
3558
|
] }),
|
|
2226
|
-
/* @__PURE__ */
|
|
3559
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2227
3560
|
" step ",
|
|
2228
3561
|
step,
|
|
2229
3562
|
" of 4"
|
|
2230
3563
|
] })
|
|
2231
3564
|
] }),
|
|
2232
|
-
/* @__PURE__ */
|
|
2233
|
-
/* @__PURE__ */
|
|
2234
|
-
/* @__PURE__ */
|
|
2235
|
-
/* @__PURE__ */
|
|
2236
|
-
/* @__PURE__ */
|
|
3565
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Stepper, { active: form.field, accent }) }),
|
|
3566
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", borderStyle: glyphs().border, borderColor: accent, paddingX: 2, paddingY: 1, children: [
|
|
3567
|
+
/* @__PURE__ */ jsx6(ProviderField, { value: form.providerId, focused: form.field === "provider" }),
|
|
3568
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3569
|
+
/* @__PURE__ */ jsx6(
|
|
2237
3570
|
FormField,
|
|
2238
3571
|
{
|
|
2239
3572
|
label: "Name",
|
|
@@ -2244,12 +3577,12 @@ function AccountFormView({ form, accounts }) {
|
|
|
2244
3577
|
placeholder: "e.g. Work, Personal"
|
|
2245
3578
|
}
|
|
2246
3579
|
),
|
|
2247
|
-
/* @__PURE__ */
|
|
2248
|
-
/* @__PURE__ */
|
|
3580
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3581
|
+
/* @__PURE__ */ jsx6(
|
|
2249
3582
|
FormField,
|
|
2250
3583
|
{
|
|
2251
3584
|
label: "Home directory",
|
|
2252
|
-
hint:
|
|
3585
|
+
hint: `path containing the tool's data dir ${glyphs().middot} ~ for default`,
|
|
2253
3586
|
value: form.homeDir,
|
|
2254
3587
|
focused: form.field === "homeDir",
|
|
2255
3588
|
accent,
|
|
@@ -2257,100 +3590,134 @@ function AccountFormView({ form, accounts }) {
|
|
|
2257
3590
|
mono: true
|
|
2258
3591
|
}
|
|
2259
3592
|
),
|
|
2260
|
-
/* @__PURE__ */
|
|
2261
|
-
/* @__PURE__ */
|
|
2262
|
-
/* @__PURE__ */
|
|
2263
|
-
/* @__PURE__ */
|
|
2264
|
-
/* @__PURE__ */
|
|
2265
|
-
|
|
2266
|
-
|
|
3593
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3594
|
+
/* @__PURE__ */ jsx6(ColorField, { value: form.color, focused: form.field === "color" }),
|
|
3595
|
+
/* @__PURE__ */ jsx6(Box6, { height: 1 }),
|
|
3596
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3597
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3598
|
+
"id ",
|
|
3599
|
+
glyphs().boxMark,
|
|
3600
|
+
" "
|
|
3601
|
+
] }),
|
|
3602
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, color: accent, children: previewId || "account" }),
|
|
3603
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3604
|
+
" ",
|
|
3605
|
+
glyphs().boxMark,
|
|
3606
|
+
" auto-generated from name"
|
|
3607
|
+
] })
|
|
2267
3608
|
] })
|
|
2268
3609
|
] }),
|
|
2269
|
-
form.error && /* @__PURE__ */
|
|
2270
|
-
|
|
3610
|
+
form.error && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
|
|
3611
|
+
glyphs().warn,
|
|
3612
|
+
" ",
|
|
2271
3613
|
form.error
|
|
2272
3614
|
] }) }),
|
|
2273
|
-
/* @__PURE__ */
|
|
2274
|
-
/* @__PURE__ */
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
|
|
2280
|
-
(form.field === "color" || form.field === "provider") && /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
2281
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2190\u2192 " }),
|
|
2282
|
-
/* @__PURE__ */ jsx5(Text5, { children: form.field === "provider" ? "pick provider" : "pick color" }),
|
|
2283
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " })
|
|
3615
|
+
/* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
|
|
3616
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3617
|
+
"tab/",
|
|
3618
|
+
glyphs().arrowU,
|
|
3619
|
+
glyphs().arrowD,
|
|
3620
|
+
" "
|
|
2284
3621
|
] }),
|
|
2285
|
-
/* @__PURE__ */
|
|
2286
|
-
/* @__PURE__ */
|
|
3622
|
+
/* @__PURE__ */ jsx6(Text6, { children: "switch field" }),
|
|
3623
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3624
|
+
" ",
|
|
3625
|
+
glyphs().middot,
|
|
3626
|
+
" "
|
|
3627
|
+
] }),
|
|
3628
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "enter " }),
|
|
3629
|
+
/* @__PURE__ */ jsx6(Text6, { children: form.field === "color" ? "save" : "next" }),
|
|
3630
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3631
|
+
" ",
|
|
3632
|
+
glyphs().middot,
|
|
3633
|
+
" "
|
|
3634
|
+
] }),
|
|
3635
|
+
(form.field === "color" || form.field === "provider") && /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
3636
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3637
|
+
glyphs().arrowL,
|
|
3638
|
+
glyphs().arrowR,
|
|
3639
|
+
" "
|
|
3640
|
+
] }),
|
|
3641
|
+
/* @__PURE__ */ jsx6(Text6, { children: form.field === "provider" ? "pick provider" : "pick color" }),
|
|
3642
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3643
|
+
" ",
|
|
3644
|
+
glyphs().middot,
|
|
3645
|
+
" "
|
|
3646
|
+
] })
|
|
3647
|
+
] }),
|
|
3648
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "esc " }),
|
|
3649
|
+
/* @__PURE__ */ jsx6(Text6, { children: "cancel" })
|
|
2287
3650
|
] })
|
|
2288
3651
|
] });
|
|
2289
3652
|
}
|
|
2290
|
-
function Stepper({ active, accent }) {
|
|
3653
|
+
function Stepper({ active: active2, accent }) {
|
|
2291
3654
|
const steps = [
|
|
2292
3655
|
{ id: "provider", label: "Provider" },
|
|
2293
3656
|
{ id: "name", label: "Name" },
|
|
2294
3657
|
{ id: "homeDir", label: "Home" },
|
|
2295
3658
|
{ id: "color", label: "Color" }
|
|
2296
3659
|
];
|
|
2297
|
-
const activeIdx = steps.findIndex((s) => s.id ===
|
|
2298
|
-
return /* @__PURE__ */
|
|
3660
|
+
const activeIdx = steps.findIndex((s) => s.id === active2);
|
|
3661
|
+
return /* @__PURE__ */ jsx6(Box6, { children: steps.map((s, i) => {
|
|
2299
3662
|
const done = i < activeIdx;
|
|
2300
3663
|
const cur = i === activeIdx;
|
|
2301
|
-
const dot = done ?
|
|
2302
|
-
return /* @__PURE__ */
|
|
2303
|
-
/* @__PURE__ */
|
|
3664
|
+
const dot = done ? glyphs().dot : cur ? glyphs().dotSel : glyphs().radioOff;
|
|
3665
|
+
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3666
|
+
/* @__PURE__ */ jsxs6(Text6, { color: cur || done ? accent : void 0, dimColor: !cur && !done, children: [
|
|
2304
3667
|
dot,
|
|
2305
3668
|
" "
|
|
2306
3669
|
] }),
|
|
2307
|
-
/* @__PURE__ */
|
|
2308
|
-
i < steps.length - 1 && /* @__PURE__ */
|
|
3670
|
+
/* @__PURE__ */ jsx6(Text6, { bold: cur, color: cur ? accent : void 0, dimColor: !cur, children: s.label }),
|
|
3671
|
+
i < steps.length - 1 && /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
3672
|
+
" ",
|
|
3673
|
+
glyphs().rule,
|
|
3674
|
+
" "
|
|
3675
|
+
] })
|
|
2309
3676
|
] }, s.id);
|
|
2310
3677
|
}) });
|
|
2311
3678
|
}
|
|
2312
3679
|
function ProviderField({ value, focused }) {
|
|
2313
|
-
return /* @__PURE__ */
|
|
2314
|
-
/* @__PURE__ */
|
|
2315
|
-
focused ?
|
|
3680
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
3681
|
+
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: focused ? PROVIDERS[value].color : void 0, bold: focused, dimColor: !focused, children: [
|
|
3682
|
+
focused ? glyphs().caretR : " ",
|
|
2316
3683
|
" Provider"
|
|
2317
3684
|
] }) }),
|
|
2318
|
-
/* @__PURE__ */
|
|
2319
|
-
/* @__PURE__ */
|
|
3685
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3686
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2320
3687
|
" ",
|
|
2321
|
-
focused ?
|
|
3688
|
+
focused ? glyphs().vbar : " ",
|
|
2322
3689
|
" "
|
|
2323
3690
|
] }),
|
|
2324
3691
|
PROVIDER_ORDER.map((pid) => {
|
|
2325
3692
|
const selected = pid === value;
|
|
2326
3693
|
const p = PROVIDERS[pid];
|
|
2327
|
-
return /* @__PURE__ */
|
|
3694
|
+
return /* @__PURE__ */ jsx6(Box6, { marginRight: 2, children: selected ? /* @__PURE__ */ jsxs6(Text6, { bold: true, color: p.color, children: [
|
|
2328
3695
|
"[",
|
|
2329
3696
|
p.name,
|
|
2330
3697
|
"]"
|
|
2331
|
-
] }) : /* @__PURE__ */
|
|
3698
|
+
] }) : /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: p.name }) }, pid);
|
|
2332
3699
|
})
|
|
2333
3700
|
] }),
|
|
2334
|
-
/* @__PURE__ */
|
|
3701
|
+
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " which tool this account tracks" }) })
|
|
2335
3702
|
] });
|
|
2336
3703
|
}
|
|
2337
3704
|
function FormField({ label, hint, value, focused, accent, placeholder, mono }) {
|
|
2338
3705
|
const isPlaceholder = value === "";
|
|
2339
3706
|
const display = isPlaceholder ? placeholder : value;
|
|
2340
|
-
return /* @__PURE__ */
|
|
2341
|
-
/* @__PURE__ */
|
|
2342
|
-
focused ?
|
|
3707
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
3708
|
+
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: focused ? accent : void 0, bold: focused, dimColor: !focused, children: [
|
|
3709
|
+
focused ? glyphs().caretR : " ",
|
|
2343
3710
|
" ",
|
|
2344
3711
|
label
|
|
2345
3712
|
] }) }),
|
|
2346
|
-
/* @__PURE__ */
|
|
2347
|
-
/* @__PURE__ */
|
|
3713
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3714
|
+
/* @__PURE__ */ jsxs6(Text6, { color: focused ? accent : void 0, children: [
|
|
2348
3715
|
" ",
|
|
2349
|
-
focused ?
|
|
3716
|
+
focused ? glyphs().vbar : " ",
|
|
2350
3717
|
" "
|
|
2351
3718
|
] }),
|
|
2352
|
-
/* @__PURE__ */
|
|
2353
|
-
|
|
3719
|
+
/* @__PURE__ */ jsx6(
|
|
3720
|
+
Text6,
|
|
2354
3721
|
{
|
|
2355
3722
|
bold: focused && !isPlaceholder,
|
|
2356
3723
|
color: focused && !isPlaceholder ? accent : void 0,
|
|
@@ -2359,39 +3726,58 @@ function FormField({ label, hint, value, focused, accent, placeholder, mono }) {
|
|
|
2359
3726
|
children: display
|
|
2360
3727
|
}
|
|
2361
3728
|
),
|
|
2362
|
-
focused && /* @__PURE__ */
|
|
3729
|
+
focused && /* @__PURE__ */ jsx6(Text6, { color: accent, children: glyphs().vbar })
|
|
2363
3730
|
] }),
|
|
2364
|
-
/* @__PURE__ */
|
|
3731
|
+
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
2365
3732
|
" ",
|
|
2366
3733
|
hint
|
|
2367
3734
|
] }) })
|
|
2368
3735
|
] });
|
|
2369
3736
|
}
|
|
2370
3737
|
function ColorField({ value, focused }) {
|
|
2371
|
-
return /* @__PURE__ */
|
|
2372
|
-
/* @__PURE__ */
|
|
2373
|
-
focused ?
|
|
3738
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
3739
|
+
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs6(Text6, { color: focused ? value : void 0, bold: focused, dimColor: !focused, children: [
|
|
3740
|
+
focused ? glyphs().caretR : " ",
|
|
2374
3741
|
" Accent color"
|
|
2375
3742
|
] }) }),
|
|
2376
|
-
/* @__PURE__ */
|
|
2377
|
-
/* @__PURE__ */
|
|
3743
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
3744
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
2378
3745
|
" ",
|
|
2379
|
-
focused ?
|
|
3746
|
+
focused ? glyphs().vbar : " ",
|
|
2380
3747
|
" "
|
|
2381
3748
|
] }),
|
|
2382
|
-
COLOR_PALETTE.map((c) => /* @__PURE__ */
|
|
3749
|
+
COLOR_PALETTE.map((c) => /* @__PURE__ */ jsx6(Box6, { marginRight: 1, children: c === value ? /* @__PURE__ */ jsxs6(Text6, { bold: true, color: c, children: [
|
|
3750
|
+
"[",
|
|
3751
|
+
glyphs().dot,
|
|
3752
|
+
"]"
|
|
3753
|
+
] }) : /* @__PURE__ */ jsxs6(Text6, { color: c, dimColor: !focused, children: [
|
|
3754
|
+
" ",
|
|
3755
|
+
glyphs().dot
|
|
3756
|
+
] }) }, c))
|
|
2383
3757
|
] }),
|
|
2384
|
-
/* @__PURE__ */
|
|
3758
|
+
/* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " shows on dashboard, account strip, borders" }) })
|
|
2385
3759
|
] });
|
|
2386
3760
|
}
|
|
2387
3761
|
|
|
2388
3762
|
// src/app.tsx
|
|
2389
|
-
import { Fragment as Fragment4, jsx as
|
|
3763
|
+
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2390
3764
|
var TABS = ["Dashboard", "Table"];
|
|
2391
3765
|
var VIEWS = ["Daily", "Weekly", "Monthly"];
|
|
2392
|
-
var SORTS = [
|
|
2393
|
-
|
|
3766
|
+
var SORTS = [
|
|
3767
|
+
{ label: "date", dir: "up" },
|
|
3768
|
+
{ label: "date", dir: "down" },
|
|
3769
|
+
{ label: "cost", dir: "up" },
|
|
3770
|
+
{ label: "cost", dir: "down" }
|
|
3771
|
+
];
|
|
3772
|
+
var CURSOR_SORTS = [
|
|
3773
|
+
{ label: "cost", dir: "down" },
|
|
3774
|
+
{ label: "amount", dir: "down" },
|
|
3775
|
+
{ label: "model", dir: null }
|
|
3776
|
+
];
|
|
2394
3777
|
var IS_TTY = process.stdin.isTTY === true;
|
|
3778
|
+
var DEBOUNCE_MS = 300;
|
|
3779
|
+
var LOADER_GRACE_MS = 600;
|
|
3780
|
+
var LOADER_MAX_MS = 8e3;
|
|
2395
3781
|
var DEFAULT_CONFIG = {
|
|
2396
3782
|
interval: 2,
|
|
2397
3783
|
billingInterval: 5,
|
|
@@ -2402,33 +3788,39 @@ var DEFAULT_CONFIG = {
|
|
|
2402
3788
|
disabledProviders: [],
|
|
2403
3789
|
onboarded: false,
|
|
2404
3790
|
dashboardLayout: "grid",
|
|
2405
|
-
defaultFocus: "all"
|
|
3791
|
+
defaultFocus: "all",
|
|
3792
|
+
ascii: "auto",
|
|
3793
|
+
knownProviders: []
|
|
2406
3794
|
};
|
|
2407
3795
|
function App({ interval: cliInterval }) {
|
|
2408
|
-
const [config2, setConfig] =
|
|
2409
|
-
const [detected, setDetected] =
|
|
2410
|
-
const [stats, setStats] =
|
|
2411
|
-
const [peak, setPeak] =
|
|
2412
|
-
const [table, setTable] =
|
|
2413
|
-
const [tableLoading, setTableLoading] =
|
|
2414
|
-
const [error, setError] =
|
|
2415
|
-
const [updated, setUpdated] =
|
|
2416
|
-
const [tab, setTab] =
|
|
2417
|
-
const [view, setView] =
|
|
2418
|
-
const [cursor, setCursor] =
|
|
2419
|
-
const [expanded, setExpanded] =
|
|
2420
|
-
const [sort, setSort] =
|
|
2421
|
-
const [tableProvider, setTableProvider] =
|
|
2422
|
-
const [search, setSearch] =
|
|
2423
|
-
const [searchMode, setSearchMode] =
|
|
2424
|
-
const [cursorRows, setCursorRows] =
|
|
2425
|
-
const [showSettings, setShowSettings] =
|
|
2426
|
-
const [settingsCursor, setSettingsCursor] =
|
|
2427
|
-
const [tzEdit, setTzEdit] =
|
|
2428
|
-
const [tzError, setTzError] =
|
|
2429
|
-
const [accountForm, setAccountForm] =
|
|
2430
|
-
const [onboardSel, setOnboardSel] =
|
|
2431
|
-
const [onboardCursor, setOnboardCursor] =
|
|
3796
|
+
const [config2, setConfig] = useState3(null);
|
|
3797
|
+
const [detected, setDetected] = useState3([]);
|
|
3798
|
+
const [stats, setStats] = useState3(/* @__PURE__ */ new Map());
|
|
3799
|
+
const [peak, setPeak] = useState3(null);
|
|
3800
|
+
const [table, setTable] = useState3(null);
|
|
3801
|
+
const [tableLoading, setTableLoading] = useState3(false);
|
|
3802
|
+
const [error, setError] = useState3(null);
|
|
3803
|
+
const [updated, setUpdated] = useState3(/* @__PURE__ */ new Date());
|
|
3804
|
+
const [tab, setTab] = useState3(0);
|
|
3805
|
+
const [view, setView] = useState3(0);
|
|
3806
|
+
const [cursor, setCursor] = useState3(0);
|
|
3807
|
+
const [expanded, setExpanded] = useState3(-1);
|
|
3808
|
+
const [sort, setSort] = useState3(1);
|
|
3809
|
+
const [tableProvider, setTableProvider] = useState3(null);
|
|
3810
|
+
const [search, setSearch] = useState3("");
|
|
3811
|
+
const [searchMode, setSearchMode] = useState3(false);
|
|
3812
|
+
const [cursorRows, setCursorRows] = useState3(null);
|
|
3813
|
+
const [showSettings, setShowSettings] = useState3(false);
|
|
3814
|
+
const [settingsCursor, setSettingsCursor] = useState3(0);
|
|
3815
|
+
const [tzEdit, setTzEdit] = useState3(null);
|
|
3816
|
+
const [tzError, setTzError] = useState3(null);
|
|
3817
|
+
const [accountForm, setAccountForm] = useState3(null);
|
|
3818
|
+
const [onboardSel, setOnboardSel] = useState3(null);
|
|
3819
|
+
const [onboardCursor, setOnboardCursor] = useState3(0);
|
|
3820
|
+
const [dashPage, setDashPage] = useState3(0);
|
|
3821
|
+
const [debouncePassed, setDebouncePassed] = useState3(false);
|
|
3822
|
+
const [graceHold, setGraceHold] = useState3(false);
|
|
3823
|
+
const loaderDone = useRef2(false);
|
|
2432
3824
|
const { stdout } = useStdout();
|
|
2433
3825
|
const { exit } = useApp();
|
|
2434
3826
|
const rows = stdout?.rows ?? 24;
|
|
@@ -2442,6 +3834,8 @@ function App({ interval: cliInterval }) {
|
|
|
2442
3834
|
const accountsRef = useRef2([]);
|
|
2443
3835
|
accountsRef.current = accounts;
|
|
2444
3836
|
const rowCountRef = useRef2(0);
|
|
3837
|
+
const dashPageCountRef = useRef2(1);
|
|
3838
|
+
const seededRef = useRef2(false);
|
|
2445
3839
|
const accountsKey = accounts.map((a) => `${a.id}:${a.homeDir ?? ""}`).join("|");
|
|
2446
3840
|
const slots = accounts.length > 1 ? [{ id: null, name: "All", color: "whiteBright" }, ...accounts.map((a) => ({ id: a.id, name: a.name, color: a.color }))] : accounts.map((a) => ({ id: a.id, name: a.name, color: a.color }));
|
|
2447
3841
|
const activeSlotIdx = (() => {
|
|
@@ -2452,21 +3846,49 @@ function App({ interval: cliInterval }) {
|
|
|
2452
3846
|
const focusId = slots[activeSlotIdx]?.id ?? null;
|
|
2453
3847
|
const visibleAccounts = focusId === null ? accounts : accounts.filter((a) => a.id === focusId);
|
|
2454
3848
|
const groups = accountsByProvider(visibleAccounts);
|
|
3849
|
+
const TOO_SMALL = cols < 40 || rows < 12;
|
|
3850
|
+
const allGroups = accountsByProvider(accounts);
|
|
3851
|
+
const allReady = accounts.length > 0 && accounts.every((a) => accountReady(stats.get(a.id), a.providerId));
|
|
3852
|
+
const hasStrip = slots.length > 1;
|
|
3853
|
+
const stripChipW = (s) => 2 + 2 + truncateName(s.name, 16).length + 2;
|
|
3854
|
+
const stripChars = slots.reduce((sum, s) => sum + stripChipW(s), 0);
|
|
3855
|
+
const stripLines = hasStrip ? Math.max(1, Math.ceil(stripChars / Math.max(
|
|
3856
|
+
1,
|
|
3857
|
+
cols - 4 - 7
|
|
3858
|
+
/*"focus "*/
|
|
3859
|
+
))) : 0;
|
|
3860
|
+
const headerRows = cols < 70 ? 2 : 1;
|
|
3861
|
+
const CHROME = 2 + headerRows + 3 + (hasStrip ? 1 + stripLines : 0) + 2;
|
|
3862
|
+
const gridBudget = Math.max(1, rows - CHROME);
|
|
3863
|
+
const dashLayout = chooseLayout(
|
|
3864
|
+
Math.max(56, cols - 4),
|
|
3865
|
+
gridBudget,
|
|
3866
|
+
groups.length,
|
|
3867
|
+
focusId !== null || cfg.dashboardLayout === "single",
|
|
3868
|
+
cols
|
|
3869
|
+
);
|
|
3870
|
+
const dashPageCount = dashLayout.pageCount;
|
|
3871
|
+
const dashPaginated = dashPageCount > 1;
|
|
3872
|
+
dashPageCountRef.current = dashPageCount;
|
|
2455
3873
|
const tableProvs = accountsByProvider(accounts).map((g) => g.provider);
|
|
2456
3874
|
const effTableProvider = tableProvider && tableProvs.includes(tableProvider) ? tableProvider : tableProvs[0] ?? null;
|
|
2457
3875
|
const tableIsCursor = !!effTableProvider && !PROVIDERS[effTableProvider].hasUsage;
|
|
2458
3876
|
const tableAccounts = effTableProvider ? accounts.filter((a) => a.providerId === effTableProvider) : [];
|
|
2459
3877
|
const SORTS_FOR = tableIsCursor ? CURSOR_SORTS : SORTS;
|
|
2460
3878
|
const needsOnboarding = configReady && !cfg.onboarded;
|
|
3879
|
+
const newProviders = configReady && cfg.onboarded ? PROVIDER_ORDER.filter((p) => !cfg.knownProviders.includes(p) && detected.includes(p)) : [];
|
|
3880
|
+
const showPicker = needsOnboarding || newProviders.length > 0;
|
|
3881
|
+
const showLoader = configReady && !showPicker && !showSettings && !TOO_SMALL && accounts.length > 0 && (!allReady || graceHold) && debouncePassed && !loaderDone.current;
|
|
3882
|
+
const pickerProviders = needsOnboarding ? PROVIDER_ORDER : newProviders;
|
|
2461
3883
|
const onboardEnabled = onboardSel ?? detected;
|
|
2462
|
-
const onboardItems =
|
|
3884
|
+
const onboardItems = pickerProviders.map((pid) => ({
|
|
2463
3885
|
id: pid,
|
|
2464
3886
|
name: PROVIDERS[pid].name,
|
|
2465
3887
|
color: PROVIDERS[pid].color,
|
|
2466
3888
|
detected: detected.includes(pid),
|
|
2467
3889
|
enabled: onboardEnabled.includes(pid)
|
|
2468
3890
|
}));
|
|
2469
|
-
|
|
3891
|
+
useEffect3(() => {
|
|
2470
3892
|
loadConfig().then((c) => {
|
|
2471
3893
|
if (cliInterval) c = { ...c, interval: cliInterval / 1e3 };
|
|
2472
3894
|
if (c.defaultFocus === "all") c = { ...c, activeAccountId: null };
|
|
@@ -2474,9 +3896,55 @@ function App({ interval: cliInterval }) {
|
|
|
2474
3896
|
});
|
|
2475
3897
|
detectProviders().then(setDetected);
|
|
2476
3898
|
}, []);
|
|
2477
|
-
|
|
3899
|
+
useEffect3(() => {
|
|
3900
|
+
if (seededRef.current || !configReady || accounts.length === 0) return;
|
|
3901
|
+
seededRef.current = true;
|
|
3902
|
+
loadSnapshot().then((snap) => {
|
|
3903
|
+
setStats((prev) => {
|
|
3904
|
+
if (prev.size > 0) return prev;
|
|
3905
|
+
const next = new Map(prev);
|
|
3906
|
+
for (const acc of accountsRef.current) {
|
|
3907
|
+
const s = snap[acc.id];
|
|
3908
|
+
if (s && (s.dashboard || s.billing)) next.set(acc.id, { account: acc, dashboard: s.dashboard ?? null, billing: s.billing ?? null });
|
|
3909
|
+
}
|
|
3910
|
+
return next;
|
|
3911
|
+
});
|
|
3912
|
+
});
|
|
3913
|
+
}, [configReady, accountsKey]);
|
|
3914
|
+
useEffect3(() => {
|
|
3915
|
+
if (stats.size === 0) return;
|
|
3916
|
+
const t = setTimeout(() => saveSnapshot(stats), 500);
|
|
3917
|
+
return () => clearTimeout(t);
|
|
3918
|
+
}, [stats]);
|
|
3919
|
+
useEffect3(() => {
|
|
3920
|
+
if (!configReady || showPicker || accounts.length === 0) return;
|
|
3921
|
+
if (allReady || loaderDone.current) return;
|
|
3922
|
+
const debounce = setTimeout(() => setDebouncePassed(true), DEBOUNCE_MS);
|
|
3923
|
+
const deadline = setTimeout(() => {
|
|
3924
|
+
loaderDone.current = true;
|
|
3925
|
+
setDebouncePassed(false);
|
|
3926
|
+
}, LOADER_MAX_MS);
|
|
3927
|
+
return () => {
|
|
3928
|
+
clearTimeout(debounce);
|
|
3929
|
+
clearTimeout(deadline);
|
|
3930
|
+
};
|
|
3931
|
+
}, [configReady, showPicker, accountsKey]);
|
|
3932
|
+
useEffect3(() => {
|
|
3933
|
+
if (!allReady || loaderDone.current) return;
|
|
3934
|
+
if (!debouncePassed) {
|
|
3935
|
+
loaderDone.current = true;
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3938
|
+
setGraceHold(true);
|
|
3939
|
+
const t = setTimeout(() => {
|
|
3940
|
+
loaderDone.current = true;
|
|
3941
|
+
setGraceHold(false);
|
|
3942
|
+
}, LOADER_GRACE_MS);
|
|
3943
|
+
return () => clearTimeout(t);
|
|
3944
|
+
}, [allReady]);
|
|
3945
|
+
useEffect3(() => {
|
|
2478
3946
|
if (!configReady) return;
|
|
2479
|
-
let
|
|
3947
|
+
let active2 = true;
|
|
2480
3948
|
let timer;
|
|
2481
3949
|
const load = async () => {
|
|
2482
3950
|
try {
|
|
@@ -2485,27 +3953,27 @@ function App({ interval: cliInterval }) {
|
|
|
2485
3953
|
if (!provider.hasUsage || !provider.fetchSummary) return;
|
|
2486
3954
|
try {
|
|
2487
3955
|
const dashboard = await provider.fetchSummary(acc, tz);
|
|
2488
|
-
if (
|
|
3956
|
+
if (active2) setStats((prev) => upsert(prev, acc, { dashboard }));
|
|
2489
3957
|
} catch {
|
|
2490
3958
|
}
|
|
2491
3959
|
}));
|
|
2492
|
-
if (
|
|
3960
|
+
if (active2) {
|
|
2493
3961
|
setError(null);
|
|
2494
3962
|
setUpdated(/* @__PURE__ */ new Date());
|
|
2495
3963
|
}
|
|
2496
3964
|
} finally {
|
|
2497
|
-
if (
|
|
3965
|
+
if (active2) timer = setTimeout(load, interval2);
|
|
2498
3966
|
}
|
|
2499
3967
|
};
|
|
2500
3968
|
load();
|
|
2501
3969
|
return () => {
|
|
2502
|
-
|
|
3970
|
+
active2 = false;
|
|
2503
3971
|
clearTimeout(timer);
|
|
2504
3972
|
};
|
|
2505
3973
|
}, [interval2, tz, configReady, accountsKey]);
|
|
2506
|
-
|
|
3974
|
+
useEffect3(() => {
|
|
2507
3975
|
if (!configReady) return;
|
|
2508
|
-
let
|
|
3976
|
+
let active2 = true;
|
|
2509
3977
|
let timer;
|
|
2510
3978
|
const load = async () => {
|
|
2511
3979
|
try {
|
|
@@ -2515,42 +3983,42 @@ function App({ interval: cliInterval }) {
|
|
|
2515
3983
|
if (!provider.hasBilling || !provider.fetchBilling) return;
|
|
2516
3984
|
try {
|
|
2517
3985
|
const billing = await provider.fetchBilling(acc);
|
|
2518
|
-
if (
|
|
3986
|
+
if (active2) setStats((prev) => upsert(prev, acc, { billing }));
|
|
2519
3987
|
} catch {
|
|
2520
3988
|
}
|
|
2521
3989
|
}));
|
|
2522
3990
|
const p = await peakP;
|
|
2523
|
-
if (
|
|
3991
|
+
if (active2 && p) setPeak(p);
|
|
2524
3992
|
} finally {
|
|
2525
|
-
if (
|
|
3993
|
+
if (active2) timer = setTimeout(load, billingMs);
|
|
2526
3994
|
}
|
|
2527
3995
|
};
|
|
2528
3996
|
load();
|
|
2529
3997
|
return () => {
|
|
2530
|
-
|
|
3998
|
+
active2 = false;
|
|
2531
3999
|
clearTimeout(timer);
|
|
2532
4000
|
};
|
|
2533
4001
|
}, [billingMs, configReady, accountsKey]);
|
|
2534
4002
|
const tableKey = `${effTableProvider}|${tableAccounts.map((a) => `${a.id}:${a.homeDir ?? ""}`).join(",")}|${tz}`;
|
|
2535
|
-
|
|
4003
|
+
useEffect3(() => {
|
|
2536
4004
|
setTable(null);
|
|
2537
4005
|
setCursorRows(null);
|
|
2538
4006
|
setCursor(0);
|
|
2539
4007
|
setExpanded(-1);
|
|
2540
4008
|
setSort(tableIsCursor ? 0 : 1);
|
|
2541
4009
|
}, [tableKey]);
|
|
2542
|
-
|
|
4010
|
+
useEffect3(() => {
|
|
2543
4011
|
if (tab !== 1 || !effTableProvider) return;
|
|
2544
|
-
let
|
|
4012
|
+
let active2 = true;
|
|
2545
4013
|
let timer;
|
|
2546
4014
|
const fetchOnce = async () => {
|
|
2547
4015
|
try {
|
|
2548
4016
|
if (tableIsCursor) {
|
|
2549
4017
|
const s = await cursorModelSpend(tableAccounts[0]?.homeDir);
|
|
2550
|
-
if (
|
|
4018
|
+
if (active2) setCursorRows(s?.models ?? []);
|
|
2551
4019
|
} else {
|
|
2552
4020
|
const r = await fetchScopeTable(tableAccounts, tz);
|
|
2553
|
-
if (
|
|
4021
|
+
if (active2) setTable(r);
|
|
2554
4022
|
}
|
|
2555
4023
|
} catch {
|
|
2556
4024
|
}
|
|
@@ -2558,35 +4026,43 @@ function App({ interval: cliInterval }) {
|
|
|
2558
4026
|
const run = async () => {
|
|
2559
4027
|
setTableLoading(true);
|
|
2560
4028
|
await fetchOnce();
|
|
2561
|
-
if (!
|
|
4029
|
+
if (!active2) return;
|
|
2562
4030
|
setTableLoading(false);
|
|
2563
4031
|
const loop = async () => {
|
|
2564
4032
|
await fetchOnce();
|
|
2565
|
-
if (
|
|
4033
|
+
if (active2) timer = setTimeout(loop, Math.max(interval2, 1e4));
|
|
2566
4034
|
};
|
|
2567
4035
|
timer = setTimeout(loop, Math.max(interval2, 1e4));
|
|
2568
4036
|
};
|
|
2569
4037
|
run();
|
|
2570
4038
|
return () => {
|
|
2571
|
-
|
|
4039
|
+
active2 = false;
|
|
2572
4040
|
clearTimeout(timer);
|
|
2573
4041
|
};
|
|
2574
4042
|
}, [tab, tableKey, interval2]);
|
|
2575
|
-
|
|
4043
|
+
useEffect3(() => {
|
|
2576
4044
|
setCursor(0);
|
|
2577
4045
|
setExpanded(-1);
|
|
2578
4046
|
}, [search]);
|
|
4047
|
+
useEffect3(() => {
|
|
4048
|
+
setDashPage((p) => Math.min(p, dashPageCount - 1));
|
|
4049
|
+
}, [dashPageCount]);
|
|
2579
4050
|
const resetView = useCallback(() => {
|
|
2580
4051
|
setCursor(0);
|
|
2581
4052
|
setExpanded(-1);
|
|
2582
4053
|
}, []);
|
|
2583
4054
|
const clampRow = (n) => Math.max(0, Math.min(rowCountRef.current - 1, n));
|
|
2584
4055
|
const mouse = useMouse();
|
|
2585
|
-
|
|
4056
|
+
useEffect3(() => {
|
|
2586
4057
|
if (!IS_TTY) return;
|
|
2587
4058
|
mouse.enable();
|
|
2588
4059
|
const onScroll = (_pos, dir) => {
|
|
2589
|
-
|
|
4060
|
+
const up = dir === "scrollup";
|
|
4061
|
+
if (tab === 1) {
|
|
4062
|
+
setCursor((c) => up ? Math.max(0, c - 3) : c + 3);
|
|
4063
|
+
} else if (tab === 0 && dashPageCountRef.current > 1) {
|
|
4064
|
+
setDashPage((p) => up ? Math.max(0, p - 1) : Math.min(dashPageCountRef.current - 1, p + 1));
|
|
4065
|
+
}
|
|
2590
4066
|
};
|
|
2591
4067
|
mouse.events.on("scroll", onScroll);
|
|
2592
4068
|
return () => {
|
|
@@ -2601,8 +4077,8 @@ function App({ interval: cliInterval }) {
|
|
|
2601
4077
|
});
|
|
2602
4078
|
}
|
|
2603
4079
|
function toggleOnboard(i) {
|
|
2604
|
-
if (i < 0 || i >=
|
|
2605
|
-
const pid =
|
|
4080
|
+
if (i < 0 || i >= pickerProviders.length) return;
|
|
4081
|
+
const pid = pickerProviders[i];
|
|
2606
4082
|
setOnboardSel((prev) => {
|
|
2607
4083
|
const base = prev ?? detected;
|
|
2608
4084
|
return base.includes(pid) ? base.filter((p) => p !== pid) : [...base, pid];
|
|
@@ -2611,16 +4087,31 @@ function App({ interval: cliInterval }) {
|
|
|
2611
4087
|
function toggleProvider(pid) {
|
|
2612
4088
|
updateConfig((c) => ({
|
|
2613
4089
|
...c,
|
|
4090
|
+
// Toggling in settings is also an explicit decision → mark it known.
|
|
4091
|
+
knownProviders: c.knownProviders.includes(pid) ? c.knownProviders : [...c.knownProviders, pid],
|
|
2614
4092
|
disabledProviders: c.disabledProviders.includes(pid) ? c.disabledProviders.filter((p) => p !== pid) : [...c.disabledProviders, pid]
|
|
2615
4093
|
}));
|
|
2616
4094
|
}
|
|
2617
4095
|
function confirmOnboarding() {
|
|
2618
4096
|
const enabled = onboardEnabled;
|
|
2619
|
-
updateConfig((c) =>
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
4097
|
+
updateConfig((c) => {
|
|
4098
|
+
if (!c.onboarded) {
|
|
4099
|
+
return {
|
|
4100
|
+
...c,
|
|
4101
|
+
disabledProviders: PROVIDER_ORDER.filter((p) => !enabled.includes(p)),
|
|
4102
|
+
knownProviders: [...PROVIDER_ORDER],
|
|
4103
|
+
onboarded: true
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
const newlyDisabled = pickerProviders.filter((p) => !enabled.includes(p));
|
|
4107
|
+
return {
|
|
4108
|
+
...c,
|
|
4109
|
+
disabledProviders: [.../* @__PURE__ */ new Set([...c.disabledProviders, ...newlyDisabled])],
|
|
4110
|
+
knownProviders: [.../* @__PURE__ */ new Set([...c.knownProviders, ...pickerProviders])]
|
|
4111
|
+
};
|
|
4112
|
+
});
|
|
4113
|
+
setOnboardSel(null);
|
|
4114
|
+
setOnboardCursor(0);
|
|
2624
4115
|
}
|
|
2625
4116
|
function cycleAccount(dir) {
|
|
2626
4117
|
if (slots.length <= 1) return;
|
|
@@ -2724,12 +4215,12 @@ function App({ interval: cliInterval }) {
|
|
|
2724
4215
|
}
|
|
2725
4216
|
const totalSettingsRows = ACCOUNT_ROWS_START + cfg.accounts.length + 1;
|
|
2726
4217
|
useInput((input, key) => {
|
|
2727
|
-
if (
|
|
4218
|
+
if (showPicker) {
|
|
2728
4219
|
if (input === "q") {
|
|
2729
4220
|
exit();
|
|
2730
4221
|
return;
|
|
2731
4222
|
}
|
|
2732
|
-
const startIdx =
|
|
4223
|
+
const startIdx = pickerProviders.length;
|
|
2733
4224
|
if (key.upArrow) {
|
|
2734
4225
|
setOnboardCursor((c) => Math.max(0, c - 1));
|
|
2735
4226
|
return;
|
|
@@ -2968,6 +4459,16 @@ function App({ interval: cliInterval }) {
|
|
|
2968
4459
|
}
|
|
2969
4460
|
return;
|
|
2970
4461
|
}
|
|
4462
|
+
if (tab === 0 && dashPaginated) {
|
|
4463
|
+
if (input === "]" || key.downArrow || key.pageDown) {
|
|
4464
|
+
setDashPage((p) => Math.min(dashPageCount - 1, p + 1));
|
|
4465
|
+
return;
|
|
4466
|
+
}
|
|
4467
|
+
if (input === "[" || key.upArrow || key.pageUp) {
|
|
4468
|
+
setDashPage((p) => Math.max(0, p - 1));
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
2971
4472
|
if (tab === 1) {
|
|
2972
4473
|
if (input === "p") {
|
|
2973
4474
|
cycleTableProvider(1);
|
|
@@ -3046,36 +4547,58 @@ function App({ interval: cliInterval }) {
|
|
|
3046
4547
|
return;
|
|
3047
4548
|
}
|
|
3048
4549
|
}, { isActive: IS_TTY });
|
|
3049
|
-
if (error) return /* @__PURE__ */
|
|
3050
|
-
if (!config2) return /* @__PURE__ */
|
|
3051
|
-
if (
|
|
3052
|
-
return /* @__PURE__ */
|
|
4550
|
+
if (error) return /* @__PURE__ */ jsx7(Box7, { padding: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "red", children: error }) });
|
|
4551
|
+
if (!config2) return /* @__PURE__ */ jsx7(Box7, { padding: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Loading..." }) });
|
|
4552
|
+
if (showPicker) {
|
|
4553
|
+
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, children: /* @__PURE__ */ jsx7(
|
|
4554
|
+
Onboarding,
|
|
4555
|
+
{
|
|
4556
|
+
items: onboardItems,
|
|
4557
|
+
cursor: onboardCursor,
|
|
4558
|
+
onToggle: toggleOnboard,
|
|
4559
|
+
onConfirm: confirmOnboarding,
|
|
4560
|
+
heading: needsOnboarding ? "Welcome to tokmon" : "New providers detected",
|
|
4561
|
+
subheading: needsOnboarding ? "Pick the tools you want to track. You can change this anytime in settings." : "tokmon found these installed since you last set up. Pick which to track."
|
|
4562
|
+
}
|
|
4563
|
+
) });
|
|
4564
|
+
}
|
|
4565
|
+
if (showLoader) {
|
|
4566
|
+
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: /* @__PURE__ */ jsx7(LoadingView, { groups: allGroups, stats, cols, rows }) });
|
|
4567
|
+
}
|
|
4568
|
+
if (TOO_SMALL && !showSettings) {
|
|
4569
|
+
return /* @__PURE__ */ jsx7(TinyFallback, { groups, stats, rows, cols });
|
|
3053
4570
|
}
|
|
3054
4571
|
const tokenRows = sortRows(filterTokenRows(table ? [table.daily, table.weekly, table.monthly][view] : [], search), sort);
|
|
3055
4572
|
const cursorTableRows = sortCursorRows(filterCursorRows(cursorRows ?? [], search), sort);
|
|
3056
4573
|
rowCountRef.current = tableIsCursor ? cursorTableRows.length : tokenRows.length;
|
|
3057
|
-
return /* @__PURE__ */
|
|
3058
|
-
/* @__PURE__ */
|
|
3059
|
-
/* @__PURE__ */
|
|
3060
|
-
/* @__PURE__ */
|
|
3061
|
-
|
|
4574
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 2, paddingY: 1, height: rows, overflow: "hidden", children: [
|
|
4575
|
+
/* @__PURE__ */ jsxs7(Box7, { justifyContent: "space-between", children: [
|
|
4576
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
4577
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: "greenBright", children: [
|
|
4578
|
+
glyphs().dotSel,
|
|
3062
4579
|
" tokmon"
|
|
3063
4580
|
] }),
|
|
3064
|
-
/* @__PURE__ */
|
|
3065
|
-
"
|
|
4581
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4582
|
+
" ",
|
|
4583
|
+
glyphs().middot,
|
|
4584
|
+
" every ",
|
|
3066
4585
|
cfg.interval,
|
|
3067
4586
|
"s"
|
|
3068
4587
|
] })
|
|
3069
4588
|
] }),
|
|
3070
|
-
/* @__PURE__ */
|
|
3071
|
-
peak && /* @__PURE__ */
|
|
3072
|
-
/* @__PURE__ */
|
|
3073
|
-
/* @__PURE__ */
|
|
4589
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
4590
|
+
peak && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
4591
|
+
/* @__PURE__ */ jsx7(PeakBadge, { peak }),
|
|
4592
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4593
|
+
" ",
|
|
4594
|
+
glyphs().middot,
|
|
4595
|
+
" "
|
|
4596
|
+
] })
|
|
3074
4597
|
] }),
|
|
3075
|
-
/* @__PURE__ */
|
|
4598
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: time(updated, tz) })
|
|
3076
4599
|
] })
|
|
3077
4600
|
] }),
|
|
3078
|
-
showSettings ? /* @__PURE__ */
|
|
4601
|
+
showSettings ? /* @__PURE__ */ jsx7(
|
|
3079
4602
|
SettingsView,
|
|
3080
4603
|
{
|
|
3081
4604
|
config: cfg,
|
|
@@ -3086,19 +4609,23 @@ function App({ interval: cliInterval }) {
|
|
|
3086
4609
|
accountForm,
|
|
3087
4610
|
activeAccountId: cfg.activeAccountId
|
|
3088
4611
|
}
|
|
3089
|
-
) : /* @__PURE__ */
|
|
3090
|
-
/* @__PURE__ */
|
|
3091
|
-
/* @__PURE__ */
|
|
4612
|
+
) : /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
4613
|
+
/* @__PURE__ */ jsxs7(Box7, { marginTop: 1, marginBottom: 1, children: [
|
|
4614
|
+
/* @__PURE__ */ jsx7(TabBar, { tabs: TABS, active: tab, onSelect: (i) => {
|
|
3092
4615
|
setTab(i);
|
|
3093
4616
|
resetView();
|
|
3094
4617
|
} }),
|
|
3095
|
-
/* @__PURE__ */
|
|
4618
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4619
|
+
" Tab/",
|
|
4620
|
+
glyphs().arrowL,
|
|
4621
|
+
glyphs().arrowR
|
|
4622
|
+
] })
|
|
3096
4623
|
] }),
|
|
3097
|
-
tab === 0 && /* @__PURE__ */
|
|
3098
|
-
/* @__PURE__ */
|
|
3099
|
-
slots.length > 1 && /* @__PURE__ */
|
|
3100
|
-
/* @__PURE__ */
|
|
3101
|
-
/* @__PURE__ */
|
|
4624
|
+
tab === 0 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
4625
|
+
/* @__PURE__ */ jsx7(DashboardView, { groups, stats, cols, budget: gridBudget, focusId, layout: cfg.dashboardLayout, page: dashPage }),
|
|
4626
|
+
slots.length > 1 && /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
|
|
4627
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "focus " }),
|
|
4628
|
+
/* @__PURE__ */ jsx7(
|
|
3102
4629
|
AccountStrip,
|
|
3103
4630
|
{
|
|
3104
4631
|
slots,
|
|
@@ -3111,28 +4638,32 @@ function App({ interval: cliInterval }) {
|
|
|
3111
4638
|
)
|
|
3112
4639
|
] })
|
|
3113
4640
|
] }),
|
|
3114
|
-
tab === 1 && /* @__PURE__ */
|
|
3115
|
-
tableProvs.length > 0 && /* @__PURE__ */
|
|
4641
|
+
tab === 1 && /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
4642
|
+
tableProvs.length > 0 && /* @__PURE__ */ jsx7(TableProviderBar, { providers: tableProvs, active: effTableProvider, onSelect: (p) => {
|
|
3116
4643
|
setTableProvider(p);
|
|
3117
4644
|
setCursor(0);
|
|
3118
4645
|
setExpanded(-1);
|
|
3119
4646
|
setSearch("");
|
|
3120
4647
|
setSearchMode(false);
|
|
3121
4648
|
} }),
|
|
3122
|
-
/* @__PURE__ */
|
|
3123
|
-
/* @__PURE__ */
|
|
4649
|
+
/* @__PURE__ */ jsx7(Box7, { height: 1 }),
|
|
4650
|
+
/* @__PURE__ */ jsx7(
|
|
3124
4651
|
ControlBar,
|
|
3125
4652
|
{
|
|
3126
4653
|
views: VIEWS,
|
|
3127
4654
|
period: view,
|
|
3128
|
-
sort: SORTS_FOR[sort % SORTS_FOR.length],
|
|
4655
|
+
sort: sortLabel(SORTS_FOR[sort % SORTS_FOR.length]),
|
|
3129
4656
|
search,
|
|
3130
4657
|
searching: searchMode,
|
|
3131
4658
|
showPeriod: !tableIsCursor
|
|
3132
4659
|
}
|
|
3133
4660
|
),
|
|
3134
|
-
/* @__PURE__ */
|
|
3135
|
-
!effTableProvider ? /* @__PURE__ */
|
|
4661
|
+
/* @__PURE__ */ jsx7(Box7, { height: 1 }),
|
|
4662
|
+
!effTableProvider ? /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4663
|
+
"No providers enabled ",
|
|
4664
|
+
glyphs().emDash,
|
|
4665
|
+
" press s to pick providers."
|
|
4666
|
+
] }) : tableLoading && !table && !cursorRows ? /* @__PURE__ */ jsx7(Spinner, { label: "Loading history" }) : tableIsCursor ? /* @__PURE__ */ jsx7(
|
|
3136
4667
|
CursorSpendTable,
|
|
3137
4668
|
{
|
|
3138
4669
|
rows: cursorTableRows,
|
|
@@ -3140,7 +4671,7 @@ function App({ interval: cliInterval }) {
|
|
|
3140
4671
|
maxRows: Math.max(1, rows - 16),
|
|
3141
4672
|
onRowClick: (idx) => setCursor(idx)
|
|
3142
4673
|
}
|
|
3143
|
-
) : /* @__PURE__ */
|
|
4674
|
+
) : /* @__PURE__ */ jsx7(
|
|
3144
4675
|
TokenTable,
|
|
3145
4676
|
{
|
|
3146
4677
|
rows: tokenRows,
|
|
@@ -3156,7 +4687,7 @@ function App({ interval: cliInterval }) {
|
|
|
3156
4687
|
)
|
|
3157
4688
|
] })
|
|
3158
4689
|
] }),
|
|
3159
|
-
(tab === 0 || showSettings) && /* @__PURE__ */
|
|
4690
|
+
(tab === 0 || showSettings) && /* @__PURE__ */ jsx7(Footer, { hasAccounts: slots.length > 1, paginated: tab === 0 && dashPaginated, cols })
|
|
3160
4691
|
] });
|
|
3161
4692
|
}
|
|
3162
4693
|
function upsert(prev, account, patch) {
|
|
@@ -3180,6 +4711,11 @@ async function fetchScopeTable(scope, tz) {
|
|
|
3180
4711
|
if (valid.length === 1) return valid[0];
|
|
3181
4712
|
return mergeTables(valid);
|
|
3182
4713
|
}
|
|
4714
|
+
function sortLabel(entry) {
|
|
4715
|
+
if (entry.dir === "up") return `${entry.label} ${glyphs().arrowU}`;
|
|
4716
|
+
if (entry.dir === "down") return `${entry.label} ${glyphs().arrowD}`;
|
|
4717
|
+
return entry.label;
|
|
4718
|
+
}
|
|
3183
4719
|
function sortRows(rows, sortIdx) {
|
|
3184
4720
|
const sorted = [...rows];
|
|
3185
4721
|
switch (sortIdx % SORTS.length) {
|
|
@@ -3217,47 +4753,113 @@ function sortCursorRows(rows, sortIdx) {
|
|
|
3217
4753
|
}
|
|
3218
4754
|
}
|
|
3219
4755
|
function AccountStrip({ slots, activeIdx, onSelect }) {
|
|
3220
|
-
return /* @__PURE__ */
|
|
3221
|
-
const
|
|
3222
|
-
const dot = s.id === null ?
|
|
4756
|
+
return /* @__PURE__ */ jsx7(Box7, { flexWrap: "wrap", children: slots.map((s, i) => {
|
|
4757
|
+
const active2 = i === activeIdx;
|
|
4758
|
+
const dot = s.id === null ? glyphs().dotAll : glyphs().dot;
|
|
3223
4759
|
const label = truncateName(s.name, 16);
|
|
3224
|
-
return /* @__PURE__ */
|
|
3225
|
-
/* @__PURE__ */
|
|
3226
|
-
/* @__PURE__ */
|
|
3227
|
-
/* @__PURE__ */
|
|
3228
|
-
/* @__PURE__ */
|
|
3229
|
-
|
|
4760
|
+
return /* @__PURE__ */ jsxs7(ClickableBox, { onClick: () => onSelect(i), marginRight: 2, children: [
|
|
4761
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: !active2, children: i }),
|
|
4762
|
+
/* @__PURE__ */ jsx7(Text7, { children: " " }),
|
|
4763
|
+
/* @__PURE__ */ jsx7(Text7, { color: s.color, bold: active2, dimColor: !active2, children: dot }),
|
|
4764
|
+
/* @__PURE__ */ jsx7(Text7, { children: " " }),
|
|
4765
|
+
active2 ? /* @__PURE__ */ jsx7(Text7, { bold: true, color: s.color, children: label }) : /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: label })
|
|
3230
4766
|
] }, s.id ?? "__all__");
|
|
3231
4767
|
}) });
|
|
3232
4768
|
}
|
|
3233
|
-
function Footer({ hasAccounts }) {
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
/* @__PURE__ */
|
|
4769
|
+
function Footer({ hasAccounts, paginated, cols }) {
|
|
4770
|
+
const inner = cols - 4;
|
|
4771
|
+
const BASE2 = "by David Ilie (davidilie.com) \xB7 s=settings q=quit".length;
|
|
4772
|
+
const JUMP = "0-9=jump a/A=cycle ".length;
|
|
4773
|
+
const PAGE = "scroll=page ".length;
|
|
4774
|
+
const showJump = hasAccounts && inner >= BASE2 + JUMP + (paginated ? PAGE : 0);
|
|
4775
|
+
const showPage = paginated && inner >= BASE2 + (showJump ? JUMP : 0) + PAGE;
|
|
4776
|
+
return /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexWrap: "nowrap", children: [
|
|
4777
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "by " }),
|
|
4778
|
+
/* @__PURE__ */ jsx7(Text7, { children: "David Ilie" }),
|
|
4779
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " (" }),
|
|
4780
|
+
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "davidilie.com" }),
|
|
4781
|
+
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4782
|
+
") ",
|
|
4783
|
+
glyphs().middot,
|
|
4784
|
+
" s=settings "
|
|
4785
|
+
] }),
|
|
4786
|
+
showJump && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "0-9=jump a/A=cycle " }),
|
|
4787
|
+
showPage && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "scroll=page " }),
|
|
4788
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "q=quit" })
|
|
4789
|
+
] });
|
|
4790
|
+
}
|
|
4791
|
+
function TinyFallback({ groups, stats, rows, cols }) {
|
|
4792
|
+
const maxLines = Math.max(1, rows - 4);
|
|
4793
|
+
const visible = groups.slice(0, maxLines);
|
|
4794
|
+
const hidden = groups.length - visible.length;
|
|
4795
|
+
const w = Math.max(8, cols - 2);
|
|
4796
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, height: rows, overflow: "hidden", children: [
|
|
4797
|
+
/* @__PURE__ */ jsxs7(Text7, { bold: true, color: "greenBright", children: [
|
|
4798
|
+
glyphs().dotSel,
|
|
4799
|
+
" tokmon"
|
|
4800
|
+
] }),
|
|
4801
|
+
groups.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4802
|
+
"No providers ",
|
|
4803
|
+
glyphs().emDash,
|
|
4804
|
+
" s=settings"
|
|
4805
|
+
] }) : visible.map((g) => /* @__PURE__ */ jsx7(TinyRow, { provider: g.provider, accounts: g.accounts, stats, width: w }, g.provider)),
|
|
4806
|
+
hidden > 0 && /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
4807
|
+
"+",
|
|
4808
|
+
hidden,
|
|
4809
|
+
" more (enlarge terminal)"
|
|
4810
|
+
] }),
|
|
4811
|
+
/* @__PURE__ */ jsx7(Box7, { flexGrow: 1 }),
|
|
4812
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "s=settings q=quit" })
|
|
4813
|
+
] });
|
|
4814
|
+
}
|
|
4815
|
+
function TinyRow({ provider, accounts, stats, width }) {
|
|
4816
|
+
const meta = PROVIDERS[provider];
|
|
4817
|
+
const dashboards = accounts.map((a) => stats.get(a.id)?.dashboard).filter(Boolean);
|
|
4818
|
+
const billings = accounts.map((a) => stats.get(a.id)?.billing).filter(Boolean);
|
|
4819
|
+
const todayCost = dashboards.reduce((sum, d) => sum + (d?.today.cost ?? 0), 0);
|
|
4820
|
+
const pctMetric = billings.flatMap((b) => b?.metrics ?? []).find((m) => m.format.kind === "percent");
|
|
4821
|
+
const detail = meta.hasUsage ? `${currency(todayCost)} today` : pctMetric ? `${Math.round(pctMetric.used)}%` : "billing";
|
|
4822
|
+
const name = truncateName(meta.name, Math.max(4, width - 18));
|
|
4823
|
+
return /* @__PURE__ */ jsxs7(Box7, { width, children: [
|
|
4824
|
+
/* @__PURE__ */ jsxs7(Text7, { color: meta.color, children: [
|
|
4825
|
+
glyphs().dot,
|
|
4826
|
+
" "
|
|
4827
|
+
] }),
|
|
4828
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: meta.color, children: name }),
|
|
4829
|
+
/* @__PURE__ */ jsx7(Box7, { flexGrow: 1 }),
|
|
4830
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: detail })
|
|
3242
4831
|
] });
|
|
3243
4832
|
}
|
|
3244
4833
|
|
|
3245
4834
|
// src/cli.tsx
|
|
3246
|
-
import { jsx as
|
|
4835
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
3247
4836
|
process.on("unhandledRejection", () => {
|
|
3248
4837
|
});
|
|
4838
|
+
EventEmitter.defaultMaxListeners = 100;
|
|
4839
|
+
var emitWarning = process.emitWarning.bind(process);
|
|
4840
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
4841
|
+
const msg = typeof warning === "string" ? warning : warning?.message;
|
|
4842
|
+
if (typeof msg === "string" && /SQLite is an experimental feature/i.test(msg)) return;
|
|
4843
|
+
return emitWarning(warning, ...rest);
|
|
4844
|
+
});
|
|
3249
4845
|
var args = process.argv.slice(2);
|
|
3250
4846
|
var interval;
|
|
4847
|
+
var asciiFlag = null;
|
|
3251
4848
|
for (let i = 0; i < args.length; i++) {
|
|
3252
4849
|
if ((args[i] === "--interval" || args[i] === "-i") && args[i + 1]) {
|
|
3253
4850
|
interval = Math.max(500, Number(args[i + 1]) * 1e3);
|
|
3254
4851
|
i++;
|
|
3255
4852
|
}
|
|
4853
|
+
if (args[i] === "--ascii") asciiFlag = "on";
|
|
4854
|
+
if (args[i] === "--no-ascii") asciiFlag = "off";
|
|
3256
4855
|
if (args[i] === "--help" || args[i] === "-h") {
|
|
3257
|
-
console.log("tokmon - Terminal dashboard for
|
|
4856
|
+
console.log("tokmon - Terminal usage dashboard for your AI coding tools\n");
|
|
4857
|
+
console.log(" Claude \xB7 Codex \xB7 Cursor \xB7 Copilot \xB7 opencode \xB7 pi \xB7 Antigravity \xB7 Gemini\n");
|
|
3258
4858
|
console.log("Usage: tokmon [options]\n");
|
|
3259
4859
|
console.log("Options:");
|
|
3260
4860
|
console.log(" -i, --interval <seconds> Refresh interval (default: from config or 2)");
|
|
4861
|
+
console.log(" --ascii Force ASCII glyphs (also: TOKMON_ASCII=1)");
|
|
4862
|
+
console.log(" --no-ascii Force Unicode glyphs");
|
|
3261
4863
|
console.log(" -h, --help Show this help\n");
|
|
3262
4864
|
console.log("Keybindings:");
|
|
3263
4865
|
console.log(" Tab Switch Dashboard / Table");
|
|
@@ -3274,6 +4876,13 @@ var config = await loadConfig();
|
|
|
3274
4876
|
if (config.clearScreen && process.stdout.isTTY) {
|
|
3275
4877
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
3276
4878
|
}
|
|
3277
|
-
|
|
4879
|
+
setGlyphs(resolveGlyphs({
|
|
4880
|
+
flag: asciiFlag,
|
|
4881
|
+
env: process.env,
|
|
4882
|
+
config: config.ascii,
|
|
4883
|
+
isTTY: !!process.stdout.isTTY,
|
|
4884
|
+
platform: process.platform
|
|
4885
|
+
}));
|
|
4886
|
+
var { waitUntilExit } = render(/* @__PURE__ */ jsx8(MouseProvider, { children: /* @__PURE__ */ jsx8(App, { interval }) }));
|
|
3278
4887
|
await waitUntilExit();
|
|
3279
4888
|
await flushDisk();
|