Releases: drogers0/aistat
v2.2.0
Changelog
v2.1.3
v2.1.2
Highlights
- Codex multi-account support.
aistat usagenow reports per-account rows for Codex (mirroring Claude's nested format), andaistat switch codexrewrites the live~/.codex/auth.jsonbetween stored ChatGPT accounts without a browser round-trip. Identity is resolved from theid_tokensubclaim (JWT decode, no network). - Slot-vs-duration window labeling. Codex windows are labeled by
limit_window_seconds, not slot position — so free-account windows (including OpenAI's recent weekly→monthly quota change) are correctly labeledthirty_dayinstead of being assumed to be the primary 5-hour window. - Provider-scoped
switch/accountsCLI with bulk-on-omission:aistat switch [provider] [--to <id>],aistat accounts list|remove [provider]. Omitting the provider fans out to every provider with ≥2 stored accounts (auto-pick best by headroom).
Codex UX
- Tighter, actionable errors for upstream OAuth failures matched to the real upstream bodies: the refresh-token rotation race (
codex loginto recover) and same-client token revocation (token_revoked/token_invalidated).
Fixes
- fsync the live-credential write on Linux; fail closed on resolver error in
accounts remove(#13).
Internal
- Test suite migrated to table-driven
t.Runsubtests with sharedtestutilhelpers and externalized golden output — no behavior change. SeeTESTING.md.
Full changelog: v2.1.1...v2.1.2
v2.1.1
v2.1.0
Changelog
- 540000f CLAUDE.md: refresh for v2.1.0
- ba00f33 Merge pull request #6 from drogers0/v2.1.0-foundation
- e5a5574 README: trim caching section justification down to user-facing facts
- 429b1b4 accounts: lock on a sentinel file on Linux, not the data file
- b75faa1 accounts: move safeWriter into store_darwin.go
- cda9d90 backoff + usage cache + darwin keychain index fix
- 1e80677 claude: drop the top-level limits mirror and AccountResult.uuid from JSON
- d0a3092 claude: extract recordFetchOutcome for the D8 classification
- 79c83c7 claude: keep AccountResult.Limits in JSON even when nil/empty
- f02b67e claude: route switch's active-usage fetch through Client.FetchUsage
- 542e5c1 claude: unify the switch and reporting usage-read paths through the cache
- a979896 cmd/aistat: extract handleGlobals helper
- bcc17df cred/darwin: drop the partition-list step from WriteClaudeLiveBlob
- 9038dd1 v2.1.0: multi-account Claude reporting + aistat switch
v2.0.0
Changelog
- 2e27430 Code-review hardening (round 10, simplicity-first): five residual findings
- ab8e666 Code-review hardening (round 2): always-on warns, --fake build tag, real UA, dedupe
- 8136765 Code-review hardening (round 5): copilot truncation + classifier split
- 970eb63 Code-review hardening (round 6): exit-code split + ten findings
- 1ab989a Code-review hardening (round 7): ctx-cancel fix + flag grammar + eight findings
- 8a504ce Code-review hardening (round 8) + rename to aistat
- 9118a73 Code-review hardening (round 9, simplicity-first): 33 REVIEW.md findings
- 277efc4 Code-review hardening: shared httpx, strict CLI grammar, CI/release
- df14f18 Merge pull request #5 from drogers0/v2-rewrite
- e5708be ci: pin go-version to 1.22.x and bump staticcheck to 2025.1.1
- 5812126 cmd/usage-check: extract buildProviders; tighten realProviders signature
- b95beed cmd/usage-check: tighten safeStderr parameter type
- 67cec56 copilot: fail closed on unknown plan slug
- dcb7221 cred: drop linux perm check; wrap unsupported-platform error
- 9d5cbf7 cred: support Claude on Linux; share parser between platforms
- 90c1607 go.mod: migrate module path to github.com/drogers0/aistat/v2
- 5b6b3cc httpx: reserve Authorization and User-Agent from ExtraHeaders
- 24a74ac providers, httpx: shared issue URL constant; limit response body to 1 MiB
- 8c5d1bc providers: extract ProjectURL constant
- c64e213 providers: unify on ctx-based timeouts; codex KnownWindows tripwire
- 45dcf83 render/text: drop redundant empty-requested guard
- 6cf1528 render: short-circuit Text on empty section list
- 9659f5e tests: warn-wiring + textLabels drift tripwires
- abd4e4d v2.0.0: rewrite as a single static Go binary
v1.0.0 — TypeScript + Chrome extension (reference implementation)
v1.0.0 — TypeScript + Chrome extension (reference implementation)
This is the stable, feature-complete state of the original llm-usage architecture: a TypeScript CLI that reads provider usage data by driving an authenticated Chrome browser session through an MV3 extension and a native messaging host.
It's tagged at the same commit as v0.0.3. Bumping to v1.0.0 marks this as the canonical reference for this architecture — v2.0.0 will be a Go rewrite that drops the browser pipeline entirely.
What this implementation does
usage-check invokes a Chrome extension via AppleScript, opening a hidden 1×1 window with tabs to claude.ai, chatgpt.com, and github.com. Each provider tab runs a scripted fetch() (or DOM scrape, for Copilot) inside the page context — inheriting the user's existing browser session cookies — and the result is relayed back through a native messaging host into a JSON cache file that the CLI renders.
Why it exists this way
When this tool was built, the only path to per-account usage data went through endpoints that required an authenticated web session:
claude.ai/api/organizations/<org>/usageis Cloudflare-fronted and rejects non-browser traffic even with a valid bearer tokenchatgpt.com/backend-api/wham/usageaccepts a bearer but the path was undocumented- GitHub Copilot exposed quota only via the rendered
/settings/billingpage
A direct CLI HTTP client couldn't reach any of them. Hence: drive a real Chrome instance, reuse the user's session cookies, shuttle results out over native messaging.
What's reusable for other scraping projects
If you need to scrape a Cloudflare-protected or cookie-gated surface from a CLI on macOS, the pattern here is a working blueprint:
- AppleScript trigger (
bin/usage-check→osascript) wakes a hidden Chrome window — keeps Chrome backgrounded, no Dock icon flash - MV3 extension with deterministic ID via manifest key (
extension/manifest.json) — avoids the "paste your extension ID" install step - Per-provider abstraction (
src/extension/providers/base.ts+claude.ts,codex.ts,copilot.ts) — clean place to add new sites - Hidden-tab pattern —
chrome.windows.create({state:"minimized",width:1,height:1})thenchrome.scripting.executeScriptinto provider tabs - Native messaging host (
native-host/) registered at~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.llm_usage.cache_host.json— extension ↔ host stdio with framed JSON, host writes to disk, CLI reads cache file - Install flow (
install.sh,usage-check-setup) — packages the extension load, native host registration, and EXTENSION_ID resolution into one command
What to redo if you fork the pattern
- The AppleScript shim is the flakiest piece — a small Swift binary or JXA script would be more robust
- The native messaging host adds setup friction; a localhost HTTP relay (see the unmerged
http-cache-serverbranch in this repo) is a simpler approach if the local-port security tradeoff is acceptable - CLI polling the cache file is racy under load — the HTTP relay path also fixes this
Limitations
- macOS only (AppleScript)
- Chrome only (MV3 + native messaging registration is Chrome-specific)
- Requires the user to be logged into all three services in Chrome
- Cloudflare can change challenge behavior at any time and break the Claude path silently
- Extension install requires Developer Mode in
chrome://extensions
Why v2.0.0 is coming
After this release shipped, it turned out the on-disk OAuth tokens that each vendor's CLI (claude, codex, gh) already stores can be used directly against:
api.anthropic.com/api/oauth/usage(verified — returns the same shape as theclaude.aiendpoint)chatgpt.com/backend-api/wham/usage(the same endpoint the extension hits, just with the bearer instead of a cookie)- GitHub's billing API via
gh apiusing the existinggh authtoken
No browser, no cookies, no AppleScript, no Cloudflare exposure. v2.0.0 will be a pure Go CLI that reads those tokens and makes three HTTP calls. The entire extension/, native-host/, install.sh, and AppleScript shim are deleted.
Branches preserved as references
http-cache-server— single-commit experiment replacing the native messaging host with a localhost HTTP relay. Kept as a reference for that simpler approach.- PR #3 (
issue-2-service-worker-startup) — draft work on MV3 service-worker startup wake. Obsoleted by the v2.0.0 rewrite (no service worker exists in the Go version).