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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
### Added

- Vercel: optionally show your personal AI Gateway budget using a separate Vercel access token and Team ID, with UTC reset times and a clear unconfigured state (#4621, #4623). Thanks @sf-jin-ku!
- Menu bar: add a default-on Show window labels layout option; turn it off for bare Session, Weekly, and Scoped weekly percentages while retaining full VoiceOver descriptions (#4634). Thanks @erikburman!
- Plugins: user plugins can declare a monochrome inline SVG icon (`icon.svg`, at most 8 KB, plain shapes only), drawn as a template image in the switcher and menu card; an SVG that fails validation falls back to the monogram (#4608). Thanks @shivanathd!
- Plugins: detail rows can declare normal, warning, or critical severity independently of progress, with system colors and warning symbols (#4602). Thanks @DamonYoungWork!

### Fixed

- Agent Sessions: keep Tailscale discovery from launching the VPN app, and add a local-only mode that disables remote discovery and SSH polling (#4624, #4583).
- Antigravity: read OAuth quota summaries from the daily host to match the current Gemini allowances reported by agy (#4632). Thanks @toxakara!
- TypeSafe: stop stretching the provider icon in the menu bar and Settings, so it keeps its shape at the size of the other provider icons; the website and HTTP dashboard use the same padded icon (#4619). Thanks @lg!

- Settings: disable Restore default order when provider order is already at its default, and clarify the enabled-provider filter (#4592). Thanks @elijahfriedman!
- Claude: preserve model-specific weekly usage beside malformed limit entries in OAuth and Web responses, and ignore boolean percentages (#4614, #4126). Thanks @Akagilnc!
- Grok: restore CLI billing through the ACP extension method, including current usage, reset, plan, and prepaid balance fields (#4595). Thanks @noisy!
Expand Down
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=60784c48f741564c" alt="CodexBar — every AI coding limit in your menu bar. 106 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
17 changes: 11 additions & 6 deletions Sources/CodexBar/AgentSessionsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ final class AgentSessionsStore {
self.settings.stayAwakeEnabled
}

var remoteMonitoringEnabled: Bool {
self.settings.agentSessionsEnabled && self.settings.agentSessionsRemoteEnabled
}

var schedulerState: SchedulerState {
SchedulerState(
isStarted: self.isStarted,
Expand Down Expand Up @@ -200,8 +204,8 @@ final class AgentSessionsStore {
// Adaptive keeps only the timestamp signal. Retained session paths and identities
// remain scoped to the explicitly enabled Agent Sessions UI.
self.localSessions = []
self.remoteHosts = []
}
if !self.remoteMonitoringEnabled { self.remoteHosts = [] }
if !self.localMonitoringEnabled {
self.latestLocalActivityAt = nil
}
Expand All @@ -226,6 +230,7 @@ final class AgentSessionsStore {
func focus(_ session: AgentSession, remoteHost: String?) {
if let remoteHost {
Task {
guard self.remoteMonitoringEnabled else { return }
await self.remoteFetcher.focus(sessionID: session.id, host: remoteHost)
}
} else {
Expand Down Expand Up @@ -302,7 +307,7 @@ final class AgentSessionsStore {
self.localImmediateTask?.cancel()
}

let needsRemoteScheduler = self.isStarted && self.settings.agentSessionsEnabled
let needsRemoteScheduler = self.isStarted && self.remoteMonitoringEnabled
if needsRemoteScheduler, self.remotePeriodicTask == nil {
let periodicSleep = self.periodicSleep
self.remotePeriodicTask = Task { [weak self] in
Expand Down Expand Up @@ -377,7 +382,7 @@ final class AgentSessionsStore {
}

private func requestRemoteRefresh() {
guard self.isStarted, self.settings.agentSessionsEnabled, self.remoteImmediateTask == nil else { return }
guard self.isStarted, self.remoteMonitoringEnabled, self.remoteImmediateTask == nil else { return }
guard let generation = self.remoteRefreshGate.begin() else { return }

let manualHosts = self.manualHosts
Expand All @@ -390,7 +395,7 @@ final class AgentSessionsStore {
}
var hosts = manualHosts
await hosts.append(contentsOf: remoteHostDiscovery())
let results = await remoteFetch(hosts)
let results = Task.isCancelled ? [] : await remoteFetch(hosts)
self?.completeRemoteRefresh(
generation: generation,
results: results,
Expand All @@ -408,15 +413,15 @@ final class AgentSessionsStore {
if !wasCancelled,
outcome.shouldPublish,
self.isStarted,
self.settings.agentSessionsEnabled,
self.remoteMonitoringEnabled,
let results,
results != self.remoteHosts
{
self.remoteHosts = results
self.lastUpdatedAt = Date()
self.onUpdate?()
}
if outcome.shouldRetry, self.isStarted, self.settings.agentSessionsEnabled {
if outcome.shouldRetry, self.isStarted, self.remoteMonitoringEnabled {
self.requestRemoteRefresh()
}
}
Expand Down
19 changes: 17 additions & 2 deletions Sources/CodexBar/MenuBarLayoutEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,16 @@ struct MenuBarLayoutEditor: View {
})
}

private var showWindowLabelsBinding: Binding<Bool> {
Binding(
get: { self.settings.menuBarLayoutShowWindowLabels },
set: { enabled in
self.settings.menuBarLayoutShowWindowLabels = enabled
MenuBarLayoutEditorPersistence.activate(
self.layout, for: self.persistenceProvider, settings: self.settings)
})
}

private var gapBinding: Binding<MenuBarLayoutGap> {
Binding(
get: { self.settings.menuBarLayoutGap },
Expand Down Expand Up @@ -754,7 +764,8 @@ struct MenuBarLayoutEditor: View {
size: self.sizeBinding,
gap: self.gapBinding,
verticalAdjustment: self.$settings.menuBarLayoutVerticalAdjustment,
monospacedDigits: self.monospacedDigitsBinding)
monospacedDigits: self.monospacedDigitsBinding,
showWindowLabels: self.showWindowLabelsBinding)
}

private func applyPreset(_ preset: MenuBarLayoutPreset) {
Expand Down Expand Up @@ -951,7 +962,8 @@ struct MenuBarLayoutPreview: View {
verticalAdjustment: self.settings.menuBarLayoutVerticalAdjustment,
colorPace: self.settings.menuBarColorPace,
colorByProvider: self.settings.menuBarColorByProvider,
monospacedDigits: self.settings.menuBarLayoutMonospacedDigits))
monospacedDigits: self.settings.menuBarLayoutMonospacedDigits,
showWindowLabels: self.settings.menuBarLayoutShowWindowLabels))
MenuBarLayoutPreviewText(rendered: rendered)
}

Expand Down Expand Up @@ -1273,6 +1285,7 @@ struct MenuBarLayoutDisplayOptions: View {
@Binding var gap: MenuBarLayoutGap
@Binding var verticalAdjustment: Int
@Binding var monospacedDigits: Bool
@Binding var showWindowLabels: Bool

var body: some View {
VStack(alignment: .leading, spacing: 12) {
Expand Down Expand Up @@ -1332,6 +1345,8 @@ struct MenuBarLayoutDisplayOptions: View {
}
Toggle(L("menu_bar_layout_monospaced_digits"), isOn: self.$monospacedDigits)
.toggleStyle(.checkbox)
Toggle(L("menu_bar_layout_show_window_labels"), isOn: self.$showWindowLabels)
.toggleStyle(.checkbox)
}
}
}
24 changes: 17 additions & 7 deletions Sources/CodexBar/MenuBarLayoutRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ struct MenuBarLayoutRenderExtra: Hashable {
struct MenuBarLayoutRenderOptions: Hashable {
let size: MenuBarLayoutSize
let monospacedDigits: Bool
let showWindowLabels: Bool
let colorPace: Bool
let colorByProvider: Bool
let highContrast: Bool
Expand Down Expand Up @@ -175,10 +176,12 @@ struct MenuBarLayoutRenderOptions: Hashable {
colorPace: Bool = false,
colorByProvider: Bool = false,
forceStackedStyle: Bool = false,
monospacedDigits: Bool = false)
monospacedDigits: Bool = false,
showWindowLabels: Bool = true)
{
self.size = size
self.monospacedDigits = monospacedDigits
self.showWindowLabels = showWindowLabels
self.colorPace = colorPace
self.colorByProvider = colorByProvider
self.highContrast = highContrast
Expand All @@ -199,6 +202,7 @@ struct MenuBarLayoutRenderKey: Hashable {
let data: MenuBarLayoutRenderData
let size: MenuBarLayoutSize
let monospacedDigits: Bool
let showWindowLabels: Bool
let colorPace: Bool
let colorByProvider: Bool
let highContrast: Bool
Expand Down Expand Up @@ -340,6 +344,7 @@ final class MenuBarLayoutRenderer {
data: data,
size: options.size,
monospacedDigits: options.monospacedDigits,
showWindowLabels: options.showWindowLabels,
colorPace: options.colorPace,
colorByProvider: options.colorByProvider,
highContrast: options.highContrast,
Expand Down Expand Up @@ -853,7 +858,7 @@ final class MenuBarLayoutRenderer {
automaticText: data.automaticText,
estimated: data.quotaIsEstimated,
showUsed: options.showUsed)
let prefix = self.windowPrefix(window, data: data)
let prefix = self.windowPrefix(window, data: data, showWindowLabels: options.showWindowLabels)
let accessibilityPrefix = Self.windowAccessibilityLabel(window, data: data)
let display = prefix.isEmpty ? resolvedValue.text : "\(prefix) \(resolvedValue.text)"
let accessibility = resolvedValue.isAvailable
Expand All @@ -862,20 +867,25 @@ final class MenuBarLayoutRenderer {
return self.textToken(display, accessibilityText: accessibility, attributes: style.attributes)
}

private static func windowPrefix(_ window: PercentWindow, data: MenuBarLayoutRenderData) -> String {
private static func windowPrefix(
_ window: PercentWindow,
data: MenuBarLayoutRenderData,
showWindowLabels: Bool = true) -> String
{
let rateWindow = self.window(window, data: data)
let prefix: String
let taggedPrefix = Self.taggedSemanticLane(window, provider: data.provider)
.flatMap { Self.laneShortLabel($0, provider: data.provider) }
// Named lanes identify distinct allowances; Spend's currency is not a window label.
if let taggedPrefix { return taggedPrefix }
guard showWindowLabels || window == .spend else { return "" }
switch window {
case .session:
prefix = taggedPrefix
?? self.primaryLabel(data: data).flatMap(\.first).map { String($0).uppercased() }
prefix = self.primaryLabel(data: data).flatMap(\.first).map { String($0).uppercased() }
?? Self.sessionPrefix(rateWindow)
case .weekly:
let secondaryLabel = Self.secondaryLabel(data: data)
prefix = taggedPrefix
?? secondaryLabel.flatMap(\.first).map { String($0).uppercased() } ?? "W"
prefix = secondaryLabel.flatMap(\.first).map { String($0).uppercased() } ?? "W"
case .scopedWeekly:
prefix = data.scopedWeeklyTitle.map { String($0.prefix(1)).uppercased() } ?? "F"
case .automatic:
Expand Down
13 changes: 8 additions & 5 deletions Sources/CodexBar/PreferencesMenuPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,21 @@ struct AgentSessionsSettingsSection: View {
})
.disabled(!self.settings.agentSessionsEnabled)

Toggle(isOn: self.$settings.agentSessionsRemoteEnabled) {
SettingsRowLabel(L("agent_sessions_remote_title"), subtitle: L("agent_sessions_remote_subtitle"))
}
.disabled(!self.settings.agentSessionsEnabled)

Toggle(isOn: self.$settings.agentSessionsHideUnreachableHosts) {
SettingsRowLabel(
L("agent_sessions_hide_unreachable_title"),
subtitle: L("agent_sessions_hide_unreachable_subtitle"))
}
.disabled(!self.settings.agentSessionsEnabled)
.disabled(!self.settings.agentSessionsEnabled || !self.settings.agentSessionsRemoteEnabled)

AgentSessionHostsEditor(settings: self.settings)
} header: {
Text(L("section_agent_sessions"))
} footer: {
SettingsSectionFooter(L("agent_sessions_footer"))
}
}
}
Expand All @@ -185,8 +188,8 @@ struct AgentSessionHostsEditor: View {
.frame(minWidth: 220, idealWidth: 280)
.accessibilityLabel(L("agent_sessions_hosts_title"))
}
.disabled(!self.settings.agentSessionsEnabled)
.help(L("agent_sessions_footer"))
.disabled(!self.settings.agentSessionsEnabled || !self.settings.agentSessionsRemoteEnabled)
.help(L("agent_sessions_remote_subtitle"))
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/ProviderIcon-typesafe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ar.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,8 @@
"overview_rows_follow_order" = "الصفوف العامة دائما تتبع ترتيب مقدم الخدمة.";
"overview_no_providers_selected" = "لم يتم اختيار أي مقدمي خدمة";
"agent_sessions_title" = "جلسات الوكلاء";
"agent_sessions_remote_title" = "الجلسات البعيدة (Tailscale / SSH)";
"agent_sessions_remote_subtitle" = "اكتشف الأجهزة البعيدة واتصل بها. عطّل الخيار لمراقبة هذا الـ Mac فقط.";
"agent_sessions_subtitle" = "إظهار جلسات Codex وClaude Code المحلية والمكتشفة عبر SSH في القائمة.";
"agent_sessions_hosts_title" = "مضيفو SSH إضافيون";
"agent_sessions_footer" = "يتم اكتشاف أجهزة Mac على شبكة tailnet تلقائيًا. يتم تحديث الجلسات المحلية كل 30 ثانية؛ والمضيفون البعيدون كل 60 ثانية وعند فتح القائمة.";
Expand Down Expand Up @@ -1444,6 +1446,7 @@
"menu_bar_layout_size_regular" = "عادي";
"menu_bar_layout_size_large" = "كبير";
"menu_bar_layout_monospaced_digits" = "أرقام متساوية العرض";
"menu_bar_layout_show_window_labels" = "إظهار تسميات الفترات";
"menu_bar_layout_gap" = "المسافة";
"menu_bar_layout_gap_tight" = "ضيّق";
"menu_bar_layout_gap_regular" = "عادي";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,8 @@
"overview_rows_follow_order" = "Les files del Resum sempre segueixen l'ordre dels proveïdors.";
"overview_no_providers_selected" = "No hi ha cap proveïdor seleccionat";
"agent_sessions_title" = "Sessions d'agents";
"agent_sessions_remote_title" = "Sessions remotes (Tailscale / SSH)";
"agent_sessions_remote_subtitle" = "Descobreix servidors remots i connecta-t’hi. Desactiva-ho per supervisar només aquest Mac.";
"agent_sessions_subtitle" = "Mostra al menú les sessions locals i descobertes per SSH de Codex i Claude Code.";
"agent_sessions_hosts_title" = "Amfitrions SSH addicionals";
"agent_sessions_footer" = "Els Mac de la vostra tailnet es descobreixen automàticament. Les sessions locals s'actualitzen cada 30 segons; els amfitrions remots, cada 60 segons i quan s'obre el menú.";
Expand Down Expand Up @@ -1447,6 +1449,7 @@
"menu_bar_layout_size_regular" = "Normal";
"menu_bar_layout_size_large" = "Gran";
"menu_bar_layout_monospaced_digits" = "Dígits monoespaiats";
"menu_bar_layout_show_window_labels" = "Mostra les etiquetes dels períodes";
"menu_bar_layout_gap" = "Espaiat";
"menu_bar_layout_gap_tight" = "Estret";
"menu_bar_layout_gap_regular" = "Normal";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/de.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,8 @@
"overview_rows_follow_order" = "Übersichtszeilen folgen immer der Anbieterreihenfolge.";
"overview_no_providers_selected" = "Keine Anbieter ausgewählt";
"agent_sessions_title" = "Agenten-Sitzungen";
"agent_sessions_remote_title" = "Entfernte Sitzungen (Tailscale / SSH)";
"agent_sessions_remote_subtitle" = "Entfernte Hosts erkennen und verbinden. Deaktivieren, um nur diesen Mac zu überwachen.";
"agent_sessions_subtitle" = "Lokale und über SSH erkannte Codex- und Claude-Code-Sitzungen im Menü anzeigen.";
"agent_sessions_hosts_title" = "Zusätzliche SSH-Hosts";
"agent_sessions_footer" = "Macs in deinem Tailnet werden automatisch erkannt. Lokale Sitzungen werden alle 30 Sekunden aktualisiert; Remote-Hosts alle 60 Sekunden und beim Öffnen des Menüs.";
Expand Down Expand Up @@ -1443,6 +1445,7 @@
"menu_bar_layout_size_regular" = "Normal";
"menu_bar_layout_size_large" = "Groß";
"menu_bar_layout_monospaced_digits" = "Ziffern mit gleicher Breite";
"menu_bar_layout_show_window_labels" = "Zeitfensterbeschriftungen anzeigen";
"menu_bar_layout_gap" = "Abstand";
"menu_bar_layout_gap_tight" = "Eng";
"menu_bar_layout_gap_regular" = "Normal";
Expand Down
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,8 @@
"overview_rows_follow_order" = "Overview rows always follow provider order.";
"overview_no_providers_selected" = "No providers selected";
"agent_sessions_title" = "Agent sessions";
"agent_sessions_remote_title" = "Remote sessions (Tailscale / SSH)";
"agent_sessions_remote_subtitle" = "Discover and connect to remote hosts. Turn off to monitor only this Mac.";
"agent_sessions_subtitle" = "Show local and SSH-discovered Codex and Claude Code sessions in the menu.";
"agent_sessions_hosts_title" = "Additional SSH hosts";
"agent_sessions_hide_unreachable_title" = "Hide unreachable hosts";
Expand Down Expand Up @@ -1452,6 +1454,7 @@
"menu_bar_layout_size_regular" = "Regular";
"menu_bar_layout_size_large" = "Large";
"menu_bar_layout_monospaced_digits" = "Monospaced digits";
"menu_bar_layout_show_window_labels" = "Show window labels";
"menu_bar_layout_gap" = "Gap";
"menu_bar_layout_gap_tight" = "Tight";
"menu_bar_layout_gap_regular" = "Regular";
Expand Down
Loading
Loading