tokmon 0.4.0 → 0.5.1
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 +55 -32
- package/dist/cli.js +201 -140
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
# tokmon
|
|
2
2
|
|
|
3
|
-
Terminal dashboard for Claude Code usage
|
|
3
|
+
Terminal dashboard for Claude Code usage, costs, and rate limits.
|
|
4
4
|
|
|
5
5
|
Built with [Ink](https://github.com/vadimdemedes/ink), TypeScript.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
|
-
◉ tokmon · 2s
|
|
8
|
+
◉ tokmon · 2s 02:48:39 AM
|
|
9
9
|
|
|
10
|
-
Dashboard
|
|
10
|
+
Dashboard Table Tab/←→
|
|
11
11
|
|
|
12
12
|
┃ Claude
|
|
13
13
|
┃
|
|
14
|
-
┃ Today $
|
|
15
|
-
┃ This Week $
|
|
16
|
-
┃ This Month $
|
|
17
|
-
|
|
18
|
-
┃ Active Block 12m remaining
|
|
14
|
+
┃ Today $372.55 614.9M tokens
|
|
15
|
+
┃ This Week $606.23 970.5M tokens
|
|
16
|
+
┃ This Month $1543.48 2.5B tokens
|
|
19
17
|
┃
|
|
20
|
-
┃
|
|
18
|
+
┃ Burn rate $132.65/hr
|
|
19
|
+
|
|
20
|
+
┃ Rate Limits
|
|
21
21
|
┃
|
|
22
|
-
┃
|
|
22
|
+
┃ Session ━━━━────────────────────────── 13% resets 4h 11m
|
|
23
|
+
┃ Weekly ━━━━━━━━━━━━━───────────────── 44% resets 17h 11m
|
|
24
|
+
┃ Sonnet ━───────────────────────────── 3% resets 2d 20h
|
|
25
|
+
┃ Extra $0.00 / $42.50 limit
|
|
23
26
|
|
|
24
27
|
──────────────────────────────────────────────────
|
|
25
|
-
Total $
|
|
28
|
+
Total $1543.48
|
|
26
29
|
|
|
27
|
-
by David Ilie (davidilie.com)
|
|
30
|
+
by David Ilie (davidilie.com) · s=settings q=quit
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
## Quick Start
|
|
@@ -45,7 +48,7 @@ pnpm dlx tokmon
|
|
|
45
48
|
npm install -g tokmon
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
Then just run `tokmon`. Press `
|
|
51
|
+
Then just run `tokmon`. Press `q` to quit.
|
|
49
52
|
|
|
50
53
|
## Options
|
|
51
54
|
|
|
@@ -54,45 +57,65 @@ Then just run `tokmon`. Press `Ctrl+C` to exit.
|
|
|
54
57
|
-h, --help Show help
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
tokmon -i 5 # refresh every 5 seconds
|
|
59
|
-
```
|
|
60
|
+
## Keybindings
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
| Key | Action |
|
|
63
|
+
|-----|--------|
|
|
64
|
+
| `Tab` / `←→` | Switch between Dashboard and Table |
|
|
65
|
+
| `1` `2` | Jump to view |
|
|
66
|
+
| `d` `w` `m` | Daily / Weekly / Monthly (in Table view) |
|
|
67
|
+
| `↑` `↓` | Scroll table |
|
|
68
|
+
| `PgUp` `PgDn` | Scroll table fast |
|
|
69
|
+
| `s` | Settings |
|
|
70
|
+
| `q` | Quit |
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
## Views
|
|
64
73
|
|
|
65
74
|
| View | Description |
|
|
66
75
|
|------|-------------|
|
|
67
|
-
| **Dashboard** | Today / week / month cost summaries,
|
|
68
|
-
| **Daily** | Per-day breakdown
|
|
76
|
+
| **Dashboard** | Today / week / month cost summaries, burn rate ($/hr), real-time rate limits with reset countdowns |
|
|
77
|
+
| **Table → Daily** | Per-day breakdown with models, tokens, and costs (6 months of history) |
|
|
78
|
+
| **Table → Weekly** | Grouped by ISO week |
|
|
79
|
+
| **Table → Monthly** | Grouped by month |
|
|
80
|
+
|
|
81
|
+
## Rate Limits
|
|
82
|
+
|
|
83
|
+
Fetches real billing data from Anthropic's OAuth API (reads your token from macOS Keychain automatically). Shows:
|
|
84
|
+
|
|
85
|
+
- **Session** — 5-hour utilization with reset countdown
|
|
86
|
+
- **Weekly** — 7-day utilization with reset countdown
|
|
87
|
+
- **Sonnet** — Sonnet-specific limits (if applicable)
|
|
88
|
+
- **Extra usage** — spend vs monthly limit
|
|
89
|
+
|
|
90
|
+
Polls every 2 minutes to stay within API rate limits.
|
|
91
|
+
|
|
92
|
+
## Settings
|
|
93
|
+
|
|
94
|
+
Press `s` to open settings. Persisted to `~/.config/tokmon/config.json` (macOS/Linux) or `%APPDATA%\tokmon\config.json` (Windows).
|
|
95
|
+
|
|
96
|
+
- **Refresh interval** — adjust with `←→`
|
|
97
|
+
- **Clear screen** — on/off toggle
|
|
69
98
|
|
|
70
99
|
## How It Works
|
|
71
100
|
|
|
72
|
-
Reads Claude Code's JSONL session logs directly from `~/.claude/projects/`. Calculates costs using Claude model pricing (Opus, Sonnet, Haiku). Caches file reads by mtime so
|
|
101
|
+
Reads Claude Code's JSONL session logs directly from `~/.claude/projects/`. Calculates costs using Claude model pricing (Opus, Sonnet, Haiku). Caches file reads by mtime so refreshes are near-instant.
|
|
102
|
+
|
|
103
|
+
Dashboard loads current month only (fast). Table loads 6 months lazily on first switch.
|
|
73
104
|
|
|
74
105
|
Cross-platform: supports macOS, Linux, and Windows (`%APPDATA%`, `XDG_CONFIG_HOME`, `CLAUDE_CONFIG_DIR`).
|
|
75
106
|
|
|
76
107
|
## CI/CD
|
|
77
108
|
|
|
78
|
-
Publishes to npm
|
|
109
|
+
Publishes to npm via GitHub Actions on version tags:
|
|
79
110
|
|
|
80
111
|
```bash
|
|
81
|
-
git tag v0.
|
|
112
|
+
git tag v0.5.0 && git push --tags
|
|
82
113
|
```
|
|
83
114
|
|
|
84
115
|
## Requirements
|
|
85
116
|
|
|
86
117
|
- Node.js 20+
|
|
87
|
-
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
|
88
|
-
|
|
89
|
-
## Tech Stack
|
|
90
|
-
|
|
91
|
-
| Tool | Purpose |
|
|
92
|
-
|------|---------|
|
|
93
|
-
| Ink 5 | React terminal UI |
|
|
94
|
-
| TypeScript 5.7+ | Strict mode |
|
|
95
|
-
| tsup | Build |
|
|
118
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
|
96
119
|
|
|
97
120
|
## Author
|
|
98
121
|
|
package/dist/cli.js
CHANGED
|
@@ -46,41 +46,6 @@ import { createReadStream } from "fs";
|
|
|
46
46
|
import { createInterface } from "readline";
|
|
47
47
|
import { join as join2 } from "path";
|
|
48
48
|
import { homedir as homedir2 } from "os";
|
|
49
|
-
|
|
50
|
-
// src/format.ts
|
|
51
|
-
function currency(value) {
|
|
52
|
-
return `$${value.toFixed(2)}`;
|
|
53
|
-
}
|
|
54
|
-
function tokens(value) {
|
|
55
|
-
if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
|
|
56
|
-
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
57
|
-
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
58
|
-
return String(value);
|
|
59
|
-
}
|
|
60
|
-
function time(date) {
|
|
61
|
-
return date.toLocaleTimeString(void 0, {
|
|
62
|
-
hour: "2-digit",
|
|
63
|
-
minute: "2-digit",
|
|
64
|
-
second: "2-digit"
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function minutes(mins) {
|
|
68
|
-
const h = Math.floor(mins / 60);
|
|
69
|
-
const m = Math.round(mins % 60);
|
|
70
|
-
return h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
71
|
-
}
|
|
72
|
-
function shortDate(iso) {
|
|
73
|
-
const [, m, d] = iso.split("-");
|
|
74
|
-
const months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
75
|
-
return `${months[Number(m)]} ${Number(d).toString().padStart(2, " ")}`;
|
|
76
|
-
}
|
|
77
|
-
function col(s, w, align = "right") {
|
|
78
|
-
if (s.length > w) return s.slice(0, w - 1) + "~";
|
|
79
|
-
const spaces = " ".repeat(w - s.length);
|
|
80
|
-
return align === "right" ? spaces + s : s + spaces;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// src/data.ts
|
|
84
49
|
var PRICING = {
|
|
85
50
|
"claude-opus-4": { i: 5e-6, o: 25e-6, cc: 625e-8, cr: 5e-7 },
|
|
86
51
|
"claude-sonnet-4": { i: 3e-6, o: 15e-6, cc: 375e-8, cr: 3e-7 },
|
|
@@ -227,47 +192,26 @@ function isoWeekLabel(ts) {
|
|
|
227
192
|
function monthLabel(ts) {
|
|
228
193
|
return new Date(ts).toISOString().slice(0, 7);
|
|
229
194
|
}
|
|
230
|
-
function findBlockStart(entries, now) {
|
|
231
|
-
const recent = entries.filter((e) => e.ts <= now).sort((a, b) => a.ts - b.ts);
|
|
232
|
-
if (recent.length === 0) return 0;
|
|
233
|
-
const GAP = 30 * 6e4;
|
|
234
|
-
let blockStart = recent[0].ts;
|
|
235
|
-
for (let i = 1; i < recent.length; i++) {
|
|
236
|
-
if (recent[i].ts - recent[i - 1].ts > GAP) {
|
|
237
|
-
blockStart = recent[i].ts;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (now - blockStart > 5 * 36e5) return 0;
|
|
241
|
-
return blockStart;
|
|
242
|
-
}
|
|
243
195
|
async function fetchDashboard() {
|
|
244
|
-
const now = Date.now();
|
|
245
196
|
const d = /* @__PURE__ */ new Date();
|
|
246
197
|
const monthStart = new Date(d.getFullYear(), d.getMonth(), 1).getTime();
|
|
247
198
|
const todayStart = new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
248
199
|
const weekDay = d.getDay();
|
|
249
200
|
const weekStart = new Date(d.getFullYear(), d.getMonth(), d.getDate() - (weekDay === 0 ? 6 : weekDay - 1)).getTime();
|
|
201
|
+
const now = Date.now();
|
|
250
202
|
const entries = await loadEntries(monthStart);
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
257
|
-
const spent = blockEntries.reduce((s, e) => s + e.cost, 0);
|
|
258
|
-
const elapsedMs = now - blockStart;
|
|
259
|
-
const elapsedHrs = elapsedMs / 36e5;
|
|
260
|
-
const burnRate = elapsedHrs > 0 ? spent / elapsedHrs : 0;
|
|
261
|
-
const remainMs = Math.max(0, blockEnd - now);
|
|
262
|
-
const percent = Math.min(100, elapsedMs / (5 * 36e5) * 100);
|
|
263
|
-
block = { spent, projected: burnRate * 5, burnRate, percent, remaining: minutes(remainMs / 6e4) };
|
|
264
|
-
}
|
|
203
|
+
const todayEntries = entries.filter((e) => e.ts >= todayStart);
|
|
204
|
+
let burnRate = 0;
|
|
205
|
+
if (todayEntries.length > 0) {
|
|
206
|
+
const oldest = Math.min(...todayEntries.map((e) => e.ts));
|
|
207
|
+
const hrs = (now - oldest) / 36e5;
|
|
208
|
+
if (hrs > 0) burnRate = todayEntries.reduce((s, e) => s + e.cost, 0) / hrs;
|
|
265
209
|
}
|
|
266
210
|
return {
|
|
267
|
-
today: sum(
|
|
211
|
+
today: sum(todayEntries),
|
|
268
212
|
week: sum(entries.filter((e) => e.ts >= weekStart)),
|
|
269
213
|
month: sum(entries.filter((e) => e.ts >= monthStart)),
|
|
270
|
-
|
|
214
|
+
burnRate
|
|
271
215
|
};
|
|
272
216
|
}
|
|
273
217
|
async function fetchTable() {
|
|
@@ -281,12 +225,113 @@ async function fetchTable() {
|
|
|
281
225
|
};
|
|
282
226
|
}
|
|
283
227
|
|
|
228
|
+
// src/billing.ts
|
|
229
|
+
import { execFile as execFileCb } from "child_process";
|
|
230
|
+
import { promisify } from "util";
|
|
231
|
+
var execFile = promisify(execFileCb);
|
|
232
|
+
async function getAccessToken() {
|
|
233
|
+
if (process.platform === "darwin") {
|
|
234
|
+
try {
|
|
235
|
+
const { stdout } = await execFile("security", [
|
|
236
|
+
"find-generic-password",
|
|
237
|
+
"-s",
|
|
238
|
+
"Claude Code-credentials",
|
|
239
|
+
"-w"
|
|
240
|
+
], { timeout: 5e3 });
|
|
241
|
+
const creds = JSON.parse(stdout.trim());
|
|
242
|
+
return creds?.claudeAiOauth?.accessToken ?? null;
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
async function fetchBilling() {
|
|
250
|
+
const token = await getAccessToken();
|
|
251
|
+
if (!token) return null;
|
|
252
|
+
try {
|
|
253
|
+
const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
254
|
+
headers: {
|
|
255
|
+
"Authorization": `Bearer ${token}`,
|
|
256
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
257
|
+
"User-Agent": "tokmon"
|
|
258
|
+
},
|
|
259
|
+
signal: AbortSignal.timeout(1e4)
|
|
260
|
+
});
|
|
261
|
+
if (!res.ok) return null;
|
|
262
|
+
const data = await res.json();
|
|
263
|
+
return {
|
|
264
|
+
session: data.five_hour ? {
|
|
265
|
+
utilization: data.five_hour.utilization,
|
|
266
|
+
resetsAt: formatReset(data.five_hour.resets_at)
|
|
267
|
+
} : null,
|
|
268
|
+
weekly: data.seven_day ? {
|
|
269
|
+
utilization: data.seven_day.utilization,
|
|
270
|
+
resetsAt: formatReset(data.seven_day.resets_at)
|
|
271
|
+
} : null,
|
|
272
|
+
sonnet: data.seven_day_sonnet ? {
|
|
273
|
+
utilization: data.seven_day_sonnet.utilization,
|
|
274
|
+
resetsAt: formatReset(data.seven_day_sonnet.resets_at)
|
|
275
|
+
} : null,
|
|
276
|
+
extraUsage: data.extra_usage?.is_enabled ? {
|
|
277
|
+
limit: data.extra_usage.monthly_limit / 100,
|
|
278
|
+
used: data.extra_usage.used_credits / 100
|
|
279
|
+
} : null
|
|
280
|
+
};
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function formatReset(iso) {
|
|
286
|
+
const d = new Date(iso);
|
|
287
|
+
const now = /* @__PURE__ */ new Date();
|
|
288
|
+
const diff = d.getTime() - now.getTime();
|
|
289
|
+
if (diff <= 0) return "now";
|
|
290
|
+
const mins = Math.round(diff / 6e4);
|
|
291
|
+
if (mins < 60) return `${mins}m`;
|
|
292
|
+
const hrs = Math.floor(mins / 60);
|
|
293
|
+
const m = mins % 60;
|
|
294
|
+
if (hrs < 24) return `${hrs}h ${m}m`;
|
|
295
|
+
const days = Math.floor(hrs / 24);
|
|
296
|
+
const h = hrs % 24;
|
|
297
|
+
return `${days}d ${h}h`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/format.ts
|
|
301
|
+
function currency(value) {
|
|
302
|
+
return `$${value.toFixed(2)}`;
|
|
303
|
+
}
|
|
304
|
+
function tokens(value) {
|
|
305
|
+
if (value >= 1e9) return `${(value / 1e9).toFixed(1)}B`;
|
|
306
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
307
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}K`;
|
|
308
|
+
return String(value);
|
|
309
|
+
}
|
|
310
|
+
function time(date) {
|
|
311
|
+
return date.toLocaleTimeString(void 0, {
|
|
312
|
+
hour: "2-digit",
|
|
313
|
+
minute: "2-digit",
|
|
314
|
+
second: "2-digit"
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function shortDate(iso) {
|
|
318
|
+
const [, m, d] = iso.split("-");
|
|
319
|
+
const months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
320
|
+
return `${months[Number(m)]} ${Number(d).toString().padStart(2, " ")}`;
|
|
321
|
+
}
|
|
322
|
+
function col(s, w, align = "right") {
|
|
323
|
+
if (s.length > w) return s.slice(0, w - 1) + "~";
|
|
324
|
+
const spaces = " ".repeat(w - s.length);
|
|
325
|
+
return align === "right" ? spaces + s : s + spaces;
|
|
326
|
+
}
|
|
327
|
+
|
|
284
328
|
// src/app.tsx
|
|
285
329
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
286
330
|
var TABS = ["Dashboard", "Table"];
|
|
287
331
|
var VIEWS = ["Daily", "Weekly", "Monthly"];
|
|
288
332
|
function App({ interval: cliInterval }) {
|
|
289
333
|
const [dashboard, setDashboard] = useState(null);
|
|
334
|
+
const [billing, setBilling] = useState(null);
|
|
290
335
|
const [table, setTable] = useState(null);
|
|
291
336
|
const [tableLoading, setTableLoading] = useState(false);
|
|
292
337
|
const [error, setError] = useState(null);
|
|
@@ -299,9 +344,11 @@ function App({ interval: cliInterval }) {
|
|
|
299
344
|
const [settingsCursor, setSettingsCursor] = useState(0);
|
|
300
345
|
const tableLoadedOnce = useRef(false);
|
|
301
346
|
const { stdout } = useStdout();
|
|
347
|
+
const { exit } = useApp();
|
|
302
348
|
const rows = stdout?.rows ?? 24;
|
|
303
349
|
const cols = stdout?.columns ?? 80;
|
|
304
350
|
const interval2 = cliInterval ?? (config2?.interval ?? 2) * 1e3;
|
|
351
|
+
const cfg = config2 ?? { interval: 2, clearScreen: true };
|
|
305
352
|
useEffect(() => {
|
|
306
353
|
loadConfig().then((c) => {
|
|
307
354
|
if (cliInterval) c = { ...c, interval: cliInterval / 1e3 };
|
|
@@ -329,6 +376,19 @@ function App({ interval: cliInterval }) {
|
|
|
329
376
|
clearInterval(id);
|
|
330
377
|
};
|
|
331
378
|
}, [interval2]);
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
let active = true;
|
|
381
|
+
const load = () => fetchBilling().then((b) => {
|
|
382
|
+
if (active && b) setBilling(b);
|
|
383
|
+
}).catch(() => {
|
|
384
|
+
});
|
|
385
|
+
load();
|
|
386
|
+
const id = setInterval(load, 12e4);
|
|
387
|
+
return () => {
|
|
388
|
+
active = false;
|
|
389
|
+
clearInterval(id);
|
|
390
|
+
};
|
|
391
|
+
}, []);
|
|
332
392
|
useEffect(() => {
|
|
333
393
|
if (tab !== 1) return;
|
|
334
394
|
if (tableLoadedOnce.current && table) return;
|
|
@@ -362,36 +422,29 @@ function App({ interval: cliInterval }) {
|
|
|
362
422
|
clearInterval(id);
|
|
363
423
|
};
|
|
364
424
|
}, [tab, interval2]);
|
|
365
|
-
const { exit } = useApp();
|
|
366
425
|
const isTTY = process.stdin.isTTY === true;
|
|
367
|
-
const settingsItems = 2;
|
|
368
|
-
const cfg = config2 ?? { interval: 2, clearScreen: true };
|
|
369
426
|
useInput((input, key) => {
|
|
370
427
|
if (showSettings) {
|
|
371
428
|
if (key.escape || input === "s") setShowSettings(false);
|
|
372
429
|
if (key.upArrow) setSettingsCursor((c) => Math.max(0, c - 1));
|
|
373
|
-
if (key.downArrow) setSettingsCursor((c) => Math.min(
|
|
430
|
+
if (key.downArrow) setSettingsCursor((c) => Math.min(1, c + 1));
|
|
374
431
|
if (settingsCursor === 0) {
|
|
375
|
-
if (key.leftArrow) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
saveConfig(next);
|
|
386
|
-
return next;
|
|
387
|
-
});
|
|
388
|
-
}
|
|
432
|
+
if (key.leftArrow) setConfig((c) => {
|
|
433
|
+
const n = { ...c, interval: Math.max(1, c.interval - 1) };
|
|
434
|
+
saveConfig(n);
|
|
435
|
+
return n;
|
|
436
|
+
});
|
|
437
|
+
if (key.rightArrow) setConfig((c) => {
|
|
438
|
+
const n = { ...c, interval: c.interval + 1 };
|
|
439
|
+
saveConfig(n);
|
|
440
|
+
return n;
|
|
441
|
+
});
|
|
389
442
|
}
|
|
390
443
|
if (settingsCursor === 1 && (key.leftArrow || key.rightArrow || key.return)) {
|
|
391
444
|
setConfig((c) => {
|
|
392
|
-
const
|
|
393
|
-
saveConfig(
|
|
394
|
-
return
|
|
445
|
+
const n = { ...c, clearScreen: !c.clearScreen };
|
|
446
|
+
saveConfig(n);
|
|
447
|
+
return n;
|
|
395
448
|
});
|
|
396
449
|
}
|
|
397
450
|
return;
|
|
@@ -481,7 +534,7 @@ function App({ interval: cliInterval }) {
|
|
|
481
534
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " Tab/\u2190\u2192" })
|
|
482
535
|
] }),
|
|
483
536
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
484
|
-
tab === 0 && /* @__PURE__ */ jsx(DashboardView, { data: dashboard }),
|
|
537
|
+
tab === 0 && /* @__PURE__ */ jsx(DashboardView, { data: dashboard, billing }),
|
|
485
538
|
tab === 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
486
539
|
/* @__PURE__ */ jsx(ViewBar, { views: VIEWS, active: view }),
|
|
487
540
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
@@ -493,8 +546,7 @@ function App({ interval: cliInterval }) {
|
|
|
493
546
|
/* @__PURE__ */ jsx(Text, { children: "David Ilie" }),
|
|
494
547
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " (" }),
|
|
495
548
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "davidilie.com" }),
|
|
496
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ") \xB7 " })
|
|
497
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "s=settings q=quit" })
|
|
549
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ") \xB7 s=settings q=quit" })
|
|
498
550
|
] })
|
|
499
551
|
] });
|
|
500
552
|
}
|
|
@@ -555,7 +607,7 @@ function SettingsView({ config: config2, cursor }) {
|
|
|
555
607
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select \u2190\u2192 adjust s/Esc close" })
|
|
556
608
|
] });
|
|
557
609
|
}
|
|
558
|
-
function DashboardView({ data }) {
|
|
610
|
+
function DashboardView({ data, billing }) {
|
|
559
611
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
560
612
|
/* @__PURE__ */ jsxs(
|
|
561
613
|
Box,
|
|
@@ -572,13 +624,51 @@ function DashboardView({ data }) {
|
|
|
572
624
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
573
625
|
/* @__PURE__ */ jsx(SummaryRow, { label: "Today", summary: data.today }),
|
|
574
626
|
/* @__PURE__ */ jsx(SummaryRow, { label: "This Week", summary: data.week }),
|
|
575
|
-
/* @__PURE__ */ jsx(SummaryRow, { label: "This Month", summary: data.month })
|
|
627
|
+
/* @__PURE__ */ jsx(SummaryRow, { label: "This Month", summary: data.month }),
|
|
628
|
+
data.burnRate > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
629
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
630
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
631
|
+
/* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Burn rate" }) }),
|
|
632
|
+
/* @__PURE__ */ jsx(Box, { width: 12, justifyContent: "flex-end", children: /* @__PURE__ */ jsx(Text, { color: "red", children: currency(data.burnRate) }) }),
|
|
633
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "/hr" })
|
|
634
|
+
] })
|
|
635
|
+
] })
|
|
576
636
|
]
|
|
577
637
|
}
|
|
578
638
|
),
|
|
579
|
-
|
|
639
|
+
billing && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
580
640
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
581
|
-
/* @__PURE__ */
|
|
641
|
+
/* @__PURE__ */ jsxs(
|
|
642
|
+
Box,
|
|
643
|
+
{
|
|
644
|
+
flexDirection: "column",
|
|
645
|
+
paddingLeft: 1,
|
|
646
|
+
borderStyle: "bold",
|
|
647
|
+
borderColor: "yellow",
|
|
648
|
+
borderRight: false,
|
|
649
|
+
borderTop: false,
|
|
650
|
+
borderBottom: false,
|
|
651
|
+
children: [
|
|
652
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: "Rate Limits" }),
|
|
653
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
654
|
+
billing.session && /* @__PURE__ */ jsx(LimitBar, { label: "Session", pct: billing.session.utilization, resets: billing.session.resetsAt }),
|
|
655
|
+
billing.weekly && /* @__PURE__ */ jsx(LimitBar, { label: "Weekly", pct: billing.weekly.utilization, resets: billing.weekly.resetsAt }),
|
|
656
|
+
billing.sonnet && /* @__PURE__ */ jsx(LimitBar, { label: "Sonnet", pct: billing.sonnet.utilization, resets: billing.sonnet.resetsAt }),
|
|
657
|
+
billing.extraUsage && /* @__PURE__ */ jsxs(Box, { children: [
|
|
658
|
+
/* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Extra" }) }),
|
|
659
|
+
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
660
|
+
"$",
|
|
661
|
+
billing.extraUsage.used.toFixed(2)
|
|
662
|
+
] }),
|
|
663
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
664
|
+
" / $",
|
|
665
|
+
billing.extraUsage.limit.toFixed(2),
|
|
666
|
+
" limit"
|
|
667
|
+
] })
|
|
668
|
+
] })
|
|
669
|
+
]
|
|
670
|
+
}
|
|
671
|
+
)
|
|
582
672
|
] }),
|
|
583
673
|
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
584
674
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(50) }),
|
|
@@ -588,46 +678,24 @@ function DashboardView({ data }) {
|
|
|
588
678
|
] })
|
|
589
679
|
] });
|
|
590
680
|
}
|
|
591
|
-
function
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
] })
|
|
610
|
-
] }),
|
|
611
|
-
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
612
|
-
/* @__PURE__ */ jsxs(Box, { children: [
|
|
613
|
-
/* @__PURE__ */ jsx(ProgressBar, { percent: block.percent, width: 36 }),
|
|
614
|
-
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
615
|
-
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
616
|
-
Math.round(block.percent),
|
|
617
|
-
"%"
|
|
618
|
-
] })
|
|
619
|
-
] }),
|
|
620
|
-
/* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
|
|
621
|
-
/* @__PURE__ */ jsx(Text, { color: "yellow", children: currency(block.spent) }),
|
|
622
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " spent \xB7 ~" }),
|
|
623
|
-
/* @__PURE__ */ jsx(Text, { children: currency(block.projected) }),
|
|
624
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " proj \xB7 " }),
|
|
625
|
-
/* @__PURE__ */ jsx(Text, { color: "red", children: currency(block.burnRate) }),
|
|
626
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "/hr" })
|
|
627
|
-
] })
|
|
628
|
-
]
|
|
629
|
-
}
|
|
630
|
-
);
|
|
681
|
+
function LimitBar({ label, pct, resets }) {
|
|
682
|
+
const width = 30;
|
|
683
|
+
const filled = Math.round(pct / 100 * width);
|
|
684
|
+
const color = pct >= 80 ? "red" : pct >= 50 ? "yellow" : "green";
|
|
685
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
686
|
+
/* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: label }) }),
|
|
687
|
+
/* @__PURE__ */ jsx(Text, { color, children: "\u2501".repeat(filled) }),
|
|
688
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(width - filled) }),
|
|
689
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
690
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
691
|
+
Math.round(pct),
|
|
692
|
+
"%"
|
|
693
|
+
] }),
|
|
694
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
695
|
+
" resets ",
|
|
696
|
+
resets
|
|
697
|
+
] })
|
|
698
|
+
] });
|
|
631
699
|
}
|
|
632
700
|
function SummaryRow({ label, summary }) {
|
|
633
701
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
@@ -639,13 +707,6 @@ function SummaryRow({ label, summary }) {
|
|
|
639
707
|
] }) })
|
|
640
708
|
] });
|
|
641
709
|
}
|
|
642
|
-
function ProgressBar({ percent, width = 36 }) {
|
|
643
|
-
const filled = Math.round(percent / 100 * width);
|
|
644
|
-
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
645
|
-
/* @__PURE__ */ jsx(Text, { color: "greenBright", children: "\u2501".repeat(filled) }),
|
|
646
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(width - filled) })
|
|
647
|
-
] });
|
|
648
|
-
}
|
|
649
710
|
function TableView({ rows: allRows, scroll, maxRows, wide }) {
|
|
650
711
|
const W = wide ? { label: 10, models: 18, input: 8, output: 8, cc: 8, cr: 9, total: 9, cost: 10 } : { label: 8, models: 14, input: 7, output: 7, cc: 7, cr: 8, total: 0, cost: 9 };
|
|
651
712
|
const lineW = W.label + W.models + W.input + W.output + W.cc + W.cr + W.total + W.cost;
|