agent-subscriptions: use /api/oauth/usage and profile for Anthropic usage and plan info

Response rate-limit headers are no longer returned. Read the access token
from the Claude Code profile credentials (user:profile scope), report
5h/7d usage and resets, and add plan info with an inferred monthly renewal.

Claude-Session: https://claude.ai/code/session_01Gz3N3q53Bx9pbjB9tZT9dt
This commit is contained in:
Paul O'Reilly
2026-10-14 16:41:22 +13:00
parent 6c69456261
commit 8935d58bd2
2 changed files with 90 additions and 88 deletions

View File

@@ -19,13 +19,10 @@ 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-5-5`, `max_tokens=1`, message `"hi"` — same headers on response (or on the HTTPError if 429).
1. Read the Anthropic OAuth access token from `claudeAiOauth.accessToken` in `$AGENT_SUBSCRIPTIONS_CLAUDE_DIR/.credentials.json` (default `~/.claude-oreillyit-anthropic`). This is the interactive-login credential, which carries the `user:profile` scope; the setup-token key in `secrets/anthropic/api_key` does not and gets 403 `oauth_scope_insufficient`. The file is read-only here: the script never refreshes it. If `expiresAt` has passed, Anthropic is reported unavailable with a "run claude once" hint.
2. Probe Anthropic with `anthropic-beta: oauth-2025-04-20`:
- `GET https://api.anthropic.com/api/oauth/usage` returns `five_hour` and `seven_day` objects with `utilization` (percent, 0-100) and `resets_at` (ISO 8601 string). The old `anthropic-ratelimit-unified-*` response headers are no longer returned and are not used.
- `GET https://api.anthropic.com/api/oauth/profile` (best-effort) returns `organization.rate_limit_tier`, `subscription_status`, `subscription_created_at`. No renewal date is exposed, so `next_renewal_inferred` assumes a monthly cycle on the day-of-month of `subscription_created_at` (day clamped to 28).
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).
@@ -47,12 +44,12 @@ The default mode (no flag) prints a coloured table suitable for terminals.
| Provider | Field | Format |
|-----------|---------------------------------------------|---------------------------|
| Anthropic | `anthropic-ratelimit-unified-5h-reset` | Unix epoch seconds (int) |
| Anthropic | `anthropic-ratelimit-unified-7d-reset` | Unix epoch seconds (int) |
| Anthropic | `usage.five_hour.resets_at` | ISO 8601 string |
| Anthropic | `usage.seven_day.resets_at` | ISO 8601 string |
| 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.
Note the format difference: Anthropic emits ISO strings; 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.
@@ -64,8 +61,8 @@ Default (no `--output`):
```
[dryrun] Would probe:
Anthropic — ~/dev/claude/secrets/anthropic/api_key (Bearer OAuth token)
GET https://api.anthropic.com/v1/models
Anthropic — ~/.claude-oreillyit-anthropic/.credentials.json (Bearer OAuth token)
GET https://api.anthropic.com/api/oauth/usage, /api/oauth/profile
MiniMax — ~/dev/claude/projects/agent-runtime-secrets/providers/minimax/v1/provider.sops.env (SOPS)
GET https://www.minimax.io/v1/token_plan/remains
```
@@ -122,6 +119,10 @@ With `--output json` (or `--dryrun --output json`):
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).
## Subscription info (Anthropic)
Default output prints a line below the table: `Anthropic plan: <tier> (<status>), renews ~<date> (in <rel>, inferred)`. JSON adds an optional `subscription` object to the Anthropic provider entry: `plan`, `status`, `started`, `next_renewal_inferred` (ISO 8601). The object is omitted when the profile call fails. The renewal date is inferred, not authoritative.
## Output format
### Default table