{"slug": "i-built-a-chrome-extension-to-track-ai-token-usage-here-s-how-it-works", "title": "I Built a Chrome Extension to Track AI Token Usage — Here's How It Works", "summary": "A developer built TokenPulse, a Chrome extension that injects a live token usage bar above the input box on Claude, ChatGPT, Gemini, DeepSeek, and Grok. The extension tracks context window usage, rate limits, cost estimates, and daily history without requiring an API key, using content scripts, a background service worker, and Chrome's storage API. It intercepts Claude's internal usage endpoint for exact percentages and estimates tokens for other platforms with approximately ±8% accuracy.", "body_md": "Six weeks ago I got cut off mid-debugging session by Claude's rate limit with no warning. Two hours of context gone. I started looking for a tool that would show me how close I was before it happened. Nothing existed that worked across more than one platform without requiring an API key.\n\nSo I built one.\n\n[TokenPulse](https://token-pulse.in) is a Chrome extension (MV3) that injects a live token bar above the input box on Claude, ChatGPT, Gemini, DeepSeek and Grok. It tracks context window usage, rate limits, cost estimates, and daily history — all from your existing browser session, no API key required.\n\nHere's how it works technically.\n\n```\nContent Scripts (per platform)\n        ↓\nBackground Service Worker\n        ↓\nChrome Storage API (local)\n        ↓\nPopup UI\n        ↓\nDesktop Notifications\n```\n\nThe extension runs a content script on each supported domain. Each script is responsible for:\n\n`chrome.runtime.sendMessage`\n\nThe service worker aggregates data, writes to `chrome.storage.local`\n\n, checks notification thresholds, and serves data to the popup on demand.Claude is the only platform that exposes real rate limit data through its internal API. When you use claude.ai, the browser session makes requests to a usage endpoint that returns exact utilization percentages and reset timestamps.\n\nThe content script intercepts this data by hooking into the platform's network requests using a `MutationObserver`\n\nto detect when Claude updates its state, then reading the cached response.\n\nThe response looks roughly like:\n\n```\n{\n  five_hour: {\n    utilization: 0.82,\n    reset_at: \"2026-07-15T14:14:00Z\"\n  },\n  seven_day: {\n    utilization: 0.34,\n    reset_at: \"2026-07-21T21:00:00Z\"\n  }\n}\n```\n\nThis gives exact percentages — not estimates. The popup shows these directly.\n\nChatGPT, Gemini, DeepSeek and Grok don't expose usage data the same way. For these, TokenPulse estimates token usage from the conversation DOM.\n\nThe estimation approach:\n\n```\nfunction estimateTokens(text) {\n  // ~4 characters per token — standard approximation\n  return Math.ceil(text.length / 4)\n}\n\nfunction getConversationTokens() {\n  const messages = document.querySelectorAll('[data-message-author-role]')\n  let total = 0\n  messages.forEach(msg => {\n    total += estimateTokens(msg.textContent || '')\n  })\n  return total\n}\n```\n\nAccuracy is approximately ±8% — sufficient for knowing whether you're at 20% or 80% of your context window.\n\nThe bar injection uses a `MutationObserver`\n\nto watch for the input box appearing in the DOM, then inserts a container element immediately above it.\n\n``` js\nconst observer = new MutationObserver(() => {\n  const inputBox = document.querySelector(PLATFORM_INPUT_SELECTOR)\n  if (inputBox && !document.getElementById('tp-bar')) {\n    injectBar(inputBox)\n  }\n})\n\nobserver.observe(document.body, {\n  childList: true,\n  subtree: true\n})\n```\n\nThe bar is a thin div with a gradient fill that updates via CSS transition whenever new token data arrives:\n\n``` js\nfunction updateBar(pct) {\n  const fill = document.getElementById('tp-bar-fill')\n  if (fill) fill.style.width = `${pct}%`\n}\n```\n\nThe CSS transition handles the smooth animation — no JavaScript animation loops needed.\n\nCost is estimated by multiplying token count by current model pricing:\n\n``` js\nconst PRICING = {\n  'claude-sonnet-4': { input: 3.00, output: 15.00 },  // per 1M tokens\n  'claude-opus-4':   { input: 15.00, output: 75.00 },\n  'gpt-4o':          { input: 2.50, output: 10.00 },\n  'gemini-2.0-flash':{ input: 0.10, output: 0.40 },\n  'deepseek-v3':     { input: 0.27, output: 1.10 },\n}\n\nfunction estimateCost(tokens, model) {\n  const price = PRICING[model]\n  if (!price) return 0\n  return (tokens / 1_000_000) * price.input\n}\n```\n\nModel detection reads the platform's model selector from the DOM.\n\nNotifications fire at configurable thresholds (default: 75%, 90%, 100%) using Chrome's notifications API. The threshold system is designed to fire once per crossing and reset when usage drops:\n\n``` js\nasync function shouldNotify(key, currentPct, settings) {\n  const thresholds = [75, 90, 100].filter(t => settings[`notify_${t}`])\n  const crossed = thresholds.filter(t => currentPct >= t).pop() || 0\n  const lastNotified = await getLastNotified()\n  const last = lastNotified[key] || 0\n\n  if (crossed <= 0 || crossed <= last) return null\n\n  lastNotified[key] = crossed\n  await saveLastNotified(lastNotified)\n  return crossed\n}\n```\n\nThis prevents notification spam — you get one notification when you cross 75%, another when you cross 90%, and they reset when your usage drops back below the threshold.\n\nChrome's Manifest V3 requirement means the background script is a service worker — not a persistent background page. Service workers can be killed by Chrome at any time, which creates two constraints:\n\n**No persistent state in memory.** Everything goes through `chrome.storage.local`\n\n. The service worker reads from storage on every message handler invocation rather than keeping state in variables.\n\n**Async message handling.** The `chrome.runtime.onMessage`\n\nlistener must handle the async/sync distinction carefully. Fire-and-forget messages (like saving usage data) return `false`\n\nimmediately. Messages that need a response return `true`\n\nand call `sendResponse`\n\nafter the async work completes.\n\n``` js\nchrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {\n  if (msg.type === 'SAVE_USAGE') {\n    // Fire and forget — no response needed\n    Storage.saveUsage(msg.usage)\n    return false\n  }\n\n  if (msg.type === 'GET_ALL_DATA') {\n    // Async response needed\n    Promise.all([\n      Storage.getUsage(),\n      Storage.getHistory(),\n      Storage.getSettings(),\n    ]).then(([usage, history, settings]) => {\n      sendResponse({ usage, history, settings })\n    })\n    return true // keep channel open\n  }\n})\n```\n\n**MV3 CSP is strict.** No inline scripts, no `eval`\n\n, no remote code execution. Every event handler must be attached via `addEventListener`\n\n— no `onclick`\n\nattributes. This caught me early and took a full debugging session to unpack.\n\n**Platform DOM structures change without notice.** Claude, ChatGPT and Gemini update their frontends regularly. Selectors that work today break in a week. The solution is multiple fallback selectors and defensive `?.`\n\naccess throughout.\n\n**Service worker timing is unpredictable.** Chrome can kill and restart the service worker between messages. Code that assumes the worker is alive from a previous message will fail intermittently and be very hard to debug.\n\n**Local storage is fast enough.** I was worried `chrome.storage.local`\n\nwould be too slow for real-time updates. In practice, reads complete in under 5ms and the popup feels instant.\n\nThe extension is open source at [github.com/anu-ship-it/TokenPulse](https://github.com/anu-ship-it/TokenPulse). If you're building a Chrome extension that reads from AI platforms, feel free to look at how the content scripts are structured.\n\n[Install TokenPulse free](https://token-pulse.in) — works on Claude, ChatGPT, Gemini, DeepSeek and Grok with no API key.", "url": "https://wpnews.pro/news/i-built-a-chrome-extension-to-track-ai-token-usage-here-s-how-it-works", "canonical_source": "https://dev.to/anoop_kumar_63925e275ea06/i-built-a-chrome-extension-to-track-ai-token-usage-heres-how-it-works-1701", "published_at": "2026-08-21 12:49:04+00:00", "updated_at": "2026-08-21 13:15:56.251039+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "artificial-intelligence", "large-language-models"], "entities": ["TokenPulse", "Claude", "ChatGPT", "Gemini", "DeepSeek", "Grok", "Chrome"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-chrome-extension-to-track-ai-token-usage-here-s-how-it-works", "markdown": "https://wpnews.pro/news/i-built-a-chrome-extension-to-track-ai-token-usage-here-s-how-it-works.md", "text": "https://wpnews.pro/news/i-built-a-chrome-extension-to-track-ai-token-usage-here-s-how-it-works.txt", "jsonld": "https://wpnews.pro/news/i-built-a-chrome-extension-to-track-ai-token-usage-here-s-how-it-works.jsonld"}}