Created
January 13, 2026 10:31
-
-
Save teamon/e862ac27efcb5312cbd10eb77863afba to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Claude Usage SwiftBar Plugin | |
| # Shows actual usage from claude.ai/settings/usage | |
| # | |
| # Filename: claude-usage.5m.sh (runs every 5 minutes) | |
| # Install: | |
| # 1. brew install --cask swiftbar jq | |
| # 2. Copy this to your SwiftBar plugins folder | |
| # 3. chmod +x claude-usage.5m.sh | |
| set -e | |
| FIVE_HOUR_SECS=18000 # 5 hours | |
| SEVEN_DAY_SECS=604800 # 7 days | |
| # Get OAuth token from macOS Keychain (stored by Claude Code) | |
| get_token() { | |
| security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null | \ | |
| jq -r '.claudeAiOauth.accessToken // empty' | |
| } | |
| # Fetch usage from Anthropic API | |
| fetch_usage() { | |
| curl -s "https://api.anthropic.com/api/oauth/usage" \ | |
| -H "Accept: application/json" \ | |
| -H "Content-Type: application/json" \ | |
| -H "Authorization: Bearer $1" \ | |
| -H "anthropic-beta: oauth-2025-04-20" \ | |
| -H "User-Agent: claude-code/2.0.31" | |
| } | |
| # Parse reset timestamp to epoch | |
| parse_reset_epoch() { | |
| local reset_at="$1" | |
| [[ -z "$reset_at" || "$reset_at" == "null" ]] && { echo "0"; return; } | |
| local clean_ts="${reset_at%%.*}" | |
| clean_ts="${clean_ts%Z}" | |
| date -u -j -f "%Y-%m-%dT%H:%M:%S" "$clean_ts" "+%s" 2>/dev/null || echo "0" | |
| } | |
| # Calculate time elapsed percentage in window | |
| calc_time_pct() { | |
| local reset_epoch="$1" window_secs="$2" | |
| local now_epoch remaining elapsed pct | |
| now_epoch=$(date "+%s") | |
| remaining=$((reset_epoch - now_epoch)) | |
| (( remaining < 0 )) && remaining=0 | |
| elapsed=$((window_secs - remaining)) | |
| (( elapsed < 0 )) && elapsed=0 | |
| (( elapsed > window_secs )) && elapsed=$window_secs | |
| pct=$((elapsed * 100 / window_secs)) | |
| echo "$pct" | |
| } | |
| # Format time remaining | |
| format_time_remaining() { | |
| local reset_epoch="$1" | |
| local now_epoch diff | |
| now_epoch=$(date "+%s") | |
| diff=$((reset_epoch - now_epoch)) | |
| if (( diff <= 0 )); then echo "now" | |
| elif (( diff < 3600 )); then echo "$((diff / 60))m" | |
| elif (( diff < 86400 )); then echo "$((diff / 3600))h$((diff % 3600 / 60))m" | |
| else echo "$((diff / 86400))d$((diff % 86400 / 3600))h" | |
| fi | |
| } | |
| # ANSI color codes (8-bit colors) | |
| RESET=$'\033[0m' | |
| GREEN=$'\033[38;5;82m' | |
| YELLOW=$'\033[38;5;220m' | |
| GRAY=$'\033[38;5;245m' | |
| DIM=$'\033[38;5;238m' | |
| # Build bar: green=usage (on track), gray=buffer, dim=remaining | |
| # If usage > time: yellow=usage (warning) | |
| # Use same character (█) throughout for consistent height | |
| build_usage_bar() { | |
| local time_pct=$1 usage_pct=$2 width=${3:-10} | |
| local time_blocks=$(( (time_pct * width + 50) / 100 )) | |
| local usage_blocks=$(( (usage_pct * width + 50) / 100 )) | |
| (( time_blocks > width )) && time_blocks=$width | |
| (( usage_blocks > width )) && usage_blocks=$width | |
| local bar="" | |
| if (( usage_pct <= time_pct )); then | |
| # On track: green for usage, gray for buffer, dim for remaining | |
| for ((i=0; i<width; i++)); do | |
| if ((i < usage_blocks)); then | |
| bar+="${GREEN}█${RESET}" | |
| elif ((i < time_blocks)); then | |
| bar+="${GRAY}█${RESET}" | |
| else | |
| bar+="${DIM}█${RESET}" | |
| fi | |
| done | |
| else | |
| # Burning too fast: yellow for usage, dim for remaining | |
| for ((i=0; i<width; i++)); do | |
| if ((i < usage_blocks)); then | |
| bar+="${YELLOW}█${RESET}" | |
| else | |
| bar+="${DIM}█${RESET}" | |
| fi | |
| done | |
| fi | |
| printf '%s' "$bar" | |
| } | |
| # --- Main --- | |
| TOKEN=$(get_token) | |
| if [[ -z "$TOKEN" ]]; then | |
| echo "☁️ ⚠️ | color=#888888" | |
| echo "---" | |
| echo "Not logged in to Claude Code | color=#FF6666" | |
| echo "Run: claude login | font=Monaco size=11" | |
| exit 0 | |
| fi | |
| USAGE_JSON=$(fetch_usage "$TOKEN") | |
| if [[ -z "$USAGE_JSON" ]] || echo "$USAGE_JSON" | jq -e '.error' >/dev/null 2>&1; then | |
| echo "☁️ ⚠️ | color=#888888" | |
| echo "---" | |
| echo "Failed to fetch usage | color=#FF6666" | |
| echo "Token may be expired - run: claude login" | |
| exit 0 | |
| fi | |
| # Extract values with jq | |
| FIVE_HOUR_UTIL=$(echo "$USAGE_JSON" | jq -r '.five_hour.utilization // 0') | |
| SEVEN_DAY_UTIL=$(echo "$USAGE_JSON" | jq -r '.seven_day.utilization // 0') | |
| FIVE_HOUR_RESET=$(echo "$USAGE_JSON" | jq -r '.five_hour.resets_at // empty') | |
| SEVEN_DAY_RESET=$(echo "$USAGE_JSON" | jq -r '.seven_day.resets_at // empty') | |
| # Parse reset times | |
| FIVE_HOUR_RESET_EPOCH=$(parse_reset_epoch "$FIVE_HOUR_RESET") | |
| SEVEN_DAY_RESET_EPOCH=$(parse_reset_epoch "$SEVEN_DAY_RESET") | |
| # Calculate time progress in each window | |
| FIVE_HOUR_TIME_PCT=$(calc_time_pct "$FIVE_HOUR_RESET_EPOCH" "$FIVE_HOUR_SECS") | |
| SEVEN_DAY_TIME_PCT=$(calc_time_pct "$SEVEN_DAY_RESET_EPOCH" "$SEVEN_DAY_SECS") | |
| # Usage percentages (already 0-100) | |
| FIVE_HOUR_USAGE_PCT=$(printf "%.0f" "$FIVE_HOUR_UTIL") | |
| SEVEN_DAY_USAGE_PCT=$(printf "%.0f" "$SEVEN_DAY_UTIL") | |
| # Format remaining time | |
| FIVE_HOUR_REMAINING=$(format_time_remaining "$FIVE_HOUR_RESET_EPOCH") | |
| SEVEN_DAY_REMAINING=$(format_time_remaining "$SEVEN_DAY_RESET_EPOCH") | |
| # Build bars for menubar | |
| FIVE_HOUR_BAR=$(build_usage_bar "$FIVE_HOUR_TIME_PCT" "$FIVE_HOUR_USAGE_PCT" 5) | |
| SEVEN_DAY_BAR=$(build_usage_bar "$SEVEN_DAY_TIME_PCT" "$SEVEN_DAY_USAGE_PCT" 5) | |
| # Build larger bars for dropdown | |
| FIVE_HOUR_BAR_LARGE=$(build_usage_bar "$FIVE_HOUR_TIME_PCT" "$FIVE_HOUR_USAGE_PCT" 10) | |
| SEVEN_DAY_BAR_LARGE=$(build_usage_bar "$SEVEN_DAY_TIME_PCT" "$SEVEN_DAY_USAGE_PCT" 10) | |
| # Menubar with ANSI colors | |
| echo -e "5h${FIVE_HOUR_BAR} 7d${SEVEN_DAY_BAR} | font=Monaco size=13 ansi=true" | |
| # Dropdown | |
| echo "---" | |
| echo "Claude Usage | size=14 color=#888888" | |
| echo "---" | |
| echo "5-Hour Session (${FIVE_HOUR_REMAINING} left) | size=12" | |
| echo -e "${FIVE_HOUR_BAR_LARGE} | font=Monaco size=11 ansi=true" | |
| echo "Time: ${FIVE_HOUR_TIME_PCT}% Usage: ${FIVE_HOUR_USAGE_PCT}% | size=11 color=#888888" | |
| echo "---" | |
| echo "Weekly (${SEVEN_DAY_REMAINING} left) | size=12" | |
| echo -e "${SEVEN_DAY_BAR_LARGE} | font=Monaco size=11 ansi=true" | |
| echo "Time: ${SEVEN_DAY_TIME_PCT}% Usage: ${SEVEN_DAY_USAGE_PCT}% | size=11 color=#888888" | |
| echo "---" | |
| echo "Green=on track Yellow=over budget | size=10 color=#666666" | |
| echo "---" | |
| echo "Refresh | refresh=true" | |
| echo "Open Usage Settings | href=https://claude.ai/settings/usage" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment