Skip to content

Instantly share code, notes, and snippets.

@chrisns
Last active February 2, 2026 09:40
Show Gist options
  • Select an option

  • Save chrisns/3400a77287bbafb400b87810b006e7e5 to your computer and use it in GitHub Desktop.

Select an option

Save chrisns/3400a77287bbafb400b87810b006e7e5 to your computer and use it in GitHub Desktop.
Claude status in menu bar

Get your claude 5hr and weekly quota in your iterm2 statusbar

mkdir -r ~/Library/Application\ Support/iTerm2/Scripts/AutoLaunch/claudestatus/claudestatus/
curl https://gist.github.com/chrisns/3400a77287bbafb400b87810b006e7e5/raw/f1cae5b8a7d7b04a3e6960ec93772c22f7897a19/claudestatus.py > ~/Library/Application\ Support/iTerm2/Scripts/AutoLaunch/claudestatus/claudestatus/claudestatus.py

Restart iterm2

Open the statusbar settings and drag the claude usage down, its not obvious the panel scrolls so you might not spot it

image image

Note:

If you've not already enabled the iterm2 python runtime you'll need to do that

#!/usr/bin/env python3.14
"""
Claude Code Usage Status Bar Component for iTerm2
Displays: 01:37 20% | 01:22 5%
- First: 5-hour session (time until reset + % used)
- Second: 7-day weekly (time until reset + % used)
- Time format: hh:mm if < 5 hours, else Mon 14:03
- Error state: Show 'err' indicator
"""
import iterm2
import json
import subprocess
from datetime import datetime, timezone
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
def get_credentials():
"""Get Claude Code credentials from macOS Keychain."""
try:
result = subprocess.run(
["security", "find-generic-password", "-s", "Claude Code-credentials", "-w"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode != 0:
return None
return json.loads(result.stdout.strip())
except (subprocess.TimeoutExpired, json.JSONDecodeError, Exception):
return None
def get_access_token(credentials):
"""Extract access token from credentials (handles nested structure)."""
if not credentials:
return None
# Handle nested structure: claudeAiOauth.accessToken or direct accessToken
if "claudeAiOauth" in credentials:
return credentials["claudeAiOauth"].get("accessToken")
return credentials.get("accessToken")
def fetch_usage(access_token):
"""Call Anthropic OAuth usage API."""
url = "https://api.anthropic.com/api/oauth/usage"
headers = {
"Authorization": f"Bearer {access_token}",
"anthropic-beta": "oauth-2025-04-20",
"User-Agent": "claude-code/2.0.32"
}
try:
request = Request(url, headers=headers, method="GET")
with urlopen(request, timeout=10) as response:
return json.loads(response.read().decode("utf-8"))
except (URLError, HTTPError, json.JSONDecodeError, Exception):
return None
def format_time_until(resets_at_str):
"""
Format time until reset.
If < 5 hours: hh:mm
If >= 5 hours: Mon 14:03
"""
try:
# Parse ISO 8601 timestamp
# Handle formats like: 2024-01-15T14:30:00Z or 2024-01-15T14:30:00.123Z
resets_at_str = resets_at_str.replace("Z", "+00:00")
# Remove milliseconds if present
if "." in resets_at_str:
parts = resets_at_str.split(".")
# Keep everything before . and timezone after
tz_start = max(parts[1].find("+"), parts[1].find("-"))
if tz_start == -1:
resets_at_str = parts[0] + "+00:00"
else:
resets_at_str = parts[0] + parts[1][tz_start:]
resets_at = datetime.fromisoformat(resets_at_str)
now = datetime.now(timezone.utc)
diff = resets_at - now
diff_seconds = max(0, int(diff.total_seconds()))
five_hours = 5 * 60 * 60
if diff_seconds < five_hours:
# Format as hh:mm
hours = diff_seconds // 3600
minutes = (diff_seconds % 3600) // 60
return f"{hours:02d}:{minutes:02d}"
else:
# Format as Mon 14:03 (in local time)
local_time = resets_at.astimezone()
return local_time.strftime("%a %H:%M")
except Exception:
return "err"
def format_percentage(utilization):
"""Format utilization as integer percentage."""
try:
util = float(utilization)
# If value is in 0-1 range, multiply by 100
if util <= 1:
pct = int(util * 100)
else:
pct = int(util)
return str(pct)
except (ValueError, TypeError):
return "?"
def get_usage_string():
"""Get formatted usage string for status bar."""
# Get credentials
credentials = get_credentials()
if not credentials:
return "err"
# Extract access token
access_token = get_access_token(credentials)
if not access_token:
return "err"
# Fetch usage data
usage = fetch_usage(access_token)
if not usage:
return "err"
# Extract data
try:
five_hour = usage.get("five_hour", {})
seven_day = usage.get("seven_day", {})
seven_day_resets_at = seven_day.get("resets_at")
seven_day_utilization = seven_day.get("utilization")
# If no weekly session data, return "NO CURRENT SESSION"
if not seven_day_resets_at or seven_day_utilization is None:
return "NO CURRENT SESSION"
five_hour_resets_at = five_hour.get("resets_at")
five_hour_utilization = five_hour.get("utilization")
# Format weekly values
weekly_time = format_time_until(seven_day_resets_at)
weekly_pct = format_percentage(seven_day_utilization)
if weekly_time == "err":
return "err"
# If no active session, show 5:00 for session time
if not five_hour_resets_at or five_hour_utilization is None:
return f"5:00 0% | {weekly_time} {weekly_pct}%"
# Format session values
session_time = format_time_until(five_hour_resets_at)
session_pct = format_percentage(five_hour_utilization)
if session_time == "err":
return "err"
return f"{session_time} {session_pct}% | {weekly_time} {weekly_pct}%"
except Exception:
return "err"
async def main(connection):
"""Main entry point for iTerm2 status bar component."""
component = iterm2.StatusBarComponent(
short_description="Claude Usage",
detailed_description="Shows Claude Code usage quotas (5-hour session and 7-day weekly)",
knobs=[],
exemplar="01:37 20% | Thu 14:03 5%",
update_cadence=60, # Update every 60 seconds
identifier="com.anthropic.claude-usage"
)
@iterm2.StatusBarRPC
async def claude_usage_coroutine(knobs):
"""Coroutine called by iTerm2 to get status bar content."""
return get_usage_string()
await component.async_register(connection, claude_usage_coroutine)
iterm2.run_forever(main)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment