Claude Code Status Line Maker

Pick the pieces. Copy the config. Your terminal, properly dressed.

Verified against Claude Code v2.1.205 · 2026-07-09

1 · Choose your elements

2 · Put them in order

    Nothing selected yet — tick an element above and it lands here.

    3 · Preview

    4 · Take it home

    Paste this into a Claude Code session — it writes and wires the status line for you, on your machine, matched to your setup.

    What the Claude Code status line is

    The status line is the single line Claude Code pins to the bottom of your terminal — the same idea as a shell prompt or a tmux status bar, but scoped to your session with Claude. Out of the box it's minimal. With one setting, it becomes yours: which model is on duty, what branch you're about to let an AI loose on, what this session has cost so far — whatever you can print, it can show.

    The mechanism is refreshingly blunt: you point Claude Code at any executable. It runs your command, feeds it a JSON blob of session state on stdin, and displays whatever your command prints. That's the whole contract. Everything on this page is detail.

    Three ways to set one up

    1. Ask Claude Code to do it (easiest)

    Claude Code has a built-in /statusline command that takes plain-English instructions and writes the script for you. That's what the generator above produces: a precise instruction, so you get exactly the line you designed instead of whatever the model improvises. Paste, press enter, done.

    2. Use a ready-made script

    Flip the output above to Script + settings.json for a finished shell script and the setting that wires it in. You see every line before it runs — the reasonable instinct for anything that executes on every update.

    3. Write your own

    Any executable works — bash, Python, Node, a compiled binary. Read JSON from stdin, print one line to stdout. The stdin reference below is everything your script will be handed.

    statusLine in settings.json

    The setting lives in ~/.claude/settings.json (all sessions) or a project's .claude/settings.json (that project only — handy for a per-repo status line):

    {
      "statusLine": {
        "type": "command",
        "command": "~/.claude/statusline.sh",
        "padding": 0
      }
    }
    KeyTypeWhat it does
    typestringAlways "command" — the status line is produced by running a command.
    commandstringThe executable to run. ~ expands; relative paths are asking for trouble — use absolute or ~-anchored.
    paddingnumberOptional, default 0. Adds extra horizontal spacing (in characters) on top of the interface's built-in spacing — it controls relative indentation, not distance from the terminal edge.
    refreshIntervalnumberOptional. Re-runs the command every N seconds (minimum 1) in addition to event-driven updates. Set it when your line shows a clock or other time-based value; leave it unset to run only on events.

    What your command receives on stdin

    On every update, Claude Code pipes a JSON object like this to your command:

    {
      "session_id": "abc123-...",
      "transcript_path": "/Users/you/.claude/projects/.../transcript.jsonl",
      "cwd": "/Users/you/projects/my-app",
      "version": "2.1.205",
      "model": {
        "id": "claude-opus-4-8",
        "display_name": "Opus"
      },
      "workspace": {
        "current_dir": "/Users/you/projects/my-app",
        "project_dir": "/Users/you/projects/my-app"
      },
      "output_style": { "name": "default" },
      "cost": {
        "total_cost_usd": 0.42,
        "total_duration_ms": 754321,
        "total_api_duration_ms": 98765,
        "total_lines_added": 156,
        "total_lines_removed": 23
      },
      "context_window": {
        "total_input_tokens": 15500,
        "total_output_tokens": 1200,
        "context_window_size": 200000,
        "used_percentage": 8,
        "remaining_percentage": 92
      },
      "exceeds_200k_tokens": false
    }
    FieldWhat it is
    model.display_nameHuman-friendly model name ("Opus", "Sonnet"). The one you want on screen.
    model.idFull model identifier — for scripts that branch on exact model.
    workspace.current_dirDirectory the session is currently working in. Basename it for a compact display.
    workspace.project_dirThe project root the session started in. Differs from current_dir when Claude wanders.
    cwdSame as workspace.current_dir; kept for compatibility.
    versionThe Claude Code version string.
    output_style.nameActive output style (e.g. default).
    cost.total_cost_usdSession spend so far, in USD. The number worth watching.
    cost.total_duration_msWall-clock session length, milliseconds.
    cost.total_api_duration_msTime spent waiting on the API specifically.
    cost.total_lines_added / total_lines_removedLines of code Claude has added / removed this session.
    context_window.used_percentagePre-calculated percentage of the context window in use. The ready-made number for a context gauge — no transcript parsing required. May be null before the first API response.
    context_window.remaining_percentageThe complement — percentage of context still free.
    context_window.context_window_sizeMaximum context window in tokens (200000, or 1000000 for extended-context models).
    context_window.total_input_tokens / total_output_tokensTokens currently in the context window, from the most recent API response.
    exceeds_200k_tokensBoolean — true when the most recent response's combined tokens exceed 200k (a fixed threshold, regardless of window size).
    session_id, transcript_pathIdentifiers for advanced scripts — e.g. parsing the transcript, or keying a per-session cache off session_id.

    How rendering actually works

    Worked examples

    The minimalist

    Model and folder. Enough to never again wonder which terminal tab is burning Opus tokens.

    #!/usr/bin/env bash
    input=$(cat)
    model=$(echo "$input" | jq -r '.model.display_name')
    dir=$(basename "$(echo "$input" | jq -r '.workspace.current_dir')")
    printf '%s │ %s' "$model" "$dir"

    The git-aware

    Adds the branch, with a * when the tree is dirty — the single most useful character an AI-assisted coder can keep in view.

    #!/usr/bin/env bash
    input=$(cat)
    model=$(echo "$input" | jq -r '.model.display_name')
    cwd=$(echo "$input" | jq -r '.workspace.current_dir')
    branch=$(git -C "$cwd" branch --show-current 2>/dev/null)
    dirty=$(git -C "$cwd" status --porcelain 2>/dev/null | head -1)
    [ -n "$dirty" ] && branch="${branch}*"
    out="$model"
    [ -n "$branch" ] && out="$out │ $branch"
    printf '%s' "$out"

    The spend-watcher

    Cost, session length, and the diff Claude has racked up. For anyone on a budget — which is everyone.

    #!/usr/bin/env bash
    input=$(cat)
    cost=$(echo "$input" | jq -r '.cost.total_cost_usd // 0' | awk '{printf "$%.2f", $1}')
    mins=$(( $(echo "$input" | jq -r '.cost.total_duration_ms // 0') / 60000 ))
    added=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
    removed=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
    printf '%s │ %sm │ +%s -%s' "$cost" "$mins" "$added" "$removed"

    Troubleshooting

    SymptomFix
    Nothing shows up at allUsual suspects: the script isn't executable (chmod +x), the command path is wrong (test with echo '{}' | ~/.claude/statusline.sh), the script prints to stderr instead of stdout, or the workspace-trust prompt hasn't been accepted (the status line needs the same trust as hooks). Run claude --debug to see the exit code and stderr.
    jq: command not foundInstall it (brew install jq / apt install jq) — or ask Claude Code to rewrite the script without the dependency; it's happy to.
    Literal \033[36m in the lineYour echo isn't interpreting escapes. Use printf, or bash's $'\033[36m' quoting, not plain echo.
    Values feel staleBy design: updates ride conversation events with a ~300 ms debounce, so clocks and tickers sit still between messages. Add "refreshInterval": 1 to statusLine to also refresh on a timer.
    Works globally, wrong per-projectA project's .claude/settings.json overrides ~/.claude/settings.json. Check which file actually holds statusLine.

    FAQ

    Will a status line slow Claude Code down?

    Not meaningfully — it runs outside the conversation loop. Keep the script itself snappy (under ~100 ms, no network calls) and you'll never notice it. A slow script doesn't block Claude; it just lags its own display.

    Does it have to be bash?

    No. Anything executable works: Python, Node, Rust, a one-line jq invocation. Read stdin, print one line. Bash is just the lingua franca of examples.

    Can different projects have different status lines?

    Yes — put statusLine in the project's .claude/settings.json and it wins over your global one in that repo. A loud red custom tag in the production-infra repo is a genuinely good idea.

    Can it show context-window usage?

    Yes, and it's easy — Claude Code hands you a pre-calculated context_window.used_percentage (and remaining_percentage), so no transcript parsing is needed:

    pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
    printf '%s%%' "$pct"

    Tick Context used in the generator above to fold it into your line, or ask /statusline for "model name and context percentage with a progress bar". The value may be null until the first API response — the // 0 fallback covers that.

    Does this work on Windows?

    Under WSL, exactly as shown. In native Windows shells, point command at anything your shell can run — a PowerShell script reading stdin works the same way.

    Is anything I do on this page sent anywhere?

    No. The generator is a static page; your selections never leave the browser. The script it produces runs only on your machine.