#!/usr/bin/env python3 """Show live subscription usage for each AI provider used by agent runtimes.""" import json import os import subprocess import sys import urllib.error import urllib.request from pathlib import Path ANTHROPIC_TOKEN_PATH = Path("~/dev/claude/secrets/anthropic/api_key").expanduser() MINIMAX_SOPS_PATH = Path( "~/dev/claude/projects/agent-runtime-secrets/providers/minimax/v1/provider.sops.env" ).expanduser() MINIMAX_SOPS_KEY = Path("~/dev/claude/secrets/sops/provider-age-key.txt").expanduser() MINIMAX_DOTENV_KEY = "ANTHROPIC_AUTH_TOKEN" # ANSI colour support _use_colour = sys.stdout.isatty() _GREEN = "\033[32m" if _use_colour else "" _YELLOW = "\033[33m" if _use_colour else "" _RED = "\033[31m" if _use_colour else "" _RESET = "\033[0m" if _use_colour else "" _BOLD = "\033[1m" if _use_colour else "" _DIM = "\033[2m" if _use_colour else "" def _colour_pct(pct: float) -> str: s = f"{pct:5.1f}%" if pct >= 80: return f"{_RED}{s}{_RESET}" if pct >= 60: return f"{_YELLOW}{s}{_RESET}" return f"{_GREEN}{s}{_RESET}" def _parse_dotenv_key(content: str, key: str) -> str: for line in content.splitlines(): line = line.strip() if not line or line.startswith("#"): continue if "=" in line: k, _, v = line.partition("=") elif ": " in line: k, _, v = line.partition(": ") else: continue if k.strip() == key: v = v.strip() if len(v) >= 2 and v[0] == v[-1] and v[0] in ('"', "'"): v = v[1:-1] return v raise KeyError(f"Key '{key}' not found in decrypted content") def _read_anthropic_token() -> str: return ANTHROPIC_TOKEN_PATH.read_text().strip() def _read_minimax_key() -> str: env = {**os.environ, "SOPS_AGE_KEY_FILE": str(MINIMAX_SOPS_KEY)} result = subprocess.run( ["sops", "--decrypt", "--output-type", "dotenv", str(MINIMAX_SOPS_PATH)], capture_output=True, text=True, check=True, env=env, ) return _parse_dotenv_key(result.stdout, MINIMAX_DOTENV_KEY) def _parse_anthropic_headers(headers) -> dict[str, float]: result = {} fh = headers.get("anthropic-ratelimit-unified-5h-utilization") sd = headers.get("anthropic-ratelimit-unified-7d-utilization") for key, raw in [("five_hour", fh), ("seven_day", sd)]: if raw is not None: try: result[key] = float(raw) * 100.0 except ValueError: pass return result def probe_anthropic(token: str) -> dict[str, float] | None: auth = {"Authorization": f"Bearer {token}", "anthropic-version": "2023-06-01"} try: req = urllib.request.Request("https://api.anthropic.com/v1/models", headers=auth) with urllib.request.urlopen(req, timeout=10) as resp: result = _parse_anthropic_headers(resp.headers) if result: return result except Exception: pass try: body = json.dumps({ "model": "claude-haiku-4-5-20251001", "max_tokens": 1, "messages": [{"role": "user", "content": "hi"}], }).encode() req = urllib.request.Request( "https://api.anthropic.com/v1/messages", data=body, headers={**auth, "content-type": "application/json"}, method="POST", ) with urllib.request.urlopen(req, timeout=15) as resp: return _parse_anthropic_headers(resp.headers) or None except urllib.error.HTTPError as e: result = _parse_anthropic_headers(e.headers) return result or None except Exception: pass return None def probe_minimax(api_key: str) -> tuple[dict[str, float] | None, str | None]: """Returns (usage_dict, error_reason). On success error_reason is None.""" try: req = urllib.request.Request( "https://www.minimax.io/v1/token_plan/remains", headers={ "Authorization": f"Bearer {api_key}", "User-Agent": "curl/7.88.1", }, ) with urllib.request.urlopen(req, timeout=10) as resp: data = json.loads(resp.read()) status = data.get("base_resp", {}).get("status_code") if status != 0: msg = data.get("base_resp", {}).get("status_msg", "unknown") return None, f"API status_code={status} ({msg})" for cat in data.get("category_remains", []): if cat.get("category") == "text_generation": result = {} interval_total = cat.get("current_interval_total_count", 0) interval_used = cat.get("current_interval_usage_count", 0) weekly_total = cat.get("current_weekly_total_count", 0) weekly_used = cat.get("current_weekly_usage_count", 0) if interval_total > 0: result["five_hour"] = float(interval_used) / float(interval_total) * 100.0 if weekly_total > 0: result["seven_day"] = float(weekly_used) / float(weekly_total) * 100.0 return (result or None), None return None, "text_generation category not found in response" except urllib.error.HTTPError as e: return None, f"HTTP {e.code} {e.reason}" except urllib.error.URLError as e: return None, f"network error: {e.reason}" except Exception as e: return None, str(e) def print_dryrun() -> None: print(f"{_BOLD}[dryrun] Would probe:{_RESET}") print(f" {_BOLD}Anthropic{_RESET} — {ANTHROPIC_TOKEN_PATH} (Bearer OAuth token)") print(f" GET https://api.anthropic.com/v1/models") print(f" {_BOLD}MiniMax{_RESET} — {MINIMAX_SOPS_PATH} (SOPS, key: {MINIMAX_DOTENV_KEY})") print(f" GET https://www.minimax.io/v1/token_plan/remains") def print_table(rows: list[tuple[str, str, str]]) -> None: print(f"\n{_BOLD}Agent Subscription Usage{_RESET}") print("========================\n") col1, col2, col3 = 12, 10, 8 header = f"{'Provider':<{col1}} {'Window':<{col2}} {'Usage':<{col3}}" sep = f"{'-'*col1} {'-'*col2} {'-'*col3}" print(f"{_BOLD}{header}{_RESET}") print(sep) for provider, window, usage in rows: print(f"{provider:<{col1}} {window:<{col2}} {usage}") print() def main() -> None: dryrun = False args = sys.argv[1:] for arg in args: if arg in ("-h", "--help"): print(__doc__) print("Usage: agent-subscriptions [OPTIONS]") print() print("Options:") print(" -n, --dryrun Preview probes without making API calls") print(" -h, --help Show this help message and exit") sys.exit(0) elif arg in ("-n", "--dryrun"): dryrun = True else: print(f"Error: unknown option: {arg}", file=sys.stderr) sys.exit(1) if dryrun: print_dryrun() return rows: list[tuple[str, str, str]] = [] # Anthropic try: token = _read_anthropic_token() usage = probe_anthropic(token) if usage is None: rows.append(("Anthropic", "5-hour", f"{_DIM}UNAVAILABLE{_RESET}")) rows.append(("Anthropic", "7-day", f"{_DIM}UNAVAILABLE{_RESET}")) else: fh = usage.get("five_hour") sd = usage.get("seven_day") rows.append(("Anthropic", "5-hour", _colour_pct(fh) if fh is not None else f"{_DIM}N/A{_RESET}")) rows.append(("Anthropic", "7-day", _colour_pct(sd) if sd is not None else f"{_DIM}N/A{_RESET}")) except Exception as e: rows.append(("Anthropic", "5-hour", f"{_DIM}UNAVAILABLE{_RESET}")) rows.append(("Anthropic", "7-day", f"{_DIM}UNAVAILABLE{_RESET}")) print(f"{_DIM}Warning: Anthropic probe failed: {e}{_RESET}", file=sys.stderr) # MiniMax try: key = _read_minimax_key() usage, reason = probe_minimax(key) if usage is None: rows.append(("MiniMax", "5-hour", f"{_DIM}UNAVAILABLE{_RESET}")) rows.append(("MiniMax", "7-day", f"{_DIM}UNAVAILABLE{_RESET}")) if reason: print(f"{_DIM}Warning: MiniMax probe: {reason}{_RESET}", file=sys.stderr) else: fh = usage.get("five_hour") sd = usage.get("seven_day") rows.append(("MiniMax", "5-hour", _colour_pct(fh) if fh is not None else f"{_DIM}N/A{_RESET}")) rows.append(("MiniMax", "7-day", _colour_pct(sd) if sd is not None else f"{_DIM}N/A{_RESET}")) except Exception as e: rows.append(("MiniMax", "5-hour", f"{_DIM}UNAVAILABLE{_RESET}")) rows.append(("MiniMax", "7-day", f"{_DIM}UNAVAILABLE{_RESET}")) print(f"{_DIM}Warning: MiniMax probe failed: {e}{_RESET}", file=sys.stderr) print_table(rows) if __name__ == "__main__": main()