clawd/scripts/claude-usage-fetch.py

113 lines
4.3 KiB
Python
Executable File

#!/home/johan/clawd/.venv/bin/python3
"""
Fetch Claude.ai usage limits using curl_cffi to bypass Cloudflare.
Uses browser TLS fingerprinting to avoid bot detection.
Usage:
./claude-usage-fetch.py # Human readable output
./claude-usage-fetch.py --json # JSON output
./claude-usage-fetch.py --save # Save to memory/claude-usage.json
"""
import json
import sys
import os
from datetime import datetime, timezone
from curl_cffi import requests
# Config file for cookies (so they can be updated without editing script)
CONFIG_FILE = "/home/johan/clawd/config/claude-cookies.json"
USAGE_FILE = "/home/johan/clawd/memory/claude-usage.json"
# Default cookies (will be overridden by config file if it exists)
DEFAULT_COOKIES = {
"sessionKey": "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",
"cf_clearance": "00xy9IG_RYfnzJK9brttMzWZ7hBPeH_V76Kp2qfC.7c-1770013650-1.2.1.1-jA6mdGLMDMq_gfdcxo_hmt94pZmc_C6nN2Z6DDxsV2EtKnsfBWSrdI6dDeLV7wZkIvi3HbZeke.hJw1Blsd_oNN2dmYFnQukyZclGUgdJ.gkT00.rWmXWzrotSMlelMrk6YWXb32.cMEhjdUHTnPx0WBAC9vfH8ImYFf4uIqOOwI400sioTg5ILmaD22.OhsgdlexnYJiTGbqhqKENmlWZrsMZLtGQqxXoasvZOlp6o",
"__cf_bm": "vDv1.m3wBtCMxGvuoUZUTugPoMMx2aey711bK86Vqdo-1770013650-1.0.1.1-Ygz_OiVEOHnTvQS1kHTw.e1iwqWD0OouyUs.RWKHXaRLUWovJ88vk94zjQNo8vMBNHtVKsDuigjntRSE8YcN5pe_Wlj_z2a5GNgPBW.dikw",
"lastActiveOrg": "1ed7bd80-0068-464f-bf6c-9f3553bda3a6",
}
def load_cookies():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE) as f:
return json.load(f)
return DEFAULT_COOKIES
def fetch_usage():
cookies = load_cookies()
org_id = cookies.get("lastActiveOrg", "1ed7bd80-0068-464f-bf6c-9f3553bda3a6")
api_url = f"https://claude.ai/api/organizations/{org_id}/usage"
try:
resp = requests.get(
api_url,
cookies=cookies,
impersonate="chrome",
headers={
"Accept": "application/json",
"Referer": "https://claude.ai/settings/usage",
}
)
if resp.status_code == 200:
return resp.json()
else:
print(f"Error: HTTP {resp.status_code}", file=sys.stderr)
if "challenge" in resp.text.lower() or "Just a moment" in resp.text:
print("COOKIES_EXPIRED: Cloudflare challenge detected - cookies need refresh", file=sys.stderr)
return None
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return None
def format_usage(data):
"""Convert API response to our standard format"""
result = {
"last_updated": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"source": "api",
}
if data.get("five_hour"):
result["session_percent"] = int(data["five_hour"]["utilization"])
result["session_resets"] = data["five_hour"]["resets_at"]
if data.get("seven_day"):
result["weekly_percent"] = int(data["seven_day"]["utilization"])
result["weekly_resets"] = data["seven_day"]["resets_at"]
if data.get("seven_day_sonnet"):
result["sonnet_percent"] = int(data["seven_day_sonnet"]["utilization"])
return result
def main():
raw_data = fetch_usage()
if not raw_data:
print("Failed to fetch usage", file=sys.stderr)
sys.exit(1)
usage = format_usage(raw_data)
if "--json" in sys.argv:
print(json.dumps(usage, indent=2))
elif "--raw" in sys.argv:
print(json.dumps(raw_data, indent=2))
elif "--save" in sys.argv:
os.makedirs(os.path.dirname(USAGE_FILE), exist_ok=True)
with open(USAGE_FILE, 'w') as f:
json.dump(usage, f, indent=2)
print(f"Saved to {USAGE_FILE}")
print(f"📊 Weekly: {usage.get('weekly_percent', '?')}% used ({100 - usage.get('weekly_percent', 0)}% remaining)")
else:
print(f"📊 Claude.ai Usage")
print(f" Session (5h): {usage.get('session_percent', '?')}%")
print(f" Weekly (7d): {usage.get('weekly_percent', '?')}%")
print(f" Sonnet only: {usage.get('sonnet_percent', '?')}%")
print(f" ─────────────")
print(f" Remaining: {100 - usage.get('weekly_percent', 0)}% weekly")
if __name__ == "__main__":
main()