#!/bin/bash # Claude Code Statusline — real usage data from Anthropic OAuth API # Format (3 lines): # Model │ ctx 33K/1M 3% │ project (branch*) # current ●●●●●○○○○○ 21% ↺ 2hr 15min # weekly ●●●●○○○○○○ 36% ↺ Fri 6:59am set -o pipefail input=$(cat) # Validate JSON if ! echo "$input" | jq -e . >/dev/null 2>&1; then printf "\033[90mLoading...\033[0m" exit 0 fi # --- Extract fields --- model=$(echo "$input" | jq -r '.model.display_name // "?"') workdir=$(echo "$input" | jq -r '.workspace.current_dir // ""') project=$(basename "$workdir" 2>/dev/null) || project="?" # Context window ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // 0') ctx_pct=$(printf "%.0f" "$ctx_pct" 2>/dev/null) || ctx_pct=0 [[ "$ctx_pct" =~ ^[0-9]+$ ]] || ctx_pct=0 ctx_size=$(echo "$input" | jq -r '.context_window.context_window_size // 0') # Calculate used tokens from current_usage ctx_input=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0') ctx_output=$(echo "$input" | jq -r '.context_window.current_usage.output_tokens // 0') ctx_cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0') ctx_cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0') ctx_used=$(( ctx_input + ctx_output + ctx_cache_create + ctx_cache_read )) # Format tokens: 1000000 → "1M", 33000 → "33K" fmt_tokens() { local n=$1 if [ "$n" -ge 1000000 ]; then printf "%dM" $(( n / 1000000 )) elif [ "$n" -ge 1000 ]; then printf "%dK" $(( n / 1000 )) else printf "%d" "$n" fi } ctx_used_fmt=$(fmt_tokens "$ctx_used") ctx_size_fmt=$(fmt_tokens "$ctx_size") # Git branch with dirty marker git_branch="" if [ -n "$workdir" ] && [ -d "$workdir" ]; then git_branch=$(cd "$workdir" 2>/dev/null && git -c gc.auto=0 rev-parse --abbrev-ref HEAD 2>/dev/null) || git_branch="" if [ -n "$git_branch" ]; then git_dirty=$(cd "$workdir" 2>/dev/null && git -c gc.auto=0 status --porcelain 2>/dev/null | head -1) [ -n "$git_dirty" ] && git_branch="${git_branch}*" fi fi # Session cost/duration not available in statusLine JSON input — skip # --- Fetch real usage from Anthropic API (cached 60s) --- USAGE_CACHE="/tmp/claude-statusline-usage.json" USAGE_CACHE_AGE=60 fetch_usage() { local creds_file="$HOME/.claude/.credentials.json" if [ ! -f "$creds_file" ]; then echo '{}' > "$USAGE_CACHE" return fi local token token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null) if [ -z "$token" ]; then echo '{}' > "$USAGE_CACHE" return fi local result result=$(curl -s --max-time 5 \ -H "Authorization: Bearer $token" \ -H "anthropic-beta: oauth-2025-04-20" \ -H "Content-Type: application/json" \ "https://api.anthropic.com/api/oauth/usage" 2>/dev/null) if echo "$result" | jq -e '.five_hour' >/dev/null 2>&1; then echo "$result" > "$USAGE_CACHE" else echo '{}' > "$USAGE_CACHE" fi } # Refresh cache if stale if [ ! -f "$USAGE_CACHE" ]; then fetch_usage else cache_age=$(( $(date +%s) - $(stat -c%Y "$USAGE_CACHE" 2>/dev/null || echo 0) )) if [ "$cache_age" -gt "$USAGE_CACHE_AGE" ]; then fetch_usage fi fi # Parse usage data five_hr_pct=$(jq -r '.five_hour.utilization // 0' "$USAGE_CACHE" 2>/dev/null) five_hr_pct=$(printf "%.0f" "$five_hr_pct" 2>/dev/null) || five_hr_pct=0 five_hr_reset=$(jq -r '.five_hour.resets_at // ""' "$USAGE_CACHE" 2>/dev/null) weekly_pct=$(jq -r '.seven_day.utilization // 0' "$USAGE_CACHE" 2>/dev/null) weekly_pct=$(printf "%.0f" "$weekly_pct" 2>/dev/null) || weekly_pct=0 weekly_reset=$(jq -r '.seven_day.resets_at // ""' "$USAGE_CACHE" 2>/dev/null) # Format reset time as local time "Fri 6:59am" style fmt_reset() { local iso_ts="$1" if [ -z "$iso_ts" ] || [ "$iso_ts" = "null" ]; then printf -- "--" return fi # Convert ISO to epoch, then to local time local epoch epoch=$(date -d "$iso_ts" +%s 2>/dev/null) if [ -n "$epoch" ]; then TZ="Europe/Bucharest" date -d "@$epoch" "+%a %-I:%M%P" 2>/dev/null || printf -- "--" else printf -- "--" fi } # Format time remaining until reset "2hr 15min" fmt_remaining() { local iso_ts="$1" if [ -z "$iso_ts" ] || [ "$iso_ts" = "null" ]; then printf -- "--" return fi local epoch now_ts remaining epoch=$(date -d "$iso_ts" +%s 2>/dev/null) now_ts=$(date +%s) if [ -n "$epoch" ] && [ "$epoch" -gt "$now_ts" ]; then remaining=$(( epoch - now_ts )) local rh rm rh=$(( remaining / 3600 )) rm=$(( (remaining % 3600) / 60 )) if [ "$rh" -gt 24 ]; then local rd rd=$(( rh / 24 )) rh=$(( rh % 24 )) printf "%dd %dh" "$rd" "$rh" elif [ "$rh" -gt 0 ]; then printf "%dhr %dmin" "$rh" "$rm" else printf "%dmin" "$rm" fi else printf "resetting" fi } five_hr_reset_str=$(fmt_reset "$five_hr_reset") five_hr_remain=$(fmt_remaining "$five_hr_reset") weekly_reset_str=$(fmt_reset "$weekly_reset") weekly_remain=$(fmt_remaining "$weekly_reset") # Sonnet weekly sonnet_pct=$(jq -r '.seven_day_sonnet.utilization // 0' "$USAGE_CACHE" 2>/dev/null) sonnet_pct=$(printf "%.0f" "$sonnet_pct" 2>/dev/null) || sonnet_pct=0 # --- Dot bar builder --- DOT_TOTAL=10 make_dots() { local filled=$1 total=$2 dots="" i for (( i=0; i/dev/null) sonnet_reset_str=$(fmt_reset "$sonnet_reset") # Build line 2 if [ "$sonnet_reset_str" != "$weekly_reset_str" ] && [ "$sonnet_reset_str" != "--" ]; then line2=$(printf "current %s %d%% ↺ %s │ weekly %s %d%% ↺ %s │ sonnet %d%% ↺ %s" \ "$current_dots" "$five_hr_pct" "$five_hr_remain" \ "$weekly_dots" "$weekly_pct" "$weekly_reset_str" \ "$sonnet_pct" "$sonnet_reset_str") else line2=$(printf "current %s %d%% ↺ %s │ weekly %s %d%% ↺ %s │ sonnet %d%%" \ "$current_dots" "$five_hr_pct" "$five_hr_remain" \ "$weekly_dots" "$weekly_pct" "$weekly_reset_str" \ "$sonnet_pct") fi # --- Output --- printf "%s\n%s" "$line1" "$line2"