Skip to content

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"