tokens-metric 0.4.13 → 0.4.14
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 +78 -36
- package/dist/tui/index.js +24 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,53 @@
|
|
|
1
1
|
# tokens-metric
|
|
2
2
|
|
|
3
|
-
Real-time token usage meter for [Claude Code](https://claude.com/claude-code) — a terminal UI plus a one-line statusline you can wire into Claude Code itself.
|
|
3
|
+
Real-time token usage meter for [Claude Code](https://claude.com/claude-code) and [OpenAI Codex CLI](https://github.com/openai/codex) — a terminal UI plus a one-line statusline you can wire into Claude Code itself.
|
|
4
4
|
|
|
5
|
-
It tails the transcripts
|
|
5
|
+
It tails the transcripts both tools write to disk, aggregates usage in-memory, and renders it live. No API calls, no telemetry.
|
|
6
|
+
|
|
7
|
+
| Source | Transcript path |
|
|
8
|
+
|--------|----------------|
|
|
9
|
+
| Claude Code | `~/.claude/projects/**/*.jsonl` |
|
|
10
|
+
| Codex CLI | `~/.codex/sessions/YYYY/MM/DD/*.jsonl` |
|
|
6
11
|
|
|
7
12
|
## What it shows
|
|
8
13
|
|
|
9
|
-
###
|
|
14
|
+
### Always-visible status bars
|
|
15
|
+
|
|
16
|
+
One bar per active source, always on screen regardless of which panel is open:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
● sonnet-4-6 · 33.4M tok · ~$23.21 · 351 msgs · since 17:23 ● live 2s ago
|
|
20
|
+
● codex · 236M tok · ~$165.35 · 896 msgs · since 18:19 ○ idle 3m ago
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### [1] Breakdown
|
|
24
|
+
|
|
25
|
+
- Input / output / cache-write / cache-read bars with percentages and per-category cost
|
|
26
|
+
- Cache hit ratio with quality rating (excellent / degraded / poor)
|
|
27
|
+
- Context window fill gauge for the last turn
|
|
28
|
+
- Per-model totals when multiple models were used in a session
|
|
29
|
+
- 32-second activity sparkline with peak and avg rate
|
|
30
|
+
- **30-minute timeline chart** — vertical bar chart, one bucket per minute, stacked cyan (Claude) / magenta (Codex), oldest bars dimmed, current minute highlighted
|
|
31
|
+
|
|
32
|
+
### [2] History
|
|
33
|
+
|
|
34
|
+
Today / 7-day / 30-day aggregate: tokens, estimated cost, session count. Dual bar chart (Claude vs Codex) for the last 7 days. Data persisted in `~/.tokens-metric/history.json` so totals survive transcript rotation.
|
|
35
|
+
|
|
36
|
+
### [3] Sessions
|
|
37
|
+
|
|
38
|
+
Today's sessions sorted by start time. Navigate with `↑↓` — the selected session expands an inline detail panel showing per-category token bars, percentages, and cost breakdown.
|
|
39
|
+
|
|
40
|
+
### [4] Transcripts
|
|
10
41
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
42
|
+
Last five transcript files with recency timestamps.
|
|
43
|
+
|
|
44
|
+
### Header
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
● Claude Code detected · ● Codex detected · 4 sessions · 3 projects today
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Codex detection dot is green when `~/.codex/` exists, red when not installed.
|
|
17
51
|
|
|
18
52
|
### Statusline
|
|
19
53
|
|
|
@@ -31,7 +65,7 @@ Or run without installing:
|
|
|
31
65
|
npx tokens-metric
|
|
32
66
|
```
|
|
33
67
|
|
|
34
|
-
Requires **Node 18
|
|
68
|
+
Requires **Node 18+**. Claude Code and/or Codex CLI must be installed for data to appear.
|
|
35
69
|
|
|
36
70
|
## Usage
|
|
37
71
|
|
|
@@ -46,15 +80,16 @@ tokens-metric
|
|
|
46
80
|
| `←` / `→` | Move cursor between tabs |
|
|
47
81
|
| `Enter` | Open / collapse the focused tab |
|
|
48
82
|
| `1` – `4` | Jump directly to a tab and open it |
|
|
83
|
+
| `↑` / `↓` | Navigate sessions (while Sessions tab is open) |
|
|
49
84
|
| `Esc` | Collapse the open panel |
|
|
50
85
|
| `q` / `Ctrl-C` | Quit |
|
|
51
86
|
|
|
52
87
|
### Privacy defaults
|
|
53
88
|
|
|
54
|
-
|
|
89
|
+
Paths are masked by default so screenshots can be shared safely:
|
|
55
90
|
|
|
56
|
-
- `cwd` paths
|
|
57
|
-
-
|
|
91
|
+
- `cwd` paths shown as `~/…` instead of `/Users/<you>/…`
|
|
92
|
+
- User IDs masked to `●●●●●●●●`
|
|
58
93
|
|
|
59
94
|
Pass `--reveal` to show everything unmasked:
|
|
60
95
|
|
|
@@ -81,30 +116,41 @@ Output looks like:
|
|
|
81
116
|
🏢 team opus-4-7 │ in 117 · out 43.5k · cache 3.21M │ Σ 3.25M · ~$12.29 API-eq
|
|
82
117
|
```
|
|
83
118
|
|
|
119
|
+
## Pricing
|
|
120
|
+
|
|
121
|
+
Costs are estimated at **API-equivalent pricing** — a reference figure, not what you pay on Pro/Max/Team subscriptions.
|
|
122
|
+
|
|
123
|
+
| Provider | Models |
|
|
124
|
+
|----------|--------|
|
|
125
|
+
| Anthropic | claude-opus-4, claude-sonnet-4, claude-haiku-3.5, and prior generations |
|
|
126
|
+
| OpenAI | o4-mini, o3, o3-mini, gpt-4o, gpt-4o-mini (via Codex CLI) |
|
|
127
|
+
|
|
128
|
+
Prices are hardcoded in `src/core/format.ts`. Update the package when providers change their rates.
|
|
129
|
+
|
|
84
130
|
## Honest limitations
|
|
85
131
|
|
|
86
|
-
1. **Plan tier is heuristic.** We read flags Claude Code writes locally
|
|
87
|
-
2. **Pro vs. Max are not distinguishable locally.** Both look identical from
|
|
88
|
-
3. **The
|
|
89
|
-
4. **
|
|
90
|
-
5. **The transcript format is not a public API.** It works today; it may shift. The parser is intentionally tolerant of unexpected shapes.
|
|
132
|
+
1. **Plan tier is heuristic.** We read flags Claude Code writes locally. Anthropic can rename these at any release; the detector falls back to `unknown`.
|
|
133
|
+
2. **Pro vs. Max are not distinguishable locally.** Both look identical from config.
|
|
134
|
+
3. **The transcript format is not a public API.** It works today; it may shift. The parser is intentionally tolerant of unexpected shapes.
|
|
135
|
+
4. **Codex CLI model key is always `codex`.** The JSONL does not include a specific model name, so all Codex usage is priced at o4-mini rates.
|
|
91
136
|
|
|
92
137
|
## How it works
|
|
93
138
|
|
|
94
139
|
```
|
|
95
|
-
~/.claude/projects/<encoded-cwd>/<session
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
140
|
+
~/.claude/projects/<encoded-cwd>/<session>.jsonl ~/.codex/sessions/YYYY/MM/DD/<session>.jsonl
|
|
141
|
+
│ │
|
|
142
|
+
└──────────────┬───────────────────────────┘
|
|
143
|
+
▼
|
|
144
|
+
src/core/parser.ts reads + aggregates usage per message
|
|
145
|
+
src/core/tailer.ts watches active files with fs.watch
|
|
146
|
+
src/core/detect.ts auth, plan hints, Codex detection
|
|
147
|
+
src/core/history.ts per-day aggregation, mtime cache
|
|
148
|
+
src/core/history-store persists daily totals to ~/.tokens-metric/
|
|
149
|
+
src/core/updater.ts npm version check, 24h cache
|
|
150
|
+
│
|
|
151
|
+
┌────────────┴────────────┐
|
|
152
|
+
▼ ▼
|
|
153
|
+
src/tui (Ink) src/statusline
|
|
108
154
|
```
|
|
109
155
|
|
|
110
156
|
## Development
|
|
@@ -120,11 +166,7 @@ npm run build # builds dist/
|
|
|
120
166
|
|
|
121
167
|
## Roadmap
|
|
122
168
|
|
|
123
|
-
- **Windows support** — Claude Code on Windows stores transcripts
|
|
124
|
-
|
|
125
|
-
- **Multi-provider support (Codex, Gemini, etc.)** — The pricing table in `format.ts` currently covers Claude models only. Adding other providers means extending the table with `gpt-*`, `gemini-*`, `o1-*` prefixes, investigating whether those agents produce compatible `.jsonl` transcripts, and likely allowing the user to point to additional transcript folders.
|
|
126
|
-
|
|
127
|
-
- **Context window usage** — Show how much of the active session's context window is consumed. Claude Code may log this in the transcript; if not, it can be inferred per model (e.g. claude-3.5-sonnet = 200k tokens). Would appear as a progress bar in the session status bar or breakdown panel.
|
|
169
|
+
- **Windows support** — Claude Code on Windows stores transcripts under `%APPDATA%\Claude\`. Requires abstracting `claudeHome()` in `detect.ts` and handling Windows path separators.
|
|
128
170
|
|
|
129
171
|
## License
|
|
130
172
|
|
package/dist/tui/index.js
CHANGED
|
@@ -357,10 +357,12 @@ function BarRow({ label, value, max, total, color, cost, }) {
|
|
|
357
357
|
return (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: label }), _jsx(Text, { color: color, children: bar(value / max, BAR_WIDTH) }), _jsxs(Text, { children: [" ", fmtNumber(value).padStart(7, ' ')] }), _jsx(Text, { dimColor: true, children: ` ${pct.toFixed(1).padStart(5, ' ')}%` }), cost !== null && _jsx(Text, { dimColor: true, children: ` ~${fmtUSD(cost)}` })] }));
|
|
358
358
|
}
|
|
359
359
|
// ── 30-minute timeline chart ─────────────────────────────────────────────────
|
|
360
|
+
const PARTIAL_BLOCKS = [' ', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
|
360
361
|
function TimelineChart({ claudeTimeline, codexTimeline, }) {
|
|
361
362
|
const combined = claudeTimeline.map((c, i) => c + (codexTimeline[i] ?? 0));
|
|
362
|
-
const
|
|
363
|
-
const hasData =
|
|
363
|
+
const maxRaw = Math.max(...combined);
|
|
364
|
+
const hasData = maxRaw > 0;
|
|
365
|
+
const max = hasData ? maxRaw : 1;
|
|
364
366
|
const N = claudeTimeline.length;
|
|
365
367
|
const fmtY = (v) => {
|
|
366
368
|
if (v >= 1_000_000)
|
|
@@ -369,7 +371,10 @@ function TimelineChart({ claudeTimeline, codexTimeline, }) {
|
|
|
369
371
|
return `${Math.round(v / 1_000)}k`;
|
|
370
372
|
return String(Math.round(v));
|
|
371
373
|
};
|
|
374
|
+
// Y axis labels: only when there is real data
|
|
372
375
|
const yLabel = (r) => {
|
|
376
|
+
if (!hasData)
|
|
377
|
+
return ' ';
|
|
373
378
|
if (r === TIMELINE_ROWS - 1)
|
|
374
379
|
return fmtY(max).padStart(5);
|
|
375
380
|
if (r === Math.floor(TIMELINE_ROWS / 2))
|
|
@@ -377,15 +382,26 @@ function TimelineChart({ claudeTimeline, codexTimeline, }) {
|
|
|
377
382
|
return ' ';
|
|
378
383
|
};
|
|
379
384
|
return (_jsxs(Box, { borderStyle: "round", borderColor: "yellow", paddingX: 1, flexDirection: "column", children: [_jsxs(Text, { bold: true, color: "yellow", children: ['activity ', _jsx(Text, { bold: false, dimColor: true, children: "\u00B7 last 30m" })] }), Array.from({ length: TIMELINE_ROWS }, (_, i) => {
|
|
380
|
-
const r = TIMELINE_ROWS - 1 - i;
|
|
381
|
-
const threshold = (r / TIMELINE_ROWS) * max;
|
|
385
|
+
const r = TIMELINE_ROWS - 1 - i; // r=ROWS-1 at top, r=0 at bottom
|
|
382
386
|
return (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: [yLabel(r), " \u2502"] }), combined.map((v, c) => {
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
|
|
387
|
+
// fractional bar heights in row units
|
|
388
|
+
const totalH = (v / max) * TIMELINE_ROWS;
|
|
389
|
+
const codexH = ((codexTimeline[c] ?? 0) / max) * TIMELINE_ROWS;
|
|
390
|
+
if (totalH <= r)
|
|
391
|
+
return _jsx(Text, { children: " " }, c);
|
|
392
|
+
// smooth top using partial block chars
|
|
393
|
+
const char = totalH >= r + 1
|
|
394
|
+
? '█'
|
|
395
|
+
: (PARTIAL_BLOCKS[Math.max(1, Math.round((totalH - r) * 8))] ?? '█');
|
|
396
|
+
// stacked color: codex fills from bottom, claude on top
|
|
397
|
+
const isCodex = codexH > r + 0.5;
|
|
398
|
+
// oldest third dimmed, current minute (rightmost) bright
|
|
399
|
+
const isCurrentMinute = c === N - 1;
|
|
400
|
+
const isOld = c < Math.floor(N / 3);
|
|
401
|
+
return (_jsx(Text, { color: isCodex ? 'magenta' : 'cyan', dimColor: isOld && !isCurrentMinute, bold: isCurrentMinute, children: char }, c));
|
|
386
402
|
})] }, r));
|
|
387
403
|
}), _jsx(Text, { dimColor: true, children: ' └' + '─'.repeat(N) }), _jsx(Text, { dimColor: true, children: ` -30m${' '.repeat(Math.max(0, N - 7))}now` }), _jsx(Box, { marginTop: 1, children: hasData
|
|
388
|
-
? _jsxs(Text, { dimColor: true, children: [
|
|
404
|
+
? _jsxs(Text, { dimColor: true, children: ['peak ', _jsx(Text, { color: "white", children: fmtY(max) }), '/min · ', _jsx(Text, { color: "cyan", children: '█' }), ' claude ', _jsx(Text, { color: "magenta", children: '█' }), ' codex'] })
|
|
389
405
|
: _jsx(Text, { dimColor: true, children: "filling\u2026 (1 bar/min)" }) })] }));
|
|
390
406
|
}
|
|
391
407
|
// ── History panel ────────────────────────────────────────────────────────────
|