Quota, context, cost, model, and git -- live in your Claude Code prompt.
One Bash file that plugs into the official statusLine command hook. Shows
what matters: active model, context window, session cost, 5h / 7d quota with
reset times, prompt-cache health, extra-usage spend, git activity, and subscription tier.
When the numbers stop meaning what they appear to mean, an advisor second
row interprets them — cap projections, expiring-surplus and
underuse advice, per-model weekly limits, sibling-account relief.
No daemon, no telemetry, no npm.
curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/install.sh | bashDownloads statusline.sh to ~/.claude/ and wires up settings.json.
Restart Claude Code (or send a message) and the statusline appears.
Requires: Bash, jq, curl.
Paste this into Claude Code and it will set everything up:
Install claude-code-statusline: download https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/statusline.sh to ~/.claude/statusline.sh, make it executable, and add a statusLine command entry to ~/.claude/settings.json pointing to it with padding 0.
Manual install / inspect first
# Download and inspect
curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/install.sh -o /tmp/install-statusline.sh
less /tmp/install-statusline.sh
bash /tmp/install-statusline.sh
# Or skip the installer entirely
curl -fsSL https://raw.githubusercontent.com/thevibeworks/claude-code-statusline/main/statusline.sh -o ~/.claude/statusline.sh
chmod +x ~/.claude/statusline.shThen add to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline.sh",
"padding": 0
}
}project (main*) +84/-14 1h30m $6.72 opus4.8[1m][███░░░26%] [MAX|you] 5h[87%@14:30] 7d[75%@2d] ex[$16.29/$200 8% bal$4.66]
| | | | | | | | | |
path branch edits time cost model+context user 5h quota 7d quota extra usage
Every component earns its place:
| Signal | Why it matters |
|---|---|
| Path and branch | Know where Claude Code is writing. Neutral grey; a dirty branch brightens to white with a *. |
| Activity | Session diff without opening git. |
| Time and cost | Track long sessions. Hours format above 60m (1h30m). |
| Model | Abbreviated: claude-opus-4-8 becomes opus4.8, claude-fable-5 becomes fabl5, claude-sonnet-5 becomes sonnet5. The [1m] tag marks a 1M-context session, detected from the window the CLI reports (context_window_size) — not the name — so it shows even when Claude Code strips the [1m] suffix (which it does since 2.1.173 whenever 1M is the default; Sonnet 5 joined Fable 5 on that path in 2.1.197). |
| Effort | Compact lowercase badge: lo / md / xh / max / ultra / auto (high is the default and stays hidden). Dim for routine levels; max / ultra use the pressure color; fast shows in fast mode. |
| Context bar | Merged with model. Green / yellow / red by window pressure. On 1M models the bar also carries the premium input-pricing band: yellow past 200k tokens, red past 800k — the % alone looks calm (320k = 32%) while every request bills at the premium rate. A real 0% (e.g. right after /compact resets the window) renders a visibly empty bar [░░░░░░0%] — the snap to empty is the refresh signal; the bar hides only when no context data exists at all. Changes flash for ~60s in reverse video: [█░░░░░30%]+9 while filling, [░░░░░░3%]-27 right after a compaction. Cost flashes the same way, cents-precise: $7.90+.37. |
| User tier | Neutral white-weight (MAX bold, PRO normal, dim otherwise) — identity, never a status color. Truncated display name. |
| Quota | Integer percentages. The 5h badge always carries its reset time while a window is live — 5h[42%@14:30] reads "42% used, resets at 14:30" — because on a 5h horizon the reset is the number you plan the current sitting around. Wall-clock, not a countdown, on purpose: Claude Code only re-renders the statusline on activity, so a relative "@1h38m" silently decays into a lie during idle gaps, while "@14:30" stays true in a frozen frame. (The 7d badge is hybrid: day-relative @5d while the reset is >= 24h out — decays one day per day, mild and narrow — switching to the same wall-clock @04:00 inside the last day, where an @6h/@<1h countdown decayed by the hour exactly when pressure keeps the suffix visible.) When a window's utilization climbs between renders, a reverse-video +N token appears right after the badge for ~60s: 5h[44%@14:32]+2 means "you just burned 2%". A drop (window reset) stays quiet — the fresh low number is its own signal. 7d is forecast, not leveled: a learned per-weekday burn profile (EWMA over your own usage history) plus your recent 24h burn project whether the quota outlasts the window — your heavy Tuesday counts more than a generic average. The verdict is color alone; under pressure the badge shows when relief arrives: 7d[44%@5d] red means "at your pace, dry days before the reset 5 days from now"; inside the last day it reads 7d[92%@04:00] — resets at 04:00. Cold start (<14 days history) falls back to window-average pacing. Recovery color when reset is imminent. Model-scoped weekly quota: when the usage API carries a per-model weekly limit (limits[], kind=weekly_scoped) for the model your session is running, it renders right after the model+context block — fabl5[1m][12%] fb[67%] on a Fable 5 session, op[33%] on Opus — because the quota is a property of the model you're running, not of the account-wide 5h/7d cluster. It's a weekly number (same reset as the 7d badge), scoped to one model. Other models' scoped quotas stay hidden: only the limit constraining this session is signal. Supersedes the legacy seven_day_opus/seven_day_sonnet fields, which the API now sends as null. |
| Extra usage | Monthly spend, limit, prepaid balance. --extra auto shows when quota runs out. |
| Cache health | Quiet until it bites. Claude Code never re-renders an idle session, and while you work the prompt cache is always freshly ~1 TTL from expiry — so a proactive "expiring soon" isn't honestly observable, and auto spends no width on it. It speaks only when a rewrite actually happens: ≡!419k the instant you resume onto a dead cache (idle longer than the TTL) or a mid-session prefix collapse — a 419k-token re-cache at ~20x the read rate (and the same burn on your 5h/7d quota on subscriptions). Bold red past 200k — the premium-band miss. ≡~ while a large prefix rebuilds. --cache always additionally keeps the freeze-safe deadline ≡@15:20 (last request + TTL; a past time in a frozen frame reads "expired at 15:20"). TTL defaults to 1h (claude.ai subscriber sessions) or 5m (API-key auth); an observed usage breakdown overrides it. The ≡ glyph (U+2261) reads as stacked cache layers — one terminal column, quiet and distinct. |
Color follows three lanes so a glance is unambiguous: status (green/yellow/red) = pressure only — quota, context, cache, the premium context band, expensive effort; identity (magenta/cyan/blue; fable = bright red, matching its Claude Code TUI color) = model family; everything else is neutral grey/white. Warm status color always means "near a limit or cost."
| Feature | /statusline |
This repo |
|---|---|---|
| Context bar, cost, git | Yes | Yes |
| Live 5h / 7d quota | -- | Yes |
| Extra usage + prepaid balance | -- | Yes |
| Quota reset time | -- | Yes |
| Adaptive polling (30s -- 5min) | -- | Yes |
Refresh ~ / error ! indicator |
-- | Yes |
--extra display gating |
-- | Yes |
| Tier display + model abbreviation | -- | Yes |
| 5 themes, 9 bar styles | -- | Yes |
| Prompt cache break detection | -- | Yes |
| OAuth + macOS Keychain | -- | Yes |
Change flash on every refresh (+.37 cost, +9/-27 context, +N quota) |
-- | Yes |
Model-scoped weekly quota (fb/op/sn) |
-- | Yes |
| Works behind trusted mitm proxies (NODE_EXTRA_CA_CERTS) | -- | Yes |
| 266 bats tests + CI | -- | Yes |
Flags go in the command string in ~/.claude/settings.json:
"command": "bash ~/.claude/statusline.sh --theme developer --extra on-limit"| Flag | Values | Default |
|---|---|---|
--theme |
minimal, compact, detailed, developer, manager |
(none) |
--style |
unicode-blocks, single-block, bracketed-bars, filled-dots, square-blocks, line-segments, ascii-bars, percent-only, fraction-display |
unicode-blocks |
--order |
Comma-separated: activity,time,cost,model,user,quota,extra |
all |
--path-display |
project, cwd, full, relative |
project |
--alignment |
left-right, right-left, center |
left-right |
--extra |
auto, always, on-limit, off |
auto |
--cache |
auto, always, off |
auto |
--advisor |
auto, always, off — second-row projection line, see Advisor line |
auto |
--debug |
Write logs to ~/.claude/statusline/logs/statusline.log |
off |
--test [json] |
Render with mock data | off |
| Theme | What it does |
|---|---|
minimal |
Model + context + user. Extra off. |
compact |
Everything. Unicode bars. Project path. |
detailed |
Bracketed bars. Working directory. |
developer |
Full path. Filled dots. Right-aligned. Extra on-limit. |
manager |
Percent-only. Cost first. Centered. |
--extra auto 5h[24%@14:30] 7d[10%] (calm, hidden)
--extra auto 5h[87%@14:30] 7d[10%] ex[$19.52/$200 10%] (5h >= 80%, shown)
--extra always 5h[24%@14:30] 7d[10%] ex[$19.52/$200 10% bal$4.66] (always shown)
--extra on-limit 5h[87%@14:30] 7d[10%] ex[$19.52/$200 10%] (same as auto minus extra_util gate)
--extra off 5h[24%@14:30] 7d[10%] (always hidden)
auto (default) shows extra when quota runs out (5h >= 80%, 7d >= 70%) or
extra budget is pressured (utilization >= 50%). Compact by default, actionable
when it matters.
--cache auto fabl5[1m][42%] (healthy: silent — no width spent)
--cache auto fabl5[1m][5%] ≡~ (a large prefix is rebuilding)
--cache auto fabl5[1m][43%] ≡!419k (resume onto a dead cache: 419k rewrite; BOLD red >200k)
--cache always opus4.8[1m][21%] ≡:1h@14:20 (opt in to the freeze-safe deadline; :1h = observed TTL)
--cache off opus4.8[1m][21%] (disabled; no state writes)
Why healthy is silent. The server refreshes the cache TTL on every request,
so while you work the cache is always freshly ~1 TTL from expiry — there is no
honest "expiring soon" to show, and a deadline that's always ~an hour out is
pure width. The expiry only becomes real during an idle gap, and Claude Code
renders the statusline only on activity — so no warning can appear while you're
away. The honest moment is when you come back: the first post-idle render sees
activity resume after a gap longer than the TTL and reports the rewrite as
≡!Nk, sized at the re-cached prefix (≡!419k), bold red past
200k — the expensive premium-band miss. This fires even when the 300ms render
debounce skipped the read=0 turn, because the detector keys off the stale
activity anchor, not the one-frame collapse; breaks are held ~60s so a busy
turn's refresh doesn't erase them. A cache miss re-caches the whole prefix at
~20x the cache-read rate, and on subscription plans the same multiplier lands
on your 5h/7d quota.
The activity anchor is the last observed usage change, not the last render — Claude Code also re-runs the statusline on vim/permission/model changes with unchanged usage, and re-stamping there would fake a warm cache.
The freeze-safe deadline, on demand. --cache always adds ≡@15:20
(last request + TTL): a past time in a frozen frame reads "expired at 15:20",
so you can decide before typing whether to resume this session or start
fresh. Wall-clock, never a countdown — "expires in 43m" rendered an hour ago is
a lie, "@15:20" stays true however stale the frame is. Claude Code's statusline
stdin exposes aggregate cache tokens only, not the ephemeral_1h/5m breakdown,
so the TTL is assumed from how the CLI actually requests caching (verified in
traces): 1h for claude.ai subscriber sessions, 5m for API-key /
custom-endpoint auth (the CLI's FORCE_PROMPT_CACHING_5M /
ENABLE_PROMPT_CACHING_1H overrides are honored). An observed breakdown wins
and renders its class as provenance (≡:5m@14:25). Known gap: a subscriber
session that started while on overage is latched to 5m server-side, invisible
here — the ≡!Nk badge still reports the miss after the fact.
| 5h utilization | Interval |
|---|---|
| < 20% | 5 min |
| 20 -- 49% | 2 min |
| 50 -- 79% | 1 min |
| >= 80% | 30 sec |
Error cooldown escalates with consecutive failures — 2 min, 4 min, 8 min, 10 min
cap — and a server Retry-After (429s carry one) extends it further. The
cooldown resets on the next successful fetch. Cache writes: atomic mv.
Indicators: ~ after quota = refresh in flight. A failed fetch shows why the
data may be stale: !429 rate limited, !auth token rejected, !5xx server
error, !net connection failed.
echo '{"model":{"id":"claude-opus-4-8[1m]","display_name":"Opus"},"cwd":"/tmp/project","workspace":{"current_dir":"/tmp/project"},"cost":{"total_cost_usd":6.72,"total_lines_added":84,"total_lines_removed":14,"total_api_duration_ms":5400000},"version":"2.1.139"}' \
| bash statusline.sh --testThe statusline can render a second row — Claude Code displays each stdout line as its own row (docs). The advisor uses it to interpret the badges: it speaks only when the numbers on line 1 don't mean what they appear to mean, and every clause derives from a badge already shown — no third alarm channel. It cuts both ways, with a voice per direction:
- pressure —
! ...in yellow/red: you'll hit a wall before a reset. - opportunity —
+ ...in cyan: paid capacity is about to expire unused, or a sibling account is free while you're pinned. Cyan can never mean pressure, so the color alone carries the stance.
Quiet means no row at all: a healthy session stays one line. The row is right-aligned to the same anchor the stats cluster ends at, so the advice sits directly beneath the badges it interprets.
proj (main*) fabl5[1m][██░░42%] fb[86%] [MAX|@work] 5h[95%@06:00] 7d[44%@07:00]
! 5h caps ~05:18, 42m before reset; 7d resets @07:00, 56% unused
What it says, in value order (max two clauses):
| Clause | When |
|---|---|
! fb capped — back ~Thu 07:00 |
The weekly limit scoped to this session's model (limits[] weekly_scoped) hit 100% — the model just became unavailable, and the one number that matters is when it returns. |
! 5h caps ~14:20, 52m before reset |
The 5h badge is already yellow/red and the linear projection lands before the reset. Suppressed when relief is <= 30min out, same as the badge's recovery color. |
+ 7d resets @07:00, 56% unused — spend it (or — ~40% expires even at full burn) |
Expiring surplus: inside the last day of the 7d window with >= 30% unused, a green badge means forfeiture, not headroom — at reset the remainder vanishes whether spent or not. The tail is feasibility-checked against the learned pct_per_window ratio (how many 7d points a fully burned 5h window costs you, mined from your own usage log): "spend it" appears only when full-tilt burning can actually consume the surplus; past that point the honest tail is how much expires no matter what. While the ratio is unlearned the clause states the bare fact and advises nothing. |
+ alt 5h[8%] free |
Fleet relief for shared-home multi-account setups: once this account's 5h hits 90%, the idlest fresh sibling under accounts/*/ is the actionable way out. Read-only, no credentials. |
! fb caps ~Wed 18:00, 1d before reset |
The running model's scoped weekly quota caps before its reset — same linear math, gates, and recovery suppression as the 7d aggregate. |
! 7d dry ~Thu 09:00, 2d before reset — then extra billing (or then hard stop) |
The learned weekday forecast projects the quota drying up early; cold start falls back to linear pace, but only once seven_day_pace already warns. The tail states what actually happens at 100%. |
+ 7d on pace to leave ~62% unused — go heavier |
Underuse: on pace to strand a large chunk of the subscription. The learned weekday profile speaks first — it knows your remaining days, so it can warn from day two; cold start falls back to linear pace past half the window. Speaks only in an engaged, unsqueezed session (5h between 25% and 80%, no pressure clause) — it reaches exactly the person who can act on it and never nags an idle one. |
- ~19x5h left, even pace 1.1%/win, heading ~52% |
--advisor always only, when calm: the weekly budget in one breath. "heading" is the learned end-of-week projection when trained, linear once the window is a day old. |
The 7d window gets one voice per render — surplus, dry, or underuse, never two that could disagree.
--advisor auto # default: speak under pressure or expiring surplus
--advisor always # add the weekly budget line when calm
--advisor off # single row, neverAll times are wall-clock (~14:20) or future-to-future gaps (52m before reset = reset minus cap, both in the future) — freeze-safe in an idle
frame, same idiom as the badges. Add "refreshInterval": 60 to your
statusLine settings if you want the row re-evaluated on a timer while
idle.
For a standalone full-screen watcher (multi-account polling,
notifications), see claude.py --watch-usage in
claudex — it consumes the same
state dir this script maintains (see docs/api/state-dir.md).
OAuth and API behavior
Quota, profile, and extra-usage requests use Claude Code's OAuth credentials
from ~/.claude/.credentials.json. Expired tokens are refreshed automatically
via the same refreshToken flow the CLI uses.
If ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or ANTHROPIC_BASE_URL is set,
all OAuth-dependent components (quota, user, extra) are skipped silently.
macOS Keychain is tried as fallback when no file credential exists. OrbStack
resolves the real Linux home directory via getent passwd.
The script never writes to Anthropic endpoints -- it only reads usage, profile, and prepaid balance data.
Account-scoped cache (one fetch, all sessions). Quota / profile / prepaid
data is identical for every session on the account, so it's cached once in a
shared dir -- ~/.claude/statusline/ -- and a single fetch serves all
concurrent sessions. Per-session prompt-cache-health state stays under
~/.claude/statusline/sessions/. Old $SCRIPT_DIR state is migrated on first
run. Setting only CLAUDE_CACHE_DIR keeps the legacy single-dir behavior.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
CLAUDE_CONTEXT_LIMIT |
auto | Override context token limit |
CLAUDE_7D_WORKDAYS |
unset | Skip weekends in the 7d pace deadline — quota you won't spend Sat/Sun no longer counts against runway (opt-in; the limit itself stays calendar-based) |
CLAUDE_DATA_DIR |
~/.claude/statusline |
Account-scoped cache + usage log location |
CLAUDE_CACHE_DIR |
$CLAUDE_DATA_DIR/sessions |
Per-session cache-health state |
STATUSLINE_ACCOUNT |
unset | Account label for multi-account setups: renders an @label chip and moves account caches to accounts/<label>/ so concurrent accounts stop sharing one quota cache |
DEVA_AUTH_TAG |
unset | Same as above, set automatically by deva from --auth-with (auth-file-<stem> -> @<stem>); auth-default means single-account and is ignored. Containers from pre-v0.18 deva without the tag are resolved from DEVA_AUTH_METHOD/DEVA_AUTH_DETAILS instead |
DEBUG_LOG |
~/.claude/statusline/logs/statusline.log |
Debug log path |
DEBUG_LOG_MAX_BYTES |
1048576 |
Debug log size cap before rotation |
CLAUDE_CODE_MAX_OUTPUT_TOKENS |
32000 |
Output token reserve |
CLAUDE_CONFIG_DIR |
~/.claude |
Claude config directory |
npm exec --yes bats -- t/321 tests across t/statusline.bats (309 statusline + integration) and
t/install.bats (12 installer). CI runs on push and PR to main.
statusline.sh Main script (one file, ~3400 lines)
install.sh One-line installer
t/statusline.bats Unit and integration tests
t/install.bats Installer tests (mock curl, isolated $HOME)
t/helpers.bash Sources real functions from statusline.sh
.github/workflows/test.yml CI workflow
CHANGELOG.md Release notes
CONTRIBUTING.md Contribution guide
docs/devlog/ Implementation history
docs/api/oauth-usage.md Observed /api/oauth/usage contract (synced: CLI v2.1.201)
See CONTRIBUTING.md. Short version: keep it to Bash + jq +
curl, add tests, run bats t/ before pushing.