|
| 1 | +#!/bin/sh |
| 2 | +input=$(cat) |
| 3 | + |
| 4 | +cwd=$(echo "$input" | jq -r '.workspace.current_dir // .cwd') |
| 5 | +model=$(echo "$input" | jq -r '.model.display_name') |
| 6 | +used=$(echo "$input" | jq -r '.context_window.used_percentage // empty') |
| 7 | +cost=$(echo "$input" | jq -r '.cost.total_cost_usd // empty') |
| 8 | + |
| 9 | +# Show only the directory name |
| 10 | +short_cwd=$(basename "$cwd") |
| 11 | + |
| 12 | +# Git branch (skip optional locks) |
| 13 | +branch="" |
| 14 | +if git -C "$cwd" rev-parse --git-dir > /dev/null 2>&1; then |
| 15 | + branch=$(git -C "$cwd" -c core.hooksPath=/dev/null symbolic-ref --short HEAD 2>/dev/null || git -C "$cwd" rev-parse --short HEAD 2>/dev/null) |
| 16 | +fi |
| 17 | + |
| 18 | +# Shorten model name: remove "Claude " prefix |
| 19 | +short_model="${model#Claude }" |
| 20 | + |
| 21 | +# === Line 1: model · dir (branch) · context% · $cost === |
| 22 | +printf "\033[35m%s\033[0m" "$short_model" |
| 23 | + |
| 24 | +if [ -n "$branch" ]; then |
| 25 | + printf " \033[90m·\033[0m \033[34m%s\033[0m \033[90m(\033[38;5;146m%s\033[90m)\033[0m" "$short_cwd" "$branch" |
| 26 | +else |
| 27 | + printf " \033[90m·\033[0m \033[34m%s\033[0m" "$short_cwd" |
| 28 | +fi |
| 29 | + |
| 30 | +if [ -n "$used" ]; then |
| 31 | + pct=$(echo "$used" | tr -d '%') |
| 32 | + if [ "$pct" -ge 90 ] 2>/dev/null; then |
| 33 | + color="\033[31m" |
| 34 | + elif [ "$pct" -ge 70 ] 2>/dev/null; then |
| 35 | + color="\033[33m" |
| 36 | + else |
| 37 | + color="\033[32m" |
| 38 | + fi |
| 39 | + printf " \033[90m·\033[0m \033[90mctx\033[0m ${color}%s%%\033[0m" "$used" |
| 40 | +fi |
| 41 | + |
| 42 | + |
| 43 | +# === Line 2: Usage (5h / 7d) from oauth/usage API === |
| 44 | + |
| 45 | +# Color based on usage percentage |
| 46 | +usage_color() { |
| 47 | + pct=$1 |
| 48 | + if [ "$pct" -ge 90 ] 2>/dev/null; then |
| 49 | + printf "\033[31m" |
| 50 | + elif [ "$pct" -ge 70 ] 2>/dev/null; then |
| 51 | + printf "\033[38;2;255;176;85m" |
| 52 | + elif [ "$pct" -ge 50 ] 2>/dev/null; then |
| 53 | + printf "\033[33m" |
| 54 | + else |
| 55 | + printf "\033[32m" |
| 56 | + fi |
| 57 | +} |
| 58 | + |
| 59 | +# Progress bar: usage_bar <pct> <width> |
| 60 | +usage_bar() { |
| 61 | + pct=$1 |
| 62 | + width=${2:-15} |
| 63 | + filled=$(( pct * width / 100 )) |
| 64 | + empty=$(( width - filled )) |
| 65 | + color=$(usage_color "$pct") |
| 66 | + bar="" |
| 67 | + i=0; while [ "$i" -lt "$filled" ]; do bar="${bar}█"; i=$((i+1)); done |
| 68 | + i=0; while [ "$i" -lt "$empty" ]; do bar="${bar}░"; i=$((i+1)); done |
| 69 | + printf "${color}%s\033[0m" "$bar" |
| 70 | +} |
| 71 | + |
| 72 | +# Format ISO reset time to local HH:MM or "Mon DD, HH:MM" |
| 73 | +format_reset() { |
| 74 | + iso=$1 |
| 75 | + style=$2 |
| 76 | + [ -z "$iso" ] || [ "$iso" = "null" ] && return |
| 77 | + stripped=$(echo "$iso" | sed 's/\.[0-9]*//' | sed 's/+00:00$//' | sed 's/Z$//') |
| 78 | + epoch=$(env TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null) |
| 79 | + [ -z "$epoch" ] && return |
| 80 | + if [ "$style" = "time" ]; then |
| 81 | + date -j -r "$epoch" +"%H:%M" 2>/dev/null |
| 82 | + else |
| 83 | + date -j -r "$epoch" +"%-m/%-d %H:%M" 2>/dev/null |
| 84 | + fi |
| 85 | +} |
| 86 | + |
| 87 | +# Fetch usage data with 60s cache |
| 88 | +cache_file="/tmp/claude-statusline-usage.json" |
| 89 | +cache_max_age=60 |
| 90 | +mkdir -p /tmp 2>/dev/null |
| 91 | +usage_data="" |
| 92 | + |
| 93 | +if [ -f "$cache_file" ]; then |
| 94 | + cache_age=$(( $(date +%s) - $(stat -f %m "$cache_file" 2>/dev/null || echo 0) )) |
| 95 | + if [ "$cache_age" -lt "$cache_max_age" ]; then |
| 96 | + usage_data=$(cat "$cache_file" 2>/dev/null) |
| 97 | + fi |
| 98 | +fi |
| 99 | + |
| 100 | +if [ -z "$usage_data" ]; then |
| 101 | + touch "$cache_file" 2>/dev/null |
| 102 | + token="" |
| 103 | + if command -v security >/dev/null 2>&1; then |
| 104 | + blob=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null) |
| 105 | + [ -n "$blob" ] && token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null) |
| 106 | + fi |
| 107 | + if [ -n "$token" ] && [ "$token" != "null" ]; then |
| 108 | + response=$(curl -s --max-time 10 \ |
| 109 | + -H "Authorization: Bearer $token" \ |
| 110 | + -H "anthropic-beta: oauth-2025-04-20" \ |
| 111 | + "https://api.anthropic.com/api/oauth/usage" 2>/dev/null) |
| 112 | + if [ -n "$response" ] && echo "$response" | jq -e '.five_hour' >/dev/null 2>&1; then |
| 113 | + usage_data="$response" |
| 114 | + echo "$response" > "$cache_file" |
| 115 | + fi |
| 116 | + fi |
| 117 | +fi |
| 118 | + |
| 119 | +if [ -n "$usage_data" ] && echo "$usage_data" | jq -e '.five_hour' >/dev/null 2>&1; then |
| 120 | + five_pct=$(echo "$usage_data" | jq -r '.five_hour.utilization // 0' | awk '{printf "%.0f", $1}') |
| 121 | + five_reset_iso=$(echo "$usage_data" | jq -r '.five_hour.resets_at // empty') |
| 122 | + five_reset=$(format_reset "$five_reset_iso" "time") |
| 123 | + |
| 124 | + seven_pct=$(echo "$usage_data" | jq -r '.seven_day.utilization // 0' | awk '{printf "%.0f", $1}') |
| 125 | + seven_reset_iso=$(echo "$usage_data" | jq -r '.seven_day.resets_at // empty') |
| 126 | + seven_reset=$(format_reset "$seven_reset_iso" "datetime") |
| 127 | + |
| 128 | + printf " \033[90m·\033[0m \033[90msession\033[0m %s%s%%\033[0m" "$(usage_color "$five_pct")" "$five_pct" |
| 129 | + [ -n "$five_reset" ] && printf " \033[90m@%s\033[0m" "$five_reset" |
| 130 | + |
| 131 | + printf " \033[90m·\033[0m \033[90mweekly\033[0m %s%s%%\033[0m" "$(usage_color "$seven_pct")" "$seven_pct" |
| 132 | + [ -n "$seven_reset" ] && printf " \033[90m@%s\033[0m" "$seven_reset" |
| 133 | +fi |
0 commit comments