Last active
September 2, 2026 08:31
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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