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.
- package/dist/cli.js +120 -24
- 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))
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 "
|
|
2325
|
-
if (lower.includes("gemini") && lower.includes("flash")) return "
|
|
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 {
|
|
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
|
|
4934
|
-
const
|
|
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(
|
|
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(
|
|
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" })
|