Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion CopilotMonitor/CLI/CLIProviderManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ actor CLIProviderManager {
private let fetchTimeout: TimeInterval = 10.0

static let registeredProviders: [ProviderIdentifier] = [
.claude, .codex, .cursor, .geminiCLI, .openRouter,
.claude, .codex, .commandCode, .cursor, .geminiCLI, .openRouter,
.antigravity, .openCodeZen, .kimi, .minimaxCodingPlan, .zaiCodingPlan,
.nanoGpt,
.chutes, .copilot,
Expand All @@ -27,6 +27,7 @@ actor CLIProviderManager {
// Shared providers (no UI dependencies)
let claudeProvider = ClaudeProvider()
let codexProvider = CodexProvider()
let commandCodeProvider = CommandCodeProvider()
let cursorProvider = CursorProvider()
let geminiCLIProvider = GeminiCLIProvider()
let openRouterProvider = OpenRouterProvider()
Expand All @@ -45,6 +46,7 @@ actor CLIProviderManager {
self.providers = [
claudeProvider,
codexProvider,
commandCodeProvider,
cursorProvider,
geminiCLIProvider,
openRouterProvider,
Expand Down
10 changes: 10 additions & 0 deletions CopilotMonitor/CopilotMonitor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
CLIANTIGRAVITY11111111 /* AntigravityProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DA8E47F4CFEFC3571708AD /* AntigravityProvider.swift */; };
CLICLAUDE11111111111111 /* ClaudeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DDC1B4DE6B5118CE4AE8F82 /* ClaudeProvider.swift */; };
CLICODEX111111111111111 /* CodexProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76783C44AA2329AE3FA7E981 /* CodexProvider.swift */; };
CMDCODEAPP111111111111 /* CommandCodeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CMDCODEFILE11111111111 /* CommandCodeProvider.swift */; };
CMDCODECLI111111111111 /* CommandCodeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CMDCODEFILE11111111111 /* CommandCodeProvider.swift */; };
CMDCODETESTBF111111111 /* CommandCodeProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CMDCODETESTFR111111111 /* CommandCodeProviderTests.swift */; };
CLICURSOR1111111111111 /* CursorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CURSORFILE222222222222 /* CursorProvider.swift */; };
CLICOPILOT11111111111111 /* CopilotCLIProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = CLICOPILOT22222222222222 /* CopilotCLIProvider.swift */; };
CLICOPILOTUSAGE111111111 /* CopilotUsage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22222222222222222222222 /* CopilotUsage.swift */; };
Expand Down Expand Up @@ -177,6 +180,8 @@
CLI5555555555555555555555 /* opencodebar-cli */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "opencodebar-cli"; sourceTree = BUILT_PRODUCTS_DIR; };
CLICOPILOT22222222222222 /* CopilotCLIProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopilotCLIProvider.swift; sourceTree = "<group>"; };
CLIPROVMGR22222222222222 /* CLIProviderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLIProviderManager.swift; sourceTree = "<group>"; };
CMDCODEFILE11111111111 /* CommandCodeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandCodeProvider.swift; sourceTree = "<group>"; };
CMDCODETESTFR111111111 /* CommandCodeProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandCodeProviderTests.swift; sourceTree = "<group>"; };
CURSORFILE222222222222 /* CursorProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CursorProvider.swift; sourceTree = "<group>"; };
CURSORTSTFR11111111111 /* CursorProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CursorProviderTests.swift; sourceTree = "<group>"; };
D22222222222222222222222 /* StatusBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -269,6 +274,7 @@
06EC3B683EB6892E4F9C8316 /* GeminiCLIProvider.swift */,
4DDC1B4DE6B5118CE4AE8F82 /* ClaudeProvider.swift */,
76783C44AA2329AE3FA7E981 /* CodexProvider.swift */,
CMDCODEFILE11111111111 /* CommandCodeProvider.swift */,
CURSORFILE222222222222 /* CursorProvider.swift */,
F4AE672516E564CC52739BD9 /* CopilotProvider.swift */,
OC2222222222222222222222 /* OpenCodeProvider.swift */,
Expand Down Expand Up @@ -430,6 +436,7 @@
CURSORTSTFR11111111111 /* CursorProviderTests.swift */,
TOKENTESTFR1111111111111 /* TokenManagerTests.swift */,
CODEXTESTFR111111111111 /* CodexProviderTests.swift */,
CMDCODETESTFR111111111 /* CommandCodeProviderTests.swift */,
OCAUTHTESTFR11111111111 /* OpenCodeAuthDecodingTests.swift */,
OCZENTESTFR111111111111 /* OpenCodeZenProviderTests.swift */,
);
Expand Down Expand Up @@ -592,6 +599,7 @@
68CE796F262067298FE88469 /* BrowserCookieService.swift in Sources */,
CLICLAUDE11111111111111 /* ClaudeProvider.swift in Sources */,
CLICODEX111111111111111 /* CodexProvider.swift in Sources */,
CMDCODECLI111111111111 /* CommandCodeProvider.swift in Sources */,
CLICURSOR1111111111111 /* CursorProvider.swift in Sources */,
F35960B456C28D71BE7575A5 /* GeminiCLIProvider.swift in Sources */,
CLIOPENROUTER111111111 /* OpenRouterProvider.swift in Sources */,
Expand Down Expand Up @@ -635,6 +643,7 @@
87297CDD8B65F7EF0BC5D650 /* ProviderManager.swift in Sources */,
AD95EBD6AE3134DF4C797577 /* ClaudeProvider.swift in Sources */,
0B6CF8BEB936A2055F19AA8C /* CodexProvider.swift in Sources */,
CMDCODEAPP111111111111 /* CommandCodeProvider.swift in Sources */,
CURSORAPP1111111111111 /* CursorProvider.swift in Sources */,
27A0B56ADAC5B75A3270F90D /* CopilotProvider.swift in Sources */,
OC1111111111111111111111 /* OpenCodeProvider.swift in Sources */,
Expand Down Expand Up @@ -676,6 +685,7 @@
CURSORTSTBF11111111111 /* CursorProviderTests.swift in Sources */,
TOKENTESTBF1111111111111 /* TokenManagerTests.swift in Sources */,
CODEXTESTBF111111111111 /* CodexProviderTests.swift in Sources */,
CMDCODETESTBF111111111 /* CommandCodeProviderTests.swift in Sources */,
OCAUTHTESTBF11111111111 /* OpenCodeAuthDecodingTests.swift in Sources */,
OCZENTESTBF111111111111 /* OpenCodeZenProviderTests.swift in Sources */,
);
Expand Down
5 changes: 5 additions & 0 deletions CopilotMonitor/CopilotMonitor/App/StatusBarController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,8 @@ final class StatusBarController: NSObject {
details?.sparkUsage,
priority: priorityForWindowHours(details?.sparkPrimaryWindowHours, fallback: .hourly)
)
case .commandCode:
add(usage.usagePercentage, priority: .monthly)
case .cursor:
add(details?.cursorAutoUsage, priority: .monthly)
add(details?.cursorApiUsage, priority: .monthly)
Expand Down Expand Up @@ -1876,6 +1878,7 @@ final class StatusBarController: NSObject {
.kimi,
.minimaxCodingPlan,
.codex,
.commandCode,
.cursor,
.zaiCodingPlan,
.nanoGpt,
Expand Down Expand Up @@ -2929,6 +2932,8 @@ final class StatusBarController: NSObject {
image = NSImage(named: "ClaudeIcon")
case .codex:
image = NSImage(named: "CodexIcon")
case .commandCode:
image = NSImage(systemSymbolName: identifier.iconName, accessibilityDescription: identifier.displayName)
case .cursor:
image = NSImage(named: "CursorIcon")
case .geminiCLI:
Expand Down
46 changes: 46 additions & 0 deletions CopilotMonitor/CopilotMonitor/Helpers/ProviderMenuBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,52 @@ extension StatusBarController {
// === Subscription ===
addSubscriptionItems(to: submenu, provider: .codex, accountId: subscriptionAccountId)

case .commandCode:
if let total = details.creditsTotal,
total > 0,
let remaining = details.creditsRemaining {
let usagePercent = max(0, min(((total - remaining) / total) * 100.0, 999.0))
createUsageWindowRow(
label: "Monthly Credits",
usagePercent: usagePercent,
resetDate: details.primaryReset,
isMonthly: true
).forEach { submenu.addItem($0) }
}

if details.planType != nil || details.creditsTotal != nil || details.creditsRemaining != nil {
submenu.addItem(NSMenuItem.separator())
}

if let plan = details.planType {
let item = NSMenuItem()
item.view = createDisabledLabelView(
text: "Plan: \(plan)",
icon: NSImage(systemSymbolName: "crown", accessibilityDescription: "Plan")
)
submenu.addItem(item)
}

if let used = details.monthlyCost, let total = details.creditsTotal {
let item = NSMenuItem()
item.view = createDisabledLabelView(text: String(format: "Monthly Used: $%.2f / $%.2f", used, total))
submenu.addItem(item)
}

if let remaining = details.creditsRemaining {
let item = NSMenuItem()
item.view = createDisabledLabelView(text: String(format: "Credits Left: $%.2f", remaining))
submenu.addItem(item)
}

if let purchasedCredits = details.creditsBalance, purchasedCredits > 0 {
let item = NSMenuItem()
item.view = createDisabledLabelView(text: String(format: "Purchased Credits: $%.2f", purchasedCredits))
submenu.addItem(item)
}

addSubscriptionItems(to: submenu, provider: .commandCode, accountId: subscriptionAccountId)

case .cursor:
var hasUsageWindow = false
func addCursorUsageWindow(label: String, usagePercent: Double?, resetDate: Date?) {
Expand Down
7 changes: 7 additions & 0 deletions CopilotMonitor/CopilotMonitor/Models/ProviderProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ enum ProviderIdentifier: String, CaseIterable {
case copilot
case claude
case codex
case commandCode = "command_code"
case cursor
case geminiCLI = "gemini_cli"
case openRouter = "openrouter"
Expand All @@ -36,6 +37,8 @@ enum ProviderIdentifier: String, CaseIterable {
return "Claude"
case .codex:
return "ChatGPT"
case .commandCode:
return "Command Code"
case .cursor:
return "Cursor"
case .geminiCLI:
Expand Down Expand Up @@ -75,6 +78,8 @@ enum ProviderIdentifier: String, CaseIterable {
return "Claude"
case .codex:
return "Codex"
case .commandCode:
return "Command"
case .cursor:
return "Cursor"
case .geminiCLI:
Expand Down Expand Up @@ -114,6 +119,8 @@ enum ProviderIdentifier: String, CaseIterable {
return "brain.head.profile"
case .codex:
return "sparkles"
case .commandCode:
return "command"
case .cursor:
return "CursorIcon"
case .geminiCLI:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ struct ProviderSubscriptionPresets {
SubscriptionPreset(name: "Pro", cost: 200)
]

static let commandCode: [SubscriptionPreset] = [
SubscriptionPreset(name: "Go", cost: 10),
SubscriptionPreset(name: "Pro", cost: 30),
SubscriptionPreset(name: "Max", cost: 150),
SubscriptionPreset(name: "Ultra", cost: 300)
Comment on lines +113 to +116
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use actual plan prices in Command Code presets

These preset amounts are the included credit buckets, not the monthly subscription fees, so selecting a Command Code preset will overstate monthly spend in subscription totals (for example, Go is treated as $10/m instead of the plan price). This directly skews the Quota Status: $.../m and aggregate status-bar cost for users who rely on presets instead of manual custom pricing.

Useful? React with 👍 / 👎.

]

static let cursor: [SubscriptionPreset] = [
SubscriptionPreset(name: "Pro", cost: 20),
SubscriptionPreset(name: "Teams", cost: 40)
Expand Down Expand Up @@ -181,6 +188,8 @@ struct ProviderSubscriptionPresets {
return claude
case .codex:
return codex
case .commandCode:
return commandCode
case .cursor:
return cursor
case .geminiCLI:
Expand Down
Loading