A status line for Claude Code that shows, under the prompt, what the session is spending: the model, how much context is left, the prompt-cache read and write tokens of the last call, the session cost, and your Pro/Max usage windows with their reset countdowns.
Version 0.0.2. Python 3 standard library only. Windows, macOS, and Linux.
Add the marketplace and install the plugin from inside Claude Code:
/plugin marketplace add gabgoss/totalizer-for-claude-code
/plugin install totalizer@totalizer-marketplace
Then register the status line. Pick a scope:
/totalizer:install # every project on this machine (user scope, full layout)
/totalizer:install project # this project only
/totalizer:install user compact # one abbreviated line instead of two
A new session shows it at once. Sessions already open pick it up on their next refresh. The VS Code extension's "Open Claude in terminal" runs the same CLI, so a user-scope install covers it. No terminal profile is needed.
Other commands:
| Command | Effect |
|---|---|
/totalizer:status |
Shows which scope carries a status line and whether it is Totalizer's. |
/totalizer:uninstall [user|project] |
Removes the registration from that scope. |
Scope is a toggle. Installing at one scope removes Totalizer's registration from the other, so one copy is active. Project settings override user settings in Claude Code. A status line from another tool is reported, never removed.
A Claude Code plugin cannot set the statusLine setting by itself, so /totalizer:install runs a small installer. It copies statusline.py to ~/.claude/totalizer/ and points the setting at that copy. Claude Code keeps each plugin version in its own cache directory and purges old ones, so pointing at the cache would break on the first update. After a plugin update, run /totalizer:install again to refresh the copy.
The installer can also run by hand from a checkout:
python plugins/totalizer/scripts/install.py --scope user
python plugins/totalizer/scripts/install.py --status
python plugins/totalizer/scripts/install.py --scope user --uninstall
--python picks the interpreter, --project-dir the project root for project scope, --shared writes the committed .claude/settings.json instead of settings.local.json, and --no-copy points the setting at the checkout for development.
Claude Code pipes a JSON snapshot of the session to the script after every assistant message. Every value comes from that payload, except the plan type.
📈 Session: Opus 5/high · Tokens: 129K · Remaining: 87% · Cache Read: 128K · Cache Write: 333 · Cache Status: 🔥 52m
⚡ Usage: Cost: $6.87 · Session (5hr): 48% (resets in 1h) · Weekly (7 day): 70% (resets in 3d)
| Full label | Compact | Source field | Rule |
|---|---|---|---|
📈 Session: Opus 5/high |
Opus 5/high |
model.display_name, effort.level |
Parenthetical dropped from the name. |
Tokens: 129K |
ctx 13% 129k |
sum of current_usage input + cache_creation + cache_read |
The true context of the last call. Compact also shows used_percentage. |
Remaining: 87% |
n/a | 100 - context_window.used_percentage |
Share of the model's context window still free. Green above 50, yellow above 20, red below. |
Cache Read: 128K |
cr 128k |
current_usage.cache_read_input_tokens |
Served from the prompt cache. The group reads no call yet before the first call and right after a compaction. |
Cache Write: 333 |
cw 333 |
current_usage.cache_creation_input_tokens |
Written into the cache by the last call. |
Cache Status: 🔥 52m |
🔥 52m |
prompt_cache.warm, prompt_cache.expires_at |
See "Cache status" under this table. Omitted until a call reports it. |
⚡ Usage: Cost: $6.87 |
$6.87 |
cost.total_cost_usd |
Session cost. The ⚡ is a visual cue for the Usage line and carries no data. |
Session (5hr): 48% (resets in 1h) |
5h 48% ↻1h |
rate_limits.five_hour |
Subscription only. Green under 50 used, yellow under 80, red from 80. |
Weekly (7 day): 70% (resets in 3d) |
7d 70% ↻3d |
rate_limits.seven_day |
Same. |
Weekly Fable: 86% |
Fable 86% |
any other rate_limits key |
Rendered generically, so a per-model window appears if the CLI sends one. |
TOTALIZER_NO_COLOR=1 disables colour.
Every message re-sends the whole conversation. The API caches that prefix, so the next call can read it instead of re-processing it. A cache read costs about a tenth of the normal input price. A cache write costs about 1.25 times it. The cache lives for a fixed time after each call (the session's TTL, 5 minutes or 1 hour).
🔥 52m— the cache is warm and expires in 52 minutes. Your next message reads it cheaply.❄️ cold— the cache expired. Your next message rewrites the whole context at the write price.
On a large session, one message sent after the cache went cold costs more than many sent while it was warm. The countdown tells you where that line is.
The usage windows appear only on a subscription. Detection, first match wins:
TOTALIZER_PLAN=payg|sub— explicit override.~/.claude/.credentials.json→claudeAiOauth.subscriptionType— present on a claude.ai login (pro,max,team,enterprise), absent on an API-key login. The script reads that one field and nothing else. The field is undocumented, so treat it as best effort.rate_limitspresent in the payload — the documented signal (subscribers only). It lags the first API response, so it is the last resort.
On pay-as-you-go the Usage line shows cost only.
/usage shows a per-model weekly line. The documented status-line schema has only five_hour, seven_day, and spend_limit. Live payloads on CLI 2.1.269 carried the first two. The generic branch renders any extra key without a code change. To see what a session receives, run it with TOTALIZER_DUMP=1 and read ~/.claude/totalizer/last-payload.json.
Every distinct reading is appended to ~/.claude/totalizer/log.jsonl (override the directory with TOTALIZER_HOME). One file for all projects. A row carries ts, session_id, cwd, project_dir, model, effort, used_percentage, total_input_tokens, current_usage, cost_usd, rate_limits, cache_hit_ratio, transcript_path, version.
Parallel sessions are handled: appends take a lock file (Windows append mode seeks then writes, so two unlocked appends tear each other), and dedupe compares against the newest row of the same session. A writer that cannot get the lock within about 500 ms drops its row rather than delay the status line.
The script never raises. A bad or empty payload still prints a line and exits 0.
| Variable | Effect |
|---|---|
TOTALIZER_HOME |
Directory for the script copy, log.jsonl, and last-payload.json. Default ~/.claude/totalizer. |
TOTALIZER_MODE |
full or compact. The --mode flag written by the installer wins. |
TOTALIZER_PLAN |
payg or sub. Overrides plan detection. |
TOTALIZER_NO_COLOR |
1 disables ANSI colour. |
TOTALIZER_DUMP |
1 also writes the raw payload to last-payload.json. |
Layout:
.claude-plugin/marketplace.json the marketplace this repo serves
plugins/totalizer/ the shipped plugin (everything under here reaches consumers)
.claude-plugin/plugin.json
commands/install.md, status.md, uninstall.md
scripts/statusline.py, install.py
tests/selftest.py, tests/fixtures/ not shipped
Test:
python tests/selftest.py
Runs the fixtures through the shipped script in a temporary home and checks both layouts, the pay-as-you-go branch, the dedupe, the no-line-ending rule, and eight parallel writers. Expected last line: OK.
Before a release, validate the plugin and confirm the shipped subtree carries no cache or test files:
claude plugin validate plugins/totalizer
The second check is a directory listing of plugins/totalizer/ with no __pycache__ in it.
- Ledger reset: on the weekly window boundary for Pro/Max, on budget replenishment for pay-as-you-go.
- Per-model weekly window, once the CLI sends it or another source is documented.
Apache License 2.0. See LICENSE.