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
11 changes: 10 additions & 1 deletion Sources/CodexBar/CodexbarApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ struct CodexBarApp: App {
store: self.store,
updater: self.appDelegate.updaterController,
selection: self.preferencesSelection,
managedCodexAccountCoordinator: self.managedCodexAccountCoordinator)
managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,
runProviderLoginFlow: { provider in
await self.appDelegate.runProviderLoginFlow(provider)
})
}
.defaultSize(width: PreferencesTab.general.preferredWidth, height: PreferencesTab.general.preferredHeight)
.windowResizability(.contentSize)
Expand Down Expand Up @@ -300,6 +303,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
TTYCommandRunner.terminateActiveProcessesForAppShutdown()
}

func runProviderLoginFlow(_ provider: UsageProvider) async {
self.ensureStatusController()
guard let statusController else { return }
await statusController.runLoginFlowFromSettings(provider: provider)
}

/// Use the classic (non-Liquid Glass) app icon on macOS versions before 26.
private func configureAppIconForMacOSVersion() {
if #unavailable(macOS 26) {
Expand Down
7 changes: 7 additions & 0 deletions Sources/CodexBar/PreferencesProviderDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
let settingsPickers: [ProviderSettingsPickerDescriptor]
let settingsToggles: [ProviderSettingsToggleDescriptor]
let settingsFields: [ProviderSettingsFieldDescriptor]
let settingsActions: [ProviderSettingsActionsDescriptor]
let settingsTokenAccounts: ProviderSettingsTokenAccountsDescriptor?
let errorDisplay: ProviderErrorDisplay?
@Binding var isErrorExpanded: Bool
Expand All @@ -28,6 +29,7 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
settingsPickers: [ProviderSettingsPickerDescriptor],
settingsToggles: [ProviderSettingsToggleDescriptor],
settingsFields: [ProviderSettingsFieldDescriptor],
settingsActions: [ProviderSettingsActionsDescriptor] = [],
settingsTokenAccounts: ProviderSettingsTokenAccountsDescriptor?,
errorDisplay: ProviderErrorDisplay?,
isErrorExpanded: Binding<Bool>,
Expand All @@ -44,6 +46,7 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
self.settingsPickers = settingsPickers
self.settingsToggles = settingsToggles
self.settingsFields = settingsFields
self.settingsActions = settingsActions
self.settingsTokenAccounts = settingsTokenAccounts
self.errorDisplay = errorDisplay
self._isErrorExpanded = isErrorExpanded
Expand Down Expand Up @@ -118,6 +121,9 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
ForEach(self.settingsFields) { field in
ProviderSettingsFieldRowView(field: field)
}
ForEach(self.settingsActions) { descriptor in
ProviderSettingsActionsRowView(descriptor: descriptor)
}
}
}

Expand All @@ -143,6 +149,7 @@ struct ProviderDetailView<SupplementaryContent: View>: View {
private var hasSettings: Bool {
!self.settingsPickers.isEmpty ||
!self.settingsFields.isEmpty ||
!self.settingsActions.isEmpty ||
self.settingsTokenAccounts != nil
}

Expand Down
34 changes: 34 additions & 0 deletions Sources/CodexBar/PreferencesProviderSettingsRows.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,40 @@ struct ProviderSettingsFieldRowView: View {
}
}

@MainActor
struct ProviderSettingsActionsRowView: View {
let descriptor: ProviderSettingsActionsDescriptor

var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(self.descriptor.title)
.font(.subheadline.weight(.semibold))

if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
Text(self.descriptor.subtitle)
.font(.footnote)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}

let actions = self.descriptor.actions.filter { $0.isVisible?() ?? true }
if !actions.isEmpty {
HStack(spacing: 10) {
ForEach(actions) { action in
Button(action.title) {
Task { @MainActor in
await action.perform()
}
}
.applyProviderSettingsButtonStyle(action.style)
.controlSize(.small)
}
}
}
}
}
}

@MainActor
struct ProviderSettingsTokenAccountsRowView: View {
let descriptor: ProviderSettingsTokenAccountsDescriptor
Expand Down
16 changes: 15 additions & 1 deletion Sources/CodexBar/PreferencesProvidersPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct ProvidersPane: View {
@Bindable var store: UsageStore
let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator
let codexAmbientLoginRunner: any CodexAmbientLoginRunning
let runProviderLoginFlow: @MainActor (UsageProvider) async -> Void
@State private var expandedErrors: Set<UsageProvider> = []
@State private var settingsStatusTextByID: [String: String] = [:]
@State private var settingsLastAppActiveRunAtByID: [String: Date] = [:]
Expand All @@ -24,12 +25,14 @@ struct ProvidersPane: View {
settings: SettingsStore,
store: UsageStore,
managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(),
codexAmbientLoginRunner: any CodexAmbientLoginRunning = DefaultCodexAmbientLoginRunner())
codexAmbientLoginRunner: any CodexAmbientLoginRunning = DefaultCodexAmbientLoginRunner(),
runProviderLoginFlow: @escaping @MainActor (UsageProvider) async -> Void = { _ in })
{
self.settings = settings
self.store = store
self.managedCodexAccountCoordinator = managedCodexAccountCoordinator
self.codexAmbientLoginRunner = codexAmbientLoginRunner
self.runProviderLoginFlow = runProviderLoginFlow
}

var body: some View {
Expand All @@ -54,6 +57,7 @@ struct ProvidersPane: View {
settingsPickers: self.extraSettingsPickers(for: provider),
settingsToggles: self.extraSettingsToggles(for: provider),
settingsFields: self.extraSettingsFields(for: provider),
settingsActions: self.extraSettingsActions(for: provider),
settingsTokenAccounts: self.tokenAccountDescriptor(for: provider),
errorDisplay: self.providerErrorDisplay(provider),
isErrorExpanded: self.expandedBinding(for: provider),
Expand Down Expand Up @@ -309,6 +313,13 @@ struct ProvidersPane: View {
.filter { $0.isVisible?() ?? true }
}

private func extraSettingsActions(for provider: UsageProvider) -> [ProviderSettingsActionsDescriptor] {
guard let impl = ProviderCatalog.implementation(for: provider) else { return [] }
let context = self.makeSettingsContext(provider: provider)
return impl.settingsActions(context: context)
.filter { $0.isVisible?() ?? true }
}

func tokenAccountDescriptor(for provider: UsageProvider) -> ProviderSettingsTokenAccountsDescriptor? {
guard let support = TokenAccountSupportCatalog.support(for: provider) else { return nil }
let context = self.makeSettingsContext(provider: provider)
Expand Down Expand Up @@ -403,6 +414,9 @@ struct ProvidersPane: View {
},
requestConfirmation: { confirmation in
self.activeConfirmation = ProviderSettingsConfirmationState(confirmation: confirmation)
},
runLoginFlow: {
await self.runProviderLoginFlow(provider)
})
}

Expand Down
9 changes: 7 additions & 2 deletions Sources/CodexBar/PreferencesView.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import AppKit
import CodexBarCore
import SwiftUI

enum PreferencesTab: String, Hashable {
Expand Down Expand Up @@ -29,6 +30,7 @@ struct PreferencesView: View {
let updater: UpdaterProviding
@Bindable var selection: PreferencesSelection
let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator
let runProviderLoginFlow: @MainActor (UsageProvider) async -> Void
@State private var contentWidth: CGFloat = PreferencesTab.general.preferredWidth
@State private var contentHeight: CGFloat = PreferencesTab.general.preferredHeight

Expand All @@ -37,13 +39,15 @@ struct PreferencesView: View {
store: UsageStore,
updater: UpdaterProviding,
selection: PreferencesSelection,
managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator())
managedCodexAccountCoordinator: ManagedCodexAccountCoordinator = ManagedCodexAccountCoordinator(),
runProviderLoginFlow: @escaping @MainActor (UsageProvider) async -> Void = { _ in })
{
self.settings = settings
self.store = store
self.updater = updater
self.selection = selection
self.managedCodexAccountCoordinator = managedCodexAccountCoordinator
self.runProviderLoginFlow = runProviderLoginFlow
}

var body: some View {
Expand All @@ -55,7 +59,8 @@ struct PreferencesView: View {
ProvidersPane(
settings: self.settings,
store: self.store,
managedCodexAccountCoordinator: self.managedCodexAccountCoordinator)
managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,
runProviderLoginFlow: self.runProviderLoginFlow)
.tabItem { Label("Providers", systemImage: "square.grid.2x2") }
.tag(PreferencesTab.providers)

Expand Down
25 changes: 22 additions & 3 deletions Sources/CodexBar/Providers/Antigravity/AntigravityLoginFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,28 @@ import CodexBarCore
@MainActor
extension StatusItemController {
func runAntigravityLoginFlow() async {
let store = self.store
let phaseHandler: @Sendable (AntigravityLoginRunner.Phase) -> Void = { [weak self] phase in
Task { @MainActor in
switch phase {
case .waitingBrowser:
self?.loginPhase = .waitingBrowser
}
}
}
let result = await AntigravityLoginRunner.run(onPhaseChange: phaseHandler) {
Task { @MainActor in
await store.refresh()
CodexBarLog.logger(LogCategories.login).info("Auto-refreshed after Antigravity auth")
}
}
guard !Task.isCancelled else { return }
self.loginPhase = .idle
self.presentLoginAlert(
title: "Antigravity login is managed in the app",
message: "Open Antigravity to sign in, then refresh CodexBar.")
self.presentAntigravityLoginResult(result)
let outcome = self.describe(result.outcome)
self.loginLogger.info("Antigravity login", metadata: ["outcome": outcome])
if case .success = result.outcome {
self.postLoginNotification(for: .antigravity)
}
}
}
Loading