|
#!/bin/bash |
|
# Row 1: Model | dir@branch | tokens (%used) | effort |
|
# Row 2: 5h %@reset | 7d %@reset | extra used/limit |
|
|
|
set -f # disable globbing |
|
|
|
input=$(cat) |
|
|
|
if [ -z "$input" ]; then |
|
printf "Claude" |
|
exit 0 |
|
fi |
|
|
|
# ANSI colors matching oh-my-posh theme |
|
blue='\033[38;2;0;153;255m' |
|
orange='\033[38;2;255;176;85m' |
|
green='\033[38;2;0;160;0m' |
|
cyan='\033[38;2;46;149;153m' |
|
red='\033[38;2;255;85;85m' |
|
yellow='\033[38;2;230;200;0m' |
|
white='\033[38;2;220;220;220m' |
|
dim='\033[2m' |
|
reset='\033[0m' |
|
|
|
# Format token counts (e.g., 50k / 200k) |
|
format_tokens() { |
|
local num=$1 |
|
if [ "$num" -ge 1000000 ]; then |
|
awk "BEGIN {printf \"%.1fm\", $num / 1000000}" |
|
elif [ "$num" -ge 1000 ]; then |
|
awk "BEGIN {printf \"%.0fk\", $num / 1000}" |
|
else |
|
printf "%d" "$num" |
|
fi |
|
} |
|
|
|
# Format number with commas (e.g., 134,938) |
|
format_commas() { |
|
printf "%'d" "$1" |
|
} |
|
|
|
# Return color escape based on usage percentage |
|
# Usage: usage_color <pct> |
|
usage_color() { |
|
local pct=$1 |
|
if [ "$pct" -ge 90 ]; then echo "$red" |
|
elif [ "$pct" -ge 70 ]; then echo "$orange" |
|
elif [ "$pct" -ge 50 ]; then echo "$yellow" |
|
else echo "$green" |
|
fi |
|
} |
|
|
|
# ===== Extract data from JSON ===== |
|
model_name=$(echo "$input" | jq -r '.model.display_name // "Claude"') |
|
|
|
# Context window |
|
size=$(echo "$input" | jq -r '.context_window.context_window_size // 200000') |
|
[ "$size" -eq 0 ] 2>/dev/null && size=200000 |
|
|
|
# Token usage |
|
input_tokens=$(echo "$input" | jq -r '.context_window.current_usage.input_tokens // 0') |
|
cache_create=$(echo "$input" | jq -r '.context_window.current_usage.cache_creation_input_tokens // 0') |
|
cache_read=$(echo "$input" | jq -r '.context_window.current_usage.cache_read_input_tokens // 0') |
|
current=$(( input_tokens + cache_create + cache_read )) |
|
|
|
used_tokens=$(format_tokens $current) |
|
total_tokens=$(format_tokens $size) |
|
|
|
if [ "$size" -gt 0 ]; then |
|
pct_used=$(( current * 100 / size )) |
|
else |
|
pct_used=0 |
|
fi |
|
pct_remain=$(( 100 - pct_used )) |
|
|
|
used_comma=$(format_commas $current) |
|
remain_comma=$(format_commas $(( size - current ))) |
|
|
|
# Reasoning effort — live resolved value from stdin (absent if model lacks effort support) |
|
settings_path="$HOME/.claude/settings.json" |
|
effort_level=$(echo "$input" | jq -r '.effort.level // empty') |
|
if [ -z "$effort_level" ]; then |
|
# fallback for older CC / models without the field |
|
if [ -n "$CLAUDE_CODE_EFFORT_LEVEL" ]; then |
|
effort_level="$CLAUDE_CODE_EFFORT_LEVEL" |
|
elif [ -f "$settings_path" ]; then |
|
effort_level=$(jq -r '.effortLevel // empty' "$settings_path" 2>/dev/null) |
|
fi |
|
fi |
|
|
|
# ===== Build row 1: model | dir | tokens | effort ===== |
|
out="" |
|
out+="${blue}${model_name}${reset}" |
|
|
|
# Current working directory |
|
cwd=$(echo "$input" | jq -r '.cwd // empty') |
|
if [ -n "$cwd" ]; then |
|
display_dir="${cwd##*/}" |
|
git_branch=$(git -C "${cwd}" rev-parse --abbrev-ref HEAD 2>/dev/null) |
|
out+=" ${dim}|${reset} " |
|
out+="${cyan}${display_dir}${reset}" |
|
if [ -n "$git_branch" ]; then |
|
out+="${dim}@${reset}${green}${git_branch}${reset}" |
|
git_stat=$(git -C "${cwd}" diff --numstat 2>/dev/null | awk '{a+=$1; d+=$2} END {if (a+d>0) printf "+%d -%d", a, d}') |
|
[ -n "$git_stat" ] && out+=" ${dim}(${reset}${green}${git_stat%% *}${reset} ${red}${git_stat##* }${reset}${dim})${reset}" |
|
fi |
|
fi |
|
|
|
out+=" ${dim}|${reset} " |
|
out+="${orange}${used_tokens}/${total_tokens}${reset} ${dim}(${reset}${green}${pct_used}%${reset}${dim})${reset}" |
|
if [ -n "$effort_level" ]; then |
|
out+=" ${dim}|${reset} " |
|
out+="effort: " |
|
case "$effort_level" in |
|
low) out+="${dim}low${reset}" ;; |
|
medium) out+="${orange}med${reset}" ;; |
|
high) out+="${green}high${reset}" ;; |
|
xhigh) out+="${green}xhigh${reset}" ;; |
|
max) out+="${green}max${reset}" ;; |
|
*) out+="${green}${effort_level}${reset}" ;; |
|
esac |
|
fi |
|
|
|
# ===== Cross-platform OAuth token resolution ===== |
|
# Tries credential sources in order: env var → macOS Keychain → Linux creds file → GNOME Keyring |
|
get_oauth_token() { |
|
local token="" |
|
|
|
# 1. Explicit env var override |
|
if [ -n "$CLAUDE_CODE_OAUTH_TOKEN" ]; then |
|
echo "$CLAUDE_CODE_OAUTH_TOKEN" |
|
return 0 |
|
fi |
|
|
|
# 2. macOS Keychain |
|
if command -v security >/dev/null 2>&1; then |
|
local blob |
|
blob=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null) |
|
if [ -n "$blob" ]; then |
|
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null) |
|
if [ -n "$token" ] && [ "$token" != "null" ]; then |
|
echo "$token" |
|
return 0 |
|
fi |
|
fi |
|
fi |
|
|
|
# 3. Linux credentials file |
|
local creds_file="${HOME}/.claude/.credentials.json" |
|
if [ -f "$creds_file" ]; then |
|
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$creds_file" 2>/dev/null) |
|
if [ -n "$token" ] && [ "$token" != "null" ]; then |
|
echo "$token" |
|
return 0 |
|
fi |
|
fi |
|
|
|
# 4. GNOME Keyring via secret-tool |
|
if command -v secret-tool >/dev/null 2>&1; then |
|
local blob |
|
blob=$(timeout 2 secret-tool lookup service "Claude Code-credentials" 2>/dev/null) |
|
if [ -n "$blob" ]; then |
|
token=$(echo "$blob" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null) |
|
if [ -n "$token" ] && [ "$token" != "null" ]; then |
|
echo "$token" |
|
return 0 |
|
fi |
|
fi |
|
fi |
|
|
|
echo "" |
|
} |
|
|
|
# ===== Extra-usage credits (OAuth usage API, cached) ===== |
|
# 5h/7d windows come from the stdin payload below; this fetch exists only for |
|
# extra_usage credits, which the statusLine JSON does not expose. |
|
cache_file="/tmp/claude/statusline-usage-cache.json" |
|
cache_max_age=60 # seconds between API calls |
|
mkdir -p /tmp/claude |
|
|
|
needs_refresh=true |
|
usage_data="" |
|
|
|
# Check cache |
|
if [ -f "$cache_file" ]; then |
|
cache_mtime=$(stat -c %Y "$cache_file" 2>/dev/null || stat -f %m "$cache_file" 2>/dev/null) |
|
now=$(date +%s) |
|
cache_age=$(( now - cache_mtime )) |
|
if [ "$cache_age" -lt "$cache_max_age" ]; then |
|
needs_refresh=false |
|
usage_data=$(cat "$cache_file" 2>/dev/null) |
|
fi |
|
fi |
|
|
|
# Fetch fresh data if cache is stale |
|
if $needs_refresh; then |
|
token=$(get_oauth_token) |
|
if [ -n "$token" ] && [ "$token" != "null" ]; then |
|
response=$(curl -s --max-time 10 \ |
|
-H "Accept: application/json" \ |
|
-H "Content-Type: application/json" \ |
|
-H "Authorization: Bearer $token" \ |
|
-H "anthropic-beta: oauth-2025-04-20" \ |
|
-H "User-Agent: claude-code/2.1.34" \ |
|
"https://api.anthropic.com/api/oauth/usage" 2>/dev/null) |
|
if [ -n "$response" ] && echo "$response" | jq . >/dev/null 2>&1; then |
|
usage_data="$response" |
|
echo "$response" > "$cache_file" |
|
fi |
|
fi |
|
# Fall back to stale cache |
|
if [ -z "$usage_data" ] && [ -f "$cache_file" ]; then |
|
usage_data=$(cat "$cache_file" 2>/dev/null) |
|
fi |
|
fi |
|
|
|
# Format Unix epoch seconds to compact local time (rate_limits.*.resets_at is epoch) |
|
format_epoch_time() { |
|
local epoch="$1" |
|
local style="$2" |
|
{ [ -z "$epoch" ] || [ "$epoch" = "null" ]; } && return |
|
|
|
case "$style" in |
|
time) |
|
date -d "@$epoch" +"%H:%M" 2>/dev/null || date -j -r "$epoch" +"%H:%M" 2>/dev/null |
|
;; |
|
datetime) |
|
date -d "@$epoch" +"%b %-d, %H:%M" 2>/dev/null || date -j -r "$epoch" +"%b %-d, %H:%M" 2>/dev/null |
|
;; |
|
*) |
|
date -d "@$epoch" +"%b %-d" 2>/dev/null || date -j -r "$epoch" +"%b %-d" 2>/dev/null |
|
;; |
|
esac |
|
} |
|
|
|
sep=" ${dim}|${reset} " |
|
|
|
# Second row: usage / rate-limit cluster, kept off the main row so it stays readable. |
|
# Each segment is optional; prepend the separator only when the row already has content. |
|
line2="" |
|
|
|
# ---- 5-hour & 7-day rate limits (from stdin payload; Pro/Max only, windows independently absent) ---- |
|
five_hour_raw=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty') |
|
if [ -n "$five_hour_raw" ]; then |
|
five_hour_pct=$(echo "$five_hour_raw" | awk '{printf "%.0f", $1}') |
|
five_hour_reset=$(format_epoch_time "$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')" "time") |
|
five_hour_color=$(usage_color "$five_hour_pct") |
|
[ -n "$line2" ] && line2+="$sep" |
|
line2+="${white}5h${reset} ${five_hour_color}${five_hour_pct}%${reset}" |
|
[ -n "$five_hour_reset" ] && line2+=" ${dim}@${five_hour_reset}${reset}" |
|
fi |
|
|
|
seven_day_raw=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty') |
|
if [ -n "$seven_day_raw" ]; then |
|
seven_day_pct=$(echo "$seven_day_raw" | awk '{printf "%.0f", $1}') |
|
seven_day_reset=$(format_epoch_time "$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')" "datetime") |
|
seven_day_color=$(usage_color "$seven_day_pct") |
|
[ -n "$line2" ] && line2+="$sep" |
|
line2+="${white}7d${reset} ${seven_day_color}${seven_day_pct}%${reset}" |
|
[ -n "$seven_day_reset" ] && line2+=" ${dim}@${seven_day_reset}${reset}" |
|
fi |
|
|
|
# ---- Extra-usage credits (only exposed by the OAuth usage API, not the stdin payload) ---- |
|
if [ -n "$usage_data" ] && echo "$usage_data" | jq -e . >/dev/null 2>&1; then |
|
extra_enabled=$(echo "$usage_data" | jq -r '.extra_usage.is_enabled // false') |
|
if [ "$extra_enabled" = "true" ]; then |
|
extra_pct=$(echo "$usage_data" | jq -r '.extra_usage.utilization // 0' | awk '{printf "%.0f", $1}') |
|
extra_used=$(echo "$usage_data" | jq -r '.extra_usage.used_credits // 0' | LC_NUMERIC=C awk '{printf "%.2f", $1/100}') |
|
extra_limit=$(echo "$usage_data" | jq -r '.extra_usage.monthly_limit // 0' | LC_NUMERIC=C awk '{printf "%.2f", $1/100}') |
|
[ -n "$line2" ] && line2+="$sep" |
|
if [ -n "$extra_used" ] && [ -n "$extra_limit" ]; then |
|
extra_color=$(usage_color "$extra_pct") |
|
line2+="${white}extra${reset} ${extra_color}${extra_used}/${extra_limit}${reset}" |
|
else |
|
line2+="${white}extra${reset} ${green}enabled${reset}" |
|
fi |
|
fi |
|
fi |
|
|
|
# Output: row 1 = identity/context/effort; row 2 = usage cluster (only if present) |
|
printf "%b" "$out" |
|
[ -n "$line2" ] && printf "\n%b" "$line2" |
|
|
|
exit 0 |