Skip to content

Ration v0.1 — AI quota tracker extension (Claude + Codex) - #1

Merged
devudilip merged 9 commits into
mainfrom
claude/ration-quota-tracker-g3uwc6
Aug 28, 2026
Merged

devudilip merged 9 commits into
mainfrom
claude/ration-quota-tracker-g3uwc6

Conversation

@devudilip

Copy link
Copy Markdown
Owner

What this is

First working version of Ration — an MV3 Chrome extension that shows remaining quota across AI subscriptions in one click, so you can decide which tool to spend on this task without visiting each provider's settings page.

What's included

  • Two providers: Claude (claude.ai) and Codex (chatgpt.com), both riding the user's existing browser session via fetch(..., { credentials: 'include' }) with per-provider optional host permissions requested at enable time. No cookies permission, no token handling of any kind — tests assert no Authorization header is ever constructed.
  • Popup: cards sorted by headroom descending (most capacity on top), per-lane bars, reset countdowns, staleness header, dark mode, honest error/logged-out cards, provider enable toggles, one-click "Clear all data".
  • Badge: lowest headroom across enabled providers — quiet above 40%, amber 15–40%, red below 15%, grey ! on any error or stale reading.
  • Service worker: 5-minute alarm refresh, persisted exponential backoff (5→60 min with jitter), hard floor of 1 request/provider/minute, all state in versioned chrome.storage.local keys (MV3 workers are ephemeral).
  • Zero runtime dependencies — hand-rolled ~90-line schema validator instead of zod; a shape mismatch is always an error state, never a coerced zero. CI fails if a runtime dependency ever appears.
  • Extensibility: adding a provider is one adapter file + one registry line + one manifest origin + fixtures/tests; CONTRIBUTING.md documents the contract with a worked Cursor example.
  • Open-source hygiene: README with legal/privacy commitments (including the Anthropic OAuth-token constraint — this project never touches Claude Code tokens), issue templates (including an "adapter broken" fast path for endpoint drift), GitHub Actions CI, Apache-2.0.

Verification

  • npm run ci green: strict typecheck, 54 tests, build. Tests cover the validator, backoff math, badge thresholds/precedence, headroom min-across-lanes logic, refresh gating against a faked chrome.*, and both adapters against fixtures (happy path per schema variant, 401→unauthenticated, 429→rate_limited, garbage→schema_mismatch, never-throws).
  • Popup visually smoke-tested in headless Chromium with stubbed storage: light, dark, and error states render correctly.

Honest limitation: this was built in a container with no logged-in provider sessions, so the real endpoints could not be hit pre-merge. The compensation is structural: every network outcome is an enumerated, tested state — the worst live-world case is a grey ! with a precise error code, never a wrong number. The claude.ai usage endpoint in particular is a runtime probe with an explicit endpoint_not_verified state and a community capture path in the issue templates.

To verify locally: npm install && npm run build, load dist/ unpacked at chrome://extensions, toggle on providers while logged into their sites, and cross-check the percentages against each provider's own usage page.

🤖 Generated with Claude Code

https://claude.ai/code/session_015gwwHsEgVxtmfRn3dEkBif


Generated by Claude Code

Zero runtime dependencies by design (see PRD store-review mitigations):
typescript, esbuild, vitest and @types/chrome as dev-only tooling.
build.mjs bundles the two MV3 entry points and copies public/ to dist/.
scripts/gen-icons.mjs generates the committed PNG icons with node
built-ins only, so no image tooling is required to regenerate them.
The ProviderAdapter/ProviderSnapshot/QuotaLane contract every provider
normalizes to, with headroomPct as the universal comparator (a provider's
headroom is the minimum across its lanes — you're blocked by whichever
ceiling you hit first).

The validator is a hand-rolled ~90-line combinator library instead of zod,
keeping the extension at zero runtime dependencies. A shape mismatch fails
with a precise path and is never coerced to zero, so endpoint drift always
surfaces as an honest error state rather than a wrong number.
All state lives in chrome.storage.local under versioned v1: keys —
MV3 service workers are ephemeral, so nothing survives in memory.
Backoff is pure math (5 min base, doubling, 60 min cap, ±25% jitter
with injectable randomness for deterministic tests) over a persisted
record, so polite-polling state survives worker restarts too.
Rides the existing chatgpt.com browser session with credentials:'include'.
Parses both observed field-name conventions (primary_window/secondary_window
and five_hour_limit/weekly_limit) as named schema variants matched on
literal field names, plus additional_rate_limits[] as extra lanes.
Every failure path — 401/403, 429, 5xx, non-JSON body, network error,
unknown shape — is an enumerated snapshot status; the adapter never throws
and never renders an unparseable response as a number.

Fixture tests cover both variants, all error paths, and assert that no
Authorization header is ever sent.
Rides the user's existing claude.ai browser session only — the adapter
never constructs an Authorization header (asserted by a test on every
request), never touches token or cookie values, and the extension never
requests the cookies permission. This keeps the project clear of
Anthropic's restriction on Claude Code / claude.ai OAuth tokens.

Org id is discovered via /api/organizations (chat-capable org preferred)
and cached. The usage endpoint behind Settings -> Usage is undocumented,
so the adapter probes a short candidate list; the first path that returns
200 AND validates is cached so steady-state refreshes make one request.
If nothing verifies the honest result is an endpoint_not_verified error,
and the issue templates give the community a DevTools capture path to
pin the real endpoint.

Also adds the adapter registry (src/adapters/index.ts) — adding a future
provider is one file plus one registry line.
The service worker is wiring only — every handler reloads state from
chrome.storage.local because MV3 workers are ephemeral. Refresh
orchestration encodes the politeness policy: a hard floor of one request
per provider per 60s regardless of trigger (persisted before the fetch so
it survives worker death mid-flight), the persisted backoff gate, and a
freshness gate so popup opens reuse recent snapshots.

The badge shows the lowest headroom across enabled providers — the wall
you hit first — going amber at 40%, red below 15%, grey ! on any error
or stale snapshot, and staying invisible when everything is fine.
computeBadge is pure and boundary-tested; refresh gating is tested
against an in-memory chrome fake.
The popup reads only from chrome.storage.local: it renders the last
snapshots instantly, asks the worker for a refresh, and re-renders as
results stream in via storage.onChanged — no network on the render path.
Cards sort by headroom descending (the routing answer), with per-lane
bars, reset countdowns and a staleness header; error and logged-out
states render as honest cards, and disabled providers appear as toggles
that request their host permission at enable time (user gesture).

Vanilla DOM via createElement/textContent only — provider-derived strings
never meet innerHTML. MV3 manifest requests just storage+alarms up front;
claude.ai and chatgpt.com are optional_host_permissions. No cookies
permission.
README carries the commitments that belong in front of users, not just
in a PRD: zero telemetry and no credential access, the Anthropic OAuth
constraint (Ration never touches Claude Code tokens), and the honest
warning that these are undocumented endpoints — breakage looks like a
grey !, never a wrong number, and the adapter-broken issue template
gives the community a redacted DevTools capture path to fix it fast.
CONTRIBUTING documents the five-artifact adapter contribution with the
non-negotiable rules (never throw, never coerce to zero, match raw
field names) and a worked Cursor example. LICENSE placeholder filled.
Runs npm ci, typecheck, vitest, and the esbuild build on every PR and
push to main; sanity-parses the built manifest, fails if any runtime
dependency ever appears in package.json, and uploads dist/ as the
ration-unpacked artifact — most of eventual store packaging for free.
@devudilip
devudilip force-pushed the claude/ration-quota-tracker-g3uwc6 branch from 5488cbe to 883b628 Compare August 28, 2026 20:11
@devudilip
devudilip merged commit c0b7a4a into main Aug 28, 2026
1 check passed
@devudilip
devudilip deleted the claude/ration-quota-tracker-g3uwc6 branch August 28, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant