tokmon 0.9.2 → 0.10.0
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 +1 -0
- package/dist/cli.js +82 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,7 @@ Then just run `tokmon`. Press `q` to quit.
|
|
|
73
73
|
- **Today / This Week / This Month** — cost and token summaries
|
|
74
74
|
- **Burn rate** — current $/hr
|
|
75
75
|
- **Rate Limits** — real-time session (5h), weekly (7d), and Sonnet utilization with reset countdowns, fetched from Anthropic's OAuth API
|
|
76
|
+
- **Peak / Off-Peak badge** — shown in the header, fetched from [promoclock.co](https://promoclock.co) (peak hours drain session limits faster)
|
|
76
77
|
|
|
77
78
|
### Table
|
|
78
79
|
|
package/dist/cli.js
CHANGED
|
@@ -297,10 +297,18 @@ async function getAccessToken() {
|
|
|
297
297
|
}
|
|
298
298
|
return readCredentialsFile();
|
|
299
299
|
}
|
|
300
|
-
var EMPTY = { session: null, weekly: null, sonnet: null, extraUsage: null, error: null };
|
|
300
|
+
var EMPTY = { session: null, weekly: null, sonnet: null, extraUsage: null, peak: null, error: null };
|
|
301
301
|
async function fetchBilling() {
|
|
302
302
|
const token = await getAccessToken();
|
|
303
303
|
if (!token) return { ...EMPTY, error: "No OAuth token \u2014 run claude and log in" };
|
|
304
|
+
const [usageRes, peak] = await Promise.all([
|
|
305
|
+
fetchUsage(token),
|
|
306
|
+
fetchPeakStatus()
|
|
307
|
+
]);
|
|
308
|
+
if ("error" in usageRes) return { ...EMPTY, peak, error: usageRes.error };
|
|
309
|
+
return { ...usageRes.data, peak, error: null };
|
|
310
|
+
}
|
|
311
|
+
async function fetchUsage(token) {
|
|
304
312
|
try {
|
|
305
313
|
const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
306
314
|
headers: {
|
|
@@ -310,31 +318,54 @@ async function fetchBilling() {
|
|
|
310
318
|
},
|
|
311
319
|
signal: AbortSignal.timeout(1e4)
|
|
312
320
|
});
|
|
313
|
-
if (res.status === 429) return {
|
|
314
|
-
if (res.status === 401) return {
|
|
315
|
-
if (!res.ok) return {
|
|
321
|
+
if (res.status === 429) return { error: "Rate limited \u2014 retrying next poll" };
|
|
322
|
+
if (res.status === 401) return { error: "Token expired \u2014 restart Claude Code" };
|
|
323
|
+
if (!res.ok) return { error: `API ${res.status}` };
|
|
316
324
|
const data = await res.json();
|
|
317
325
|
return {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
326
|
+
data: {
|
|
327
|
+
session: data.five_hour ? {
|
|
328
|
+
utilization: data.five_hour.utilization,
|
|
329
|
+
resetsAt: formatReset(data.five_hour.resets_at)
|
|
330
|
+
} : null,
|
|
331
|
+
weekly: data.seven_day ? {
|
|
332
|
+
utilization: data.seven_day.utilization,
|
|
333
|
+
resetsAt: formatReset(data.seven_day.resets_at)
|
|
334
|
+
} : null,
|
|
335
|
+
sonnet: data.seven_day_sonnet ? {
|
|
336
|
+
utilization: data.seven_day_sonnet.utilization,
|
|
337
|
+
resetsAt: formatReset(data.seven_day_sonnet.resets_at)
|
|
338
|
+
} : null,
|
|
339
|
+
extraUsage: data.extra_usage?.is_enabled ? {
|
|
340
|
+
limit: data.extra_usage.monthly_limit / 100,
|
|
341
|
+
used: data.extra_usage.used_credits / 100
|
|
342
|
+
} : null
|
|
343
|
+
}
|
|
335
344
|
};
|
|
336
345
|
} catch {
|
|
337
|
-
return {
|
|
346
|
+
return { error: "Network error" };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
async function fetchPeakStatus() {
|
|
350
|
+
try {
|
|
351
|
+
const res = await fetch("https://promoclock.co/api/status", {
|
|
352
|
+
headers: { "Accept": "application/json", "User-Agent": "tokmon" },
|
|
353
|
+
signal: AbortSignal.timeout(3e3)
|
|
354
|
+
});
|
|
355
|
+
if (!res.ok) return null;
|
|
356
|
+
const data = await res.json();
|
|
357
|
+
let state;
|
|
358
|
+
if (data.isPeak === true || data.status === "peak") state = "peak";
|
|
359
|
+
else if (data.isWeekend === true || data.status === "weekend") state = "weekend";
|
|
360
|
+
else if (data.isOffPeak === true || data.status === "off_peak" || data.status === "off-peak") state = "off-peak";
|
|
361
|
+
else return null;
|
|
362
|
+
return {
|
|
363
|
+
state,
|
|
364
|
+
label: state === "peak" ? "Peak" : state === "weekend" ? "Weekend" : "Off-Peak",
|
|
365
|
+
minutesUntilChange: typeof data.minutesUntilChange === "number" ? data.minutesUntilChange : null
|
|
366
|
+
};
|
|
367
|
+
} catch {
|
|
368
|
+
return null;
|
|
338
369
|
}
|
|
339
370
|
}
|
|
340
371
|
function formatReset(iso) {
|
|
@@ -504,6 +535,10 @@ function App({ interval: cliInterval }) {
|
|
|
504
535
|
};
|
|
505
536
|
}, [tab]);
|
|
506
537
|
useInput((input, key) => {
|
|
538
|
+
if (input === "q") {
|
|
539
|
+
exit();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
507
542
|
if (showSettings) {
|
|
508
543
|
if (key.escape || input === "s") setShowSettings(false);
|
|
509
544
|
if (key.upArrow) setSettingsCursor((c) => Math.max(0, c - 1));
|
|
@@ -521,10 +556,6 @@ function App({ interval: cliInterval }) {
|
|
|
521
556
|
}
|
|
522
557
|
return;
|
|
523
558
|
}
|
|
524
|
-
if (input === "q") {
|
|
525
|
-
exit();
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
559
|
if (input === "s") {
|
|
529
560
|
setShowSettings(true);
|
|
530
561
|
return;
|
|
@@ -631,7 +662,13 @@ function App({ interval: cliInterval }) {
|
|
|
631
662
|
"s"
|
|
632
663
|
] })
|
|
633
664
|
] }),
|
|
634
|
-
/* @__PURE__ */
|
|
665
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
666
|
+
billing?.peak && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
667
|
+
/* @__PURE__ */ jsx(PeakBadge, { peak: billing.peak }),
|
|
668
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " })
|
|
669
|
+
] }),
|
|
670
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: time(updated) })
|
|
671
|
+
] })
|
|
635
672
|
] }),
|
|
636
673
|
showSettings ? /* @__PURE__ */ jsx(SettingsView, { config: cfg, cursor: settingsCursor }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
637
674
|
/* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
|
|
@@ -836,6 +873,24 @@ function DashboardView({ data, billing }) {
|
|
|
836
873
|
)
|
|
837
874
|
] });
|
|
838
875
|
}
|
|
876
|
+
function PeakBadge({ peak }) {
|
|
877
|
+
const color = peak.state === "peak" ? "red" : "green";
|
|
878
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
879
|
+
/* @__PURE__ */ jsx(Text, { color, children: "\u25CF " }),
|
|
880
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color, children: peak.label }),
|
|
881
|
+
peak.minutesUntilChange !== null && peak.minutesUntilChange > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
882
|
+
" (",
|
|
883
|
+
fmtMinutes(peak.minutesUntilChange),
|
|
884
|
+
")"
|
|
885
|
+
] })
|
|
886
|
+
] });
|
|
887
|
+
}
|
|
888
|
+
function fmtMinutes(mins) {
|
|
889
|
+
if (mins < 60) return `${mins}m`;
|
|
890
|
+
const h = Math.floor(mins / 60);
|
|
891
|
+
const m = mins % 60;
|
|
892
|
+
return m === 0 ? `${h}h` : `${h}h ${m}m`;
|
|
893
|
+
}
|
|
839
894
|
function LimitBar({ label, pct, resets }) {
|
|
840
895
|
const width = 30;
|
|
841
896
|
const filled = Math.round(pct / 100 * width);
|