# agent-subscriptions ## Purpose Show live subscription usage percentages and weekly-reset timing for each AI provider used by the agent runtimes system (Anthropic OAuth, MiniMax). Output is human-readable by default; a `--output json` mode produces a machine-consumable structure for downstream cron jobs. ## Usage ``` agent-subscriptions [OPTIONS] Options: -n, --dryrun Show what would be probed without making API calls -j, --output json Emit structured JSON to stdout (plain text, no ANSI) -h, --help Show this help message and exit ``` The default mode (no flag) prints a coloured table suitable for terminals. ## Behaviour 1. Read the Anthropic OAuth token from `~/dev/claude/secrets/anthropic/api_key` (whole-file Bearer token). 2. Probe Anthropic subscription usage and reset times: - First attempt: `GET https://api.anthropic.com/v1/models` with `Authorization: Bearer ` — zero token cost. - Read `anthropic-ratelimit-unified-5h-utilization` and `anthropic-ratelimit-unified-7d-utilization` (fractions 0.0–1.0; multiply by 100 for percentage). - Read `anthropic-ratelimit-unified-5h-reset` and `anthropic-ratelimit-unified-7d-reset`. Values are **Unix epoch seconds** (integer string). - Source: https://platform.claude.com/docs/en/api/rate-limits and https://github.com/anthropics/claude-code/issues/12829 (example values `1764554400`, `1764615600`). - If headers absent, fall back: `POST https://api.anthropic.com/v1/messages` with model `claude-haiku-4-5-20251001`, `max_tokens=1`, message `"hi"` — same headers on response (or on the HTTPError if 429). 3. Read the MiniMax API key from `~/dev/claude/projects/agent-runtime-secrets/providers/minimax/v1/provider.sops.env` via `sops --decrypt --output-type dotenv` (key: `ANTHROPIC_AUTH_TOKEN`). Uses `SOPS_AGE_KEY_FILE=~/dev/claude/secrets/sops/provider-age-key.txt`. 4. Probe MiniMax subscription usage and reset times: - `GET https://www.minimax.io/v1/token_plan/remains` with `Authorization: Bearer ` and `User-Agent: curl/7.88.1` (minimax.io blocks Python-urllib default UA). - For the `model_remains[].model_name == "general"` entry: - `five_hour = 100 - current_interval_remaining_percent` - `seven_day = 100 - current_weekly_remaining_percent` - 5-hour reset: `end_time` (Unix epoch **milliseconds**) — divide by 1000 for seconds. - Weekly reset: `weekly_end_time` (Unix epoch **milliseconds**). - Source for response shape: https://github.com/Hukilow/Minimax-usage/blob/main/PLAN.md - v1 fallback (`category_remains[]` where `category == "text_generation"`) carries no reset timestamp fields — show `UNKNOWN` reset and continue. 5. Display the formatted table (default) or JSON (`--output json`). 6. Colour-code the percentage column (default output only): - < 60%: green - 60–80%: yellow - ≥ 80%: red - JSON output is **never** coloured (safe to pipe). ## Reset time fields | Provider | Field | Format | |-----------|---------------------------------------------|---------------------------| | Anthropic | `anthropic-ratelimit-unified-5h-reset` | Unix epoch seconds (int) | | Anthropic | `anthropic-ratelimit-unified-7d-reset` | Unix epoch seconds (int) | | MiniMax | `model_remains[general].end_time` | Unix epoch **milliseconds** | | MiniMax | `model_remains[general].weekly_end_time` | Unix epoch **milliseconds** | Note the unit difference: Anthropic emits seconds; MiniMax emits milliseconds. The implementation normalises both to seconds. If a reset field is absent (header missing on the Anthropic side, or the v1 fallback path on the MiniMax side), the reset cell in the table shows `UNKNOWN` and the JSON field `reset_at` is set to `null` with `reset_in_seconds: null`. `reset_in_seconds` is computed as `reset_at - now` at probe time. ## Dryrun behaviour Prints what it would probe without reading credential files or making HTTP calls. The dryrun mode honours `--output json` and emits a JSON sample with placeholder values that match the live JSON schema (same keys, sample numbers, no API calls performed). Default (no `--output`): ``` [dryrun] Would probe: Anthropic — ~/dev/claude/secrets/anthropic/api_key (Bearer OAuth token) GET https://api.anthropic.com/v1/models MiniMax — ~/dev/claude/projects/agent-runtime-secrets/providers/minimax/v1/provider.sops.env (SOPS) GET https://www.minimax.io/v1/token_plan/remains ``` With `--output json` (or `--dryrun --output json`): ```json { "probed_at": "2026-08-01T14:23:00+00:00", "providers": [ { "provider": "Anthropic", "available": true, "windows": { "five_hour": { "utilization_pct": 23.4, "reset_at": "2026-08-01T19:00:00+00:00", "reset_in_seconds": 16620, "window_seconds": 18000, "elapsed_pct": 0.0767 }, "seven_day": { "utilization_pct": 41.2, "reset_at": "2026-08-07T23:00:00+00:00", "reset_in_seconds": 538200, "window_seconds": 604800, "elapsed_pct": 0.1101 } } }, { "provider": "MiniMax", "available": true, "windows": { "five_hour": { "utilization_pct": 12.1, "reset_at": "2026-08-01T19:00:00+00:00", "reset_in_seconds": 16620, "window_seconds": 18000, "elapsed_pct": 0.0767 }, "seven_day": { "utilization_pct": 8.3, "reset_at": "2026-08-07T23:00:00+00:00", "reset_in_seconds": 538200, "window_seconds": 604800, "elapsed_pct": 0.1101 } } } ] } ``` Note: `probed_at` and the reset values are placeholders — the dryrun output does not depend on the actual wall clock, only on the structure. `window_seconds` is always the fixed constant for that window key (never a placeholder); `elapsed_pct` is derived from `reset_in_seconds` and `window_seconds` so the sample stays internally consistent (shown rounded to 4 decimal places above; the implementation does not round). ## Output format ### Default table ``` Agent Subscription Usage ======================== Provider Window Usage Resets ----------- ---------- -------- ------- Anthropic 5-hour 23.4% 4h 37m Anthropic 7-day 41.2% 6d 8h MiniMax 5-hour 12.1% 2h 11m MiniMax 7-day 8.3% 4d 19h ``` - `Resets` column shows **relative time until the window resets** (`1d 2h`, `12h 23m`, `45m`, `12s`). - 7-day window: days+hours (`6d 8h`). 5-hour window: hours+minutes (`4h 37m`). - `Resets` shows `UNKNOWN` (dim) when the underlying field is absent. - Percentage column is ANSI-coloured (green/yellow/red) when stdout is a TTY; `Resets` is always plain. - JSON output is always plain (no ANSI), regardless of TTY state. ### JSON (`--output json` / `-j`) Top-level keys: - `probed_at` (string, ISO 8601 with timezone offset, UTC if unknown): when the probe was performed. - `providers` (array): one entry per provider in fixed order — `Anthropic` first, then `MiniMax`. Each provider entry: - `provider` (string): `"Anthropic"` or `"MiniMax"`. - `available` (bool): `true` if at least one window returned data; `false` if the whole probe failed. - On failure, add `error` (string) describing the reason and omit `windows`. - On success, add `windows` (object) with both `five_hour` and `seven_day` sub-objects. Each window sub-object: - `utilization_pct` (float, may be `null`): percentage of the window already used (0.0–100.0). - `reset_at` (string ISO 8601, may be `null`): absolute wall-clock time of the next reset. - `reset_in_seconds` (int, may be `null`): seconds from `probed_at` until `reset_at`. Downstream consumers computing "days remaining" divide this by 86400. **Clamped to a minimum of 0** — if the reset epoch is already in the past at probe time (clock skew, or probing in the same second the reset fired), the script clamps the value to `0` itself rather than emitting a negative number. This also pins `elapsed_pct` (below) to `1.0` in that case. - `window_seconds` (int, may be `null`): the nominal length of this window in seconds — a fixed constant, not measured: `18000` for `five_hour`, `604800` for `seven_day`. These are assumed nominal lengths (Anthropic documents 5h/7d unified rate-limit windows; MiniMax v2's `end_time`/`weekly_end_time` fields are consistent with the same cadence) — the script does not attempt runtime verification against either API. `null` only when the window key itself has no defined constant (should not occur for `five_hour`/`seven_day`). - `elapsed_pct` (float, may be `null`): fraction of the window already elapsed, computed as `1 − (reset_in_seconds / window_seconds)`, clamped to `[0.0, 1.0]`. **`null` whenever `reset_in_seconds` is `null`** — i.e. whenever this window has no reset timestamp (the MiniMax v1 fallback path returns utilization with no reset fields). This is a deliberate fail-closed contract: downstream consumers (e.g. an idle-capacity scheduler pacing dispatch against elapsed window time) MUST treat `null` as "cannot pace this window" and never substitute `0.0` or `1.0`. Example live output: ```json { "probed_at": "2026-08-01T14:23:00+00:00", "providers": [ { "provider": "Anthropic", "available": true, "windows": { "five_hour": { "utilization_pct": 23.4, "reset_at": "2026-08-01T19:00:00+00:00", "reset_in_seconds": 16620, "window_seconds": 18000, "elapsed_pct": 0.0767 }, "seven_day": { "utilization_pct": 41.2, "reset_at": "2026-08-07T23:00:00+00:00", "reset_in_seconds": 538200, "window_seconds": 604800, "elapsed_pct": 0.1101 } } }, { "provider": "MiniMax", "available": false, "error": "HTTP 401 Unauthorized" } ] } ``` The downstream consumer (cron job that drafts workload based on subscription usage thresholds) needs `utilization_pct`, `reset_in_seconds`, and `elapsed_pct` for the `seven_day` window. `utilization_pct` and `reset_in_seconds` are guaranteed present (non-null) on the success path; `elapsed_pct` is guaranteed present only when the provider's reset timestamp is available (true for both providers' `v2`/header paths; `null` for MiniMax's `v1` fallback — see Edge cases). ## Edge cases - If the Anthropic token file does not exist, mark Anthropic `available: false`, set `error`, continue. - If SOPS decryption fails (missing key file, wrong key, sops not installed), mark MiniMax `available: false`, set `error`, continue. - If an API call fails for any reason, that provider is `available: false` with `error`. The other provider is still probed. - If a utilization header is present for only one window, the other window has `utilization_pct: null`. The reset field is independent — a window can have a reset timestamp without a utilization value, or vice versa. - If the reset field is absent for a window: `reset_at: null`, `reset_in_seconds: null`, `elapsed_pct: null`. The default table prints `UNKNOWN` in the `Resets` cell. This is the normal case for MiniMax's v1 fallback path (`category_remains[]`), which carries utilization but no reset timestamps. - If `reset_in_seconds` would be negative (reset epoch already passed at probe time), it is clamped to `0` before being emitted — never negative in JSON output. `elapsed_pct` for that window is then `1.0` (fully elapsed), not `null`. - `elapsed_pct` is computed only from `reset_in_seconds` and the fixed `window_seconds` constant; it does not depend on `utilization_pct`. A window can have `elapsed_pct` with `utilization_pct: null`, or vice versa. - `window_seconds` values (`18000`, `604800`) are the same regardless of provider or probe outcome — they are compile-time constants, not derived from any API response. - Exit 0 even if some providers are unavailable (the tool is informational). The only non-zero exit is `--help` is not requested and an unknown flag was passed. - `--output json` and `--dryrun` compose: `--dryrun --output json` emits the dryrun JSON sample without making API calls. ## Examples ```sh agent-subscriptions # live probe, coloured table agent-subscriptions --output json # live probe, machine-readable JSON agent-subscriptions -j # short flag for JSON agent-subscriptions --dryrun # preview only (table) agent-subscriptions --dryrun -j # preview only (JSON sample) agent-subscriptions --help # usage ```