feat: add native macOS menu bar app (ClaudeMonitor)#215
Conversation
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>
📝 WalkthroughWalkthroughThis PR introduces ClaudeMonitor, a complete macOS status bar application that monitors Claude API usage by reading local JSONL files from ChangesmacOS Status Bar App (ClaudeMonitor)
🎯 4 (Complex) | ⏱️ ~60 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (4)
macos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITests.swift (1)
26-32: 🏗️ Heavy lift
testExampleshould 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 winUI tests are not part of this scheme’s TestAction.
Only
ClaudeMonitorTestsis 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 valueInconsistent 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 valueMisleading thread-safety comment about DateFormatter.
The comment on lines 86-87 states that
DateFormatteris protected byisLoadingmutex, but:
isLoadinginMonitoringViewModelis a simple boolean, not a mutexTokenDataReadermethods can be called independently ofMonitoringViewModelfallbackDateFormattersare static and shared across all instancesIn practice, the current usage is single-threaded (one
Task.detachedat a time), so this isn't an active bug. However, the misleading comment could cause issues if someone reusesTokenDataReaderconcurrently.Consider either:
- Removing the incorrect comment
- Making
fallbackDateFormattersthread-local or using a lock- Using
ISO8601DateFormatterexclusively (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
⛔ Files ignored due to path filters (8)
macos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_1024x1024.pngis excluded by!**/*.pngmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_128x128.pngis excluded by!**/*.pngmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_16x16.pngis excluded by!**/*.pngmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_256x256.pngis excluded by!**/*.pngmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_32x32.pngis excluded by!**/*.pngmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_512x512.pngis excluded by!**/*.pngmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/icon_64x64.pngis excluded by!**/*.pngmacos-stats-bar/assets/screenshot.pngis excluded by!**/*.png
📒 Files selected for processing (21)
README.mdmacos-stats-bar/.gitignoremacos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.pbxprojmacos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/project.xcworkspace/contents.xcworkspacedatamacos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj/xcshareddata/xcschemes/ClaudeMonitor.xcschememacos-stats-bar/ClaudeMonitor/ClaudeMonitor/AppSettings.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AccentColor.colorset/Contents.jsonmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/AppIcon.appiconset/Contents.jsonmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Assets.xcassets/Contents.jsonmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/BookmarkManager.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/MonitoringViewModel.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Backend/TokenDataReader.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/ClaudeMonitor.entitlementsmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/ClaudeMonitorApp.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/Localization.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/SettingsView.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitor/StatusBarView.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitorTests/ClaudeMonitorTests.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITests.swiftmacos-stats-bar/ClaudeMonitor/ClaudeMonitorUITests/ClaudeMonitorUITestsLaunchTests.swiftmacos-stats-bar/README.md
| 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; |
There was a problem hiding this comment.
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.
| { | ||
| "idiom" : "universal" | ||
| } |
There was a problem hiding this comment.
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.
| { | |
| "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.
| @Test func example() async throws { | ||
| // Write your test here and use APIs like `#expect(...)` to check expected conditions. | ||
| } |
There was a problem hiding this comment.
🛠️ 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.
| ``` | ||
| ~/.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) | ||
| ``` |
There was a problem hiding this comment.
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>
|
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. |
|
@Maciek-roboblog Thanks for the clear explanation — totally makes sense to keep the I'll set up a standalone companion repo for ClaudeMonitor. Will drop the companion repo link here once it's up — might be useful |
|
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: I've also added v4 state protocol support (v1.2.0), which reads 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. |
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.message_id:request_idhash — mirrors the Python logic exactlyFALLBACK_PRICINGin the Python project (Opus / Sonnet / Haiku)~/.claude/projects/*.jsonldirectly; all data stays localScreenshot
Project structure
Test plan
open macos-stats-bar/ClaudeMonitor/ClaudeMonitor.xcodeproj→ Cmd+R🤖 Generated with Claude Code