Skip to content

Instantly share code, notes, and snippets.

@simoninglis
Last active February 6, 2026 06:30
Show Gist options
  • Select an option

  • Save simoninglis/b6f909ba0aa2f67af872866e2f22dbd4 to your computer and use it in GitHub Desktop.

Select an option

Save simoninglis/b6f909ba0aa2f67af872866e2f22dbd4 to your computer and use it in GitHub Desktop.
claude-usage: Display Claude Code API usage (5h/7d quotas) in tmux status bar with color-coded thresholds
#!/usr/bin/env bash
# claude-usage: Display Claude Code API usage in tmux status bar
# https://gist.github.com/simoninglis/b6f909ba0aa2f67af872866e2f22dbd4
#
# Usage: Add to tmux status-right:
# set-option -g status-right '#(~/.local/bin/claude-usage) │ %H:%M'
#
# Dependencies: curl, jq, GNU date, GNU stat
# Platform: Linux/WSL (GNU coreutils required)
# License: MIT
#
# Output: 5h:XX(Yh) 7d:ZZ(Wd)
# 5h:XX = 5-hour utilization percentage
# 7d:ZZ = 7-day utilization percentage
# (Yh) = hours until reset (<24h) or (Wd) days (>=24h) or (Xm) minutes (<1h)
# ~ = suffix when showing stale cached data
set -euo pipefail
#=============================================================================
# USER CONFIGURATION - Customize these settings (or set via environment)
#=============================================================================
# Cache TTL in seconds (default: 5 minutes)
CACHE_TTL="${CLAUDE_USAGE_CACHE_TTL:-300}"
# Utilization thresholds (percentage)
THRESHOLD_LOW="${CLAUDE_USAGE_THRESHOLD_LOW:-50}" # Below = good
THRESHOLD_MED="${CLAUDE_USAGE_THRESHOLD_MED:-80}" # Below = warning, above = critical
# Tmux color styles - customize for your status bar background
# Format: tmux style string (e.g., "fg=white,bold" or "fg=colour136")
STYLE_LOW="${CLAUDE_USAGE_STYLE_LOW:-fg=white,bold}" # Low utilization (< threshold_low)
STYLE_MED="${CLAUDE_USAGE_STYLE_MED:-fg=colour136}" # Medium utilization (goldenrod)
STYLE_HIGH="${CLAUDE_USAGE_STYLE_HIGH:-fg=colour160}" # High utilization (dark red)
STYLE_ERROR="${CLAUDE_USAGE_STYLE_ERROR:-fg=white,dim}" # Error/fallback state
#=============================================================================
# INTERNAL CONFIGURATION
#=============================================================================
readonly CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/claude-usage"
readonly CACHE_FILE="${CACHE_DIR}/status"
readonly CREDENTIALS_FILE="${CLAUDE_USAGE_CREDENTIALS:-$HOME/.claude/.credentials.json}"
readonly API_URL="https://api.anthropic.com/api/oauth/usage"
#=============================================================================
# FUNCTIONS
#=============================================================================
check_dependencies() {
local missing=()
for cmd in curl jq date stat; do
command -v "$cmd" &>/dev/null || missing+=("$cmd")
done
if [[ ${#missing[@]} -gt 0 ]]; then
echo "#[fg=red]ERR:missing ${missing[*]}#[default]"
exit 1
fi
}
ensure_cache_dir() {
if [[ ! -d "$CACHE_DIR" ]]; then
mkdir -p "$CACHE_DIR"
chmod 700 "$CACHE_DIR"
fi
}
get_style() {
local util="${1%.*}"
util="${util:-0}"
if [[ "$util" -lt "$THRESHOLD_LOW" ]]; then
echo "$STYLE_LOW"
elif [[ "$util" -lt "$THRESHOLD_MED" ]]; then
echo "$STYLE_MED"
else
echo "$STYLE_HIGH"
fi
}
# Convert ISO 8601 timestamp to human-readable time-until-reset
format_time_until() {
local reset_at="$1"
if [[ -z "$reset_at" ]] || [[ "$reset_at" == "null" ]]; then
echo "?"
return
fi
# Parse ISO 8601: 2026-01-29T07:00:00.469844+00:00 -> 2026-01-29 07:00:00 +0000
local parsed_date
parsed_date=$(echo "$reset_at" | sed -E 's/T/ /; s/\.[0-9]+//; s/([+-][0-9]{2}):([0-9]{2})$/ \1\2/')
local reset_epoch now_epoch diff_seconds
reset_epoch=$(date -d "$parsed_date" +%s 2>/dev/null) || { echo "?"; return; }
now_epoch=$(date +%s)
diff_seconds=$((reset_epoch - now_epoch))
if [[ "$diff_seconds" -le 0 ]]; then
echo "0m"
return
fi
local diff_minutes=$((diff_seconds / 60))
local diff_hours=$((diff_seconds / 3600))
local diff_days=$((diff_seconds / 86400))
if [[ "$diff_hours" -lt 1 ]]; then
echo "${diff_minutes}m"
elif [[ "$diff_hours" -lt 24 ]]; then
echo "${diff_hours}h"
else
echo "${diff_days}d"
fi
}
format_output() {
local five_hour="$1" seven_day="$2" five_hour_reset="$3" seven_day_reset="$4"
local style_5h style_7d
style_5h=$(get_style "$five_hour")
style_7d=$(get_style "$seven_day")
local display_5h="${five_hour%.*}" display_7d="${seven_day%.*}"
display_5h="${display_5h:-0}"
display_7d="${display_7d:-0}"
local time_5h time_7d
time_5h=$(format_time_until "$five_hour_reset")
time_7d=$(format_time_until "$seven_day_reset")
echo "#[${style_5h}]5h:${display_5h}(${time_5h})#[default] #[${style_7d}]7d:${display_7d}(${time_7d})#[default]"
}
fallback_output() {
echo "#[${STYLE_ERROR}]5h:--(?)#[default] #[${STYLE_ERROR}]7d:--(?)#[default]"
}
cache_valid() {
[[ -f "$CACHE_FILE" ]] || return 1
local cache_age=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) ))
[[ "$cache_age" -lt "$CACHE_TTL" ]]
}
fetch_usage() {
[[ -f "$CREDENTIALS_FILE" ]] || return 1
local token
token=$(jq -r '.claudeAiOauth.accessToken // empty' "$CREDENTIALS_FILE" 2>/dev/null)
[[ -n "$token" ]] || return 1
# Use --config to hide token from process list
local response
response=$(curl -s --max-time 5 "$API_URL" \
--config <(printf 'header = "Authorization: Bearer %s"\n' "$token") \
-H "anthropic-beta: oauth-2025-04-20" \
-H "Content-Type: application/json" 2>/dev/null)
[[ -n "$response" ]] || return 1
# Parse all values in single jq call
local parsed
parsed=$(echo "$response" | jq -r '[
.five_hour.utilization // 0,
.seven_day.utilization // 0,
.five_hour.resets_at // "",
.seven_day.resets_at // ""
] | @tsv' 2>/dev/null) || return 1
local five_hour seven_day five_hour_reset seven_day_reset
IFS=$'\t' read -r five_hour seven_day five_hour_reset seven_day_reset <<< "$parsed"
[[ -n "$five_hour" && -n "$seven_day" ]] || return 1
# Atomic cache write
ensure_cache_dir
local tmp_file
tmp_file=$(mktemp "${CACHE_DIR}/status.XXXXXX")
format_output "$five_hour" "$seven_day" "$five_hour_reset" "$seven_day_reset" > "$tmp_file"
mv -f "$tmp_file" "$CACHE_FILE"
chmod 600 "$CACHE_FILE"
cat "$CACHE_FILE"
}
mark_stale() {
sed 's/#\[default\]$/~#[default]/'
}
main() {
check_dependencies
if cache_valid; then
cat "$CACHE_FILE"
return 0
fi
if ! fetch_usage; then
if [[ -f "$CACHE_FILE" ]]; then
cat "$CACHE_FILE" | mark_stale
else
fallback_output
fi
fi
}
main "$@"

claude-usage: Claude Code Usage in tmux Status Bar

Display your Claude Code API usage (5-hour and 7-day quotas) directly in your tmux status bar with color-coded thresholds and time-until-reset.

tmux status bar showing claude-usage

What It Shows

5h:14(3h) 7d:75(19h) │ 13:20 29-Jan
Element Meaning
5h:14 14% of 5-hour rolling quota used
(3h) Resets in 3 hours
7d:75 75% of 7-day rolling quota used
(19h) Resets in 19 hours
~ Suffix shown when displaying stale cached data

Color Coding

Color Threshold Meaning
White (bold) < 50% You're fine
Goldenrod 50-80% Getting warm
Red > 80% Slow down or wait for reset

Colors are customizable - see Configuration below.

Features

  • Minimal API calls: Caches results for 5 minutes (configurable)
  • Secure: OAuth token hidden from process list
  • Time-aware: Shows minutes/hours/days until quota reset
  • Stale indicator: Shows ~ when using cached data after API failure
  • User-specific cache: Safe on multi-user systems
  • Configurable: Colors, thresholds, and cache TTL via environment variables

Requirements

  • Linux/WSL (uses GNU coreutils)
  • curl - HTTP client
  • jq - JSON processor
  • date - GNU date with -d flag
  • stat - GNU stat with -c flag
  • Claude Code with OAuth authentication (the ~/.claude/.credentials.json file)

Note: Not compatible with BSD/macOS due to GNU-specific flags. PRs welcome for portability!

Installation

  1. Download the script:

    curl -o ~/.local/bin/claude-usage \
      https://gist.githubusercontent.com/YOUR_USERNAME/GIST_ID/raw/claude-usage
    chmod +x ~/.local/bin/claude-usage
  2. Test it works:

    ~/.local/bin/claude-usage
    # Should output something like:
    # #[fg=white,bold]5h:14(3h)#[default] #[fg=colour136]7d:75(19h)#[default]
  3. Add to tmux status bar (in ~/.tmux.conf or per-session):

    set-option -g status-right-length 60
    set-option -g status-right '#(~/.local/bin/claude-usage) │ %H:%M %d-%b'
  4. Reload tmux:

    tmux source-file ~/.tmux.conf
    # Or press: prefix + r (if you have reload bound)

Configuration

All settings can be customized via environment variables (or edit the script directly):

# In ~/.bashrc or ~/.zshrc:

# Cache TTL in seconds (default: 300 = 5 minutes)
export CLAUDE_USAGE_CACHE_TTL=300

# Color thresholds (percentages)
export CLAUDE_USAGE_THRESHOLD_LOW=50   # Below = low (good)
export CLAUDE_USAGE_THRESHOLD_MED=80   # Below = medium (warning)
                                        # Above = high (critical)

# Tmux style strings (customize for your status bar background)
export CLAUDE_USAGE_STYLE_LOW="fg=white,bold"     # Low utilization
export CLAUDE_USAGE_STYLE_MED="fg=colour136"      # Medium (goldenrod)
export CLAUDE_USAGE_STYLE_HIGH="fg=colour160"     # High (dark red)
export CLAUDE_USAGE_STYLE_ERROR="fg=white,dim"    # Error/stale state

# Custom credentials path (if not default)
export CLAUDE_USAGE_CREDENTIALS="$HOME/.claude/.credentials.json"

Tmux Color Reference

Common tmux colors that work well:

  • white, black, red, green, yellow, blue, magenta, cyan
  • colour0 - colour255 (256-color palette)
  • brightred, brightyellow, etc. (bright variants)
  • Add ,bold or ,dim for emphasis

How It Works

  1. Reads OAuth token from Claude Code's credentials file
  2. Calls https://api.anthropic.com/api/oauth/usage (same API Claude Code uses)
  3. Caches the formatted result to ~/.cache/claude-usage/status
  4. Returns cached value if fresh, fetches new data if stale
  5. Shows ~ suffix if using stale cache after API failure

API Response

The script uses this undocumented(?) endpoint:

curl -s "https://api.anthropic.com/api/oauth/usage" \
  -H "Authorization: Bearer $TOKEN" \
  -H "anthropic-beta: oauth-2025-04-20"

Returns:

{
  "five_hour": {"utilization": 14.0, "resets_at": "2026-01-29T10:00:00+00:00"},
  "seven_day": {"utilization": 75.0, "resets_at": "2026-01-30T08:00:00+00:00"},
  "extra_usage": {"utilization": 96.4, "monthly_limit": 60000, "used_credits": 57845}
}

Troubleshooting

Shows 5h:--(?) 7d:--(?):

  • Check credentials exist: ls -la ~/.claude/.credentials.json
  • Test manually: ~/.local/bin/claude-usage and check for errors

Colors not showing:

  • Ensure your terminal supports 256 colors
  • Check echo $TERM shows something like xterm-256color or tmux-256color

Wrong reset times:

  • The script requires GNU date. Check: date --version should show GNU coreutils

Token visible in ps:

  • Shouldn't happen with this version. The token is passed via --config with process substitution.

License

MIT - Use it, modify it, share it.

Credits

  • Built with Claude Code (naturally)
  • Code reviewed by OpenAI Codex (grade: A)
  • Inspired by wanting to know "why is Claude slow right now?"

If this helped you, consider starring the gist!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment