Skip to content

Instantly share code, notes, and snippets.

@phramz
Last active March 20, 2026 14:50
Show Gist options
  • Select an option

  • Save phramz/d053a7eebb8f87fee99133b52a811333 to your computer and use it in GitHub Desktop.

Select an option

Save phramz/d053a7eebb8f87fee99133b52a811333 to your computer and use it in GitHub Desktop.

claude-statusline.sh

A custom status line for Claude Code that replaces the default status bar with a rich, gitstatus-inspired display.

image

What it shows

Git info (when inside a repo):

  • Repository name (worktree root)
  • Branch, tag (#v1.0), or detached commit (@abc1234)
  • Upstream ahead/behind (⇡3 ⇣1)
  • Push remote ahead/behind (⇢2 ⇠1) — only when an explicit push remote is configured
  • Stash count (*2)
  • Active action: merge, rebase, cherry-pick, or bisect
  • Working tree status: conflicts (~1), staged (+2), unstaged (!3), untracked (?4)

Model — the active Claude model's display name.

Context window — a 10-segment progress bar with token usage (120k/200k (60%)), color-coded green/yellow/red.

Example output

my-project main ⇡1 +2 !1 | Opus 4.6 (1M context) | |||||||||| 45k/200k (22%)

Installation

1. Download the script

# Create the directory and download
mkdir -p ~/.claude/scripts
curl -fsSL https://gist.githubusercontent.com/phramz/d053a7eebb8f87fee99133b52a811333/raw/claude-statusline.sh \
  -o ~/.claude/scripts/claude-statusline.sh
chmod +x ~/.claude/scripts/claude-statusline.sh

2. Configure Claude Code to use it

Add the following to your Claude Code settings (~/.claude/settings.json):

{
  "statusLine": {
    "type": "command",
    "command": "~/.claude/scripts/claude-statusline.sh"
  },
}

3. Allow the command (optional)

To avoid being prompted for permission each time, add the command to your allowed list in ~/.claude/settings.json:

{
  "permissions": {
    "allow": [
      "Bash(bash ~/.claude/scripts/claude-statusline.sh)"
    ]
  }
}

Or accept the permission prompt once when Claude Code first renders the status line.

Requirements

  • Claude Code with status line support
  • bash, git, and jq (all standard on macOS/Linux)

How it works

Claude Code pipes a JSON object to the status line command via stdin. The script reads this JSON (with jq) to extract the model name and context window stats, then runs local git commands for repository information. It outputs a single line of ANSI-colored text that Claude Code renders at the bottom of the terminal.

#!/bin/bash
# Status line - shows worktree, gitstatus-style git info, model, and context usage
data=$(cat)
# Get model name
model=$(echo "$data" | jq -r '.model.display_name // .model.id // "unknown"')
# Color codes
GREEN='\033[32m'
YELLOW='\033[33m'
RED='\033[31m'
BLUE='\033[34m'
CYAN='\033[36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
# Git info (gitstatus-style)
git_info=""
if git_dir_path=$(git rev-parse --git-dir 2>/dev/null); then
worktree=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")
# Branch / tag / commit
branch=$(git symbolic-ref --short HEAD 2>/dev/null)
if [ -n "$branch" ]; then
ref="${GREEN}${branch}${RESET}"
else
# Detached HEAD — check for tag first, then show commit hash
tag=$(git describe --tags --exact-match HEAD 2>/dev/null)
if [ -n "$tag" ]; then
ref="${GREEN}#${tag}${RESET}"
else
commit=$(git rev-parse --short HEAD 2>/dev/null)
ref="${GREEN}@${commit}${RESET}"
fi
fi
# Ahead/behind upstream
upstream_info=""
if upstream_counts=$(git rev-list --left-right --count @{upstream}...HEAD 2>/dev/null); then
behind=$(echo "$upstream_counts" | cut -f1)
ahead=$(echo "$upstream_counts" | cut -f2)
[ "$behind" -gt 0 ] 2>/dev/null && upstream_info="${upstream_info} ${CYAN}⇣${behind}${RESET}"
[ "$ahead" -gt 0 ] 2>/dev/null && upstream_info="${upstream_info} ${CYAN}⇡${ahead}${RESET}"
fi
# Ahead/behind push remote (only if explicit push remote configured)
push_info=""
push_remote=$(git config "branch.${branch}.pushRemote" 2>/dev/null || git config remote.pushDefault 2>/dev/null)
if [ -n "$push_remote" ]; then
if push_counts=$(git rev-list --left-right --count @{push}...HEAD 2>/dev/null); then
push_behind=$(echo "$push_counts" | cut -f1)
push_ahead=$(echo "$push_counts" | cut -f2)
[ "$push_behind" -gt 0 ] 2>/dev/null && push_info="${push_info} ${CYAN}⇠${push_behind}${RESET}"
[ "$push_ahead" -gt 0 ] 2>/dev/null && push_info="${push_info} ${CYAN}⇢${push_ahead}${RESET}"
fi
fi
# Stashes
stash_info=""
stash_count=$(git stash list 2>/dev/null | wc -l | tr -d ' ')
[ "$stash_count" -gt 0 ] && stash_info=" ${CYAN}*${stash_count}${RESET}"
# Action (merge, rebase, cherry-pick, bisect)
action_info=""
if [ -f "$git_dir_path/MERGE_HEAD" ]; then
action_info=" ${BOLD}${YELLOW}merge${RESET}"
elif [ -d "$git_dir_path/rebase-merge" ] || [ -d "$git_dir_path/rebase-apply" ]; then
action_info=" ${BOLD}${YELLOW}rebase${RESET}"
elif [ -f "$git_dir_path/CHERRY_PICK_HEAD" ]; then
action_info=" ${BOLD}${YELLOW}cherry-pick${RESET}"
elif [ -f "$git_dir_path/BISECT_LOG" ]; then
action_info=" ${BOLD}${YELLOW}bisect${RESET}"
fi
# Working tree status (single pass with awk)
porcelain=$(git status --porcelain 2>/dev/null)
if [ -n "$porcelain" ]; then
read -r conflicts staged unstaged untracked <<< $(echo "$porcelain" | awk '{
xy = substr($0, 1, 2)
if (xy == "DD" || xy == "AU" || xy == "UD" || xy == "UA" || xy == "DU" || xy == "AA" || xy == "UU") { c++; next }
x = substr($0, 1, 1)
y = substr($0, 2, 1)
if (x == "?") { u++; next }
if (x != " ") s++
if (y != " ") m++
} END { printf "%d %d %d %d", c+0, s+0, m+0, u+0 }')
else
conflicts=0 staged=0 unstaged=0 untracked=0
fi
wt_info=""
[ "$conflicts" -gt 0 ] && wt_info="${wt_info} ${RED}~${conflicts}${RESET}"
[ "$staged" -gt 0 ] && wt_info="${wt_info} ${GREEN}+${staged}${RESET}"
[ "$unstaged" -gt 0 ] && wt_info="${wt_info} ${YELLOW}!${unstaged}${RESET}"
[ "$untracked" -gt 0 ] && wt_info="${wt_info} ${BLUE}?${untracked}${RESET}"
git_info="${worktree} ${ref}${upstream_info}${push_info}${stash_info}${action_info}${wt_info} | "
fi
# Get context info
max_ctx=$(echo "$data" | jq -r '.context_window.context_window_size // 200000')
used_pct=$(echo "$data" | jq -r '.context_window.used_percentage // empty')
# Format context display
if [ -z "$used_pct" ] || [ "$used_pct" = "null" ]; then
context_info="${DIM}||||||||||${RESET} loading..."
else
pct=$(printf "%.0f" "$used_pct" 2>/dev/null || echo "$used_pct")
[ "$pct" -gt 100 ] 2>/dev/null && pct=100
used_k=$(( max_ctx * pct / 100 / 1000 ))
max_k=$(( max_ctx / 1000 ))
bar=""
filled=$(( pct / 10 ))
if [ "$pct" -gt 80 ]; then
COLOR="$RED"
elif [ "$pct" -gt 30 ]; then
COLOR="$YELLOW"
else
COLOR="$GREEN"
fi
for i in 0 1 2 3 4 5 6 7 8 9; do
if [ "$i" -lt "$filled" ]; then
bar="${bar}${COLOR}|${RESET}"
else
bar="${bar}${DIM}|${RESET}"
fi
done
context_info="${bar} ${used_k}k/${max_k}k (${pct}%)"
fi
printf '%b\n' "${git_info}${model} | ${context_info}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment