Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<a href="https://codexbar.app"><img src="docs/social.png" alt="CodexBar — every AI coding limit in your menu bar. 60 providers." width="100%" /></a>

Tiny macOS 14+ menu bar app that keeps **AI coding-provider limits visible** and shows when each window resets. Codex, OpenAI, Claude, Cursor, Gemini, Copilot, Grok, GroqCloud, ElevenLabs, Deepgram, z.ai, MiniMax, Kiro, Zed, Vertex AI, Augment, OpenRouter, LiteLLM, LLM Proxy, Codebuff, Command Code, AWS Bedrock, and many newer coding providers. One status item per provider, or Merge Icons mode with a provider switcher. No Dock icon, minimal UI, dynamic bar icons.
Tiny macOS 14+ menu bar app that keeps **AI coding-provider limits visible** and shows when each window resets. Codex, OpenAI, Claude, Cursor, Gemini, Copilot, Grok, GroqCloud, ElevenLabs, Deepgram, z.ai, MiniMax, Kiro, Zed, Vertex AI, Augment, OpenRouter, LiteLLM, LLM Proxy, Codebuff, Command Code, ClinePass, AWS Bedrock, and many newer coding providers. One status item per provider, or Merge Icons mode with a provider switcher. No Dock icon, minimal UI, dynamic bar icons.

<img src="docs/codexbar.png" alt="CodexBar menu popover with provider tiles, usage bars, and reset countdowns" width="520" />

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import CodexBarCore
import Foundation

struct ClinePassProviderImplementation: ProviderImplementation {
let id: UsageProvider = .clinepass

@MainActor
func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {
ProviderPresentation { _ in "api" }
}

@MainActor
func observeSettings(_ settings: SettingsStore) {
_ = settings.clinePassAPIKey
}

@MainActor
func isAvailable(context: ProviderAvailabilityContext) -> Bool {
if ClinePassSettingsReader.apiKey(environment: context.environment) != nil {
return true
}
return !context.settings.clinePassAPIKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "clinepass-api-key",
title: "API key",
subtitle: "Stored in ~/.codexbar/config.json. Paste a ClinePass API key.",
kind: .secure,
placeholder: "ClinePass API key...",
binding: context.stringBinding(\.clinePassAPIKey),
actions: [],
isVisible: nil,
onActivate: nil),
]
}
}
14 changes: 14 additions & 0 deletions Sources/CodexBar/Providers/ClinePass/ClinePassSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import CodexBarCore
import Foundation

extension SettingsStore {
var clinePassAPIKey: String {
get { self.configSnapshot.providerConfig(for: .clinepass)?.sanitizedAPIKey ?? "" }
set {
self.updateProviderConfig(provider: .clinepass) { entry in
entry.apiKey = self.normalizedConfigValue(newValue)
}
self.logSecretUpdate(provider: .clinepass, field: "apiKey", value: newValue)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum ProviderImplementationRegistry {
case .openai: OpenAIAPIProviderImplementation()
case .azureopenai: AzureOpenAIProviderImplementation()
case .claude: ClaudeProviderImplementation()
case .clinepass: ClinePassProviderImplementation()
case .cursor: CursorProviderImplementation()
case .opencode: OpenCodeProviderImplementation()
case .opencodego: OpenCodeGoProviderImplementation()
Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBar/Resources/ProviderIcon-clinepass.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions Sources/CodexBar/UsageStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,7 @@ extension UsageStore {
let unimplementedDebugLogMessages: [UsageProvider: String] = [
.gemini: "Gemini debug log not yet implemented",
.antigravity: "Antigravity debug log not yet implemented",
.clinepass: "ClinePass debug log not yet implemented",
.opencode: "OpenCode debug log not yet implemented",
.alibaba: "Alibaba Coding Plan debug log not yet implemented",
.alibabatokenplan: "Alibaba Token Plan debug log not yet implemented",
Expand Down Expand Up @@ -1151,12 +1152,11 @@ extension UsageStore {
configToken: nil,
hasEnvToken: deepSeekHasEnvToken,
hasTokenAccount: deepSeekHasTokenAccount)
case .gemini, .antigravity, .opencode, .opencodego, .alibabatokenplan, .factory, .copilot, .devin,
.vertexai, .kilo, .kiro, .kimi, .kimik2, .moonshot, .jetbrains, .perplexity, .mimo, .doubao,
.sakana, .abacus, .mistral, .codebuff, .crof, .windsurf, .venice, .manus, .commandcode, .qoder,
.stepfun,
.bedrock, .grok, .groq, .t3chat, .llmproxy, .litellm, .zed, .deepgram, .poe, .chutes,
.clawrouter, .wayfinder, .sub2api, .zenmux:
case .clinepass, .gemini, .antigravity, .opencode, .opencodego, .alibabatokenplan, .factory,
.copilot, .devin, .vertexai, .kilo, .kiro, .kimi, .kimik2, .moonshot, .jetbrains, .perplexity,
.mimo, .doubao, .sakana, .abacus, .mistral, .codebuff, .crof, .windsurf, .venice, .manus,
.commandcode, .qoder, .stepfun, .bedrock, .grok, .groq, .t3chat, .llmproxy, .litellm, .zed,
.deepgram, .poe, .chutes, .clawrouter, .wayfinder, .sub2api, .zenmux:
return unimplementedDebugLogMessages[provider] ?? "Debug log not yet implemented"
}
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBarCLI/CLIDiagnoseCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ extension CodexBarCLI {
BedrockSettingsReader.hasCredentials(environment: environment)
case .claude:
ClaudeAdminAPISettingsReader.apiKey(environment: environment) != nil
case .clinepass:
ClinePassSettingsReader.apiKey(environment: environment) != nil
case .codebuff:
CodebuffSettingsReader.apiKey(environment: environment) != nil
case .chutes:
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBarCore/Config/ProviderConfigEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public enum ProviderConfigEnvironment {
}
}

// swiftlint:disable:next cyclomatic_complexity
private static func directAPIKeyEnvironmentKey(for provider: UsageProvider) -> String? {
switch provider {
case .amp:
Expand All @@ -135,6 +136,8 @@ public enum ProviderConfigEnvironment {
AzureOpenAISettingsReader.apiKeyEnvironmentKey
case .claude:
ClaudeAdminAPISettingsReader.adminAPIKeyEnvironmentKey
case .clinepass:
ClinePassSettingsReader.apiKeyEnvironmentKey
case .zai:
ZaiSettingsReader.apiTokenKey
case .minimax:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Generated by Scripts/regenerate-codex-parser-hash.sh. Do not edit by hand.

enum CodexParserHash {
static let value = "9b26fd821cf090dc"
static let value = "8b3e77f6024d4053"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import Foundation

public enum ClinePassProviderDescriptor {
public static let descriptor: ProviderDescriptor = Self.makeDescriptor()

static func makeDescriptor() -> ProviderDescriptor {
ProviderDescriptor(
id: .clinepass,
metadata: ProviderMetadata(
id: .clinepass,
displayName: "ClinePass",
sessionLabel: "5-hour",
weeklyLabel: "Weekly",
opusLabel: "Monthly",
supportsOpus: true,
supportsCredits: false,
creditsHint: "",
toggleTitle: "Show ClinePass usage",
cliName: "clinepass",
defaultEnabled: false,
isPrimaryProvider: false,
usesAccountFallback: false,
browserCookieOrder: nil,
dashboardURL: "https://app.cline.bot/dashboard/subscription?personal=true",
statusPageURL: nil,
statusLinkURL: nil),
branding: ProviderBranding(
iconStyle: .clinepass,
iconResourceName: "ProviderIcon-clinepass",
color: ProviderColor(red: 0.38, green: 0.64, blue: 0.98)),
tokenCost: ProviderTokenCostConfig(
supportsTokenCost: false,
noDataMessage: { "ClinePass cost history is not available via the usage-limits API." }),
fetchPlan: .apiToken(
strategyID: "clinepass.api",
resolveToken: { ProviderTokenResolver.clinePassToken(environment: $0) },
missingCredentialsError: { ClinePassUsageError.missingCredentials },
loadUsage: { apiKey, _ in
try await ClinePassUsageFetcher.fetchUsage(apiKey: apiKey).toUsageSnapshot()
}),
cli: ProviderCLIConfig(
name: "clinepass",
aliases: [],
versionDetector: nil))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation

public enum ClinePassSettingsReader {
public static let apiKeyEnvironmentKey = "CLINE_API_KEY"
public static let alternateAPIKeyEnvironmentKey = "CLINEPASS_API_KEY"
public static let apiKeyEnvironmentKeys = [
Self.apiKeyEnvironmentKey,
Self.alternateAPIKeyEnvironmentKey,
]

public static func apiKey(
environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
{
for key in self.apiKeyEnvironmentKeys {
if let value = self.cleaned(environment[key]) {
return value
}
}
return nil
}

private static func cleaned(_ raw: String?) -> String? {
guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
return nil
}
if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
(value.hasPrefix("'") && value.hasSuffix("'"))
{
value = String(value.dropFirst().dropLast())
}
value = value.trimmingCharacters(in: .whitespacesAndNewlines)
return value.isEmpty ? nil : value
}
}
Loading
Loading