{"slug": "building-tokenmonitor-a-native-macos-menu-bar-app-for-watching-claude-code-eat", "title": "Building TokenMonitor: A Native macOS Menu-Bar App for Watching Claude Code Eat Tokens", "summary": "Developer Corti built TokenMonitor, a native macOS menu-bar app that tracks Claude Code token usage, subscription limits, and API spend, using undocumented OAuth endpoints and JSONL transcript parsing. The app, styled as a cyberpunk 'Neon HUD,' shows per-session context gauges and was developed almost entirely with Claude Code. It queries the Anthropic Models API to support 1M-token context windows for current models like Claude 5 and Opus/Sonnet 4.6+.", "body_md": "# Building TokenMonitor: A Native macOS Menu-Bar App for Watching Claude Code Eat Tokens\n\n*How a weekend itch turned into a notarized, cyberpunk-styled menu-bar app — and what I learned about undocumented OAuth endpoints, JSONL transcript spelunking, and the dark corners of NSPanel.*\n\n## Why\n\nIf you use [Claude Code](https://claude.com/claude-code?ref=corti.com) a lot, you live with three nagging questions:\n\n**How close am I to my 5-hour and weekly subscription limits?** The CLI shows this if you ask, but I wanted it glanceable, all the time.**How full is the context window of each session I have running?** With multiple terminals open, one of them is always creeping toward compaction — and you only find out when it happens.**What is my Anthropic API spend this month?** Separate from the subscription, easy to forget, occasionally alarming.\n\nThere are excellent terminal tools in this space (ccusage being the best known), but I wanted a **native macOS menu-bar app**: always visible, pinnable above every window, zero terminal real estate, and — because dashboards should be fun — styled like a cyberpunk \"Neon HUD\" with glowing gauges and CRT scanlines.\n\nSo TokenMonitor was born. It shows:\n\n**Subscription usage**— 5-hour session %, weekly %, and per-model weekly limits** Local token counts**— today and the current 5-hour window** Active sessions**— one gauge per running Claude Code session, showing context-window fill (e.g.`533.9K / 1M tokens`\n\n)**API spend**— month-to-date, via the Admin API\n\nFittingly, the app was built almost entirely *with* Claude Code — including the feature that watches Claude Code.\n\n## How it works\n\n### Where the data comes from\n\nTokenMonitor has four independent data sources, and the core architectural rule is that **each one is allowed to fail without taking the others down**:\n\n| Source | Mechanism |\n|---|---|\n| Subscription usage | Claude Code's own OAuth token (read from the Keychain) against the usage endpoint |\n| Local token counts | Parsing `~/.claude/projects/**/*.jsonl` transcript files |\n| Active sessions + context | Tail-parsing the newest usage entry of each recently-written transcript |\n| API spend | Anthropic Admin API cost report, with an Admin key you provide |\n| Context windows | Anthropic Models API (`/v1/models` ), fetched once per launch |\n\nThe subscription endpoint is **undocumented** — it's what Claude Code itself uses. I verified it with a quick spike before building anything on top of it, and the provider is written to degrade to \"unavailable\" the day it changes. Rule of the codebase: never crash because a data source moved.\n\n### The interesting part: per-session context gauges\n\nClaude Code appends to a session's `.jsonl`\n\ntranscript in real time — one line per event, and every assistant message carries a `usage`\n\nblock. That means the current context size of a session is simply, from its newest usage entry:\n\n```\ncontext = input_tokens + cache_read_input_tokens + cache_creation_input_tokens\n```\n\nTokenMonitor scans the transcript tree, treats any file written in the last 5 minutes as an *active* session, and reads only the **last 256 KB** of each file backwards until it finds a usage entry — transcripts grow to tens of megabytes, and nobody wants a menu-bar app that re-reads all of them every 30 seconds.\n\nTwo gotchas surfaced immediately in real use:\n\n**Background Agent SDK sessions.** Tools like claude-mem spawn headless SDK sessions in your project directory. They have their own transcripts and context windows, so the panel showed \"duplicate\" rows with the same project name and different percentages. The fix: transcript entries carry an`entrypoint`\n\nfield (`cli`\n\nvs`sdk-py`\n\n), so SDK sessions are filtered out.**Wrong context windows.** I initially hard-coded 200k for everything. Then a session showed 649k of context — impossible. Current models (the Claude 5 family, Opus/Sonnet 4.6+) have**1M-token windows**. Instead of chasing a hard-coded table forever, the app now queries the Models API at launch (it happily accepts Claude Code's OAuth token) and reads`max_input_tokens`\n\nper model, with a static fallback for offline use. New model launches need no app update.\n\n### Native shell, honest panel\n\nThe UI is SwiftUI hosted inside a hand-rolled, non-activating `NSPanel`\n\n— **not** `MenuBarExtra`\n\n, which can't reliably float above full-screen apps or pin. Details that took real debugging:\n\n- The panel\n**auto-sizes to its content**(`NSHostingView.preferredContentSize`\n\n) and keeps its*top*edge anchored under the menu-bar icon, so the session list grows downward and shrinks back. - Menu-bar managers (Ice, Bartender) park hidden status items\n*offscreen*—`NSScreen.main`\n\ncan even be`nil`\n\nfor a non-activating app — so the panel clamps itself into the visible screen frame rather than opening 60 points above your display. - Left-click toggles the panel; right-click shows a transient About/Quit menu (assigned only during the click, so left-click keeps working).\n\n### Engineering setup\n\n**Swift 6 strict concurrency**, all data-layer types`Sendable`\n\n**TokenMonitorKit**— an SPM package holding*all*logic (parsers, API clients, aggregator), zero AppKit imports, fast`swift test`\n\nloop (41 unit tests)**Thin app shell**— status item, panel,`@Observable`\n\nview models that own the polling (30 s local, 60 s network, exponential backoff)**XcodeGen**— the`.xcodeproj`\n\nis generated from`project.yml`\n\nand gitignored**Test-driven throughout**— every feature and bug fix in this app started life as a failing test, including the SDK-session filter and the context-window mapping\n\n### Signing and notarization\n\nRelease builds are signed with a Developer ID Application certificate, hardened runtime, and a secure timestamp, then notarized with `notarytool`\n\nand stapled. The non-obvious flags that make notarization pass: `CODE_SIGN_INJECT_BASE_ENTITLEMENTS: false`\n\n(no debug `get-task-allow`\n\nentitlement) and `OTHER_CODE_SIGN_FLAGS: --timestamp`\n\n. Gatekeeper verdict on the shipped app: `source=Notarized Developer ID`\n\n.\n\n## How to use it\n\n**Download** the latest release (see below), unzip, drag`TokenMonitor.app`\n\nto Applications, launch.**Keychain prompt**: on first launch macOS asks to let TokenMonitor read the password for \"Claude Code-credentials\" — click** Always Allow**. The app reads Claude Code's OAuth token*read-only*to query your subscription usage. (Approval is tied to the app's code signature, so it persists across launches of the same signed build.)**Optional — API spend**: generate an Admin API key (`sk-ant-admin…`\n\n) in the Anthropic Console and paste it into the app's settings (gear icon). The key is validated live against the API before it's stored in your Keychain. Without it, everything else still works.**Pin** the panel with the pin button to keep it floating above all windows; unpinned, it closes on an outside click.- Watch the\n**SESSIONS · CONTEXT** gauges as you work — cyan is comfortable, amber means the window is filling, magenta means compaction is around the corner.\n\nThere's no Dock icon (`LSUIElement`\n\n) — the app lives entirely in the menu bar. If you use a menu-bar manager, set TokenMonitor to \"Always show\".\n\n## Download\n\n**Releases**:[github.com/TechPreacher/TokenMonitor/releases](https://github.com/TechPreacher/TokenMonitor/releases?ref=corti.com)— signed and notarized, currently v1.1.0**Source**:[github.com/TechPreacher/TokenMonitor](https://github.com/TechPreacher/TokenMonitor?ref=corti.com)— MIT licensed\n\nRequirements: macOS 15+. Build from source with Xcode 26+ and XcodeGen (`xcodegen generate`\n\n, then build the `TokenMonitor`\n\nscheme).\n\n## Caveats, honestly\n\n- The subscription usage endpoint is unofficial and may break without notice; the app degrades to local transcript data if it does.\n- Context percentages are derived from transcript usage entries — they track what the API reported for the\n*last*turn, so they lag by one message. - Not affiliated with or endorsed by Anthropic. The app reads your own local data and calls Anthropic APIs with your own credentials.", "url": "https://wpnews.pro/news/building-tokenmonitor-a-native-macos-menu-bar-app-for-watching-claude-code-eat", "canonical_source": "https://corti.com/building-tokenmonitor-a-native-macos-menu-bar-app-for-watching-claude-code-eat-tokens/", "published_at": "2026-08-27 08:52:19+00:00", "updated_at": "2026-08-27 09:20:34.453069+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-products"], "entities": ["Claude Code", "Anthropic", "TokenMonitor", "Corti", "Claude 5", "Opus", "Sonnet 4.6"], "alternates": {"html": "https://wpnews.pro/news/building-tokenmonitor-a-native-macos-menu-bar-app-for-watching-claude-code-eat", "markdown": "https://wpnews.pro/news/building-tokenmonitor-a-native-macos-menu-bar-app-for-watching-claude-code-eat.md", "text": "https://wpnews.pro/news/building-tokenmonitor-a-native-macos-menu-bar-app-for-watching-claude-code-eat.txt", "jsonld": "https://wpnews.pro/news/building-tokenmonitor-a-native-macos-menu-bar-app-for-watching-claude-code-eat.jsonld"}}