quotch 0.5.4

Fast cross-platform CLI for AI coding-agent usage limits
# quotch

`quotch` ("quota + check") prints subscription-plan usage for AI coding agents — fast, cross-platform, and JSON-native.

It reads the usage limits your agents actually enforce. For Claude Code that means the **authoritative numbers from Anthropic's OAuth usage endpoint** — the same figures the service bills against — not a log-file estimate that drifts from reality. Credentials are read-only: quotch reads each agent's own credential store and never writes or refreshes tokens. One provider failing never affects the others. Humans get colored bars; AI agents get a frozen JSON contract.

## Providers

| Provider          | Status     | Windows                                        |
|-------------------|------------|------------------------------------------------|
| Claude Code       | ✅ shipped | 5h session, 7d weekly (all models), 7d per-model |
| GitHub Copilot    | ✅ shipped | monthly premium interactions (request counts)  |
| Codex             | ✅ shipped | 5h + weekly (Plus/Pro) or monthly (Go)         |
| Antigravity       | ✅ shipped | 5h + weekly per model group (via agy / Google) |

## Install

From crates.io:

```bash
cargo install quotch
```

From git or a local checkout:

```bash
cargo install --git https://github.com/sibincbaby/quotch
cargo install --path .
```

Prebuilt binaries are attached to each GitHub Release: static musl on Linux, macOS (arm64 + x64), and Windows — produced by the release workflow on version tags.

## Usage

### Humans

```
$ quotch
claude · max  5h        ████░░░░░░░░░░░░░░░░  19%  resets 3h 26m
              7d        ████░░░░░░░░░░░░░░░░  21%  resets 5d 22h
              7d:fable  ███████░░░░░░░░░░░░░  35%  resets 5d 22h
```

On a TTY the bar and percentage are colored by utilization: green < 70, yellow < 90, red ≥ 90. Non-`ok` snapshots render as a single line:

```
claude (stale 2m)  ...        # header suffix when serving cached data after a failed fetch
codex  ⚠ not logged in (run: codex)    # auth_missing — log in with the provider's own CLI
codex  ✗ network: timed out    # error — the message comes from the provider
```

### AI agents

Always use `--json`. The envelope is a **frozen v1 contract**:

```bash
quotch --json            # machine output, served from cache (≤ 60s old)
quotch --json --raw      # also include each provider's untouched response under "raw"
quotch --refresh         # bypass cache and failure backoff, force a live fetch
                         #   (for Codex, also refreshes an expired token via `codex doctor`)
```

```json
{
  "v": 1,
  "generated_at": "2026-07-19T10:00:00Z",
  "snapshots": [
    {
      "provider": "claude",
      "account": "default",
      "plan": "max",
      "status": "ok",
      "fetched_at": "2026-07-19T09:59:59Z",
      "windows": [
        { "key": "5h", "kind": "rolling", "unit": "percent",
          "used_pct": 51.0, "used": null, "limit": null,
          "unlimited": false, "resets_at": "2026-07-19T13:20:00Z" }
      ]
    }
  ]
}
```

Full JSON Schema: [`docs/schema.json`](docs/schema.json). Agent-facing usage notes: [`skill/SKILL.md`](skill/SKILL.md).

**Frozen-schema guarantee.** `v` is `1`. Evolution is additive only: new fields, new `status` values, and new window `key`s may appear, but existing fields never change meaning or type. **Consumers must ignore unknown fields** and never assume a fixed set of window keys.

- `status`: `ok` | `stale` (live fetch failed, cached data shown — check `fetched_at` for age) | `auth_missing` | `error` (see `error`). (`not_detected` is reserved and not currently emitted — undetected providers are omitted from the array.)
- `used_pct` is always present (0–100, may exceed 100 on overage). `used`/`limit` are populated only for request/credit-count providers.
- Window `key`: `5h` rolling session, `7d` weekly all-models, `7d:<model>` weekly per-model, `5h:<group>` / `7d:<group>` per-model-group (Antigravity), `monthly` / `monthly:<key>` calendar month (Copilot premium requests), plus unknown keys.

**Exit codes.** Always `0` — per-provider problems are data in `status`, never process failures. Parse stdout; do not branch on exit code. `2` is returned only for CLI usage errors.

## For AI agents

quotch ships an agent skill inside the binary. Install it so an agent knows when and how to check quota:

```bash
quotch skill install               # ~/.claude/skills/quotch/SKILL.md  (user scope)
quotch skill install --project     # ./.claude/skills/quotch/SKILL.md
quotch skill install --dir <path>  # <path>/quotch/SKILL.md
quotch skill print                 # write the skill to stdout
```

`SKILL.md` tells an agent to prefer `quotch --json`, how to read the envelope, when to warn before token-heavy work (highest `used_pct` is the binding constraint; ≥ 90 means warn and cite `resets_at`), and useful `jq` one-liners. The skill is embedded in the binary, so **reinstall after upgrading quotch** to pick up the latest version.

## Architecture

```
src/
  main.rs        arg parsing, warm-cache fast path, concurrent per-account fetches, output
  model.rs       Account, Snapshot, Window, Status — the internal + wire types
  registry.rs    all() — the list of active providers
  cache.rs       atomic on-disk cache, 60s TTL
  render/        json.rs (frozen v1 envelope) + line.rs (human bars)
  providers/     mod.rs (Provider trait) + claude.rs, codex.rs, copilot.rs, antigravity.rs
```

Each provider reads credentials inline today; a shared `creds.rs` helper may be
extracted once multi-account discovery lands.

Every provider implements one trait:

```rust
pub trait Provider: Sync {
    fn id(&self) -> &'static str;
    fn discover(&self) -> Vec<Account>;                              // offline, cheap, no network
    fn fetch(&self, acct: &Account) -> Result<Snapshot, FetchError>; // blocking, own timeout
}
```

An `Account` is a **credential source, not a person** — its `id` is offline-derivable (`default`, a label, or a hash of a path). The cache is keyed `provider:account`, so multiple accounts of one provider never collide. Discovery is offline and cheap; only stale/missing pairs hit the network, each on its own thread, so a slow or broken provider can't stall the others.

### Adding a provider

1. Implement `Provider` in `src/providers/<name>.rs`.
2. Return one `Account` per credential source from `discover()` (offline, no network).
3. In `fetch()`, call the provider's usage endpoint and map its native response into one or more `Window`s.
4. Register it in `registry.rs::all()`.

## Roadmap

- multi-account per provider (Codex `--auth` files, multiple Google/GitHub logins)
- statusline / tmux renderers
- usage history and burn-rate

## License

MIT