Skip to content

Instantly share code, notes, and snippets.

@captainpete
Last active May 12, 2026 01:16
Show Gist options
  • Select an option

  • Save captainpete/afccec338c4916f3a837f41450d268c2 to your computer and use it in GitHub Desktop.

Select an option

Save captainpete/afccec338c4916f3a837f41450d268c2 to your computer and use it in GitHub Desktop.
ClaudeWatch statusline for Claude Code — model, cwd@branch, token usage, effort level, 5h/7d rate-limit bars with linear-pace deltas, Claude Code service status. Derived from daniel3303/ClaudeCodeStatusLine (MIT).
#!/bin/bash
# ClaudeWatch statusline for Claude Code.
#
# Derived from ClaudeCodeStatusLine by @daniel3303:
# https://github.com/daniel3303/ClaudeCodeStatusLine
# The upstream project is MIT-licensed; see its LICENSE file for the
# original copyright and permission notice. This derivative is
# redistributed under the same MIT terms.
#
# Modifications for ClaudeWatch:
# - Write resolved usage data to `$CLAUDE_CONFIG_DIR/claudewatch-usage.json`
# on every render so the ClaudeWatch menu-bar app can consume it.
# - Removed the upstream self-update check (version comparison against
# the upstream GitHub releases and the "Update available" line).
#
# Output (single line): Model | tokens | %used | %remain | think |
# 5h bar @reset | 7d bar @reset | extra
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
}
# Compute pace delta in seconds vs. the linear-target burn rate.
# Positive = under pace (conservative); negative = ahead (aggressive).
# Args: pct (may be float), reset_epoch, window_secs
compute_pace_delta() {
local pct=$1 reset_epoch=$2 window_secs=$3
{ [ -z "$pct" ] || [ -z "$reset_epoch" ] || [ "$reset_epoch" = "null" ]; } && return 1
local now
now=$(date +%s)
awk -v now="$now" -v reset="$reset_epoch" -v p="$pct" -v w="$window_secs" \
'BEGIN {
elapsed = now - (reset - w);
if (elapsed < 0) elapsed = 0;
if (elapsed > w) elapsed = w;
printf "%d", elapsed - (p * w / 100);
}'
}
# Format pace seconds as ±H:MM (for 5h window; max ~5:00)
format_pace_short() {
local secs=$1 sign="+"
if [ "$secs" -lt 0 ]; then sign="-"; secs=$(( -secs )); fi
printf "%s%d:%02d" "$sign" $(( secs / 3600 )) $(( (secs % 3600) / 60 ))
}
# Format pace seconds as ±Xd HH:MM (or ±H:MM when <1 day) for 7d window
format_pace_long() {
local secs=$1 sign="+"
if [ "$secs" -lt 0 ]; then sign="-"; secs=$(( -secs )); fi
local d=$(( secs / 86400 ))
local h=$(( (secs % 86400) / 3600 ))
local m=$(( (secs % 3600) / 60 ))
if [ "$d" -gt 0 ]; then
printf "%s%dd %02d:%02d" "$sign" "$d" "$h" "$m"
else
printf "%s%d:%02d" "$sign" "$h" "$m"
fi
}
# Format seconds-until-reset as compact relative time.
# short: "Hh Mm" or "Mm" (5h window, minute resolution)
# long: "Dd Hh" or "Hh" (7d window, hour resolution)
format_time_until() {
local secs=$1 style=$2
[ -z "$secs" ] && return
[ "$secs" -lt 0 ] && secs=0
local d=$(( secs / 86400 ))
local h=$(( (secs % 86400) / 3600 ))
local m=$(( (secs % 3600) / 60 ))
case "$style" in
short)
if [ "$h" -gt 0 ]; then printf "%dh %dm" "$h" "$m"
else printf "%dm" "$m"
fi
;;
long)
if [ "$d" -gt 0 ]; then printf "%dd %dh" "$d" "$h"
else printf "%dh" "$h"
fi
;;
esac
}
# Color for pace delta: green when under pace, orange when ahead.
pace_color() {
if [ "$1" -lt 0 ]; then echo "$orange"; else echo "$green"; fi
}
# Resolve config directory: CLAUDE_CONFIG_DIR (set by alias) or default ~/.claude
claude_config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
# ===== 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 )))
# Check reasoning effort
settings_path="$claude_config_dir/settings.json"
effort_level="medium"
if [ -n "$CLAUDE_CODE_EFFORT_LEVEL" ]; then
effort_level="$CLAUDE_CODE_EFFORT_LEVEL"
elif [ -f "$settings_path" ]; then
effort_val=$(jq -r '.effortLevel // empty' "$settings_path" 2>/dev/null)
[ -n "$effort_val" ] && effort_level="$effort_val"
fi
# ===== Build single-line output =====
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}"
out+=" ${dim}|${reset} "
out+="effort: "
case "$effort_level" in
low) out+="${dim}${effort_level}${reset}" ;;
medium) out+="${orange}med${reset}" ;;
max) out+="${red}${effort_level}${reset}" ;;
*) out+="${green}${effort_level}${reset}" ;;
esac
# ===== Cross-platform OAuth token resolution (from statusline.sh) =====
# 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 (Claude Code appends a SHA256 hash of CLAUDE_CONFIG_DIR to the service name)
if command -v security >/dev/null 2>&1; then
local keychain_svc="Claude Code-credentials"
if [ -n "$CLAUDE_CONFIG_DIR" ]; then
local dir_hash
dir_hash=$(echo -n "$CLAUDE_CONFIG_DIR" | shasum -a 256 | cut -c1-8)
keychain_svc="Claude Code-credentials-${dir_hash}"
fi
local blob
blob=$(security find-generic-password -s "$keychain_svc" -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="${claude_config_dir}/.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 ""
}
# ===== LINE 2 & 3: Usage limits with progress bars =====
# First, try to use rate_limits data provided directly by Claude Code in the JSON input.
# This is the most reliable source — no OAuth token or API call required.
builtin_five_hour_pct=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
builtin_five_hour_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
builtin_seven_day_pct=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
builtin_seven_day_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
use_builtin=false
if [ -n "$builtin_five_hour_pct" ] || [ -n "$builtin_seven_day_pct" ]; then
use_builtin=true
fi
# Fall back to cached API call only when Claude Code didn't supply rate_limits data
claude_config_dir_hash=$(echo -n "$claude_config_dir" | shasum -a 256 2>/dev/null || echo -n "$claude_config_dir" | sha256sum 2>/dev/null)
claude_config_dir_hash=$(echo "$claude_config_dir_hash" | cut -c1-8)
cache_file="/tmp/claude/statusline-usage-cache-${claude_config_dir_hash}.json"
cache_max_age=60 # seconds between API calls
mkdir -p /tmp/claude
needs_refresh=true
usage_data=""
# Check cache — shared across all Claude Code instances to avoid rate limits.
# We always read the cached API response (even when use_builtin=true) because
# extra_usage only comes from the API, not from Claude Code's rate_limits input.
if [ -f "$cache_file" ] && [ -s "$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
fi
usage_data=$(cat "$cache_file" 2>/dev/null)
fi
# When rate_limits are provided inline by Claude Code we don't need a fresh
# fetch just for 5h/7d, but we do refresh so extra_usage stays current.
if $needs_refresh; then
touch "$cache_file" # stampede lock: prevent parallel panes from fetching simultaneously
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)
# Only cache valid usage responses (not error/rate-limit JSON)
if [ -n "$response" ] && echo "$response" | jq -e '.five_hour' >/dev/null 2>&1; then
usage_data="$response"
echo "$response" > "$cache_file"
fi
fi
fi
# Cross-platform ISO to epoch conversion
# Converts ISO 8601 timestamp (e.g. "2025-06-15T12:30:00Z" or "2025-06-15T12:30:00.123+00:00") to epoch seconds.
# Properly handles UTC timestamps and converts to local time.
iso_to_epoch() {
local iso_str="$1"
# Try GNU date first (Linux) — handles ISO 8601 format automatically
local epoch
epoch=$(date -d "${iso_str}" +%s 2>/dev/null)
if [ -n "$epoch" ]; then
echo "$epoch"
return 0
fi
# BSD date (macOS) - handle various ISO 8601 formats
local stripped="${iso_str%%.*}" # Remove fractional seconds (.123456)
stripped="${stripped%%Z}" # Remove trailing Z
stripped="${stripped%%+*}" # Remove timezone offset (+00:00)
stripped="${stripped%%-[0-9][0-9]:[0-9][0-9]}" # Remove negative timezone offset
# Check if timestamp is UTC (has Z or +00:00 or -00:00)
if [[ "$iso_str" == *"Z"* ]] || [[ "$iso_str" == *"+00:00"* ]] || [[ "$iso_str" == *"-00:00"* ]]; then
# For UTC timestamps, parse with timezone set to UTC
epoch=$(env TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
else
epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "$stripped" +%s 2>/dev/null)
fi
if [ -n "$epoch" ]; then
echo "$epoch"
return 0
fi
return 1
}
# Format ISO reset time to compact local time
# Usage: format_reset_time <iso_string> <style: time|datetime|date>
format_reset_time() {
local iso_str="$1"
local style="$2"
{ [ -z "$iso_str" ] || [ "$iso_str" = "null" ]; } && return
# Parse ISO datetime and convert to local time (cross-platform)
local epoch
epoch=$(iso_to_epoch "$iso_str")
[ -z "$epoch" ] && return
# Format based on style
# Try GNU date first (Linux), then BSD date (macOS)
# Previous implementation piped BSD date through sed/tr, which always returned
# exit code 0 from the last pipe stage, preventing the GNU date fallback from
# ever executing on Linux.
local formatted=""
case "$style" in
time)
formatted=$(date -d "@$epoch" +"%H:%M" 2>/dev/null) || \
formatted=$(date -j -r "$epoch" +"%H:%M" 2>/dev/null)
;;
datetime)
formatted=$(date -d "@$epoch" +"%b %-d, %H:%M" 2>/dev/null) || \
formatted=$(date -j -r "$epoch" +"%b %-d, %H:%M" 2>/dev/null)
;;
*)
formatted=$(date -d "@$epoch" +"%b %-d" 2>/dev/null) || \
formatted=$(date -j -r "$epoch" +"%b %-d" 2>/dev/null)
;;
esac
[ -n "$formatted" ] && echo "$formatted"
}
# ===== Write usage data for ClaudeWatch =====
claudewatch_file="$claude_config_dir/claudewatch-usage.json"
# Coerces a value to a JSON-safe number literal or "null".
# --argjson aborts jq if passed a non-numeric string, which would silently
# nuke the entire claudewatch-usage.json write (including the 5h/7d fields).
numeric_or_null() {
case "$1" in
''|null) echo null ;;
*[!0-9.+-]*) echo null ;;
*) echo "$1" ;;
esac
}
cw_five_pct=""
cw_five_reset=""
cw_seven_pct=""
cw_seven_reset=""
cw_extra_enabled="false"
cw_extra_pct=""
cw_extra_used_credits=""
cw_extra_monthly_limit=""
if $use_builtin; then
cw_five_pct="$builtin_five_hour_pct"
cw_five_reset="$builtin_five_hour_reset"
cw_seven_pct="$builtin_seven_day_pct"
cw_seven_reset="$builtin_seven_day_reset"
fi
if [ -n "$usage_data" ] && echo "$usage_data" | jq -e '.five_hour' >/dev/null 2>&1; then
if ! $use_builtin; then
cw_five_pct=$(echo "$usage_data" | jq -r '.five_hour.utilization // empty')
cw_seven_pct=$(echo "$usage_data" | jq -r '.seven_day.utilization // empty')
cw_five_reset_iso=$(echo "$usage_data" | jq -r '.five_hour.resets_at // empty')
cw_seven_reset_iso=$(echo "$usage_data" | jq -r '.seven_day.resets_at // empty')
[ -n "$cw_five_reset_iso" ] && [ "$cw_five_reset_iso" != "null" ] && cw_five_reset=$(iso_to_epoch "$cw_five_reset_iso")
[ -n "$cw_seven_reset_iso" ] && [ "$cw_seven_reset_iso" != "null" ] && cw_seven_reset=$(iso_to_epoch "$cw_seven_reset_iso")
fi
cw_extra_enabled=$(echo "$usage_data" | jq -r '.extra_usage.is_enabled // false')
if [ "$cw_extra_enabled" = "true" ]; then
cw_extra_pct=$(echo "$usage_data" | jq -r '.extra_usage.utilization // empty')
cw_extra_used_credits=$(echo "$usage_data" | jq -r '.extra_usage.used_credits // empty')
cw_extra_monthly_limit=$(echo "$usage_data" | jq -r '.extra_usage.monthly_limit // empty')
fi
fi
if [ -n "$cw_five_pct" ] || [ -n "$cw_seven_pct" ] || [ "$cw_extra_enabled" = "true" ]; then
case "$cw_extra_enabled" in true|false) ;; *) cw_extra_enabled=false ;; esac
jq -n \
--argjson five_hour_pct "$(numeric_or_null "$cw_five_pct")" \
--argjson five_hour_reset "$(numeric_or_null "$cw_five_reset")" \
--argjson seven_day_pct "$(numeric_or_null "$cw_seven_pct")" \
--argjson seven_day_reset "$(numeric_or_null "$cw_seven_reset")" \
--argjson extra_enabled "$cw_extra_enabled" \
--argjson extra_pct "$(numeric_or_null "$cw_extra_pct")" \
--argjson extra_used_credits "$(numeric_or_null "$cw_extra_used_credits")" \
--argjson extra_monthly_limit "$(numeric_or_null "$cw_extra_monthly_limit")" \
--argjson updated_at "$(date +%s)" \
'{
five_hour: { used_percentage: $five_hour_pct, resets_at: $five_hour_reset },
seven_day: { used_percentage: $seven_day_pct, resets_at: $seven_day_reset },
extra_usage: {
is_enabled: $extra_enabled,
used_percentage: $extra_pct,
used_credits: $extra_used_credits,
monthly_limit: $extra_monthly_limit
},
updated_at: $updated_at
}' > "$claudewatch_file" 2>/dev/null
fi
sep=" ${dim}|${reset} "
if $use_builtin; then
# ---- Use rate_limits data provided directly by Claude Code in JSON input ----
# resets_at values are Unix epoch integers in this source
if [ -n "$builtin_five_hour_pct" ]; then
five_hour_pct=$(printf "%.0f" "$builtin_five_hour_pct")
five_hour_color=$(usage_color "$five_hour_pct")
out+="${sep}${white}5h${reset} ${five_hour_color}${five_hour_pct}%${reset}"
if [ "$five_hour_pct" -lt 100 ] && [ -n "$builtin_five_hour_reset" ] && [ "$builtin_five_hour_reset" != "null" ]; then
pace_secs=$(compute_pace_delta "$builtin_five_hour_pct" "$builtin_five_hour_reset" 18000)
if [ -n "$pace_secs" ]; then
pace_clr=$(pace_color "$pace_secs")
out+=" ${dim}(${reset}${pace_clr}$(format_pace_short "$pace_secs")${reset}${dim})${reset}"
fi
fi
if [ -n "$builtin_five_hour_reset" ] && [ "$builtin_five_hour_reset" != "null" ]; then
now_secs=$(date +%s)
time_until=$(format_time_until "$(( builtin_five_hour_reset - now_secs ))" short)
[ -n "$time_until" ] && out+=" ${dim}${time_until}${reset}"
fi
fi
if [ -n "$builtin_seven_day_pct" ]; then
seven_day_pct=$(printf "%.0f" "$builtin_seven_day_pct")
seven_day_color=$(usage_color "$seven_day_pct")
out+="${sep}${white}7d${reset} ${seven_day_color}${seven_day_pct}%${reset}"
if [ "$seven_day_pct" -lt 100 ] && [ -n "$builtin_seven_day_reset" ] && [ "$builtin_seven_day_reset" != "null" ]; then
pace_secs=$(compute_pace_delta "$builtin_seven_day_pct" "$builtin_seven_day_reset" 604800)
if [ -n "$pace_secs" ]; then
pace_clr=$(pace_color "$pace_secs")
out+=" ${dim}(${reset}${pace_clr}$(format_pace_long "$pace_secs")${reset}${dim})${reset}"
fi
fi
if [ -n "$builtin_seven_day_reset" ] && [ "$builtin_seven_day_reset" != "null" ]; then
now_secs=$(date +%s)
time_until=$(format_time_until "$(( builtin_seven_day_reset - now_secs ))" long)
[ -n "$time_until" ] && out+=" ${dim}${time_until}${reset}"
fi
fi
elif [ -n "$usage_data" ] && echo "$usage_data" | jq -e '.five_hour' >/dev/null 2>&1; then
# ---- Fall back: API-fetched usage data ----
# ---- 5-hour (current) ----
five_hour_pct_raw=$(echo "$usage_data" | jq -r '.five_hour.utilization // 0')
five_hour_pct=$(echo "$five_hour_pct_raw" | awk '{printf "%.0f", $1}')
five_hour_reset_iso=$(echo "$usage_data" | jq -r '.five_hour.resets_at // empty')
five_hour_reset_epoch=$(iso_to_epoch "$five_hour_reset_iso")
five_hour_color=$(usage_color "$five_hour_pct")
out+="${sep}${white}5h${reset} ${five_hour_color}${five_hour_pct}%${reset}"
if [ "$five_hour_pct" -lt 100 ] && [ -n "$five_hour_reset_epoch" ]; then
pace_secs=$(compute_pace_delta "$five_hour_pct_raw" "$five_hour_reset_epoch" 18000)
if [ -n "$pace_secs" ]; then
pace_clr=$(pace_color "$pace_secs")
out+=" ${dim}(${reset}${pace_clr}$(format_pace_short "$pace_secs")${reset}${dim})${reset}"
fi
fi
if [ -n "$five_hour_reset_epoch" ]; then
now_secs=$(date +%s)
time_until=$(format_time_until "$(( five_hour_reset_epoch - now_secs ))" short)
[ -n "$time_until" ] && out+=" ${dim}${time_until}${reset}"
fi
# ---- 7-day (weekly) ----
seven_day_pct_raw=$(echo "$usage_data" | jq -r '.seven_day.utilization // 0')
seven_day_pct=$(echo "$seven_day_pct_raw" | awk '{printf "%.0f", $1}')
seven_day_reset_iso=$(echo "$usage_data" | jq -r '.seven_day.resets_at // empty')
seven_day_reset_epoch=$(iso_to_epoch "$seven_day_reset_iso")
seven_day_color=$(usage_color "$seven_day_pct")
out+="${sep}${white}7d${reset} ${seven_day_color}${seven_day_pct}%${reset}"
if [ "$seven_day_pct" -lt 100 ] && [ -n "$seven_day_reset_epoch" ]; then
pace_secs=$(compute_pace_delta "$seven_day_pct_raw" "$seven_day_reset_epoch" 604800)
if [ -n "$pace_secs" ]; then
pace_clr=$(pace_color "$pace_secs")
out+=" ${dim}(${reset}${pace_clr}$(format_pace_long "$pace_secs")${reset}${dim})${reset}"
fi
fi
if [ -n "$seven_day_reset_epoch" ]; then
now_secs=$(date +%s)
time_until=$(format_time_until "$(( seven_day_reset_epoch - now_secs ))" long)
[ -n "$time_until" ] && out+=" ${dim}${time_until}${reset}"
fi
# ---- Extra usage ----
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}')
# Validate: if values are empty or contain unexpanded variables, show simple "enabled" label
if [ -n "$extra_used" ] && [ -n "$extra_limit" ] && [[ "$extra_used" != *'$'* ]] && [[ "$extra_limit" != *'$'* ]]; then
extra_color=$(usage_color "$extra_pct")
out+="${sep}${white}extra${reset} ${extra_color}\$${extra_used}/\$${extra_limit}${reset}"
else
out+="${sep}${white}extra${reset} ${green}enabled${reset}"
fi
fi
else
# No valid usage data — show placeholders
out+="${sep}${white}5h${reset} ${dim}-${reset}"
out+="${sep}${white}7d${reset} ${dim}-${reset}"
fi
# ===== Claude Code Status (from summary API, cached 5min TTL) =====
# Shows the Claude Code component status as the primary label, and appends
# the page-wide rollup in parentheses when it disagrees — so broad Anthropic
# incidents (API, console, etc.) that aren't yet scoped to the Claude Code
# component on the status page still surface in the statusline.
status_cache_file="/tmp/claude/statusline-service-status-cache.json"
status_cache_max_age=300 # 5 minutes
status_needs_refresh=true
status_data=""
if [ -f "$status_cache_file" ] && [ -s "$status_cache_file" ]; then
sc_mtime=$(stat -c %Y "$status_cache_file" 2>/dev/null || stat -f %m "$status_cache_file" 2>/dev/null)
sc_now=$(date +%s)
sc_age=$(( sc_now - sc_mtime ))
if [ "$sc_age" -lt "$status_cache_max_age" ]; then
status_needs_refresh=false
fi
status_data=$(cat "$status_cache_file" 2>/dev/null)
fi
if $status_needs_refresh; then
touch "$status_cache_file" 2>/dev/null
status_response=$(curl -s --max-time 5 "https://status.claude.com/api/v2/summary.json" 2>/dev/null)
if [ -n "$status_response" ] && echo "$status_response" | jq -e '.components and .status.indicator' >/dev/null 2>&1; then
status_data="$status_response"
echo "$status_response" > "$status_cache_file"
fi
fi
if [ -n "$status_data" ]; then
cc_status=$(echo "$status_data" | jq -r '.components[] | select(.name == "Claude Code") | .status // empty')
overall_indicator=$(echo "$status_data" | jq -r '.status.indicator // empty')
cc_color=""; cc_label=""
case "$cc_status" in
operational) cc_color="$green"; cc_label="ok" ;;
degraded_performance) cc_color="$yellow"; cc_label="degraded" ;;
partial_outage) cc_color="$orange"; cc_label="partial" ;;
major_outage) cc_color="$red"; cc_label="major" ;;
under_maintenance) cc_color="$yellow"; cc_label="maintenance" ;;
"") ;;
*) cc_color="$dim"; cc_label="$cc_status" ;;
esac
overall_color=""; overall_label=""
case "$overall_indicator" in
none) overall_color="$green"; overall_label="ok" ;;
minor) overall_color="$yellow"; overall_label="degraded" ;;
major) overall_color="$orange"; overall_label="major" ;;
critical) overall_color="$red"; overall_label="critical" ;;
"") ;;
*) overall_color="$dim"; overall_label="$overall_indicator" ;;
esac
if [ -n "$cc_label" ]; then
out+="${sep}${white}status${reset} ${cc_color}${cc_label}${reset}"
if [ -n "$overall_label" ] && [ "$overall_label" != "$cc_label" ]; then
out+=" ${dim}(overall:${reset} ${overall_color}${overall_label}${dim})${reset}"
fi
elif [ -n "$overall_label" ]; then
# Component missing but rollup present — show overall as fallback.
out+="${sep}${white}status${reset} ${dim}(overall:${reset} ${overall_color}${overall_label}${dim})${reset}"
fi
fi
# Output
printf "%b" "$out"
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment