One dashboard to track every AI token and dollar across all providers.
Token Monitor is a privacy-first desktop application that gives you real-time visibility into your AI spending across Anthropic, OpenAI, Google Gemini, OpenRouter, Claude Code, and more — all from a single dashboard. Every byte of data stays on your machine.
- Features
- Architecture
- Supported Providers
- Prerequisites
- Getting Started
- Project Structure
- How It Works
- Browser Extension
- OpenClaw Skill
- Configuration
- Views & Personas
- Database Schema
- API & Ports
- Building for Production
- Troubleshooting
- License
- Multi-provider tracking — Monitor tokens and costs across 10+ AI provider types from one place
- Real-time updates — Live token counts, cost tracking, and sparkline charts as you use AI
- Exact & estimated data — API-level exact counts from direct integrations; character-based estimates from browser extensions
- Privacy-first — All data stored locally in SQLite. API keys encrypted via OS keychain (
safeStorage). No telemetry, no cloud sync - Multiple views — Widget (casual), Grid (builder), and Command Center (power user) dashboards
- Budget alerts — Set spending limits with threshold warnings (75%, 90%, 100%) and optional hard caps
- Local proxy — Transparent HTTP proxy that intercepts API calls to capture usage without modifying your workflow
- Browser extension — Track consumer usage on claude.ai, chatgpt.com, and gemini.google.com
- File watcher — Auto-detect Claude Code sessions by watching
~/.claude/projects/JSONL logs - Export — CSV and JSON export of all usage data
- Dark mode — System-aware theming with manual override
┌──────────────────────────────────────────────────────────┐
│ Desktop App (Electron) │
│ │
│ ┌───────────┐ ┌──────────┐ ┌───────────────────────┐ │
│ │ Renderer │ │ Main │ │ Services │ │
│ │ (React) │◄─┤ Process │──┤ │ │
│ │ │ │ │ │ DataEngine │ │
│ │ Zustand │ │ IPC │ │ ├── Adapters │ │
│ │ Stores │ │ Tray │ │ │ ├── Anthropic │ │
│ │ │ │ Preload │ │ │ ├── OpenAI │ │
│ │ Views: │ │ │ │ │ ├── Gemini │ │
│ │ ▸ Widget │ │ │ │ │ ├── OpenRouter │ │
│ │ ▸ Grid │ │ │ │ │ ├── Claude Code │ │
│ │ ▸ CmdCtr │ │ │ │ │ ├── Copilot │ │
│ │ ▸ Settings│ │ │ │ │ ├── Browser Ext │ │
│ │ ▸ Onboard │ │ │ │ │ └── OpenClaw │ │
│ └───────────┘ └──────────┘ │ │ │ │
│ │ Database (SQLite) │ │
│ │ Proxy (:7878) │ │
│ │ WebSocket (:7879) │ │
│ └───────────────────────┘ │
└─────────────────┬──────────────────────┬─────────────────┘
│ │
┌───────────┘ └──────────┐
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Browser │ WebSocket :7879 │ OpenClaw │
│ Extension │─────────────────────────► │ Skill │
│ (MV3) │ │ POST :7878 │
│ ▸ claude.ai│ └──────────────┘
│ ▸ chatgpt │
│ ▸ gemini │ ┌───────────────────┐
└─────────────┘ │ AI Provider APIs │
│ (via proxy :7878 │
│ or direct) │
└───────────────────┘
Data flows:
- Proxy path — Point your API base URL to
http://localhost:7878/{provider}/...→ proxy forwards to the real API → captures the response → adapter extracts tokens/cost → engine persists to DB + pushes to renderer - File watcher — Claude Code writes JSONL to
~/.claude/projects/→chokidarwatches for changes → parses usage lines → engine ingests - Browser extension — Content scripts intercept
fetchon AI platforms → estimate tokens → send via WebSocket to desktop → engine ingests (quality:estimated) - OpenClaw skill — After each AI response → HTTP POST to
:7878/api/usage→ engine ingests - Polling — OpenAI and OpenRouter adapters poll provider APIs periodically for backfill data
| Provider | Type | Connection Method | Data Quality | Notes |
|---|---|---|---|---|
| Anthropic API | anthropic_api |
API Key / Proxy | Exact | Reads usage from API responses |
| OpenAI API | openai_api |
API Key / Proxy | Exact | + Usage API polling every 5 min |
| Google Gemini | gemini_api |
API Key / Proxy | Exact | Reads usageMetadata from responses |
| OpenRouter | openrouter |
API Key / Proxy | Exact | Uses x-openrouter-cost header + generation history polling |
| Claude Code | claude_code |
File Watcher | Exact | Watches ~/.claude/projects/ JSONL — no API key needed |
| OpenClaw | openclaw |
Skill (HTTP) | Exact | Receives data from OpenClaw skill via HTTP POST |
| Claude.ai (Consumer) | claude_consumer |
Browser Extension | Estimated | Intercepts fetch, estimates from response text |
| ChatGPT (Consumer) | chatgpt_consumer |
Browser Extension | Estimated | Intercepts fetch on /backend-api/conversation |
| Gemini (Consumer) | gemini_consumer |
Browser Extension | Estimated | Intercepts fetch, parses nested JSON responses |
- Node.js ≥ 20.0.0
- pnpm ≥ 9.0.0
- macOS (primary target — uses
hiddenInsettitlebar, macOS tray)
# Install pnpm if you don't have it
npm install -g pnpm@9
# Verify versions
node -v # Should be v20+
pnpm -v # Should be v9+Note: If you use
nvm, make sure to switch to Node 20+ before running any commands:nvm use 20 # or nvm use 22
# 1. Clone the repository
git clone https://github.com/Git-push-01/token-monitor.git
cd token-monitor
# 2. Install dependencies
pnpm install
# 3. Rebuild native modules for Electron
cd apps/desktop
npx @electron/rebuild -f -w better-sqlite3
cd ../..
# 4. Start the development server
pnpm devThis launches:
- Vite dev server at
http://localhost:5173(renderer hot-reload) - Electron main process with the desktop window
- Local proxy on port
7878 - WebSocket server on port
7879
On first launch you'll see the Onboarding screen where you pick a persona:
- Casual 💬 — Simple widget view with a daily spend ring
- Builder 🛠 — Grid of provider cards with sparklines
- Power User 🦞 — Full command center with tables, analytics, and budgets
Then head to Settings to connect your first provider.
token-monitor/
├── package.json # Root workspace config
├── pnpm-workspace.yaml # pnpm workspace definition
├── tsconfig.json # Root TypeScript config
│
├── apps/
│ ├── desktop/ # Electron + Vite + React desktop app
│ │ ├── src/
│ │ │ ├── main/ # Electron main process
│ │ │ │ ├── index.ts # Window creation, service init
│ │ │ │ ├── ipc.ts # IPC handlers (CRUD, export, pairing)
│ │ │ │ ├── preload.ts # contextBridge API for renderer
│ │ │ │ └── tray.ts # macOS menu bar tray
│ │ │ ├── renderer/ # React frontend
│ │ │ │ ├── App.tsx # Root component, routing, real-time events
│ │ │ │ ├── components/
│ │ │ │ │ ├── BudgetBar.tsx # Budget progress bar
│ │ │ │ │ ├── ProviderCard.tsx # Provider instance card
│ │ │ │ │ ├── Sparkline.tsx # SVG mini-chart
│ │ │ │ │ └── UsageRing.tsx # Circular progress ring
│ │ │ │ ├── store/
│ │ │ │ │ ├── useInstances.ts # Live instance state
│ │ │ │ │ ├── useProviders.ts # Provider CRUD
│ │ │ │ │ └── useSettings.ts # User preferences
│ │ │ │ └── views/
│ │ │ │ ├── CommandCenter.tsx # Power user dashboard
│ │ │ │ ├── Grid.tsx # Builder grid view
│ │ │ │ ├── Onboarding.tsx # First-run persona picker
│ │ │ │ ├── Settings.tsx # Provider mgmt, preferences
│ │ │ │ └── Widget.tsx # Casual daily spend view
│ │ │ └── services/ # Core backend services
│ │ │ ├── database.ts # SQLite schema, queries, aggregations
│ │ │ ├── engine.ts # DataEngine: event bus, adapter registry
│ │ │ ├── proxy.ts # HTTP proxy on :7878
│ │ │ ├── tokenizer.ts # Fallback token estimation
│ │ │ ├── websocket.ts # WS server on :7879
│ │ │ └── adapters/ # Provider-specific adapters
│ │ │ ├── anthropic.ts
│ │ │ ├── openai.ts
│ │ │ ├── gemini.ts
│ │ │ ├── openrouter.ts
│ │ │ ├── claude-code.ts
│ │ │ ├── browser-ext.ts
│ │ │ └── openclaw.ts
│ │ ├── vite.config.ts
│ │ ├── tailwind.config.js
│ │ └── package.json
│ │
│ └── extension/ # Chrome browser extension (MV3)
│ ├── manifest.json
│ ├── background.ts # WebSocket bridge to desktop
│ ├── content/
│ │ ├── claude.ts # Intercepts claude.ai fetch
│ │ ├── chatgpt.ts # Intercepts chatgpt.com fetch
│ │ └── gemini.ts # Intercepts gemini.google.com fetch
│ └── popup/
│ ├── popup.html # Extension popup UI
│ └── popup.ts # Pairing token input, status display
│
└── packages/
├── shared/ # Shared types, pricing, constants
│ └── src/
│ ├── types.ts # All TypeScript interfaces
│ ├── pricing.ts # Model pricing table + cost calculator
│ ├── constants.ts # Provider definitions, ports, tier limits
│ └── index.ts # Re-exports
│
└── openclaw-skill/ # OpenClaw integration skill
├── index.js # POST usage data to desktop app
├── manifest.json # Skill trigger config
└── README.md
The DataEngine is the heart of the application. It:
- Ingests events (
UsageEventV1) from any adapter or external source - Normalizes total token counts
- Calculates cost using the shared pricing table (
calculateCost) - Persists to SQLite — individual record + hourly/daily aggregates atomically
- Updates in-memory state — instance map with running totals and sparkline data (last 30 points)
- Broadcasts to renderer via IPC (
usage:event) - Checks budgets against thresholds, fires
budget:alertif exceeded
Each adapter implements a common interface:
interface ProviderAdapter {
type: ProviderType;
start(): void;
stop(): void;
testConnection(): Promise<{ valid: boolean; info?: string }>;
}Adapters use different strategies:
- Proxy intercept — Anthropic, OpenAI, Gemini, OpenRouter parse response bodies captured by the local proxy
- File watching — Claude Code uses
chokidarto watch JSONL session logs - API polling — OpenAI (Usage API every 5 min), OpenRouter (generation history every 30s)
- Passive receive — Browser Extension (via WebSocket) and OpenClaw (via HTTP POST)
The proxy on port 7878 acts as a transparent pass-through:
Your Code → localhost:7878/anthropic/v1/messages → api.anthropic.com/v1/messages
↓
Capture response
Extract usage data
Forward to DataEngine
To use it, set your API base URL:
# Anthropic
export ANTHROPIC_BASE_URL=http://localhost:7878/anthropic
# OpenAI
export OPENAI_BASE_URL=http://localhost:7878/openai
# Gemini
export GEMINI_BASE_URL=http://localhost:7878/gemini
# OpenRouter
export OPENROUTER_BASE_URL=http://localhost:7878/openrouterThe shared pricing.ts contains per-model pricing (USD per 1M tokens) for all supported models:
- Anthropic: Claude Opus 4, Sonnet 4, 3.5 Sonnet, Haiku 3.5 (with cache read/write prices)
- OpenAI: GPT-4.1, GPT-4.1-mini/nano, GPT-4o/mini, o3/o3-mini/o4-mini
- Google: Gemini 2.5 Pro/Flash, 2.0 Flash, 1.5 Pro/Flash
- OpenRouter: Uses provider-reported cost from
x-openrouter-costheader
Cost calculation handles input, output, cache read, cache write, and reasoning tokens with 6-decimal precision. Model name matching is fuzzy (strips date suffixes, tries prefix matching).
- API keys encrypted at rest via Electron
safeStorage(OS keychain) and stored as base64 in SQLite - Proxy & WebSocket bound to
127.0.0.1only (loopback — not accessible from network) - Electron uses
contextIsolation: true,nodeIntegration: false, CSP headers - WebSocket requires pairing token authentication with rate limiting (50 msg/sec)
- No telemetry — zero external network calls except to the AI providers you configure
The Chrome extension (Manifest V3) tracks consumer usage on AI web apps.
- Content scripts inject at
document_starton claude.ai, chatgpt.com, and gemini.google.com - Scripts intercept
window.fetchto capture API responses - Token counts are estimated from response text length (~4 chars per token)
- Events are sent to the background service worker via
chrome.runtime.sendMessage - The background worker maintains a persistent WebSocket connection to the desktop app on port
7879 - Events are relayed to the DataEngine for ingestion
pnpm build:extThen load the apps/extension/dist folder as an unpacked extension in Chrome.
- Open Token Monitor desktop → Settings → Show Pairing Token
- Click the extension popup icon
- Paste the token and click Pair
- Badge turns green "ON" when connected
The packages/openclaw-skill package integrates with OpenClaw to automatically report usage after each AI response.
// Triggered after each AI response (after_response hook)
// POSTs usage data to http://127.0.0.1:7878/api/usageInstall it in your OpenClaw configuration and it will silently send usage data to Token Monitor whenever the desktop app is running.
No .env file is required. All secrets are entered in-app and encrypted via OS keychain.
| Variable | Purpose | Default |
|---|---|---|
NODE_ENV |
Development mode detection | Auto-detected via app.isPackaged |
- Persona — Casual / Builder / Power User (changes default view)
- Theme — Light / Dark / System
- Budget alerts — Per-provider or global, with configurable thresholds
- Pairing token — For browser extension WebSocket authentication
A single-glance view with a large UsageRing showing daily spend vs. budget, aggregate sparkline, and a simple provider status list. Plain language, no jargon.
A responsive grid of ProviderCards, each showing provider icon, model, status dot, sparkline chart, token counts (input/output/cost), request count, and time since last activity. Summary bar at top.
Full dashboard with 6 stat cards, budget bars, and a tabbed interface:
- Instances — Live provider sessions
- Events — Scrollable table of recent events with time, provider, model, tokens, cost, quality badge
- Analytics — Cost-by-provider horizontal bar chart
SQLite database stored in Electron's userData directory as token-monitor.db.
| Table | Purpose |
|---|---|
settings |
Key/value store for preferences, schema version, pairing token |
providers |
Provider configs with encrypted API keys, status, timestamps |
usage_records |
Individual usage events with full token breakdown |
usage_hourly |
Hourly aggregates (upsert on composite PK: provider_id + hour + model) |
usage_daily |
Daily aggregates (same pattern) |
budgets |
Spending limits with thresholds, notification channels, hard cap option |
WAL mode enabled with 64MB cache and foreign keys.
| Port | Service | Purpose |
|---|---|---|
5173 |
Vite Dev Server | Renderer hot-reload (dev only) |
7878 |
HTTP Proxy | Transparent API proxy + OpenClaw skill endpoint |
7879 |
WebSocket | Browser extension communication |
7880 |
API (reserved) | Future REST API |
All services bind to 127.0.0.1 (localhost only).
# Build the desktop app (TypeScript → Vite → electron-builder)
pnpm build
# Build the browser extension
pnpm build:extThe desktop build produces platform-specific distributables via electron-builder.
| Command | Description |
|---|---|
pnpm install |
Install all workspace dependencies |
pnpm dev |
Start desktop app in dev mode (Vite + Electron) |
pnpm build |
Build desktop app for distribution |
pnpm build:ext |
Build browser extension |
pnpm lint |
ESLint across all .ts/.tsx files |
pnpm typecheck |
TypeScript type checking |
NODE_MODULE_VERSION 127. This version of Node.js requires NODE_MODULE_VERSION 130.
Rebuild the native module for Electron:
cd apps/desktop
npx @electron/rebuild -f -w better-sqlite3The Electron main process runs as ESM. The codebase uses import.meta.url to polyfill __dirname:
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);nvm use 22 # or nvm use 20If ports 7878 or 7879 are occupied, kill the existing process:
lsof -ti:7878 | xargs kill -9
lsof -ti:7879 | xargs kill -9- Runtime: Electron 33 + Node.js 22
- Frontend: React 19, Zustand 5, Recharts, Tailwind CSS 3
- Build: Vite 6, vite-plugin-electron, TypeScript 5.7
- Database: SQLite via better-sqlite3 (WAL mode)
- Networking: Fastify (proxy), ws (WebSocket)
- Packaging: electron-builder, pnpm workspaces
- Extension: Chrome MV3
MIT