Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.derived/
.derived-release/
dist/
scripts/
DerivedData/
.build/
.swiftpm/
Expand Down
48 changes: 48 additions & 0 deletions AIMeter.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ All notable changes to AIMeter will be documented in this file.

This project follows semantic versioning while it is practical for a small macOS utility.

## [0.4.0] - 2026-05-07

### Added

- Claude support with an isolated local web session, URL validation, and usage parsing from Claude's usage settings page.
- Provider-aware dashboard state for tracking Cursor and Claude side by side.
- Claude usage cards for current session, All models, Claude Design, and reset timing when Claude exposes it.
- Start-at-login setting using macOS login items.
- Local development and notarized DMG verification helper scripts.

### Changed

- Menu bar progress now uses the highest connected provider usage percentage.
- Popover only shows connected providers and keeps a loading state visible while saved provider sessions are still being checked.
- Claude usage UI now renders a stable set of cards instead of changing shape while the page loads.

### Fixed

- Deduplicated repeated Claude usage rows so `All models` and `Claude Design` appear only once.
- Filtered Claude template and explanatory copy so it does not become bogus usage text.
- Prevented the first-run provider connection screen from flashing during startup loading.

## [0.3.0] - 2026-05-06

### Changed

- Replaced menu bar percentage text with a compact progress bar-only status item.
- Kept warning/disconnected states visible in the menu bar with a small status indicator.

## [0.1.0] - 2026-05-05

### Added
Expand Down
4 changes: 3 additions & 1 deletion Sources/AIMeter/App/AIMeterApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ struct AIMeterApp: App {
SettingsView(
settingsStore: environment.settingsStore,
dashboardStore: environment.dashboardStore,
cursorUsageCoordinator: environment.cursorUsageCoordinator
cursorUsageCoordinator: environment.cursorUsageCoordinator,
claudeUsageCoordinator: environment.claudeUsageCoordinator,
launchAtLoginController: environment.launchAtLoginController
)
}
}
Expand Down
8 changes: 5 additions & 3 deletions Sources/AIMeter/App/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import AppKit

@MainActor
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let environment = AppEnvironment.shared
environment.menuBarController.install()
environment.cursorUsageCoordinator.start()
environment.claudeUsageCoordinator.start()
}

func applicationWillTerminate(_ notification: Notification) {
Task { @MainActor in
AppEnvironment.shared.cursorUsageCoordinator.stop()
}
let environment = AppEnvironment.shared
environment.cursorUsageCoordinator.stop()
environment.claudeUsageCoordinator.stop()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
26 changes: 24 additions & 2 deletions Sources/AIMeter/App/AppEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,67 @@ final class AppEnvironment {

let settingsStore: SettingsStore
let cursorSessionManager: CursorSessionManager
let claudeSessionManager: ClaudeSessionManager
let cursorUsageClient: CursorUsageClient
let claudeUsageClient: ProviderUsageClient
let cursorUsageCoordinator: CursorUsageCoordinator
let claudeUsageCoordinator: ClaudeUsageCoordinator
let dashboardStore: DashboardStore
let launchAtLoginController: LaunchAtLoginController

lazy var settingsWindowController: SettingsWindowController = {
SettingsWindowController(
settingsStore: settingsStore,
dashboardStore: dashboardStore,
cursorUsageCoordinator: cursorUsageCoordinator
cursorUsageCoordinator: cursorUsageCoordinator,
claudeUsageCoordinator: claudeUsageCoordinator,
launchAtLoginController: launchAtLoginController
)
}()
lazy var menuBarController: MenuBarController = {
MenuBarController(
dashboardStore: dashboardStore,
cursorUsageCoordinator: cursorUsageCoordinator,
claudeUsageCoordinator: claudeUsageCoordinator,
settingsWindowController: settingsWindowController
)
}()

private init() {
let settingsStore = SettingsStore()
let cursorSessionManager = CursorSessionManager()
let claudeSessionManager = ClaudeSessionManager()
let cursorUsageClient = CursorDashboardClient(
settingsStore: settingsStore,
sessionManager: cursorSessionManager
)
let claudeUsageClient = ClaudeDashboardClient(
settingsStore: settingsStore,
sessionManager: claudeSessionManager
)
let cursorUsageCoordinator = CursorUsageCoordinator(
settingsStore: settingsStore,
client: cursorUsageClient
)
let claudeUsageCoordinator = ClaudeUsageCoordinator(
settingsStore: settingsStore,
client: claudeUsageClient
)
let dashboardStore = DashboardStore(
settingsStore: settingsStore,
cursorUsageCoordinator: cursorUsageCoordinator
cursorUsageCoordinator: cursorUsageCoordinator,
claudeUsageCoordinator: claudeUsageCoordinator
)
let launchAtLoginController = LaunchAtLoginController()

self.settingsStore = settingsStore
self.cursorSessionManager = cursorSessionManager
self.claudeSessionManager = claudeSessionManager
self.cursorUsageClient = cursorUsageClient
self.claudeUsageClient = claudeUsageClient
self.cursorUsageCoordinator = cursorUsageCoordinator
self.claudeUsageCoordinator = claudeUsageCoordinator
self.dashboardStore = dashboardStore
self.launchAtLoginController = launchAtLoginController
}
}
41 changes: 41 additions & 0 deletions Sources/AIMeter/Claude/ClaudeDashboardClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import Foundation

@MainActor
final class ClaudeDashboardClient: ProviderUsageClient {
let provider = UsageProvider.claude

private let settingsStore: SettingsStore
private let sessionManager: ClaudeSessionManaging
private var pendingConnectedSnapshot: ProviderUsageSnapshot?

init(settingsStore: SettingsStore, sessionManager: ClaudeSessionManaging) {
self.settingsStore = settingsStore
self.sessionManager = sessionManager
}

func connect() async throws {
let usagePageURL = try resolvedUsagePageURL()
pendingConnectedSnapshot = try await sessionManager.connect(to: usagePageURL)
}

func fetchUsage() async throws -> ProviderUsageSnapshot {
if let pendingConnectedSnapshot {
self.pendingConnectedSnapshot = nil
if pendingConnectedSnapshot.progressPercent != nil {
return pendingConnectedSnapshot
}
}

let usagePageURL = try resolvedUsagePageURL()
return try await sessionManager.fetchUsage(from: usagePageURL)
}

func disconnect() {
pendingConnectedSnapshot = nil
sessionManager.disconnect()
}

private func resolvedUsagePageURL() throws -> URL {
try ClaudeURLValidator.validatedUsageURL(from: settingsStore.settings.claude.usagePageURL)
}
}
Loading
Loading