Skip to content

Instantly share code, notes, and snippets.

@mickaelxd
Created May 8, 2026 13:31
Show Gist options
  • Select an option

  • Save mickaelxd/d7d4b70475e5440ca7be3c5356de6d3d to your computer and use it in GitHub Desktop.

Select an option

Save mickaelxd/d7d4b70475e5440ca7be3c5356de6d3d to your computer and use it in GitHub Desktop.
AI Usage
#!/bin/bash
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title AI Usage
# @raycast.mode fullOutput
# Optional parameters:
# @raycast.icon 📊
# @raycast.packageName AI Usage
# @raycast.description Shows Claude Code, Codex, and Copilot real-time usage limits and reset times
# ──────────────────────────────────────
# Helpers
# ──────────────────────────────────────
bar() {
local pct_remaining=$1
local width=20
local used=$(( (100 - pct_remaining) * width / 100 ))
local empty=$(( width - used ))
printf '%s%s' "$(printf '%*s' "$used" '' | tr ' ' '█')" "$(printf '%*s' "$empty" '' | tr ' ' '░')"
}
icon() {
local pct=$1
if [ "$pct" -le 10 ]; then echo "🔴"
elif [ "$pct" -le 30 ]; then echo "🟡"
else echo "🟢"
fi
}
fmt_secs() {
local s=$1
[ "$s" -le 0 ] && echo "now" && return
local h=$(( s / 3600 )) m=$(( (s % 3600) / 60 ))
if [ "$h" -gt 23 ]; then
local d=$(( h / 24 )) rh=$(( h % 24 ))
[ "$rh" -gt 0 ] && echo "${d}d ${rh}h" || echo "${d}d"
elif [ "$h" -gt 0 ]; then
[ "$m" -gt 0 ] && echo "${h}h ${m}m" || echo "${h}h"
else echo "${m}m"
fi
}
clock_from_secs() {
local reset_epoch=$(( $(date +%s) + $1 ))
date -r "$reset_epoch" '+%H:%M' 2>/dev/null
}
iso_to_reset() {
local iso=$1
[ -z "$iso" ] || [ "$iso" = "null" ] && return
local clean="${iso%%Z*}"; clean="${clean%%+*}"; clean="${clean%%.*}"
local epoch
epoch=$(TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$clean" +%s 2>/dev/null)
[ -z "$epoch" ] && return
local diff=$(( epoch - $(date +%s) ))
[ "$diff" -le 0 ] && echo "now" && return
echo "$(fmt_secs "$diff") ($(date -r "$epoch" '+%H:%M'))"
}
# Collect rows: "agent|window|remaining_pct|reset_str|extra"
ROWS=()
add_row() { ROWS+=("$1|$2|$3|$4|$5"); }
CLAUDE_FILE=$(mktemp)
CODEX_FILE=$(mktemp)
COPILOT_FILE=$(mktemp)
trap "rm -f $CLAUDE_FILE $CODEX_FILE $COPILOT_FILE" EXIT
# ──────────────────────────────────────
# Claude: token from Keychain
# ──────────────────────────────────────
fetch_claude() {
local raw
raw=$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null)
[ -z "$raw" ] && echo '{"error":"No credentials. Run claude to auth."}' > "$CLAUDE_FILE" && return
local json
echo "$raw" | grep -q '^{' && json="$raw" || json=$(echo "$raw" | xxd -r -p 2>/dev/null)
local token
token=$(echo "$json" | jq -r '.claudeAiOauth.accessToken // empty' 2>/dev/null)
[ -z "$token" ] && echo '{"error":"No OAuth token in credentials."}' > "$CLAUDE_FILE" && return
token="${token#Bearer }"; token="${token#bearer }"
curl -s --max-time 10 \
-H "Authorization: Bearer $token" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "anthropic-beta: oauth-2025-04-20" \
"https://api.anthropic.com/api/oauth/usage" > "$CLAUDE_FILE" 2>/dev/null
}
# ──────────────────────────────────────
# Codex: token from auth.json
# ──────────────────────────────────────
fetch_codex() {
local token
token=$(jq -r '.tokens.access_token // empty' ~/.codex/auth.json 2>/dev/null)
[ -z "$token" ] && echo '{"error":"No token. Run codex login."}' > "$CODEX_FILE" && return
curl -s --max-time 10 \
-H "Authorization: Bearer $token" \
-H "Accept: application/json" \
-H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
"https://chatgpt.com/backend-api/wham/usage" > "$CODEX_FILE" 2>/dev/null
}
# ──────────────────────────────────────
# Copilot: token from gh / env
# ──────────────────────────────────────
fetch_copilot() {
local token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
[ -z "$token" ] && token=$(gh auth token 2>/dev/null)
[ -z "$token" ] && echo '{"error":"No GH token. Set GITHUB_TOKEN or run gh auth login."}' > "$COPILOT_FILE" && return
curl -s --max-time 10 \
-H "Authorization: token $token" \
-H "Accept: application/json" \
-H "Editor-Version: vscode/1.96.2" \
-H "Editor-Plugin-Version: copilot-chat/0.26.7" \
-H "User-Agent: GitHubCopilotChat/0.26.7" \
-H "X-Github-Api-Version: 2025-04-01" \
"https://api.github.com/copilot_internal/user" > "$COPILOT_FILE" 2>/dev/null
}
# ──────────────────────────────────────
# Fetch all in parallel
# ──────────────────────────────────────
fetch_claude &
fetch_codex &
fetch_copilot &
wait
# ──────────────────────────────────────
# Parse Claude
# ──────────────────────────────────────
claude_err=$(jq -r '.error // empty' "$CLAUDE_FILE" 2>/dev/null)
if [ -n "$claude_err" ]; then
add_row "Claude" "—" "-1" "⚠ $claude_err" ""
else
c5_util=$(jq -r '.five_hour.utilization // 0' "$CLAUDE_FILE")
c5_reset=$(jq -r '.five_hour.resets_at // empty' "$CLAUDE_FILE")
c5_rem=$(( 100 - ${c5_util%.*} ))
[ "$c5_rem" -lt 0 ] && c5_rem=0; [ "$c5_rem" -gt 100 ] && c5_rem=100
add_row "Claude" "Session" "$c5_rem" "$(iso_to_reset "$c5_reset")" ""
c7_util=$(jq -r '.seven_day.utilization // empty' "$CLAUDE_FILE")
c7_reset=$(jq -r '.seven_day.resets_at // empty' "$CLAUDE_FILE")
if [ -n "$c7_util" ] && [ "$c7_util" != "null" ]; then
c7_rem=$(( 100 - ${c7_util%.*} ))
[ "$c7_rem" -lt 0 ] && c7_rem=0; [ "$c7_rem" -gt 100 ] && c7_rem=100
add_row "Claude" "Weekly" "$c7_rem" "$(iso_to_reset "$c7_reset")" ""
fi
fi
# ──────────────────────────────────────
# Parse Codex
# ──────────────────────────────────────
codex_err=$(jq -r '.error // .detail // empty' "$CODEX_FILE" 2>/dev/null)
if [ -n "$codex_err" ]; then
add_row "Codex" "—" "-1" "⚠ $codex_err" ""
else
cx_plan=$(jq -r '.plan_type // ""' "$CODEX_FILE")
cx5_used=$(jq -r '.rate_limit.primary_window.used_percent // 0' "$CODEX_FILE")
cx5_secs=$(jq -r '.rate_limit.primary_window.reset_after_seconds // 0' "$CODEX_FILE")
cx5_rem=$(( 100 - ${cx5_used%.*} ))
[ "$cx5_rem" -lt 0 ] && cx5_rem=0; [ "$cx5_rem" -gt 100 ] && cx5_rem=100
cx5_reset="$(fmt_secs "${cx5_secs%.*}") ($(clock_from_secs "${cx5_secs%.*}"))"
add_row "Codex" "Session" "$cx5_rem" "$cx5_reset" ""
cxw_used=$(jq -r '.rate_limit.secondary_window.used_percent // 0' "$CODEX_FILE")
cxw_secs=$(jq -r '.rate_limit.secondary_window.reset_after_seconds // 0' "$CODEX_FILE")
cxw_rem=$(( 100 - ${cxw_used%.*} ))
[ "$cxw_rem" -lt 0 ] && cxw_rem=0; [ "$cxw_rem" -gt 100 ] && cxw_rem=100
add_row "Codex" "Weekly" "$cxw_rem" "$(fmt_secs "${cxw_secs%.*}")" ""
fi
# ──────────────────────────────────────
# Parse Copilot
# ──────────────────────────────────────
copilot_err=$(jq -r '.error // .message // empty' "$COPILOT_FILE" 2>/dev/null)
if [ -n "$copilot_err" ]; then
add_row "Copilot" "—" "-1" "⚠ $copilot_err" ""
else
cp_plan=$(jq -r '.copilot_plan // ""' "$COPILOT_FILE")
cp_reset_date=$(jq -r '.quota_reset_date // ""' "$COPILOT_FILE")
# Convert YYYY-MM-DD to relative time
cp_reset_rel=""
if [ -n "$cp_reset_date" ] && [ "$cp_reset_date" != "null" ]; then
cp_epoch=$(date -j -f "%Y-%m-%d" "$cp_reset_date" +%s 2>/dev/null)
if [ -n "$cp_epoch" ]; then
cp_diff=$(( cp_epoch - $(date +%s) ))
[ "$cp_diff" -gt 0 ] && cp_reset_rel=$(fmt_secs "$cp_diff") || cp_reset_rel="now"
fi
fi
# Premium interactions
cp_prem=$(jq -r '.quota_snapshots.premium_interactions.percent_remaining // empty' "$COPILOT_FILE" 2>/dev/null)
if [ -z "$cp_prem" ]; then
cp_ent=$(jq -r '.monthly_quotas.completions // empty' "$COPILOT_FILE" 2>/dev/null)
cp_lim=$(jq -r '.limited_user_quotas.completions // empty' "$COPILOT_FILE" 2>/dev/null)
if [ -n "$cp_ent" ] && [ -n "$cp_lim" ] && [ "$cp_ent" != "0" ]; then
cp_prem=$(echo "scale=0; $cp_lim * 100 / $cp_ent" | bc 2>/dev/null)
fi
fi
if [ -n "$cp_prem" ]; then
cp_prem_int=${cp_prem%.*}
[ "$cp_prem_int" -lt 0 ] 2>/dev/null && cp_prem_int=0
[ "$cp_prem_int" -gt 100 ] 2>/dev/null && cp_prem_int=100
add_row "Copilot" "Premium" "$cp_prem_int" "$cp_reset_rel" ""
fi
# Chat
cp_chat=$(jq -r '.quota_snapshots.chat.percent_remaining // empty' "$COPILOT_FILE" 2>/dev/null)
if [ -z "$cp_chat" ]; then
cp_cent=$(jq -r '.monthly_quotas.chat // empty' "$COPILOT_FILE" 2>/dev/null)
cp_clim=$(jq -r '.limited_user_quotas.chat // empty' "$COPILOT_FILE" 2>/dev/null)
if [ -n "$cp_cent" ] && [ -n "$cp_clim" ] && [ "$cp_cent" != "0" ]; then
cp_chat=$(echo "scale=0; $cp_clim * 100 / $cp_cent" | bc 2>/dev/null)
fi
fi
if [ -n "$cp_chat" ]; then
cp_chat_int=${cp_chat%.*}
[ "$cp_chat_int" -lt 0 ] 2>/dev/null && cp_chat_int=0
[ "$cp_chat_int" -gt 100 ] 2>/dev/null && cp_chat_int=100
add_row "Copilot" "Chat" "$cp_chat_int" "$cp_reset_rel" ""
fi
# If no rows added for copilot, show error
if [ -z "$cp_prem" ] && [ -z "$cp_chat" ]; then
add_row "Copilot" "—" "-1" "⚠ No quota data in response" ""
fi
fi
# ──────────────────────────────────────
# Render table
# ──────────────────────────────────────
echo ""
echo " AI Usage — $(date '+%d %b %Y, %H:%M')"
echo ""
printf " %-10s %-9s %-22s %5s %s\n" "Agent" "Window" "" "Left" "Resets"
echo " ──────────────────────────────────────────────────────────────────"
prev_agent=""
for row in "${ROWS[@]}"; do
IFS='|' read -r agent window pct reset extra <<< "$row"
# Separator between different agents
if [ -n "$prev_agent" ] && [ "$agent" != "$prev_agent" ]; then
echo " ──────────────────────────────────────────────────────────────────"
fi
prev_agent="$agent"
if [ "$pct" = "-1" ]; then
# Error row
printf " %-10s %s\n" "$agent" "$reset"
else
local_icon=$(icon "$pct")
local_bar=$(bar "$pct")
printf " %-10s %-9s %s %s %4s%% %s\n" "$agent" "$window" "$local_icon" "$local_bar" "$pct" "$reset"
fi
done
echo " ──────────────────────────────────────────────────────────────────"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment