Skip to content

feat: add native macOS menu bar app (ClaudeMonitor)#215

Closed
HAOGRE wants to merge 3 commits into
Maciek-roboblog:mainfrom
HAOGRE:main
Closed

feat: add native macOS menu bar app (ClaudeMonitor)#215
HAOGRE wants to merge 3 commits into
Maciek-roboblog:mainfrom
HAOGRE:main

Conversation

@HAOGRE

@HAOGRE HAOGRE commented May 28, 2026

Copy link
Copy Markdown

Summary

This PR adds macos-stats-bar/ — a native macOS menu bar companion app for Claude Code Usage Monitor, built entirely in Swift/SwiftUI with zero external dependencies.

  • Live menu bar indicator: double-row token rates (↗ input / ↙ output) while Claude is active; switches to accumulated cost when idle
  • Detail panel: total cost, today's cost, input/output/cache tokens, top 5 projects by cost, recent 5 records, 30-day cost bar chart (collapsible)
  • i18n support: English and Chinese UI, defaults to system locale
  • Deduplication: skips duplicate entries via message_id:request_id hash — mirrors the Python logic exactly
  • Pricing: matches FALLBACK_PRICING in the Python project (Opus / Sonnet / Haiku)
  • No network, no daemon: reads ~/.claude/projects/*.jsonl directly; all data stays local
  • Universal binary: Intel + Apple Silicon, requires macOS 14.0+

Screenshot

ClaudeMonitor Screenshot

Project structure

macos-stats-bar/
├── README.md
├── assets/screenshot.png
└── ClaudeMonitor/
    └── ClaudeMonitor/
        ├── ClaudeMonitorApp.swift       # App entry, menu bar label rendering
        ├── StatusBarView.swift          # Detail panel UI
        ├── SettingsView.swift           # Settings panel
        ├── AppSettings.swift            # Persistent user preferences
        ├── Localization.swift           # English + Chinese strings
        └── Backend/
            ├── TokenDataReader.swift    # JSONL parser, pricing engine, mtime cache
            ├── MonitoringViewModel.swift # Observable state, auto-refresh, rate smoothing
            └── BookmarkManager.swift    # Security-scoped bookmark for sandbox access

Test plan

  • Build from source: open macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj → Cmd+R
  • Menu bar icon appears and shows cost when Claude is idle
  • Menu bar shows live token rates when Claude Code is active
  • Detail panel displays correct totals matching the Python terminal monitor
  • Settings: language switch, refresh interval, section toggles, launch at login

🤖 Generated with Claude Code

Adds macos-stats-bar/ — a zero-dependency Swift/SwiftUI menu bar app
that monitors Claude Code token usage and costs in real-time by reading
~/.claude/projects JSONL files directly (no network, no daemon).

Key features:
- Live double-row token rates (↗ input / ↙ output) in the menu bar;
  switches to accumulated cost when idle
- Detail panel: total/today cost, input/output/cache tokens, top 5
  projects by cost, recent 5 records, 30-day bar chart
- Deduplication via message_id:request_id hash (mirrors Python logic)
- Multi-model pricing: Opus / Sonnet / Haiku (matches FALLBACK_PRICING)
- i18n: English + Chinese, defaults to system locale
- Universal binary: Intel + Apple Silicon, requires macOS 14.0+

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces ClaudeMonitor, a complete macOS status bar application that monitors Claude API usage by reading local JSONL files from ~/.claude/projects. The app displays real-time token rates and costs in the menu bar, offers a detailed dashboard with project rankings and cost charts, includes configurable settings, and supports both English and Chinese localization—all without external dependencies.

Changes

macOS Status Bar App (ClaudeMonitor)

Layer / File(s) Summary
Xcode project infrastructure
macos-stats-bar/.gitignore, macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.pbxproj, macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.xcworkspace/contents.xcworkspacedata, macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/xcshareddata/xcschemes/ClaudeMonitor.xcscheme
Complete Xcode project definition with targets for app, unit tests, and UI tests; Debug/Release/AppStore configurations; build phases; cross-target dependencies; and workspace/scheme wiring.
App entitlements and entry point
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/ClaudeMonitor.entitlements, macos-stats-bar/ClaudeMonitor/ClaudeMonitor/ClaudeMonitorApp.swift
Sandbox entitlements with file bookmark capability; SwiftUI app entry point with MenuBarExtra scene; AppDelegate handling dock icon policy on launch and requesting bookmark access in sandboxed environments; MenuBarLabel switching between active token-rate icon and idle cost icon; image rendering for menu bar display with caching.
App state and localization
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/AppSettings.swift, macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Localization.swift
AppSettings observable singleton persisting UI preferences (section visibility, refresh interval, language, dock icon, launch-at-login) via UserDefaults and system service management; L10n localization singleton providing bilingual lookups with English/Chinese support and formatting helpers.
Data reading and monitoring backend
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/BookmarkManager.swift, macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/TokenDataReader.swift, macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/MonitoringViewModel.swift
BookmarkManager manages security-scoped file access to ~/.claude/projects; TokenDataReader parses JSONL usage files with mtime-based file caching, deduplication via message/request IDs, multi-model pricing, and ISO-8601 timestamp parsing; MonitoringViewModel asynchronously loads aggregated statistics, computes smoothed per-second token rates via sliding window, manages periodic auto-refresh, and provides formatting helpers for costs and rates.
Settings and dashboard UI
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/SettingsView.swift, macos-stats-bar/ClaudeMonitor/ClaudeMonitor/StatusBarView.swift
SettingsView provides toggle and picker controls for launch-at-login, dock icon, refresh interval, language selection, and section visibility; StatusBarView implements the main dashboard with header/timestamp, All vs. Today selector, live double-row token rate display, 2×2 stats grid, top-5 project ranking with progress bars, recent entries list, collapsible 30-day cost chart, and toolbar with settings/reset/quit actions.
Asset definitions
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/...
App icon asset catalog with size/scale variants and accent color definitions.
Test scaffolding
macos-stats-bar/ClaudeMonitor/ClaudeMonitorTests/ClaudeMonitorTests.swift, macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITests.swift, macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITestsLaunchTests.swift
Unit test stub with Testing framework; UI test stubs including app launch and performance measurement.
Documentation
README.md, macos-stats-bar/README.md
Top-level README updated with Table of Contents entry and feature summary; new macos-stats-bar/README.md provides full documentation including feature list, installation (DMG and source build), local JSONL operation model, multi-model pricing table, project structure diagram, FAQ, and MIT license link.

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A status bar's born today, so neat and tidy—
Reading Claude's secrets, local and tidy,
No network needed, just JSONL files,
Token rates dancing with cost-counting smiles! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add native macOS menu bar app (ClaudeMonitor)' clearly and concisely summarizes the main change—adding a new native macOS menu bar application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITests.swift (1)

26-32: 🏗️ Heavy lift

testExample should validate at least one stable UI state after launch.

Right now it only launches the app, so it won’t catch UI regressions. Add one deterministic assertion (for example, existence of a known menu/detail element or accessibility label).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITests.swift`
around lines 26 - 32, The UI test testExample currently only launches the app;
update it to assert a deterministic UI state after launch by querying the
XCUIApplication instance (app) for a known accessibility element and using
XCTAssert to verify existence/visibility — for example, check
app.staticTexts["<ACCESSIBILITY_ID>"].exists or
app.buttons["<ACCESSIBILITY_ID>"].isHittable (replace <ACCESSIBILITY_ID> with a
stable identifier exposed by the app). Ensure the assertion targets a stable
element (label/button/menu item) provided by the app so the test fails on UI
regressions.
macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/xcshareddata/xcschemes/ClaudeMonitor.xcscheme (1)

31-43: ⚡ Quick win

UI tests are not part of this scheme’s TestAction.

Only ClaudeMonitorTests is listed, so UI tests won’t run under this shared scheme by default.

Suggested addition
       <Testables>
          <TestableReference
             skipped = "NO"
             parallelizable = "YES">
             <BuildableReference
                BuildableIdentifier = "primary"
                BlueprintIdentifier = "25CF94682F8508E600C06791"
                BuildableName = "ClaudeMonitorTests.xctest"
                BlueprintName = "ClaudeMonitorTests"
                ReferencedContainer = "container:ClaudeMonitor.xcodeproj">
             </BuildableReference>
          </TestableReference>
+         <TestableReference
+            skipped = "NO"
+            parallelizable = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "25CF94722F8508E600C06791"
+               BuildableName = "ClaudeMonitorUITests.xctest"
+               BlueprintName = "ClaudeMonitorUITests"
+               ReferencedContainer = "container:ClaudeMonitor.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
       </Testables>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/xcshareddata/xcschemes/ClaudeMonitor.xcscheme`
around lines 31 - 43, The scheme's TestAction currently only includes the unit
test target "ClaudeMonitorTests", so add the UI test target (e.g.,
"ClaudeMonitorUITests") as another TestableReference under the <Testables>
section: create a new <TestableReference> with skipped="NO" and
parallelizable="YES" and a <BuildableReference> that sets
BuildableIdentifier="primary", BuildableName="ClaudeMonitorUITests.xctest",
BlueprintName="ClaudeMonitorUITests" and the same ReferencedContainer as the
existing entry; ensure you use the correct BlueprintIdentifier for the UI-test
target so the UI tests run under this shared scheme.
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/MonitoringViewModel.swift (1)

226-235: 💤 Low value

Inconsistent token formatting between ranges.

The formatting logic has inconsistent precision:

  • >= 100M: "%.1f M" (one decimal)
  • >= 1M: "\(count / 1_000) K" (integer division, no decimal)
  • >= 1K: "%.1f K" (one decimal)

For example, 1,500,000 tokens formats as "1500 K" while 999,999 formats as "1000.0 K". Consider using consistent decimal formatting:

♻️ Suggested fix for consistent formatting
     static func formatTokens(_ count: Int) -> String {
         if count >= 100_000_000 {
             return String(format: "%.1f M", Double(count) / 1_000_000)
         } else if count >= 1_000_000 {
-            return "\(count / 1_000) K"
+            return String(format: "%.1f M", Double(count) / 1_000_000)
         } else if count >= 1_000 {
             return String(format: "%.1f K", Double(count) / 1_000)
         }
         return String(count)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/MonitoringViewModel.swift`
around lines 226 - 235, The formatTokens(_ count: Int) function uses
inconsistent formatting across ranges (>=100_000_000 uses one decimal,
>=1_000_000 uses integer division producing "K" with no decimals, >=1_000 uses
one decimal), causing values like 1_500_000 -> "1500 K" while 999_999 -> "1000.0
K"; update formatTokens to use consistent decimal formatting for millions and
thousands (e.g., use String(format: "%.1f M" for millions and String(format:
"%.1f K" for thousands) and divide by 1_000_000 or 1_000.0 respectively) and
ensure the 100_000_000 branch follows the same pattern or is merged consistently
so all ranges produce similar precision.
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/TokenDataReader.swift (1)

85-107: 💤 Low value

Misleading thread-safety comment about DateFormatter.

The comment on lines 86-87 states that DateFormatter is protected by isLoading mutex, but:

  1. isLoading in MonitoringViewModel is a simple boolean, not a mutex
  2. TokenDataReader methods can be called independently of MonitoringViewModel
  3. fallbackDateFormatters are static and shared across all instances

In practice, the current usage is single-threaded (one Task.detached at a time), so this isn't an active bug. However, the misleading comment could cause issues if someone reuses TokenDataReader concurrently.

Consider either:

  • Removing the incorrect comment
  • Making fallbackDateFormatters thread-local or using a lock
  • Using ISO8601DateFormatter exclusively (which is thread-safe)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/TokenDataReader.swift`
around lines 85 - 107, The comment claiming DateFormatter is protected by an
isLoading mutex is incorrect; update TokenDataReader to avoid misleading
thread-safety guarantees by either removing the comment and clarifying that
fallbackDateFormatters is shared and not thread-safe, or make
fallbackDateFormatters thread-safe: replace the static [DateFormatter] with
thread-safe ISO8601DateFormatter usage (use isoWithFractional and isoBasic
exclusively in parsing), or protect access to fallbackDateFormatters with a
synchronization primitive (e.g., a DispatchQueue or actor) and update references
in TokenDataReader accordingly; also remove or correct the reference to
MonitoringViewModel.isLoading since it is a Bool, not a mutex.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.pbxproj`:
- Around line 328-357: The AppStore XCBuildConfiguration (name = AppStore)
enables the app sandbox (ENABLE_APP_SANDBOX = YES) but does not set
CODE_SIGN_ENTITLEMENTS, so entitlements from ClaudeMonitor.entitlements
(including com.apple.security.files.bookmarks.app-scope) won’t be applied;
update the AppStore buildSettings to add CODE_SIGN_ENTITLEMENTS =
"ClaudeMonitor.entitlements" (or the correct entitlements filename used in the
project) so code signing uses the entitlements for AppStore builds.

In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AccentColor.colorset/Contents.json`:
- Around line 3-5: The AccentColor.colorset Contents.json currently only
declares an idiom ("universal") but omits the required "color" object, so add a
"color" entry inside the JSON for the AccentColor.colorset (Contents.json) that
defines the desired accent using either sRGB/ARGB components or a system color
name; update the "idiom":"universal" object to include the "color" property
(with correct keys like "color-space" and component values) if you want a custom
accent, otherwise leave as-is to use the system default.

In `@macos-stats-bar/ClaudeMonitor/ClaudeMonitorTests/ClaudeMonitorTests.swift`:
- Around line 13-15: Replace the placeholder test example() with a real,
assertion-based unit test that verifies core parsing/pricing behavior: call
TokenDataReader (or the parser/deduplicator you added) to load a controlled
input fixture, assert expected outputs such as deduplicated token counts and
calculated price values, and use XCTAssert/XCTAssertEqual to validate results;
ensure the test method (e.g., testTokenPricingOrDeduplication) is async throws
and uses deterministic input so CI will fail on regressions.

In `@macos-stats-bar/README.md`:
- Around line 64-76: The README.md contains fenced code blocks used as
diagrams/trees without language identifiers (MD040); update each such block
(e.g., the project tree blocks shown around the
TokenDataReader/MonitoringViewModel/ClaudeMonitorApp and the ClaudeMonitor/
tree) by adding a neutral language tag like ```text immediately after the
opening backticks so the blocks become ```text ... ```, ensuring all
diagram/code-fence instances (including the other occurrence noted at lines
~94-104) use the same `text` tag.

---

Nitpick comments:
In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/xcshareddata/xcschemes/ClaudeMonitor.xcscheme`:
- Around line 31-43: The scheme's TestAction currently only includes the unit
test target "ClaudeMonitorTests", so add the UI test target (e.g.,
"ClaudeMonitorUITests") as another TestableReference under the <Testables>
section: create a new <TestableReference> with skipped="NO" and
parallelizable="YES" and a <BuildableReference> that sets
BuildableIdentifier="primary", BuildableName="ClaudeMonitorUITests.xctest",
BlueprintName="ClaudeMonitorUITests" and the same ReferencedContainer as the
existing entry; ensure you use the correct BlueprintIdentifier for the UI-test
target so the UI tests run under this shared scheme.

In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/MonitoringViewModel.swift`:
- Around line 226-235: The formatTokens(_ count: Int) function uses inconsistent
formatting across ranges (>=100_000_000 uses one decimal, >=1_000_000 uses
integer division producing "K" with no decimals, >=1_000 uses one decimal),
causing values like 1_500_000 -> "1500 K" while 999_999 -> "1000.0 K"; update
formatTokens to use consistent decimal formatting for millions and thousands
(e.g., use String(format: "%.1f M" for millions and String(format: "%.1f K" for
thousands) and divide by 1_000_000 or 1_000.0 respectively) and ensure the
100_000_000 branch follows the same pattern or is merged consistently so all
ranges produce similar precision.

In `@macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/TokenDataReader.swift`:
- Around line 85-107: The comment claiming DateFormatter is protected by an
isLoading mutex is incorrect; update TokenDataReader to avoid misleading
thread-safety guarantees by either removing the comment and clarifying that
fallbackDateFormatters is shared and not thread-safe, or make
fallbackDateFormatters thread-safe: replace the static [DateFormatter] with
thread-safe ISO8601DateFormatter usage (use isoWithFractional and isoBasic
exclusively in parsing), or protect access to fallbackDateFormatters with a
synchronization primitive (e.g., a DispatchQueue or actor) and update references
in TokenDataReader accordingly; also remove or correct the reference to
MonitoringViewModel.isLoading since it is a Bool, not a mutex.

In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITests.swift`:
- Around line 26-32: The UI test testExample currently only launches the app;
update it to assert a deterministic UI state after launch by querying the
XCUIApplication instance (app) for a known accessibility element and using
XCTAssert to verify existence/visibility — for example, check
app.staticTexts["<ACCESSIBILITY_ID>"].exists or
app.buttons["<ACCESSIBILITY_ID>"].isHittable (replace <ACCESSIBILITY_ID> with a
stable identifier exposed by the app). Ensure the assertion targets a stable
element (label/button/menu item) provided by the app so the test fails on UI
regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 379f21c5-94dd-4867-b5ba-4b347ea42c35

📥 Commits

Reviewing files that changed from the base of the PR and between 06f0fe1 and a31df6f.

⛔ Files ignored due to path filters (8)
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.png is excluded by !**/*.png
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_128x128.png is excluded by !**/*.png
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_16x16.png is excluded by !**/*.png
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_256x256.png is excluded by !**/*.png
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_32x32.png is excluded by !**/*.png
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_512x512.png is excluded by !**/*.png
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_64x64.png is excluded by !**/*.png
  • macos-stats-bar/assets/screenshot.png is excluded by !**/*.png
📒 Files selected for processing (21)
  • README.md
  • macos-stats-bar/.gitignore
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.pbxproj
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/xcshareddata/xcschemes/ClaudeMonitor.xcscheme
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/AppSettings.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AccentColor.colorset/Contents.json
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/Contents.json
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/Contents.json
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/BookmarkManager.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/MonitoringViewModel.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/TokenDataReader.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/ClaudeMonitor.entitlements
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/ClaudeMonitorApp.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Localization.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/SettingsView.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitor/StatusBarView.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitorTests/ClaudeMonitorTests.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITests.swift
  • macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITestsLaunchTests.swift
  • macos-stats-bar/README.md

Comment on lines +328 to +357
25A3B8AA2F98A36E001CAFE5 /* AppStore */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_TEAM = FB5Z8HKV28;
ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = ClaudeMonitor;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 2.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.claudemonitor.statusbar;
PRODUCT_NAME = ClaudeMonitor;
REGISTER_APP_GROUPS = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = AppStore;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

AppStore config is missing entitlements wiring.

Line 337 enables sandbox, but this configuration does not set CODE_SIGN_ENTITLEMENTS, so com.apple.security.files.bookmarks.app-scope from ClaudeMonitor.entitlements may not be applied in AppStore builds.

Suggested fix
 		25A3B8AA2F98A36E001CAFE5 /* AppStore */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+				CODE_SIGN_ENTITLEMENTS = ClaudeMonitor/ClaudeMonitor.entitlements;
 				CODE_SIGN_STYLE = Automatic;
 				COMBINE_HIDPI_IMAGES = YES;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.pbxproj` around
lines 328 - 357, The AppStore XCBuildConfiguration (name = AppStore) enables the
app sandbox (ENABLE_APP_SANDBOX = YES) but does not set CODE_SIGN_ENTITLEMENTS,
so entitlements from ClaudeMonitor.entitlements (including
com.apple.security.files.bookmarks.app-scope) won’t be applied; update the
AppStore buildSettings to add CODE_SIGN_ENTITLEMENTS =
"ClaudeMonitor.entitlements" (or the correct entitlements filename used in the
project) so code signing uses the entitlements for AppStore builds.

Comment on lines +3 to +5
{
"idiom" : "universal"
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

AccentColor definition is empty.

The color entry specifies a "universal" idiom but does not include a "color" property with actual color values (RGB components, system color name, etc.). This will cause the app to use the system default accent color rather than a custom one.

If a custom accent color is intended for the app's UI elements, add the color specification. Otherwise, this can remain as-is to use the system default.

🎨 Example fix to add a custom accent color
   {
-    "idiom" : "universal"
+    "idiom" : "universal",
+    "color" : {
+      "color-space" : "srgb",
+      "components" : {
+        "red" : "0.000",
+        "green" : "0.478",
+        "blue" : "1.000",
+        "alpha" : "1.000"
+      }
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"idiom" : "universal"
}
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "0.000",
"green" : "0.478",
"blue" : "1.000",
"alpha" : "1.000"
}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AccentColor.colorset/Contents.json`
around lines 3 - 5, The AccentColor.colorset Contents.json currently only
declares an idiom ("universal") but omits the required "color" object, so add a
"color" entry inside the JSON for the AccentColor.colorset (Contents.json) that
defines the desired accent using either sRGB/ARGB components or a system color
name; update the "idiom":"universal" object to include the "color" property
(with correct keys like "color-space" and component values) if you want a custom
accent, otherwise leave as-is to use the system default.

Comment on lines +13 to +15
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Add at least one real assertion-based unit test for core parsing/pricing behavior.

This placeholder test currently always passes and doesn’t protect any functionality. Please replace it with a concrete test (e.g., deduplication or pricing calculation from TokenDataReader) so CI can catch regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos-stats-bar/ClaudeMonitor/ClaudeMonitorTests/ClaudeMonitorTests.swift`
around lines 13 - 15, Replace the placeholder test example() with a real,
assertion-based unit test that verifies core parsing/pricing behavior: call
TokenDataReader (or the parser/deduplicator you added) to load a controlled
input fixture, assert expected outputs such as deduplicated token counts and
calculated price values, and use XCTAssert/XCTAssertEqual to validate results;
ensure the test method (e.g., testTokenPricingOrDeduplication) is async throws
and uses deterministic input so CI will fail on regressions.

Comment thread macos-stats-bar/README.md
Comment on lines +64 to +76
```
~/.claude/projects/<project>/*.jsonl
|
v
TokenDataReader.swift -- parse JSONL, extract tokens, calculate cost, deduplicate
|
v
MonitoringViewModel.swift -- aggregate totals, compute rates, manage UI state
|
v
ClaudeMonitorApp.swift -- render menu bar label (NSImage)
StatusBarView.swift -- render detail panel (SwiftUI)
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifiers to fenced code blocks to satisfy markdownlint (MD040).

Use a neutral language like text for diagrams/tree blocks.

Proposed fix
-```
+```text
 ~/.claude/projects/<project>/*.jsonl
         |
         v
   TokenDataReader.swift        -- parse JSONL, extract tokens, calculate cost, deduplicate
         |
         v
   MonitoringViewModel.swift    -- aggregate totals, compute rates, manage UI state
         |
         v
   ClaudeMonitorApp.swift       -- render menu bar label (NSImage)
   StatusBarView.swift           -- render detail panel (SwiftUI)

@@
- +text
ClaudeMonitor/
├── ClaudeMonitor.xcodeproj/
└── ClaudeMonitor/
├── ClaudeMonitorApp.swift # App entry, MenuBarExtra, menu bar label
├── StatusBarView.swift # Detail panel UI
├── Backend/
│ ├── TokenDataReader.swift # JSONL parser, pricing engine
│ └── MonitoringViewModel.swift # Observable state, auto-refresh
└── Assets.xcassets/ # App icon

Also applies to: 94-104

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 64-64: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos-stats-bar/README.md` around lines 64 - 76, The README.md contains
fenced code blocks used as diagrams/trees without language identifiers (MD040);
update each such block (e.g., the project tree blocks shown around the
TokenDataReader/MonitoringViewModel/ClaudeMonitorApp and the ClaudeMonitor/
tree) by adding a neutral language tag like ```text immediately after the
opening backticks so the blocks become ```text ... ```, ensuring all
diagram/code-fence instances (including the other occurrence noted at lines
~94-104) use the same `text` tag.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Maciek-roboblog

Copy link
Copy Markdown
Owner

Thanks for the contribution. After the v4 merge, this repo now has a more explicit core boundary: the monitor owns the CLI/TUI, usage warehouse, trust layer, and external state protocol. A native menu bar app is better kept as a separate companion application consuming the v4 state output/protocol instead of being merged into the core package.

Closing this PR to keep the main repo focused. A fresh companion-app PR or separate repository proposal that targets the v4 state protocol would be easier to review.

@HAOGRE

HAOGRE commented Jun 29, 2026

Copy link
Copy Markdown
Author

@Maciek-roboblog Thanks for the clear explanation — totally makes sense to keep the
core boundary clean.

I'll set up a standalone companion repo for ClaudeMonitor.
One quick question: is the v4 state protocol documented anywhere?
I'd like to switch from reading JSONL directly to consuming the proper
output surface once it's available.

Will drop the companion repo link here once it's up — might be useful
for the community.

@HAOGRE

HAOGRE commented Jun 29, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed explanation, @Maciek-roboblog! This makes complete sense.

I've already moved forward with exactly this approach — the app now lives in its own dedicated repository:
👉 https://github.com/HAOGRE/ClaudeTokenMonitorBar-macOS

I've also added v4 state protocol support (v1.2.0), which reads ~/.claude-monitor/state/latest.json and displays the official 5-hour rate limit window with a color-coded progress bar and reset time directly in the menu bar popup — acting as a pure consumer of your protocol layer.

I've opened a small PR (#222) to add it to the README's Companion Apps section. Happy to adjust anything to fit your preferences.

If there's any documentation on the v4 state protocol schema I should follow for forward compatibility, I'd love a pointer — currently working from the output file structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants