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
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum ProviderImplementationRegistry {
case .synthetic: SyntheticProviderImplementation()
case .openrouter: OpenRouterProviderImplementation()
case .warp: WarpProviderImplementation()
case .windsurf: WindsurfProviderImplementation()
case .perplexity: PerplexityProviderImplementation()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import CodexBarCore
import CodexBarMacroSupport
import Foundation
import SwiftUI

@ProviderImplementationRegistration
struct WindsurfProviderImplementation: ProviderImplementation {
let id: UsageProvider = .windsurf

@MainActor
func observeSettings(_ settings: SettingsStore) {
_ = settings.windsurfUsageDataSource
_ = settings.windsurfCookieSource
_ = settings.windsurfCookieHeader
}

@MainActor
func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {
.windsurf(context.settings.windsurfSettingsSnapshot(tokenOverride: context.tokenOverride))
}

@MainActor
func sourceMode(context: ProviderSourceModeContext) -> ProviderSourceMode {
switch context.settings.windsurfUsageDataSource {
case .auto: .auto
case .web: .web
case .cli: .cli
}
}

@MainActor
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
// Usage source picker
let usageBinding = Binding(
get: { context.settings.windsurfUsageDataSource.rawValue },
set: { raw in
context.settings.windsurfUsageDataSource = WindsurfUsageDataSource(rawValue: raw) ?? .auto
})
let usageOptions = WindsurfUsageDataSource.allCases.map {
ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)
}

// Cookie source picker
let cookieBinding = Binding(
get: { context.settings.windsurfCookieSource.rawValue },
set: { raw in
context.settings.windsurfCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto
})
let cookieOptions = ProviderCookieSourceUI.options(
allowsOff: true,
keychainDisabled: context.settings.debugDisableKeychainAccess)

let cookieSubtitle: () -> String? = {
ProviderCookieSourceUI.subtitle(
source: context.settings.windsurfCookieSource,
keychainDisabled: context.settings.debugDisableKeychainAccess,
auto: "Automatic imports Firebase tokens from browser IndexedDB.",
manual: "Paste a Firebase access token for windsurf.com.",
off: "Windsurf web API access is disabled.")
}

return [
ProviderSettingsPickerDescriptor(
id: "windsurf-usage-source",
title: "Usage source",
subtitle: "Auto falls back to the next source if the preferred one fails.",
binding: usageBinding,
options: usageOptions,
isVisible: nil,
onChange: nil,
trailingText: {
guard context.settings.windsurfUsageDataSource == .auto else { return nil }
let label = context.store.sourceLabel(for: .windsurf)
return label == "auto" ? nil : label
}),
ProviderSettingsPickerDescriptor(
id: "windsurf-cookie-source",
title: "Cookie source",
subtitle: "Automatic imports Firebase tokens from browser IndexedDB.",
dynamicSubtitle: cookieSubtitle,
binding: cookieBinding,
options: cookieOptions,
isVisible: nil,
onChange: nil,
trailingText: {
guard !context.settings.debugDisableKeychainAccess else { return nil }
guard let entry = CookieHeaderCache.load(provider: .windsurf) else { return nil }
let when = entry.storedAt.relativeDescription()
return "Cached: \(entry.sourceLabel) • \(when)"
}),
]
}

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "windsurf-cookie-header",
title: "",
subtitle: "",
kind: .secure,
placeholder: "Firebase refresh token (AMf-vB…) or access token (eyJ…)",
binding: context.stringBinding(\.windsurfCookieHeader),
actions: [],
isVisible: {
context.settings.windsurfCookieSource == .manual
},
onActivate: nil),
]
}
}
93 changes: 93 additions & 0 deletions Sources/CodexBar/Providers/Windsurf/WindsurfSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import CodexBarCore
import Foundation

extension SettingsStore {
var windsurfUsageDataSource: WindsurfUsageDataSource {
get {
let source = self.configSnapshot.providerConfig(for: .windsurf)?.source
return Self.windsurfUsageDataSource(from: source)
}
set {
let source: ProviderSourceMode? = switch newValue {
case .auto: .auto
case .web: .web
case .cli: .cli
}
self.updateProviderConfig(provider: .windsurf) { entry in
entry.source = source
}
self.logProviderModeChange(provider: .windsurf, field: "usageSource", value: newValue.rawValue)
}
}

var windsurfCookieSource: ProviderCookieSource {
get { self.resolvedCookieSource(provider: .windsurf, fallback: .auto) }
set {
self.updateProviderConfig(provider: .windsurf) { entry in
entry.cookieSource = newValue
}
self.logProviderModeChange(provider: .windsurf, field: "cookieSource", value: newValue.rawValue)
}
}

var windsurfCookieHeader: String {
get { self.configSnapshot.providerConfig(for: .windsurf)?.sanitizedCookieHeader ?? "" }
set {
self.updateProviderConfig(provider: .windsurf) { entry in
entry.cookieHeader = self.normalizedConfigValue(newValue)
}
self.logSecretUpdate(provider: .windsurf, field: "cookieHeader", value: newValue)
}
}
}

extension SettingsStore {
func windsurfSettingsSnapshot(
tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.WindsurfProviderSettings
{
ProviderSettingsSnapshot.WindsurfProviderSettings(
usageDataSource: self.windsurfUsageDataSource,
cookieSource: self.windsurfSnapshotCookieSource(tokenOverride: tokenOverride),
manualCookieHeader: self.windsurfSnapshotCookieHeader(tokenOverride: tokenOverride))
}

private static func windsurfUsageDataSource(from source: ProviderSourceMode?) -> WindsurfUsageDataSource {
guard let source else { return .auto }
switch source {
case .auto, .oauth, .api:
return .auto
case .web:
return .web
case .cli:
return .cli
}
}

private func windsurfSnapshotCookieHeader(tokenOverride: TokenAccountOverride?) -> String {
let fallback = self.windsurfCookieHeader
guard let support = TokenAccountSupportCatalog.support(for: .windsurf),
case .cookieHeader = support.injection
else {
return fallback
}
guard let account = ProviderTokenAccountSelection.selectedAccount(
provider: .windsurf,
settings: self,
override: tokenOverride)
else {
return fallback
}
return TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)
}

private func windsurfSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {
let fallback = self.windsurfCookieSource
guard let support = TokenAccountSupportCatalog.support(for: .windsurf),
support.requiresManualCookieSource
else {
return fallback
}
if self.tokenAccounts(for: .windsurf).isEmpty { return fallback }
return .manual
}
}
3 changes: 3 additions & 0 deletions Sources/CodexBar/Resources/ProviderIcon-windsurf.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: 1 addition & 1 deletion Sources/CodexBar/UsageStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@ extension UsageStore {
let source = resolution?.source.rawValue ?? "none"
return "WARP_API_KEY=\(hasAny ? "present" : "missing") source=\(source)"
case .gemini, .antigravity, .opencode, .factory, .copilot, .vertexai, .kilo, .kiro, .kimi,
.kimik2, .jetbrains, .perplexity:
.kimik2, .jetbrains, .windsurf, .perplexity:
return unimplementedDebugLogMessages[provider] ?? "Debug log not yet implemented"
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/CodexBarCLI/TokenAccountCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ struct TokenAccountCLIContext {
perplexity: ProviderSettingsSnapshot.PerplexityProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .gemini, .antigravity, .copilot, .kiro, .vertexai, .kimik2, .synthetic, .openrouter, .warp:
case .gemini, .antigravity, .copilot, .kiro, .vertexai, .kimik2, .synthetic, .openrouter, .warp,
.windsurf:
return nil
}
}
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBarCore/Providers/ProviderDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public enum ProviderDescriptorRegistry {
.synthetic: SyntheticProviderDescriptor.descriptor,
.openrouter: OpenRouterProviderDescriptor.descriptor,
.warp: WarpProviderDescriptor.descriptor,
.windsurf: WindsurfProviderDescriptor.descriptor,
.perplexity: PerplexityProviderDescriptor.descriptor,
]
private static let bootstrap: Void = {
Expand Down
25 changes: 25 additions & 0 deletions Sources/CodexBarCore/Providers/ProviderSettingsSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public struct ProviderSettingsSnapshot: Sendable {
amp: AmpProviderSettings? = nil,
ollama: OllamaProviderSettings? = nil,
jetbrains: JetBrainsProviderSettings? = nil,
windsurf: WindsurfProviderSettings? = nil,
perplexity: PerplexityProviderSettings? = nil) -> ProviderSettingsSnapshot
{
ProviderSettingsSnapshot(
Expand All @@ -39,6 +40,7 @@ public struct ProviderSettingsSnapshot: Sendable {
amp: amp,
ollama: ollama,
jetbrains: jetbrains,
windsurf: windsurf,
perplexity: perplexity)
}

Expand Down Expand Up @@ -211,6 +213,22 @@ public struct ProviderSettingsSnapshot: Sendable {
}
}

public struct WindsurfProviderSettings: Sendable {
public let usageDataSource: WindsurfUsageDataSource
public let cookieSource: ProviderCookieSource
public let manualCookieHeader: String?

public init(
usageDataSource: WindsurfUsageDataSource,
cookieSource: ProviderCookieSource,
manualCookieHeader: String?)
{
self.usageDataSource = usageDataSource
self.cookieSource = cookieSource
self.manualCookieHeader = manualCookieHeader
}
}

public struct PerplexityProviderSettings: Sendable {
public let cookieSource: ProviderCookieSource
public let manualCookieHeader: String?
Expand Down Expand Up @@ -238,6 +256,7 @@ public struct ProviderSettingsSnapshot: Sendable {
public let amp: AmpProviderSettings?
public let ollama: OllamaProviderSettings?
public let jetbrains: JetBrainsProviderSettings?
public let windsurf: WindsurfProviderSettings?
public let perplexity: PerplexityProviderSettings?

public var jetbrainsIDEBasePath: String? {
Expand All @@ -262,6 +281,7 @@ public struct ProviderSettingsSnapshot: Sendable {
amp: AmpProviderSettings?,
ollama: OllamaProviderSettings?,
jetbrains: JetBrainsProviderSettings? = nil,
windsurf: WindsurfProviderSettings? = nil,
perplexity: PerplexityProviderSettings? = nil)
{
self.debugMenuEnabled = debugMenuEnabled
Expand All @@ -281,6 +301,7 @@ public struct ProviderSettingsSnapshot: Sendable {
self.amp = amp
self.ollama = ollama
self.jetbrains = jetbrains
self.windsurf = windsurf
self.perplexity = perplexity
}
}
Expand All @@ -301,6 +322,7 @@ public enum ProviderSettingsSnapshotContribution: Sendable {
case amp(ProviderSettingsSnapshot.AmpProviderSettings)
case ollama(ProviderSettingsSnapshot.OllamaProviderSettings)
case jetbrains(ProviderSettingsSnapshot.JetBrainsProviderSettings)
case windsurf(ProviderSettingsSnapshot.WindsurfProviderSettings)
case perplexity(ProviderSettingsSnapshot.PerplexityProviderSettings)
}

Expand All @@ -322,6 +344,7 @@ public struct ProviderSettingsSnapshotBuilder: Sendable {
public var amp: ProviderSettingsSnapshot.AmpProviderSettings?
public var ollama: ProviderSettingsSnapshot.OllamaProviderSettings?
public var jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings?
public var windsurf: ProviderSettingsSnapshot.WindsurfProviderSettings?
public var perplexity: ProviderSettingsSnapshot.PerplexityProviderSettings?

public init(debugMenuEnabled: Bool = false, debugKeepCLISessionsAlive: Bool = false) {
Expand All @@ -346,6 +369,7 @@ public struct ProviderSettingsSnapshotBuilder: Sendable {
case let .amp(value): self.amp = value
case let .ollama(value): self.ollama = value
case let .jetbrains(value): self.jetbrains = value
case let .windsurf(value): self.windsurf = value
case let .perplexity(value): self.perplexity = value
}
}
Expand All @@ -369,6 +393,7 @@ public struct ProviderSettingsSnapshotBuilder: Sendable {
amp: self.amp,
ollama: self.ollama,
jetbrains: self.jetbrains,
windsurf: self.windsurf,
perplexity: self.perplexity)
}
}
2 changes: 2 additions & 0 deletions Sources/CodexBarCore/Providers/Providers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum UsageProvider: String, CaseIterable, Sendable, Codable {
case synthetic
case warp
case openrouter
case windsurf
case perplexity
}

Expand Down Expand Up @@ -55,6 +56,7 @@ public enum IconStyle: Sendable, CaseIterable {
case synthetic
case warp
case openrouter
case windsurf
case perplexity
case combined
}
Expand Down
Loading