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
4 changes: 4 additions & 0 deletions Sources/CodexBar/Config/CodexBarConfigMigrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct CodexBarConfigMigrator {
let kimiK2TokenStore: any KimiK2TokenStoring
let augmentCookieStore: any CookieHeaderStoring
let ampCookieStore: any CookieHeaderStoring
let traeCookieStore: any CookieHeaderStoring
let copilotTokenStore: any CopilotTokenStoring
let tokenAccountStore: any ProviderTokenAccountStoring
}
Expand Down Expand Up @@ -99,6 +100,7 @@ struct CodexBarConfigMigrator {
(.factory, stores.factoryCookieStore.loadCookieHeader),
(.augment, stores.augmentCookieStore.loadCookieHeader),
(.amp, stores.ampCookieStore.loadCookieHeader),
(.trae, stores.traeCookieStore.loadCookieHeader),
],
config: &config,
state: &state)
Expand All @@ -123,6 +125,7 @@ struct CodexBarConfigMigrator {
(.kimi, "kimiCookieSource"),
(.augment, "augmentCookieSource"),
(.amp, "ampCookieSource"),
(.trae, "traeCookieSource"),
]

for (provider, key) in sources {
Expand Down Expand Up @@ -294,6 +297,7 @@ struct CodexBarConfigMigrator {
try stores.minimaxCookieStore.storeCookieHeader(nil)
try stores.augmentCookieStore.storeCookieHeader(nil)
try stores.ampCookieStore.storeCookieHeader(nil)
try stores.traeCookieStore.storeCookieHeader(nil)
} catch {
log.error("Failed to clear legacy secrets: \(error)")
}
Expand Down
5 changes: 5 additions & 0 deletions Sources/CodexBar/KeychainPromptCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ enum KeychainPromptCoordinator {
"CodexBar will ask macOS Keychain for your Amp cookie header",
"so it can fetch usage. Click OK to continue.",
].joined(separator: " "))
case .traeCookie:
return (title, [
"CodexBar will ask macOS Keychain for your Trae JWT token",
"so it can fetch usage. Click OK to continue.",
].joined(separator: " "))
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/CodexBar/PreferencesProvidersPane+Testing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ enum ProvidersPaneTestHarness {
settings.factoryCookieSource = .manual
settings.minimaxCookieSource = .manual
settings.augmentCookieSource = .manual
settings.traeCookieSource = .manual
}

private static func exercisePaneBasics(pane: ProvidersPane) {
Expand All @@ -68,6 +69,7 @@ enum ProvidersPaneTestHarness {
_ = pane._test_providerSubtitle(.minimax)
_ = pane._test_providerSubtitle(.kimi)
_ = pane._test_providerSubtitle(.gemini)
_ = pane._test_providerSubtitle(.trae)

_ = pane._test_menuBarMetricPicker(for: .codex)
_ = pane._test_menuBarMetricPicker(for: .gemini)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum ProviderImplementationRegistry {
case .jetbrains: JetBrainsProviderImplementation()
case .kimik2: KimiK2ProviderImplementation()
case .amp: AmpProviderImplementation()
case .trae: TraeProviderImplementation()
case .synthetic: SyntheticProviderImplementation()
}
}
Expand Down
51 changes: 51 additions & 0 deletions Sources/CodexBar/Providers/Trae/TraeProviderImplementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import AppKit
import CodexBarCore
import CodexBarMacroSupport
import Foundation
import SwiftUI

@ProviderImplementationRegistration
struct TraeProviderImplementation: ProviderImplementation {
let id: UsageProvider = .trae

@MainActor
func observeSettings(_ settings: SettingsStore) {
_ = settings.traeCookieHeader
_ = settings.tokenAccounts(for: .trae)
}

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

// No settingsPickers - Trae only supports manual JWT entry
// The JWT is not stored in browser cookies, only in HTTP headers

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "trae-jwt-token",
title: "JWT Token",
subtitle: "Paste Authorization header from browser DevTools. Instructions:\n1. Open trae.ai and log in\n2. Open DevTools → Network tab\n3. Find request to 'user_current_entitlement_list'\n4. Copy 'Authorization' header (starts with 'Cloud-IDE-JWT')\n5. Paste here",
kind: .secure,
placeholder: "Cloud-IDE-JWT eyJ...",
binding: context.stringBinding(\.traeCookieHeader),
actions: [
ProviderSettingsActionDescriptor(
id: "trae-open-settings",
title: "Open Trae Settings",
style: .link,
isVisible: nil,
perform: {
if let url = URL(string: "https://www.trae.ai/account-setting") {
NSWorkspace.shared.open(url)
}
}),
],
isVisible: { true },
onActivate: { context.settings.ensureTraeCookieLoaded() }),
]
}
}
62 changes: 62 additions & 0 deletions Sources/CodexBar/Providers/Trae/TraeSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import CodexBarCore
import Foundation

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

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

func ensureTraeCookieLoaded() {}
}

extension SettingsStore {
func traeSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.TraeProviderSettings {
ProviderSettingsSnapshot.TraeProviderSettings(
cookieSource: self.traeSnapshotCookieSource(tokenOverride: tokenOverride),
manualCookieHeader: self.traeSnapshotCookieHeader(tokenOverride: tokenOverride))
}

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

private func traeSnapshotCookieSource(tokenOverride: TokenAccountOverride?) -> ProviderCookieSource {
let fallback = self.traeCookieSource
guard let support = TokenAccountSupportCatalog.support(for: .trae),
support.requiresManualCookieSource
else {
return fallback
}
if self.tokenAccounts(for: .trae).isEmpty { return fallback }
return .manual
}
}
1 change: 1 addition & 0 deletions Sources/CodexBar/Resources/ProviderIcon-trae.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/CodexBar/SettingsStore+MenuObservation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extension SettingsStore {
_ = self.kimiCookieSource
_ = self.augmentCookieSource
_ = self.ampCookieSource
_ = self.traeCookieSource
_ = self.mergeIcons
_ = self.switcherShowsIcons
_ = self.zaiAPIToken
Expand All @@ -52,6 +53,7 @@ extension SettingsStore {
_ = self.kimiK2APIToken
_ = self.augmentCookieHeader
_ = self.ampCookieHeader
_ = self.traeCookieHeader
_ = self.copilotAPIToken
_ = self.tokenAccountsByProvider
_ = self.debugLoadingPattern
Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBar/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ final class SettingsStore {
ampCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(
account: "amp-cookie",
promptKind: .ampCookie),
traeCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore(
account: "trae-cookie",
promptKind: .traeCookie),
copilotTokenStore: any CopilotTokenStoring = KeychainCopilotTokenStore(),
tokenAccountStore: any ProviderTokenAccountStoring = FileTokenAccountStore())
{
Expand All @@ -124,6 +127,7 @@ final class SettingsStore {
kimiK2TokenStore: kimiK2TokenStore,
augmentCookieStore: augmentCookieStore,
ampCookieStore: ampCookieStore,
traeCookieStore: traeCookieStore,
copilotTokenStore: copilotTokenStore,
tokenAccountStore: tokenAccountStore)
let config = CodexBarConfigMigrator.loadOrMigrate(
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBar/UsageStore+Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ extension UsageStore {
"kimiCookieSource": self.settings.kimiCookieSource.rawValue,
"augmentCookieSource": self.settings.augmentCookieSource.rawValue,
"ampCookieSource": self.settings.ampCookieSource.rawValue,
"traeCookieSource": self.settings.traeCookieSource.rawValue,
"openAIWebAccess": self.settings.openAIWebAccessEnabled ? "1" : "0",
"claudeWebExtras": self.settings.claudeWebExtrasEnabled ? "1" : "0",
]
Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBar/UsageStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,10 @@ extension UsageStore {
ampCookieHeader: self.settings.ampCookieHeader)
await MainActor.run { self.probeLogs[.amp] = text }
return text
case .trae:
let text = await TraeUsageFetcher.latestDumps()
await MainActor.run { self.probeLogs[.trae] = text }
return text
case .jetbrains:
let text = "JetBrains AI debug log not yet implemented"
await MainActor.run { self.probeLogs[.jetbrains] = text }
Expand Down
7 changes: 7 additions & 0 deletions Sources/CodexBarCLI/TokenAccountCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ struct TokenAccountCLIContext {
return self.makeSnapshot(
jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings(
ideBasePath: nil))
case .trae:
return self.makeSnapshot(
trae: ProviderSettingsSnapshot.TraeProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .gemini, .antigravity, .copilot, .kiro, .vertexai, .kimik2, .synthetic:
return nil
}
Expand All @@ -163,6 +168,7 @@ struct TokenAccountCLIContext {
kimi: ProviderSettingsSnapshot.KimiProviderSettings? = nil,
augment: ProviderSettingsSnapshot.AugmentProviderSettings? = nil,
amp: ProviderSettingsSnapshot.AmpProviderSettings? = nil,
trae: ProviderSettingsSnapshot.TraeProviderSettings? = nil,
jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings? = nil) -> ProviderSettingsSnapshot
{
ProviderSettingsSnapshot.make(
Expand All @@ -176,6 +182,7 @@ struct TokenAccountCLIContext {
kimi: kimi,
augment: augment,
amp: amp,
trae: trae,
jetbrains: jetbrains)
}

Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBarCore/KeychainAccessPreflight.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public struct KeychainPromptContext: Sendable {
case minimaxToken
case augmentCookie
case ampCookie
case traeCookie
}

public let kind: Kind
Expand Down
1 change: 1 addition & 0 deletions Sources/CodexBarCore/Logging/LogCategories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public enum LogCategories {
public static let syntheticTokenStore = "synthetic-token-store"
public static let syntheticUsage = "synthetic-usage"
public static let terminal = "terminal"
public static let trae = "trae"
public static let tokenAccounts = "token-accounts"
public static let tokenCost = "token-cost"
public static let ttyRunner = "tty-runner"
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 @@ -70,6 +70,7 @@ public enum ProviderDescriptorRegistry {
.jetbrains: JetBrainsProviderDescriptor.descriptor,
.kimik2: KimiK2ProviderDescriptor.descriptor,
.amp: AmpProviderDescriptor.descriptor,
.trae: TraeProviderDescriptor.descriptor,
.synthetic: SyntheticProviderDescriptor.descriptor,
]
private static let bootstrap: Void = {
Expand Down
19 changes: 19 additions & 0 deletions Sources/CodexBarCore/Providers/ProviderSettingsSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public struct ProviderSettingsSnapshot: Sendable {
kimi: KimiProviderSettings? = nil,
augment: AugmentProviderSettings? = nil,
amp: AmpProviderSettings? = nil,
trae: TraeProviderSettings? = nil,
jetbrains: JetBrainsProviderSettings? = nil) -> ProviderSettingsSnapshot
{
ProviderSettingsSnapshot(
Expand All @@ -31,6 +32,7 @@ public struct ProviderSettingsSnapshot: Sendable {
kimi: kimi,
augment: augment,
amp: amp,
trae: trae,
jetbrains: jetbrains)
}

Expand Down Expand Up @@ -167,6 +169,16 @@ public struct ProviderSettingsSnapshot: Sendable {
}
}

public struct TraeProviderSettings: Sendable {
public let cookieSource: ProviderCookieSource
public let manualCookieHeader: String?

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

public let debugMenuEnabled: Bool
public let debugKeepCLISessionsAlive: Bool
public let codex: CodexProviderSettings?
Expand All @@ -180,6 +192,7 @@ public struct ProviderSettingsSnapshot: Sendable {
public let kimi: KimiProviderSettings?
public let augment: AugmentProviderSettings?
public let amp: AmpProviderSettings?
public let trae: TraeProviderSettings?
public let jetbrains: JetBrainsProviderSettings?

public var jetbrainsIDEBasePath: String? {
Expand All @@ -200,6 +213,7 @@ public struct ProviderSettingsSnapshot: Sendable {
kimi: KimiProviderSettings?,
augment: AugmentProviderSettings?,
amp: AmpProviderSettings?,
trae: TraeProviderSettings?,
jetbrains: JetBrainsProviderSettings? = nil)
{
self.debugMenuEnabled = debugMenuEnabled
Expand All @@ -215,6 +229,7 @@ public struct ProviderSettingsSnapshot: Sendable {
self.kimi = kimi
self.augment = augment
self.amp = amp
self.trae = trae
self.jetbrains = jetbrains
}
}
Expand All @@ -231,6 +246,7 @@ public enum ProviderSettingsSnapshotContribution: Sendable {
case kimi(ProviderSettingsSnapshot.KimiProviderSettings)
case augment(ProviderSettingsSnapshot.AugmentProviderSettings)
case amp(ProviderSettingsSnapshot.AmpProviderSettings)
case trae(ProviderSettingsSnapshot.TraeProviderSettings)
case jetbrains(ProviderSettingsSnapshot.JetBrainsProviderSettings)
}

Expand All @@ -248,6 +264,7 @@ public struct ProviderSettingsSnapshotBuilder: Sendable {
public var kimi: ProviderSettingsSnapshot.KimiProviderSettings?
public var augment: ProviderSettingsSnapshot.AugmentProviderSettings?
public var amp: ProviderSettingsSnapshot.AmpProviderSettings?
public var trae: ProviderSettingsSnapshot.TraeProviderSettings?
public var jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings?

public init(debugMenuEnabled: Bool = false, debugKeepCLISessionsAlive: Bool = false) {
Expand All @@ -268,6 +285,7 @@ public struct ProviderSettingsSnapshotBuilder: Sendable {
case let .kimi(value): self.kimi = value
case let .augment(value): self.augment = value
case let .amp(value): self.amp = value
case let .trae(value): self.trae = value
case let .jetbrains(value): self.jetbrains = value
}
}
Expand All @@ -287,6 +305,7 @@ public struct ProviderSettingsSnapshotBuilder: Sendable {
kimi: self.kimi,
augment: self.augment,
amp: self.amp,
trae: self.trae,
jetbrains: self.jetbrains)
}
}
2 changes: 2 additions & 0 deletions Sources/CodexBarCore/Providers/Providers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum UsageProvider: String, CaseIterable, Sendable, Codable {
case jetbrains
case kimik2
case amp
case trae
case synthetic
}

Expand All @@ -43,6 +44,7 @@ public enum IconStyle: Sendable, CaseIterable {
case augment
case jetbrains
case amp
case trae
case synthetic
case combined
}
Expand Down
Loading