Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[![License: MIT](https://img.shields.io/badge/license-MIT-6e5aff?style=flat-square)](LICENSE)
[![Site](https://img.shields.io/badge/site-codexbar.app-16d3b4?style=flat-square)](https://codexbar.app)

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

Tiny macOS 14+ menu bar app that keeps **AI coding-provider limits visible** and shows when each window resets. See the [supported providers](#providers) below. One status item per provider, or Merge Icons mode with a provider switcher. No Dock icon, minimal UI, dynamic bar icons.

Expand Down
51 changes: 51 additions & 0 deletions Sources/CodexBar/Providers/OMP/OMPProviderImplementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import CodexBarCore
import Foundation
import SwiftUI

struct OMPProviderImplementation: ProviderImplementation {
let id: UsageProvider = .omp

@MainActor
func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {
.omp(context.settings.ompSettingsSnapshot())
}

@MainActor
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
[
ProviderSettingsPickerDescriptor(
id: "omp-subprovider",
title: "Subprovider",
subtitle: "Select which provider limits to display from OMP, or Combined for all aggregated limits.",
binding: context.binding(\.ompSubprovider),
options: [
ProviderSettingsPickerOption(id: "all", title: "Combined (All Limits)"),
ProviderSettingsPickerOption(id: "google-antigravity", title: "Google Antigravity (agy)"),
ProviderSettingsPickerOption(id: "openai-codex", title: "OpenAI Codex"),
ProviderSettingsPickerOption(id: "anthropic", title: "Anthropic Claude"),
ProviderSettingsPickerOption(id: "github-copilot", title: "GitHub Copilot"),
ProviderSettingsPickerOption(id: "xai-oauth", title: "xAI Grok"),
ProviderSettingsPickerOption(id: "custom", title: "Custom Subprovider…"),
],
isVisible: nil,
onChange: { _ in
await context.store.refreshProvider(.omp)
}),
]
}

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "omp-custom-subprovider",
title: "Custom Subprovider ID",
subtitle: "Specify a custom subprovider ID (e.g. for new providers added to OMP).",
kind: .plain,
placeholder: "e.g. google-antigravity",
binding: context.binding(\.ompCustomSubprovider),
actions: [],
isVisible: { context.settings.ompSubprovider == "custom" }),
]
}
}
24 changes: 24 additions & 0 deletions Sources/CodexBar/Providers/OMP/OMPSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import CodexBarCore
import Foundation

extension SettingsStore {
public var ompSubprovider: String {
get { self.configSnapshot.providerConfig(for: .omp)?.ompSubprovider ?? "all" }
set {
self.updateProviderConfig(provider: .omp) { $0.ompSubprovider = newValue }
}
}

public var ompCustomSubprovider: String {
get { self.configSnapshot.providerConfig(for: .omp)?.ompCustomSubprovider ?? "" }
set {
self.updateProviderConfig(provider: .omp) { $0.ompCustomSubprovider = newValue }
}
}

public func ompSettingsSnapshot() -> OMPProviderSettings {
OMPProviderSettings(
subprovider: self.ompSubprovider,
customSubprovider: self.ompCustomSubprovider)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,6 @@ enum ProviderImplementationManifest {
PluginCookieProviderImplementation(spec: ParallelProviderDescriptor.spec),
PluginCookieProviderImplementation(spec: ClaudePlatformProviderDescriptor.spec),
PluginAPIKeyProviderImplementation(spec: MunitoProviderDescriptor.spec),
OMPProviderImplementation(),
]
}
4 changes: 4 additions & 0 deletions Sources/CodexBar/Resources/ProviderIcon-omp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions Sources/CodexBarCLI/CLIServeProviderIcons.swift

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Sources/CodexBarCore/AgentSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ public enum AgentPSOutputParser {
executableBasename: String,
arguments: @autoclosure () -> [String]) -> AgentSession.Dialect?
{
// Provider-specific by design: This helper detects Pi and OMP process dialects.
let firstBasename = AgentProcessPath.basename(executableBasename).lowercased()
if firstBasename == AgentSession.Provider.pi.rawValue {
return .pi
Expand Down
28 changes: 28 additions & 0 deletions Sources/CodexBarCore/PathEnvironment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,34 @@ public enum BinaryLocator {
home: home)
}

public static func resolveOMPBinary(
env: [String: String] = ProcessInfo.processInfo.environment,
loginPATH: [String]? = LoginShellPathCache.shared.current,
commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,
aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator
.resolveAlias,
fileManager: FileManager = .default,
home: String = NSHomeDirectory()) -> String?
{
// Provider-specific by design: This named resolver supplies OMP's actual CLI executable name.
return self.resolveBinary(
name: "omp",
overrideKey: "OMP_CLI_PATH",
env: env,
loginPATH: loginPATH,
commandV: commandV,
aliasResolver: aliasResolver,
wellKnownPaths: [
"\(home)/Library/Application Support/Tern/omp/bin/omp",
"\(home)/.omp/bin/omp",
"\(home)/.local/bin/omp",
"/opt/homebrew/bin/omp",
"/usr/local/bin/omp",
],
fileManager: fileManager,
home: home)
}

public static func resolveCodexBinary(
env: [String: String] = ProcessInfo.processInfo.environment,
loginPATH: [String]? = LoginShellPathCache.shared.current,
Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBarCore/PiFamilySessionRootResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ struct OMPSessionRootResolver: Sendable {
home: home,
baseDirectory: baseDirectory)
{
// Provider-specific by design: This resolves OMP's default XDG session root.
let xdgSessions = xdgDataHome
.appendingPathComponent("omp", isDirectory: true)
.appendingPathComponent("sessions", isDirectory: true)
Expand Down Expand Up @@ -183,6 +184,7 @@ struct OMPSessionRootResolver: Sendable {
home: home,
baseDirectory: baseDirectory)
{
// Provider-specific by design: This resolves OMP's named profile roots under XDG.
let xdgProfileRoot = xdgDataHome
.appendingPathComponent("omp", isDirectory: true)
.appendingPathComponent("profiles", isDirectory: true)
Expand Down Expand Up @@ -221,6 +223,7 @@ struct OMPSessionRootResolver: Sendable {
home: home,
baseDirectory: baseDirectory)
{
// Provider-specific by design: This discovers OMP profiles under XDG.
directories.append(
xdgDataHome
.appendingPathComponent("omp", isDirectory: true)
Expand Down Expand Up @@ -315,6 +318,7 @@ struct OMPSessionRootResolver: Sendable {
{
configuredPath
} else {
// Provider-specific by design: This defaults to OMP's standard configuration directory.
".omp"
}
guard !name.hasPrefix("/") else { return nil }
Expand Down
5 changes: 5 additions & 0 deletions Sources/CodexBarCore/PiFamilySessionScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum PiFamilySessionFileParser {

var titleSlotWasPresent = false
var titleSlot: String?
// Provider-specific by design: OMP sessions can start with a title block.
if dialect == .omp,
let first = Self.jsonObject(from: nonEmptyLines[0]),
first["type"] as? String == "title"
Expand Down Expand Up @@ -563,6 +564,7 @@ struct PiFamilySessionScanner: Sendable {
startedAt: nil,
command: context.command,
arguments: context.arguments)
// Provider-specific by design: Check for explicit OMP profile in process context.
return AgentPSOutputParser.piDialect(for: process) == .omp &&
(Self.commandLineValue(
"--profile",
Expand Down Expand Up @@ -667,6 +669,7 @@ struct PiFamilySessionScanner: Sendable {
{
return self.isCWDIndependentPath(selector, environment: environment)
}
// Provider-specific by design: Check dialect-specific environment and command line selectors.
if AgentPSOutputParser.piDialect(for: process) == .pi,
let selector = environment["PI_CODING_AGENT_DIR"],
!selector.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
Expand Down Expand Up @@ -722,6 +725,7 @@ struct PiFamilySessionScanner: Sendable {
{
return "process:" + dialect.rawValue + ":session-dir:" + url.path
}
// Provider-specific by design: OMP command-line profile identifier.
if dialect == .omp,
let profile = Self.commandLineValue(
"--profile",
Expand Down Expand Up @@ -915,6 +919,7 @@ struct PiFamilySessionScanner: Sendable {
environment: safeEnvironment,
baseDirectory: baseDirectory).map { root in
let canonical = OMPSessionRootResolver.canonicalURL(root.url)
// Provider-specific by design: OMP default cost root resolution.
let defaultRootIsKnownEmpty = !processHasExplicitSelection &&
!Self.hasExplicitCostRootSelection(dialect: .omp, environment: environment) &&
Self.defaultCostSessionRoot(for: .omp, environment: environment) == canonical
Expand Down
70 changes: 70 additions & 0 deletions Sources/CodexBarCore/Providers/OMP/OMPCLIProbe.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Foundation

public enum OMPUsageError: LocalizedError, Sendable {
case binaryNotFound(String)
case executionFailed(String)
case parseFailed(String)

public var errorDescription: String? {
switch self {
case let .binaryNotFound(binary):
"\(binary) executable not found. Ensure OMP is installed and in PATH."
case let .executionFailed(msg):
"OMP execution failed: \(msg)"
case let .parseFailed(msg):
"Failed to parse OMP usage output: \(msg)"
}
}
}

public struct OMPCLIProbe: Sendable {
private static let commandTimeout: TimeInterval = 15

public init() {}

public func fetch(
environment: [String: String] = ProcessInfo.processInfo.environment,
subprovider: String = "all",
now: Date = Date()) async throws -> UsageSnapshot
{
let loginPATH = LoginShellPathCache.shared.current
guard let executable = BinaryLocator.resolveOMPBinary(env: environment, loginPATH: loginPATH) else {
throw OMPUsageError.binaryNotFound("omp")
}

var commandEnvironment = environment
commandEnvironment["NO_COLOR"] = "1"
commandEnvironment["PATH"] = PathBuilder.effectivePATH(
purposes: [.tty, .nodeTooling],
env: environment,
loginPATH: loginPATH)

let result: SubprocessResult
do {
result = try await SubprocessRunner.run(
binary: executable,
arguments: ["usage", "--json"],
environment: commandEnvironment,
timeout: Self.commandTimeout,
standardInput: FileHandle.nullDevice,
label: "omp-usage")
} catch let error as SubprocessRunnerError {
throw OMPUsageError.executionFailed(error.localizedDescription)
}

let stdout = result.stdout.trimmingCharacters(in: .whitespacesAndNewlines)
guard !stdout.isEmpty else {
let stderr = result.stderr.trimmingCharacters(in: .whitespacesAndNewlines)
if !stderr.isEmpty {
let sanitized = String(stderr.prefix(200))
throw OMPUsageError.executionFailed(sanitized)
}
throw OMPUsageError.parseFailed("The omp CLI returned no usage data.")
}

return try OMPUsageParser.parse(
jsonString: stdout,
subprovider: subprovider,
now: now)
}
}
13 changes: 13 additions & 0 deletions Sources/CodexBarCore/Providers/OMP/OMPProviderConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Foundation

extension ProviderConfig {
public var ompSubprovider: String? {
get { self.extensionValue(forKey: "ompSubprovider") }
set { self.setExtensionValue(newValue, forKey: "ompSubprovider") }
}

public var ompCustomSubprovider: String? {
get { self.extensionValue(forKey: "ompCustomSubprovider") }
set { self.setExtensionValue(newValue, forKey: "ompCustomSubprovider") }
}
}
Loading