Native macOS dashboard for your Claude Code usage. Token volume, daily activity heatmap, hour-of-day patterns, per-project + per-model breakdown, cache hit ratio, sessions, live velocity. 100 % local — no network calls, no telemetry.
UX: a Grammarly-style floating bubble lives at the edge of your screen, color-shifts as you approach a daily token goal (Apple-Health-Activity-style ring with overflow), and clicks open into a full Apple-Health-aesthetic dashboard. No Dock icon, no menu bar bloat. Reads ~/.claude/projects/*/*.jsonl directly. Nothing leaves your Mac.
Built with Swift 6 + SwiftUI + Apple's Charts framework. Zero third-party dependencies.
v1.0 — public release. Code-signed (ad-hoc by default; Developer ID + notarization supported). Hardened-runtime, explicit-deny entitlements, self-verifying signature on launch, anti-debug in Release builds. See Hardening below.
- macOS 14 (Sonoma) or later
- Xcode 15+ (for
xcodebuildand SwiftUI Charts) xcodegen(brew install xcodegen) — only needed if you changeproject.yml
The project is preconfigured for the Apple Development: Max Polwin (QXT62Q4V34) cert / team PDPT7GQQWN. To use a different developer:
- Edit project.yml — change
DEVELOPMENT_TEAMandCODE_SIGN_IDENTITY. xcodegen(regeneratesClaudeHealth.xcodeproj).
Then:
./build.sh --install # signed Release build → /Applications/ClaudeHealth.app
open /Applications/ClaudeHealth.appThat's it — the app is signed by your Apple Development cert, so it launches without Gatekeeper warnings on your registered Mac.
./build.sh # Debug build into build/, no install
./build.sh --run # Debug build + launch from build/
./build.sh --release # Release build into build/, no install
./build.sh --notarize # Release + Developer ID + notarize + staple (see below)open ClaudeHealth.xcodeprojCmd+R runs the Debug scheme. Signing is preconfigured under the Signing & Capabilities tab — switch certs there if you want.
To share the app with others (or run on Macs not registered to your dev account), you need a Developer ID Application cert + notarization. The team-pinned Designated Requirement in build.sh is already written to accept either Apple Development or Developer ID Application certs from team PDPT7GQQWN — no other team, no Distribution / Mac App Store certs — so you can flip between them without re-issuing the DR.
-
Create the Developer ID Application cert (you don't have one yet):
- Open Keychain Access → menu Certificate Assistant → Request a Certificate from a Certificate Authority → "Saved to disk" + "Let me specify key pair information" → 2048-bit RSA. Save the
.certSigningRequest. - Go to https://developer.apple.com/account/resources/certificates/add → pick Developer ID Application → upload the CSR → download the resulting
.cer. - Double-click the
.certo install. Verify with:security find-identity -v -p codesigning | grep "Developer ID Application"
- Open Keychain Access → menu Certificate Assistant → Request a Certificate from a Certificate Authority → "Saved to disk" + "Let me specify key pair information" → 2048-bit RSA. Save the
-
Store notarytool credentials in your login keychain (one-time, lives in Keychain — encrypted at rest):
xcrun notarytool store-credentials ClaudeHealthNotary \ --apple-id "you@example.com" \ --team-id PDPT7GQQWN # prompts for an app-specific password from appleid.apple.com # (NOT your real Apple ID password — generate a 4-word app password)
SIGN_IDENTITY="Developer ID Application: Max Polwin (PDPT7GQQWN)" \
NOTARY_PROFILE=ClaudeHealthNotary \
./build.sh --notarizeThis produces a Release build, re-signs with Developer ID + the team-pinned DR, submits to Apple's notary service (1–10 min wait), and staples the resulting ticket so the app verifies offline. Verify the result with:
spctl -a -vv -t install build/ClaudeHealth.app
# expect: build/ClaudeHealth.app: accepted
# source=Notarized Developer ID
xcrun stapler validate build/ClaudeHealth.app
# expect: The validate action worked!Then cp -R build/ClaudeHealth.app /Applications/ and you're done — no Gatekeeper warnings on any Mac, and tampering with any sealed file invalidates the signature and the staple.
- No network: zero
URLSession/Network/HTTP refs in source; noNSAppTransportSecurityexceptions; verifiable withLuLu. - No subprocess / dyn-loading: no
Process()/dlopen/posix_spawnin app sources. - Hardened Runtime ON, with seven explicit-deny entitlements (
allow-jit,allow-unsigned-executable-memory,allow-dyld-environment-variables,disable-library-validation,disable-executable-page-protection,allow-relative-library-loads+app-sandbox). - Self-verify on launch via
SecStaticCodeCheckValidityWithErrors(strict + nested-code + all-architectures). Tampered bundles trigger an alert. - Anti-debug:
PT_DENY_ATTACHin Release builds. Verified:lldb -p $(pgrep ClaudeHealth)→ "Not allowed to attach." - Team-pinned Designated Requirement:
subject.OU = "PDPT7GQQWN"— survives Apple Development cert renewals. - Cache file 0600 via
open(O_CREAT, 0600)+fsync+ atomicreplaceItemAt(no TOCTOU window). - Cache schema/bundle-id integrity guard: a substituted
cache.jsonis rejected and rebuilt from JSONL ground truth. - Bounded streaming JSONL parser: 1 MiB / line, 100 MiB / file, 500 MiB / parse caps. Symlink escapes from
~/.claude/projects/rejected; files opened by canonical resolved path. - OSLog with
.privateredaction for token counts + budgets; filenames passed throughString.logSafeto block log injection. - DYLD_ env-var audit* at launch: any DYLD_* that survived hardened runtime triggers a
Log.security.fault. - Confetti panel: at
.popUpMenulevel (not screen-saver),sharingType = .none(excluded from screen recording),ignoresMouseEvents = true. - Bundle integrity baseline:
./build.sh --installwritesbuild/checksums.txtwith SHA256 of binary + every bundled.icnsso post-install tampering is onediffaway from being detected. - Clean uninstall: Settings → Advanced → "Uninstall ClaudeHealth…" unregisters the Login Item, deletes Application Support + UserDefaults, then quits — leaves no orphan persistence (verified via Objective-See's KnockKnock).
Just open /Applications/ClaudeHealth.app. With Apple Development signing on your registered Mac there's no warning. With Developer ID + notarization there's no warning anywhere.
If you somehow get the "can't be opened" warning (e.g., copied an unsigned build from elsewhere):
xattr -dr com.apple.quarantine /Applications/ClaudeHealth.app
open /Applications/ClaudeHealth.appA small floating bubble in the top-right of your main display showing today's token count inside a progress ring (ring fills relative to your 30-day daily average).
- Click → expands the dashboard alongside it.
- Drag → reposition; on release the bubble snaps to the nearest screen edge (Grammarly-style) with a native trackpad haptic.
- Right-click → context menu (Show Dashboard, Refresh Now, Settings…, Quit).
- Hover → tooltip with today / 7-day / 30-day totals + 5h-window status if you've set a budget.
- Color — the ring shifts blue → green as you approach the 5-hour usage limit you set in Settings. When you cross it, the ring pulses green and the screen fills with confetti for 5 seconds.
12 cards in Apple Health style. Every card has an (i) info icon — hover for a plain-English explanation. Every chart has a hover tooltip showing the precise value under the cursor.
- Hero ring — today vs 30-day average, % delta, current velocity
- Today / 7-day / 30-day summary cards with period-over-period delta
- Live velocity — tokens/min over the last 15 min + 60-min sparkline
- 5-hour usage limit — rolling-window usage vs your set budget; ring blue → green as you approach it; counts crossings
- Streak — current + longest consecutive-day streak
- Year heatmap — 53 weeks × 7 days, GitHub-style intensity
- Daily stacked bar (last 30 days) — input / output / cache write / cache read with hover breakdown
- Hour × day-of-week heatmap — when you actually use Claude
- By project — top 7 projects + Other rollup
- By model — donut with hover slice highlighting
- Cache hit ratio — 30-day sparkline
- Sessions per day — 30-day bars
Click outside or Esc to dismiss. ⌘R to force-refresh. Auto-refreshes every 2 s when transcript files change. Click the share icon (top-right of dashboard) to export the full dashboard as a Retina PNG via the system share sheet.
In Settings → Usage limit, set the number of tokens you'd consider your 5-hour cap (e.g., 5000000). Then:
- The bubble's ring color interpolates blue → green as you approach the limit (green = "filled up").
- When you cross the limit, ClaudeHealth logs a
LimitEventand fires 5 seconds of confetti across the screen (click-through, doesn't block your work). Idempotent — won't re-fire until usage drops below the threshold. - A counter in Settings shows how many crossings happened in the last 30 days. There's a "Test confetti now" button.
Settings → Startup → "Open ClaudeHealth at login" toggle. Uses macOS SMAppService.mainApp.register(). If macOS shows "Needs approval," open System Settings → General → Login Items and enable ClaudeHealth.
| Surface | Token data locally? | What ClaudeHealth shows |
|---|---|---|
| Claude Code (CLI) | ✅ yes — full per-turn breakdown | everything: tokens, cache hits, daily/hourly heatmaps, projects, models, velocity |
| Claude Desktop App, agent mode (Cowork) | ❌ no tokens (run in a sandboxed VM) | session metadata only: count per day + recent list (title, model, when) |
| Claude Desktop App, normal chat (webview) | ❌ no | nothing — webview offloads to claude.ai server |
| Claude.ai in browser | ❌ no | nothing — pure server-rendered |
| Anthropic API directly | n/a | nothing — out of scope |
For the unified plan gauge across all surfaces, use Anthropic's own page (Settings → Help → "Open Anthropic plan usage on claude.ai", or directly: https://claude.ai/settings/usage).
- Transcripts:
~/.claude/projects/<project-key>/*.jsonl. Onlytype == "assistant"events withmessage.usageare kept. - Cowork agent sessions:
~/Library/Application Support/Claude/local-agent-mode-sessions/**/local_*.json. Session metadata only (title, model, timestamps); no token data is exposed by the desktop app outside the VM. - Cache:
~/Library/Application Support/ClaudeHealth/cache.json— per-file(size, mtime)index plus rolled-up aggregates. Cold start ≈ 1–3 s on a 65 MB transcript pile; warm restart < 100 ms. - Usage-limit log: persisted in
UserDefaultsunderClaudeHealth.limitEvents. Each entry records when you crossed your set 5-hour budget, the budget at that time, and the tokens in the window. - No cost / pricing displays. Removed by design — this app tracks token volume, not dollars.
- No network calls.
- No telemetry.
- No file writes outside
~/Library/Application Support/ClaudeHealth/. - Reads
~/.claude/projects/only. Doesn't touch project source code.
Right-click the bubble → Quit ClaudeHealth, or click the status-bar waveform icon → Quit.
pkill -x ClaudeHealth
rm -rf /Applications/ClaudeHealth.app
rm -rf ~/Library/Application\ Support/ClaudeHealth
defaults delete com.max.ClaudeHealthClaudeHealth/
├── project.yml # xcodegen source of truth
├── ClaudeHealth.xcodeproj/ # generated by xcodegen, committed
├── Package.swift # also works for swift build, no Xcode
├── build.sh # xcodebuild wrapper + notarize helper
├── Resources/
│ ├── ClaudeHealth.entitlements # sandbox=false
│ └── GeneratedInfo.plist # generated by xcodegen
└── Sources/ClaudeHealth/
├── App/ # ClaudeHealthApp, AppDelegate, window controllers
├── Models/ # TranscriptEntry, Record, Aggregates
├── Data/ # parser, aggregator, pricing, cache, file watcher
└── Views/
├── BubbleView.swift
├── DashboardView.swift
├── SettingsView.swift
├── Style/ # HealthCard modifier, Palette
└── Cards/ # 10 dashboard cards
The app icon (Resources/AppIcon.icns) is generated by tools/MakeIcon.swift using CoreGraphics. Re-run after design changes:
swift tools/MakeIcon.swift
xcodegen
./build.sh --installThe script produces a squircle with a blue → indigo gradient, a centered SF Symbol (chart.line.uptrend.xyaxis) in white, and a soft drop shadow at every required iconset resolution.
If you fork ClaudeHealth and want to sign with your own Apple Developer account:
- Create your own Apple Development (or Developer ID Application) cert via Keychain Access → Certificate Assistant → "Request a Certificate from a Certificate Authority…" → upload to https://developer.apple.com/account/resources/certificates/add.
- Replace these three values throughout the repo:
PDPT7GQQWN→ your team identifier (security find-identity -v -p codesigningshows it in parentheses after the cert name)Apple Development: Max Polwin (QXT62Q4V34)→ your full cert subject CNMax Polwin→ your name (inLICENSE,project.ymlproperties,Info.plist)
- Optionally rebrand: change
com.max.ClaudeHealth→com.<you>.ClaudeHealthinproject.yml,Resources/ClaudeHealth.entitlements, andSources/ClaudeHealth/App/Logging.swift. - Run
xcodegento regenerate the Xcode project from your editedproject.yml. ./build.sh --install— your fork now signs with your cert.
For ad-hoc-signed personal use (no Developer Account required), ./build.sh --install works out of the box: build script falls back to ad-hoc signing when no SIGN_IDENTITY env var is set and the author's cert isn't in your keychain. The app still runs; macOS shows a one-time "Open anyway" dialog on first launch.
- ClaudeHealth is local-only. No network sockets, no embedded HTTP server, no iPhone bridge — everything stays on this Mac for security.
- v2 ideas — Per-tool token attribution, CSV export, "main thread only" filter, real "API said no" usage-limit detection if Anthropic ever exposes a structured rate-limit field.
Max Polwin — built scratching my own itch, polished publicly.
MIT — do whatever, no warranty.