tokmon 0.14.3 → 0.14.5

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 +120 -24
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -609,6 +609,7 @@ function glyphs() {
609
609
 
610
610
  // src/app.tsx
611
611
  import { spawn } from "child_process";
612
+ import { appendFileSync as appendFileSync2 } from "fs";
612
613
  import { useState as useState3, useEffect as useEffect3, useCallback, useRef as useRef2, useMemo } from "react";
613
614
  import { Box as Box7, Text as Text7, Transform, useInput, useStdout, useApp } from "ink";
614
615
  import { useMouse } from "@zenobius/ink-mouse";
@@ -704,7 +705,9 @@ async function detectClaude(homeDir) {
704
705
  }
705
706
  function priceFor(model) {
706
707
  for (const key of PRICE_KEYS) {
707
- if (model.startsWith(key)) return PRICING[key];
708
+ if (!model.startsWith(key)) continue;
709
+ const rest = model.slice(key.length);
710
+ if (rest === "" || rest[0] === "-") return PRICING[key];
708
711
  }
709
712
  return FALLBACK;
710
713
  }
@@ -1012,10 +1015,15 @@ function subtractClamped(cur, prev) {
1012
1015
  reasoning_output_tokens: sub(cur.reasoning_output_tokens, prev?.reasoning_output_tokens)
1013
1016
  };
1014
1017
  }
1018
+ function eventSig(last, total) {
1019
+ const f = (x) => x ? `${x.input_tokens ?? 0},${x.cached_input_tokens ?? 0},${x.output_tokens ?? 0},${x.reasoning_output_tokens ?? 0}` : "-";
1020
+ return `${f(last)}|${f(total)}`;
1021
+ }
1015
1022
  async function parseFile2(path) {
1016
1023
  const entries = [];
1017
1024
  let model = "gpt-5-codex";
1018
1025
  let prevTotal = null;
1026
+ let prevSig = null;
1019
1027
  const rl = createInterface2({ input: createReadStream2(path), crlfDelay: Infinity });
1020
1028
  for await (const rawLine of rl) {
1021
1029
  if (!rawLine.includes("token_count") && !rawLine.includes("turn_context")) continue;
@@ -1031,7 +1039,11 @@ async function parseFile2(path) {
1031
1039
  if (payloadType !== "token_count") continue;
1032
1040
  const info = obj?.payload?.info;
1033
1041
  const total = info?.total_token_usage;
1034
- let d = info?.last_token_usage;
1042
+ const last = info?.last_token_usage;
1043
+ const sig = eventSig(last, total);
1044
+ if (sig === prevSig) continue;
1045
+ prevSig = sig;
1046
+ let d = last;
1035
1047
  if (!d && total) {
1036
1048
  const reset = !!prevTotal && (total.input_tokens ?? 0) < (prevTotal.input_tokens ?? 0);
1037
1049
  d = reset ? total : subtractClamped(total, prevTotal);
@@ -1621,9 +1633,10 @@ async function parseFile3(path) {
1621
1633
  const c = u.cost ?? {};
1622
1634
  const costInput = pos(c.input);
1623
1635
  const cacheSavings = input > 0 && cacheRead > 0 ? Math.max(0, cacheRead * (costInput / input) - pos(c.cacheRead)) : 0;
1636
+ const model = typeof msg.responseModel === "string" && msg.responseModel || typeof msg.model === "string" && msg.model || "unknown";
1624
1637
  entries.push({
1625
1638
  ts,
1626
- model: typeof msg.model === "string" && msg.model ? msg.model : "unknown",
1639
+ model,
1627
1640
  cost: pos(c.total),
1628
1641
  input,
1629
1642
  output,
@@ -1914,6 +1927,7 @@ function resetDate(value) {
1914
1927
  }
1915
1928
  function percentMetric2(label, snapshot, reset, primary) {
1916
1929
  if (!snapshot || typeof snapshot.percent_remaining !== "number") return null;
1930
+ if (snapshot.unlimited === true || snapshot.entitlement === 0) return null;
1917
1931
  const used = Math.min(100, Math.max(0, 100 - snapshot.percent_remaining));
1918
1932
  return { label, used, limit: 100, format: { kind: "percent" }, resetsAt: reset, primary };
1919
1933
  }
@@ -2244,9 +2258,10 @@ async function requestCloudCodeJson(path, token, body) {
2244
2258
  }
2245
2259
  function readPlan(loadData) {
2246
2260
  const paid = typeof loadData?.paidTier?.name === "string" ? loadData.paidTier.name.trim() : "";
2247
- if (paid) return paid;
2248
2261
  const current = typeof loadData?.currentTier?.name === "string" ? loadData.currentTier.name.trim() : "";
2249
- return current || null;
2262
+ const raw = paid || current;
2263
+ if (!raw) return null;
2264
+ return raw.replace(/^Gemini Code Assist (?:in|for)\s+/i, "").replace(/^Gemini Code Assist$/i, "Code Assist");
2250
2265
  }
2251
2266
  function parseBuckets(data) {
2252
2267
  if (!Array.isArray(data?.buckets)) return [];
@@ -2321,8 +2336,8 @@ function normalizeLabel(label) {
2321
2336
  }
2322
2337
  function poolLabel(label) {
2323
2338
  const lower = normalizeLabel(label).toLowerCase();
2324
- if (lower.includes("gemini") && lower.includes("pro")) return "Gemini Pro";
2325
- if (lower.includes("gemini") && lower.includes("flash")) return "Gemini Flash";
2339
+ if (lower.includes("gemini") && lower.includes("pro")) return "Pro";
2340
+ if (lower.includes("gemini") && lower.includes("flash")) return "Flash";
2326
2341
  return "Claude";
2327
2342
  }
2328
2343
  function sortKey(label) {
@@ -2667,7 +2682,8 @@ function saveSnapshot(stats) {
2667
2682
  }
2668
2683
 
2669
2684
  // src/ui/shared.tsx
2670
- import { useState, useEffect, useRef } from "react";
2685
+ import { appendFileSync } from "fs";
2686
+ import { useEffect, useRef, useState } from "react";
2671
2687
  import { Box, Text } from "ink";
2672
2688
  import { useOnMouseClick } from "@zenobius/ink-mouse";
2673
2689
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -2682,6 +2698,74 @@ function ClickableBox({ onClick, children, ...props }) {
2682
2698
  });
2683
2699
  return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
2684
2700
  }
2701
+ var SGR_PRESS = /\x1b\[<(\d+);(\d+);(\d+)M/g;
2702
+ var linkHits = /* @__PURE__ */ new Set();
2703
+ function dispatchLinkClicks(chunk) {
2704
+ if (linkHits.size === 0) return;
2705
+ const s = typeof chunk === "string" ? chunk : chunk.toString("utf8");
2706
+ SGR_PRESS.lastIndex = 0;
2707
+ let m;
2708
+ while ((m = SGR_PRESS.exec(s)) !== null) {
2709
+ const code = Number(m[1]);
2710
+ if (code & 64 || code & 32) continue;
2711
+ const mx = Number(m[2]) - 1;
2712
+ const my = Number(m[3]) - 1;
2713
+ if (process.env.TOKMON_LINKDEBUG) {
2714
+ try {
2715
+ appendFileSync(process.env.TOKMON_LINKDEBUG, `DISPATCH code=${code} mx=${mx} my=${my} hits=${linkHits.size}
2716
+ `);
2717
+ } catch {
2718
+ }
2719
+ }
2720
+ for (const hit of [...linkHits]) {
2721
+ if (hit(mx, my)) break;
2722
+ }
2723
+ }
2724
+ }
2725
+ function nodeBox(node) {
2726
+ const yn = node?.yogaNode;
2727
+ if (!yn) return null;
2728
+ const l = yn.getComputedLayout();
2729
+ let left = l.left, top = l.top;
2730
+ let p = node?.parentNode;
2731
+ while (p) {
2732
+ const pn = p.yogaNode;
2733
+ if (!pn) break;
2734
+ const pl = pn.getComputedLayout();
2735
+ left += pl.left;
2736
+ top += pl.top;
2737
+ p = p.parentNode;
2738
+ }
2739
+ return { left, top, width: l.width, height: l.height };
2740
+ }
2741
+ function LinkBox({ onClick, children, ...props }) {
2742
+ const ref = useRef(null);
2743
+ const onClickRef = useRef(onClick);
2744
+ onClickRef.current = onClick;
2745
+ useEffect(() => {
2746
+ const hit = (mx, my) => {
2747
+ const box = nodeBox(ref.current);
2748
+ if (process.env.TOKMON_LINKDEBUG) {
2749
+ try {
2750
+ appendFileSync(process.env.TOKMON_LINKDEBUG, `HIT? mx=${mx} my=${my} box=${box ? `${box.left},${box.top} ${box.width}x${box.height}` : "null"}
2751
+ `);
2752
+ } catch {
2753
+ }
2754
+ }
2755
+ if (!box || box.width <= 0 || box.height <= 0) return false;
2756
+ if (mx >= box.left && mx < box.left + box.width && my >= box.top && my < box.top + box.height) {
2757
+ onClickRef.current();
2758
+ return true;
2759
+ }
2760
+ return false;
2761
+ };
2762
+ linkHits.add(hit);
2763
+ return () => {
2764
+ linkHits.delete(hit);
2765
+ };
2766
+ }, []);
2767
+ return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
2768
+ }
2685
2769
  function Spinner({ label }) {
2686
2770
  const frames = glyphs().spinner;
2687
2771
  const [i, setI] = useState(0);
@@ -2889,14 +2973,6 @@ function ProviderCard({ provider, accounts, stats, width, variant }) {
2889
2973
  /* @__PURE__ */ jsx2(Text2, { color: meta.color, children: sparkline(activity.series) }),
2890
2974
  /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, justifyContent: "flex-end", children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: activity.summary }) })
2891
2975
  ] })
2892
- ] }),
2893
- !meta.hasUsage && !activity && variant === "full" && /* @__PURE__ */ jsxs2(Fragment, { children: [
2894
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1 }),
2895
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
2896
- "Billing only ",
2897
- glyphs().emDash,
2898
- " no local history"
2899
- ] })
2900
2976
  ] })
2901
2977
  ] });
2902
2978
  }
@@ -2974,7 +3050,7 @@ function MetricRow({ m, color, barW }) {
2974
3050
  if (m.format.kind === "percent") {
2975
3051
  const barColor = m.used >= 90 ? "red" : m.used >= 75 ? "yellow" : color;
2976
3052
  return /* @__PURE__ */ jsxs2(Box2, { children: [
2977
- /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: m.label }) }),
3053
+ /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: m.label }) }),
2978
3054
  /* @__PURE__ */ jsx2(Bar, { pct: m.used, color: barColor, width: barW }),
2979
3055
  /* @__PURE__ */ jsx2(Box2, { width: 5, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { bold: true, children: [
2980
3056
  Math.round(m.used),
@@ -2984,7 +3060,7 @@ function MetricRow({ m, color, barW }) {
2984
3060
  ] });
2985
3061
  }
2986
3062
  return /* @__PURE__ */ jsxs2(Box2, { children: [
2987
- /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: m.label }) }),
3063
+ /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, wrap: "truncate", children: m.label }) }),
2988
3064
  /* @__PURE__ */ jsx2(Text2, { bold: true, color: "yellow", children: metricValueText(m) })
2989
3065
  ] });
2990
3066
  }
@@ -3885,6 +3961,8 @@ var CURSOR_SORTS = [
3885
3961
  ];
3886
3962
  var IS_TTY = process.stdin.isTTY === true;
3887
3963
  var REPO_URL = "https://github.com/DavidIlie/tokmon";
3964
+ var SITE_URL = "https://davidilie.com";
3965
+ var IS_APPLE_TERMINAL = process.env.TERM_PROGRAM === "Apple_Terminal";
3888
3966
  function detectHyperlinks(env, isTTY) {
3889
3967
  const force = env.FORCE_HYPERLINK;
3890
3968
  if (force != null && force !== "") return force !== "0" && force.toLowerCase() !== "false";
@@ -3902,6 +3980,13 @@ function detectHyperlinks(env, isTTY) {
3902
3980
  }
3903
3981
  var HYPERLINKS = detectHyperlinks(process.env, process.stdout.isTTY === true);
3904
3982
  function openUrl(url) {
3983
+ if (process.env.TOKMON_OPENLOG) {
3984
+ try {
3985
+ appendFileSync2(process.env.TOKMON_OPENLOG, url + "\n");
3986
+ } catch {
3987
+ }
3988
+ return;
3989
+ }
3905
3990
  try {
3906
3991
  if (process.platform === "darwin") spawn("open", [url], { stdio: "ignore", detached: true }).unref();
3907
3992
  else if (process.platform === "win32") spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
@@ -4223,8 +4308,11 @@ function App({ interval: cliInterval, initialConfig }) {
4223
4308
  }
4224
4309
  };
4225
4310
  mouse.events.on("scroll", onScroll);
4311
+ const onData = (d) => dispatchLinkClicks(d);
4312
+ process.stdin.on("data", onData);
4226
4313
  return () => {
4227
4314
  mouse.events.off("scroll", onScroll);
4315
+ process.stdin.off("data", onData);
4228
4316
  };
4229
4317
  }, [tab]);
4230
4318
  function updateConfig(fn) {
@@ -4515,6 +4603,10 @@ function App({ interval: cliInterval, initialConfig }) {
4515
4603
  exit();
4516
4604
  return;
4517
4605
  }
4606
+ if (input === "O") {
4607
+ openUrl(REPO_URL);
4608
+ return;
4609
+ }
4518
4610
  if (showSettings) {
4519
4611
  if (key.escape || input === "s") {
4520
4612
  setShowSettings(false);
@@ -4927,21 +5019,25 @@ function AccountStrip({ slots, activeIdx, onSelect }) {
4927
5019
  }
4928
5020
  function Footer({ hasAccounts, paginated, cols }) {
4929
5021
  const inner = cols - 4;
4930
- const BASE2 = "by David Ilie (davidilie.com) \xB7 s=settings q=quit".length;
5022
+ const BASE2 = "by David Ilie (davidilie.com) \xB7 O=repo s=settings q=quit".length;
5023
+ const optHint = (glyphs().shift === "\u21E7" ? "\u2325" : "opt") + "-click links ";
5024
+ const OPT = IS_APPLE_TERMINAL ? optHint.length : 0;
4931
5025
  const JUMP = "0-9=jump a/A=cycle ".length;
4932
5026
  const PAGE = "scroll=page ".length;
4933
- const showJump = hasAccounts && inner >= BASE2 + JUMP + (paginated ? PAGE : 0);
4934
- const showPage = paginated && inner >= BASE2 + (showJump ? JUMP : 0) + PAGE;
5027
+ const showOpt = IS_APPLE_TERMINAL && inner >= BASE2 + OPT;
5028
+ const showJump = hasAccounts && inner >= BASE2 + (showOpt ? OPT : 0) + JUMP + (paginated ? PAGE : 0);
5029
+ const showPage = paginated && inner >= BASE2 + (showOpt ? OPT : 0) + (showJump ? JUMP : 0) + PAGE;
4935
5030
  return /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexWrap: "nowrap", children: [
4936
5031
  /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "by " }),
4937
- /* @__PURE__ */ jsx7(ClickableBox, { onClick: () => openUrl(REPO_URL), children: /* @__PURE__ */ jsx7(Transform, { transform: (s) => osc8(s, REPO_URL), children: /* @__PURE__ */ jsx7(Text7, { underline: true, children: "David Ilie" }) }) }),
5032
+ /* @__PURE__ */ jsx7(LinkBox, { onClick: () => openUrl(REPO_URL), children: /* @__PURE__ */ jsx7(Transform, { transform: (s) => osc8(s, REPO_URL), children: /* @__PURE__ */ jsx7(Text7, { underline: true, children: "David Ilie" }) }) }),
4938
5033
  /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " (" }),
4939
- /* @__PURE__ */ jsx7(ClickableBox, { onClick: () => openUrl("https://davidilie.com"), children: /* @__PURE__ */ jsx7(Transform, { transform: (s) => osc8(s, "https://davidilie.com"), children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", underline: true, children: "davidilie.com" }) }) }),
5034
+ /* @__PURE__ */ jsx7(LinkBox, { onClick: () => openUrl(SITE_URL), children: /* @__PURE__ */ jsx7(Transform, { transform: (s) => osc8(s, SITE_URL), children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", underline: true, children: "davidilie.com" }) }) }),
4940
5035
  /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
4941
5036
  ") ",
4942
5037
  glyphs().middot,
4943
- " s=settings "
5038
+ " O=repo s=settings "
4944
5039
  ] }),
5040
+ showOpt && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: optHint }),
4945
5041
  showJump && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "0-9=jump a/A=cycle " }),
4946
5042
  showPage && /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "scroll=page " }),
4947
5043
  /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "q=quit" })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.14.3",
3
+ "version": "0.14.5",
4
4
  "description": "Terminal dashboard for Claude Code, Codex, and Cursor usage, limits, and costs",
5
5
  "type": "module",
6
6
  "bin": {