#!/bin/bash # url: https://code.claude.com/docs/en/statusline # don't forget to make this file executable with: chmod +x statusline.sh # Status line for Claude Code — uses Python for JSON parsing (no jq required) # Configure in ~/.claude/settings.json: # "statusLine": { "type": "command", "command": "~/.claude/statusline.sh" } input=$(cat) # Detect Python: python3 may be a broken Windows Store stub, fall back to python if python3 -c "import sys" 2>/dev/null; then PY=python3 elif python -c "import sys" 2>/dev/null; then PY=python else PY="" fi if [ -n "$PY" ]; then JSON=$(echo "$input" | "$PY" -c " import json, sys from datetime import datetime d = json.load(sys.stdin) m = d.get('model', {}) cw = d.get('context_window', {}) c = d.get('cost', {}) rl = d.get('rate_limits', {}) model = m.get('display_name', 'unknown') cwd = d.get('workspace', {}).get('current_dir', '') or d.get('cwd', '') cost = float(c.get('total_cost_usd', 0) or 0) pct = int(cw.get('used_percentage', 0) or 0) dur = int(c.get('total_duration_ms', 0) or 0) tok_in = int(cw.get('total_input_tokens', 0) or 0) tok_out = int(cw.get('total_output_tokens', 0) or 0) rl_5h = rl.get('five_hour', {}).get('used_percentage') rl_7d = rl.get('seven_day', {}).get('used_percentage') rl_5h_s = f'{rl_5h:.0f}' if rl_5h is not None else '' rl_7d_s = f'{rl_7d:.0f}' if rl_7d is not None else '' def fmt_ts(ts): if ts is None: return '' return datetime.fromtimestamp(int(ts)).strftime('%H:%M %d.%m.%Y') rl_5h_ra = fmt_ts(rl.get('five_hour', {}).get('resets_at')) rl_7d_ra = fmt_ts(rl.get('seven_day', {}).get('resets_at')) print(f'{model}|{cwd}|{cost:.4f}|{pct}|{dur}|{tok_in}|{tok_out}|{rl_5h_s}|{rl_7d_s}|{rl_5h_ra}|{rl_7d_ra}') " 2>/dev/null) IFS='|' read -r MODEL DIR COST PCT DURATION_MS TOK_IN TOK_OUT RL_5H RL_7D RL_5H_RA RL_7D_RA <<< "$JSON" fi MODEL=${MODEL:-unknown} # with the abo model, not cost are there, this is just as info to get a feeling for the cost of the token COST=${COST:-0.0000} # if the "memory" of the sessions tends to go toward 100% then the responsed getting worse because the content of the sesstion will be shorten # then it's wise to use a new session, claude will give a hint about that PCT=${PCT:-0} DURATION_MS=${DURATION_MS:-0} TOK_IN=${TOK_IN:-0} TOK_OUT=${TOK_OUT:-0} # RL_5H / RL_7D stay empty if rate_limits is absent (non-Pro/Max sessions) CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m' if [ "$PCT" -ge 90 ]; then BAR_COLOR="$RED" elif [ "$PCT" -ge 70 ]; then BAR_COLOR="$YELLOW" else BAR_COLOR="$GREEN"; fi FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED)) [ "$FILLED" -gt 0 ] && printf -v FILL "%${FILLED}s" || FILL="" [ "$EMPTY" -gt 0 ] && printf -v PAD "%${EMPTY}s" || PAD="" BAR="${FILL// /█}${PAD// /░}" MINS=$((DURATION_MS / 60000)) SECS=$(( (DURATION_MS % 60000) / 1000 )) BRANCH="" git rev-parse --git-dir > /dev/null 2>&1 \ && BRANCH=" | 🌿 $(git branch --show-current 2>/dev/null)" TOK_TOTAL=$((TOK_IN + TOK_OUT)) COST_FMT=$(printf '$%.4f' "$COST") echo -e "${CYAN}[$MODEL]${RESET}$BRANCH | 🧠 ${BAR_COLOR}${BAR}${RESET} ${PCT}% | 🔢 ${TOK_TOTAL} tok | 💰 ${YELLOW}${COST_FMT}${RESET} | ⏱️ ${MINS}m ${SECS}s" # Rate limits are only available for Pro/Max subscribers after the first API response if [ -n "$RL_5H" ] || [ -n "$RL_7D" ]; then RL_LINE="" if [ -n "$RL_5H" ]; then if [ "$RL_5H" -ge 90 ]; then RL_5H_COLOR="$RED" elif [ "$RL_5H" -ge 70 ]; then RL_5H_COLOR="$YELLOW" else RL_5H_COLOR="$GREEN"; fi RL_5H_RESET="" [ -n "$RL_5H_RA" ] && RL_5H_RESET=" ↺ ${RL_5H_RA}" RL_LINE=" 📊 5h: ${RL_5H_COLOR}${RL_5H}%${RESET}${RL_5H_RESET}" fi if [ -n "$RL_7D" ]; then if [ "$RL_7D" -ge 90 ]; then RL_7D_COLOR="$RED" elif [ "$RL_7D" -ge 70 ]; then RL_7D_COLOR="$YELLOW" else RL_7D_COLOR="$GREEN"; fi RL_7D_RESET="" [ -n "$RL_7D_RA" ] && RL_7D_RESET=" ↺ ${RL_7D_RA}" [ -n "$RL_LINE" ] && RL_LINE="${RL_LINE} |" RL_LINE="${RL_LINE} 7d: ${RL_7D_COLOR}${RL_7D}%${RESET}${RL_7D_RESET}" fi echo -e "Rate Limits: ${RL_LINE}" fi