@sriinnu/runic 1.2.0 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +267 -0
- package/LICENSE +373 -21
- package/README.md +98 -84
- package/THIRD-PARTY-NOTICES.md +651 -0
- package/package.json +14 -4
- package/.swiftformat +0 -50
- package/.swiftlint.yml +0 -160
- package/CONTRIBUTING.md +0 -456
- package/Icon.icns +0 -0
- package/Icon.icon/Assets/runic.png +0 -0
- package/Icon.icon/icon.json +0 -36
- package/Package.resolved +0 -51
- package/Package.swift +0 -136
- package/Packages/Helix/LICENSE +0 -21
- package/Packages/Helix/Package.swift +0 -29
- package/Packages/Helix/README.md +0 -976
- package/Packages/Helix/Sources/Helix/Binding.swift +0 -111
- package/Packages/Helix/Sources/Helix/CommandParser.swift +0 -160
- package/Packages/Helix/Sources/Helix/CommandSignature.swift +0 -117
- package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +0 -72
- package/Packages/Helix/Sources/Helix/HelpGenerator.swift +0 -175
- package/Packages/Helix/Sources/Helix/NameSpecification.swift +0 -98
- package/Packages/Helix/Sources/Helix/ParsableCommand.swift +0 -204
- package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +0 -44
- package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +0 -34
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +0 -91
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +0 -94
- package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +0 -167
- package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +0 -99
- package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +0 -146
- package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +0 -97
- package/Packages/Helix/Sources/Helix/Program.swift +0 -204
- package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +0 -367
- package/Packages/Silo/LICENSE +0 -21
- package/Packages/Silo/Package.swift +0 -34
- package/Packages/Silo/README.md +0 -466
- package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +0 -169
- package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +0 -365
- package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +0 -594
- package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +0 -326
- package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +0 -154
- package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +0 -119
- package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +0 -121
- package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +0 -391
- package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +0 -240
- package/Packages/Silo/Sources/Silo/CryptoSupport.swift +0 -86
- package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +0 -172
- package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +0 -172
- package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +0 -105
- package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +0 -191
- package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +0 -41
- package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +0 -110
- package/RuniciOS/App/RuniciOSApp.swift +0 -105
- package/RuniciOS/Core/iOSUsageStore.swift +0 -192
- package/RuniciOS/Views/ProviderDetailView.swift +0 -312
- package/RuniciOS/Views/ProviderListView.swift +0 -228
- package/RuniciOS/Widgets/LargeWidget.swift +0 -367
- package/RuniciOS/Widgets/LiveActivity.swift +0 -342
- package/RuniciOS/Widgets/LockScreenWidget.swift +0 -273
- package/RuniciOS/Widgets/MediumWidget.swift +0 -370
- package/RuniciOS/Widgets/RunicWidget.swift +0 -274
- package/RuniciOS/Widgets/SmallWidget.swift +0 -281
- package/RuniciOS/Widgets/WidgetModels.swift +0 -285
- package/RuniciOS/Widgets/WidgetTimeline.swift +0 -270
- package/Scripts/CREATION-SUMMARY.md +0 -367
- package/Scripts/DIRECTORY-STRUCTURE.md +0 -358
- package/Scripts/GETTING-STARTED-CHECKLIST.md +0 -364
- package/Scripts/INDEX.md +0 -365
- package/Scripts/QUICK-REFERENCE.md +0 -164
- package/Scripts/README-SCRIPTS.md +0 -926
- package/Scripts/build-android.sh +0 -526
- package/Scripts/build-ios.sh +0 -529
- package/Scripts/build-macos.sh +0 -603
- package/Scripts/build-windows.sh +0 -504
- package/Scripts/build_icon.sh +0 -49
- package/Scripts/changelog-to-html.sh +0 -89
- package/Scripts/check-release-assets.sh +0 -10
- package/Scripts/ci-config.yml +0 -474
- package/Scripts/compile_and_run.sh +0 -219
- package/Scripts/compile_and_run_adhoc.sh +0 -4
- package/Scripts/docs-list.mjs +0 -119
- package/Scripts/make_appcast.sh +0 -58
- package/Scripts/package_app.sh +0 -409
- package/Scripts/release-lib.sh +0 -214
- package/Scripts/release.sh +0 -76
- package/Scripts/setup-all.sh +0 -387
- package/Scripts/setup-ios.sh +0 -404
- package/Scripts/setup-react-native.sh +0 -523
- package/Scripts/sign-and-notarize.sh +0 -122
- package/Scripts/test_live_update.sh +0 -38
- package/Scripts/validate_changelog.sh +0 -23
- package/Scripts/verify_appcast.sh +0 -92
- package/Sources/Runic/About.swift +0 -93
- package/Sources/Runic/AppNotifications.swift +0 -115
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +0 -666
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +0 -518
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +0 -810
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +0 -87
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +0 -1407
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +0 -337
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +0 -395
- package/Sources/Runic/Controllers/StatusItemController.swift +0 -360
- package/Sources/Runic/Core/BudgetNotificationManager.swift +0 -80
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +0 -103
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +0 -154
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +0 -206
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +0 -92
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +0 -1216
- package/Sources/Runic/Core/Rendering/IconView.swift +0 -120
- package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +0 -71
- package/Sources/Runic/Core/RunicAnimation.swift +0 -53
- package/Sources/Runic/Core/RunicColors.swift +0 -73
- package/Sources/Runic/Core/RunicCornerRadius.swift +0 -15
- package/Sources/Runic/Core/RunicSpacing.swift +0 -68
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +0 -158
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +0 -30
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/SettingsStore.swift +0 -1711
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +0 -20
- package/Sources/Runic/Core/Stores/UsageStore+Status.swift +0 -189
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +0 -33
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +0 -67
- package/Sources/Runic/Core/Stores/UsageStore.swift +0 -3605
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +0 -118
- package/Sources/Runic/Core/UsageExporter.swift +0 -193
- package/Sources/Runic/Date+RelativeDescription.swift +0 -21
- package/Sources/Runic/HiddenWindowView.swift +0 -32
- package/Sources/Runic/InstallOrigin.swift +0 -9
- package/Sources/Runic/KeyboardShortcuts+Names.swift +0 -6
- package/Sources/Runic/MouseLocationReader.swift +0 -65
- package/Sources/Runic/Notifications+Runic.swift +0 -6
- package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +0 -503
- package/Sources/Runic/ProviderRegistry.swift +0 -100
- package/Sources/Runic/ProviderSwitcherButtons.swift +0 -267
- package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +0 -11
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +0 -14
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +0 -52
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +0 -42
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +0 -26
- package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +0 -14
- package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +0 -18
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +0 -193
- package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +0 -110
- package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +0 -28
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +0 -12
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +0 -20
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +0 -32
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +0 -15
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +0 -48
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +0 -66
- package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +0 -12
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +0 -99
- package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +0 -18
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +0 -24
- package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +0 -2
- package/Sources/Runic/Resources/ProviderIcon-auggie.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-azure.svg +0 -5
- package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +0 -7
- package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-claude.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-codex.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-cohere.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-copilot.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-cursor.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-factory.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-gemini.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-groq.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-kimi.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-minimax.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-mistral.svg +0 -5
- package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +0 -7
- package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-qwen.svg +0 -9
- package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-together.svg +0 -5
- package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +0 -17
- package/Sources/Runic/Resources/ProviderIcon-xai.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-zai.svg +0 -5
- package/Sources/Runic/Resources/RunicMenubarIcon.svg +0 -7
- package/Sources/Runic/Resources/provider-context-windows.json +0 -20
- package/Sources/Runic/RunicApp.swift +0 -357
- package/Sources/Runic/SessionQuotaNotifications.swift +0 -58
- package/Sources/Runic/UsagePaceText.swift +0 -52
- package/Sources/Runic/UsageProgressBar.swift +0 -224
- package/Sources/Runic/Utilities/DisplayLink.swift +0 -96
- package/Sources/Runic/Utilities/LoadingPattern.swift +0 -60
- package/Sources/Runic/Utilities/PerformanceConstants.swift +0 -85
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +0 -256
- package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +0 -22
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +0 -275
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +0 -377
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +0 -312
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +0 -312
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +0 -228
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +0 -228
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +0 -155
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +0 -64
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +0 -268
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +0 -104
- package/Sources/Runic/Views/Menu/MenuCardView.swift +0 -1900
- package/Sources/Runic/Views/Menu/MenuContent.swift +0 -150
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +0 -369
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +0 -42
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +0 -235
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +0 -278
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +0 -209
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +0 -220
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +0 -88
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +0 -201
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +0 -398
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +0 -232
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +0 -364
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +0 -190
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +0 -132
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +0 -618
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +0 -305
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +0 -271
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +0 -268
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +0 -422
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +0 -641
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +0 -344
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +0 -230
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +0 -322
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +0 -469
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +0 -231
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +0 -50
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +0 -567
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +0 -288
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +0 -3435
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +0 -25
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +0 -59
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +0 -206
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +0 -238
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +0 -371
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +0 -175
- package/Sources/Runic/Views/Team/TeamManagementView.swift +0 -761
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +0 -181
- package/Sources/RunicCLI/CLIEntry.swift +0 -942
- package/Sources/RunicCLI/CLIRenderer.swift +0 -87
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +0 -588
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +0 -362
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +0 -439
- package/Sources/RunicCLI/Commands/ResetCommand.swift +0 -429
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +0 -168
- package/Sources/RunicClaudeWatchdog/main.swift +0 -122
- package/Sources/RunicClaudeWebProbe/main.swift +0 -62
- package/Sources/RunicCore/AlertRuleStore.swift +0 -187
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +0 -25
- package/Sources/RunicCore/CopilotUsageModels.swift +0 -84
- package/Sources/RunicCore/CostUsageFetcher.swift +0 -119
- package/Sources/RunicCore/CostUsageModels.swift +0 -479
- package/Sources/RunicCore/CreditsModels.swift +0 -50
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +0 -269
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +0 -633
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +0 -500
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +0 -679
- package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +0 -135
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +0 -173
- package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +0 -79
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +0 -86
- package/Sources/RunicCore/Logging/RunicLog.swift +0 -123
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +0 -371
- package/Sources/RunicCore/OpenAIDashboardModels.swift +0 -210
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +0 -676
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +0 -492
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +0 -51
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +0 -483
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +0 -558
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +0 -204
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +0 -78
- package/Sources/RunicCore/PathEnvironment.swift +0 -482
- package/Sources/RunicCore/Performance/PerformanceModels.swift +0 -371
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +0 -409
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +0 -178
- package/Sources/RunicCore/ProjectBudgetStore.swift +0 -99
- package/Sources/RunicCore/ProviderCostSnapshot.swift +0 -29
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +0 -66
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +0 -685
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +0 -142
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +0 -141
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +0 -188
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +0 -126
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +0 -168
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +0 -332
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +0 -180
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +0 -137
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +0 -207
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +0 -654
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +0 -28
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +0 -480
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +0 -780
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +0 -147
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +0 -280
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +0 -100
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +0 -243
- package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +0 -125
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +0 -24
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +0 -181
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +0 -85
- package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +0 -123
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +0 -114
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +0 -100
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +0 -291
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +0 -386
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +0 -65
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +0 -595
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +0 -85
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +0 -108
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +0 -398
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +0 -65
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +0 -1276
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +0 -65
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +0 -836
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +0 -71
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +0 -77
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +0 -92
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +0 -54
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +0 -168
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +0 -911
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +0 -87
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +0 -203
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/ProviderBranding.swift +0 -25
- package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +0 -17
- package/Sources/RunicCore/Providers/ProviderDescriptor.swift +0 -129
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +0 -263
- package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +0 -98
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +0 -501
- package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +0 -68
- package/Sources/RunicCore/Providers/Providers.swift +0 -184
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +0 -30
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +0 -315
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +0 -123
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +0 -48
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +0 -144
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +0 -72
- package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +0 -41
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +0 -718
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +0 -385
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +0 -310
- package/Sources/RunicCore/Sync/SyncProtocol.swift +0 -288
- package/Sources/RunicCore/Sync/SyncRecord.swift +0 -405
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +0 -294
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +0 -775
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +0 -333
- package/Sources/RunicCore/Team/TeamModels.swift +0 -583
- package/Sources/RunicCore/Team/TeamShowbackExporter.swift +0 -495
- package/Sources/RunicCore/Team/TeamStore.swift +0 -792
- package/Sources/RunicCore/TextParsing.swift +0 -55
- package/Sources/RunicCore/UsageFetcher.swift +0 -694
- package/Sources/RunicCore/UsageFormatter.swift +0 -410
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +0 -427
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +0 -494
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +0 -158
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +0 -550
- package/Sources/RunicCore/UsageLedger/UsageLedger.swift +0 -29
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +0 -660
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +0 -209
- package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +0 -417
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +0 -245
- package/Sources/RunicCore/UsagePace.swift +0 -81
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +0 -73
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +0 -74
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +0 -208
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +0 -455
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +0 -132
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +0 -589
- package/Sources/RunicCore/WidgetSnapshot.swift +0 -183
- package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +0 -14
- package/Sources/RunicMacros/ProviderRegistrationMacros.swift +0 -216
- package/Sources/RunicWidget/RunicWidgetBundle.swift +0 -79
- package/Sources/RunicWidget/RunicWidgetProvider.swift +0 -283
- package/Sources/RunicWidget/RunicWidgetViews.swift +0 -680
- package/Tests/RunicTests/AntigravityStatusProbeTests.swift +0 -50
- package/Tests/RunicTests/AppDelegateTests.swift +0 -43
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +0 -22
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +0 -70
- package/Tests/RunicTests/CLICostTests.swift +0 -65
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +0 -22
- package/Tests/RunicTests/CLISnapshotTests.swift +0 -72
- package/Tests/RunicTests/CLIWebFallbackTests.swift +0 -58
- package/Tests/RunicTests/ClaudeOAuthTests.swift +0 -164
- package/Tests/RunicTests/ClaudeResilienceTests.swift +0 -30
- package/Tests/RunicTests/ClaudeUsageTests.swift +0 -370
- package/Tests/RunicTests/CodexOAuthTests.swift +0 -122
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +0 -82
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +0 -75
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +0 -75
- package/Tests/RunicTests/CostUsageDecodingTests.swift +0 -394
- package/Tests/RunicTests/CostUsagePricingTests.swift +0 -50
- package/Tests/RunicTests/CostUsageScannerTests.swift +0 -700
- package/Tests/RunicTests/CursorStatusProbeTests.swift +0 -323
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +0 -69
- package/Tests/RunicTests/GeminiAPITestHelpers.swift +0 -94
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +0 -28
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +0 -283
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +0 -279
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +0 -233
- package/Tests/RunicTests/GeminiTestEnvironment.swift +0 -87
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +0 -73
- package/Tests/RunicTests/InstallOriginTests.swift +0 -18
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +0 -9
- package/Tests/RunicTests/LiveAccountTests.swift +0 -24
- package/Tests/RunicTests/LoadingPatternTests.swift +0 -35
- package/Tests/RunicTests/MenuCardModelTests.swift +0 -545
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +0 -8
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +0 -8
- package/Tests/RunicTests/MiniMaxParsingTests.swift +0 -68
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +0 -8
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +0 -112
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +0 -17
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +0 -75
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +0 -27
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +0 -134
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +0 -53
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +0 -92
- package/Tests/RunicTests/PathBuilderTests.swift +0 -237
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +0 -160
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +0 -38
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +0 -16
- package/Tests/RunicTests/ProviderRegistryTests.swift +0 -29
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +0 -202
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +0 -51
- package/Tests/RunicTests/RunicTests.swift +0 -120
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +0 -36
- package/Tests/RunicTests/SettingsStoreTests.swift +0 -161
- package/Tests/RunicTests/StatusItemAnimationTests.swift +0 -49
- package/Tests/RunicTests/StatusMenuTests.swift +0 -434
- package/Tests/RunicTests/StatusProbeTests.swift +0 -377
- package/Tests/RunicTests/SubprocessRunnerTests.swift +0 -20
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +0 -65
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +0 -148
- package/Tests/RunicTests/TTYIntegrationTests.swift +0 -41
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +0 -159
- package/Tests/RunicTests/TextParsingTests.swift +0 -12
- package/Tests/RunicTests/UsageFormatterTests.swift +0 -112
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +0 -172
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +0 -213
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +0 -83
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +0 -170
- package/Tests/RunicTests/UsagePaceTests.swift +0 -77
- package/Tests/RunicTests/UsagePaceTextTests.swift +0 -109
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +0 -91
- package/Tests/RunicTests/WidgetSnapshotTests.swift +0 -45
- package/Tests/RunicTests/ZaiProviderTests.swift +0 -61
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +0 -6
- package/TestsLinux/PlatformGatingTests.swift +0 -46
- package/appcast.xml +0 -144
- package/assets/logo-concept-stack.svg +0 -24
- package/assets/logo-minimal-bars.svg +0 -15
- package/assets/logo.svg +0 -20
- package/assets/logo2.svg +0 -43
- package/bin/docs-list +0 -5
- package/build-and-run.sh +0 -369
- package/docs/.nojekyll +0 -1
- package/docs/CNAME +0 -1
- package/docs/antigravity.md +0 -75
- package/docs/architecture.md +0 -32
- package/docs/claude.md +0 -93
- package/docs/cli-commands/integration_guide.md +0 -371
- package/docs/cli-commands/quick_reference.md +0 -362
- package/docs/cli-commands/readme.md +0 -361
- package/docs/cli.md +0 -156
- package/docs/cloudkit_sync_guide.md +0 -495
- package/docs/codebase_review.md +0 -288
- package/docs/codex-oauth.md +0 -635
- package/docs/codex.md +0 -104
- package/docs/codexbar.png +0 -0
- package/docs/copilot.md +0 -44
- package/docs/cursor.md +0 -46
- package/docs/design/designcomparisonchart.md +0 -510
- package/docs/design/implementationguide.md +0 -792
- package/docs/design/menubaricondesignconcepts.md +0 -796
- package/docs/design/mockups/hybridiconconcept.svg +0 -62
- package/docs/design/mockups/infinityiconconcept.svg +0 -41
- package/docs/design/mockups/infinityiconstates.svg +0 -129
- package/docs/design/mockups/wavelogorefined.svg +0 -48
- package/docs/design/quick_summary.md +0 -426
- package/docs/design/readme.md +0 -478
- package/docs/design/sizespecifications.md +0 -575
- package/docs/design-system.md +0 -836
- package/docs/factory.md +0 -103
- package/docs/gemini.md +0 -73
- package/docs/icon.md +0 -37
- package/docs/icon.png +0 -0
- package/docs/index.html +0 -175
- package/docs/keychain_fix.md +0 -274
- package/docs/minimax.md +0 -71
- package/docs/packaging.md +0 -25
- package/docs/progress-bar-improvements.md +0 -240
- package/docs/progress-bar-quick-reference.md +0 -283
- package/docs/progress-bar-technical-details.md +0 -432
- package/docs/progress-bar-visual-test.swift +0 -375
- package/docs/provider.md +0 -202
- package/docs/providers.md +0 -86
- package/docs/refactor/macros.md +0 -35
- package/docs/refresh-loop.md +0 -22
- package/docs/releasing-homebrew.md +0 -35
- package/docs/releasing.md +0 -121
- package/docs/research/concrete_todo_2026-02-22.md +0 -47
- package/docs/research/core-feature-research.md +0 -248
- package/docs/research/feature_uiux_roadmap_2026-02-22.md +0 -202
- package/docs/research/parity_matrix.md +0 -49
- package/docs/research/provider_expansion_candidates_2026-02-22.md +0 -80
- package/docs/research/provider_model_token_project_matrix_2026-02-23.md +0 -46
- package/docs/research/release_gate_checklist.md +0 -57
- package/docs/site.css +0 -505
- package/docs/sparkle.md +0 -28
- package/docs/status.md +0 -28
- package/docs/sync/implementation.md +0 -393
- package/docs/sync/readme.md +0 -317
- package/docs/sync_files_summary.md +0 -391
- package/docs/sync_quick_reference.md +0 -225
- package/docs/todo.md +0 -11
- package/docs/ui.md +0 -29
- package/docs/web-integration.md +0 -9
- package/docs/widgets.md +0 -85
- package/docs/workflows/error-handling-improvements.md +0 -434
- package/docs/workflows/folder_structure.md +0 -299
- package/docs/workflows/loading_states.md +0 -446
- package/docs/workflows/loading_states_examples.md +0 -565
- package/docs/workflows/loading_states_flow.md +0 -430
- package/docs/workflows/performance_optimizations.md +0 -457
- package/docs/workflows/phase1-cli-testing-report.md +0 -313
- package/docs/workflows/quick_reference.md +0 -228
- package/docs/workflows/readme.md +0 -59
- package/docs/workflows/recent_improvements.md +0 -275
- package/docs/workflows/sync_enablement_summary.md +0 -400
- package/docs/zai.md +0 -43
- package/runic.png +0 -0
- package/version.env +0 -2
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import RunicCore
|
|
2
|
-
import Foundation
|
|
3
|
-
import Security
|
|
4
|
-
|
|
5
|
-
protocol MiniMaxTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum MiniMaxTokenStoreError: LocalizedError {
|
|
11
|
-
case keychainStatus(OSStatus)
|
|
12
|
-
case invalidData
|
|
13
|
-
|
|
14
|
-
var errorDescription: String? {
|
|
15
|
-
switch self {
|
|
16
|
-
case let .keychainStatus(status):
|
|
17
|
-
"Keychain error: \(status)"
|
|
18
|
-
case .invalidData:
|
|
19
|
-
"Keychain returned invalid data."
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
struct KeychainMiniMaxTokenStore: MiniMaxTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("minimax-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "minimax-api-token"
|
|
29
|
-
|
|
30
|
-
func loadToken() throws -> String? {
|
|
31
|
-
// Try standard keychain first.
|
|
32
|
-
if let token = try self.readToken(dataProtection: false) {
|
|
33
|
-
return token
|
|
34
|
-
}
|
|
35
|
-
// Migrate from the old Data Protection keychain if present.
|
|
36
|
-
if let token = try self.readToken(dataProtection: true) {
|
|
37
|
-
Self.log.info("Migrating MiniMax token from Data Protection keychain")
|
|
38
|
-
// Store first, delete old only on success — prevents data loss.
|
|
39
|
-
if (try? self.storeToken(token)) != nil {
|
|
40
|
-
try? self.deleteToken(dataProtection: true)
|
|
41
|
-
}
|
|
42
|
-
return token
|
|
43
|
-
}
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func storeToken(_ token: String?) throws {
|
|
48
|
-
let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
-
if cleaned == nil || cleaned?.isEmpty == true {
|
|
50
|
-
try? self.deleteToken(dataProtection: false)
|
|
51
|
-
try? self.deleteToken(dataProtection: true)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
|
|
57
|
-
// Always delete-then-add to ensure SecAccess ACL is set.
|
|
58
|
-
// SecItemUpdate does NOT update the ACL, so existing items with
|
|
59
|
-
// missing or stale ACLs would continue to prompt for passwords.
|
|
60
|
-
try? self.deleteToken(dataProtection: false)
|
|
61
|
-
try? self.deleteToken(dataProtection: true)
|
|
62
|
-
|
|
63
|
-
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
-
addQuery[kSecValueData as String] = data
|
|
65
|
-
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
69
|
-
guard addStatus == errSecSuccess else {
|
|
70
|
-
Self.log.error("Keychain add failed: \(addStatus)")
|
|
71
|
-
throw MiniMaxTokenStoreError.keychainStatus(addStatus)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MARK: - Private
|
|
76
|
-
|
|
77
|
-
private func readToken(dataProtection: Bool) throws -> String? {
|
|
78
|
-
var result: CFTypeRef?
|
|
79
|
-
var query = self.baseQuery(dataProtection: dataProtection)
|
|
80
|
-
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
81
|
-
query[kSecReturnData as String] = true
|
|
82
|
-
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
83
|
-
|
|
84
|
-
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
85
|
-
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
86
|
-
return nil
|
|
87
|
-
}
|
|
88
|
-
guard status == errSecSuccess else {
|
|
89
|
-
Self.log.error("Keychain read failed: \(status)")
|
|
90
|
-
throw MiniMaxTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw MiniMaxTokenStoreError.invalidData
|
|
94
|
-
}
|
|
95
|
-
let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
-
return (token?.isEmpty ?? true) ? nil : token
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func deleteToken(dataProtection: Bool) throws {
|
|
100
|
-
let query = self.baseQuery(dataProtection: dataProtection)
|
|
101
|
-
let status = SecItemDelete(query as CFDictionary)
|
|
102
|
-
if status == errSecSuccess || status == errSecItemNotFound { return }
|
|
103
|
-
Self.log.error("Keychain delete failed: \(status)")
|
|
104
|
-
throw MiniMaxTokenStoreError.keychainStatus(status)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private func baseQuery(dataProtection: Bool) -> [String: Any] {
|
|
108
|
-
var q: [String: Any] = [
|
|
109
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
110
|
-
kSecAttrService as String: self.service,
|
|
111
|
-
kSecAttrAccount as String: self.account,
|
|
112
|
-
]
|
|
113
|
-
if dataProtection {
|
|
114
|
-
q[kSecUseDataProtectionKeychain as String] = true
|
|
115
|
-
}
|
|
116
|
-
return q
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import RunicCore
|
|
2
|
-
import Foundation
|
|
3
|
-
import Security
|
|
4
|
-
|
|
5
|
-
protocol MistralTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum MistralTokenStoreError: LocalizedError {
|
|
11
|
-
case keychainStatus(OSStatus)
|
|
12
|
-
case invalidData
|
|
13
|
-
|
|
14
|
-
var errorDescription: String? {
|
|
15
|
-
switch self {
|
|
16
|
-
case let .keychainStatus(status):
|
|
17
|
-
"Keychain error: \(status)"
|
|
18
|
-
case .invalidData:
|
|
19
|
-
"Keychain returned invalid data."
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
struct KeychainMistralTokenStore: MistralTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("mistral-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "mistral-api-token"
|
|
29
|
-
|
|
30
|
-
func loadToken() throws -> String? {
|
|
31
|
-
// Try standard keychain first.
|
|
32
|
-
if let token = try self.readToken(dataProtection: false) {
|
|
33
|
-
return token
|
|
34
|
-
}
|
|
35
|
-
// Migrate from the old Data Protection keychain if present.
|
|
36
|
-
if let token = try self.readToken(dataProtection: true) {
|
|
37
|
-
Self.log.info("Migrating Mistral token from Data Protection keychain")
|
|
38
|
-
// Store first, delete old only on success — prevents data loss.
|
|
39
|
-
if (try? self.storeToken(token)) != nil {
|
|
40
|
-
try? self.deleteToken(dataProtection: true)
|
|
41
|
-
}
|
|
42
|
-
return token
|
|
43
|
-
}
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func storeToken(_ token: String?) throws {
|
|
48
|
-
let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
-
if cleaned == nil || cleaned?.isEmpty == true {
|
|
50
|
-
try? self.deleteToken(dataProtection: false)
|
|
51
|
-
try? self.deleteToken(dataProtection: true)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
|
|
57
|
-
// Always delete-then-add to ensure SecAccess ACL is set.
|
|
58
|
-
// SecItemUpdate does NOT update the ACL, so existing items with
|
|
59
|
-
// missing or stale ACLs would continue to prompt for passwords.
|
|
60
|
-
try? self.deleteToken(dataProtection: false)
|
|
61
|
-
try? self.deleteToken(dataProtection: true)
|
|
62
|
-
|
|
63
|
-
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
-
addQuery[kSecValueData as String] = data
|
|
65
|
-
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
69
|
-
guard addStatus == errSecSuccess else {
|
|
70
|
-
Self.log.error("Keychain add failed: \(addStatus)")
|
|
71
|
-
throw MistralTokenStoreError.keychainStatus(addStatus)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MARK: - Private
|
|
76
|
-
|
|
77
|
-
private func readToken(dataProtection: Bool) throws -> String? {
|
|
78
|
-
var result: CFTypeRef?
|
|
79
|
-
var query = self.baseQuery(dataProtection: dataProtection)
|
|
80
|
-
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
81
|
-
query[kSecReturnData as String] = true
|
|
82
|
-
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
83
|
-
|
|
84
|
-
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
85
|
-
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
86
|
-
return nil
|
|
87
|
-
}
|
|
88
|
-
guard status == errSecSuccess else {
|
|
89
|
-
Self.log.error("Keychain read failed: \(status)")
|
|
90
|
-
throw MistralTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw MistralTokenStoreError.invalidData
|
|
94
|
-
}
|
|
95
|
-
let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
-
return (token?.isEmpty ?? true) ? nil : token
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func deleteToken(dataProtection: Bool) throws {
|
|
100
|
-
let query = self.baseQuery(dataProtection: dataProtection)
|
|
101
|
-
let status = SecItemDelete(query as CFDictionary)
|
|
102
|
-
if status == errSecSuccess || status == errSecItemNotFound { return }
|
|
103
|
-
Self.log.error("Keychain delete failed: \(status)")
|
|
104
|
-
throw MistralTokenStoreError.keychainStatus(status)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private func baseQuery(dataProtection: Bool) -> [String: Any] {
|
|
108
|
-
var q: [String: Any] = [
|
|
109
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
110
|
-
kSecAttrService as String: self.service,
|
|
111
|
-
kSecAttrAccount as String: self.account,
|
|
112
|
-
]
|
|
113
|
-
if dataProtection {
|
|
114
|
-
q[kSecUseDataProtectionKeychain as String] = true
|
|
115
|
-
}
|
|
116
|
-
return q
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import RunicCore
|
|
2
|
-
import Foundation
|
|
3
|
-
import Security
|
|
4
|
-
|
|
5
|
-
protocol OpenRouterTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum OpenRouterTokenStoreError: LocalizedError {
|
|
11
|
-
case keychainStatus(OSStatus)
|
|
12
|
-
case invalidData
|
|
13
|
-
|
|
14
|
-
var errorDescription: String? {
|
|
15
|
-
switch self {
|
|
16
|
-
case let .keychainStatus(status):
|
|
17
|
-
"Keychain error: \(status)"
|
|
18
|
-
case .invalidData:
|
|
19
|
-
"Keychain returned invalid data."
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
struct KeychainOpenRouterTokenStore: OpenRouterTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("openrouter-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "openrouter-api-token"
|
|
29
|
-
|
|
30
|
-
func loadToken() throws -> String? {
|
|
31
|
-
// Try standard keychain first.
|
|
32
|
-
if let token = try self.readToken(dataProtection: false) {
|
|
33
|
-
return token
|
|
34
|
-
}
|
|
35
|
-
// Migrate from the old Data Protection keychain if present.
|
|
36
|
-
if let token = try self.readToken(dataProtection: true) {
|
|
37
|
-
Self.log.info("Migrating OpenRouter token from Data Protection keychain")
|
|
38
|
-
// Store first, delete old only on success — prevents data loss.
|
|
39
|
-
if (try? self.storeToken(token)) != nil {
|
|
40
|
-
try? self.deleteToken(dataProtection: true)
|
|
41
|
-
}
|
|
42
|
-
return token
|
|
43
|
-
}
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func storeToken(_ token: String?) throws {
|
|
48
|
-
let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
-
if cleaned == nil || cleaned?.isEmpty == true {
|
|
50
|
-
try? self.deleteToken(dataProtection: false)
|
|
51
|
-
try? self.deleteToken(dataProtection: true)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
|
|
57
|
-
// Always delete-then-add to ensure SecAccess ACL is set.
|
|
58
|
-
// SecItemUpdate does NOT update the ACL, so existing items with
|
|
59
|
-
// missing or stale ACLs would continue to prompt for passwords.
|
|
60
|
-
try? self.deleteToken(dataProtection: false)
|
|
61
|
-
try? self.deleteToken(dataProtection: true)
|
|
62
|
-
|
|
63
|
-
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
-
addQuery[kSecValueData as String] = data
|
|
65
|
-
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
69
|
-
guard addStatus == errSecSuccess else {
|
|
70
|
-
Self.log.error("Keychain add failed: \(addStatus)")
|
|
71
|
-
throw OpenRouterTokenStoreError.keychainStatus(addStatus)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MARK: - Private
|
|
76
|
-
|
|
77
|
-
private func readToken(dataProtection: Bool) throws -> String? {
|
|
78
|
-
var result: CFTypeRef?
|
|
79
|
-
var query = self.baseQuery(dataProtection: dataProtection)
|
|
80
|
-
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
81
|
-
query[kSecReturnData as String] = true
|
|
82
|
-
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
83
|
-
|
|
84
|
-
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
85
|
-
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
86
|
-
return nil
|
|
87
|
-
}
|
|
88
|
-
guard status == errSecSuccess else {
|
|
89
|
-
Self.log.error("Keychain read failed: \(status)")
|
|
90
|
-
throw OpenRouterTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw OpenRouterTokenStoreError.invalidData
|
|
94
|
-
}
|
|
95
|
-
let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
-
return (token?.isEmpty ?? true) ? nil : token
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func deleteToken(dataProtection: Bool) throws {
|
|
100
|
-
let query = self.baseQuery(dataProtection: dataProtection)
|
|
101
|
-
let status = SecItemDelete(query as CFDictionary)
|
|
102
|
-
if status == errSecSuccess || status == errSecItemNotFound { return }
|
|
103
|
-
Self.log.error("Keychain delete failed: \(status)")
|
|
104
|
-
throw OpenRouterTokenStoreError.keychainStatus(status)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private func baseQuery(dataProtection: Bool) -> [String: Any] {
|
|
108
|
-
var q: [String: Any] = [
|
|
109
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
110
|
-
kSecAttrService as String: self.service,
|
|
111
|
-
kSecAttrAccount as String: self.account,
|
|
112
|
-
]
|
|
113
|
-
if dataProtection {
|
|
114
|
-
q[kSecUseDataProtectionKeychain as String] = true
|
|
115
|
-
}
|
|
116
|
-
return q
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import RunicCore
|
|
2
|
-
import Foundation
|
|
3
|
-
import Security
|
|
4
|
-
|
|
5
|
-
protocol PerplexityTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum PerplexityTokenStoreError: LocalizedError {
|
|
11
|
-
case keychainStatus(OSStatus)
|
|
12
|
-
case invalidData
|
|
13
|
-
|
|
14
|
-
var errorDescription: String? {
|
|
15
|
-
switch self {
|
|
16
|
-
case let .keychainStatus(status):
|
|
17
|
-
"Keychain error: \(status)"
|
|
18
|
-
case .invalidData:
|
|
19
|
-
"Keychain returned invalid data."
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
struct KeychainPerplexityTokenStore: PerplexityTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("perplexity-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "perplexity-api-token"
|
|
29
|
-
|
|
30
|
-
func loadToken() throws -> String? {
|
|
31
|
-
// Try standard keychain first.
|
|
32
|
-
if let token = try self.readToken(dataProtection: false) {
|
|
33
|
-
return token
|
|
34
|
-
}
|
|
35
|
-
// Migrate from the old Data Protection keychain if present.
|
|
36
|
-
if let token = try self.readToken(dataProtection: true) {
|
|
37
|
-
Self.log.info("Migrating Perplexity token from Data Protection keychain")
|
|
38
|
-
// Store first, delete old only on success — prevents data loss.
|
|
39
|
-
if (try? self.storeToken(token)) != nil {
|
|
40
|
-
try? self.deleteToken(dataProtection: true)
|
|
41
|
-
}
|
|
42
|
-
return token
|
|
43
|
-
}
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func storeToken(_ token: String?) throws {
|
|
48
|
-
let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
-
if cleaned == nil || cleaned?.isEmpty == true {
|
|
50
|
-
try? self.deleteToken(dataProtection: false)
|
|
51
|
-
try? self.deleteToken(dataProtection: true)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
|
|
57
|
-
// Always delete-then-add to ensure SecAccess ACL is set.
|
|
58
|
-
// SecItemUpdate does NOT update the ACL, so existing items with
|
|
59
|
-
// missing or stale ACLs would continue to prompt for passwords.
|
|
60
|
-
try? self.deleteToken(dataProtection: false)
|
|
61
|
-
try? self.deleteToken(dataProtection: true)
|
|
62
|
-
|
|
63
|
-
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
-
addQuery[kSecValueData as String] = data
|
|
65
|
-
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
69
|
-
guard addStatus == errSecSuccess else {
|
|
70
|
-
Self.log.error("Keychain add failed: \(addStatus)")
|
|
71
|
-
throw PerplexityTokenStoreError.keychainStatus(addStatus)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MARK: - Private
|
|
76
|
-
|
|
77
|
-
private func readToken(dataProtection: Bool) throws -> String? {
|
|
78
|
-
var result: CFTypeRef?
|
|
79
|
-
var query = self.baseQuery(dataProtection: dataProtection)
|
|
80
|
-
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
81
|
-
query[kSecReturnData as String] = true
|
|
82
|
-
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
83
|
-
|
|
84
|
-
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
85
|
-
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
86
|
-
return nil
|
|
87
|
-
}
|
|
88
|
-
guard status == errSecSuccess else {
|
|
89
|
-
Self.log.error("Keychain read failed: \(status)")
|
|
90
|
-
throw PerplexityTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw PerplexityTokenStoreError.invalidData
|
|
94
|
-
}
|
|
95
|
-
let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
-
return (token?.isEmpty ?? true) ? nil : token
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func deleteToken(dataProtection: Bool) throws {
|
|
100
|
-
let query = self.baseQuery(dataProtection: dataProtection)
|
|
101
|
-
let status = SecItemDelete(query as CFDictionary)
|
|
102
|
-
if status == errSecSuccess || status == errSecItemNotFound { return }
|
|
103
|
-
Self.log.error("Keychain delete failed: \(status)")
|
|
104
|
-
throw PerplexityTokenStoreError.keychainStatus(status)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private func baseQuery(dataProtection: Bool) -> [String: Any] {
|
|
108
|
-
var q: [String: Any] = [
|
|
109
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
110
|
-
kSecAttrService as String: self.service,
|
|
111
|
-
kSecAttrAccount as String: self.account,
|
|
112
|
-
]
|
|
113
|
-
if dataProtection {
|
|
114
|
-
q[kSecUseDataProtectionKeychain as String] = true
|
|
115
|
-
}
|
|
116
|
-
return q
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import RunicCore
|
|
2
|
-
import Foundation
|
|
3
|
-
|
|
4
|
-
struct ProviderToggleStore {
|
|
5
|
-
private let userDefaults: UserDefaults
|
|
6
|
-
private let key = "providerToggles"
|
|
7
|
-
|
|
8
|
-
init(userDefaults: UserDefaults = .standard) {
|
|
9
|
-
self.userDefaults = userDefaults
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
func isEnabled(metadata: ProviderMetadata) -> Bool {
|
|
13
|
-
self.load()[metadata.cliName] ?? metadata.defaultEnabled
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
func setEnabled(_ enabled: Bool, metadata: ProviderMetadata) {
|
|
17
|
-
var toggles = self.load()
|
|
18
|
-
toggles[metadata.cliName] = enabled
|
|
19
|
-
self.userDefaults.set(toggles, forKey: self.key)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
private func load() -> [String: Bool] {
|
|
23
|
-
(self.userDefaults.dictionary(forKey: self.key) as? [String: Bool]) ?? [:]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
func purgeLegacyKeys() {
|
|
27
|
-
self.userDefaults.removeObject(forKey: "showCodexUsage")
|
|
28
|
-
self.userDefaults.removeObject(forKey: "showClaudeUsage")
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import RunicCore
|
|
2
|
-
import Foundation
|
|
3
|
-
import Security
|
|
4
|
-
|
|
5
|
-
protocol QwenTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum QwenTokenStoreError: LocalizedError {
|
|
11
|
-
case keychainStatus(OSStatus)
|
|
12
|
-
case invalidData
|
|
13
|
-
|
|
14
|
-
var errorDescription: String? {
|
|
15
|
-
switch self {
|
|
16
|
-
case let .keychainStatus(status):
|
|
17
|
-
"Keychain error: \(status)"
|
|
18
|
-
case .invalidData:
|
|
19
|
-
"Keychain returned invalid data."
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
struct KeychainQwenTokenStore: QwenTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("qwen-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "qwen-api-token"
|
|
29
|
-
|
|
30
|
-
func loadToken() throws -> String? {
|
|
31
|
-
// Try standard keychain first.
|
|
32
|
-
if let token = try self.readToken(dataProtection: false) {
|
|
33
|
-
return token
|
|
34
|
-
}
|
|
35
|
-
// Migrate from the old Data Protection keychain if present.
|
|
36
|
-
if let token = try self.readToken(dataProtection: true) {
|
|
37
|
-
Self.log.info("Migrating Qwen token from Data Protection keychain")
|
|
38
|
-
// Store first, delete old only on success — prevents data loss.
|
|
39
|
-
if (try? self.storeToken(token)) != nil {
|
|
40
|
-
try? self.deleteToken(dataProtection: true)
|
|
41
|
-
}
|
|
42
|
-
return token
|
|
43
|
-
}
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func storeToken(_ token: String?) throws {
|
|
48
|
-
let cleaned = token?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
-
if cleaned == nil || cleaned?.isEmpty == true {
|
|
50
|
-
try? self.deleteToken(dataProtection: false)
|
|
51
|
-
try? self.deleteToken(dataProtection: true)
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
|
|
57
|
-
// Always delete-then-add to ensure SecAccess ACL is set.
|
|
58
|
-
// SecItemUpdate does NOT update the ACL, so existing items with
|
|
59
|
-
// missing or stale ACLs would continue to prompt for passwords.
|
|
60
|
-
try? self.deleteToken(dataProtection: false)
|
|
61
|
-
try? self.deleteToken(dataProtection: true)
|
|
62
|
-
|
|
63
|
-
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
-
addQuery[kSecValueData as String] = data
|
|
65
|
-
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
69
|
-
guard addStatus == errSecSuccess else {
|
|
70
|
-
Self.log.error("Keychain add failed: \(addStatus)")
|
|
71
|
-
throw QwenTokenStoreError.keychainStatus(addStatus)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MARK: - Private
|
|
76
|
-
|
|
77
|
-
private func readToken(dataProtection: Bool) throws -> String? {
|
|
78
|
-
var result: CFTypeRef?
|
|
79
|
-
var query = self.baseQuery(dataProtection: dataProtection)
|
|
80
|
-
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
81
|
-
query[kSecReturnData as String] = true
|
|
82
|
-
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
83
|
-
|
|
84
|
-
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
85
|
-
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
86
|
-
return nil
|
|
87
|
-
}
|
|
88
|
-
guard status == errSecSuccess else {
|
|
89
|
-
Self.log.error("Keychain read failed: \(status)")
|
|
90
|
-
throw QwenTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw QwenTokenStoreError.invalidData
|
|
94
|
-
}
|
|
95
|
-
let token = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
-
return (token?.isEmpty ?? true) ? nil : token
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func deleteToken(dataProtection: Bool) throws {
|
|
100
|
-
let query = self.baseQuery(dataProtection: dataProtection)
|
|
101
|
-
let status = SecItemDelete(query as CFDictionary)
|
|
102
|
-
if status == errSecSuccess || status == errSecItemNotFound { return }
|
|
103
|
-
Self.log.error("Keychain delete failed: \(status)")
|
|
104
|
-
throw QwenTokenStoreError.keychainStatus(status)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private func baseQuery(dataProtection: Bool) -> [String: Any] {
|
|
108
|
-
var q: [String: Any] = [
|
|
109
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
110
|
-
kSecAttrService as String: self.service,
|
|
111
|
-
kSecAttrAccount as String: self.account,
|
|
112
|
-
]
|
|
113
|
-
if dataProtection {
|
|
114
|
-
q[kSecUseDataProtectionKeychain as String] = true
|
|
115
|
-
}
|
|
116
|
-
return q
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
}
|