statuslin.es

Model, context fill and both subscription windows as pace-coloured bars: the colour is the projected end-of-window usage, not the current percentage, so a nearly full window about to reset stays green.

Updated 2026-08-27

0 copies

Preview

Clean repo
[Opus 4.8] ctx 22% · 5h ▓▓░░░░░░ 26% on track · resets in 2h06m · 7d ▓░░░░░░░ 7% on track · resets in 2d0h
New session
[Opus 4.8] · 5h/7d limits appear after Claude's first reply
Dirty branch
[Sonnet 4.6] ctx 48% · 5h ▓▓▓░░░░░ 40% on track · resets in 1h11m · 7d ▓░░░░░░░ 18% on track · resets in 2d21h
Near-full
[Opus 4.8] ctx 91% · 5h ▓▓▓▓▓▓▓░ 88% running tight · resets in 17m · 7d ▓▓▓▓▓░░░ 61% on track · resets in 19h59m
1M context
[Fable 5] ctx 64% · 5h ▓▓▓░░░░░ 33% over pace · resets in 3h29m · 7d ▓▓▓▓▓▓░░ 80% over pace · resets in 4d23h
Post-compact
[Haiku 4.5] · 5h ▓▓▓▓░░░░ 52% over pace · resets in 4h01m
Worktree
[Opus 4.8] ctx 37% · 5h ▓▓▓▓░░░░ 44% running tight · resets in 2h49m · 7d ▓▓░░░░░░ 20% on track · resets in 1d6h
Non-git
[Opus 4.8] ctx 22% · 5h/7d limits appear after Claude's first reply

Source

#!/usr/bin/env python3
"""Claude Code statusLine script + local quota bridge for the cc-statusbar extension.

Claude Code hands its statusLine hook a JSON payload on stdin after every
assistant message. That payload carries the REAL 5h/7d subscription limits
(`rate_limits`), read from the headers of Claude Code's own ongoing traffic —
no extra request, no extra token. The VS Code extension cannot see that stdin,
so this script mirrors those limits to a tiny local file that the extension
reads with zero network:

    ~/.claude/.cc-statusbar-quota.json

This is source 2 of the extension's four quota sources (see README →
"How it gets data"). It is entirely optional: without it the extension still
reads quota over the network. It matters on links too weak for a network call
to complete, because it costs no request at all.

Terminal sessions only. The VS Code / Cursor integration runs Claude Code
*without* a status line, so an IDE-only session never triggers this script.

Setup — add to ~/.claude/settings.json (adjust the path to where you put it):

    {
      "statusLine": {
        "type": "command",
        "command": "python ~/.claude/statusline.py"
      }
    }

Already have your own statusLine script? Keep it, and copy just
`dump_quota_bridge()` plus its call at the end of `main()` into it — the bridge
is independent of whatever your script prints.

Requires Python 3.8+. No third-party packages. Windows, macOS and Linux.
"""

from __future__ import annotations

import json
import sys
import time
from pathlib import Path

# Where the extension looks for the mirrored limits. Must match
# src/localQuota.ts → bridgePath(). Do not rename without changing both.
QUOTA_BRIDGE_PATH = Path.home() / ".claude" / ".cc-statusbar-quota.json"

# Windows consoles default to a legacy code page, so the block glyphs below
# would raise UnicodeEncodeError and the status line would show nothing.
if sys.platform == "win32":
    try:
        sys.stdout.reconfigure(encoding="utf-8", errors="replace")
    except Exception:
        pass

GREEN = "\033[32m"
YELLOW = "\033[33m"
RED = "\033[31m"
DIM = "\033[2m"
RESET = "\033[0m"


def fmt_remaining(seconds: float) -> str:
    """Countdown to a window reset: '38m' / '2h41m' / '4d3h'."""
    secs = int(seconds)
    if secs <= 0:
        return "—"
    days, rem = divmod(secs, 86_400)
    hours, rem = divmod(rem, 3_600)
    mins = rem // 60
    if days:
        return f"{days}d{hours}h"
    if hours:
        return f"{hours}h{mins:02d}m"
    return f"{mins}m"


def quota_segment(label: str, window: object, now: float, window_seconds: int) -> "str | None":
    """One quota window as a pace-coloured bar: '5h ▓▓▓░░░░░ 34% on track · resets in 2h41m'.

    The colour is the projected end-of-window usage, not the current percentage:
    a nearly full window that is about to reset is normal, not an alarm.
        green  — projected under 90%
        yellow — projected 90-102% (lands right on the limit)
        red    — projected over 102% (on pace to run out before the reset)
    Skipped during the first 3% of a window: too little data to extrapolate.
    Returns None when the window is missing from the payload.
    """
    if not isinstance(window, dict):
        return None
    pct = window.get("used_percentage")
    if not isinstance(pct, (int, float)):
        return None
    pct = float(pct)

    color, status, reset_txt = GREEN, "on track", ""
    resets_at = window.get("resets_at")
    if isinstance(resets_at, (int, float)):
        remaining = resets_at - now
        reset_txt = f" · resets in {fmt_remaining(remaining)}"
        elapsed = (window_seconds - remaining) / window_seconds
        if 0.03 < elapsed <= 1 and remaining > 0:
            projected = pct / elapsed
            if projected > 102:
                color, status = RED, "over pace"
            elif projected >= 90:
                color, status = YELLOW, "running tight"

    width = 8
    filled = max(0, min(width, round(pct / 100 * width)))
    bar = "▓" * filled + "░" * (width - filled)
    return f"{label} {color}{bar}{RESET} {pct:.0f}% {status}{reset_txt}"


def build_status_line(data: dict, now: "float | None" = None) -> str:
    """Compact one-line status: model, context fill, then both quota windows."""
    if now is None:
        now = time.time()

    model = (data.get("model") or {}).get("display_name") or "model?"
    ctx = data.get("context_window") or {}
    limits = data.get("rate_limits") or {}

    ctx_pct = ctx.get("used_percentage")
    head = f"[{model}]"
    if isinstance(ctx_pct, (int, float)):
        head += f" ctx {float(ctx_pct):.0f}%"

    segments = [
        quota_segment("5h", limits.get("five_hour"), now, 5 * 3600),
        quota_segment("7d", limits.get("seven_day"), now, 7 * 86_400),
    ]
    segments = [s for s in segments if s]
    if not segments:
        return f"{head} · {DIM}5h/7d limits appear after Claude's first reply{RESET}"
    return head + " · " + " · ".join(segments)


def render(raw: str) -> str:
    """Raw stdin to the line we print. All error handling lives here."""
    if not raw.strip():
        return "[statusline] no input"
    try:
        data = json.loads(raw)
    except Exception:
        return "[statusline] invalid json"
    if not isinstance(data, dict):
        return "[statusline] unexpected payload"
    try:
        return build_status_line(data)
    except Exception:
        return "[statusline] render error"


def dump_quota_bridge(raw: str, now: "float | None" = None) -> None:
    """Mirror the stdin `rate_limits` to QUOTA_BRIDGE_PATH for the extension.

    Best-effort and isolated: every failure is swallowed, and this runs only
    after the status line is already on stdout, so the bridge can never break
    what you see. Writes only when at least one window carries a real
    `used_percentage`, so an early render before the first API response never
    overwrites a good last-known reading. The write is atomic (temp file plus
    rename), so the extension never reads a half-written file.
    """
    try:
        data = json.loads(raw)
        if not isinstance(data, dict):
            return
        limits = data.get("rate_limits") or {}
        windows = [limits.get("five_hour"), limits.get("seven_day")]
        if not any(isinstance(w, dict) and w.get("used_percentage") is not None for w in windows):
            return
        payload = {
            "writtenAtSec": int(now if now is not None else time.time()),
            "rate_limits": limits,
        }
        QUOTA_BRIDGE_PATH.parent.mkdir(parents=True, exist_ok=True)
        tmp = QUOTA_BRIDGE_PATH.with_name(QUOTA_BRIDGE_PATH.name + ".tmp")
        tmp.write_text(json.dumps(payload), encoding="utf-8")
        tmp.replace(QUOTA_BRIDGE_PATH)
    except Exception:
        pass


def main() -> None:
    try:
        raw = sys.stdin.read()
    except Exception:
        raw = ""

    try:
        sys.stdout.write(render(raw) + "\n")
    except Exception:
        # Last resort: never leave the status line blank on an encoding error.
        print("[statusline] render error")

    dump_quota_bridge(raw)


if __name__ == "__main__":
    main()

What it shows

  • Model display name in square brackets
  • Context window fill as a percentage, when stdin includes it
  • 5-hour quota bar with used percent, a pace label, and time until reset
  • 7-day quota in the same layout, omitted when that window is missing
  • Pace as on track, running tight, or over pace, colored from projected end-of-window usage rather than the current percent

Requirements

  • Python 3.8 or newer, standard library only
  • No network. Quota numbers come from Claude Code stdin.
  • Writes ~/.claude/.cc-statusbar-quota.json after the line prints, with the same rate_limits object. Remove the dump_quota_bridge(raw) call if you do not want that file.
  • Bar fill uses ▓ and ░, not a Nerd Font

Behavior notes

  • Every preview is a single line.
  • A brand-new session hides ctx and both bars, and prints a dim note that 5h/7d limits appear after Claude's first reply.
  • The non-git scratch dir still shows ctx 22%, then that same dim note, because those fixtures omit rate_limits.
  • Post-compact Haiku drops ctx and the 7-day window. Only a red 5h bar remains.
  • Near-full marks 5h as running tight (yellow) at 88% with 17m left. 7d stays on track at 61%.
  • The 1M-context Fable preview is over pace on both windows (red), even though 5h is only 33% used, because the projection overshoots 102%.
  • Worktree and dirty-feature keep the same segments and only change the numbers.

More status lines

Dreambase Panel

155 copies

╭─ CONTEXT ────────────────────────────────────────────╮ ████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD ├─ STATS ──────────────────────────────────────────────┤ 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 ├─ REPO ───────────────────────────────────────────────┤ Opus 4.8 · main · 16:21 ╰──────────────────────────────────────────────────────╯
app · main · Opus-4.8 · effort high · ctx 22% · 5h 26% ↻2h6m · 7d 7% ↻2d0h
Opus 4.8 [high] ~/app main 16:20:55 | ⛁ ██░░░░░░░░ 22% | 5h ●◔○○○ 26% ↻2h7m | 7d ◔○○○○○○ 7% ↻2d1h │ $0.41 ⏱ 10m
Opus 4.8 main app 44k/200k $0.41

Activity Feed

39 copies

Opus 4.8 high ctx 22% app (main) PR #1287 $0.41 · 10m +128 -34 Session 74% left Resets in 2h 6m Weekly 93% left Resets in 2d 0h Fable 85% left Resets in 2d 0h ◐ Read:index.ts ✓ Read×1 ✓ Bash×1 ✓ Edit×1 ✓ Grep×1 ◐ code-reviewer Review the change for regressions
████▊░░░░░░░░░░░░░░░░░ 22% of 200K · GOOD 44.0K 1.4K $0.41 ⏱ 10m 12s +128/-34 Opus 4.8 · main · 16:22