Model, project, a context bar with token count, what each turn costs in cache reads, how much the context has grown, and five-hour and seven-day usage. Adds a line suggesting /compact or /clear once context gets large. Python, no network calls. Keeps a small JSON file per session in ~/.claude/.statusline-state/ and deletes ones older than a week.
0 copies
Preview
Opus 4.8 · app · ▕███░░░░░░░░░▏ 44k · 4.4k/turn · 5h 26% · 7d 7%
Opus 4.8 · app · ctx —
Sonnet 4.6 · app · ▕██████░░░░░░▏ 96k · 9.6k/turn · 5h 40% · 7d 18%
Opus 4.8 · app · ▕███████████░▏ 182k · 18k/turn · 5h 88%↻17m · 7d 61%↻19h59m
⚠ context 91% full — /compact to keep this thread, /clear to start fresh
Fable 5 · app · ▕████████░░░░▏ 640k · 64k/turn · 5h 33% · 7d 80%↻119h59m
⚠ 640k context — every turn now costs 64k before you type. /clear if you have switched task, /compact to keep going
Haiku 4.5 · app · ctx — · 5h 52%↻4h01m
Opus 4.8 · feature · ▕████░░░░░░░░▏ 74k · 7.4k/turn · 5h 44% · 7d 20%
Opus 4.8 · scratch · ▕███░░░░░░░░░▏ 44k · 4.4k/turn
What it does
Model, project, a context bar with token count, each turn's cache-read cost, and 5-hour and 7-day usage. A second line suggests /compact or /clear once context gets large.
- Written in
- Python
- Network access
- None
- Works in
- Terminal
How it works
- Reads model, folder, total_input_tokens, context percent and rate_limits from stdin.
- Shows a tenth of the input tokens as each turn's cache-read cost, like 4.4k/turn.
- Tracks the session's smallest context and adds growth like 3.0× at 1.5 times that.
- Limits at 50%+ add a reset like ↻17m. At 80% full or 120k tokens, a warning line shows.
Good to know
- Writes ~/.claude/.statusline-state/<session_id>.json. About 1 run in 37 deletes files there over a week old.
- CC_CTX_WARN, CC_CTX_HIGH and CC_CTX_FULL_PCT change the thresholds. CC_SHOW_COST adds the cost.
- Turn counts, a turns-left estimate and cache-miss alerts read prompt_cache fields, which no preview has.
Source
#!/usr/bin/env python3
"""
ClaudeStatsBar - a Claude Code status line that shows what a turn costs.
Tells you when a session has grown expensive enough to be worth clearing.
Why: every turn re-reads the whole accumulated context as cache reads. That is
the dominant cost in a long session (measured: 57.8% of weighted spend), and it
grows with the square of session length, because turn N re-reads everything from
turns 1..N-1. Doing the same work in several short sessions instead of one long
one is materially cheaper. This makes that visible while you work.
Install: ./install.sh (see README.md)
Cross-platform: pure stdlib, no shell, no transcript parsing.
"""
import json
import os
import sys
import time
# --- thresholds (tokens of context). Override via env. ---
WARN = int(os.environ.get("CC_CTX_WARN", 60_000))
HIGH = int(os.environ.get("CC_CTX_HIGH", 120_000))
# fraction of the context window at which "running out of room" beats "too costly"
FULL_PCT = float(os.environ.get("CC_CTX_FULL_PCT", 80))
NO_COLOR = bool(os.environ.get("NO_COLOR"))
STATE_DIR = os.path.join(os.path.expanduser("~"), ".claude", ".statusline-state")
R = "\033[0m"
DIM = "\033[2m"
BOLD = "\033[1m"
GREEN = "\033[38;5;78m"
AMBER = "\033[38;5;214m"
RED = "\033[38;5;203m"
BLUE = "\033[38;5;110m"
GREY = "\033[38;5;245m"
def c(s, colour):
return s if NO_COLOR else f"{colour}{s}{R}"
def human(n):
if n is None:
return "?"
if n >= 1_000_000:
return f"{n / 1_000_000:.2f}M"
if n >= 1_000:
return f"{n / 1000:.0f}k" if n >= 10_000 else f"{n / 1000:.1f}k"
return str(n)
def dig(d, *path, default=None):
"""Safe nested lookup; every field here can be absent early in a session."""
for k in path:
if not isinstance(d, dict):
return default
d = d.get(k)
if d is None:
return default
return d
def load_state(session_id):
if not session_id:
return {}
try:
p = os.path.join(STATE_DIR, f"{session_id}.json")
if os.path.exists(p):
with open(p) as f:
return json.load(f)
except Exception:
pass
return {}
def save_state(session_id, state):
if not session_id:
return
try:
os.makedirs(STATE_DIR, exist_ok=True)
with open(os.path.join(STATE_DIR, f"{session_id}.json"), "w") as f:
json.dump(state, f)
# opportunistic cleanup of state files older than a week
if int(time.time()) % 37 == 0:
cutoff = time.time() - 7 * 86400
for fn in os.listdir(STATE_DIR):
fp = os.path.join(STATE_DIR, fn)
if os.path.getmtime(fp) < cutoff:
os.remove(fp)
except Exception:
pass
def baseline(state, ctx):
"""
Smallest context this session has been observed at, so we can show how much
more each turn costs now than at the start. Self-calibrating: no hardcoded
assumption about the size of your CLAUDE.md or tool schemas.
Returns None when the status line joined an already-large session: we never
saw the real start, so any ratio would be a reassuring lie.
"""
lo = state.get("min_ctx")
if lo is None or (ctx and ctx < lo):
lo = ctx
state["min_ctx"] = lo
return None if (lo is None or lo >= WARN) else lo
def rate_burn(state, pct, turns):
"""
Turns of headroom left in the 5-hour window at this session's observed burn
rate. This, not a notional dollar figure, is the real budget on a
subscription. Re-baselines when the rolling window resets (pct drops).
"""
if pct is None or turns is None:
return None
first_pct, first_turns = state.get("rl5_pct"), state.get("rl5_turns")
if first_pct is None or pct < first_pct or first_turns is None:
state["rl5_pct"], state["rl5_turns"] = pct, turns
return None
dt, dp = turns - first_turns, pct - first_pct
if dt < 5 or dp <= 0: # too few samples to extrapolate honestly
return None
return int((100 - pct) / (dp / dt))
def bar(pct, width=12):
pct = max(0.0, min(100.0, pct or 0.0))
filled = int(round(width * pct / 100.0))
return "▕" + "█" * filled + "░" * (width - filled) + "▏"
def resets_in(epoch):
if not epoch:
return None
secs = int(epoch - time.time())
if secs <= 0:
return "now"
if secs > 8 * 86400: # no real limit window is longer; treat as bad data
return None
h, m = secs // 3600, (secs % 3600) // 60
return f"{h}h{m:02d}m" if h else f"{m}m"
def main():
try:
d = json.load(sys.stdin)
except Exception:
print("")
return
ctx = dig(d, "context_window", "total_input_tokens")
window = dig(d, "context_window", "context_window_size", default=200_000)
used_pct = dig(d, "context_window", "used_percentage", default=0)
model = dig(d, "model", "display_name", default="claude")
proj = os.path.basename(
dig(d, "workspace", "current_dir", default=dig(d, "cwd", default="")) or ""
)
cost = dig(d, "cost", "total_cost_usd")
sid = dig(d, "session_id")
turns = dig(d, "prompt_cache", "requests")
state = load_state(sid)
left = [c(model, BLUE)]
if proj:
left.append(c(proj, DIM))
warning = None
if ctx:
tone = GREEN if ctx < WARN else (AMBER if ctx < HIGH else RED)
# weighted cost of one turn's cache re-read (cache reads bill at 0.1x)
per_turn = ctx * 0.1
seg = f"{bar(used_pct)} {human(ctx)}"
left.append(c(seg, tone))
left.append(c(f"{human(int(per_turn))}/turn", tone))
lo = baseline(state, ctx)
if lo and ctx >= lo * 1.5:
left.append(c(f"{ctx / lo:.1f}×", tone))
if used_pct and used_pct >= FULL_PCT:
warning = (
f"context {used_pct:.0f}% full — /compact to keep this thread, "
f"/clear to start fresh"
)
elif ctx >= HIGH:
extra = f" ({ctx / lo:.1f}× its cheapest)" if lo and lo < ctx else ""
warning = (
f"{human(ctx)} context{extra} — every turn now costs "
f"{human(int(per_turn))} before you type. /clear if you have "
f"switched task, /compact to keep going"
)
else:
left.append(c("ctx —", GREY))
if turns:
left.append(c(f"{turns}t", GREY))
# rate limits: on a subscription this IS the budget, not the dollar estimate
for key, label in (("five_hour", "5h"), ("seven_day", "7d")):
pct = dig(d, "rate_limits", key, "used_percentage")
if pct is None:
continue
tone = GREEN if pct < 50 else (AMBER if pct < 80 else RED)
txt = f"{label} {pct:.0f}%"
rst = resets_in(dig(d, "rate_limits", key, "resets_at"))
if rst and pct >= 50:
txt += f"↻{rst}"
if key == "five_hour":
burn = rate_burn(state, pct, turns)
if burn is not None and burn < 200:
txt += f" ~{burn}t left"
if burn < 15:
tone = RED
left.append(c(txt, tone))
# wasted cache rebuilds: real money, invisible otherwise
misses = dig(d, "prompt_cache", "misses")
if misses:
wasted = dig(d, "prompt_cache", "miss_recache_tokens")
cause = dig(d, "prompt_cache", "last_miss_cause", "causes", default=[])
bit = f"⚠ {misses} cache miss"
if wasted:
bit += f" ({human(wasted)} rebuilt)"
if cause:
bit += f" {cause[0]}"
left.append(c(bit, AMBER))
# Off by default: on a Claude subscription this is a client-side estimate at
# API list price, not money anyone pays. The rate-limit percentages above are
# the real budget. Set CC_SHOW_COST=1 when billing through an API key.
if cost and os.environ.get("CC_SHOW_COST"):
left.append(c(f"${cost:.2f}", GREY))
save_state(sid, state)
print(c(" · ", GREY).join(left))
if warning:
print(c(f"⚠ {warning}", RED if ctx >= HIGH else AMBER))
if __name__ == "__main__":
try:
main()
except Exception as e: # a broken status line must never break the session
print(f"statusline error: {type(e).__name__}", file=sys.stderr)
print("")
# ---------------------------------------------------------------------------
# Install: ./install.sh (see README.md for manual setup and Windows)
# Tune: CC_CTX_WARN, CC_CTX_HIGH, CC_CTX_FULL_PCT, CC_SHOW_COST, NO_COLOR
# ---------------------------------------------------------------------------
More status lines
Noctu
3 copies
◆ Opus 4.8 ▏ ctx ▓▓░░░░░░░░ 22% ▏ high ▏ 10m
main ▏ week ▓░░░░░░░░░ 7% ▏ 2d0h ▏ $0.41 ▏ +128/-34Width-Aware Monitor
2 copies
◆ Opus 4.8 │ app/main
▰▰▰▰▱ 78% │ ↑44k ↓1.4k │ 5h: -- │ 7d: -- │ 10m12s
cc-statusbar
1 copy
[Opus 4.8] ctx 22% · 5h ▓▓░░░░░░ 26% on track · resets in 2h06m · 7d ▓░░░░░░░ 7% on track · resets in 2d0h
Heat Dot Line
0 copies
Opus 4.8 · ctx ● 22% · 5h ● 26% · 7d ● 7%Keyblade Status bar
0 copies
✧ ███████████▋ 73% 🗝 Ultima Weapon ✦ app ∙ main
♥ ████████████████████████ 100% 「SAVE POINT」 ✶ Limit Form ◉ 41
Dreambase Panel
223 copies
╭─ CONTEXT ────────────────────────────────────────────╮
│ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD │
├─ STATS ──────────────────────────────────────────────┤
│ ↓ 44.0K ↑ 1.4K │ $0.41 │ ⏱ 10m 12s │ +128/-34 │
├─ REPO ───────────────────────────────────────────────┤
│ ◆ Opus 4.8 · main · 16:21 │
╰──────────────────────────────────────────────────────╯
Related mods
Terminal
❯ Run the tests again
● The repo is clean and the tests pass.
✻ Cooked for 0s · done 10:12 PM
☀ 44k │ 5h 26% │ 7d 7% │ cache 5% [-]
❯ Claude Desktop

One line above the prompt: context tokens with their weather, a bar per recent prompt, your 5-hour and 7-day limits against the time elapsed, the prompt cache with its time left, the session cost with the last prompt's share in dollars and in points of the 5-hour limit, and the subagents running. Labels in English, French or Russian. After Token Weather (Anthropic, Apache-2.0) and the ideas of usage-meter (HolyGrail) and prompt-cache-control (Daniel San).
Terminal
❯ /deck demo
⎿ deck: Sample plan running above the prompt, with sample routing until real ones arrive.
[-]
0: deck ▾ · 5 rows 22:11:41
⇄ Model route opus = opus jev: haiku · not applied effort ▁▃·· medi = kept ✕
✓ Orders module Done · 12/12 · 2/2 agents 100% ✕
● Context 44k / 200k 22% ✕
◔ Session · 5h 5h · 26% used ↻ 2:04:24 ✕
◔ Weekly · 7d 7d · 7% used ↻ 2d 0h 57m ✕
❯
⏵⏵ auto mode on (shift+tab to cycle) · ← for agents [ Deck 1 ]Claude Desktop

A cockpit for Claude Code: plan bars and subagent strips, context, usage limits with reset countdowns and burn rates, spend, the model route and a pet. Six bar styles, three collapsed looks. CLI and Desktop.
Terminal
❯ /flightdeck open
⎿ flightdeck: Flightdeck opened. Focus it with ctrl+x tab; 1-6 expand cards, f/s/o open the gate
rows.
╭────────────────────────────────────────────────────────────────────────────────────────────────✕─╮
│ Opus 4.8 ○ idle · ctx ▰▱▱▱▱▱ 22% · $0.25 │
│ last turn 0s · 0 agents · 0 edits · 0 errors │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
❯
⚠ flightdeck: ctx 22%Claude Desktop

Flightdeck: a live agent dashboard for Claude Code. Main model vitals, an on-call architect, every permission check, subagent cards and swimlanes, a turn receipt and a session log, all from real session events
Terminal
[-]
zZ
▀▄ ▄▀
▄▄▀▀▀▀▀▀▀▀▀▀ ▀▀▀▄▄▄
▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀
ctx 22% [ Pet ♥ ] [ ⚙ ] [ ↻ Adventurer ] [ Hide ]
❯ Claude Desktop

A little pixel cat that wanders above the Claude Code prompt, watches your usage limits and announces when work finishes; or switch packs for an adventurer who mines, chops trees and fights zombies