A status line for Claude Code that answers the questions you keep asking mid-session. Which model and effort am I on? How much context is left? Am I burning my rate limits faster than the clock? What state is this repo in? A small animal companion comments on all of it, if you want one.
It reads the JSON Claude Code already pipes to a status-line command, plus a few local git calls. No network, no API keys, no model calls, no dependencies. One Node file you drop into ~/.claude/.
A real session. The context bar and the workspace shimmer move; a screenshot catches one frame.
- What Each Line Shows
- Install
- The Animal Companion
- Settings
- Optional: Agent Model Labels
- How It Works
- Troubleshooting
- Versions
Four lines, read top to bottom: how full this conversation is, how much of your usage allowance is left, what state the repo is in, and a companion who comments on it. The shimmering bar down the left edge is the workspace colour; it tells you which workspace this session belongs to.
⏺ Opus 5.5 ✦ high ▕████████████░░░░░░░░▏ 61% · 610k/1M · ⚠ /compact │ +156 −23
| Segment | Meaning |
|---|---|
Opus 5.5 ✦ high |
active model and reasoning effort |
▕████░░▏ 61% |
context used. The heat is front-loaded: amber from 30%, orange at 40%, red from 50% |
⚠ /compact |
appears at 50%, where answers start to suffer; compact or start a fresh session |
610k/1M |
tokens used of the context window (scales to your window, 1M included) |
+156 −23 |
lines added and removed this session |
Why red at 50%. Claude answers best with a lean context. As a conversation grows past roughly 40%, older detail starts crowding out what matters now, so the bar turns orange at 40% as a warning and red at 50%, where ⚠ /compact appears. That's the moment to run /compact (summarise and keep going) or /clear (start fresh). Both lines are settings.
◷ 5h ▕█████│███░░▏ 80% · 2h30m left │ 7d ▕████░░│░░░░▏ 41% · 3d0h left
| Segment | Meaning |
|---|---|
▕█████ |
how much of the 5-hour or 7-day window you've used |
│ |
how much of the window's time has passed. Fill that stops before the notch means you're within pace |
███ past the notch |
red: you're spending faster than the clock, and will run out before the reset |
80% · 2h30m left |
usage in numbers, and time until the window resets |
How to read a limit bar. Think of each window as an allowance that refills at the reset.
- The
│notch is now. It sits halfway along when half the window's time has gone. - The fill is what you've spent.
- Spending evenly would put the end of the fill exactly on the notch.
- Fill that stops before the notch means you're spending slower than time passes. You'll make it to the reset. In the example, 7d has 41% spent with 57% of the week gone.
- Fill past the notch is red. That's what you've spent ahead of schedule. In the example, 5h is 80% spent at the halfway mark, so the last 3 cells are red. At that pace you hit 100% after about 3 hours and wait roughly 1h50m for the reset.
📁 AI_WORKSPACE_Personal ▸ my-app : feat/souls · ✚ 3 · ⇡2 · ↻ 3h ago · gh:you/my-app · PR #12 approved
| Segment | Meaning |
|---|---|
📁 AI_WORKSPACE_Personal |
the launch folder, shimmering in its own workspace colour |
▸ my-app |
the repo you're in, shown only when it differs from the launch folder |
feat/souls |
branch: white on main/master, amber elsewhere |
✚ 3 |
uncommitted files |
⇡2 ⇣1 |
commits ahead of and behind upstream, or ✓ synced when clean and even |
↻ 3h ago |
age of the upstream's last commit |
gh:you/my-app |
the origin remote; click it to open the repo (gh: is github.com; other hosts show their domain) |
PR #12 approved |
the open PR and its review state: approved, pending, changes requested, or draft; click it to open the PR. GitLab merge requests read MR !12 |
What the arrows mean.
⇡2: two commits you made on this computer that GitHub doesn't have yet.git pushsends them; until then they exist only on your machine.⇣1: one commit on GitHub that you don't have yet, from a merged PR, a teammate, or you on another machine.git pullbrings it in.✓ synced: both sides match. Nothing to push or pull.
Both links (the remote and the PR) open in your browser with Ctrl+click, or Cmd+click on macOS.
╰─ 🦊 3 files dirty and no commit. living dangerously.
Off by default. The animal speaks in italics under line 3, in lines written ahead of time for what's going on: uncommitted files, a full context, a branch behind its upstream, late nights. It never calls a model and uses no tokens. See The Animal Companion.
You need Node.js 18 or newer and a truecolor terminal: Windows Terminal, iTerm2, WezTerm, Kitty, or the VS Code terminal. macOS Terminal.app and the classic Windows console aren't truecolor and garble the colours.
Point Claude Code at this repo and say:
install this status line on my machine
Claude follows AGENTS.md: it copies the script, the souls, and the /animal command into ~/.claude/, merges the settings block, checks the result, and tells you to restart.
macOS / Linux
git clone https://github.com/risukisu/claude-code-statusline.git
cd claude-code-statusline
mkdir -p ~/.claude/souls ~/.claude/commands
cp statusline.js ~/.claude/statusline.js
cp souls/*.md ~/.claude/souls/
cp commands/animal.md ~/.claude/commands/Windows (PowerShell)
git clone https://github.com/risukisu/claude-code-statusline.git
Set-Location claude-code-statusline
New-Item -ItemType Directory -Force "$HOME\.claude\souls","$HOME\.claude\commands" | Out-Null
Copy-Item statusline.js "$HOME\.claude\statusline.js"
Copy-Item souls\*.md "$HOME\.claude\souls\"
Copy-Item commands\animal.md "$HOME\.claude\commands\"Then merge this block into ~/.claude/settings.json, keeping every key already there:
{
"statusLine": {
"type": "command",
"command": "node ~/.claude/statusline.js",
"refreshInterval": 10
}
}On Windows, use the full path with forward slashes: node C:/Users/YOUR_USERNAME/.claude/statusline.js.
Restart Claude Code. The dashboard appears under the prompt, and line 4 invites you to run /animal. To check which version you have, run node ~/.claude/statusline.js --version.
Pipe in the sample payload:
cat examples/sample-input.json | node statusline.js # bash
Get-Content examples/sample-input.json | node statusline.js # PowerShellRun /animal to pick a companion, or name one directly: /animal fox, /animal off. Every line it says is hand-written in a plain markdown soul file. It never calls a model.
- 🐿️ squirrel: manic, cheerful, forgets where the nuts are
- 🦊 fox: clever, sly, efficiency-minded, a little sassy
- 🐢 turtle: slow, patient, talks you out of rushing
The companion picks a new line every 30 seconds, based on what's going on. When something needs attention, two lines in three speak to it. The third is idle chatter, so the character still comes through.
| Section | When it speaks | Placeholder |
|---|---|---|
dirty |
you have uncommitted changes | {dirty} |
ahead |
you have commits you haven't pushed | {ahead} |
behind |
upstream has commits you don't | {behind} |
context |
the context window has reached the danger line (50% by default) | {ctx} |
limits |
the 5-hour window is at 80%, or you're 15+ points ahead of the clock | {limit} |
work |
any of the five above; also covers a soul that lacks one of them | |
synced |
clean and even with upstream | |
branch |
you're on a branch other than main or master |
{branch} |
norepo |
you're outside a git repo | |
night |
midnight to 5 a.m., local time | |
ambient |
idle chatter |
Each soul in souls/ is a markdown file with one bullet list per section. Edit ~/.claude/souls/<animal>.md and the next redraw picks it up. Add :noun to a count to get a plural: {dirty:file} becomes "1 file" or "3 files". A line whose placeholder has no value right now is skipped, so a half-filled line never reaches the screen.
## behind
- origin moved {behind:commit} ahead. pull before you pounce.Everything works without a settings file. To change the defaults, create ~/.claude/statusline.json with only the keys you want; examples/statusline.json has them all. Upgrades never touch this file, so edit it instead of statusline.js.
{
"theme": "default",
"quiet": true,
"links": true,
"gutter": true,
"context": { "warn": 40, "danger": 50 },
"palettes": [
{ "match": "ai_workspace_personal", "from": "#06b6d4", "to": "#4ade80" }
],
"hide": []
}| Key | Default | What it does |
|---|---|---|
theme |
"default" |
"default", "high-contrast" (brighter greys and text), or "colorblind" (blue, yellow, and vermillion instead of green and red) |
quiet |
true |
dims what's healthy, like ✓ synced or low usage, so only what needs attention stands out |
links |
true |
makes the remote and the PR clickable |
gutter |
true |
a ▌ down the left edge in the workspace colours, so the four lines read as one block |
context |
40 / 50 |
where the context bar turns orange (warn) and red with the /compact hint (danger) |
palettes |
two built-in | workspace shimmer colours; see below |
hide |
[] |
segments to leave out: effort, tokens, diff, limits, sync, remote, pr, companion |
barWidth |
20 (12 in narrow terminals) |
context bar width, 4 to 40 cells |
agentTag |
— | the dots for the optional agent model labels |
A bad value falls back to its default, so a typo never breaks the status line. Set the standard NO_COLOR environment variable to print plain text with no colour codes at all.
Each launch folder can shimmer in its own colours, so line 3 tells you at a glance which workspace you're in. I keep a personal workspace and a work one strictly apart, and a PowerShell launcher starts Claude in each (examples/profile.ps1).
Each palette entry has a match, which is any part of the launch folder's path (case and slash direction don't matter), and two hex colours, from and to. The first entry that matches wins; unmatched folders show in plain blue. Without a palettes key, two built-in entries colour ai_workspace_personal cyan → mint and ai_workspace_appsilon amber → gold. An empty list turns the shimmer off.
Claude Code lists each subagent by its description but never says which model it runs on. The add-on extras/agent-model-tag.js is a small hook that adds the model, its exact version, and a dot per model family:
Agent "🤖 Plan the release [🟡 fable: Fable 5.1]" finished · 2m 10s
Agent "🤖 Fix the tests [🟣 opus: Opus 5.5]" finished · 1m 1s
Agent "🤖 Small edit [🔵 sonnet: Sonnet 5.5]" finished · 30s
Agent "🤖 Read the docs [🟢 haiku: Haiku 5.5]" finished · 20s
Agent "🤖 Explore the repo [⚪ inherit: Fable 5.1]" finished · 45s
It follows Claude Code's own order for picking a subagent's model (docs): the model passed at launch, then the agent definition's model: (project agents, then your own, then plugin agents), then CLAUDE_CODE_SUBAGENT_MODEL, otherwise the session model (inherit). Forks always run on the session model. The exact version comes from the model ids in your local transcripts; until a model has run once in that project, the version comes from a built-in table and carries a ?. The label shows what was asked for at launch; /tasks shows what a running agent really uses.
It's opt-in and separate from the status line, which still needs no hook. To install it, copy the file:
mkdir -p ~/.claude/hooks && cp extras/agent-model-tag.js ~/.claude/hooks/New-Item -ItemType Directory -Force "$HOME\.claude\hooks" | Out-Null
Copy-Item extras\agent-model-tag.js "$HOME\.claude\hooks\"Then merge this block into ~/.claude/settings.json, keeping every hook already there (on Windows, use the full path with forward slashes), and restart Claude Code:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Agent",
"hooks": [{ "type": "command", "command": "node ~/.claude/hooks/agent-model-tag.js", "timeout": 10 }]
}
]
}
}To change a dot, add agentTag to ~/.claude/statusline.json. The keys are fable, opus, sonnet, haiku and other (inherit, fork, and unknown models), and a bad value keeps the default:
{ "agentTag": { "emoji": { "opus": "🧠", "other": "·" } } }What it reads: agent definition files, plugins/installed_plugins.json, statusline.json, and the model field from the last 512 KB of local transcript files. It makes no network or model calls, writes nothing, and never blocks a launch: on any error the label just gets plainer.
Claude Code runs a status-line command after each message and every refreshInterval seconds, and hands it a JSON description of the session on stdin (docs). This script prints four lines from it:
| Input | Drives |
|---|---|
model.display_name, effort.level |
model and effort |
context_window |
the context bar and token count |
cost.total_lines_added / _removed |
the +/− counter |
rate_limits.five_hour / .seven_day |
usage, pace, and reset countdowns |
workspace.project_dir / .current_dir |
launch folder versus the repo you're in |
pr.number, pr.url, pr.review_state, pr.kind |
the PR badge and its link |
git status, rev-parse, config, log |
branch, changes, ahead/behind, remote |
In a narrow terminal (a split pane, the VS Code side panel), each line drops its least useful parts instead of wrapping. Line 3 drops the remote first, then the sync age, the PR's review words, the ▸ repo name, and finally the PR. Line 1 drops the token count, then the diff, then the effort; line 2 drops the countdowns, then the 7-day window. Branch, uncommitted files, ahead/behind, the context %, and the /compact hint always stay. Claude Code passes the real width in COLUMNS.
Links use the OSC 8 escape code that Claude Code passes through to the terminal. The git reads use --no-optional-locks and short timeouts, and each session caches them for three seconds, so a burst of redraws runs git once. A render that Claude Code cancels exits on its own. Cache files live in ~/.claude/statusline-git.*, and a new session clears the ones idle for a day.
The colours show up as escape codes
Your terminal isn't truecolor. Use Windows Terminal, iTerm2, WezTerm, Kitty, or the VS Code terminal.
The repo and PR aren't clickable
Your terminal needs OSC 8 hyperlink support: Windows Terminal, iTerm2, WezTerm, Kitty, and the VS Code terminal have it. Ctrl+click (Cmd+click on macOS) opens the link. If the text shows but won't click, Claude Code may not have detected hyperlink support; set FORCE_HYPERLINK=1 before launching Claude Code. Over SSH or inside tmux, the escape codes can get stripped.
The workspace name barely shimmers
The shimmer moves one step per redraw. With refreshInterval: 10 that's once every ten seconds plus after each message. Lower values animate more, but every redraw starts a new process in every open session, so stay at 5 or above.
/animal doesn't exist
Slash commands load at session start. Restart Claude Code, and check that ~/.claude/commands/animal.md is there.
Line 3 says "no repo"
You're outside a git repository, or git isn't on your PATH. The line still shows the launch folder.
Nothing appears after editing settings.json
Restart Claude Code, and check the command path. On Windows it needs the full C:/Users/.../.claude/statusline.js path with forward slashes.
Releases follow semantic versioning and are listed on the releases page. CHANGELOG.md has the details, including upgrade notes for pre-1.0 installs. Working on the code? Start with CONTRIBUTING.md.
MIT. Use it, fork it, bend it to your setup. PRs welcome.
- The context-bar gradient started as a port of getagentseal/codeburn.
- The README visuals are drawn from the script's own output by
assets/readme/source/.
Made by risu · 🐿️
