Skip to content

Instantly share code, notes, and snippets.

@ericboehs
Last active March 30, 2026 16:30
Show Gist options
  • Select an option

  • Save ericboehs/c4340c6febd1b9848eb1656197bf17ca to your computer and use it in GitHub Desktop.

Select an option

Save ericboehs/c4340c6febd1b9848eb1656197bf17ca to your computer and use it in GitHub Desktop.
Claude Code status line — git status, proxy detection, pace-aware usage tracking, auto-wrapping
#!/bin/bash
input=$(cat)
# --- Configuration ---
PACE_AHEAD_THRESHOLD=10 # Show warning when this many % ahead of expected pace
PACE_BEHIND_THRESHOLD=25 # Show nudge when this many % behind expected pace
USAGE_ALWAYS_SHOW=90 # Always show usage when actual % >= this value
# --- Parse input JSON ---
current_dir=$(echo "$input" | jq -r '.workspace.current_dir')
total=$(echo "$input" | jq -r '.context_window.context_window_size // 200000')
pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
used=$(echo "$total * $pct / 100" | bc)
model_id=$(echo "$input" | jq -r '.model.id // "unknown"')
model=$(echo "$model_id" | sed 's/\[.*\]$//; s/^claude-//' | sed \
-e 's/^opus-4-6$/opus/' \
-e 's/^opus-4\.6$/opus/' \
-e 's/^sonnet-4-6$/sonnet/' \
-e 's/^sonnet-4\.6$/sonnet/' \
-e 's/^sonnet-4\.5$/sonnet-4.5/' \
-e 's/^haiku-4-5.*$/haiku/' \
)
# --- Proxy-specific context window overrides ---
if [ "$ANTHROPIC_BASE_URL" = "http://localhost:4141" ]; then
total=192000
pct=$(echo "$used * 100 / $total" | bc)
elif [ "$ANTHROPIC_BASE_URL" = "http://localhost:8083" ]; then
total=131000
pct=$(echo "$used * 100 / $total" | bc)
fi
# --- Format token counts ---
if [ "$used" -ge 1000000 ]; then
used_fmt="$(echo "$used/1000000" | bc)M"
elif [ "$used" -ge 1000 ]; then
used_fmt="$(echo "scale=0; $used/1000" | bc)k"
else
used_fmt="$used"
fi
if [ "$total" -ge 1000000 ]; then
total_fmt="$(echo "$total/1000000" | bc)M"
else
total_fmt="$(echo "scale=0; $total/1000" | bc)k"
fi
# --- Context percentage color ---
if [ "$pct" -ge 80 ] 2>/dev/null; then
pct_color='\033[31m'
elif [ "$pct" -ge 50 ] 2>/dev/null; then
pct_color='\033[33m'
else
pct_color='\033[36m'
fi
# --- Time-aware usage projection ---
# Projects usage to end of window. Outputs: "projected_pct color_code"
# Args: usage_pct elapsed_pct (both 0-100 integers)
pace_projected() {
local usage=$1 elapsed=$2
local projected=$usage
if [ "$elapsed" -gt 5 ] 2>/dev/null && [ "$usage" -gt 0 ] 2>/dev/null; then
projected=$(( usage * 100 / elapsed ))
fi
local color
if [ "$projected" -ge 100 ] 2>/dev/null; then color='\033[31m'
elif [ "$projected" -ge 75 ] 2>/dev/null; then color='\033[33m'
else color='\033[36m'; fi
echo "${projected}:${color}"
}
# === Build segments (plain text for measuring, colored for display) ===
now=$(date +%s)
cols=$(tput cols 2>/dev/null || echo 80)
# --- Dir + branch ---
if [ "$current_dir" = "$HOME" ]; then
dir_display="~"
else
dir_display=$(basename "$current_dir")
[ ${#dir_display} -gt 30 ] && dir_display="${dir_display:0:29}…"
fi
if [ -n "$SSH_CONNECTION" ]; then
host=$(hostname -s)
# Friendly hostname aliases
case "$host" in
OKL-*) host="gfe" ;;
esac
seg_dir_plain="${host}:${dir_display}"
seg_dir_color="\033[32m${host}\033[0m:\033[34m${dir_display}\033[0m"
else
seg_dir_plain="$dir_display"
seg_dir_color="\033[34m${dir_display}\033[0m"
fi
# --- Git branch + status ---
seg_git_plain=""
seg_git_color=""
if [ -d "$current_dir/.git" ]; then
cd "$current_dir"
git_branch=$(git branch --show-current 2>/dev/null)
if [ -n "$git_branch" ]; then
[ ${#git_branch} -gt 30 ] && git_branch="${git_branch:0:29}…"
git_indicators=""
git_status=$(git status --porcelain 2>/dev/null | head -20)
if [ -n "$git_status" ]; then
echo "$git_status" | grep -q '^.[MD]' && git_indicators="${git_indicators}!"
echo "$git_status" | grep -q '^??' && git_indicators="${git_indicators}?"
echo "$git_status" | grep -q '^[MADRC]' && git_indicators="${git_indicators}+"
fi
if [ -n "$git_indicators" ]; then
seg_git_plain=" ${git_branch} [${git_indicators}]"
seg_git_color=" \033[35m${git_branch}\033[0m \033[33m[${git_indicators}]\033[0m"
else
seg_git_plain=" ${git_branch}"
seg_git_color=" \033[35m${git_branch}\033[0m"
fi
fi
fi
# --- Proxy ---
seg_proxy_plain=""
seg_proxy_color=""
usage_str=""
usage_str_plain=""
if [ "$ANTHROPIC_BASE_URL" = "http://localhost:4141" ]; then
seg_proxy_plain=" copilot"
seg_proxy_color=" \033[31mcopilot\033[0m"
usage_pct=$(curl -s --max-time 1 http://localhost:4141/usage 2>/dev/null \
| jq -r '.quota_snapshots.premium_interactions | ((.entitlement - .remaining) / .entitlement * 100 | round)' 2>/dev/null)
if [ -n "$usage_pct" ] && [ "$usage_pct" != "null" ]; then
if [ "$usage_pct" -ge 80 ] 2>/dev/null; then uc='\033[31m'; elif [ "$usage_pct" -ge 50 ] 2>/dev/null; then uc='\033[33m'; else uc='\033[36m'; fi
usage_str=" ${uc}${usage_pct}%%\033[0m"
usage_str_plain=" ${usage_pct}%"
fi
elif proxy="${ANTHROPIC_BASE_URL:-${HTTPS_PROXY:-$HTTP_PROXY}}"; [ -n "$proxy" ]; then
if echo "$proxy" | grep -q "localhost:8083"; then
seg_proxy_plain=" cerebras"
seg_proxy_color=" \033[31mcerebras\033[0m"
cache_file="/tmp/cerebras-usage-cache"
cache_max_age=60
if [ -f "$cache_file" ] && [ "$(( now - $(stat -f%m "$cache_file") ))" -lt "$cache_max_age" ]; then
cerebras_info=$(cat "$cache_file")
else
(
CEREBRAS_API_KEY="${CEREBRAS_API_KEY:-$(op item get "cerebras.ai" --fields "label=API Key (Cerebras Code)" --reveal 2>/dev/null)}"
if [ -n "$CEREBRAS_API_KEY" ]; then
headers=$(curl -sS -D /dev/stdout --max-time 5 'https://api.cerebras.ai/v1/chat/completions' \
-H "Authorization: Bearer $CEREBRAS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model":"zai-glm-4.7","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \
-o /dev/null 2>/dev/null)
req_day_rem=$(echo "$headers" | grep -i 'x-ratelimit-remaining-requests-day' | cut -d' ' -f2 | tr -d '\r')
tok_day_rem=$(echo "$headers" | grep -i 'x-ratelimit-remaining-tokens-day' | cut -d' ' -f2 | tr -d '\r')
if [ -n "$req_day_rem" ]; then
req_pct=$(( (72000 - req_day_rem) * 100 / 72000 ))
tok_pct=$(( (24000000 - tok_day_rem) * 100 / 24000000 ))
echo "${req_pct}:${tok_pct}" > "$cache_file"
fi
fi
) &
[ -f "$cache_file" ] && cerebras_info=$(cat "$cache_file")
fi
if [ -n "$cerebras_info" ]; then
req_pct=$(echo "$cerebras_info" | cut -d: -f1)
tok_pct=$(echo "$cerebras_info" | cut -d: -f2)
day_elapsed=$(( (now % 86400) * 100 / 86400 ))
req_result=$(pace_projected "$req_pct" "$day_elapsed")
req_proj=$(echo "$req_result" | cut -d: -f1)
rc=$(echo "$req_result" | cut -d: -f2-)
tok_result=$(pace_projected "$tok_pct" "$day_elapsed")
tok_proj=$(echo "$tok_result" | cut -d: -f1)
tc=$(echo "$tok_result" | cut -d: -f2-)
if [ "$req_proj" -ge 90 ] 2>/dev/null; then
usage_str="${usage_str} ${rc}r:~${req_proj}%%\033[0m"
usage_str_plain="${usage_str_plain} r:~${req_proj}%"
fi
if [ "$tok_proj" -ge 90 ] 2>/dev/null; then
usage_str="${usage_str} ${tc}t:~${tok_proj}%%\033[0m"
usage_str_plain="${usage_str_plain} t:~${tok_proj}%"
fi
fi
else
proxy_short=$(echo "$proxy" | sed 's|^https\?://||')
seg_proxy_plain=" ${proxy_short}"
seg_proxy_color=" \033[31m${proxy_short}\033[0m"
fi
else
# Direct Claude subscription — fetch 5h/7d usage with reset times
# Cache format: five_pct:seven_pct:five_reset_epoch:seven_reset_epoch:extra_used:extra_limit
cache_file="/tmp/claude-usage-cache"
cache_max_age=120
if [ -f "$cache_file" ] && [ "$(( now - $(stat -f%m "$cache_file") ))" -lt "$cache_max_age" ]; then
claude_usage=$(cat "$cache_file")
else
(
token=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
if [ -n "$token" ]; then
resp=$(curl -s --max-time 5 'https://api.anthropic.com/api/oauth/usage' \
-H "Authorization: Bearer $token" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "Content-Type: application/json" 2>/dev/null)
five=$(echo "$resp" | jq -r '.five_hour.utilization // empty' 2>/dev/null | cut -d. -f1)
seven=$(echo "$resp" | jq -r '.seven_day.utilization // empty' 2>/dev/null | cut -d. -f1)
five_reset=$(echo "$resp" | jq -r '.five_hour.resets_at // empty' 2>/dev/null)
seven_reset=$(echo "$resp" | jq -r '.seven_day.resets_at // empty' 2>/dev/null)
if [ -n "$five" ] && [ -n "$seven" ]; then
five_epoch=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$five_reset" | cut -d. -f1)" +%s 2>/dev/null || echo "0")
seven_epoch=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$(echo "$seven_reset" | cut -d. -f1)" +%s 2>/dev/null || echo "0")
extra_used=$(echo "$resp" | jq -r '.extra_usage.used_credits // 0' 2>/dev/null | cut -d. -f1)
extra_limit=$(echo "$resp" | jq -r '.extra_usage.monthly_limit // 0' 2>/dev/null | cut -d. -f1)
echo "${five}:${seven}:${five_epoch}:${seven_epoch}:${extra_used}:${extra_limit}" > "$cache_file"
fi
fi
) &
[ -f "$cache_file" ] && claude_usage=$(cat "$cache_file")
fi
if [ -n "$claude_usage" ]; then
five_pct=$(echo "$claude_usage" | cut -d: -f1)
seven_pct=$(echo "$claude_usage" | cut -d: -f2)
five_reset_epoch=$(echo "$claude_usage" | cut -d: -f3)
seven_reset_epoch=$(echo "$claude_usage" | cut -d: -f4)
extra_used=$(echo "$claude_usage" | cut -d: -f5)
extra_limit=$(echo "$claude_usage" | cut -d: -f6)
[ -z "$extra_used" ] && extra_used=0
[ -z "$extra_limit" ] && extra_limit=0
# Format time-to-reset as compact string (e.g. "4.5h" or "2.3d")
fmt_reset() {
local reset_epoch=$1
if [ -n "$reset_epoch" ] && [ "$reset_epoch" -gt 0 ] 2>/dev/null; then
local remaining=$(( reset_epoch - now ))
[ "$remaining" -lt 0 ] && remaining=0
if [ "$remaining" -lt 3600 ]; then
echo "$((remaining / 60))m"
elif [ "$remaining" -lt 86400 ]; then
local h=$((remaining / 3600))
local m=$(( (remaining % 3600) / 60 ))
if [ "$m" -ge 45 ]; then echo "$(( h + 1 ))h"
elif [ "$m" -ge 15 ]; then echo "${h}.5h"
else echo "${h}h"; fi
else
local d=$((remaining / 86400))
local h=$(( (remaining % 86400) / 3600 ))
if [ "$h" -ge 12 ]; then echo "$(( d + 1 ))d"
else echo "${d}.$(( h * 10 / 24 ))d"; fi
fi
fi
}
# How far ahead/behind expected pace for a window
# ahead = actual_usage - expected_usage_at_this_point
# expected = elapsed_pct = ((window_duration - remaining) / window_duration) * 100
# Positive = ahead (burning fast), negative = behind (on track)
# Args: usage_pct reset_epoch window_seconds
# Output: "ahead_pct:color_code"
claude_pace() {
local usage=$1 reset_epoch=$2 window_secs=$3
local ahead=0
if [ "$reset_epoch" -gt 0 ] 2>/dev/null; then
local remaining=$(( reset_epoch - now ))
[ "$remaining" -lt 0 ] && remaining=0
local elapsed=$(( window_secs - remaining ))
[ "$elapsed" -lt 0 ] && elapsed=0
local expected=$(( elapsed * 100 / window_secs ))
ahead=$(( usage - expected ))
fi
local color
if [ "$ahead" -ge 20 ] 2>/dev/null; then color='\033[31m'
elif [ "$ahead" -ge 10 ] 2>/dev/null; then color='\033[33m'
else color='\033[36m'; fi
echo "${ahead}:${color}"
}
# --- 5h window (18000s) ---
if [ "$five_pct" -gt 0 ] 2>/dev/null; then
five_result=$(claude_pace "$five_pct" "$five_reset_epoch" 18000)
five_ahead=$(echo "$five_result" | cut -d: -f1)
fc=$(echo "$five_result" | cut -d: -f2-)
five_behind=$(( -five_ahead ))
if [ "$five_ahead" -ge "$PACE_AHEAD_THRESHOLD" ] 2>/dev/null || [ "$five_behind" -ge "$PACE_BEHIND_THRESHOLD" ] 2>/dev/null || [ "$five_pct" -ge "$USAGE_ALWAYS_SHOW" ] 2>/dev/null; then
if [ "$five_ahead" -gt 0 ] 2>/dev/null; then
five_label="5h: +${five_ahead}%% @ ${five_pct}%%"
five_label_plain="5h: +${five_ahead}% @ ${five_pct}%"
else
five_label="5h: ${five_ahead}%%"
five_label_plain="5h: ${five_ahead}%"
fi
# Add time-to-reset when usage is high
if [ "$five_pct" -ge 80 ] 2>/dev/null; then
five_reset_str=$(fmt_reset "$five_reset_epoch")
if [ -n "$five_reset_str" ]; then
five_label="${five_label}↻${five_reset_str}"
five_label_plain="${five_label_plain}↻${five_reset_str}"
fi
fi
usage_str="${usage_str} ${fc}${five_label}\033[0m"
usage_str_plain="${usage_str_plain} ${five_label_plain}"
fi
fi
# --- 7d window (604800s) ---
if [ "$seven_pct" -gt 0 ] 2>/dev/null; then
seven_result=$(claude_pace "$seven_pct" "$seven_reset_epoch" 604800)
seven_ahead=$(echo "$seven_result" | cut -d: -f1)
sc=$(echo "$seven_result" | cut -d: -f2-)
seven_behind=$(( -seven_ahead ))
if [ "$seven_ahead" -ge "$PACE_AHEAD_THRESHOLD" ] 2>/dev/null || [ "$seven_behind" -ge "$PACE_BEHIND_THRESHOLD" ] 2>/dev/null || [ "$seven_pct" -ge "$USAGE_ALWAYS_SHOW" ] 2>/dev/null; then
if [ "$seven_ahead" -gt 0 ] 2>/dev/null; then
seven_label="7d: +${seven_ahead}%% @ ${seven_pct}%%"
seven_label_plain="7d: +${seven_ahead}% @ ${seven_pct}%"
else
seven_label="7d: ${seven_ahead}%%"
seven_label_plain="7d: ${seven_ahead}%"
fi
if [ "$seven_pct" -ge 80 ] 2>/dev/null; then
seven_reset_str=$(fmt_reset "$seven_reset_epoch")
if [ -n "$seven_reset_str" ]; then
seven_label="${seven_label}↻${seven_reset_str}"
seven_label_plain="${seven_label_plain}↻${seven_reset_str}"
fi
fi
usage_str="${usage_str} ${sc}${seven_label}\033[0m"
usage_str_plain="${usage_str_plain} ${seven_label_plain}"
fi
fi
# --- Extra usage (overuse billing) — only show when 5h is at 100% ---
if [ "$five_pct" -ge 100 ] 2>/dev/null && [ "$extra_limit" -gt 0 ] 2>/dev/null; then
if [ "$extra_used" -ge 100 ]; then
extra_used_fmt="\$$(( extra_used / 100 ))"
else
extra_used_fmt="\$0"
fi
extra_limit_fmt="\$$(( extra_limit / 100 ))"
extra_pct=$(( extra_used * 100 / extra_limit ))
if [ "$extra_pct" -ge 80 ] 2>/dev/null; then ec='\033[31m'
elif [ "$extra_pct" -ge 50 ] 2>/dev/null; then ec='\033[33m'
else ec='\033[36m'; fi
usage_str="${usage_str} ${ec}${extra_used_fmt}/${extra_limit_fmt}\033[0m"
usage_str_plain="${usage_str_plain} ${extra_used_fmt}/${extra_limit_fmt}"
fi
fi
fi
# --- Model + context ---
seg_model_plain=" ${model}"
seg_model_color=" \033[33m${model}\033[0m"
seg_ctx_plain=" ${used_fmt}/${total_fmt}"
seg_ctx_color=" ${pct_color}${used_fmt}/${total_fmt}\033[0m"
# === Measure and output (wrap to second line if needed) ===
line1_plain="${seg_dir_plain}${seg_git_plain}${seg_proxy_plain}${seg_model_plain}${seg_ctx_plain}${usage_str_plain}"
if [ ${#line1_plain} -le "$cols" ]; then
# Fits on one line
printf "${seg_dir_color}${seg_git_color}${seg_proxy_color}${seg_model_color}${seg_ctx_color}${usage_str}"
else
# Split: dir+branch on line 1, rest on line 2
printf "${seg_dir_color}${seg_git_color}"
printf "\n"
# Remove leading space from first segment on line 2
l2_proxy="${seg_proxy_color# }"
l2_model="${seg_model_color}"
if [ -z "$seg_proxy_plain" ]; then
l2_model="${seg_model_color# }"
fi
printf "${l2_proxy}${l2_model}${seg_ctx_color}${usage_str}"
fi
@ericboehs
Copy link
Copy Markdown
Author

ericboehs commented Mar 19, 2026

Claude Code Status Line

A feature-rich status line script for Claude Code that packs directory, git, model, context, and usage tracking into a smart single-line (or auto-wrapping two-line) display.

image image image image

Features

  • Directory & git branch with status indicators (! modified, ? untracked, + staged), truncated at 30 chars
  • Proxy detection — labels for Copilot (localhost:4141), Cerebras (localhost:8083), or custom proxies
  • Model shortnamesclaude-opus-4-6[1m]opus, claude-sonnet-4-6sonnet, etc.
  • Context window — shows used/total with color coding (cyan < 50%, yellow 50-79%, red 80%+)
  • Proxy-specific context overrides — hardcoded totals for Copilot (107k) and Cerebras (131k)
  • Subscription usage (Claude direct) — fetches 5h/7d utilization from OAuth API, cached 120s
  • Pace-aware usage tracking — compares actual usage against expected usage based on elapsed time in the window
  • Overuse billing — shows $X/$Y spend when 5h usage hits 100% and extra_usage is enabled
  • Cerebras usage — daily request/token limits from rate limit headers, cached 60s
  • Conditional display — only shows usage when ahead of pace (≥10%), behind pace (≥25%), or at high usage (≥90%)
  • Configurable thresholds — tune ahead/behind/always-show thresholds at the top of the script
  • Time-to-reset — shows compact countdown (e.g. ↻30m, ↻4.5h) when usage ≥80%
  • SSH hostname aliases — map long hostnames to friendly names via glob patterns (e.g., OKL-*gfe)
  • Auto line wrapping — measures output against terminal width, splits to two lines if needed
  • Home directory — shows ~ when in home dir

Example Output

# Clean — on pace, low usage (nothing shown):
~ opus 42k/200k

# Behind pace — nudge to use more:
dotfiles main opus 42k/200k 7d: -26%

# Ahead of pace — slow down warning:
dotfiles main opus 42k/200k 5h: +20% @ 40%

# High usage with reset countdown:
dotfiles main opus 42k/200k 5h: +15% @ 85%↻30m

# Overuse billing (5h at 100%):
dotfiles main opus 42k/200k 5h: +50% @ 100%↻2h $8/$20

# With git:
dotfiles main [!?] opus 42k/200k

# With proxy:
earl-scribe main cerebras zai-glm-4.7 8k/131k

# SSH session:
gfe:earl-scribe main opus 42k/200k

# Long line (auto-wrapped):
earl-scribe main [!?]
cerebras zai-glm-4.7 8k/131k r:~110%

Installation

mkdir -p ~/.claude/scripts
curl -fsSL https://gist.githubusercontent.com/ericboehs/c4340c6febd1b9848eb1656197bf17ca/raw/statusline.sh \
  -o ~/.claude/scripts/statusline.sh
chmod +x ~/.claude/scripts/statusline.sh

Add to ~/.claude/settings.json:

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

Configuration

Configurable thresholds at the top of the script:

PACE_AHEAD_THRESHOLD=10    # Show warning when this many % ahead of expected pace
PACE_BEHIND_THRESHOLD=25   # Show nudge when this many % behind expected pace
USAGE_ALWAYS_SHOW=90       # Always show usage when actual % >= this value

Other customizations:

  • Proxy context overrides — adjust token limits for your proxies
  • Cerebras rate limits — update 72000 req/day and 24000000 tok/day if your plan differs
  • Truncation length — adjust the 30 char limit for dir/branch
  • SSH hostname aliases — add glob patterns to the case block (e.g., OKL-*gfe)

Dependencies

  • jq — JSON parsing
  • bc — arithmetic
  • git — branch/status info
  • curl — usage API calls (Cerebras, Claude OAuth, Copilot)
  • tput — terminal width detection

Optional (for usage tracking)

  • Claude subscription usage: macOS Keychain with Claude Code-credentials entry (auto-created by Claude Code)
  • Cerebras usage: CEREBRAS_API_KEY env var, or 1Password CLI (op) with a cerebras.ai item
  • Copilot usage: Local proxy at localhost:4141 with /usage endpoint

How Pace Tracking Works

The script computes how far ahead or behind your expected usage you are at the current point in a window:

elapsed = window_duration - time_remaining
expected = (elapsed / window_duration) × 100
ahead = actual_usage - expected

Display format:

  • Ahead: 5h: +20% @ 40% — you've used 40% but should have only used ~20%, so you're +20% ahead
  • Behind: 5h: -26% — you're 26% behind expected pace (room to use more)

Color coding:

  • Cyan: ahead < 10% (comfortable)
  • Yellow: ahead 10-19% (warming up)
  • Red: ahead ≥ 20% (slow down)

This means 40% usage early in a window can be red (burning too fast), while 60% near the end can be cyan (will reset before hitting limit).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment