Claude Code statusline: width-aware two-line status with model, effort, cost, context bar, duration, rate limits, and repo:branch A developer created a custom statusline script for Claude Code that displays model, effort, cost, context bar, duration, rate limits, and git branch info in a width-aware two-line format. The script uses a single jq call and bash builtins to minimize forks, and includes a circle bar for context window usage. | /bin/bash | | | shopt -s extglob | | | Color configuration | | | COLOR="\033 96m" Cyan - easy on eyes at night | | | MAGENTA="\033 95m" | | | RESET="\033 0m" | | | Read JSON input from stdin builtin read , no cat fork | | | IFS= read -r -d '' input | | | DEBUG: Uncomment the next line to see what JSON is being passed | | | echo "$input" /tmp/statusline-debug.json | | | Terminal width: Claude Code v2.1.153+ sets $COLUMNS on the script's env since | | | stdout isn't a tty here, so tput cols/stty can't read it directly. | | | term width=${COLUMNS:-80} | | | Strip literal "\033 ...m" color codes not real ESC bytes, since they're | | | expanded later by echo -e via a bash builtin pattern, to measure the | | | visible length of a candidate line without forking printf/sed. | | | strip ansi { | | | local s="$1" | | | printf '%s' "${s//\\033\ + 0-9 m/}" | | | } | | | Extract all scalar fields with a single jq call was 7 separate echo | jq | | | forks . "|" is safe as a field separator here: unlike tab/@tsv, bash's | | | read only collapses consecutive IFS-whitespace delimiters, so empty | | | fields e.g. cost/context before the first API call won't shift columns. | | | IFS='|' read -r model name current dir output style total cost context used max ctx \ | | | duration ms rl 5h pct rl 7d pct effort level <<< "$ | | | jq -r ' | | | .model.display name, | | | .workspace.current dir, | | | .output style.name // "default" , | | | .cost.total cost usd // "" , | | | .context window.used percentage // "" , | | | .context window.context window size // 200000 , | | | .cost.total duration ms // "" , | | | .rate limits.five hour.used percentage // "" , | | | .rate limits.seven day.used percentage // "" , | | | .effort.level // "" | | | | join "|" ' <<< "$input" | | | " | | | Extract cost information | | | cost info="" | | | if -n "$total cost" ; then | | | cost formatted=$ printf "%.4f" "$total cost" | | | cost info="\$${cost formatted}" | | | fi | | | Context window usage with circle bar added in Claude Code 2.1.6 | | | context info="" | | | context info short="" | | | if -n "$context used" ; then | | | pct=$ printf "%.0f" "$context used" | | | $pct -gt 100 && pct=100 | | | Calculate tokens in k | | | used k=$ max ctx pct / 100 / 1000 | | | max k=$ max ctx / 1000 | | | Build circle bar 10 segments, each worth 10% | | | bar="" | | | filled=$ pct / 10 | | | Always blue | | | BAR COLOR="\033 94m" | | | for i in 0 1 2 3 4 5 6 7 8 9; do | | | if $i -lt $filled ; then | | | bar="${bar}${BAR COLOR}●${RESET}" | | | else | | | bar="${bar}${BAR COLOR}○${RESET}" | | | fi | | | done | | | context info="${bar} ${COLOR}${used k}k/${max k}k ${pct}%${RESET}" | | | context info short="${COLOR}${pct}%${RESET}" | | | else | | | Loading state both tiers, so the fallback chain never adds an empty segment | | | context info="${COLOR}○○○○○○○○○○ loading${RESET}" | | | context info short="${COLOR}...${RESET}" | | | fi | | | Get git information if in a git repo . rev-parse --abbrev-ref HEAD alone | | | already fails silently empty output outside a repo, so no need for a | | | separate rev-parse --git-dir check first. | | | git info="" | | | branch=$ git -C "$current dir" rev-parse --abbrev-ref HEAD 2 /dev/null | | | if -n "$branch" ; then | | | Get git status with --no-optional-locks to avoid lock issues | | | git status=$ git -C "$current dir" --no-optional-locks status --porcelain 2 /dev/null | | | if -n "$git status" ; then | | | Count changes in a single awk pass instead of 3 separate greps | | | read -r modified added untracked <<< "$ awk ' | | | /^ M/{m++} /^A/{a++} /^\?\?/{u++} | | | END{print m+0, a+0, u+0} | | | ' <<< "$git status" " | | | status indicator="" | | | $modified -gt 0 && status indicator="${status indicator}~${modified}" | | | $added -gt 0 && status indicator="${status indicator}+${added}" | | | $untracked -gt 0 && status indicator="${status indicator}?${untracked}" | | | git info="${branch}${status indicator}" | | | else | | | git info="${branch}" | | | fi | | | fi | | | repo:branch e.g. "zra-ingestor:lwan/foo" — the org/parent dir segment | | | rarely adds information, so just pair the repo leaf name with its branch. | | | dir basename="${current dir /}" | | | if -n "$git info" ; then | | | repo info="${dir basename}:${git info}" | | | else | | | repo info="$dir basename" | | | fi | | | Effort level only shown if set , magenta to stand out from the rest of the line | | | effort info="" | | | if -n "$effort level" ; then | | | effort info="${MAGENTA}${effort level}${RESET}" | | | fi | | | Output style suffix only shown if non-default | | | style info="" | | | if "$output style" = "default" ; then | | | style info="style:${output style}" | | | fi | | | Session duration, e.g. "1h23m" / "9m" | | | duration info="" | | | if -n "$duration ms" ; then | | | total sec=$ duration ms / 1000 | | | dur h=$ total sec / 3600 | | | dur m=$ total sec % 3600 / 60 | | | if dur h 0 ; then | | | duration info="${dur h}h${dur m}m" | | | else | | | duration info="${dur m}m" | | | fi | | | fi | | | 5-hour / 7-day rate limit usage, e.g. "5h:30% 7d:12%" | | | rate limit info="" | | | rate limit info short="" | | | if -n "$rl 5h pct" || -n "$rl 7d pct" ; then | | | rl 5h disp=$ printf "%.0f" "${rl 5h pct:-0}" | | | rl 7d disp=$ printf "%.0f" "${rl 7d pct:-0}" | | | rate limit info="5h:${rl 5h disp}% 7d:${rl 7d disp}%" | | | rate limit info short="${rl 5h disp}/${rl 7d disp}%" | | | fi | | | Full current dir, with $HOME collapsed to "~" as in a typical shell prompt | | | full dir="${current dir/ $HOME/~}" | | | Build a line by adding segments in priority order highest first into | | | $status line, up to $term width. Each segment can pass multiple | | | decreasing-detail variants; add segment tries them in order and keeps the | | | first that fits, skipping empty ones. | | | SEP=" | " | | | add segment { | | | local variant candidate visible | | | for variant in "$@"; do | | | -z "$variant" && continue | | | candidate="${status line:+${status line}${SEP}}${variant}" | | | visible=$ strip ansi "$candidate" | | | if ${ visible} <= term width ; then | | | status line="$candidate" | | | return 0 | | | fi | | | done | | | return 1 | | | } | | | Line 1 priority: model effort cost context duration rate limits | | | model+effort are joined with a plain space no separator so effort reads | | | as a modifier on the model name rather than its own segment. | | | status line="" | | | add segment "$model name" | | | if -n "$effort info" ; then | | | status line="${status line} ${effort info}" | | | fi | | | add segment "$cost info" | | | add segment "$context info" "$context info short" | | | add segment "$duration info" | | | add segment "$rate limit info" "$rate limit info short" | | | line1="$status line" | | | Line 2 priority: repo:branch style full path | | | status line="" | | | add segment "$repo info" "📁 $dir basename" | | | add segment "$style info" | | | add segment "$full dir" | | | line2="$status line" | | | echo -e "${COLOR}${line1}${RESET}" | | | echo -e "${COLOR}${line2}${RESET}" |