The Problem: ccusage Lives in a Terminal You Keep Closing
Claude Code writes a JSONL transcript of every session to `~/.claude/projects/`. `ccusage` is the community CLI that parses those files and tells you how many tokens you have burned in the current 5-hour block, today, this week. It works beautifully from a terminal.
The problem is that most of the day you are not in a terminal. You are in Figma, or a browser, or a Slack thread, or your editor. Alt-tabbing to a terminal just to type `ccusage blocks --active` is the same friction that drives people to ignore cost dashboards. We wanted a number in the menu bar that we glance at while the kettle boils.
An afternoon of Swift later, we had ClaudeBar: ~600 lines of SwiftUI, no Electron, no dependencies, no sign-in, ~1.6 MB binary. It reads Claude's local JSONL files directly and renders a tabbed popover with everything we would actually want to see.
ClaudeBar is open source at github.com/displace-agency/claudebar. MIT licensed. Download the DMG from Releases, drag to Applications, done.

By the numbers
~1.6 MB
Binary size
0
Dependencies at runtime
~20 ms
Refresh with mtime cache
100%
Local. No network calls.
Why Tokens, Not Dollars
Most Claude usage dashboards lead with a dollar figure. That makes sense if you are billed per API call. It makes no sense on a Pro or Max plan, where the dollar number is entirely hypothetical. On a subscription, tokens are the real resource: you have a weekly budget and the question you want answered is "am I on track to fill my 5-hour block, or do I have headroom?".
So ClaudeBar leads with tokens, and the dollar figure is muted alongside as `≈ $X API`. Costs are still useful as a sanity check — you want to know when you have torched 100 million output tokens on one project — but they are a secondary signal, not the headline.
That reframe unlocked a small but meaningful design decision: the Cache Hits stat. Cache reads look expensive in token counts (they dominate total tokens on any long session) but are billed at 10% of fresh input. Instead of burying cache reads in the totals, we surface them as a positive metric: the higher your cache hit rate, the more efficient you are being. 99% reuse? Great, you are keeping context warm.
Three Tabs, Three Questions
ClaudeBar answers three questions, one per tab. Overview: am I burning hot right now? Sessions: which conversations are eating my budget? History: what does my usage look like over time?

The Sessions tab was the one that surprised us. Claude Code encodes a session's working directory (`cwd`) into each JSONL event, so for project-scoped sessions the name is obvious: `website-todoweekend`, `tool-claude-bar`, `website-lomvok`. But most of our sessions ran from the home directory, because we had started Claude from `~` and named the project later in the first user message. 368 of our 389 sessions were labelled `Home` — which is useless.
The fix was a small inference pass. For any home-dir session, ClaudeBar scans the first 40 lines of the transcript for references to `/websites/<project>` or `CLIENTS/<client>` paths, filters out obvious placeholders, and uses the most-mentioned project as the display name. That pushed our recovery rate from 0% to 82%. The remaining 18% of home-dir sessions genuinely have no project context and stay as `Home`.

We Almost Shipped with ccusage Under the Hood. Then It Hung.
The first version of ClaudeBar was a thin wrapper over `ccusage`: shell out to the Node CLI, parse its JSON, update the menu bar. From a terminal, `ccusage blocks --active` runs in under a second. From a macOS app context, the same command hung for 30 seconds or longer, with Node stuck in a filesystem callback loop. We tried six things: direct binary invocation, concurrent pipe draining, raising QoS, a battery of environment variables, /dev/null stdin, wrapping in a login shell. None of it helped.
At that point it was simpler to just reimplement the parser in Swift. The JSONL format is trivial: each line is a message event with `timestamp`, `message.model`, and `usage.{input,output,cache_creation,cache_read}_tokens`. Aggregate by day, by 5-hour window, by session, by model. Total Swift weight: 240 lines in `TranscriptParser.swift`. We also added two things ccusage does not: mtime-keyed file caching (so unchanged transcripts are skipped on every 60-second refresh) and deduplication by `(messageId, requestId)` to avoid double-counting resumed sessions.
let cwd = (obj["cwd"] as? String).flatMap { $0.isEmpty ? nil : $0 }
let projectPath = cwd
?? Self.decodeProjectFolder(url.deletingLastPathComponent().lastPathComponent)
let messageId = (message["id"] as? String) ?? ""
let requestId = (obj["requestId"] as? String) ?? ""
let dedupKey = messageId.isEmpty && requestId.isEmpty
? "\(sessionId):\(timestampStr)"
: "\(messageId):\(requestId)"The payoff is that ClaudeBar has no runtime dependencies. macOS 13 or newer, drag to Applications, done. No Node, no ccusage, no auto-update nag, no telemetry.
The Menu Bar Title, Color-Tinted by Burn Rate
The menu bar title shows the active 5-hour block's token count, tinted green, amber, or red based on cost-per-hour burn rate. Green below $5/hr, amber below $15/hr, red above. We chose the thresholds empirically — watching the red tint appear during an ambitious refactor is a surprisingly effective nudge to pause and consider whether the next prompt is worth it.
- Menu bar icon: SF Symbol (chart.bar.xaxis) as a template image, auto-adapts to dark/light menu bar
- Title text: monospaced digits so the width does not jitter every refresh
- Color tint: NSColor on contentTintColor + attributedTitle, so both icon and text recolor together
- Left-click: opens the popover; right-click: context menu with Refresh and Quit
- No dock icon (LSUIElement = true), so the app is invisible outside the menu bar
Build, Release, Install
The whole thing is a Swift Package — no Xcode project, no storyboards. `./scripts/build-app.sh [version]` produces a universal (arm64 + x86_64) `.app` bundle and ad-hoc signs it. A GitHub Actions release workflow does the same build on `macos-14` runners when you push a `v*` tag, and uploads a DMG to the release. End-to-end from `git tag v0.4.0 && git push --tags` to a downloadable DMG: about four minutes.
# Build locally ./scripts/build-app.sh 0.4.0 open build/ClaudeBar.app # Cut a release git tag v0.4.0 git push --tags # → GitHub Actions builds DMG and attaches it to the release
First-launch on an unsigned build needs the right-click → Open → Open dance to get past Gatekeeper. That is standard for ad-hoc-signed apps and we decided not to pay for a developer certificate for something this small.
Why Open-Source It
This is the fourth open-source tool we have shipped in four weeks, after x-bookmarks-exporter, FocusGuard, and copy-translate. The pattern keeps recurring: a small annoyance, an afternoon of code, a repo. The code rarely takes long. The discipline is pushing it out instead of leaving it in a one-off script on a laptop.
If you are on Claude Pro or Max and you have ever wondered "did I just blow through my 5-hour block?" — install this. If you are on the API and you want the dollar number in your menu bar, the feature is already there, just labeled as hypothetical. And if you want to extend it, the code is small enough to read in one sitting.
ClaudeBar is live at github.com/displace-agency/claudebar. MIT license. macOS 13+, universal binary, ~1.6 MB. Drag to Applications and watch your token burn rate from the menu bar.

