Skip to content

Instantly share code, notes, and snippets.

@pento
Last active February 9, 2026 17:58
Show Gist options
  • Select an option

  • Save pento/d5cd21682486f83e2328cbb7861262d3 to your computer and use it in GitHub Desktop.

Select an option

Save pento/d5cd21682486f83e2328cbb7861262d3 to your computer and use it in GitHub Desktop.
Claude Code usage status line: save the scripts in your ~/.claude/scripts directory, and add the settings to your ~/.claude/settings.json
#!/usr/bin/env bash
set -euo pipefail
CACHE_DIR="$HOME/.claude/cache"
CACHE_FILE="$CACHE_DIR/claude-usage.json"
TTL_SECONDS=900 # 15 minutes
mkdir -p "$CACHE_DIR"
# Check cache age unless --force
if [[ "${1:-}" != "--force" ]] && [[ -f "$CACHE_FILE" ]]; then
now=$(date +%s)
mod=$(stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0)
age=$(( now - mod ))
if (( age < TTL_SECONDS )); then
exit 0
fi
fi
write_error() {
local err="$1"
local now_ms=$(( $(date +%s) * 1000 ))
cat > "$CACHE_FILE" <<EJSON
{"error":"$err","fetched_at":$now_ms}
EJSON
}
# Extract OAuth token - try credentials file first, then fall back to keychain
CREDS_FILE="$HOME/.claude/.credentials.json"
if [[ -f "$CREDS_FILE" ]]; then
cred_json=$(cat "$CREDS_FILE" 2>/dev/null) || {
write_error "credentials_file_read_error"
exit 0
}
else
cred_json=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null) || {
write_error "keychain_error"
exit 0
}
fi
access_token=$(printf '%s' "$cred_json" | /usr/bin/python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d['claudeAiOauth']['accessToken'])
" 2>/dev/null) || {
write_error "token_parse_error"
exit 0
}
# Make a minimal API call to read rate-limit headers
# Using claude-haiku-4-5-20251001 with 1 max_token for minimal cost
tmpbody=$(mktemp)
response=$(curl -sS -D- -o "$tmpbody" \
-X POST https://api.anthropic.com/v1/messages \
-H "Authorization: Bearer $access_token" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: oauth-2025-04-20" \
-H "content-type: application/json" \
-d '{"model":"claude-haiku-4-5-20251001","max_tokens":1,"messages":[{"role":"user","content":"x"}]}' \
2>/dev/null) || {
rm -f "$tmpbody"
write_error "api_request_error"
exit 0
}
# Check for HTTP errors
http_status=$(echo "$response" | head -1 | grep -oE '[0-9]{3}' | head -1 || true)
if [[ "$http_status" != "200" ]]; then
body=$(cat "$tmpbody" 2>/dev/null | head -c 300 | tr '"' "'" || echo "")
rm -f "$tmpbody"
# Also log token length for debugging (not the token itself)
token_len=${#access_token}
write_error "api_error_${http_status:-unknown}|token_len=${token_len}|$body"
exit 0
fi
rm -f "$tmpbody"
# Extract rate-limit headers (case-insensitive)
# Actual header names: anthropic-ratelimit-unified-*
# Note: grep returns exit code 1 on no match; use || true to avoid pipefail exit
util_5h=$(echo "$response" | grep -i '^anthropic-ratelimit-unified-5h-utilization:' | tr -d '[:space:]' | cut -d: -f2 || true)
util_7d=$(echo "$response" | grep -i '^anthropic-ratelimit-unified-7d-utilization:' | tr -d '[:space:]' | cut -d: -f2 || true)
status_5h=$(echo "$response" | grep -i '^anthropic-ratelimit-unified-5h-status:' | tr -d '[:space:]' | cut -d: -f2 || true)
reset_5h=$(echo "$response" | grep -i '^anthropic-ratelimit-unified-5h-reset:' | tr -d '[:space:]' | cut -d: -f2 || true)
reset_7d=$(echo "$response" | grep -i '^anthropic-ratelimit-unified-7d-reset:' | tr -d '[:space:]' | cut -d: -f2 || true)
# If none of the expected headers are present, dump all headers for debugging
if [[ -z "$util_5h" && -z "$util_7d" && -z "$status_5h" ]]; then
all_headers=$(echo "$response" | grep -i ':' | tr '\r' ' ' | tr '\n' '|' || true)
write_error "no_utilization_headers|headers:$all_headers"
exit 0
fi
# Convert utilization floats to percentages (0.81 -> 81)
pct_5h=""
pct_7d=""
if [[ -n "$util_5h" ]]; then
pct_5h=$(/usr/bin/python3 -c "print(int(float('$util_5h') * 100 + 0.5))" 2>/dev/null || echo "")
fi
if [[ -n "$util_7d" ]]; then
pct_7d=$(/usr/bin/python3 -c "print(int(float('$util_7d') * 100 + 0.5))" 2>/dev/null || echo "")
fi
now_ms=$(( $(date +%s) * 1000 ))
cat > "$CACHE_FILE" <<EJSON
{"used_5h_percent":${pct_5h:-null},"used_7d_percent":${pct_7d:-null},"reset_5h":${reset_5h:-null},"reset_7d":${reset_7d:-null},"status":"${status_5h:-unknown}","fetched_at":$now_ms}
EJSON
#!/usr/bin/env bash
set -euo pipefail
CACHE_FILE="$HOME/.claude/cache/claude-usage.json"
WINDOW_5H=18000 # 5 hours in seconds
WINDOW_7D=604800 # 7 days in seconds
# Read stdin JSON from Claude Code
input=$(cat)
# Extract model display name and context % via stdin (safe for any JSON content)
model=$(printf '%s' "$input" | /usr/bin/python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('model', {}).get('display_name', '--'))
" 2>/dev/null || echo "--")
ctx=$(printf '%s' "$input" | /usr/bin/python3 -c "
import sys, json
d = json.load(sys.stdin)
print(int(d.get('context_window', {}).get('used_percentage', 0)))
" 2>/dev/null || echo "0")
# ANSI color helpers
reset='\033[0m'
green='\033[32m'
yellow='\033[33m'
red='\033[31m'
dim='\033[2m'
# Color based on usage vs expected pace through window
# delta = actual_pct - expected_pct (where expected = time_elapsed_fraction * 100)
# green: well below pace (delta < -5)
# yellow: near pace (-5 <= delta <= 5)
# red: above pace (delta > 5)
color_for_pace() {
local actual=$1
local expected=$2
local delta=$(( actual - expected ))
if (( delta > 5 )); then
echo -ne "$red"
elif (( delta >= -5 )); then
echo -ne "$yellow"
else
echo -ne "$green"
fi
}
# Simple color for absolute percentage (used for context window)
color_for_pct() {
local pct=$1
if (( pct > 70 )); then
echo -ne "$red"
elif (( pct > 40 )); then
echo -ne "$yellow"
else
echo -ne "$green"
fi
}
# Build progress bar with time-position marker (10 chars wide)
# Usage: progress_bar <usage_pct> <time_pct>
# ███░░░░▏░░ = usage at 30%, time marker at 70%
progress_bar_with_marker() {
local pct=$1
local time_pct=$2
local filled=$(( pct / 10 ))
local marker_pos=$(( time_pct / 10 ))
# Clamp to 0-9 range (marker is placed within the 10-char bar)
(( marker_pos > 9 )) && marker_pos=9
(( marker_pos < 0 )) && marker_pos=0
local bar=""
for ((i=0; i<10; i++)); do
if (( i == marker_pos )); then
bar+="*"
elif (( i < filled )); then
bar+="█"
else
bar+="░"
fi
done
echo -n "$bar"
}
# Read cache file
pct_5h="--"
pct_7d="--"
reset_5h=""
reset_7d=""
status=""
stale_note=""
time_pct_5h=0
time_pct_7d=0
if [[ -f "$CACHE_FILE" ]]; then
cache=$(<"$CACHE_FILE")
# Parse all cache values in one python call via stdin
eval "$(printf '%s' "$cache" | /usr/bin/python3 -c "
import sys, json
d = json.load(sys.stdin)
if 'error' not in d:
v5 = d.get('used_5h_percent')
v7 = d.get('used_7d_percent')
print(f'pct_5h=\"{v5 if v5 is not None else \"--\"}\"')
print(f'pct_7d=\"{v7 if v7 is not None else \"--\"}\"')
print(f'status=\"{d.get(\"status\", \"\")}\"')
r5 = d.get('reset_5h')
r7 = d.get('reset_7d')
if r5 is not None: print(f'reset_5h={int(r5)}')
if r7 is not None: print(f'reset_7d={int(r7)}')
print(f'fetched_at={d.get(\"fetched_at\", 0)}')
" 2>/dev/null)" 2>/dev/null || true
now=$(date +%s)
# Calculate time elapsed as percentage of each window
if [[ -n "$reset_5h" ]]; then
remaining_5h=$(( reset_5h - now ))
(( remaining_5h < 0 )) && remaining_5h=0
elapsed_5h=$(( WINDOW_5H - remaining_5h ))
time_pct_5h=$(( elapsed_5h * 100 / WINDOW_5H ))
(( time_pct_5h > 100 )) && time_pct_5h=100
(( time_pct_5h < 0 )) && time_pct_5h=0
fi
if [[ -n "$reset_7d" ]]; then
remaining_7d=$(( reset_7d - now ))
(( remaining_7d < 0 )) && remaining_7d=0
elapsed_7d=$(( WINDOW_7D - remaining_7d ))
time_pct_7d=$(( elapsed_7d * 100 / WINDOW_7D ))
(( time_pct_7d > 100 )) && time_pct_7d=100
(( time_pct_7d < 0 )) && time_pct_7d=0
fi
# Check staleness
now_ms=$(( now * 1000 ))
age_min=$(( (now_ms - ${fetched_at:-0}) / 60000 ))
if (( age_min > 20 )); then
stale_note=" ${dim}(${age_min}m ago)${reset}"
fi
fi
# Build output
output=""
# Model name
output+="$model"
output+=" │ "
# 5h utilization with time marker
if [[ "$pct_5h" != "--" ]]; then
output+="5h: $(color_for_pace "$pct_5h" "$time_pct_5h")$(progress_bar_with_marker "$pct_5h" "$time_pct_5h") ${pct_5h}%${reset}"
else
output+="5h: ${dim}--${reset}"
fi
output+=" │ "
# 7d utilization (color based on pace)
if [[ "$pct_7d" != "--" ]]; then
output+="7d: $(color_for_pace "$pct_7d" "$time_pct_7d")${pct_7d}%${reset}"
else
output+="7d: ${dim}--${reset}"
fi
output+=" │ "
# Context window
output+="ctx: $(color_for_pct "$ctx")${ctx}%${reset}"
# Rate limited warning
if [[ "$status" == "rate_limited" ]]; then
output+=" ${red}⚠ RATE LIMITED${reset}"
fi
# Staleness
output+="$stale_note"
echo -ne "$output"
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/scripts/refresh-usage.sh --force",
"timeout": 15
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/scripts/refresh-usage.sh",
"timeout": 15
}
]
}
]
},
"statusLine": {
"type": "command",
"command": "bash ~/.claude/scripts/statusline.sh"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment