import AppKitimport CodexBarCoreimport Observationimport ServiceManagement enum RefreshFrequency: String, CaseIterable, Identifiable { case manual case oneMinute case twoMinutes case fiveMinutes case fifteenMinutes case thirtyMinutes case adaptive /// Adaptive plus consent-gated local agent activity. Kept after plain Adaptive so the /// privacy-preserving mode remains the first adaptive choice. case adaptiveAgentAware var id: String { self.rawValue } /// nil for `.manual` (no timer) and adaptive modes (delay is computed per tick by /// `AdaptiveRefreshPolicy`, not a fixed interval). var seconds: TimeInterval? { switch self { case .manual: nil case .oneMinute: 60 case .twoMinutes: 120 case .fiveMinutes: 300 case .fifteenMinutes: 900 case .thirtyMinutes: 1800 case .adaptive, .adaptiveAgentAware: nil } } var label: String { switch self { case .manual: L("refresh_manual") case .oneMinute: L("refresh_1min") case .twoMinutes: L("refresh_2min") case .fiveMinutes: L("refresh_5min") case .fifteenMinutes: L("refresh_15min") case .thirtyMinutes: L("refresh_30min") case .adaptive: L("refresh_adaptive") case .adaptiveAgentAware: L("refresh_adaptive_agent_aware") } } var usesAdaptivePolicy: Bool { self == .adaptive || self == .adaptiveAgentAware }} enum AdaptiveActivityScanConsent: String, Sendable { case undecided case allowed case declined} enum MenuBarMetricPreference: String, CaseIterable, Identifiable { case automatic case primary case secondary case primaryAndSecondary case tertiary case extraUsage case average case monthlyPlan var id: String { self.rawValue } var label: String { switch self { case .automatic: L("metric_pref_automatic") case .primary: L("metric_pref_primary") case .secondary: L("metric_pref_secondary") case .primaryAndSecondary: "\(L("metric_pref_primary")) + \(L("metric_pref_secondary"))" case .tertiary: L("metric_pref_tertiary") case .extraUsage: L("metric_pref_extra_usage") case .average: L("metric_pref_average") case .monthlyPlan: L("metric_mistral_monthly_plan") } }} enum KiroMenuBarDisplayMode: String, CaseIterable, Identifiable { case automatic case hidden case creditsLeft case percentLeft case creditsAndPercent case usedAndTotal case overageCreditsWhenExhausted case overageCostWhenExhausted case overageCreditsAndCostWhenExhausted var id: String { self.rawValue } var label: String { switch self { case .automatic: L("Automatic") case .hidden: L("Hidden") case .creditsLeft: L("Credits left") case .percentLeft: L("Percent left") case .creditsAndPercent: L("Credits + percent") case .usedAndTotal: L("Used / total") case .overageCreditsWhenExhausted: L("Overage credits at zero") case .overageCostWhenExhausted: L("Overage cost at zero") case .overageCreditsAndCostWhenExhausted: L("Overage credits + cost at zero") } }} enum MultiAccountMenuLayout: String, CaseIterable, Identifiable { case segmented case stacked var id: String { self.rawValue } var label: String { switch self { case .segmented: L("multi_account_layout_segmented") case .stacked: L("multi_account_layout_stacked") } }} enum CostSummaryDisplayStyle: String, CaseIterable, Identifiable { case inlineSummary case costSubmenu case both var id: String { self.rawValue } var label: String { switch self { case .inlineSummary: L("cost_summary_style_inline") case .costSubmenu: L("cost_summary_style_submenu") case .both: L("cost_summary_style_both") } } var helpText: String { switch self { case .inlineSummary: L("cost_summary_style_inline_help") case .costSubmenu: L("cost_summary_style_submenu_help") case .both: L("cost_summary_style_both_help") } } var showsInlineSummary: Bool { self != .costSubmenu } var showsCostSubmenu: Bool { self != .inlineSummary }} struct CachedCodexAccountReconciliationSnapshot { let activeSource: CodexActiveSource let loadedAt: Date let snapshot: CodexAccountReconciliationSnapshot} struct CachedCodexAccountMenuProjection: Equatable { let activeSource: CodexActiveSource let loadedAt: Date let projection: CodexVisibleAccountProjection} enum CodexAccountMenuProjectionRevalidationResult: Equatable { case skipped case discarded case unchanged case updated} @MainActor@Observablefinal class SettingsStore { static let sharedDefaults = AppGroupSupport.sharedDefaults() static let mergedOverviewProviderLimit = 6 static let productionCodexAccountReconciliationSnapshotCacheInterval: TimeInterval = 2 static let isRunningTests: Bool = { let env = ProcessInfo.processInfo.environment if env["XCTestConfigurationFilePath"] != nil { return true } if env["TESTING_LIBRARY_VERSION"] != nil { return true } if env["SWIFT_TESTING"] != nil { return true } return NSClassFromString("XCTestCase") != nil }() #if DEBUG static var codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting: TimeInterval? #endif @ObservationIgnored let userDefaults: UserDefaults @ObservationIgnored let configStore: CodexBarConfigStore @ObservationIgnored let antigravityOAuthCredentialsStore: AntigravityOAuthCredentialsStore @ObservationIgnored var config: CodexBarConfig @ObservationIgnored var configPersistTask: Task<Void, Never>? @ObservationIgnored var configLoading = false @ObservationIgnored var tokenAccountsLoaded = false @ObservationIgnored var cachedCodexAccountReconciliationSnapshot: CachedCodexAccountReconciliationSnapshot? @ObservationIgnored var cachedCodexAccountMenuProjection: CachedCodexAccountMenuProjection? @ObservationIgnored var codexAccountReconciliationGeneration: UInt = 0 #if DEBUG @ObservationIgnored var _test_codexAccountSnapshotLoader: (@Sendable (CodexActiveSource) -> CodexAccountReconciliationSnapshot)? #endif @ObservationIgnored var mergedMenuLastSelectedWasOverviewStorage = false @ObservationIgnored var selectedMenuProviderRawStorage: String? var defaultsState: SettingsDefaultsState var configRevision: Int = 0 var providerDetailSettingsRevision: Int = 0 var backgroundWorkSettingsRevision: Int = 0 var costUsageSettingsRevision: UInt64 = 0 var providerOrder: [UsageProvider] = [] var providerEnablement: [UsageProvider: Bool] = [:] @ObservationIgnored var providerEnablementRevisions: [UsageProvider: UInt64] = [:] @ObservationIgnored var providerConfigRevisions: [UsageProvider: UInt64] = [:] @ObservationIgnored var providerConfigFingerprints: [UsageProvider: Data] = [:] static func shouldBridgeSharedDefaults(for userDefaults: UserDefaults) -> Bool { if !self.isRunningTests { return true } if userDefaults === UserDefaults.standard { return true } if let shared = sharedDefaults, userDefaults === shared { return true } return false } init( userDefaults: UserDefaults = .standard, configStore: CodexBarConfigStore = CodexBarConfigStore(), zaiTokenStore: any ZaiTokenStoring = KeychainZaiTokenStore(), syntheticTokenStore: any SyntheticTokenStoring = KeychainSyntheticTokenStore(), codexCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "codex-cookie", promptKind: .codexCookie), claudeCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "claude-cookie", promptKind: .claudeCookie), cursorCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "cursor-cookie", promptKind: .cursorCookie), opencodeCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "opencode-cookie", promptKind: .opencodeCookie), factoryCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "factory-cookie", promptKind: .factoryCookie), minimaxCookieStore: any MiniMaxCookieStoring = KeychainMiniMaxCookieStore(), minimaxAPITokenStore: any MiniMaxAPITokenStoring = KeychainMiniMaxAPITokenStore(), kimiTokenStore: any KimiTokenStoring = KeychainKimiTokenStore(), augmentCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "augment-cookie", promptKind: .augmentCookie), ampCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "amp-cookie", promptKind: .ampCookie), copilotTokenStore: any CopilotTokenStoring = KeychainCopilotTokenStore(), tokenAccountStore: any ProviderTokenAccountStoring = FileTokenAccountStore(), antigravityOAuthCredentialsStore: AntigravityOAuthCredentialsStore = AntigravityOAuthCredentialsStore(), performInitialProviderDetection: Bool = !SettingsStore.isRunningTests) { // Capture this before app-group/config migrations can create prior-installation state. let hadExistingConfig = (try? configStore.load()) != nil let hadPreviousInstallationState = hadExistingConfig || Self.hadPreviousAppLaunch(userDefaults: userDefaults) let appGroupID = AppGroupSupport.currentGroupID() let appGroupMigration: AppGroupSupport.MigrationResult if Self.isRunningTests { appGroupMigration = AppGroupSupport.migrateLegacyDataIfNeeded(standardDefaults: userDefaults) } else { Self.scheduleAppGroupMigration() appGroupMigration = AppGroupSupport.MigrationResult(status: .targetUnavailable) } let sharedDefaultsAvailable = Self.sharedDefaults != nil if !Self.isRunningTests { CodexBarLog.logger(LogCategories.settings).info( "App group resolved", metadata: [ "groupID": appGroupID, "sharedDefaultsAvailable": sharedDefaultsAvailable ? "1" : "0", "migrationStatus": appGroupMigration.status.rawValue, "migratedSnapshot": appGroupMigration.copiedSnapshot ? "1" : "0", "migratedDefaults": "\(appGroupMigration.copiedDefaults)", ]) } if userDefaults.object(forKey: "openAIWebAccessEnabled") == nil, let legacyOpenAIWebAccess = userDefaults.object(forKey: "openAIWebAccess") as? Bool { userDefaults.set(legacyOpenAIWebAccess, forKey: "openAIWebAccessEnabled") } let hasStoredOpenAIWebAccessPreference = userDefaults.object(forKey: "openAIWebAccessEnabled") != nil let legacyStores = CodexBarConfigMigrator.LegacyStores( zaiTokenStore: zaiTokenStore, syntheticTokenStore: syntheticTokenStore, codexCookieStore: codexCookieStore, claudeCookieStore: claudeCookieStore, cursorCookieStore: cursorCookieStore, opencodeCookieStore: opencodeCookieStore, factoryCookieStore: factoryCookieStore, minimaxCookieStore: minimaxCookieStore, minimaxAPITokenStore: minimaxAPITokenStore, kimiTokenStore: kimiTokenStore, augmentCookieStore: augmentCookieStore, ampCookieStore: ampCookieStore, copilotTokenStore: copilotTokenStore, tokenAccountStore: tokenAccountStore) let config = CodexBarConfigMigrator.loadOrMigrate( configStore: configStore, userDefaults: userDefaults, stores: legacyStores) self.userDefaults = userDefaults self.configStore = configStore self.antigravityOAuthCredentialsStore = antigravityOAuthCredentialsStore self.config = config self.configLoading = true let defaultsState = Self.loadDefaultsState( userDefaults: userDefaults, hadPreviousInstallationState: hadPreviousInstallationState) self.defaultsState = defaultsState self.mergedMenuLastSelectedWasOverviewStorage = defaultsState.mergedMenuLastSelectedWasOverview self.selectedMenuProviderRawStorage = defaultsState.selectedMenuProviderRaw self.updateProviderState(config: config) self.configLoading = false CodexBarLog.setFileLoggingEnabled(self.debugFileLoggingEnabled) userDefaults.removeObject(forKey: "showCodexUsage") userDefaults.removeObject(forKey: "showClaudeUsage") LaunchAtLoginManager.setEnabled(self.launchAtLogin) if performInitialProviderDetection { self.runInitialProviderDetectionIfNeeded() } self.ensureAlibabaProviderAutoEnabledIfNeeded() self.applyTokenCostDefaultIfNeeded() if self.claudeUsageDataSource != .cli { if Self.isRunningTests { self.claudeWebExtrasEnabled = false } else { self.defaultsState.claudeWebExtrasEnabledRaw = false } } let resolvedOpenAIWebAccessEnabled = if hasStoredOpenAIWebAccessPreference { self.defaultsState.openAIWebAccessEnabled } else { Self.inferredInitialOpenAIWebAccessEnabled( config: config, hadExistingConfig: hadExistingConfig) } if Self.isRunningTests { self.openAIWebAccessEnabled = resolvedOpenAIWebAccessEnabled } else { self.defaultsState.openAIWebAccessEnabled = resolvedOpenAIWebAccessEnabled } KeychainAccessGate.isDisabled = self.debugDisableKeychainAccess }} extension SettingsStore { private struct NotificationDefaults { let statusChecksEnabled: Bool let sessionQuotaNotificationsEnabled: Bool let predictivePaceWarningNotificationsEnabled: Bool } private static func scheduleAppGroupMigration() { Task.detached(priority: .utility) { let result = AppGroupSupport.migrateLegacyDataIfNeeded() CodexBarLog.logger(LogCategories.settings).info( "App group migration completed", metadata: [ "migrationStatus": result.status.rawValue, "migratedSnapshot": result.copiedSnapshot ? "1" : "0", "migratedDefaults": "\(result.copiedDefaults)", ]) } } private static func inferredInitialOpenAIWebAccessEnabled( config: CodexBarConfig, hadExistingConfig: Bool) -> Bool { guard let codex = config.providerConfig(for: .codex) else { return false } if let cookieSource = codex.cookieSource { return cookieSource.isEnabled } if codex.sanitizedCookieHeader != nil { return true } return hadExistingConfig } // swiftlint:disable:next function_body_length private static func loadDefaultsState( userDefaults: UserDefaults, hadPreviousInstallationState: Bool) -> SettingsDefaultsState { let refreshFrequency = Self.loadRefreshFrequency( userDefaults: userDefaults, hadPreviousInstallationState: hadPreviousInstallationState) let adaptiveActivityScanConsent = Self.loadAdaptiveActivityScanConsent(userDefaults: userDefaults) let refreshAllProvidersOnMenuOpen = userDefaults.object( forKey: "refreshAllProvidersOnMenuOpen") as? Bool ?? false let launchAtLogin = userDefaults.object(forKey: "launchAtLogin") as? Bool ?? false let debugMenuEnabled = userDefaults.object(forKey: "debugMenuEnabled") as? Bool ?? false let debugDisableKeychainAccess = Self.loadDebugDisableKeychainAccess(userDefaults: userDefaults) let debugFileLoggingEnabled = userDefaults.object(forKey: "debugFileLoggingEnabled") as? Bool ?? false let debugLogLevelRaw = userDefaults.string(forKey: "debugLogLevel") ?? CodexBarLog.Level.verbose.rawValue if Self.isRunningTests, userDefaults.string(forKey: "debugLogLevel") == nil { userDefaults.set(debugLogLevelRaw, forKey: "debugLogLevel") } let debugLoadingPatternRaw = userDefaults.string(forKey: "debugLoadingPattern") let debugKeepCLISessionsAlive = userDefaults.object(forKey: "debugKeepCLISessionsAlive") as? Bool ?? false let notificationDefaults = Self.loadNotificationDefaults(userDefaults: userDefaults) let quotaWarnings = Self.loadQuotaWarningDefaults(userDefaults: userDefaults) let quotaWarningMarkersVisibleDefault = userDefaults.object(forKey: "quotaWarningMarkersVisible") as? Bool let quotaWarningMarkersVisible = quotaWarningMarkersVisibleDefault ?? true if Self.isRunningTests, quotaWarningMarkersVisibleDefault == nil { userDefaults.set(true, forKey: "quotaWarningMarkersVisible") } let weeklyProgressWorkDays = userDefaults.object(forKey: "weeklyProgressWorkDays") as? Int let usageBarsShowUsed = userDefaults.object(forKey: "usageBarsShowUsed") as? Bool ?? false let resetTimesShowAbsolute = userDefaults.object(forKey: "resetTimesShowAbsolute") as? Bool ?? false let providerChangelogLinksEnabled = userDefaults.object( forKey: "providerChangelogLinksEnabled") as? Bool ?? false let menuBarShowsBrandIconWithPercent = userDefaults.object( forKey: "menuBarShowsBrandIconWithPercent") as? Bool ?? false let menuBarHidesCritters = userDefaults.object(forKey: "menuBarHidesCritters") as? Bool ?? false let menuBarHighContrastOnInactiveDisplays = userDefaults.object( forKey: "menuBarHighContrastOnInactiveDisplays") as? Bool ?? false let menuBarDisplayModeRaw = userDefaults.string(forKey: "menuBarDisplayMode") ?? MenuBarDisplayMode.percent.rawValue let menuBarShowsResetTimeWhenExhausted = userDefaults.object( forKey: "menuBarShowsResetTimeWhenExhausted") as? Bool ?? false let kiroMenuBarDisplayModeRaw = userDefaults.string(forKey: "kiroMenuBarDisplayMode") ?? KiroMenuBarDisplayMode.automatic.rawValue let historicalTrackingEnabled = userDefaults.object(forKey: "historicalTrackingEnabled") as? Bool ?? false let multiAccountMenuLayoutRaw = Self.loadMultiAccountMenuLayoutRaw(userDefaults: userDefaults) let resolvedPreferences = Self.loadMenuBarMetricPreferences(userDefaults: userDefaults) let storedMenuBarLayout = Self.loadMenuBarLayout(userDefaults: userDefaults, key: "menuBarLayout") let menuBarLayoutOverridesRaw = Self.loadMenuBarLayoutOverrides(userDefaults: userDefaults) let menuBarLayoutSizeRaw = userDefaults.string(forKey: "menuBarLayoutSize") ?? MenuBarLayoutSize.regular.rawValue let menuBarLayoutGapRaw = userDefaults.string(forKey: "menuBarLayoutGap") ?? MenuBarLayoutGap.regular.rawValue let copilotBudgetExtrasEnabled = userDefaults.object(forKey: "copilotBudgetExtrasEnabled") as? Bool ?? false let copilotIconSecondaryWindowIDRaw = Self.loadCopilotIconSecondaryWindowIDRaw(userDefaults: userDefaults) let costUsageEnabled = userDefaults.object(forKey: "tokenCostUsageEnabled") as? Bool ?? false let codexLocalSessionCostLedgerEnabled = userDefaults.object( forKey: "codexLocalSessionCostLedgerEnabled") as? Bool ?? false let rawCostUsageHistoryDays = userDefaults.object(forKey: "tokenCostUsageHistoryDays") as? Int ?? 30 let costUsageHistoryDays = max(1, min(365, rawCostUsageHistoryDays)) let costComparisonPeriodsEnabled = userDefaults.object( forKey: "costComparisonPeriodsEnabled") as? Bool ?? false let costSummaryDisplayStyleRaw = Self.loadCostSummaryDisplayStyleRaw( userDefaults: userDefaults, costUsageEnabled: costUsageEnabled) let hidePersonalInfo = userDefaults.object(forKey: "hidePersonalInfo") as? Bool ?? false let randomBlinkEnabled = userDefaults.object(forKey: "randomBlinkEnabled") as? Bool ?? false let confettiOnReset = Self.loadConfettiOnResetDefaults(userDefaults: userDefaults) let menuBarShowsHighestUsage = userDefaults.object(forKey: "menuBarShowsHighestUsage") as? Bool ?? false let claudeOAuthKeychainReadStrategyRaw = Self.loadClaudeOAuthKeychainReadStrategyRaw(userDefaults: userDefaults) let claudeOAuthKeychainPromptModeRaw = userDefaults.string(forKey: "claudeOAuthKeychainPromptMode") let claudeWebExtrasEnabledRaw = userDefaults.object(forKey: "claudeWebExtrasEnabled") as? Bool ?? false let creditsExtrasDefault = userDefaults.object(forKey: "showOptionalCreditsAndExtraUsage") as? Bool let showOptionalCreditsAndExtraUsage = creditsExtrasDefault ?? true if Self.isRunningTests, creditsExtrasDefault == nil { userDefaults.set(true, forKey: "showOptionalCreditsAndExtraUsage") } let codexSparkUsageVisibleDefault = userDefaults.object(forKey: "codexSparkUsageVisible") as? Bool let codexSparkUsageVisible = codexSparkUsageVisibleDefault ?? true if Self.isRunningTests, codexSparkUsageVisibleDefault == nil { userDefaults.set(true, forKey: "codexSparkUsageVisible") } let openAIWebAccessDefault = userDefaults.object(forKey: "openAIWebAccessEnabled") as? Bool let openAIWebAccessEnabled = openAIWebAccessDefault ?? false if Self.isRunningTests, openAIWebAccessDefault == nil { userDefaults.set(false, forKey: "openAIWebAccessEnabled") }Lines 1–500 of 951. Load more
import AppKit import CodexBarCore import Observation import ServiceManagement enum RefreshFrequency: String, CaseIterable, Identifiable { case manual case oneMinute case twoMinutes case fiveMinutes case fifteenMinutes case thirtyMinutes case adaptive /// Adaptive plus consent-gated local agent activity. Kept after plain Adaptive so the /// privacy-preserving mode remains the first adaptive choice. case adaptiveAgentAware var id: String { self.rawValue } /// nil for `.manual` (no timer) and adaptive modes (delay is computed per tick by /// `AdaptiveRefreshPolicy`, not a fixed interval). var seconds: TimeInterval? { switch self { case .manual: nil case .oneMinute: 60 case .twoMinutes: 120 case .fiveMinutes: 300 case .fifteenMinutes: 900 case .thirtyMinutes: 1800 case .adaptive, .adaptiveAgentAware: nil } } var label: String { switch self { case .manual: L("refresh_manual") case .oneMinute: L("refresh_1min") case .twoMinutes: L("refresh_2min") case .fiveMinutes: L("refresh_5min") case .fifteenMinutes: L("refresh_15min") case .thirtyMinutes: L("refresh_30min") case .adaptive: L("refresh_adaptive") case .adaptiveAgentAware: L("refresh_adaptive_agent_aware") } } var usesAdaptivePolicy: Bool { self == .adaptive || self == .adaptiveAgentAware } } enum AdaptiveActivityScanConsent: String, Sendable { case undecided case allowed case declined } enum MenuBarMetricPreference: String, CaseIterable, Identifiable { case automatic case primary case secondary case primaryAndSecondary case tertiary case extraUsage case average case monthlyPlan var id: String { self.rawValue } var label: String { switch self { case .automatic: L("metric_pref_automatic") case .primary: L("metric_pref_primary") case .secondary: L("metric_pref_secondary") case .primaryAndSecondary: "\(L("metric_pref_primary")) + \(L("metric_pref_secondary"))" case .tertiary: L("metric_pref_tertiary") case .extraUsage: L("metric_pref_extra_usage") case .average: L("metric_pref_average") case .monthlyPlan: L("metric_mistral_monthly_plan") } } } enum KiroMenuBarDisplayMode: String, CaseIterable, Identifiable { case automatic case hidden case creditsLeft case percentLeft case creditsAndPercent case usedAndTotal case overageCreditsWhenExhausted case overageCostWhenExhausted case overageCreditsAndCostWhenExhausted var id: String { self.rawValue } var label: String { switch self { case .automatic: L("Automatic") case .hidden: L("Hidden") case .creditsLeft: L("Credits left") case .percentLeft: L("Percent left") case .creditsAndPercent: L("Credits + percent") case .usedAndTotal: L("Used / total") case .overageCreditsWhenExhausted: L("Overage credits at zero") case .overageCostWhenExhausted: L("Overage cost at zero") case .overageCreditsAndCostWhenExhausted: L("Overage credits + cost at zero") } } } enum MultiAccountMenuLayout: String, CaseIterable, Identifiable { case segmented case stacked var id: String { self.rawValue } var label: String { switch self { case .segmented: L("multi_account_layout_segmented") case .stacked: L("multi_account_layout_stacked") } } } enum CostSummaryDisplayStyle: String, CaseIterable, Identifiable { case inlineSummary case costSubmenu case both var id: String { self.rawValue } var label: String { switch self { case .inlineSummary: L("cost_summary_style_inline") case .costSubmenu: L("cost_summary_style_submenu") case .both: L("cost_summary_style_both") } } var helpText: String { switch self { case .inlineSummary: L("cost_summary_style_inline_help") case .costSubmenu: L("cost_summary_style_submenu_help") case .both: L("cost_summary_style_both_help") } } var showsInlineSummary: Bool { self != .costSubmenu } var showsCostSubmenu: Bool { self != .inlineSummary } } struct CachedCodexAccountReconciliationSnapshot { let activeSource: CodexActiveSource let loadedAt: Date let snapshot: CodexAccountReconciliationSnapshot } struct CachedCodexAccountMenuProjection: Equatable { let activeSource: CodexActiveSource let loadedAt: Date let projection: CodexVisibleAccountProjection } enum CodexAccountMenuProjectionRevalidationResult: Equatable { case skipped case discarded case unchanged case updated } @MainActor @Observable final class SettingsStore { static let sharedDefaults = AppGroupSupport.sharedDefaults() static let mergedOverviewProviderLimit = 6 static let productionCodexAccountReconciliationSnapshotCacheInterval: TimeInterval = 2 static let isRunningTests: Bool = { let env = ProcessInfo.processInfo.environment if env["XCTestConfigurationFilePath"] != nil { return true } if env["TESTING_LIBRARY_VERSION"] != nil { return true } if env["SWIFT_TESTING"] != nil { return true } return NSClassFromString("XCTestCase") != nil }() #if DEBUG static var codexAccountReconciliationSnapshotCacheIntervalOverrideForTesting: TimeInterval? #endif @ObservationIgnored let userDefaults: UserDefaults @ObservationIgnored let configStore: CodexBarConfigStore @ObservationIgnored let antigravityOAuthCredentialsStore: AntigravityOAuthCredentialsStore @ObservationIgnored var config: CodexBarConfig @ObservationIgnored var configPersistTask: Task<Void, Never>? @ObservationIgnored var configLoading = false @ObservationIgnored var tokenAccountsLoaded = false @ObservationIgnored var cachedCodexAccountReconciliationSnapshot: CachedCodexAccountReconciliationSnapshot? @ObservationIgnored var cachedCodexAccountMenuProjection: CachedCodexAccountMenuProjection? @ObservationIgnored var codexAccountReconciliationGeneration: UInt = 0 #if DEBUG @ObservationIgnored var _test_codexAccountSnapshotLoader: (@Sendable (CodexActiveSource) -> CodexAccountReconciliationSnapshot)? #endif @ObservationIgnored var mergedMenuLastSelectedWasOverviewStorage = false @ObservationIgnored var selectedMenuProviderRawStorage: String? var defaultsState: SettingsDefaultsState var configRevision: Int = 0 var providerDetailSettingsRevision: Int = 0 var backgroundWorkSettingsRevision: Int = 0 var costUsageSettingsRevision: UInt64 = 0 var providerOrder: [UsageProvider] = [] var providerEnablement: [UsageProvider: Bool] = [:] @ObservationIgnored var providerEnablementRevisions: [UsageProvider: UInt64] = [:] @ObservationIgnored var providerConfigRevisions: [UsageProvider: UInt64] = [:] @ObservationIgnored var providerConfigFingerprints: [UsageProvider: Data] = [:] static func shouldBridgeSharedDefaults(for userDefaults: UserDefaults) -> Bool { if !self.isRunningTests { return true } if userDefaults === UserDefaults.standard { return true } if let shared = sharedDefaults, userDefaults === shared { return true } return false } init( userDefaults: UserDefaults = .standard, configStore: CodexBarConfigStore = CodexBarConfigStore(), zaiTokenStore: any ZaiTokenStoring = KeychainZaiTokenStore(), syntheticTokenStore: any SyntheticTokenStoring = KeychainSyntheticTokenStore(), codexCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "codex-cookie", promptKind: .codexCookie), claudeCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "claude-cookie", promptKind: .claudeCookie), cursorCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "cursor-cookie", promptKind: .cursorCookie), opencodeCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "opencode-cookie", promptKind: .opencodeCookie), factoryCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "factory-cookie", promptKind: .factoryCookie), minimaxCookieStore: any MiniMaxCookieStoring = KeychainMiniMaxCookieStore(), minimaxAPITokenStore: any MiniMaxAPITokenStoring = KeychainMiniMaxAPITokenStore(), kimiTokenStore: any KimiTokenStoring = KeychainKimiTokenStore(), augmentCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "augment-cookie", promptKind: .augmentCookie), ampCookieStore: any CookieHeaderStoring = KeychainCookieHeaderStore( account: "amp-cookie", promptKind: .ampCookie), copilotTokenStore: any CopilotTokenStoring = KeychainCopilotTokenStore(), tokenAccountStore: any ProviderTokenAccountStoring = FileTokenAccountStore(), antigravityOAuthCredentialsStore: AntigravityOAuthCredentialsStore = AntigravityOAuthCredentialsStore(), performInitialProviderDetection: Bool = !SettingsStore.isRunningTests) { // Capture this before app-group/config migrations can create prior-installation state. let hadExistingConfig = (try? configStore.load()) != nil let hadPreviousInstallationState = hadExistingConfig || Self.hadPreviousAppLaunch(userDefaults: userDefaults) let appGroupID = AppGroupSupport.currentGroupID() let appGroupMigration: AppGroupSupport.MigrationResult if Self.isRunningTests { appGroupMigration = AppGroupSupport.migrateLegacyDataIfNeeded(standardDefaults: userDefaults) } else { Self.scheduleAppGroupMigration() appGroupMigration = AppGroupSupport.MigrationResult(status: .targetUnavailable) } let sharedDefaultsAvailable = Self.sharedDefaults != nil if !Self.isRunningTests { CodexBarLog.logger(LogCategories.settings).info( "App group resolved", metadata: [ "groupID": appGroupID, "sharedDefaultsAvailable": sharedDefaultsAvailable ? "1" : "0", "migrationStatus": appGroupMigration.status.rawValue, "migratedSnapshot": appGroupMigration.copiedSnapshot ? "1" : "0", "migratedDefaults": "\(appGroupMigration.copiedDefaults)", ]) } if userDefaults.object(forKey: "openAIWebAccessEnabled") == nil, let legacyOpenAIWebAccess = userDefaults.object(forKey: "openAIWebAccess") as? Bool { userDefaults.set(legacyOpenAIWebAccess, forKey: "openAIWebAccessEnabled") } let hasStoredOpenAIWebAccessPreference = userDefaults.object(forKey: "openAIWebAccessEnabled") != nil let legacyStores = CodexBarConfigMigrator.LegacyStores( zaiTokenStore: zaiTokenStore, syntheticTokenStore: syntheticTokenStore, codexCookieStore: codexCookieStore, claudeCookieStore: claudeCookieStore, cursorCookieStore: cursorCookieStore, opencodeCookieStore: opencodeCookieStore, factoryCookieStore: factoryCookieStore, minimaxCookieStore: minimaxCookieStore, minimaxAPITokenStore: minimaxAPITokenStore, kimiTokenStore: kimiTokenStore, augmentCookieStore: augmentCookieStore, ampCookieStore: ampCookieStore, copilotTokenStore: copilotTokenStore, tokenAccountStore: tokenAccountStore) let config = CodexBarConfigMigrator.loadOrMigrate( configStore: configStore, userDefaults: userDefaults, stores: legacyStores) self.userDefaults = userDefaults self.configStore = configStore self.antigravityOAuthCredentialsStore = antigravityOAuthCredentialsStore self.config = config self.configLoading = true let defaultsState = Self.loadDefaultsState( userDefaults: userDefaults, hadPreviousInstallationState: hadPreviousInstallationState) self.defaultsState = defaultsState self.mergedMenuLastSelectedWasOverviewStorage = defaultsState.mergedMenuLastSelectedWasOverview self.selectedMenuProviderRawStorage = defaultsState.selectedMenuProviderRaw self.updateProviderState(config: config) self.configLoading = false CodexBarLog.setFileLoggingEnabled(self.debugFileLoggingEnabled) userDefaults.removeObject(forKey: "showCodexUsage") userDefaults.removeObject(forKey: "showClaudeUsage") LaunchAtLoginManager.setEnabled(self.launchAtLogin) if performInitialProviderDetection { self.runInitialProviderDetectionIfNeeded() } self.ensureAlibabaProviderAutoEnabledIfNeeded() self.applyTokenCostDefaultIfNeeded() if self.claudeUsageDataSource != .cli { if Self.isRunningTests { self.claudeWebExtrasEnabled = false } else { self.defaultsState.claudeWebExtrasEnabledRaw = false } } let resolvedOpenAIWebAccessEnabled = if hasStoredOpenAIWebAccessPreference { self.defaultsState.openAIWebAccessEnabled } else { Self.inferredInitialOpenAIWebAccessEnabled( config: config, hadExistingConfig: hadExistingConfig) } if Self.isRunningTests { self.openAIWebAccessEnabled = resolvedOpenAIWebAccessEnabled } else { self.defaultsState.openAIWebAccessEnabled = resolvedOpenAIWebAccessEnabled } KeychainAccessGate.isDisabled = self.debugDisableKeychainAccess } } extension SettingsStore { private struct NotificationDefaults { let statusChecksEnabled: Bool let sessionQuotaNotificationsEnabled: Bool let predictivePaceWarningNotificationsEnabled: Bool } private static func scheduleAppGroupMigration() { Task.detached(priority: .utility) { let result = AppGroupSupport.migrateLegacyDataIfNeeded() CodexBarLog.logger(LogCategories.settings).info( "App group migration completed", metadata: [ "migrationStatus": result.status.rawValue, "migratedSnapshot": result.copiedSnapshot ? "1" : "0", "migratedDefaults": "\(result.copiedDefaults)", ]) } } private static func inferredInitialOpenAIWebAccessEnabled( config: CodexBarConfig, hadExistingConfig: Bool) -> Bool { guard let codex = config.providerConfig(for: .codex) else { return false } if let cookieSource = codex.cookieSource { return cookieSource.isEnabled } if codex.sanitizedCookieHeader != nil { return true } return hadExistingConfig } // swiftlint:disable:next function_body_length private static func loadDefaultsState( userDefaults: UserDefaults, hadPreviousInstallationState: Bool) -> SettingsDefaultsState { let refreshFrequency = Self.loadRefreshFrequency( userDefaults: userDefaults, hadPreviousInstallationState: hadPreviousInstallationState) let adaptiveActivityScanConsent = Self.loadAdaptiveActivityScanConsent(userDefaults: userDefaults) let refreshAllProvidersOnMenuOpen = userDefaults.object( forKey: "refreshAllProvidersOnMenuOpen") as? Bool ?? false let launchAtLogin = userDefaults.object(forKey: "launchAtLogin") as? Bool ?? false let debugMenuEnabled = userDefaults.object(forKey: "debugMenuEnabled") as? Bool ?? false let debugDisableKeychainAccess = Self.loadDebugDisableKeychainAccess(userDefaults: userDefaults) let debugFileLoggingEnabled = userDefaults.object(forKey: "debugFileLoggingEnabled") as? Bool ?? false let debugLogLevelRaw = userDefaults.string(forKey: "debugLogLevel") ?? CodexBarLog.Level.verbose.rawValue if Self.isRunningTests, userDefaults.string(forKey: "debugLogLevel") == nil { userDefaults.set(debugLogLevelRaw, forKey: "debugLogLevel") } let debugLoadingPatternRaw = userDefaults.string(forKey: "debugLoadingPattern") let debugKeepCLISessionsAlive = userDefaults.object(forKey: "debugKeepCLISessionsAlive") as? Bool ?? false let notificationDefaults = Self.loadNotificationDefaults(userDefaults: userDefaults) let quotaWarnings = Self.loadQuotaWarningDefaults(userDefaults: userDefaults) let quotaWarningMarkersVisibleDefault = userDefaults.object(forKey: "quotaWarningMarkersVisible") as? Bool let quotaWarningMarkersVisible = quotaWarningMarkersVisibleDefault ?? true if Self.isRunningTests, quotaWarningMarkersVisibleDefault == nil { userDefaults.set(true, forKey: "quotaWarningMarkersVisible") } let weeklyProgressWorkDays = userDefaults.object(forKey: "weeklyProgressWorkDays") as? Int let usageBarsShowUsed = userDefaults.object(forKey: "usageBarsShowUsed") as? Bool ?? false let resetTimesShowAbsolute = userDefaults.object(forKey: "resetTimesShowAbsolute") as? Bool ?? false let providerChangelogLinksEnabled = userDefaults.object( forKey: "providerChangelogLinksEnabled") as? Bool ?? false let menuBarShowsBrandIconWithPercent = userDefaults.object( forKey: "menuBarShowsBrandIconWithPercent") as? Bool ?? false let menuBarHidesCritters = userDefaults.object(forKey: "menuBarHidesCritters") as? Bool ?? false let menuBarHighContrastOnInactiveDisplays = userDefaults.object( forKey: "menuBarHighContrastOnInactiveDisplays") as? Bool ?? false let menuBarDisplayModeRaw = userDefaults.string(forKey: "menuBarDisplayMode") ?? MenuBarDisplayMode.percent.rawValue let menuBarShowsResetTimeWhenExhausted = userDefaults.object( forKey: "menuBarShowsResetTimeWhenExhausted") as? Bool ?? false let kiroMenuBarDisplayModeRaw = userDefaults.string(forKey: "kiroMenuBarDisplayMode") ?? KiroMenuBarDisplayMode.automatic.rawValue let historicalTrackingEnabled = userDefaults.object(forKey: "historicalTrackingEnabled") as? Bool ?? false let multiAccountMenuLayoutRaw = Self.loadMultiAccountMenuLayoutRaw(userDefaults: userDefaults) let resolvedPreferences = Self.loadMenuBarMetricPreferences(userDefaults: userDefaults) let storedMenuBarLayout = Self.loadMenuBarLayout(userDefaults: userDefaults, key: "menuBarLayout") let menuBarLayoutOverridesRaw = Self.loadMenuBarLayoutOverrides(userDefaults: userDefaults) let menuBarLayoutSizeRaw = userDefaults.string(forKey: "menuBarLayoutSize") ?? MenuBarLayoutSize.regular.rawValue let menuBarLayoutGapRaw = userDefaults.string(forKey: "menuBarLayoutGap") ?? MenuBarLayoutGap.regular.rawValue let copilotBudgetExtrasEnabled = userDefaults.object(forKey: "copilotBudgetExtrasEnabled") as? Bool ?? false let copilotIconSecondaryWindowIDRaw = Self.loadCopilotIconSecondaryWindowIDRaw(userDefaults: userDefaults) let costUsageEnabled = userDefaults.object(forKey: "tokenCostUsageEnabled") as? Bool ?? false let codexLocalSessionCostLedgerEnabled = userDefaults.object( forKey: "codexLocalSessionCostLedgerEnabled") as? Bool ?? false let rawCostUsageHistoryDays = userDefaults.object(forKey: "tokenCostUsageHistoryDays") as? Int ?? 30 let costUsageHistoryDays = max(1, min(365, rawCostUsageHistoryDays)) let costComparisonPeriodsEnabled = userDefaults.object( forKey: "costComparisonPeriodsEnabled") as? Bool ?? false let costSummaryDisplayStyleRaw = Self.loadCostSummaryDisplayStyleRaw( userDefaults: userDefaults, costUsageEnabled: costUsageEnabled) let hidePersonalInfo = userDefaults.object(forKey: "hidePersonalInfo") as? Bool ?? false let randomBlinkEnabled = userDefaults.object(forKey: "randomBlinkEnabled") as? Bool ?? false let confettiOnReset = Self.loadConfettiOnResetDefaults(userDefaults: userDefaults) let menuBarShowsHighestUsage = userDefaults.object(forKey: "menuBarShowsHighestUsage") as? Bool ?? false let claudeOAuthKeychainReadStrategyRaw = Self.loadClaudeOAuthKeychainReadStrategyRaw(userDefaults: userDefaults) let claudeOAuthKeychainPromptModeRaw = userDefaults.string(forKey: "claudeOAuthKeychainPromptMode") let claudeWebExtrasEnabledRaw = userDefaults.object(forKey: "claudeWebExtrasEnabled") as? Bool ?? false let creditsExtrasDefault = userDefaults.object(forKey: "showOptionalCreditsAndExtraUsage") as? Bool let showOptionalCreditsAndExtraUsage = creditsExtrasDefault ?? true if Self.isRunningTests, creditsExtrasDefault == nil { userDefaults.set(true, forKey: "showOptionalCreditsAndExtraUsage") } let codexSparkUsageVisibleDefault = userDefaults.object(forKey: "codexSparkUsageVisible") as? Bool let codexSparkUsageVisible = codexSparkUsageVisibleDefault ?? true if Self.isRunningTests, codexSparkUsageVisibleDefault == nil { userDefaults.set(true, forKey: "codexSparkUsageVisible") } let openAIWebAccessDefault = userDefaults.object(forKey: "openAIWebAccessEnabled") as? Bool let openAIWebAccessEnabled = openAIWebAccessDefault ?? false if Self.isRunningTests, openAIWebAccessDefault == nil { userDefaults.set(false, forKey: "openAIWebAccessEnabled") }