Skip to content

Instantly share code, notes, and snippets.

@mohemohe
Last active November 22, 2025 20:52
Show Gist options
  • Select an option

  • Save mohemohe/23c90d86c3817ff1606e79957e0e85b3 to your computer and use it in GitHub Desktop.

Select an option

Save mohemohe/23c90d86c3817ff1606e79957e0e85b3 to your computer and use it in GitHub Desktop.
Claudeのusage APIを叩いて使用量を表示するxbarプラグイン Claude Max 20プランで動作確認
Finder 2025-11-23 05 51 11

Claudeに脱法アクセスすることになるので、何かあったとしても自己責任で

使用量ページにアクセスしてブラウザのdevtoolでorganizationとsessionKeyをぶっこ抜く必要がある
https://claude.ai/api/organizations/UUID文字列/usage にアクセスしているので、 CLAUDE_ORGANIZATION にUUID文字列を、CLAUDE_INITIAL_SESSION_KEY にCookieの中身のsessionKeyを貼り付ける
動かしている限りは Set-Cookie で返ってくる sessionKey~/.claude/sessionKey に保存して再利用するので、認証が切れることは無いはず
CLAUDE_INITIAL_SESSION_KEY にベタ書きしなくても最初から ~/.claude/sessionKey を用意してもOK

ClaudeのWebUIはCloudflareで保護されているので、突破するために FlareSolverr/FlareSolverr が必要
ローカルで動かす場合はそのままでOK
リモートで動かす場合は、 FLARESOLVERR_HOST FLARESOLVERR_PORT を適切に書き換える

#!/usr/bin/env python3
# xbar plugin for Claude API usage monitoring
# <xbar.title>Claude Usage</xbar.title>
# <xbar.version>1.0</xbar.version>
# <xbar.author>mohemohe</xbar.author>
# <xbar.author.github>mohemohe</xbar.author.github>
# <xbar.desc>Displays Claude API usage status with 5-hour and weekly limits</xbar.desc>
# <xbar.dependencies>python3,flaresolverr</xbar.dependencies>
import json
import os
import sys
import traceback
import urllib.error
import urllib.request
from datetime import datetime, timedelta, timezone
# Configuration
CLAUDE_ORGANIZATION = "CHANGEME" # Set your organization ID
CLAUDE_INITIAL_SESSION_KEY = "sk-ant-sid01-CHANGEME" # Set your initial session key if no file exists
CLAUDE_USAGE_API_URL = (
f"https://claude.ai/api/organizations/{CLAUDE_ORGANIZATION}/usage"
)
# FlareSolverr configuration
FLARESOLVERR_HOST = "172.0.0.1" # FlareSolverr IP address
FLARESOLVERR_PORT = "8191" # FlareSolverr port
FLARESOLVERR_URL = f"http://{FLARESOLVERR_HOST}:{FLARESOLVERR_PORT}/v1"
LOG_FILE = "/tmp/claude-usage.log" # Log file for debugging
SESSION_KEY_FILE = os.path.expanduser("~/.claude/sessionKey")
TZ = timezone(timedelta(hours=9))
def get_session_key():
"""Get session key from file or environment variable"""
if os.path.exists(SESSION_KEY_FILE):
try:
with open(SESSION_KEY_FILE, "r") as f:
return f.read().strip()
except Exception:
pass
# Fallback to environment variable
if CLAUDE_INITIAL_SESSION_KEY:
return CLAUDE_INITIAL_SESSION_KEY
return None
def save_session_key(session_key):
"""Save session key to file"""
try:
os.makedirs(os.path.dirname(SESSION_KEY_FILE), exist_ok=True)
with open(SESSION_KEY_FILE, "w") as f:
f.write(session_key)
except Exception as e:
print(f"Error saving session key: {e}", file=sys.stderr)
def format_datetime(iso_string):
"""Format ISO datetime string to readable format"""
if not iso_string:
return "N/A"
try:
dt = datetime.fromisoformat(iso_string)
dt_tz = dt.astimezone(TZ)
return dt_tz.strftime("%Y/%m/%d %H:%M")
except Exception:
return iso_string
def make_api_request():
"""Make API request to Claude usage endpoint using FlareSolverr"""
session_key = get_session_key()
if not session_key:
raise Exception("No session key available")
# Create FlareSolverr request payload
flaresolverr_payload = {
"cmd": "request.get",
"url": CLAUDE_USAGE_API_URL,
"maxTimeout": 60000,
"cookies": [
{"name": "sessionKey", "value": session_key, "domain": "claude.ai"}
],
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "ja,en-US;q=0.9,en;q=0.8",
"anthropic-client-platform": "web_claude_ai",
"anthropic-client-version": "1.0.0",
"origin": "https://claude.ai",
"referer": "https://claude.ai/settings/usage",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
},
}
# Convert payload to JSON
payload_data = json.dumps(flaresolverr_payload).encode("utf-8")
# Create request to FlareSolverr
headers = {"Content-Type": "application/json"}
request = urllib.request.Request(
FLARESOLVERR_URL, data=payload_data, headers=headers
)
response_data = None
try:
with urllib.request.urlopen(request) as response:
response_data = response.read().decode()
flaresolverr_response = json.loads(response_data)
# Check if FlareSolverr request was successful
if flaresolverr_response.get("status") != "ok":
raise Exception(
f"FlareSolverr error: {flaresolverr_response.get('message', 'Unknown error')}"
)
# Extract the actual response from FlareSolverr
solution = flaresolverr_response.get("solution", {})
actual_response = solution.get("response", "")
# Extract JSON from HTML response
# The response is embedded in HTML as: <html>...<pre>{JSON}</pre>...</html>
import re
json_match = re.search(r"<pre>(\{.*?\})</pre>", actual_response, re.DOTALL)
if json_match:
json_text = json_match.group(1)
# Parse the extracted JSON
data = json.loads(json_text)
else:
# If no JSON found in HTML, try parsing the response directly
data = json.loads(actual_response)
# Check for new session key in response cookies
cookies = solution.get("cookies", [])
for cookie in cookies:
if cookie.get("name") == "sessionKey":
save_session_key(cookie.get("value"))
break
return data
except urllib.error.HTTPError as e:
raise Exception(f"HTTP Error {e.code}: {e.reason}, {response_data}")
except json.JSONDecodeError as e:
# Log the full FlareSolverr response for debugging
try:
with open(LOG_FILE, "w") as f:
f.write(f"FlareSolverr Response:\n{response_data}\n\n")
f.write(f"JSON Decode Error: {str(e)}\n")
f.write(f"Traceback:\n{traceback.format_exc()}")
except Exception as log_error:
print(f"Failed to write log: {log_error}", file=sys.stderr)
raise Exception(f"JSON decode error: {str(e)}, Response: {response_data}")
except Exception as e:
# Log the full FlareSolverr response for debugging
try:
with open(LOG_FILE, "w") as f:
f.write(f"FlareSolverr Response:\n{response_data}\n\n")
f.write(f"Error: {str(e)}\n")
f.write(f"Traceback:\n{traceback.format_exc()}")
except Exception as log_error:
print(f"Failed to write log: {log_error}", file=sys.stderr)
raise Exception(f"Request failed: {str(e)}")
def display_usage(usage_data):
"""Display usage data in xbar format"""
try:
# Get 5-hour utilization
five_hour = usage_data.get("five_hour", {})
five_hour_util = five_hour.get("utilization", 0)
# Display main status bar item
percentage = int(five_hour_util)
color = (
"color=#26FF4E"
if percentage < 70
else "color=#FFC107"
if percentage < 90
else "color=#FF5722"
)
print(f"✳️ {percentage}% | {color}")
print("---") # Separator
# 5-hour usage details
five_hour_resets = format_datetime(five_hour.get("resets_at"))
print(f"🕔 5-Hour Usage: {percentage}%")
print(f"-- Resets at: {five_hour_resets}")
# Weekly usage for all models
seven_day = usage_data.get("seven_day", {})
if seven_day:
weekly_util = int(seven_day.get("utilization", 0))
weekly_resets = format_datetime(seven_day.get("resets_at"))
print(f"📅 Weekly All Models: {weekly_util}%")
print(f"-- Resets at: {weekly_resets}")
# Weekly Opus usage
seven_day_opus = usage_data.get("seven_day_opus", {})
if seven_day_opus:
opus_util = int(seven_day_opus.get("utilization", 0))
opus_resets = format_datetime(seven_day_opus.get("resets_at"))
print(f"🎼 Weekly Opus: {opus_util}%")
print(f"-- Resets at: {opus_resets}")
# Other models if available
seven_day_sonnet = usage_data.get("seven_day_sonnet")
if seven_day_sonnet:
sonnet_util = int(seven_day_sonnet.get("utilization", 0))
print(f"📝 Weekly Sonnet: {sonnet_util}%")
# Extra usage if available
extra_usage = usage_data.get("extra_usage")
if extra_usage:
extra_util = int(extra_usage.get("utilization", 0))
print(f"🔥 Extra Usage: {extra_util}%")
print("---")
print("🔄 Refresh | refresh=true")
except Exception as e:
print(f"❌ Error displaying data: {str(e)}", file=sys.stderr)
def main():
"""Main function"""
try:
usage_data = make_api_request()
display_usage(usage_data)
except Exception as e:
print(f"✳️ Claude: Error | color=red")
print("---")
print(f"❌ {str(e)}")
print("🔄 Refresh | refresh=true")
# Show session key status for debugging
session_key = get_session_key()
if not session_key:
print("---")
print("⚠️ No session key configured")
print("-- Set CLAUDE_INITIAL_SESSION_KEY or create ~/.claude/sessionKey")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment