tokmon 0.14.4 → 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 +80 -44
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -610,9 +610,9 @@ function glyphs() {
610
610
  // src/app.tsx
611
611
  import { spawn } from "child_process";
612
612
  import { appendFileSync as appendFileSync2 } from "fs";
613
- import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef2, useMemo } from "react";
613
+ import { useState as useState3, useEffect as useEffect3, useCallback, useRef as useRef2, useMemo } from "react";
614
614
  import { Box as Box7, Text as Text7, Transform, useInput, useStdout, useApp } from "ink";
615
- import { useMouse as useMouse2 } from "@zenobius/ink-mouse";
615
+ import { useMouse } from "@zenobius/ink-mouse";
616
616
 
617
617
  // src/http.ts
618
618
  async function readJson(res) {
@@ -705,7 +705,9 @@ async function detectClaude(homeDir) {
705
705
  }
706
706
  function priceFor(model) {
707
707
  for (const key of PRICE_KEYS) {
708
- 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];
709
711
  }
710
712
  return FALLBACK;
711
713
  }
@@ -1013,10 +1015,15 @@ function subtractClamped(cur, prev) {
1013
1015
  reasoning_output_tokens: sub(cur.reasoning_output_tokens, prev?.reasoning_output_tokens)
1014
1016
  };
1015
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
+ }
1016
1022
  async function parseFile2(path) {
1017
1023
  const entries = [];
1018
1024
  let model = "gpt-5-codex";
1019
1025
  let prevTotal = null;
1026
+ let prevSig = null;
1020
1027
  const rl = createInterface2({ input: createReadStream2(path), crlfDelay: Infinity });
1021
1028
  for await (const rawLine of rl) {
1022
1029
  if (!rawLine.includes("token_count") && !rawLine.includes("turn_context")) continue;
@@ -1032,7 +1039,11 @@ async function parseFile2(path) {
1032
1039
  if (payloadType !== "token_count") continue;
1033
1040
  const info = obj?.payload?.info;
1034
1041
  const total = info?.total_token_usage;
1035
- 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;
1036
1047
  if (!d && total) {
1037
1048
  const reset = !!prevTotal && (total.input_tokens ?? 0) < (prevTotal.input_tokens ?? 0);
1038
1049
  d = reset ? total : subtractClamped(total, prevTotal);
@@ -1622,9 +1633,10 @@ async function parseFile3(path) {
1622
1633
  const c = u.cost ?? {};
1623
1634
  const costInput = pos(c.input);
1624
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";
1625
1637
  entries.push({
1626
1638
  ts,
1627
- model: typeof msg.model === "string" && msg.model ? msg.model : "unknown",
1639
+ model,
1628
1640
  cost: pos(c.total),
1629
1641
  input,
1630
1642
  output,
@@ -1915,6 +1927,7 @@ function resetDate(value) {
1915
1927
  }
1916
1928
  function percentMetric2(label, snapshot, reset, primary) {
1917
1929
  if (!snapshot || typeof snapshot.percent_remaining !== "number") return null;
1930
+ if (snapshot.unlimited === true || snapshot.entitlement === 0) return null;
1918
1931
  const used = Math.min(100, Math.max(0, 100 - snapshot.percent_remaining));
1919
1932
  return { label, used, limit: 100, format: { kind: "percent" }, resetsAt: reset, primary };
1920
1933
  }
@@ -2670,9 +2683,9 @@ function saveSnapshot(stats) {
2670
2683
 
2671
2684
  // src/ui/shared.tsx
2672
2685
  import { appendFileSync } from "fs";
2673
- import { useState, useEffect, useRef, useCallback } from "react";
2686
+ import { useEffect, useRef, useState } from "react";
2674
2687
  import { Box, Text } from "ink";
2675
- import { useOnMouseClick, useMouse, useElementPosition, useElementDimensions } from "@zenobius/ink-mouse";
2688
+ import { useOnMouseClick } from "@zenobius/ink-mouse";
2676
2689
  import { jsx, jsxs } from "react/jsx-runtime";
2677
2690
  function truncateName(s, n) {
2678
2691
  const ell = glyphs().ellipsis;
@@ -2685,40 +2698,72 @@ function ClickableBox({ onClick, children, ...props }) {
2685
2698
  });
2686
2699
  return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
2687
2700
  }
2688
- function LinkBox({ onClick, children, ...props }) {
2689
- const ref = useRef(null);
2690
- const mouse = useMouse();
2691
- const pos3 = useElementPosition(ref);
2692
- const dim = useElementDimensions(ref);
2693
- const handler = useCallback((m, action) => {
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;
2694
2713
  if (process.env.TOKMON_LINKDEBUG) {
2695
2714
  try {
2696
- appendFileSync(process.env.TOKMON_LINKDEBUG, `evt action=${action} m=${m.x},${m.y} pos=${pos3.left},${pos3.top} dim=${dim.width},${dim.height}
2715
+ appendFileSync(process.env.TOKMON_LINKDEBUG, `DISPATCH code=${code} mx=${mx} my=${my} hits=${linkHits.size}
2697
2716
  `);
2698
2717
  } catch {
2699
2718
  }
2700
2719
  }
2701
- if (action !== "press") return;
2702
- const mx = m.x - 1;
2703
- const my = m.y - 1;
2704
- const { left, top } = pos3;
2705
- const { width, height } = dim;
2706
- if (mx >= left && mx <= left + width && my >= top && my <= top + height) onClick();
2707
- }, [pos3, dim, onClick]);
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;
2708
2745
  useEffect(() => {
2709
- const events = mouse.events;
2710
- if (process.env.TOKMON_LINKDEBUG) {
2711
- try {
2712
- appendFileSync(process.env.TOKMON_LINKDEBUG, `LINKBOX subscribe events=${events ? "present" : "MISSING"} on=${typeof events?.on}
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"}
2713
2751
  `);
2714
- } catch {
2752
+ } catch {
2753
+ }
2715
2754
  }
2716
- }
2717
- events.on("click", handler);
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);
2718
2763
  return () => {
2719
- events.off("click", handler);
2764
+ linkHits.delete(hit);
2720
2765
  };
2721
- }, [mouse.events, handler]);
2766
+ }, []);
2722
2767
  return /* @__PURE__ */ jsx(Box, { ref, ...props, children });
2723
2768
  }
2724
2769
  function Spinner({ label }) {
@@ -4245,12 +4290,12 @@ function App({ interval: cliInterval, initialConfig }) {
4245
4290
  useEffect3(() => {
4246
4291
  setDashPage((p) => Math.min(p, dashPageCount - 1));
4247
4292
  }, [dashPageCount]);
4248
- const resetView = useCallback2(() => {
4293
+ const resetView = useCallback(() => {
4249
4294
  setCursor(0);
4250
4295
  setExpanded(-1);
4251
4296
  }, []);
4252
4297
  const clampRow = (n) => Math.max(0, Math.min(rowCountRef.current - 1, n));
4253
- const mouse = useMouse2();
4298
+ const mouse = useMouse();
4254
4299
  useEffect3(() => {
4255
4300
  if (!IS_TTY) return;
4256
4301
  mouse.enable();
@@ -4263,20 +4308,11 @@ function App({ interval: cliInterval, initialConfig }) {
4263
4308
  }
4264
4309
  };
4265
4310
  mouse.events.on("scroll", onScroll);
4266
- let onClickDbg = null;
4267
- if (process.env.TOKMON_LINKDEBUG) {
4268
- onClickDbg = (p, a) => {
4269
- try {
4270
- appendFileSync2(process.env.TOKMON_LINKDEBUG, `APP click p=${p.x},${p.y} a=${a}
4271
- `);
4272
- } catch {
4273
- }
4274
- };
4275
- mouse.events.on("click", onClickDbg);
4276
- }
4311
+ const onData = (d) => dispatchLinkClicks(d);
4312
+ process.stdin.on("data", onData);
4277
4313
  return () => {
4278
4314
  mouse.events.off("scroll", onScroll);
4279
- if (onClickDbg) mouse.events.off("click", onClickDbg);
4315
+ process.stdin.off("data", onData);
4280
4316
  };
4281
4317
  }, [tab]);
4282
4318
  function updateConfig(fn) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokmon",
3
- "version": "0.14.4",
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": {