Last active
March 6, 2026 14:56
-
-
Save jeremyronking/7dc1978531b36a4d3741d2faef553a8e to your computer and use it in GitHub Desktop.
Claude Code Status Line - Enhanced statusline showing model, mode, context usage, token counts, session cost, API limits with reset times, and git branch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # ============================================================================== | |
| # Claude Code Status Line | |
| # ============================================================================== | |
| # This script creates a rich status line for Claude Code showing: | |
| # - Current model being used | |
| # - Mode (if in plan/edit mode) | |
| # - Context window usage percentage | |
| # - Token usage (input/output totals for the session) | |
| # - Session cost in USD | |
| # - API usage limits (5-hour and 7-day windows) with time until reset | |
| # - Current git branch | |
| # | |
| # Usage: Configure this script in your Claude Code settings as a statusline hook | |
| # Requirements: jq, curl, git (optional) | |
| # | |
| # The script expects JSON input from Claude Code via stdin | |
| # ============================================================================== | |
| input=$(cat) | |
| # ============================================================================== | |
| # Extract Model Information | |
| # ============================================================================== | |
| MODEL=$(echo "$input" | jq -r '.model.display_name // "Unknown"') | |
| # ============================================================================== | |
| # API Usage Limits Fetching (with caching) | |
| # ============================================================================== | |
| # Fetches usage data from Anthropic API and caches it to avoid rate limiting | |
| # Cache expires after CACHE_MAX_AGE seconds; backs off to BACKOFF_AGE on failures | |
| CACHE_FILE="/tmp/claude-usage-cache" | |
| CACHE_MAX_AGE=120 | |
| BACKOFF_FILE="/tmp/claude-usage-backoff" | |
| BACKOFF_AGE=300 | |
| get_usage_limits() { | |
| # Returns cached usage data if fresh, otherwise fetches new data | |
| # This prevents hammering the API on every statusline refresh | |
| local now cache_age max_age | |
| now=$(date +%s) | |
| # If backing off from a previous failure, use the longer backoff TTL | |
| if [ -f "$BACKOFF_FILE" ]; then | |
| max_age=$BACKOFF_AGE | |
| else | |
| max_age=$CACHE_MAX_AGE | |
| fi | |
| if [ -f "$CACHE_FILE" ]; then | |
| cache_age=$((now - $(stat -f %m "$CACHE_FILE" 2>/dev/null || echo 0))) | |
| if [ "$cache_age" -lt "$max_age" ]; then | |
| cat "$CACHE_FILE" | |
| return | |
| fi | |
| fi | |
| # Retrieve OAuth credentials from macOS Keychain | |
| local token | |
| token=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | jq -r '.claudeAiOauth.accessToken // empty') | |
| if [ -z "$token" ]; then | |
| # No credentials - serve stale cache if available | |
| [ -f "$CACHE_FILE" ] && cat "$CACHE_FILE" | |
| return | |
| fi | |
| # Fetch fresh data from Anthropic API (capture HTTP status code) | |
| local response http_code data | |
| response=$(curl -s --max-time 2 -w "\n%{http_code}" \ | |
| -H "Authorization: Bearer $token" \ | |
| -H "anthropic-beta: oauth-2025-04-20" \ | |
| https://api.anthropic.com/api/oauth/usage 2>/dev/null) | |
| http_code=$(echo "$response" | tail -1) | |
| data=$(echo "$response" | sed '$d') | |
| if [ "$http_code" = "200" ] && [ -n "$data" ]; then | |
| # Success - update cache and clear backoff | |
| echo "$data" > "$CACHE_FILE" | |
| rm -f "$BACKOFF_FILE" | |
| echo "$data" | |
| else | |
| # Failure (429, timeout, etc.) - enter backoff and serve stale cache | |
| touch "$BACKOFF_FILE" | |
| if [ -f "$CACHE_FILE" ]; then | |
| touch "$CACHE_FILE" | |
| cat "$CACHE_FILE" | |
| fi | |
| fi | |
| } | |
| # ============================================================================== | |
| # Time Formatting Helper | |
| # ============================================================================== | |
| # Converts ISO 8601 timestamp to human-readable relative time | |
| # Examples: "2h30m", "3d5h", "45m", "now" | |
| format_time_until() { | |
| local reset_at="$1" | |
| if [ -z "$reset_at" ] || [ "$reset_at" = "null" ]; then | |
| echo "" | |
| return | |
| fi | |
| # Parse ISO timestamp (e.g., "2025-12-31T23:59:59.000Z") and convert to epoch | |
| # Note: Uses macOS 'date -j' format - may need adjustment for GNU date | |
| local reset_epoch now_epoch diff | |
| reset_epoch=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "${reset_at%%.*}" "+%s" 2>/dev/null) | |
| if [ -z "$reset_epoch" ]; then | |
| echo "" | |
| return | |
| fi | |
| now_epoch=$(date +%s) | |
| diff=$((reset_epoch - now_epoch)) | |
| if [ "$diff" -le 0 ]; then | |
| echo "now" | |
| return | |
| fi | |
| # Calculate days, hours, and minutes | |
| local days hours mins | |
| days=$((diff / 86400)) | |
| hours=$(((diff % 86400) / 3600)) | |
| mins=$(((diff % 3600) / 60)) | |
| # Format output based on magnitude (show top 2 units) | |
| if [ "$days" -gt 0 ]; then | |
| echo "${days}d${hours}h" | |
| elif [ "$hours" -gt 0 ]; then | |
| echo "${hours}h${mins}m" | |
| else | |
| echo "${mins}m" | |
| fi | |
| } | |
| # Fetch usage limits from API | |
| USAGE_LIMITS=$(get_usage_limits) | |
| # ============================================================================== | |
| # Extract Mode Information | |
| # ============================================================================== | |
| # Mode shows if Claude is in a special state (e.g., "plan", "edit") | |
| MODE=$(echo "$input" | jq -r '.mode // empty') | |
| if [ -z "$MODE" ]; then | |
| MODE_DISPLAY="" | |
| else | |
| MODE_DISPLAY=" | ${MODE} |" | |
| fi | |
| # ============================================================================== | |
| # Context Window Usage Calculation | |
| # ============================================================================== | |
| # Calculate percentage of context window used (including cache tokens) | |
| CONTEXT_SIZE=$(echo "$input" | jq -r '.context_window.context_window_size // 200000') | |
| USAGE=$(echo "$input" | jq '.context_window.current_usage // null') | |
| if [ "$USAGE" != "null" ]; then | |
| # Sum all token types: regular input, cache creation, and cache reads | |
| CURRENT=$(echo "$USAGE" | jq '.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens') | |
| PERCENT=$((CURRENT * 100 / CONTEXT_SIZE)) | |
| else | |
| PERCENT=0 | |
| fi | |
| # ============================================================================== | |
| # Token Usage & Cost | |
| # ============================================================================== | |
| # Extract cumulative session token counts and cost | |
| TOTAL_IN=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0') | |
| TOTAL_OUT=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0') | |
| COST_USD=$(echo "$input" | jq -r '.cost.total_cost_usd // 0') | |
| # Format token counts as human-readable (e.g., 15234 → "15.2k", 1234567 → "1.2M") | |
| format_tokens() { | |
| local count=$1 | |
| if [ "$count" -ge 1000000 ]; then | |
| printf "%.1fM" "$(echo "$count / 1000000" | bc -l)" | |
| elif [ "$count" -ge 1000 ]; then | |
| printf "%.1fk" "$(echo "$count / 1000" | bc -l)" | |
| else | |
| echo "$count" | |
| fi | |
| } | |
| IN_DISPLAY=$(format_tokens "$TOTAL_IN") | |
| OUT_DISPLAY=$(format_tokens "$TOTAL_OUT") | |
| # Format cost (show 3 decimal places, or 2 if >= $1) | |
| if [ "$(echo "$COST_USD >= 1" | bc -l 2>/dev/null)" = "1" ]; then | |
| COST_DISPLAY=$(printf "\$%.2f" "$COST_USD") | |
| else | |
| COST_DISPLAY=$(printf "\$%.3f" "$COST_USD") | |
| fi | |
| # ============================================================================== | |
| # Color Coding Helper | |
| # ============================================================================== | |
| # Returns ANSI color code based on usage percentage | |
| # Green (0-59%), Yellow (60-79%), Red (80-100%) | |
| get_color() { | |
| local pct=$1 | |
| if [ "$pct" -ge 80 ]; then | |
| echo "\033[31m" # Red | |
| elif [ "$pct" -ge 60 ]; then | |
| echo "\033[33m" # Yellow | |
| else | |
| echo "\033[32m" # Green | |
| fi | |
| } | |
| # ============================================================================== | |
| # ANSI Color Codes | |
| # ============================================================================== | |
| RESET="\033[0m" | |
| CYAN="\033[36m" | |
| MAGENTA="\033[35m" | |
| WHITE="\033[97m" | |
| # Apply color to context percentage | |
| CTX_COLOR=$(get_color "$PERCENT") | |
| # ============================================================================== | |
| # Parse API Usage Limits | |
| # ============================================================================== | |
| # Anthropic enforces two rate limit windows: | |
| # - 5-hour rolling window | |
| # - 7-day rolling window | |
| # This section displays both usage percentages with time until reset | |
| if [ -n "$USAGE_LIMITS" ]; then | |
| # Extract utilization percentages (strip decimal places) | |
| FIVE_HOUR=$(echo "$USAGE_LIMITS" | jq -r '.five_hour.utilization // empty' | cut -d. -f1) | |
| SEVEN_DAY=$(echo "$USAGE_LIMITS" | jq -r '.seven_day.utilization // empty' | cut -d. -f1) | |
| FIVE_RESET=$(echo "$USAGE_LIMITS" | jq -r '.five_hour.resets_at // empty') | |
| SEVEN_RESET=$(echo "$USAGE_LIMITS" | jq -r '.seven_day.resets_at // empty') | |
| if [ -n "$FIVE_HOUR" ] && [ -n "$SEVEN_DAY" ]; then | |
| # Apply color coding to each limit | |
| FIVE_COLOR=$(get_color "$FIVE_HOUR") | |
| SEVEN_COLOR=$(get_color "$SEVEN_DAY") | |
| # Convert reset timestamps to human-readable format | |
| FIVE_TIME=$(format_time_until "$FIVE_RESET") | |
| SEVEN_TIME=$(format_time_until "$SEVEN_RESET") | |
| # Build display strings with colored percentages and reset times | |
| FIVE_DISPLAY="5h: ${FIVE_COLOR}${FIVE_HOUR}%${RESET}" | |
| [ -n "$FIVE_TIME" ] && FIVE_DISPLAY="${FIVE_DISPLAY} → ${MAGENTA}${FIVE_TIME}${RESET}" | |
| SEVEN_DISPLAY="7d: ${SEVEN_COLOR}${SEVEN_DAY}%${RESET}" | |
| [ -n "$SEVEN_TIME" ] && SEVEN_DISPLAY="${SEVEN_DISPLAY} → ${MAGENTA}${SEVEN_TIME}${RESET}" | |
| LIMITS_DISPLAY=" | ${FIVE_DISPLAY} | ${SEVEN_DISPLAY}" | |
| else | |
| LIMITS_DISPLAY="" | |
| fi | |
| else | |
| LIMITS_DISPLAY="" | |
| fi | |
| # ============================================================================== | |
| # Git Branch Detection | |
| # ============================================================================== | |
| # Shows current git branch if working directory is in a git repository | |
| GIT_BRANCH="" | |
| if git rev-parse --git-dir > /dev/null 2>&1; then | |
| BRANCH=$(git branch --show-current 2>/dev/null) | |
| if [ -n "$BRANCH" ]; then | |
| GIT_BRANCH=" | ${CYAN}${BRANCH}${RESET}" | |
| fi | |
| fi | |
| # ============================================================================== | |
| # Final Output | |
| # ============================================================================== | |
| # Assemble all components into final statusline | |
| # Format: [Model] | mode | Context: XX% | In: XXk Out: XXk | $X.XX | 5h: XX% → Xh | 7d: XX% → Xd | branch | |
| YELLOW="\033[33m" | |
| GREEN="\033[32m" | |
| TOKENS_DISPLAY=" | ${WHITE}In:${RESET} ${CYAN}${IN_DISPLAY}${RESET} ${WHITE}Out:${RESET} ${CYAN}${OUT_DISPLAY}${RESET} | ${GREEN}${COST_DISPLAY}${RESET}" | |
| echo -e "[${MODEL}]${MODE_DISPLAY} ${WHITE}Context:${RESET} ${CTX_COLOR}${PERCENT}%${RESET}${TOKENS_DISPLAY}${LIMITS_DISPLAY}${GIT_BRANCH}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment