tokmon 0.12.2 → 0.12.4

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 +141 -69
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -434,16 +434,15 @@ async function readMacKeychain() {
434
434
  }
435
435
  }
436
436
  async function getAccessToken(homeDir) {
437
- if (homeDir) {
438
- const fromFile = await readCredentialsFile(homeDir);
439
- if (fromFile) return fromFile;
440
- return null;
441
- }
442
- if (process.platform === "darwin") {
443
- const token = await readMacKeychain();
444
- if (token) return token;
437
+ const isDefault = !homeDir || homeDir === homedir3();
438
+ if (isDefault) {
439
+ if (process.platform === "darwin") {
440
+ const token = await readMacKeychain();
441
+ if (token) return token;
442
+ }
443
+ return readCredentialsFile(homeDir);
445
444
  }
446
- return readCredentialsFile();
445
+ return readCredentialsFile(homeDir);
447
446
  }
448
447
  var EMPTY = { session: null, weekly: null, sonnet: null, extraUsage: null, peak: null, error: null };
449
448
  async function fetchBilling(homeDir) {
@@ -485,8 +484,9 @@ async function fetchUsage(token) {
485
484
  resetsAt: formatReset(data.seven_day_sonnet.resets_at)
486
485
  } : null,
487
486
  extraUsage: data.extra_usage?.is_enabled ? {
488
- limit: data.extra_usage.monthly_limit / 100,
489
- used: data.extra_usage.used_credits / 100
487
+ limit: data.extra_usage.monthly_limit != null ? data.extra_usage.monthly_limit / 100 : null,
488
+ used: (data.extra_usage.used_credits ?? 0) / 100,
489
+ currency: data.extra_usage.currency ?? "USD"
490
490
  } : null
491
491
  }
492
492
  };
@@ -580,6 +580,9 @@ var DEFAULT_CONFIG = {
580
580
  };
581
581
  var GENERAL_ROWS = 4;
582
582
  var IS_TTY = process.stdin.isTTY === true;
583
+ function truncateName(s, n) {
584
+ return s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
585
+ }
583
586
  function buildSlots(config2) {
584
587
  const slots = [];
585
588
  if (config2.accounts.length === 0) {
@@ -638,27 +641,30 @@ function App({ interval: cliInterval }) {
638
641
  setConfig(c);
639
642
  });
640
643
  }, []);
644
+ const billingMs = cfg.billingInterval * 6e4;
645
+ const dataSlots = cfg.accounts.length > 0 ? slots.slice(1) : slots;
646
+ const dataSlotsKey = dataSlots.map((s) => s.id ?? "__default__").join(",");
641
647
  useEffect(() => {
642
648
  if (!config2) return;
643
649
  let active = true;
644
- const slotsToLoad = activeSlot.id === null && cfg.accounts.length > 0 ? slots.slice(1) : [activeSlot];
645
650
  const load = async () => {
646
- try {
647
- const next = /* @__PURE__ */ new Map();
648
- await Promise.all(slotsToLoad.map(async (slot) => {
649
- const [dashboard, billing] = await Promise.all([
650
- fetchDashboard(tz, slot.homeDir),
651
- fetchBilling(slot.homeDir)
652
- ]);
653
- next.set(slotKey(slot), { slot, dashboard, billing });
654
- }));
655
- if (active) {
656
- setStats(next);
657
- setError(null);
658
- setUpdated(/* @__PURE__ */ new Date());
651
+ await Promise.all(dataSlots.map(async (slot) => {
652
+ try {
653
+ const dashboard = await fetchDashboard(tz, slot.homeDir);
654
+ if (!active) return;
655
+ setStats((prev) => {
656
+ const next = new Map(prev);
657
+ const cur = next.get(slotKey(slot)) ?? { slot, dashboard: null, billing: null };
658
+ next.set(slotKey(slot), { ...cur, slot, dashboard });
659
+ return next;
660
+ });
661
+ } catch (e) {
662
+ if (active) setError(e instanceof Error ? e.message : String(e));
659
663
  }
660
- } catch (e) {
661
- if (active) setError(e instanceof Error ? e.message : String(e));
664
+ }));
665
+ if (active) {
666
+ setError(null);
667
+ setUpdated(/* @__PURE__ */ new Date());
662
668
  }
663
669
  };
664
670
  load();
@@ -667,7 +673,32 @@ function App({ interval: cliInterval }) {
667
673
  active = false;
668
674
  clearInterval(id);
669
675
  };
670
- }, [interval2, tz, config2, activeSlot.id]);
676
+ }, [interval2, tz, config2, dataSlotsKey]);
677
+ useEffect(() => {
678
+ if (!config2) return;
679
+ let active = true;
680
+ const load = async () => {
681
+ await Promise.all(dataSlots.map(async (slot) => {
682
+ try {
683
+ const billing = await fetchBilling(slot.homeDir);
684
+ if (!active) return;
685
+ setStats((prev) => {
686
+ const next = new Map(prev);
687
+ const cur = next.get(slotKey(slot)) ?? { slot, dashboard: null, billing: null };
688
+ next.set(slotKey(slot), { ...cur, slot, billing });
689
+ return next;
690
+ });
691
+ } catch {
692
+ }
693
+ }));
694
+ };
695
+ load();
696
+ const id = setInterval(load, billingMs);
697
+ return () => {
698
+ active = false;
699
+ clearInterval(id);
700
+ };
701
+ }, [billingMs, config2, dataSlotsKey]);
671
702
  useEffect(() => {
672
703
  tableLoadedOnce.current = false;
673
704
  setTable(null);
@@ -813,6 +844,21 @@ function App({ interval: cliInterval }) {
813
844
  activeAccountId: c.activeAccountId === id ? null : c.activeAccountId
814
845
  }));
815
846
  }
847
+ function moveAccount(idx, dir) {
848
+ updateConfig((c) => {
849
+ const next = [...c.accounts];
850
+ const target = idx + dir;
851
+ if (target < 0 || target >= next.length) return c;
852
+ [next[idx], next[target]] = [next[target], next[idx]];
853
+ return { ...c, accounts: next };
854
+ });
855
+ setSettingsCursor((c) => {
856
+ const target = c + dir;
857
+ const min = accountRowsStart;
858
+ const max = accountRowsStart + cfg.accounts.length - 1;
859
+ return Math.max(min, Math.min(max, target));
860
+ });
861
+ }
816
862
  const accountRowsStart = GENERAL_ROWS;
817
863
  const totalSettingsRows = GENERAL_ROWS + cfg.accounts.length + 1;
818
864
  useInput((input, key) => {
@@ -915,6 +961,12 @@ function App({ interval: cliInterval }) {
915
961
  setShowSettings(false);
916
962
  return;
917
963
  }
964
+ const accIdxNav = settingsCursor - accountRowsStart;
965
+ const onAccountRow = accIdxNav >= 0 && accIdxNav < cfg.accounts.length;
966
+ if (onAccountRow && key.shift && (key.upArrow || key.downArrow)) {
967
+ moveAccount(accIdxNav, key.upArrow ? -1 : 1);
968
+ return;
969
+ }
918
970
  if (key.upArrow) {
919
971
  setSettingsCursor((c) => Math.max(0, c - 1));
920
972
  return;
@@ -1103,19 +1155,7 @@ function App({ interval: cliInterval }) {
1103
1155
  activeAccountId: cfg.activeAccountId
1104
1156
  }
1105
1157
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1106
- slots.length > 1 && /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(
1107
- AccountStrip,
1108
- {
1109
- slots,
1110
- activeIdx: activeSlotIdx,
1111
- onSelect: (i) => {
1112
- const id = slots[i].id;
1113
- updateConfig((c) => ({ ...c, activeAccountId: id }));
1114
- resetView();
1115
- }
1116
- }
1117
- ) }),
1118
- /* @__PURE__ */ jsxs(Box, { marginTop: slots.length > 1 ? 0 : 1, children: [
1158
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
1119
1159
  /* @__PURE__ */ jsx(TabBar, { tabs: TABS, active: tab, onSelect: (i) => {
1120
1160
  setTab(i);
1121
1161
  resetView();
@@ -1123,14 +1163,31 @@ function App({ interval: cliInterval }) {
1123
1163
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab/\u2190\u2192" })
1124
1164
  ] }),
1125
1165
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1126
- tab === 0 && /* @__PURE__ */ jsx(
1127
- DashboardView,
1128
- {
1129
- slots: visibleSlots,
1130
- stats,
1131
- compact: visibleSlots.length > 1
1132
- }
1133
- ),
1166
+ tab === 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1167
+ /* @__PURE__ */ jsx(
1168
+ DashboardView,
1169
+ {
1170
+ slots: visibleSlots,
1171
+ stats,
1172
+ compact: visibleSlots.length > 1
1173
+ }
1174
+ ),
1175
+ slots.length > 1 && /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
1176
+ /* @__PURE__ */ jsx(Text, { bold: true, children: "Accounts" }),
1177
+ /* @__PURE__ */ jsx(Box, { marginTop: 0, children: /* @__PURE__ */ jsx(
1178
+ AccountStrip,
1179
+ {
1180
+ slots,
1181
+ activeIdx: activeSlotIdx,
1182
+ onSelect: (i) => {
1183
+ const id = slots[i].id;
1184
+ updateConfig((c) => ({ ...c, activeAccountId: id }));
1185
+ resetView();
1186
+ }
1187
+ }
1188
+ ) })
1189
+ ] })
1190
+ ] }),
1134
1191
  tab === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
1135
1192
  /* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view, sort: SORTS[sort], onSelect: (i) => {
1136
1193
  setView(i);
@@ -1346,7 +1403,7 @@ function SettingsView({
1346
1403
  /* @__PURE__ */ jsx(Text, { children: "Add account" })
1347
1404
  ] }),
1348
1405
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1349
- editingTz ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "type IANA name (e.g. Europe/London) \xB7 empty = System \xB7 Enter save \xB7 Esc cancel" }) : cursor >= accountRowsStart && cursor < accountRowsStart + config2.accounts.length ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \xB7 Enter edit \xB7 space activate \xB7 d delete \xB7 s/Esc close" }) : cursor === accountRowsStart + config2.accounts.length ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \xB7 Enter add account \xB7 s/Esc close" }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \u2190\u2192 adjust Enter edit s/Esc close" })
1406
+ editingTz ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "type IANA name (e.g. Europe/London) \xB7 empty = System \xB7 Enter save \xB7 Esc cancel" }) : cursor >= accountRowsStart && cursor < accountRowsStart + config2.accounts.length ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \xB7 \u21E7\u2191\u2193 reorder \xB7 Enter edit \xB7 space activate \xB7 d delete \xB7 s/Esc close" }) : cursor === accountRowsStart + config2.accounts.length ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \xB7 Enter add account \xB7 s/Esc close" }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \u2190\u2192 adjust Enter edit s/Esc close" })
1350
1407
  ] });
1351
1408
  }
1352
1409
  function AccountFormView({ form, accounts }) {
@@ -1622,7 +1679,7 @@ function RateLimitsCard({ items }) {
1622
1679
  /* @__PURE__ */ jsx(Box, { height: 1 }),
1623
1680
  !anyData ? anyError ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map(({ slot, s }) => s.billing?.error && /* @__PURE__ */ jsxs(Box, { children: [
1624
1681
  /* @__PURE__ */ jsx(Text, { color: slot.color, children: "\u25CF " }),
1625
- /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: slot.name }) }),
1682
+ /* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateName(slot.name, 20) }) }),
1626
1683
  /* @__PURE__ */ jsx(Text, { color: "red", children: s.billing.error })
1627
1684
  ] }, slot.id ?? "__default__")) }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetching..." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
1628
1685
  /* @__PURE__ */ jsx(MetricBlock, { label: "Total Usage", sublabel: "5h session", pick: (b) => b?.session, items, showResets: true }),
@@ -1633,17 +1690,19 @@ function RateLimitsCard({ items }) {
1633
1690
  items.map(({ slot, s }) => {
1634
1691
  const e = s.billing?.extraUsage;
1635
1692
  if (!e) return null;
1693
+ const sym = e.currency === "EUR" ? "\u20AC" : "$";
1636
1694
  return /* @__PURE__ */ jsxs(Box, { children: [
1637
1695
  /* @__PURE__ */ jsx(Text, { color: slot.color, children: "\u25CF " }),
1638
- /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: slot.name }) }),
1696
+ /* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateName(slot.name, 20) }) }),
1639
1697
  /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
1640
- "$",
1698
+ sym,
1641
1699
  e.used.toFixed(2)
1642
1700
  ] }),
1643
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1644
- " / $",
1701
+ e.limit != null ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1702
+ " / ",
1703
+ sym,
1645
1704
  e.limit.toFixed(2)
1646
- ] })
1705
+ ] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: " used" })
1647
1706
  ] }, slot.id ?? "__default__");
1648
1707
  })
1649
1708
  ] })
@@ -1659,8 +1718,12 @@ function MetricBlock({
1659
1718
  items,
1660
1719
  showResets
1661
1720
  }) {
1662
- const rows = items.map((i) => ({ slot: i.slot, lim: pick(i.s.billing) ?? null })).filter((r) => r.lim !== null);
1663
- if (rows.length === 0) return null;
1721
+ const rows = items.map((i) => ({
1722
+ slot: i.slot,
1723
+ lim: pick(i.s.billing) ?? null,
1724
+ error: i.s.billing?.error ?? null
1725
+ }));
1726
+ if (rows.every((r) => r.lim === null && !r.error)) return null;
1664
1727
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
1665
1728
  /* @__PURE__ */ jsxs(Box, { children: [
1666
1729
  /* @__PURE__ */ jsx(Text, { bold: true, children: label }),
@@ -1669,16 +1732,25 @@ function MetricBlock({
1669
1732
  sublabel
1670
1733
  ] })
1671
1734
  ] }),
1672
- rows.map(({ slot, lim }) => /* @__PURE__ */ jsx(
1673
- AccountLimitBar,
1674
- {
1675
- slot,
1676
- pct: lim.utilization,
1677
- resets: showResets ? lim.resetsAt : null,
1678
- showName: items.length > 1
1679
- },
1680
- slot.id ?? "__default__"
1681
- ))
1735
+ rows.map(({ slot, lim, error }) => {
1736
+ if (lim) {
1737
+ return /* @__PURE__ */ jsx(
1738
+ AccountLimitBar,
1739
+ {
1740
+ slot,
1741
+ pct: lim.utilization,
1742
+ resets: showResets ? lim.resetsAt : null,
1743
+ showName: items.length > 1
1744
+ },
1745
+ slot.id ?? "__default__"
1746
+ );
1747
+ }
1748
+ return /* @__PURE__ */ jsxs(Box, { children: [
1749
+ /* @__PURE__ */ jsx(Text, { color: slot.color, children: "\u25CF " }),
1750
+ /* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateName(slot.name, 20) }) }),
1751
+ /* @__PURE__ */ jsx(Text, { color: error ? "red" : void 0, dimColor: !error, children: error ?? "no data" })
1752
+ ] }, slot.id ?? "__default__");
1753
+ })
1682
1754
  ] });
1683
1755
  }
1684
1756
  function AccountLimitBar({
@@ -1691,7 +1763,7 @@ function AccountLimitBar({
1691
1763
  const filled = Math.max(0, Math.min(width, Math.round(pct / 100 * width)));
1692
1764
  return /* @__PURE__ */ jsxs(Box, { children: [
1693
1765
  /* @__PURE__ */ jsx(Text, { color: slot.color, children: "\u25CF " }),
1694
- showName ? /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: slot.name }) }) : /* @__PURE__ */ jsx(Box, { width: 2, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
1766
+ showName ? /* @__PURE__ */ jsx(Box, { width: 22, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateName(slot.name, 20) }) }) : /* @__PURE__ */ jsx(Box, { width: 2, children: /* @__PURE__ */ jsx(Text, { children: " " }) }),
1695
1767
  /* @__PURE__ */ jsx(Text, { color: slot.color, children: "\u2501".repeat(filled) }),
1696
1768
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(width - filled) }),
1697
1769
  /* @__PURE__ */ jsx(Text, { children: " " }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "description": "Terminal dashboard for Claude Code usage and costs",
5
5
  "type": "module",
6
6
  "bin": {