About the Project
Inspiration
I use half a dozen different AI coding tools in a given week — Claude Code, Codex, Cursor, Gemini CLI, and a rotating cast of newer entrants. Each one has its own usage dashboard, if it has one at all, and none of them talk to each other. I had no idea how many tokens I was actually burning across all of them combined, or what it was really costing me, until the bill arrived.
I tried the existing community tools first. Some got close, but when I checked their numbers against the token counts providers themselves report, the totals didn't match — usually because of double-counting: retried requests, forked sub-agent sessions, or multi-file session logs all getting summed instead of deduplicated. If I was going to trust a number enough to make decisions off it, I needed to trust the pipeline that produced it. That distrust is what got me building instead of adopting.
The other half of the motivation was privacy. Every one of these tools reads through my actual prompts and file contents to log usage. I did not want a piece of software with that kind of access phoning anything back to a server. So the constraint became a design principle from day one: this had to work entirely on my machine, and if it ever left the machine, only aggregate token counts would go with it — never a single character of what I actually typed or generated.
How I built it
Token Tracker is local-first by construction. A CLI (bin/tracker.js) installs lightweight hooks into each provider's own session-log format on disk, parses them incrementally, and normalizes wildly different schemas into one consistent shape: input tokens, cached input, cache-creation, output, and reasoning tokens, each tracked separately because they're priced differently and providers report them inconsistently. Parsed events land in an append-only local queue and get served through a small local HTTP API on port 7680.
On top of that sits a React + TypeScript dashboard, built once with Vite and reused everywhere: as a locally-served page, as a Vercel-hosted marketing/companion site, embedded in a native macOS menu-bar app (Swift, with the Node runtime and built dashboard bundled inside so the .app is fully self-contained), and embedded again in a Windows system-tray app (.NET + WebView2, launching the same CLI runtime on a dynamic loopback port to dodge Windows services that squat on the default port). One dashboard, four surfaces.
I later added an opt-in cloud layer — cross-device aggregation, a leaderboard, achievement badges — for people who wanted to see their usage across machines or compare notes with others, but it stayed strictly additive: local tracking works fully offline, and cloud sync never touches prompt content, only the same token counters the local pipeline already computes.
What I learned
Parsing other tools' internal log formats is an exercise in humility. Nothing is documented, and the same-looking field means different things across vendors — Codex's input_tokens already includes cached tokens, so treating it like Claude's equivalent field silently inflated computed cost by 6–7x until I caught it against real billing data. Deduplication keys that look obviously correct fail open in subtle ways: a bare msgId + reqId check drops silently to just msgId whenever a provider's sub-agent forks don't emit a reqId, and suddenly you're overcounting by 2–4x without a single error being thrown anywhere.
I also learned that "local-first" and "cross-device aggregation" are in real tension, and reconciling them honestly (same user, several machines, no double-counting, no data loss when a machine goes offline for a week) took more design iteration than any single parser did.
Challenges I ran into
The biggest recurring challenge was trust without a source of truth: there's no vendor-published spec for what these token counts should sum to, so every parser had to be validated against actual provider billing pages one provider at a time, and every dedup fix had to be re-verified by running the sync pipeline twice in a row to catch state that only shows up on the second pass.
Packaging was its own battle — getting a signed, notarizable macOS .app with an embedded Node runtime through Gatekeeper, and getting a self-contained Windows executable to bundle the same runtime without triggering antivirus heuristics, are two very different fights that both had to be won before either desktop app was usable by anyone but me.
And once real usage data started flowing through a shared backend, security stopped being theoretical: a missing row-level-security policy on one cross-device table was a genuine, fixable vulnerability, not a hypothetical one — which reframed how carefully I treat every table and endpoint added after that.
Built With
- javascript
- plpgsql
- swift
- typescript
Log in or sign up for Devpost to join the conversation.