Skip to content

Instantly share code, notes, and snippets.

@lis186
Last active April 16, 2026 00:28
Show Gist options
  • Select an option

  • Save lis186/4f920c8657161278e0ad9166f03871ca to your computer and use it in GitHub Desktop.

Select an option

Save lis186/4f920c8657161278e0ad9166f03871ca to your computer and use it in GitHub Desktop.
Claude Code statusline — model, project, git, ctx bar, cost, duration, rate limits with remaining quota

Claude Code Statusline

A two-line status display for Claude Code's statusLine hook — shows model, project, git status, context window, cost, duration, and rate limit quota.

Screenshots

Without multi-account setup

without multi-account

Sonnet 4.6  my-project  → main
ctx ████░░░░░░ 42.3%  · $1.24  · 8m  │  5h: 80% left in 2h 14m  · 7d: 95% left in 5d 3h

With multi-account setup (claude-code-account-switcher)

with multi-account

Sonnet 4.6  my-project  → main
ctx ████░░░░░░ 42.3%  · $1.24  · 8m  │   personal · user@example.com   5h: 80% left in 2h 14m  · 7d: 95% left in 5d 3h

The account badge (personal · user@example.com) appears when you have claude-code-account-switcher configured. It reads the account name from ~/.claude-switch/links and the email from ~/.claude-switch/accounts/<name>/.account-info.json.

What's displayed

Line 1

  • Model name (e.g. Sonnet 4.6, Opus 4.6)
  • Project directory name
  • Git branch + dirty (!) / ahead (↑N) / behind (↓N) indicators

Line 2

  • ctx ████░░░░░░ 42.3% — context window usage bar (green → amber → red)
  • · $1.24 — session cost (amber when ≥ $10)
  • · 8m — session duration
  • │ personal · user@example.com — active account name + email badge (requires claude-code-account-switcher)
  • 5h: 80% left in 2h 14m — 5-hour rate limit: remaining quota + time until reset
  • · 7d: 95% left in 5d 3h — 7-day rate limit: same format

Colors: green = plenty of quota, amber = getting low (≤30%), red = nearly exhausted (≤10%).

Setup

1. Save the script

curl -o ~/.claude/statusline.sh https://gist.githubusercontent.com/your-handle/4f920c8657161278e0ad9166f03871ca/raw/statusline.sh
chmod +x ~/.claude/statusline.sh

2. Add the hook to ~/.claude/settings.json

{
  "hooks": {
    "PostToolUse": [],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/statusline.sh"
          }
        ]
      }
    ]
  },
  "statusLine": {
    "command": "bash ~/.claude/statusline.sh"
  }
}

The statusLine key is what drives the two-line status display. It receives a JSON payload on stdin with model, workspace, cost, context window, and rate limit data.

3. (Optional) Multi-account support

Install claude-code-account-switcher to get the account badge. Once configured, the script reads ~/.claude-switch/links to detect which account is active for the current directory, and ~/.claude-switch/accounts/<name>/.account-info.json for the associated email — both are displayed in the badge on line 2.

Requirements

  • jq (for JSON parsing)
  • git (for branch info)
  • A Nerd Font in your terminal (for the account badge personal powerline glyphs; falls back cleanly without one)
#!/bin/bash
# Claude Code Status Line Script
# Line 1: Sonnet 4.6 project → branch ! ↑2
# Line 2: ctx ████░░░░░░ 35% · $84.31 · 8h 31m │ personal · user@example.com 5h: 77% left in 2h 43m · 7d: 95% left in 1d 1h
# Read JSON input from stdin, parse all fields in one jq call
eval "$(cat | jq -r '
@sh "model_name=\(.model.display_name // "")",
@sh "current_dir=\(.workspace.current_dir // "")",
@sh "total_cost=\(.cost.total_cost_usd // 0)",
@sh "ctx_pct=\(.context_window.used_percentage // 0)",
@sh "duration_ms=\(.cost.total_duration_ms // 0)",
@sh "five_hour_pct=\(.rate_limits.five_hour.used_percentage // "")",
@sh "five_hour_resets_at=\(.rate_limits.five_hour.resets_at // "")",
@sh "seven_day_pct=\(.rate_limits.seven_day.used_percentage // "")",
@sh "seven_day_resets_at=\(.rate_limits.seven_day.resets_at // "")"
' | tr ',' '\n')"
# Fallbacks for empty values
total_cost=${total_cost:-0}
ctx_pct=${ctx_pct:-0}
duration_ms=${duration_ms:-0}
# Account name and email from claude-switch links + .account-info.json
account_name=""
account_email=""
if [ -n "$current_dir" ] && [ -f "$HOME/.claude-switch/links" ]; then
account_name=$(grep "^${current_dir}=" "$HOME/.claude-switch/links" | cut -d= -f2)
fi
if [ -n "$account_name" ]; then
info_file="$HOME/.claude-switch/accounts/${account_name}/.account-info.json"
if [ -f "$info_file" ]; then
account_email=$(jq -r '.email // empty' "$info_file" 2>/dev/null)
fi
fi
# ANSI colors — tuned to match Claude Code's purple/blue palette
RESET="\033[0m"
PURPLE="\033[38;5;255m" # model name — bright white
BLUE="\033[38;5;111m" # project name — calm blue
DIM="\033[38;5;245m" # secondary info (cost, duration, git branch)
OK="\033[38;5;114m" # green — safe (<50%)
WARN="\033[38;5;221m" # amber — caution (50-79%)
CRIT="\033[38;5;204m" # rose-red — danger (≥80/90%)
# Shorten model name: "Claude Opus 4.6" → "Opus 4.6"
short_model="${model_name#Claude }"
# Get project name
if [ -n "$current_dir" ]; then
project_name=$(basename "$current_dir")
else
project_name=$(basename "$(pwd)")
fi
# Get git info: branch, dirty, ahead/behind — one subprocess via status -sb
git_branch=""
git_plain=""
if [ -n "$current_dir" ]; then
git_status=$(timeout 1 git -C "$current_dir" status -sb 2>/dev/null)
if [ -n "$git_status" ]; then
first_line=$(printf '%s' "$git_status" | head -1)
git_branch=$(printf '%s' "$first_line" | sed 's/^## //;s/\.\.\..*//;s/^No commits yet on //')
ahead=$(printf '%s' "$first_line" | sed -n 's/.*ahead \([0-9]*\).*/\1/p')
behind=$(printf '%s' "$first_line" | sed -n 's/.*behind \([0-9]*\).*/\1/p')
dirty_lines=$(printf '%s' "$git_status" | tail -n +2)
[ -n "$dirty_lines" ] && git_plain+=" !"
[ -n "$ahead" ] && git_plain+=" ↑${ahead}"
[ -n "$behind" ] && git_plain+=" ↓${behind}"
fi
fi
# Context window progress bar (10 blocks)
ctx_int=${ctx_pct%%.*}
ctx_int=${ctx_int:-0}
if [ "$ctx_int" -ge 90 ] 2>/dev/null; then
CTX_COLOR="$CRIT"
elif [ "$ctx_int" -ge 70 ] 2>/dev/null; then
CTX_COLOR="$WARN"
else
CTX_COLOR="$OK"
fi
if [ "$ctx_int" -eq 0 ] 2>/dev/null; then
# Session start: no API call yet, all counts are zero — show placeholder
ctx_display="${DIM}ctx ░░░░░░░░░░ --${RESET}"
else
filled=$((ctx_int / 10))
empty=$((10 - filled))
bar=$(printf "%${filled}s" | tr ' ' '█')$(printf "%${empty}s" | tr ' ' '░')
ctx_pct_fmt=$(printf "%.1f" "$ctx_pct")
ctx_display="${CTX_COLOR}ctx ${bar} ${ctx_pct_fmt}%${RESET}"
fi
# Cost
cost_display=""
if [ "$total_cost" != "0" ] && [ "$total_cost" != "null" ]; then
cost_int=${total_cost%%.*}
if [ "${cost_int:-0}" -ge 10 ] 2>/dev/null; then
cost_display=$(printf " · ${WARN}\$%.2f${RESET}" "$total_cost")
else
cost_display=$(printf " · ${DIM}\$%.2f${RESET}" "$total_cost")
fi
fi
# Duration — human readable: <60m → Xm, <24h → Xh Ym, ≥24h → Xd Yh
duration_display=""
if [ "$duration_ms" != "0" ] && [ "$duration_ms" != "null" ]; then
total_mins=$((duration_ms / 60000))
if [ "$total_mins" -lt 60 ]; then
dur_text="${total_mins}m"
elif [ "$total_mins" -lt 1440 ]; then
dur_text="$((total_mins / 60))h $((total_mins % 60))m"
else
days=$((total_mins / 1440))
hours=$(( (total_mins % 1440) / 60 ))
dur_text="${days}d ${hours}h"
fi
duration_display=" · ${DIM}${dur_text}${RESET}"
fi
# Human-readable time until window reset
format_reset() {
local resets_at="$1"
[ -z "$resets_at" ] && echo "" && return
local now remaining mins
now=$(date +%s)
remaining=$(( resets_at - now ))
[ "$remaining" -le 0 ] && echo "now" && return
mins=$(( remaining / 60 ))
if [ "$mins" -lt 60 ]; then echo "${mins}m"
elif [ "$mins" -lt 1440 ]; then echo "$((mins/60))h $((mins%60))m"
else echo "$((mins/1440))d $(( (mins%1440)/60 ))h"
fi
}
# One rate-limit segment: "5h: 20% left in 3h 23m"
build_rate_segment() {
local sep="$1" label="$2" usage_pct="$3" resets_at="$4"
[ -z "$usage_pct" ] || [ "$usage_pct" = "null" ] && return
local usage_int remaining color reset_str
usage_int="${usage_pct%%.*}"
remaining=$(( 100 - ${usage_int:-0} ))
if [ "$remaining" -le 10 ] 2>/dev/null; then color="$CRIT"
elif [ "$remaining" -le 30 ] 2>/dev/null; then color="$WARN"
else color="$OK"
fi
reset_str=$(format_reset "$resets_at")
local reset_display=""
[ -n "$reset_str" ] && reset_display=" ${DIM}in ${reset_str}${RESET}"
printf '%s%s: %b%d%% left%b' "$sep" "$label" "$color" "$remaining" "$RESET$reset_display"
}
rate_display=""
rate_display+=$(build_rate_segment " " "5h" "$five_hour_pct" "$five_hour_resets_at")
rate_display+=$(build_rate_segment " · " "7d" "$seven_day_pct" "$seven_day_resets_at")
# Line 1: model · project → branch
git_info=""
[ -n "$git_branch" ] && git_info=" ${DIM}→ ${git_branch}${git_plain}${RESET}"
printf "%b\n" "${PURPLE}${short_model}${RESET} ${BLUE}${project_name}${RESET}${git_info}"
# Line 2: ctx · cost · duration │ account 5h … · 7d …
account_badge=""
if [ -n "$account_name" ]; then
PL_L=$(printf '\xee\x82\xb6')
PL_R=$(printf '\xee\x82\xb4')
badge_text="$account_name"
[ -n "$account_email" ] && badge_text="${account_name} · ${account_email}"
account_badge=" \033[38;5;141m${PL_L}\033[48;5;141m\033[38;5;232m ${badge_text} \033[0m\033[38;5;141m${PL_R}\033[0m"
fi
rate_section=""
[ -n "${account_badge}${rate_display}" ] && rate_section=" │ ${account_badge}${rate_display}"
printf "%b\n" "${ctx_display}${cost_display}${duration_display}${rate_section}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment