113 lines
4.3 KiB
Python
Executable File
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()
|