Claude Code Status Line¶
A two-line status bar shown at the bottom of every Claude Code prompt.
Line 1: CTX bar · git status · model · 5h reset timer · 7d reset timer
Line 2: 5h usage bar · 7d usage bar · burn rate vs average
Rate limit timers and usage bars only appear on Claude Max/Pro plans (requires the API to supply rate-limit data). The git section only appears inside git repos. The CTX bar populates after the first exchange in a session.
Setup¶
1. Install jq¶
The script uses jq to parse the JSON Claude passes in.
Windows: run in an elevated PowerShell (Run as Administrator):
choco install jq -y
No Chocolatey? Download jq-windows-amd64.exe from https://github.com/jqlang/jq/releases, rename to jq.exe, and place it somewhere on your PATH (e.g. C:\Windows\System32\).
macOS: brew install jq
Linux: sudo apt install jq / sudo dnf install jq
2. Save the script¶
Copy the script below to ~/.claude/statusline-command.sh and make it executable:
chmod +x ~/.claude/statusline-command.sh
3. Register it in Claude Code settings¶
Add to ~/.claude/settings.json (create the file if it doesn't exist):
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/statusline-command.sh"
}
}
Restart Claude Code — the status bar appears immediately at the next prompt.
Script¶
Save as ~/.claude/statusline-command.sh:
#!/usr/bin/env bash
# Claude Code status line script
# Elements: context bar, git status, model, 5h/7d reset timers, 5h/7d usage bars, burn rate
input=$(cat)
# ── helpers ──────────────────────────────────────────────────────────────────
fill_bar() {
local pct="${1:-0}"
local width="${2:-10}"
local filled=$(( (pct * width + 50) / 100 ))
[ "$filled" -gt "$width" ] && filled=$width
local empty=$(( width - filled ))
local f=0; while [ $f -lt $filled ]; do printf '█'; f=$((f+1)); done
local e=0; while [ $e -lt $empty ]; do printf '░'; e=$((e+1)); done
}
fmt_seconds() {
local secs="$1"
if [ -z "$secs" ] || [ "$secs" -le 0 ] 2>/dev/null; then echo "now"; return; fi
local h=$(( secs / 3600 ))
local m=$(( (secs % 3600) / 60 ))
local s=$(( secs % 60 ))
if [ "$h" -gt 0 ]; then printf '%dh %02dm' "$h" "$m"
elif [ "$m" -gt 0 ]; then printf '%dm %02ds' "$m" "$s"
else printf '%ds' "$s"; fi
}
fmt_seconds_days() {
local secs="$1"
if [ -z "$secs" ] || [ "$secs" -le 0 ] 2>/dev/null; then echo "now"; return; fi
local d=$(( secs / 86400 ))
local h=$(( (secs % 86400) / 3600 ))
if [ "$d" -gt 0 ]; then printf '%dd %02dh' "$d" "$h"
else printf '%dh' "$h"; fi
}
now=$(date +%s)
# ── 1. Context window bar ─────────────────────────────────────────────────────
ctx_used=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
ctx_part=""
if [ -n "$ctx_used" ]; then
ctx_int=$(printf '%.0f' "$ctx_used")
ctx_bar=$(fill_bar "$ctx_int" 10)
if [ "$ctx_int" -ge 80 ]; then color="\033[0;31m"
elif [ "$ctx_int" -ge 50 ]; then color="\033[0;33m"
else color="\033[0;32m"; fi
ctx_part=$(printf "${color}CTX [%s] %d%%\033[0m" "$ctx_bar" "$ctx_int")
else
ctx_part="CTX [──────────]"
fi
# ── 2. Git status ─────────────────────────────────────────────────────────────
cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd // empty')
git_part=""
if [ -n "$cwd" ]; then
git_status=$(git -C "$cwd" --no-optional-locks status --porcelain 2>/dev/null)
if [ $? -eq 0 ]; then
[ -z "$git_status" ] && changed=0 || changed=$(echo "$git_status" | grep -c '^')
branch=$(git -C "$cwd" --no-optional-locks rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ "$changed" -gt 0 ]; then
git_part=$(printf "\033[0;33m±%d %s\033[0m" "$changed" "${branch:-HEAD}")
else
git_part=$(printf "\033[0;32m✔ %s\033[0m" "${branch:-HEAD}")
fi
fi
fi
# ── 2b. Current model ─────────────────────────────────────────────────────────
model=$(echo "$input" | jq -r '.model // empty' | grep -oE '(opus|sonnet|haiku)' | tr '[:lower:]' '[:upper:]')
model_part=""
if [ -n "$model" ]; then
case "$model" in
OPUS) color="\033[0;35m" ;;
SONNET) color="\033[0;36m" ;;
HAIKU) color="\033[0;34m" ;;
*) color="\033[0;37m" ;;
esac
model_part=$(printf "${color}%s\033[0m" "$model")
fi
# ── 3 & 4. Reset timers ───────────────────────────────────────────────────────
five_resets_at=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
week_resets_at=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
timer_5h=""
[ -n "$five_resets_at" ] && timer_5h=$(printf "5h↺ %s" "$(fmt_seconds $(( five_resets_at - now )))")
timer_7d=""
[ -n "$week_resets_at" ] && timer_7d=$(printf "7d↺ %s" "$(fmt_seconds_days $(( week_resets_at - now )))")
# ── 5 & 6. Usage bars ────────────────────────────────────────────────────────
five_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
week_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
bar_5h=""
if [ -n "$five_pct" ]; then
five_int=$(printf '%.0f' "$five_pct")
five_bar=$(fill_bar "$five_int" 8)
if [ "$five_int" -ge 80 ]; then color="\033[0;31m"
elif [ "$five_int" -ge 50 ]; then color="\033[0;33m"
else color="\033[0;32m"; fi
bar_5h=$(printf "${color}5h[%s]%d%%\033[0m" "$five_bar" "$five_int")
fi
bar_7d=""
if [ -n "$week_pct" ]; then
week_int=$(printf '%.0f' "$week_pct")
week_bar=$(fill_bar "$week_int" 8)
if [ "$week_int" -ge 80 ]; then color="\033[0;31m"
elif [ "$week_int" -ge 50 ]; then color="\033[0;33m"
else color="\033[0;32m"; fi
bar_7d=$(printf "${color}7d[%s]%d%%\033[0m" "$week_bar" "$week_int")
fi
# ── 7. Burn rate ──────────────────────────────────────────────────────────────
burn_part=""
total_in=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0')
total_out=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0')
ctx_size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
if [ -n "$five_resets_at" ] && [ "$total_in" -gt 0 ] 2>/dev/null; then
elapsed_secs=$(( 18000 - (five_resets_at - now) ))
[ "$elapsed_secs" -le 0 ] && elapsed_secs=1
elapsed_min=$(( elapsed_secs / 60 ))
[ "$elapsed_min" -le 0 ] && elapsed_min=1
burn_rate=$(( (total_in + total_out) / elapsed_min ))
avg_rate=$(( ctx_size / 60 ))
[ "$avg_rate" -le 0 ] && avg_rate=1
ratio=$(( burn_rate * 100 / avg_rate ))
if [ "$ratio" -ge 200 ]; then color="\033[0;31m"; label="🔥"
elif [ "$ratio" -ge 120 ]; then color="\033[0;33m"; label="↑"
elif [ "$ratio" -ge 80 ]; then color="\033[0;32m"; label="~"
else color="\033[0;36m"; label="↓"; fi
burn_part=$(printf "${color}burn %s%d%%avg\033[0m" "$label" "$ratio")
fi
# ── Assemble ──────────────────────────────────────────────────────────────────
sep=" "
join_parts() {
local result=""
for part in "$@"; do
[ -z "$result" ] && result="$part" || result="$result$sep$part"
done
printf '%s' "$result"
}
line1_parts=()
[ -n "$ctx_part" ] && line1_parts+=("$ctx_part")
[ -n "$git_part" ] && line1_parts+=("$git_part")
[ -n "$model_part" ] && line1_parts+=("$model_part")
[ -n "$timer_5h" ] && line1_parts+=("$timer_5h")
[ -n "$timer_7d" ] && line1_parts+=("$timer_7d")
line2_parts=()
[ -n "$bar_5h" ] && line2_parts+=("$bar_5h")
[ -n "$bar_7d" ] && line2_parts+=("$bar_7d")
[ -n "$burn_part" ] && line2_parts+=("$burn_part")
line1=$(join_parts "${line1_parts[@]}")
line2=$(join_parts "${line2_parts[@]}")
[ -n "$line1" ] && printf '%s\n' "$line1"
[ -n "$line2" ] && printf '%s\n' "$line2"