A one-file status line for Claude Code. No daemon, no cache, no network, no dependencies.
5h ██░░░ 36% →104% 3h15m │ 7d ███░░ 62% →103% 2d19h │ Opus 5 12% │ $5.23
Claude Code already pipes everything a status line needs to its statusLine
command on stdin — quota usage and reset times, context window, cost, model,
cache state. Reading that blob and printing one line is the whole job.
So ccbar is ~150 lines of stdlib Python in a single file. It spawns per tick, prints, and exits. Nothing stays resident.
| ccbar | a daemon-backed bar | |
|---|---|---|
| resident processes | 0 | 3+ |
| install size | 5 KB | ~28 MB |
| per tick | ~22 ms | ~86 ms |
| dependencies | none | Python runtime bundle |
The line mixes three things that reset on different clocks. Worth knowing which is which before you react to a red number.
| segment | resets when |
|---|---|
5h 7d quota |
the window rolls over, on its own schedule |
Opus 5 12% context |
you start a new conversation |
$5.23 cost |
you start a new conversation |
Share of the rolling 5-hour and 7-day limits already spent, from
rate_limits.five_hour and rate_limits.seven_day. The bar is the same number
as the digits, there to be readable at a glance without reading. Green below
60%, yellow to 85%, red above.
On a third-party relay or Bedrock/Vertex there is no quota, so Claude Code
sends no rate_limits and both segments disappear rather than showing zeros.
Where usage lands at window close if the current burn rate holds. Over 100% means you run out before the window resets.
Claude Code sends when a window resets but not when it opened, so the start
is resets_at - window and the rest is used ÷ elapsed_fraction. Two calls in
the first minute of a window look like an infinite rate, so the projection
stays hidden until 10% of the window has passed.
It extrapolates the whole window's average forward in a straight line. A heavy burst in the last twenty minutes will overstate it — treat it as a direction, not a forecast.
Counts down to resets_at. Reads 2d19h past a day, 14m under an hour.
The percentage is how much of the context window this conversation has
filled (context_window.used_percentage), not quota and not anything about the
model. At 12% of a 1,000,000-token window, roughly 122k tokens of history are
live. It only climbs as a conversation grows; near the top Claude Code compacts
the earlier turns. A new conversation starts over at zero.
cost.total_cost_usd — what this conversation has cost so far. Not a daily
or monthly total, and it resets with each new session. Dimmed because it is the
least actionable number on the line.
/plugin install ccbar --marketplace egg-/ccbar
/ccbar:setup
A plugin can't set the status line itself, so /ccbar:setup writes the
statusLine entry in ~/.claude/settings.json for you, asking first if one is
already there. What the plugin does, in full:
- On each new session, a
SessionStarthook copiesccbar.pyinto the plugin's data directory (~/.claude/plugins/data/ccbar-ccbar/). The status line points there because that path survives plugin updates, so an update reaches the bar on the next session. /ccbar:setupcopies the file once more, editsstatusLinein your user settings, and nothing else. It only runs when you type it.- Nothing else. No network, no other files, no permission changes.
Uninstalling deletes the data directory, so remove the statusLine entry from
~/.claude/settings.json at the same time, or the bar goes blank.
Drop the file somewhere and point Claude Code at it:
curl -fsSL https://raw.githubusercontent.com/egg-/ccbar/main/ccbar.py -o ~/.local/bin/ccbar
chmod +x ~/.local/bin/ccbarThen in ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "/Users/you/.local/bin/ccbar",
"refreshInterval": 10
}
}Restart Claude Code once. That's the install — there is nothing to uninstall but the file and those four lines.
refreshInterval is in seconds. At 10s a 22 ms tick costs about 0.2% of one
core; at 1s it costs 2%. Quota percentages do not move fast enough to be worth
the difference.
Because each tick is a fresh process, edits to the script take effect on the next tick. No restart, no reload.
Edit the constants at the top of the file:
WARN, CRIT = 60, 85 # percentages where green turns yellow, then red
BAR_WIDTH = 5There is no config file, and that is deliberate. Editing two numbers in a
script you already own is less machinery than a config format, a parser, and a
config set subcommand.
The stdin blob carries more than the bar shows — prompt_cache (hit ratio,
TTL, expiry), effort.level, session_name, thinking.enabled, fast_mode,
lines added and removed. Read the field and append to seg in render():
eff = (d.get("effort") or {}).get("level")
if eff:
seg.append(f"{DIM}{eff}{RESET}")To see the whole blob for your own session, have the status line dump it once:
"command": "tee /tmp/blob.json | /Users/you/.local/bin/ccbar"python3 ccbar.py --selftestRender against a payload:
echo '{"model":{"display_name":"Opus 5"},"cost":{"total_cost_usd":1.5}}' | python3 ccbar.pyEvery segment is dropped when its data is absent, so a partial blob renders a shorter line instead of erroring, and malformed JSON prints nothing at all.
leeguooooo/claude-code-usage-bar (MIT) is the fuller take on this idea — themes, styles, a desktop HUD, a daemon render path. Reach for it if you want those. ccbar shares no code with it and exists because the stdin contract makes the minimal version this small.
MIT