Terminal status bars and account tooling for AI coding CLIs —
cost, context, rate limits, burn-down, and multi-account routing, at a glance.
Install · What You See · Formats · Configure · Accounts · Token Scanning · macOS Native · How It Works
Four tools, one repo, shared data files:
- Claude Code statusline (
bin/statusline.sh) — the multi-line dashboard below - Codex statusline (
bin/codex-statusline,codex-top) — the same idea for the Codex CLI accounts(bin/accounts.py) — native-profile account routing and headroom board- Token scanning & redaction (
bin/scan-tokens*) — attribute every token, redact before sharing
model Fable 5.ultracode
time ⏱ 2:29:20
account you@example.com
repo my-project feature/fix-the-thing (v1.2.0*)
pr #N Fix The Thing The Session Is Working On
context ●●●●●●●○○○○○○○○ 49%
session ●●●●●●●●●○○○○○○ 60.2% resets 10:00pm PDT
weekly ●●●●○○○○○○○○○○○ 31.07% resets jul 27, 12:00pm PDT
fable ●●●●○○○○○○○○○○○ 33%
usage today 5.57M · session 1.16M · lifetime 593.31M
acct 5h reset week fable reset
· Work 84% 2h15m 51% 80% 2d
· Work-Max 25% 2h25m 68% 100% 2d
* Uni 60% 3h45m 31% 33% 6d
· Mail 0% — 100% 87% 2d
· Side 0% — 100% 16% 2d
· Personal 0% — 100% 8% 23h
Everything you need to not get rate-limited, blow your budget, or lose context mid-task. The Claude statusline is one bash script, zero dependencies beyond jq.
curl -fsSL https://raw.githubusercontent.com/AndrewTKent/statusline/main/install.sh | bashOr via npm:
npx @andrewkent/claude-statusline installOr manually — copy the script, add one key to settings:
cp bin/statusline.sh ~/.claude/statusline.sh && chmod +x ~/.claude/statusline.sh{ "statusLine": { "type": "command", "command": "~/.claude/statusline.sh", "padding": 0 } }Restart Claude Code. Done.
Requires: jq · Claude Code (logged in) · Optional: gh for PR badges
Requires: Codex CLI · Python 3 · tmux · ~/.local/bin on PATH · Optional: gh for PR linkage
./install-codex.sh
codex-statusline
codex-statusline --sandbox read-only --ask-for-approval on-requestcodex-statusline launches Codex in tmux with a fixed bottom pane matching the
multi-line Claude Code status view. It shows the current model, elapsed time,
account, repository, linked pull request, context use, 5-hour and weekly limits,
remaining purchased credits, tokens, agents, and running tools. It binds each
footer to the rollout file opened by its owning Codex process, so concurrent and
resumed sessions do not exchange context values. Mouse
wheel scrollback is enabled with a 100,000-line history; tune it with
CODEX_STATUSLINE_HISTORY_LIMIT. Mouse-dragging output selects it in tmux and
copies it to the system clipboard on release in OSC 52-capable terminals
(iTerm2, kitty, WezTerm — not stock macOS Terminal.app). When launched inside an existing
tmux pane, that pane keeps the history depth it was created with; the session
mouse and window history-limit options are restored when the launcher exits.
When launched outside tmux, detaching (prefix d) leaves Codex running — reattach with
tmux attach -t codex-statusline-<pid>; the session ends when Codex exits.
The footer refreshes every 3s (CODEX_STATUSLINE_INTERVAL) and backs off to a
30s poll once its session has been idle for 10 minutes, exits when the owning
process is gone, and opportunistically truncates the state DB's WAL when it
grows past 128 MB — long-lived footers previously starved SQLite checkpoints
until every Codex query slowed to a crawl.
The launcher defaults to Codex YOLO mode by passing
--dangerously-bypass-approvals-and-sandbox. An explicit -a/--ask-for-approval,
-s/--sandbox, or dangerous-bypass flag replaces that default; profile (-p) or
-c approval overrides do not. Set
CODEX_STATUSLINE_MANAGE_APPROVALS=0 to pass no permission default. The launcher also uses
tui.status_line=[] so Codex keeps only its compact built-in prompt footer while
the detailed dashboard stays in the fixed pane.
Settings load from ${CODEX_HOME:-~/.codex}/statusline.conf; non-empty environment
variables override file values, and CODEX_STATUSLINE_CONFIG points at a
different file.
codex-top is the live fleet view for parent and subagent sessions. Both views
read the newest ~/.codex/state_N.sqlite and rollout JSONL files locally; neither
calls an API. Use codex-watch --details for expanded session details or
codex-statusline --json for a machine-readable snapshot (renderer-only first flags
dispatch to the renderer; anything else launches Codex). codex-top monitors existing sessions.
default renders one labeled row per fact — the block at the top of this README. Every row below repo is conditional on data actually being available:
| Row | Shown when | What it shows |
|---|---|---|
model |
always | Model + effort suffix (.low/.medium/.high/.xhigh/.max/.ultracode) + ⚡fast when Settings' fast mode is on |
time |
session duration available | Wall-clock (⏱ 24:12); adds idle Nm after 30s with no user turn |
account |
account resolved | Tag from ACCOUNT_LABELS, colored per LABEL_COLORS |
repo |
always | Dir name, worktree/primary tag, branch (dirty *, ↑/↓ ahead/behind), PR badge |
pr |
the checkout maps to an open PR | PR number and title for the checked-out branch or detached PR head |
context |
always | Context-window fill — 15-dot sweet-spot bar (blue <30%, green 30–70%, yellow 70–85%, red 85%+) |
session |
5h rate-limit data available | 5h window used, 15-dot bar + resets <time> |
weekly |
7-day rate-limit data available | 7-day window used, 15-dot bar + resets <date> |
fable |
account has a per-model weekly cap | That cap's usage, 15-dot bar (label = the scoped model; opt-out SHOW_FABLE_ROW=0) |
budget |
DAILY_BUDGET set |
Spend vs. cap, 10-dot bar |
tokens |
scan data available | All-time work/personal token ratio, 10-dot bar (opt-out SHOW_TOKENS_ROW=0) |
| goal row | CHALLENGE_GOAL_M set (see script header comment) |
Progress toward a token goal, labeled CHALLENGE_LABEL (opt-out SHOW_CHALLENGE_ROW=0) |
bounty |
bounty config set and uncleared | ETA to a work-token floor (opt-out SHOW_BOUNTY_ROW=0) |
usage |
scan data available | Today / this session / lifetime totals, human-formatted |
stack |
SHOW_BACKENDS_ROW=1 |
Live snapshot across Claude/Codex/remote agents (bin/live-state.py) |
| per-account rows | SHOW_ACCOUNT_RESETS=1 |
One row per tracked account: 5h%, reset, week%, fable%, reset, work-unit cap |
PR badge states: [draft], [PR✗] checks failing, [PR△] changes requested, [PR✓] approved, [PR⋯] checks pending, [PR] open with no strong signal either way.
tokens and usage are both fed by bin/scan-tokens.py's background scan of every session JSONL, cached to ~/.claude/token-scan-summary.json (small, preferred) or ~/.claude/token-scan-cache.json (full, fallback) — rescanned in the background whenever that cache is older than 180s.
tokens— all-time work/personal ratio (cyan = work, magenta = personal), classified per-request by theWORK_PATHS/WORK_KEYWORDSvsPERSONAL_PATHS/PERSONAL_KEYWORDSrules instatusline.confusage— today / this session / lifetime, human-formatted (k/M/B)- Subagent (Agent tool) tokens are scanned separately (30s cache) and only break out in the optional token-goal row
All cost and token ledgers are tagged with your account label (e.g., work or personal), derived from your OAuth email via ACCOUNT_LABELS. This lets you aggregate spend by account after the fact. Two related but distinct dimensions live inside the token scanner itself: EMAIL_PAYER_MAP (which plan paid) and the work/personal path/keyword classifier (what the work was) — see Configure.
The script sets the terminal tab title (via ANSI escape) to repo-name on main/master, or repo-name (branch) on feature branches. Useful in Zed, iTerm2, and other terminals to tell sessions apart at a glance.
macOS Notification Center alerts fire automatically (once per threshold, deduped):
- Rate limit at 80%, 90%, 95%
- Context at 80%, 95%
- Budget at 90%, 100%
When you /login inside a routed profile, the status bar detects the credential change before writing its ledgers, refreshes the profile, and updates the rate limits and account label on the next render. Sessions using that same native profile see the refreshed login.
Seven render modes. Set FORMAT= in ~/.claude/statusline.conf or STATUSLINE_FORMAT= env var.
The full cockpit, one labeled row per fact. Auto-falls-through to narrow when the detected terminal width is below NARROW_THRESHOLD (default 60 cols).
Just the context and session rows — the two numbers that actually gate you.
Same facts as default (model+effort, dir+branch, context, 5h, 7d+cost), trimmed hard: short labels, 5–8 char bars scaled to COLS, no reset timestamps or breakdowns. Auto-selected under default when the panel is narrow; can also be set explicitly.
◈ Opus 4.6 · $2.14 ($8.90/d) · ●●●○○ 60% · ⎇ feature-123✦↑1[PR✓] · 42%⏱24:12 · 71%w
Width-adaptive: full detail (cost, daily aggregate, context, git, 5h rate, weekly) at ≥120 cols; drops the daily aggregate and weekly at ≥80; drops git detail to a bare branch name and rate to a bare percentage below 80. Good for tmux status bars or small terminals.
...default output...
trend cost▁▂▃▅▃▂▁▄▆█ rate▁▃▅▇█▇▅▃▂▁
Appends inline ▁▂▃▄▅▆▇█ mini-charts (cost and 5h-rate trend, last 15 sessions) read from ~/.claude/session-history.jsonl. See if you're burning hotter today than yesterday.
Writes zsh-formatted status to ~/.claude/rprompt.txt. Add to .zshrc:
_claude_rprompt() {
local f=~/.claude/rprompt.txt
[[ -f "$f" ]] || return
local age=$(( $(date +%s) - $(stat -f %m "$f") ))
(( age > 300 )) && { RPROMPT=""; return }
RPROMPT="$(cat "$f")"
}
autoload -Uz add-zsh-hook
add-zsh-hook precmd _claude_rpromptClaude metrics in your shell prompt gutter. Zero vertical space. Auto-hides after 5 minutes of inactivity. Also emits sigil to stdout for Claude Code's own status area.
Pushes structured data to iTerm2 via OSC 1337;SetUserVar or sets the Kitty window title via OSC 2. Auto-detects your terminal; also emits sigil to stdout as a fallback.
iTerm2 setup: Preferences → Profiles → Session → Status Bar → add "Interpolated String" components:
\(user.claude_model) · \(user.claude_cost) · \(user.claude_ctx) · \(user.claude_git) · \(user.claude_rate) · \(user.claude_timer)
Create ~/.claude/statusline.conf (bash, sourced directly). Full annotated version with every knob: config/statusline.conf.example. All settings are optional — the script works with no config file at all.
Cost & format
DAILY_BUDGET=20— daily cost ceiling; enables thebudgetrow + 90%/100% notificationsFORMAT=default—default | compact | narrow | sigil | sparkline | rprompt | iterm2
Branch display
BRANCH_PREFIX_STRIP="andrew/"— strip a literal prefix off the displayed branch nameMAX_BRANCH=24— max visible branch chars before an ellipsis
Account labels
ACCOUNT_LABELS="work:*@company.com personal:me@gmail.com"— email pattern → short tag, first match winsLABEL_COLORS="work:cyan personal:magenta"— tag → color for theaccountrow (unmapped tags default to orange)EMAIL_PAYER_MAP="work:you@company.com personal:me@gmail.com"— which plan paid, for the token scanner'spayerdimension (independent of the work/personal classifier below)SHOW_ACCOUNT_RESETS=1— adds a per-account board (5h%, reset, week%, fable%, reset, work-unit cap) below the main rows
Token classifier (feeds the tokens row's work/personal split — see bin/scan-tokens.py)
WORK_PATHS/PERSONAL_PATHS— comma-separated cwd/file-path substringsWORK_KEYWORDS/PERSONAL_KEYWORDS— comma-separated prompt keywords (weighted 3× a path hit)
Bounty / challenge tracker (opt-in token-goal ETA)
CHALLENGE_START,BOUNTY_TARGET_TOKENS,BOUNTY_LOOKBACK_DAYS,BOUNTY_SESSION_GAP_MIN
Row visibility (each defaults on when its data exists; 0 hides it)
SHOW_FABLE_ROW,SHOW_TOKENS_ROW,SHOW_CHALLENGE_ROW,SHOW_BOUNTY_ROW
Live state stack row (opt-in)
SHOW_BACKENDS_ROW=1— adds astackrow frombin/live-state.py: a snapshot across Claude (account-resets.json), Codex (neweststate_N.sqlite), and remote autobuild agents ($AGENT_SESSIONS_PATH)
accounts (bin/accounts.py) is a per-session router and headroom board. Each
account gets a native Claude config under ~/.accounts/profiles/<label>.
Credentials and entitlement caches are isolated; projects, transcripts, settings,
skills, and plugins are shared. Interactive sessions remain first-party
claude.ai subscription sessions instead of API/setup-token sessions.
Install the router from a local checkout:
./install-account-router.shThe installer leaves Claude's native binary at ~/.local/bin/claude, installs
the router tools under ~/.local/bin, and prepends a supervised launcher from
~/.accounts/bin in new zsh sessions.
| Command | What it does |
|---|---|
accounts set <label> |
Force every supervised session onto <label> |
accounts auto |
Route supervised sessions to the freshest account |
accounts fable |
Switch live supervised sessions to Fable while headroom is available |
accounts status |
Mode + per-account 5h/7d/Fable headroom + ⚠login flags |
accounts poll |
Refresh dormant stored/native profiles, then poll every routable account |
accounts refresh [label] |
Refresh stale file-backed credentials without a browser |
accounts mint <label> |
Mint + vault a 1-year token for headless jobs |
accounts tokens |
List minted tokens and expiry |
accounts sync |
Converge the token vault with a second machine |
accounts pick-env |
Emit CLAUDE_CONFIG_DIR and account metadata |
Inside Claude Code, prefix these with ! (for example,
!accounts set acme-max). Set "respondToBashCommands": false in
~/.claude/settings.json so the switch does not trigger an LLM response.
claude-router.py supervises interactive sessions. It reserves the selected
account, watches the active model's quota windows, and resumes the exact session
under another isolated profile before a window is exhausted. The shell never
regains control during a handoff. Changing to Fable mode also moves running
supervised sessions to Fable in place — except a session you explicitly put on
another model (a --model launch flag or a live /model switch), which stays
there until you switch back to /model fable or re-run accounts fable. If
every Fable-capable account is gated, the same session resumes on Opus using the
safest general-model account.
Minted long-lived tokens remain outside ~/.claude
(~/.accounts/vault.json); archival copies only session JSONLs from
~/.claude/projects.
Two independent tools, both built on the same session JSONLs.
Token scanning (bin/scan_tokens_core.py + the bin/scan-tokens*.py/.sh CLIs) attributes every request to work/personal and to a payer, incrementally, and feeds the tokens/usage/goal/bounty rows above plus the work-unit cap columns on the account board. bin/derive-cap.py fits those per-account caps from utilization history — it's a manual, unscheduled tool you re-run occasionally, not something cron or launchd calls. Full design, cache schema, and failure modes: bin/ARCHITECTURE.md.
Durable ledger & archival (bin/usage-ledger.py, bin/archive-transcripts.sh, bin/vault-snapshot.sh) keep a permanent per-day/per-model token ledger at ~/.claude/usage-ledger.json and mirror Claude Code session JSONLs nightly — rows never pruned, survives transcript cleanup.
Three companion apps that read the same data files — no extra API calls.
Color-coded icon: green = ok, yellow = rate limit 70%+, red = 90%+ or context critical.
Click for a SwiftUI popover with full dashboard.
cd macos/ClaudeMenuBar
./build.sh # Compiles with swiftc — no Xcode needed
./install.sh # Copies to ~/Applications, auto-starts at loginSearch "Claude Status" for a full metric list, or pin to menu bar for always-visible $12.34 | 5hr: 45%.
macos/claude-raycast/ # TypeScript — ready when Raycast is installed
Consolidates all status data into ~/.claude/widget-snapshot.json with a 24-hour cost sparkline. Foundation for WidgetKit desktop/lock screen widgets.
swift macos/claude-widget/Bridge/claude-widget-bridge.swiftRun on a 30s launchd timer for auto-refresh. See macos/claude-widget/README.md for setup.
Claude Code pipes a JSON status blob into the script via stdin on every tool call. The script:
- Parses model, cost, context, session metadata (single
jqcall) - Detects credential changes and validates changed profile identity before any account-tagged ledger write
- Resolves the account label from the OAuth profile cache and updates the daily cost/token ledgers in
~/.claude/ - Scans subagent JSONL files for the current session (cached 30s) and reads
token-scan-summary.json(fallback:token-scan-cache.json) for the work/personal token split — kicks off a backgroundscan-tokens.pyrescan when that cache is stale (>180s) - Builds the git/PR segment (branch, dirty, ahead/behind,
gh pr viewcached 90s) and the effort/fast-mode/focus badges - Refreshes rate limits and profile from Anthropic's OAuth API in the background (usage cached 60s, profile cached 5min)
- Interpolates usage between polls — tracks velocity across consecutive API responses for smooth fractional percentages
- Builds the budget row (if
DAILY_BUDGETis set) and the optional multi-account reset board (ifSHOW_ACCOUNT_RESETS=1) - Sets terminal tab title to repo + branch
- Checks notification thresholds (fires once per crossing, deduped)
- Renders in your chosen format, falling back to
narrowunderNARROW_THRESHOLDcolumns
Claude Code statusline.sh
│ │
├─ stdin JSON ────────────────►│ parse (jq)
│ │
│ ├─► changed credential: fetch profile (≤2s)
│ ├─► resolve account label (profile cache)
│ ├─► update daily-cost.json (tagged w/ account)
│ ├─► update daily-tokens.json (tagged w/ account)
│ ├─► scan subagent JSONL files (cached 30s)
│ ├─► read token-scan-summary.json (fallback: token-scan-cache.json)
│ ├─► background: fetch /api/oauth/usage (cached 60s)
│ ├─► background: refresh /api/oauth/profile (cached 5min)
│ ├─► check notification thresholds
│ ├─► set terminal tab title (\033]0;repo (branch)\007)
│ │
│ stdout ANSI ◄──────────────├─► render (default|compact|narrow|sigil|sparkline|rprompt|iterm2)
│ │
├─ /tmp/claude/*.json ────────►│ macOS apps read these
| Concern | How it's handled |
|---|---|
| Network latency | Background refreshes; a changed credential can block up to 2s for identity validation |
| Concurrent sessions | Lock file with stale-PID detection (auto-cleanup at 30s) |
| Git dirty check | git diff-index --quiet HEAD (faster than git status) |
| PR status | Repository-scoped gh lookup cached 90s, background-refreshed |
| Ledger writes | Atomic (mktemp + mv) |
| Account switch | OAuth token hash + credential mtime tracking, synchronous identity validation before ledger writes |
| Subagent scan | File-based cache with 30s TTL, scoped to current session |
| Token bar | jq read from token-scan-summary.json (fallback: token-scan-cache.json); the actual JSONL rescan runs in the background via scan-tokens.py, never inline |
| File | Purpose | Lifetime |
|---|---|---|
~/.claude/statusline.sh |
The script (or symlink) | Permanent |
~/.claude/statusline.conf |
Config | Permanent |
~/.claude/daily-cost.json |
Daily cost ledger (account-tagged) | Resets daily |
~/.claude/daily-tokens.json |
Daily token tracker (account-tagged) | Resets daily |
~/.claude/token-scan-summary.json |
Small token-scan summary (preferred read) | Persistent |
~/.claude/token-scan-cache.json |
Full token-scan cache (fallback read) | Persistent |
~/.claude/account-resets.json |
Multi-account reset ledger (SHOW_ACCOUNT_RESETS) |
Persistent |
~/.claude/account-caps.json |
Per-account work-unit caps, written by bin/derive-cap.py |
Persistent |
~/.claude/utilization-history.jsonl |
Raw utilization samples backing the account board | Rolling |
~/.claude/session-history.jsonl |
Sparkline history (account + subagent fields) | Rolling 100 entries |
~/.claude/rprompt.txt |
Zsh RPROMPT (rprompt format) |
Updated each render |
~/.claude/usage-ledger.json |
Durable per-day/per-model token ledger (bin/usage-ledger.py) |
Permanent |
~/.claude/statusline-tz |
Optional timezone override for reset-time display | Permanent |
~/.claude/.credentials.json |
Claude Code's own OAuth credential — read-only, mtime-tracked | Claude-Code-managed |
/tmp/claude/statusline-usage-cache-<profile>.json |
Account-keyed rate-limit API cache | 60s TTL |
/tmp/claude/statusline-profile-cache-<profile>.json |
Account-keyed profile API cache | 5min TTL |
/tmp/claude/statusline-usage-prev-<profile>.json |
Account-keyed previous poll, for interpolation | Updated each poll |
/tmp/claude/statusline-{usage,profile}-cache.json |
Current-profile aliases for companion apps | Updated each render |
/tmp/claude/statusline-subagent-<sid>.txt |
Subagent token cache per session | 30s TTL |
/tmp/claude/ctx-history-<sid>.txt |
Context-fill samples, for the fill-ETA calc | Rolling |
/tmp/claude/statusline-pr-<repo-ref-key>.json |
PR status cache | 90s TTL |
/tmp/claude/statusline-pr-<repo-ref-key>.json.lock |
PR refresh lock | Persistent file, transient lock |
/tmp/claude/statusline-raw.json |
Raw status blob, for macOS apps | Updated each render |
/tmp/claude/statusline-notif-state.json |
Notification dedup state | Per-threshold |
/tmp/claude/statusline-refresh-<profile>.lock |
Account-keyed background refresh lock | Transient |
/tmp/claude/statusline-creds-mtime-<profile> |
Account-keyed credential mtime detector | Persistent |
/tmp/claude/statusline-token-hash-<profile> |
Account-keyed OAuth token hash detector | Persistent |
# curl install
curl -fsSL https://raw.githubusercontent.com/AndrewTKent/statusline/main/uninstall.sh | bash
# npm
npx @andrewkent/claude-statusline uninstall
# Manual
rm ~/.claude/statusline.sh
# Remove "statusLine" key from ~/.claude/settings.json
# Codex monitor
./uninstall-codex.sh
# Optionally: rm ~/.codex/statusline.confMIT