Elapsed is null when a window has no reset timestamp (MiniMax v1 fallback) so pacing consumers fail closed; negative reset_in_seconds clamps to 0. Claude-Session: https://claude.ai/code/session_01YQDoWNM7XPPii28khFWoMc
228 lines
12 KiB
Markdown
228 lines
12 KiB
Markdown
# 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 <token>` — 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 <key>` 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
|
||
``` |