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.
Files changed (2) hide show
  1. package/dist/cli.js +82 -36
  2. 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 null;
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 (!res.ok) return null;
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 null;
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 && b) setBilling(b);
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(TableView, { rows: tableData, cursor, expanded, maxRows: rows - 12, wide: cols > 90 })
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(Box, { marginRight: 1, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, inverse: true, children: [
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(Box, { marginRight: 2, children: i === active ? /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
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: " d/w/m \xB7 sort: " }),
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
- billing && /* @__PURE__ */ jsxs(Fragment, { children: [
713
- /* @__PURE__ */ jsx(Box, { height: 1 }),
714
- /* @__PURE__ */ jsxs(
715
- Box,
716
- {
717
- flexDirection: "column",
718
- paddingLeft: 1,
719
- borderStyle: "bold",
720
- borderColor: "yellow",
721
- borderRight: false,
722
- borderTop: false,
723
- borderBottom: false,
724
- children: [
725
- /* @__PURE__ */ jsx(Text, { bold: true, children: "Rate Limits" }),
726
- /* @__PURE__ */ jsx(Box, { height: 1 }),
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.7.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
  },