macos-ai-menubar-usage/AIMenubarUsage/Models/UsageMetric.swift
Felipe M. 00c4e33171
Initial commit: AI Menubar Usage — macOS menu bar app for Cursor and Claude.
Includes provider integrations, settings, NSStatusItem UI, tests, Makefile, and README with screenshot.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 20:41:40 +02:00

121 lines
4.4 KiB
Swift

import Foundation
enum UsageMetric: String, Codable, CaseIterable, Identifiable, Sendable {
// Cursor
case cursorTotalPercentUsed
case cursorApiPercentUsed
case cursorAutoPercentUsed
case cursorPlanUsed
case cursorPlanLimit
case cursorPlanRemaining
case cursorOnDemandUsed
case cursorBillingCycleStart
case cursorBillingCycleEnd
case cursorMembershipType
// Claude OAuth
case claudeFiveHourUtilization
case claudeFiveHourResetsAt
case claudeSevenDayUtilization
case claudeSevenDayResetsAt
case claudeExtraUsage
case claudeSevenDayOpusUtilization
case claudeSevenDaySonnetUtilization
// Claude Admin API
case claudeAdminInputTokens
case claudeAdminOutputTokens
case claudeAdminCostUSD
// Cursor Admin API
case cursorAdminSpendCents
case cursorAdminMemberCount
var id: String { rawValue }
var providerID: ProviderID {
switch self {
case .cursorTotalPercentUsed, .cursorApiPercentUsed, .cursorAutoPercentUsed,
.cursorPlanUsed, .cursorPlanLimit, .cursorPlanRemaining, .cursorOnDemandUsed,
.cursorBillingCycleStart, .cursorBillingCycleEnd, .cursorMembershipType,
.cursorAdminSpendCents, .cursorAdminMemberCount:
.cursor
case .claudeFiveHourUtilization, .claudeFiveHourResetsAt, .claudeSevenDayUtilization,
.claudeSevenDayResetsAt, .claudeExtraUsage, .claudeSevenDayOpusUtilization,
.claudeSevenDaySonnetUtilization, .claudeAdminInputTokens, .claudeAdminOutputTokens,
.claudeAdminCostUSD:
.claude
}
}
var displayName: String {
switch self {
case .cursorTotalPercentUsed: "Total usage %"
case .cursorApiPercentUsed: "API usage %"
case .cursorAutoPercentUsed: "Auto usage %"
case .cursorPlanUsed: "Plan used"
case .cursorPlanLimit: "Plan limit"
case .cursorPlanRemaining: "Plan remaining"
case .cursorOnDemandUsed: "On-demand spend"
case .cursorBillingCycleStart: "Billing cycle start"
case .cursorBillingCycleEnd: "Billing cycle end"
case .cursorMembershipType: "Membership"
case .claudeFiveHourUtilization: "5-hour session %"
case .claudeFiveHourResetsAt: "5-hour reset"
case .claudeSevenDayUtilization: "Weekly usage %"
case .claudeSevenDayResetsAt: "Weekly reset"
case .claudeExtraUsage: "Extra usage"
case .claudeSevenDayOpusUtilization: "Weekly Opus %"
case .claudeSevenDaySonnetUtilization: "Weekly Sonnet %"
case .claudeAdminInputTokens: "Input tokens (7d)"
case .claudeAdminOutputTokens: "Output tokens (7d)"
case .claudeAdminCostUSD: "Cost USD (7d)"
case .cursorAdminSpendCents: "Team spend (cycle)"
case .cursorAdminMemberCount: "Team members"
}
}
static func defaults(for provider: ProviderID, location: MetricLocation) -> [UsageMetric] {
switch (provider, location) {
case (.cursor, .menuBar):
return [.cursorTotalPercentUsed]
case (.cursor, .popover):
return [
.cursorTotalPercentUsed,
.cursorApiPercentUsed,
.cursorAutoPercentUsed,
.cursorPlanUsed,
.cursorPlanLimit,
.cursorOnDemandUsed,
.cursorBillingCycleEnd,
]
case (.claude, .menuBar):
return [.claudeFiveHourUtilization]
case (.claude, .popover):
return [
.claudeFiveHourUtilization,
.claudeFiveHourResetsAt,
.claudeSevenDayUtilization,
.claudeSevenDayResetsAt,
]
}
}
static func metrics(for provider: ProviderID, authMode: AuthMode) -> [UsageMetric] {
switch (provider, authMode) {
case (.cursor, .adminAPI):
return [.cursorAdminSpendCents, .cursorAdminMemberCount]
case (.cursor, _):
return allCases.filter { $0.providerID == .cursor && !$0.rawValue.hasPrefix("cursorAdmin") }
case (.claude, .adminAPI):
return [.claudeAdminInputTokens, .claudeAdminOutputTokens, .claudeAdminCostUSD]
case (.claude, _):
return allCases.filter { $0.providerID == .claude && !$0.rawValue.hasPrefix("claudeAdmin") }
}
}
}
enum MetricLocation: String, Codable, Sendable {
case menuBar
case popover
}