A statusline tuned for power-user / multi-agent / 1M-context / parallel-worktree workflows. Renders model · repo · context · effort · rate limits · cache · cost · perf · duration into a single scannable line, with a compact fallback for narrow terminals and a CRIT banner when multiple red signals fire at once.
Opus 4.7 · banknet2-retail/main ●3 ↑2 wt:5 2stale #247 · todo +4 ·
ctx 78% ████████░░ 1m · xhigh · 5h 64%→1h12m 7d 71%→98% ·
cache 84% ttl 2:47 · $4.21 $12.4/h +247/-89 $0.017/LOC lpm 49 · 142t/s · api 41% · 47m
Written in Rust, ships as a single ~1.6 MB binary with zero runtime
dependencies beyond what every Mac/Linux machine has by default. ~6 ms per
render on Apple Silicon (~18× faster than the original Node version, ~6.7×
faster than the first Rust cut that still used git subprocesses). Local
git operations use libgit2 directly; only the anthropic status check
involves any non-libgit2 file I/O.
Prereq: Rust must be available. Platform-specific installers below; same behavior on all three.
git clone git@github.com:aabdellah/claude-code-statusline.git
cd claude-code-statusline
./install.sh # or: ./install.sh --with-autobuildInstall Rust first if needed:
brew install rust # macOS
sudo apt install rustc cargo build-essential # Debian/Ubuntu
sudo dnf install rust cargo gcc # Fedora
sudo pacman -S rust base-devel # Arch
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # any *nixgit clone git@github.com:aabdellah/claude-code-statusline.git
cd claude-code-statusline
.\install.ps1 # or: .\install.ps1 -WithAutobuildInstall Rust first if needed:
winget install Rustlang.Rustup- Build the release binary (~30s on first build — libgit2 vendors itself)
- Place the binary at the canonical hooks path on this OS
- Patch your
~/.claude/settings.json(%USERPROFILE%\.claude\settings.jsonon Windows) with the rightstatusLineblock — preserves every other key untouched
Start a new Claude Code turn and the statusline will appear.
Each platform uses its native job-scheduling system, all running the same
underlying cargo watch watcher:
| Platform | --with-autobuild uses |
|---|---|
| macOS | LaunchAgent (~/Library/LaunchAgents/*.plist) |
| Linux | systemd user unit (~/.config/systemd/user/*.service) |
| Windows | Scheduled Task (visible in taskschd.msc) |
Same idempotent path — re-run the installer after pulling new code:
git pull && ./install.sh # macOS / Linux
git pull; .\install.ps1 # Windows./install.sh --uninstall # macOS / Linux
.\install.ps1 -Uninstall # WindowsRemoves the symlink/copy, the statusLine block from settings.json (other keys preserved), and the auto-rebuild job if installed.
git(required for repo state — the binary shells out)curl(optional — used forstatus.claude.combackground fetch)- macOS / Linux:
python3(used by install.sh to patch settings.json safely; bundled with macOS since 12.3, install via your distro's package manager on Linux) - Windows: PowerShell 5.1+ (bundled with Windows 10/11)
- Linux: a C compiler for the vendored libgit2 build (
gcc/build-essential/base-develper distro) - That's it. No Node, no Python, no shared libraries beyond
libSystem(macOS) /glibc(Linux).
| Env var | Effect |
|---|---|
STATUSLINE_DEBUG_TIMING=1 |
Print per-segment ms to stderr |
STATUSLINE_SHOW_PLUGINS=1 |
Show learning+explanatory plugin styles |
STATUSLINE_NO_BLINK=1 |
Disable boss-fight blink at ≥90% context |
STATUSLINE_HIDE=mileage,perf,duration |
Suppress specific segments |
STATUSLINE_MODE=auto|full|compact |
auto (default) = adaptive layout; full = every segment at full text; compact = every segment at smallest variant |
STATUSLINE_WIDTH=N |
Force terminal width (for testing) |
STATUSLINE_WIDTH_MARGIN=N |
Cells subtracted from detected width before fitting (default 4 — Claude Code draws 2 cells of frame on each side of the pane). Set to 0 if using a host without margins. |
STATUSLINE_DEBUG_WIDTH=1 |
Persist width-detection trace to /tmp |
claude-code-statusline/
├── Cargo.toml # crate manifest + release profile (opt-z, LTO, strip)
├── src/
│ ├── main.rs # Entry — read stdin → parse → render → write stdout
│ ├── ansi.rs # color codes, gradient, gradient_bar, visible_length
│ ├── config.rs # env-var Config + per-render timing instrumentation
│ ├── input.rs # typed serde structs for the CC JSON input
│ ├── format.rs # null-safe value formatters (full + compact)
│ ├── git.rs # git status, todo Δ, worktree stats (libgit2)
│ ├── transcript.rs # JSONL tail reader + derived metrics
│ ├── anthropic.rs # status.claude.com check (cached + bg refresh)
│ ├── pace.rs # 7-day rate-limit pace projection
│ ├── width.rs # terminal-width detection (8 layered fallbacks)
│ ├── layout.rs # priority-tiered adaptive fit (full/compact/micro/drop)
│ └── render.rs # segment assembly
└── docs/
└── ROADMAP.md # shipped segments + backlog + rejected ideas
Every segment declares a priority and 1-3 variants (full → compact → micro). At render time, the fitter starts from FULL and downgrades the lowest-priority segments until the line fits the detected terminal width.
| Tier | Segments | Behavior |
|---|---|---|
| Critical | model, context, CRIT banner |
Never drops; can only downgrade variants |
| Important | repo/branch, cost, capabilities, anthropic-status |
Drops last |
| Normal | rate-limits, cache, duration |
Drops in tight layouts |
| Optional | yak, todo, pr, wt, perf, output-style, cwd-drift, destruction |
Drops first |
Example degradation for a session in a git repo with cost data:
width=220 → Opus 4.7 · claude-code-statusline/main ○3+1 · ctx 78% ████████░░ 1m · medium · 5h 64% 7d 71% · cache 84% · $4.21 $5.37/h +247/-89 $0.017/LOC · 47m
width=130 → Opus 4.7 · claude-code-statusline/main ○3+1 · ctx 78% ████████░░ 1m · medium · cache 84% · $4.21 $5.37/h +247/-89 $0.017/LOC · 47m
width=100 → Opus 4.7 · claude-code-statusline/main ○3+1 · ctx 78% ████████░░ 1m · medium · $4.2 $5.4/h +247/-89
width=80 → Opus 4.7 · claude/main ○3+1 · ctx 78% ████████░░ 1m · medium · $4.21
width=60 → Opus 4.7 · main ○3+1 · 78%/1m · medium · $4.21
width=40 → Opus 4.7 · 78%/1m · $4.21
- Every segment is gated on data presence — formatters return
Option<String>and aNonesimply omits that segment. - Git is invoked via
std::process::Command(no shell, no injection surface). - 24-bit truecolor used for the context bar; falls back to readable text on terminals that don't support it.
Configreads every env var once at startup; nothing else touches env.- Four runtime deps total:
serde+serde_json(JSON),regex(TODO/dest patterns),libc(ioctl(TIOCGWINSZ) for ancestor-PTY width),git2(libgit2 bindings, statically linked via vendored-libgit2 feature so the resulting binary doesn't depend on a system libgit2).
$ STATUSLINE_DEBUG_TIMING=1 ./target/release/statusline < input.json
[statusline:timing] total=6.2ms 200cols mode=auto→full len=86
2.14ms todo-delta (libgit2 diff)
1.91ms git-status (libgit2 status + ahead/behind + stash)
1.42ms anthropic-status (stat /tmp/cc-anthropic-status.json)
0.58ms destruction (transcript scan for rm/drop/--hard)
0.12ms worktree-stats (libgit2 worktree enumeration)
0.01ms gitdir-discover (walk up looking for .git)
0.00ms width-detect, transcript-read, ftl, yak-depth
Three optimization layers got us here from the original ~40-105ms:
| Version | In-repo | No-repo | Notes |
|---|---|---|---|
| Node.js (original) | ~105ms | ~80ms | Node startup dominated |
| Rust (subprocess git, sequential) | ~40ms | ~25ms | Fork/exec to git 3-4× per render |
| Rust (subprocess git, parallel) | ~20ms | ~1.5ms | std::thread::scope for git calls |
| Rust (libgit2 + fs fast-path) | ~6ms | ~1.5ms | Current — no subprocess at all |
The fast path: when .git/ isn't found, all git work is skipped entirely.
When found, libgit2 is called directly (no fork/exec) and the branch name
comes from a 40-byte read of .git/HEAD rather than git symbolic-ref.
cargo test25 unit tests covering ANSI escapes, RFC3339 parsing, formatters, model-name extraction, yak depth, destruction counter, pace color thresholds.
See docs/ROADMAP.md for what's shipped, what's planned
next, the backlog of ideas, and what we've explicitly decided not to build.