tokmon 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +82 -36
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.tsx
|
|
4
4
|
import { render } from "ink";
|
|
5
|
+
import { MouseProvider } from "@zenobius/ink-mouse";
|
|
5
6
|
|
|
6
7
|
// src/config.ts
|
|
7
8
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
@@ -36,6 +37,7 @@ async function saveConfig(config2) {
|
|
|
36
37
|
// src/app.tsx
|
|
37
38
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
38
39
|
import { Box, Text, useInput, useStdout, useApp } from "ink";
|
|
40
|
+
import { useMouse, useOnMouseClick } from "@zenobius/ink-mouse";
|
|
39
41
|
|
|
40
42
|
// src/data.ts
|
|
41
43
|
import { readdir, stat as fsStat } from "fs/promises";
|
|
@@ -267,9 +269,10 @@ async function getAccessToken() {
|
|
|
267
269
|
}
|
|
268
270
|
return null;
|
|
269
271
|
}
|
|
272
|
+
var EMPTY = { session: null, weekly: null, sonnet: null, extraUsage: null, error: null };
|
|
270
273
|
async function fetchBilling() {
|
|
271
274
|
const token = await getAccessToken();
|
|
272
|
-
if (!token) return
|
|
275
|
+
if (!token) return { ...EMPTY, error: "No OAuth token found (macOS Keychain)" };
|
|
273
276
|
try {
|
|
274
277
|
const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
275
278
|
headers: {
|
|
@@ -279,7 +282,9 @@ async function fetchBilling() {
|
|
|
279
282
|
},
|
|
280
283
|
signal: AbortSignal.timeout(1e4)
|
|
281
284
|
});
|
|
282
|
-
if (
|
|
285
|
+
if (res.status === 429) return { ...EMPTY, error: "Rate limited \u2014 retrying in 2m" };
|
|
286
|
+
if (res.status === 401) return { ...EMPTY, error: "Token expired \u2014 restart Claude Code" };
|
|
287
|
+
if (!res.ok) return { ...EMPTY, error: `API ${res.status}` };
|
|
283
288
|
const data = await res.json();
|
|
284
289
|
return {
|
|
285
290
|
session: data.five_hour ? {
|
|
@@ -297,10 +302,11 @@ async function fetchBilling() {
|
|
|
297
302
|
extraUsage: data.extra_usage?.is_enabled ? {
|
|
298
303
|
limit: data.extra_usage.monthly_limit / 100,
|
|
299
304
|
used: data.extra_usage.used_credits / 100
|
|
300
|
-
} : null
|
|
305
|
+
} : null,
|
|
306
|
+
error: null
|
|
301
307
|
};
|
|
302
308
|
} catch {
|
|
303
|
-
return
|
|
309
|
+
return { ...EMPTY, error: "Network error" };
|
|
304
310
|
}
|
|
305
311
|
}
|
|
306
312
|
function formatReset(iso) {
|
|
@@ -407,7 +413,7 @@ function App({ interval: cliInterval }) {
|
|
|
407
413
|
useEffect(() => {
|
|
408
414
|
let active = true;
|
|
409
415
|
const load = () => fetchBilling().then((b) => {
|
|
410
|
-
if (active
|
|
416
|
+
if (active) setBilling(b);
|
|
411
417
|
}).catch(() => {
|
|
412
418
|
});
|
|
413
419
|
load();
|
|
@@ -454,6 +460,20 @@ function App({ interval: cliInterval }) {
|
|
|
454
460
|
setCursor(0);
|
|
455
461
|
setExpanded(-1);
|
|
456
462
|
}, []);
|
|
463
|
+
const mouse = useMouse();
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (!IS_TTY) return;
|
|
466
|
+
mouse.enable();
|
|
467
|
+
const onScroll = (_pos, dir) => {
|
|
468
|
+
if (tab === 1) {
|
|
469
|
+
setCursor((c) => dir === "scrollup" ? Math.max(0, c - 3) : c + 3);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
mouse.events.on("scroll", onScroll);
|
|
473
|
+
return () => {
|
|
474
|
+
mouse.events.off("scroll", onScroll);
|
|
475
|
+
};
|
|
476
|
+
}, [tab]);
|
|
457
477
|
useInput((input, key) => {
|
|
458
478
|
if (showSettings) {
|
|
459
479
|
if (key.escape || input === "s") setShowSettings(false);
|
|
@@ -582,15 +602,34 @@ function App({ interval: cliInterval }) {
|
|
|
582
602
|
] }),
|
|
583
603
|
showSettings ? /* @__PURE__ */ jsx(SettingsView, { config: cfg, cursor: settingsCursor }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
584
604
|
/* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
|
|
585
|
-
/* @__PURE__ */ jsx(TabBar, { tabs: TABS, active: tab
|
|
605
|
+
/* @__PURE__ */ jsx(TabBar, { tabs: TABS, active: tab, onSelect: (i) => {
|
|
606
|
+
setTab(i);
|
|
607
|
+
resetView();
|
|
608
|
+
} }),
|
|
586
609
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab/\u2190\u2192" })
|
|
587
610
|
] }),
|
|
588
611
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
589
612
|
tab === 0 && /* @__PURE__ */ jsx(DashboardView, { data: dashboard, billing }),
|
|
590
613
|
tab === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
591
|
-
/* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view, sort: SORTS[sort]
|
|
614
|
+
/* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view, sort: SORTS[sort], onSelect: (i) => {
|
|
615
|
+
setView(i);
|
|
616
|
+
resetView();
|
|
617
|
+
} }),
|
|
592
618
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
593
|
-
tableLoading && !table ? /* @__PURE__ */ jsx(Spinner, { label: "Loading 6 months of history" }) : /* @__PURE__ */ jsx(
|
|
619
|
+
tableLoading && !table ? /* @__PURE__ */ jsx(Spinner, { label: "Loading 6 months of history" }) : /* @__PURE__ */ jsx(
|
|
620
|
+
TableView,
|
|
621
|
+
{
|
|
622
|
+
rows: tableData,
|
|
623
|
+
cursor,
|
|
624
|
+
expanded,
|
|
625
|
+
maxRows: rows - 12,
|
|
626
|
+
wide: cols > 90,
|
|
627
|
+
onRowClick: (idx) => {
|
|
628
|
+
if (idx === cursor) setExpanded((e) => e === idx ? -1 : idx);
|
|
629
|
+
else setCursor(idx);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
)
|
|
594
633
|
] })
|
|
595
634
|
] }),
|
|
596
635
|
(tab === 0 || showSettings) && /* @__PURE__ */ jsx(Footer, {})
|
|
@@ -605,8 +644,8 @@ function Footer() {
|
|
|
605
644
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ") \xB7 s=settings q=quit" })
|
|
606
645
|
] });
|
|
607
646
|
}
|
|
608
|
-
function TabBar({ tabs, active }) {
|
|
609
|
-
return /* @__PURE__ */ jsx(Box, { children: tabs.map((t, i) => /* @__PURE__ */ jsx(
|
|
647
|
+
function TabBar({ tabs, active, onSelect }) {
|
|
648
|
+
return /* @__PURE__ */ jsx(Box, { children: tabs.map((t, i) => /* @__PURE__ */ jsx(ClickableBox, { onClick: () => onSelect(i), marginRight: 1, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, inverse: true, children: [
|
|
610
649
|
" ",
|
|
611
650
|
t,
|
|
612
651
|
" "
|
|
@@ -616,14 +655,14 @@ function TabBar({ tabs, active }) {
|
|
|
616
655
|
" "
|
|
617
656
|
] }) }, t)) });
|
|
618
657
|
}
|
|
619
|
-
function ViewBar({ views, active, sort }) {
|
|
658
|
+
function ViewBar({ views, active, sort, onSelect }) {
|
|
620
659
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
621
|
-
views.map((v, i) => /* @__PURE__ */ jsx(
|
|
660
|
+
views.map((v, i) => /* @__PURE__ */ jsx(ClickableBox, { onClick: () => onSelect(i), marginRight: 2, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
|
|
622
661
|
"[",
|
|
623
662
|
v,
|
|
624
663
|
"]"
|
|
625
664
|
] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: v }) }, v)),
|
|
626
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "
|
|
665
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " sort: " }),
|
|
627
666
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "magenta", children: sort }),
|
|
628
667
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " o=cycle" })
|
|
629
668
|
] });
|
|
@@ -709,21 +748,21 @@ function DashboardView({ data, billing }) {
|
|
|
709
748
|
]
|
|
710
749
|
}
|
|
711
750
|
),
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
children:
|
|
725
|
-
|
|
726
|
-
|
|
751
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
752
|
+
/* @__PURE__ */ jsxs(
|
|
753
|
+
Box,
|
|
754
|
+
{
|
|
755
|
+
flexDirection: "column",
|
|
756
|
+
paddingLeft: 1,
|
|
757
|
+
borderStyle: "bold",
|
|
758
|
+
borderColor: billing?.error ? "red" : "yellow",
|
|
759
|
+
borderRight: false,
|
|
760
|
+
borderTop: false,
|
|
761
|
+
borderBottom: false,
|
|
762
|
+
children: [
|
|
763
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Rate Limits" }),
|
|
764
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
765
|
+
billing?.error ? /* @__PURE__ */ jsx(Text, { color: "red", children: billing.error }) : billing?.session || billing?.weekly ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
727
766
|
billing.session && /* @__PURE__ */ jsx(LimitBar, { label: "Session", pct: billing.session.utilization, resets: billing.session.resetsAt }),
|
|
728
767
|
billing.weekly && /* @__PURE__ */ jsx(LimitBar, { label: "Weekly", pct: billing.weekly.utilization, resets: billing.weekly.resetsAt }),
|
|
729
768
|
billing.sonnet && /* @__PURE__ */ jsx(LimitBar, { label: "Sonnet", pct: billing.sonnet.utilization, resets: billing.sonnet.resetsAt }),
|
|
@@ -739,10 +778,10 @@ function DashboardView({ data, billing }) {
|
|
|
739
778
|
" limit"
|
|
740
779
|
] })
|
|
741
780
|
] })
|
|
742
|
-
]
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
781
|
+
] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetching..." })
|
|
782
|
+
]
|
|
783
|
+
}
|
|
784
|
+
)
|
|
746
785
|
] });
|
|
747
786
|
}
|
|
748
787
|
function LimitBar({ label, pct, resets }) {
|
|
@@ -774,7 +813,7 @@ function SummaryRow({ label, summary }) {
|
|
|
774
813
|
] }) })
|
|
775
814
|
] });
|
|
776
815
|
}
|
|
777
|
-
function TableView({ rows: allRows, cursor, expanded, maxRows, wide }) {
|
|
816
|
+
function TableView({ rows: allRows, cursor, expanded, maxRows, wide, onRowClick }) {
|
|
778
817
|
const W = wide ? { label: 10, models: 18, input: 8, output: 8, cc: 8, cr: 9, total: 9, cost: 10 } : { label: 8, models: 14, input: 7, output: 7, cc: 7, cr: 8, total: 0, cost: 9 };
|
|
779
818
|
const lineW = W.label + W.models + W.input + W.output + W.cc + W.cr + W.total + W.cost;
|
|
780
819
|
const totals = { input: 0, output: 0, cacheCreate: 0, cacheRead: 0, cost: 0 };
|
|
@@ -808,7 +847,7 @@ function TableView({ rows: allRows, cursor, expanded, maxRows, wide }) {
|
|
|
808
847
|
const selected = idx === clampedCursor;
|
|
809
848
|
const isExpanded = idx === expanded;
|
|
810
849
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
811
|
-
/* @__PURE__ */ jsxs(Text, { inverse: selected, children: [
|
|
850
|
+
/* @__PURE__ */ jsx(ClickableBox, { onClick: () => onRowClick(idx), children: /* @__PURE__ */ jsxs(Text, { inverse: selected, children: [
|
|
812
851
|
/* @__PURE__ */ jsxs(Text, { color: selected ? void 0 : "cyan", children: [
|
|
813
852
|
selected ? "\u25B8 " : " ",
|
|
814
853
|
col(fmtLabel(r.label), W.label, "left")
|
|
@@ -820,7 +859,7 @@ function TableView({ rows: allRows, cursor, expanded, maxRows, wide }) {
|
|
|
820
859
|
/* @__PURE__ */ jsx(Text, { children: col(tokens(r.cacheRead), W.cr) }),
|
|
821
860
|
W.total > 0 && /* @__PURE__ */ jsx(Text, { children: col(tokens(r.total), W.total) }),
|
|
822
861
|
/* @__PURE__ */ jsx(Text, { bold: true, color: selected ? void 0 : "yellow", children: col(currency(r.cost), W.cost) })
|
|
823
|
-
] }),
|
|
862
|
+
] }) }),
|
|
824
863
|
isExpanded && /* @__PURE__ */ jsx(RowDetail, { row: r, indent: W.label + 2 })
|
|
825
864
|
] }, r.label);
|
|
826
865
|
}),
|
|
@@ -900,6 +939,13 @@ function fmtLabel(label) {
|
|
|
900
939
|
}
|
|
901
940
|
return shortDate(label);
|
|
902
941
|
}
|
|
942
|
+
function ClickableBox({ onClick, children, ...props }) {
|
|
943
|
+
const ref = useRef(null);
|
|
944
|
+
useOnMouseClick(ref, (clicked) => {
|
|
945
|
+
if (clicked) onClick();
|
|
946
|
+
});
|
|
947
|
+
return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
|
|
948
|
+
}
|
|
903
949
|
|
|
904
950
|
// src/cli.tsx
|
|
905
951
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
@@ -929,5 +975,5 @@ var config = await loadConfig();
|
|
|
929
975
|
if (config.clearScreen && process.stdout.isTTY) {
|
|
930
976
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
931
977
|
}
|
|
932
|
-
var { waitUntilExit } = render(/* @__PURE__ */ jsx2(App, { interval }));
|
|
978
|
+
var { waitUntilExit } = render(/* @__PURE__ */ jsx2(MouseProvider, { children: /* @__PURE__ */ jsx2(App, { interval }) }));
|
|
933
979
|
await waitUntilExit();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokmon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Terminal dashboard for Claude Code usage and costs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"dev": "tsx src/cli.tsx"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@zenobius/ink-mouse": "^1.0.3",
|
|
17
18
|
"ink": "^5.0.0",
|
|
18
19
|
"react": "^18.0.0"
|
|
19
20
|
},
|