A Chrome extension (Manifest V3) that tracks your Claude.ai usage with:
- In-composer strip — a small pill embedded inside the chat composer (just under the project chip), showing session %, reset time, and messages-left when known.
- Toolbar popup — Session (5-hour) + Weekly card auto-labelled with whichever Claude model you're actively using. Clicking the toolbar icon immediately fetches fresh data.
- Threshold notifications — desktop alerts when usage crosses percentages you configure.
- Auto-scroll — optionally keeps the chat pinned to the bottom while Claude is replying, so the latest text stays in view. Only engages when you're already near the bottom, so scrolling up to read history is never interrupted. Toggle it in Settings → Reading experience (on by default).
- Unzip the folder.
- Open
chrome://extensions(oredge://,brave://, etc.). - Toggle Developer mode on.
- Click Load unpacked → select the
claude-usage-meterfolder. - Sign in to claude.ai in the same browser profile.
- Open a claude.ai chat — the usage strip appears inside the composer area.
If upgrading from an earlier version, remove the old install first at chrome://extensions.
Polling. The service worker calls GET https://claude.ai/api/organizations/{id}/usage every N minutes (default 1, configurable). The endpoint returns a snapshot like:
{
"five_hour": { "utilization": 34, "resets_at": "..." },
"seven_day": { "utilization": 72, "resets_at": "..." },
"seven_day_opus": { "utilization": 93, "resets_at": "..." }
}Cookies are sent automatically because manifest.json declares host_permissions for claude.ai.
Model detection. A content script scans the page DOM for the model selector button (text matching Claude (Opus|Sonnet|Haiku) [version]) and reports the active family to the service worker. The popup's Weekly card uses that family to pick the matching scope:
- Opus user →
seven_day_opus, withseven_dayas fallback - Sonnet/Haiku user →
seven_day_sonnet/seven_day_haikuif present, elseseven_day - Unknown / no claude.ai tab open → just
seven_day, labelled "Weekly"
Messages-left. Comes from intercepting the message_limit event embedded in Claude's chat completion SSE stream. Only populates after you send a message under the installed extension.
Click toolbar icon = fresh fetch. The popup fires a force-refresh on open with a pulsing loading indicator, then renders.
The content script finds the contenteditable input, walks up to the composer wrapper (<fieldset> typically), and inserts the strip as the wrapper's first child. A MutationObserver keeps it remounted across React rerenders and chat navigations. Click × to hide for 12 hours.
claude-usage-meter/
├── manifest.json MV3 manifest
├── background.js Service worker — polling, scope selection, notifications
├── content-script.js Isolated-world: composer-mounted strip + model detection
├── page-injector.js Main-world: tees SSE bodies for message_limit
├── overlay.css Composer-strip styles (adapts to light/dark)
├── popup/ Toolbar popup UI
├── options/ Settings page
└── icons/
| Symptom | Try |
|---|---|
| Popup empty | Wait ~3s after opening — fresh fetch runs on open. If still empty, error banner will explain why. |
| "Not signed in to claude.ai" | Sign in in the same browser profile. |
| Strip not appearing in chat | Reload the claude.ai tab. If you hit × earlier, popup → Reset clears the dismiss. |
| "Weekly" label not showing model name | The content script may not have detected the picker yet — wait a few seconds, or open the model dropdown once. |
| Messages-left never shows | Send a fresh message; it populates from the response SSE stream. |
| Live diagnostics | chrome://extensions → service worker → Console. |
/usageand the SSEmessage_limitshape are undocumented and can change.chrome.alarmsminimum period is 1 minute.- Tracks claude.ai web usage, not API key usage.
storage,alarms,notifications,tabs— local state, polling, alerts, broadcasting to claude.ai tabs.host_permissions: claude.ai— to call the usage endpoint and inject the strip.
Nothing leaves your browser.