cd /news/developer-tools/building-tokenmonitor-a-native-macos… · home topics developer-tools article
[ARTICLE · art-112852] src=corti.com ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Building TokenMonitor: A Native macOS Menu-Bar App for Watching Claude Code Eat Tokens

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+.

read6 min views3 publishedAug 27, 2026
Building TokenMonitor: A Native macOS Menu-Bar App for Watching Claude Code Eat Tokens
Image: Corti (auto-discovered)

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.

Why #

If you use Claude Code a lot, you live with three nagging questions:

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.

There 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.

So TokenMonitor was born. It shows:

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

)API spend— month-to-date, via the Admin API

Fittingly, the app was built almost entirely with Claude Code — including the feature that watches Claude Code.

How it works #

Where the data comes from

TokenMonitor has four independent data sources, and the core architectural rule is that each one is allowed to fail without taking the others down:

Source Mechanism
Subscription usage Claude Code's own OAuth token (read from the Keychain) against the usage endpoint
Local token counts Parsing ~/.claude/projects/**/*.jsonl transcript files
Active sessions + context Tail-parsing the newest usage entry of each recently-written transcript
API spend Anthropic Admin API cost report, with an Admin key you provide
Context windows Anthropic Models API (/v1/models ), fetched once per launch

The 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.

The interesting part: per-session context gauges

Claude Code appends to a session's .jsonl

transcript in real time — one line per event, and every assistant message carries a usage

block. That means the current context size of a session is simply, from its newest usage entry:

context = input_tokens + cache_read_input_tokens + cache_creation_input_tokens

TokenMonitor 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.

Two gotchas surfaced immediately in real use:

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 anentrypoint

field (cli

vssdk-py

), 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+) have1M-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 readsmax_input_tokens

per model, with a static fallback for offline use. New model launches need no app update.

Native shell, honest panel

The UI is SwiftUI hosted inside a hand-rolled, non-activating NSPanel

not MenuBarExtra

, which can't reliably float above full-screen apps or pin. Details that took real debugging:

  • The panel auto-sizes to its content(NSHostingView.preferredContentSize

) and keeps itstopedge anchored under the menu-bar icon, so the session list grows downward and shrinks back. - Menu-bar managers (Ice, Bartender) park hidden status items offscreenNSScreen.main

can even benil

for 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).

Engineering setup

Swift 6 strict concurrency, all data-layer typesSendable

TokenMonitorKit— an SPM package holdingalllogic (parsers, API clients, aggregator), zero AppKit imports, fastswift test

loop (41 unit tests)Thin app shell— status item, panel,@Observable

view models that own the polling (30 s local, 60 s network, exponential backoff)XcodeGen— the.xcodeproj

is generated fromproject.yml

and gitignoredTest-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

Signing and notarization

Release builds are signed with a Developer ID Application certificate, hardened runtime, and a secure timestamp, then notarized with notarytool

and stapled. The non-obvious flags that make notarization pass: CODE_SIGN_INJECT_BASE_ENTITLEMENTS: false

(no debug get-task-allow

entitlement) and OTHER_CODE_SIGN_FLAGS: --timestamp

. Gatekeeper verdict on the shipped app: source=Notarized Developer ID

.

How to use it #

Download the latest release (see below), unzip, dragTokenMonitor.app

to 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 tokenread-onlyto 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…

) 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 SESSIONS · CONTEXT gauges as you work — cyan is comfortable, amber means the window is filling, magenta means compaction is around the corner.

There's no Dock icon (LSUIElement

) — the app lives entirely in the menu bar. If you use a menu-bar manager, set TokenMonitor to "Always show".

Download #

Releases:github.com/TechPreacher/TokenMonitor/releases— signed and notarized, currently v1.1.0Source:github.com/TechPreacher/TokenMonitor— MIT licensed

Requirements: macOS 15+. Build from source with Xcode 26+ and XcodeGen (xcodegen generate

, then build the TokenMonitor

scheme).

Caveats, honestly #

  • The subscription usage endpoint is unofficial and may break without notice; the app degrades to local transcript data if it does.
  • Context percentages are derived from transcript usage entries — they track what the API reported for the lastturn, 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.
── more in #developer-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/building-tokenmonito…] indexed:0 read:6min 2026-08-27 ·