Skip to content

v1.0.0 — TypeScript + Chrome extension (reference implementation)

Choose a tag to compare

@drogers0 drogers0 released this 26 May 19:56
· 62 commits to main since this release

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>/usage is Cloudflare-fronted and rejects non-browser traffic even with a valid bearer token
  • chatgpt.com/backend-api/wham/usage accepts a bearer but the path was undocumented
  • GitHub Copilot exposed quota only via the rendered /settings/billing page

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-checkosascript) 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 patternchrome.windows.create({state:"minimized",width:1,height:1}) then chrome.scripting.executeScript into 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-server branch 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 the claude.ai endpoint)
  • 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 api using the existing gh auth token

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).