Skip to content

Instantly share code, notes, and snippets.

@szerintedmi
Last active September 2, 2026 08:31
Show Gist options
  • Select an option

  • Save szerintedmi/e3904bbb9c1b5617da5c1c870aa784f1 to your computer and use it in GitHub Desktop.

Select an option

Save szerintedmi/e3904bbb9c1b5617da5c1c870aa784f1 to your computer and use it in GitHub Desktop.
Claude Code custom statusline with context bar, git status, and lines of code
#!/bin/bash
#
# Claude Code Custom Statusline
# ==============================
# Shows model name, effort level, context usage, git status, and lines changed.
#
# Example output:
# Fable 5.1 ▌▌▌▌▌ ⛁⛁⛁⛁⛁⛶⛶⛶⛶⛶⛶⛶⛶⛶⛶⛶ 67% │ ~/project [main ↑2 ●3] │ +127 -45
#
# Features:
# - Effort bar: five levels (low, medium, high, xhigh, max), one bar each.
# Orange ▌ = active, dark gray ▌ = inactive. Reads the live value from the
# status line JSON (`effort.level`, Claude Code >= 2.1.119). The bar is
# hidden when the model does not support effort.
# - ⚡ after the effort bar when fast mode is on. ◌ when thinking is off.
# - Context usage bar in the /context style
# - Purple ⛁ = used tokens
# - Gray ⛶ = free space
# - Gray ⛝ = space above the auto-compact window (only when you set one
# with /autocompact, `autoCompactWindow`, or CLAUDE_CODE_AUTO_COMPACT_WINDOW)
# - Free % of the usable window (below the auto-compact point)
# - Git status: [branch ↑ahead↓behind ●staged ✖conflicts ✚changed ⚑stashed]
# - Lines of code: green +added, red -removed
#
# Installation:
# 1. Download this script:
# curl -o ~/.claude/statusline-battery.sh https://gist.githubusercontent.com/szerintedmi/e3904bbb9c1b5617da5c1c870aa784f1/raw/statusline-battery.sh
#
# 2. Make it executable:
# chmod +x ~/.claude/statusline-battery.sh
#
# 3. Add to ~/.claude/settings.json:
# {
# "statusLine": {
# "type": "command",
# "command": "bash ~/.claude/statusline-battery.sh"
# }
# }
#
# Requirements: jq, git, Claude Code >= 2.1.119
#
input=$(cat)
# One jq call for all scalar fields (tab separated). "-" marks a missing value.
IFS=$'\t' read -r model cwd effort_level fast_mode thinking_enabled \
used_pct ctx_size total_used lines_added lines_removed < <(
echo "$input" | jq -r '
def d: if . == null then "-" else . end;
[
(.model.display_name | d),
(.workspace.current_dir // .cwd | d),
(.effort.level | d),
(.fast_mode | d),
(.thinking.enabled | d),
(.context_window.used_percentage | d),
(.context_window.context_window_size | d),
((.context_window.current_usage
| if . == null then null
else (.input_tokens // 0) + (.cache_creation_input_tokens // 0) + (.cache_read_input_tokens // 0)
end) | d),
(.cost.total_lines_added // .cost.lines_of_code.added // 0),
(.cost.total_lines_removed // .cost.lines_of_code.removed // 0)
] | @tsv')
# Colors
c_reset="$(printf '\033[0m')"
c_gray="$(printf '\033[38;2;153;153;153m')"
c_dim="$(printf '\033[38;2;80;80;80m')"
c_orange="$(printf '\033[38;2;204;102;51m')"
c_purple="$(printf '\033[38;2;147;51;234m')"
repeat() { local s="" i; for ((i=0; i<$2; i++)); do s="${s}$1"; done; printf '%s' "$s"; }
# Effort bar: five levels, one bar each. Hidden when the model has no effort.
effort_bar=""
if [ "$effort_level" != "-" ]; then
case "$effort_level" in
low) active=1 ;;
medium) active=2 ;;
high) active=3 ;;
xhigh) active=4 ;;
max) active=5 ;;
*) active=3 ;;
esac
effort_bar=" ${c_orange}$(repeat ▌ $active)${c_dim}$(repeat ▌ $((5 - active)))${c_reset}"
fi
[ "$fast_mode" = "true" ] && effort_bar="${effort_bar} ${c_orange}⚡${c_reset}"
[ "$thinking_enabled" = "false" ] && effort_bar="${effort_bar} ${c_dim}◌${c_reset}"
# Abbreviate home directory with ~
display_path="$cwd"
[[ "$cwd" == "$HOME"* ]] && display_path="~${cwd#$HOME}"
# Git status
git_info=""
if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then
g="git -C $cwd --no-optional-locks"
branch=$($g rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -n "$branch" ]; then
ahead=$($g rev-list @{u}..HEAD 2>/dev/null | wc -l | tr -d ' ')
behind=$($g rev-list HEAD..@{u} 2>/dev/null | wc -l | tr -d ' ')
staged=$($g diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
conflicts=$($g diff --name-only --diff-filter=U 2>/dev/null | wc -l | tr -d ' ')
changed=$($g diff --numstat 2>/dev/null | wc -l | tr -d ' ')
stashed=$($g stash list 2>/dev/null | wc -l | tr -d ' ')
git_status=""
[ "$ahead" != "0" ] && git_status="${git_status}$(printf '\033[33m')↑${ahead}${c_reset}"
[ "$behind" != "0" ] && git_status="${git_status}$(printf '\033[33m')↓${behind}${c_reset}"
[ "$staged" != "0" ] && git_status="${git_status}$(printf '\033[32m')●${staged}${c_reset}"
[ "$conflicts" != "0" ] && git_status="${git_status}$(printf '\033[31m')✖${conflicts}${c_reset}"
[ "$changed" != "0" ] && git_status="${git_status}$(printf '\033[34m')✚${changed}${c_reset}"
[ "$stashed" != "0" ] && git_status="${git_status}$(printf '\033[35m')⚑${stashed}${c_reset}"
if [ -n "$git_status" ]; then
git_info=" [$(printf '\033[36m')${branch}${c_reset} ${git_status}]"
else
git_info=" [$(printf '\033[36m')${branch}${c_reset}]"
fi
fi
fi
# Context bar
context_bar=""
if [ "$ctx_size" != "-" ] && [ "$total_used" != "-" ]; then
# Auto-compact window: by default Claude Code compacts at the context
# limit, so there is no reserved buffer. Only a user-set window shrinks
# the usable space. Priority: env var > user settings.
compact_window="$ctx_size"
if [ -n "$CLAUDE_CODE_AUTO_COMPACT_WINDOW" ]; then
compact_window="$CLAUDE_CODE_AUTO_COMPACT_WINDOW"
elif [ -f "$HOME/.claude/settings.json" ]; then
w=$(jq -r '.autoCompactWindow // empty' "$HOME/.claude/settings.json" 2>/dev/null)
[ -n "$w" ] && compact_window="$w"
fi
[ "$compact_window" -gt "$ctx_size" ] && compact_window="$ctx_size"
usable=$compact_window
buffer=$((ctx_size - compact_window))
free=$((usable - total_used)); [ $free -lt 0 ] && free=0
free_pct=$((free * 100 / usable))
bar_width=16
used_portion=$((total_used * bar_width / ctx_size))
buffer_portion=$((buffer * bar_width / ctx_size))
[ $used_portion -gt $((bar_width - buffer_portion)) ] && used_portion=$((bar_width - buffer_portion))
free_portion=$((bar_width - used_portion - buffer_portion))
bar="${c_purple}$(repeat ⛁ $used_portion)${c_gray}$(repeat ⛶ $free_portion)$(repeat ⛝ $buffer_portion)${c_reset}"
context_bar=" ${bar} ${free_pct}%"
fi
# Lines of code
loc_info=""
if [ "$lines_added" != "0" ] || [ "$lines_removed" != "0" ]; then
loc_info=" ${c_gray}│${c_reset} "
[ "$lines_added" != "0" ] && loc_info="${loc_info}$(printf '\033[32m')+${lines_added}${c_reset}"
[ "$lines_added" != "0" ] && [ "$lines_removed" != "0" ] && loc_info="${loc_info} "
[ "$lines_removed" != "0" ] && loc_info="${loc_info}$(printf '\033[31m')-${lines_removed}${c_reset}"
fi
sep="${c_gray}│${c_reset}"
printf "%s%s%s %s %s%s%s\n" "$model" "$effort_bar" "$context_bar" "$sep" "$display_path" "$git_info" "$loc_info"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment