tokmon 0.9.0 → 0.9.2
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/README.md +2 -1
- package/dist/cli.js +67 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,8 @@ Sort by date or cost with `o`.
|
|
|
96
96
|
|
|
97
97
|
Press `s` to open. Persisted to `~/.config/tokmon/config.json` (macOS/Linux) or `%APPDATA%\tokmon\config.json` (Windows).
|
|
98
98
|
|
|
99
|
-
- **Refresh interval** —
|
|
99
|
+
- **Refresh interval** — dashboard poll rate (default: 2s)
|
|
100
|
+
- **Billing poll** — rate limits API poll rate (default: 5m, min 1m to avoid 429s)
|
|
100
101
|
- **Clear screen** — clears terminal on launch (like `watch`)
|
|
101
102
|
|
|
102
103
|
## How It Works
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import { MouseProvider } from "@zenobius/ink-mouse";
|
|
|
8
8
|
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
|
-
var DEFAULTS = { interval: 2, clearScreen: true };
|
|
11
|
+
var DEFAULTS = { interval: 2, billingInterval: 5, clearScreen: true };
|
|
12
12
|
function configDir() {
|
|
13
13
|
if (process.platform === "win32") {
|
|
14
14
|
return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "tokmon");
|
|
@@ -258,29 +258,49 @@ async function fetchTable() {
|
|
|
258
258
|
|
|
259
259
|
// src/billing.ts
|
|
260
260
|
import { execFile as execFileCb } from "child_process";
|
|
261
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
262
|
+
import { join as join3 } from "path";
|
|
263
|
+
import { homedir as homedir3 } from "os";
|
|
261
264
|
import { promisify } from "util";
|
|
262
265
|
var execFile = promisify(execFileCb);
|
|
266
|
+
function credentialsFilePath() {
|
|
267
|
+
const base = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
|
|
268
|
+
return join3(base, ".credentials.json");
|
|
269
|
+
}
|
|
270
|
+
async function readCredentialsFile() {
|
|
271
|
+
try {
|
|
272
|
+
const raw = await readFile2(credentialsFilePath(), "utf-8");
|
|
273
|
+
const creds = JSON.parse(raw);
|
|
274
|
+
return creds?.claudeAiOauth?.accessToken ?? creds?.accessToken ?? null;
|
|
275
|
+
} catch {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function readMacKeychain() {
|
|
280
|
+
try {
|
|
281
|
+
const { stdout } = await execFile("security", [
|
|
282
|
+
"find-generic-password",
|
|
283
|
+
"-s",
|
|
284
|
+
"Claude Code-credentials",
|
|
285
|
+
"-w"
|
|
286
|
+
], { timeout: 5e3 });
|
|
287
|
+
const creds = JSON.parse(stdout.trim());
|
|
288
|
+
return creds?.claudeAiOauth?.accessToken ?? creds?.accessToken ?? null;
|
|
289
|
+
} catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
263
293
|
async function getAccessToken() {
|
|
264
294
|
if (process.platform === "darwin") {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
"find-generic-password",
|
|
268
|
-
"-s",
|
|
269
|
-
"Claude Code-credentials",
|
|
270
|
-
"-w"
|
|
271
|
-
], { timeout: 5e3 });
|
|
272
|
-
const creds = JSON.parse(stdout.trim());
|
|
273
|
-
return creds?.claudeAiOauth?.accessToken ?? null;
|
|
274
|
-
} catch {
|
|
275
|
-
return null;
|
|
276
|
-
}
|
|
295
|
+
const token = await readMacKeychain();
|
|
296
|
+
if (token) return token;
|
|
277
297
|
}
|
|
278
|
-
return
|
|
298
|
+
return readCredentialsFile();
|
|
279
299
|
}
|
|
280
300
|
var EMPTY = { session: null, weekly: null, sonnet: null, extraUsage: null, error: null };
|
|
281
301
|
async function fetchBilling() {
|
|
282
302
|
const token = await getAccessToken();
|
|
283
|
-
if (!token) return { ...EMPTY, error: "No OAuth token
|
|
303
|
+
if (!token) return { ...EMPTY, error: "No OAuth token \u2014 run claude and log in" };
|
|
284
304
|
try {
|
|
285
305
|
const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
286
306
|
headers: {
|
|
@@ -290,7 +310,7 @@ async function fetchBilling() {
|
|
|
290
310
|
},
|
|
291
311
|
signal: AbortSignal.timeout(1e4)
|
|
292
312
|
});
|
|
293
|
-
if (res.status === 429) return { ...EMPTY, error: "Rate limited \u2014 retrying
|
|
313
|
+
if (res.status === 429) return { ...EMPTY, error: "Rate limited \u2014 retrying next poll" };
|
|
294
314
|
if (res.status === 401) return { ...EMPTY, error: "Token expired \u2014 restart Claude Code" };
|
|
295
315
|
if (!res.ok) return { ...EMPTY, error: `API ${res.status}` };
|
|
296
316
|
const data = await res.json();
|
|
@@ -367,7 +387,7 @@ var VIEWS = ["Daily", "Weekly", "Monthly"];
|
|
|
367
387
|
var SORTS = ["date \u2191", "date \u2193", "cost \u2191", "cost \u2193"];
|
|
368
388
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
369
389
|
var MONTHS = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
370
|
-
var DEFAULT_CONFIG = { interval: 2, clearScreen: true };
|
|
390
|
+
var DEFAULT_CONFIG = { interval: 2, billingInterval: 5, clearScreen: true };
|
|
371
391
|
var IS_TTY = process.stdin.isTTY === true;
|
|
372
392
|
function App({ interval: cliInterval }) {
|
|
373
393
|
const [dashboard, setDashboard] = useState(null);
|
|
@@ -418,6 +438,7 @@ function App({ interval: cliInterval }) {
|
|
|
418
438
|
clearInterval(id);
|
|
419
439
|
};
|
|
420
440
|
}, [interval2]);
|
|
441
|
+
const billingMs = cfg.billingInterval * 6e4;
|
|
421
442
|
useEffect(() => {
|
|
422
443
|
let active = true;
|
|
423
444
|
const load = () => fetchBilling().then((b) => {
|
|
@@ -425,12 +446,12 @@ function App({ interval: cliInterval }) {
|
|
|
425
446
|
}).catch(() => {
|
|
426
447
|
});
|
|
427
448
|
load();
|
|
428
|
-
const id = setInterval(load,
|
|
449
|
+
const id = setInterval(load, billingMs);
|
|
429
450
|
return () => {
|
|
430
451
|
active = false;
|
|
431
452
|
clearInterval(id);
|
|
432
453
|
};
|
|
433
|
-
}, []);
|
|
454
|
+
}, [billingMs]);
|
|
434
455
|
useEffect(() => {
|
|
435
456
|
if (tab !== 1) return;
|
|
436
457
|
if (tableLoadedOnce.current && table) return;
|
|
@@ -486,12 +507,16 @@ function App({ interval: cliInterval }) {
|
|
|
486
507
|
if (showSettings) {
|
|
487
508
|
if (key.escape || input === "s") setShowSettings(false);
|
|
488
509
|
if (key.upArrow) setSettingsCursor((c) => Math.max(0, c - 1));
|
|
489
|
-
if (key.downArrow) setSettingsCursor((c) => Math.min(
|
|
510
|
+
if (key.downArrow) setSettingsCursor((c) => Math.min(2, c + 1));
|
|
490
511
|
if (settingsCursor === 0) {
|
|
491
512
|
if (key.leftArrow) updateConfig((c) => ({ ...c, interval: Math.max(1, c.interval - 1) }));
|
|
492
513
|
if (key.rightArrow) updateConfig((c) => ({ ...c, interval: c.interval + 1 }));
|
|
493
514
|
}
|
|
494
|
-
if (settingsCursor === 1
|
|
515
|
+
if (settingsCursor === 1) {
|
|
516
|
+
if (key.leftArrow) updateConfig((c) => ({ ...c, billingInterval: Math.max(1, c.billingInterval - 1) }));
|
|
517
|
+
if (key.rightArrow) updateConfig((c) => ({ ...c, billingInterval: c.billingInterval + 1 }));
|
|
518
|
+
}
|
|
519
|
+
if (settingsCursor === 2 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
495
520
|
updateConfig((c) => ({ ...c, clearScreen: !c.clearScreen }));
|
|
496
521
|
}
|
|
497
522
|
return;
|
|
@@ -701,7 +726,7 @@ function SettingsView({ config: config2, cursor }) {
|
|
|
701
726
|
cursor === 0 ? "\u25B8" : " ",
|
|
702
727
|
" "
|
|
703
728
|
] }),
|
|
704
|
-
/* @__PURE__ */ jsx(Text, { children: "Refresh interval
|
|
729
|
+
/* @__PURE__ */ jsx(Text, { children: "Refresh interval " }),
|
|
705
730
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
706
731
|
"\u25C2",
|
|
707
732
|
" "
|
|
@@ -720,7 +745,26 @@ function SettingsView({ config: config2, cursor }) {
|
|
|
720
745
|
cursor === 1 ? "\u25B8" : " ",
|
|
721
746
|
" "
|
|
722
747
|
] }),
|
|
723
|
-
/* @__PURE__ */ jsx(Text, { children: "
|
|
748
|
+
/* @__PURE__ */ jsx(Text, { children: "Billing poll " }),
|
|
749
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
750
|
+
"\u25C2",
|
|
751
|
+
" "
|
|
752
|
+
] }),
|
|
753
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, color: "yellow", children: [
|
|
754
|
+
config2.billingInterval,
|
|
755
|
+
"m"
|
|
756
|
+
] }),
|
|
757
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
758
|
+
" ",
|
|
759
|
+
"\u25B8"
|
|
760
|
+
] })
|
|
761
|
+
] }),
|
|
762
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
763
|
+
/* @__PURE__ */ jsxs(Text, { color: cursor === 2 ? "green" : void 0, children: [
|
|
764
|
+
cursor === 2 ? "\u25B8" : " ",
|
|
765
|
+
" "
|
|
766
|
+
] }),
|
|
767
|
+
/* @__PURE__ */ jsx(Text, { children: "Clear screen " }),
|
|
724
768
|
/* @__PURE__ */ jsx(Text, { bold: true, color: config2.clearScreen ? "green" : "red", children: config2.clearScreen ? "on" : "off" })
|
|
725
769
|
] }),
|
|
726
770
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|