Skip to content

Repository files navigation

Claude Code status line — plain, then coloured

The line under the Claude Code prompt. Out of the box it is plain and the same in every session. This repo replaces it with a shell script you own, and then makes that script colour-coded by meaning — so a wall of eight Claudes in a tmux grid is readable at a glance.

status line

▏chessuno  │  Opus 5  │  xhigh  │  880k/1.0M (88%)
 gradient     blue      green     orange → red

Four fields, four jobs:

field colour what it tells you
project a per-pane gradient which session this is — matches that pane's border
model always blue never changes meaning, so it never changes colour
effort green, brighter as it climbs low → dim sea green, xhigh → acid
tokens orange → red how close the context window is to full

Two versions ship here:

  • statusline-command.sh — plain text, sh + jq, no colour. Start here.
  • statusline-neon.sh — the coloured one above. bash + python3, and it talks to tmux.

How a status line works at all

Claude Code has a statusLine setting. Set it to type: "command" and the CLI runs your command on every render, piping a JSON blob into it on stdin. Whatever the command prints to stdout becomes the line.

That is the entire contract. No plugin, no extension, no daemon — a shell script that reads stdin and prints a string.

The payload, trimmed to the fields these scripts use:

{
  "cwd": "/Users/you/projects/chessuno",
  "workspace": { "current_dir": "/Users/you/projects/chessuno" },
  "model": { "id": "claude-opus-5", "display_name": "Opus 5" },
  "effort": { "level": "xhigh" },
  "context_window": {
    "total_input_tokens": 880000,
    "context_window_size": 1000000,
    "used_percentage": 88
  }
}

Not sure what your version sends? Dump it once — add this as the first line after the script reads stdin, trigger a render, then jq . the file and delete the line:

echo "$input" > /tmp/statusline-input.json

Two rules the scripts follow:

  • Every segment is optional. A segment is only appended when its value is non-empty, so a non-git folder gets no branch segment rather than an ugly | |.
  • Git is read-only. git --no-optional-locks — this runs on every render, and a plain git call can take a lock and fight whatever you are doing in the repo.

The colour: how statusline-neon.sh does it

Truecolor ANSI, not the 256-colour palette

Every colour is a 24-bit escape, ESC[38;2;R;G;Bm, so the exact hex survives:

printf '\033[1;38;2;77;163;255m%s' "$MODEL"     # bold #4da3ff
printf '\033[0m'                                # reset — always reset

38;5;N (the old 256-colour form) is used only for the dim separator, where precision does not matter.

The gradient is one escape per character

Terminals have no gradients. You fake one by colouring each character individually and interpolating between two endpoint colours:

for (( i=0; i<n; i++ )); do
  p=$(( n > 1 ? i * 1000 / (n - 1) : 0 ))          # 0..1000 across the string
  r=$(( r1 + (r2 - r1) * p / 1000 ))               # integer lerp, no bc
  g=$(( g1 + (g2 - g1) * p / 1000 ))
  b=$(( b1 + (b2 - b1) * p / 1000 ))
  printf '\033[1;38;2;%d;%d;%dm%s' "$r" "$g" "$b" "${t:i:1}"
done

Per-mille integers instead of floats: the whole thing stays in bash arithmetic, no bc subprocess on a script that runs on every keystroke-ish render.

Colour carries meaning, so the thresholds are the design

case "$EFFORT" in
  low)    eff='38;2;46;139;87'    ;;   # sea green, deliberately dim
  medium) eff='38;2;57;255;136'   ;;
  high)   eff='1;38;2;125;255;79' ;;
  xhigh)  eff='1;38;2;198;255;0'  ;;   # acid — you can see it across the room
esac

if   [ "$PCT" -lt 25 ]; then tok='38;2;255;157;77'    # warm orange
elif [ "$PCT" -lt 50 ]; then tok='38;2;255;122;31'
elif [ "$PCT" -lt 75 ]; then tok='1;38;2;255;77;43'   # bold, it is getting real
else                         tok='1;38;2;255;43;77'   # red, compact soon
fi

Bold is a second channel: it does the work when the hue difference is subtle.

Why python3 and not jq

The neon script pulls six fields at once and needs them as shell words:

read -r DIR MODEL EFFORT USED SIZE PCT < <(printf '%s' "$json" | python3 -c '...')

One interpreter start instead of six jq calls. jq would work fine — this is a speed choice, not a correctness one. The plain script sticks with jq.


Making the status line agree with the pane it lives in

The project gradient is not random. Each tmux pane stores its own ramp as a user option, and the status line reads it:

[ -n "${TMUX_PANE:-}" ] && pinned=$(tmux show -pv -t "$TMUX_PANE" @ramp)
if [ -n "$pinned" ]; then
  read -r C1 C2 <<<"$pinned"            # pane's own colours
else
  read -r C1 C2 <<<"${RAMPS[$(( h % ${#RAMPS[@]} ))]}"   # hash of the folder name
fi

TMUX_PANE is exported by tmux into every process in the pane, so the script knows which pane it is rendering in without being told. Two consequences worth having:

  • Inside tmux, the status line and the pane border are the same two colours — repaint the pane and the line follows, no restart.
  • Outside tmux, it falls back to hashing the folder name, so one project keeps one colour across machines and sessions. Deterministic, not random.

Per-pane tmux colours (paint)

themes

Eight Claude sessions in one tmux window look identical. paint gives each pane its own background, text colour and border.

Three tmux levers, all per-pane:

what tmux option the catch
pane background window-style / window-active-style despite the name, these are pane options — set -p makes backgrounds genuinely per-pane, not a border trick
pane border pane-border-style, pane-active-border-style also set -p
gradient title pane-border-format reading a @grad user option tmux has no gradients either; paint pre-renders one #[fg=…] span per character
tmux set -p -t "$pane" window-style        "bg=$dim,fg=$fg"     # inactive: dimmer
tmux set -p -t "$pane" window-active-style "bg=$bg,fg=$fg"      # active: full
tmux set -p -t "$pane" pane-border-style        "fg=$border"
tmux set -p -t "$pane" pane-active-border-style "fg=$border,bold"
tmux set -p -t "$pane" @ramp "${border#\#} $grad_end"           # ← the status line reads this

The inactive background is a darker variant of the same hue, so the pane you are typing in lifts forward without the others going grey.

Use

paint auto              # colour every pane in the window, one theme each
paint neon              # one theme on the current pane
paint bg '#12061f'      # one colour, current pane
paint border '#b26bff'  # just the border
paint gradient on       # gradient pane titles (costs a text row per pane)
paint list              # show the nine themes
paint off all           # back to plain

Wire it into tmux

Append tmux-paint.conf to ~/.tmux.conf, then tmux source-file ~/.tmux.conf. It sets truecolor, repaints on split/kill so a new pane is never left white, and binds prefix + P to repaint on demand.

Truecolor is not optional here. Without it every colour snaps to the nearest of 256 and the gradients band into stripes:

set  -g default-terminal "tmux-256color"
set -ga terminal-overrides ",xterm-256color:Tc"

Match the override to what the terminal outside tmux reports (echo $TERM) — ,*:Tc is the blunt version if you are unsure.


Setup

Everything needs jq — brew install jq on macOS, apt install jq on Debian/Ubuntu. The neon version also needs bash and python3 (both already on macOS).

Option 1 — one command

# plain
curl -fsSL https://github.com/ghraw/mattypark/statusline/main/install.sh | sh

# coloured
curl -fsSL https://github.com/ghraw/mattypark/statusline/main/install.sh | sh -s -- --neon

It copies the script to ~/.claude/, registers it in ~/.claude/settings.json (backing that file up first and leaving every other setting untouched), then prints a live preview of the line. Safe to re-run. Restart Claude Code afterwards.

Prefer to read before you pipe to a shell — reasonable:

git clone https://github.com/mattypark/statusline.git
cd statusline
cat install.sh      # look it over
sh install.sh --neon

--neon also installs paint to ~/bin/paint when ~/bin is on your PATH, and tells you what to add to ~/.tmux.conf. It never edits ~/.tmux.conf for you.

Option 2 — let Claude Code do it

Paste this into a session:

Install the coloured status line from https://github.com/mattypark/statusline —
read the README, copy statusline-neon.sh into ~/.claude/, and register it under
"statusLine" in ~/.claude/settings.json using an absolute path. Back up
settings.json first and leave my other settings alone. Then verify it by piping a
sample JSON payload into the script and showing me the output with `cat -v` so I
can see the escape codes.

To generate a script from scratch instead — your own segments, your own colours — use the prompts in PROMPT.md.

Option 3 — step by step

  1. Install jq (brew install jq).

  2. Copy the script into place.

    curl -fsSL https://github.com/ghraw/mattypark/statusline/main/statusline-neon.sh \
      -o ~/.claude/statusline-neon.sh
    chmod +x ~/.claude/statusline-neon.sh
  3. Test it before wiring anything up. The loop is fast here and slow inside Claude Code:

    echo '{"workspace":{"current_dir":"/tmp/chessuno"},"model":{"display_name":"Opus 5"},
    "effort":{"level":"xhigh"},"context_window":{"total_input_tokens":880000,
    "context_window_size":1000000,"used_percentage":88}}' \
      | bash ~/.claude/statusline-neon.sh

    Colours wrong? Pipe it through cat -v to see the raw escapes. Nothing at all? You have a copy problem, not a Claude problem.

  4. Register it. Add a top-level statusLine key to ~/.claude/settings.json, alongside whatever is already there:

    {
      "statusLine": {
        "type": "command",
        "command": "bash /Users/YOUR_USERNAME/.claude/statusline-neon.sh"
      }
    }

    Absolute path — ~ is not expanded here. That is the single most common reason this silently does nothing.

  5. Restart Claude Code. The command is read at session start.

  6. Optional, for the tmux grid: install paint, append tmux-paint.conf to ~/.tmux.conf, reload, and run paint auto.


Customising it

Plain script — the bottom half is a chain of if [ -n "$x" ] blocks appending to a parts string. A new segment is three lines:

cost=$(echo "$input" | jq -r '.cost.total_cost_usd // empty')
if [ -n "$cost" ]; then
  parts="${parts} | \$$(printf '%.2f' "$cost")"
fi

Reordering is moving blocks. Removing is deleting one.

Neon script — same shape, but each field prints its own colour and then resets. The pattern for a new segment is:

sep; printf '\033[0m\033[%sm%s' "$my_colour" "$my_value"

Always emit \033[0m before the next colour. A dropped reset bleeds into the rest of the line, and on some terminals into the prompt below it.

To change the palette, edit RAMPS in statusline-neon.sh and the theme_spec table in paint. They are separate on purpose — the status line falls back to RAMPS when it is not inside tmux.


Gotchas

Symptom Cause
Status line is blank jq / python3 missing from the PATH Claude Code launched with
Status line never changes command used ~ instead of an absolute path
Edits don't apply The command is read at session start — restart the session
Colours look like 8 flat colours; gradients band No truecolor: terminal-overrides ",*:Tc" missing, or the outer terminal doesn't do 24-bit
Colour bleeds into the next line A missing \033[0m reset
Gradient colour ignores the pane Not inside tmux (no TMUX_PANE), or the pane has no @ramp — run paint auto
Pane backgrounds all change together Used set -g/set -w instead of set -p; window-style is a pane option
New split is white The after-split-window hook isn't installed — see tmux-paint.conf
Line is slow / the repo feels locked You dropped --no-optional-locks from the git call

Keep it fast. This runs on every render: shell, jq/python3, and tmux show only. No network, no npm, nothing that can hang.


Files

statusline-command.sh   plain text version — sh + jq
statusline-neon.sh      coloured, tmux-aware version — bash + python3
paint                   per-pane tmux colouriser (backgrounds, borders, gradients)
tmux-paint.conf         the ~/.tmux.conf lines that make paint automatic
install.sh              one-command installer (copies + registers + verifies)
PROMPT.md               prompts that generate either version from scratch

MIT.

About

Colour-coded Claude Code status line — gradient project name, blue model, green effort, orange-to-red tokens — plus a tmux painter that gives every pane its own background so the line and its pane always match.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages