@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 FireworksTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum FireworksTokenStoreError: 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 KeychainFireworksTokenStore: FireworksTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("fireworks-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "fireworks-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 Fireworks 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 FireworksTokenStoreError.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 FireworksTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw FireworksTokenStoreError.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 FireworksTokenStoreError.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 GroqTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum GroqTokenStoreError: 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 KeychainGroqTokenStore: GroqTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("groq-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "groq-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 Groq 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 GroqTokenStoreError.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 GroqTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw GroqTokenStoreError.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 GroqTokenStoreError.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 KimiTokenStoring: Sendable {
|
|
6
|
-
func loadToken() throws -> String?
|
|
7
|
-
func storeToken(_ token: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum KimiTokenStoreError: 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 KeychainKimiTokenStore: KimiTokenStoring {
|
|
25
|
-
private static let log = RunicLog.logger("kimi-token-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "kimi-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 Kimi 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 KimiTokenStoreError.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 KimiTokenStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw KimiTokenStoreError.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 KimiTokenStoreError.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 MiniMaxCookieHeaderStoring: Sendable {
|
|
6
|
-
func loadHeader() throws -> String?
|
|
7
|
-
func storeHeader(_ header: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum MiniMaxCookieHeaderStoreError: 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 KeychainMiniMaxCookieHeaderStore: MiniMaxCookieHeaderStoring {
|
|
25
|
-
private static let log = RunicLog.logger("minimax-cookie-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "minimax-cookie-header"
|
|
29
|
-
|
|
30
|
-
func loadHeader() throws -> String? {
|
|
31
|
-
// Try standard keychain first.
|
|
32
|
-
if let header = try self.readHeader(dataProtection: false) {
|
|
33
|
-
return header
|
|
34
|
-
}
|
|
35
|
-
// Migrate from the old Data Protection keychain if present.
|
|
36
|
-
if let header = try self.readHeader(dataProtection: true) {
|
|
37
|
-
Self.log.info("Migrating MiniMax cookie header from Data Protection keychain")
|
|
38
|
-
// Store first, delete old only on success — prevents data loss.
|
|
39
|
-
if (try? self.storeHeader(header)) != nil {
|
|
40
|
-
try? self.deleteHeader(dataProtection: true)
|
|
41
|
-
}
|
|
42
|
-
return header
|
|
43
|
-
}
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func storeHeader(_ header: String?) throws {
|
|
48
|
-
let cleaned = header?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
-
if cleaned == nil || cleaned?.isEmpty == true {
|
|
50
|
-
try? self.deleteHeader(dataProtection: false)
|
|
51
|
-
try? self.deleteHeader(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.deleteHeader(dataProtection: false)
|
|
61
|
-
try? self.deleteHeader(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 MiniMaxCookieHeaderStoreError.keychainStatus(addStatus)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MARK: - Private
|
|
76
|
-
|
|
77
|
-
private func readHeader(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 MiniMaxCookieHeaderStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw MiniMaxCookieHeaderStoreError.invalidData
|
|
94
|
-
}
|
|
95
|
-
let header = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
-
return (header?.isEmpty ?? true) ? nil : header
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func deleteHeader(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 MiniMaxCookieHeaderStoreError.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 MiniMaxGroupIDStoring: Sendable {
|
|
6
|
-
func loadGroupID() throws -> String?
|
|
7
|
-
func storeGroupID(_ groupID: String?) throws
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
enum MiniMaxGroupIDStoreError: 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 KeychainMiniMaxGroupIDStore: MiniMaxGroupIDStoring {
|
|
25
|
-
private static let log = RunicLog.logger("minimax-groupid-store")
|
|
26
|
-
|
|
27
|
-
private let service = "com.sriinnu.athena.Runic"
|
|
28
|
-
private let account = "minimax-group-id"
|
|
29
|
-
|
|
30
|
-
func loadGroupID() throws -> String? {
|
|
31
|
-
// Try standard keychain first.
|
|
32
|
-
if let groupID = try self.readGroupID(dataProtection: false) {
|
|
33
|
-
return groupID
|
|
34
|
-
}
|
|
35
|
-
// Migrate from the old Data Protection keychain if present.
|
|
36
|
-
if let groupID = try self.readGroupID(dataProtection: true) {
|
|
37
|
-
Self.log.info("Migrating MiniMax group ID from Data Protection keychain")
|
|
38
|
-
// Store first, delete old only on success — prevents data loss.
|
|
39
|
-
if (try? self.storeGroupID(groupID)) != nil {
|
|
40
|
-
try? self.deleteGroupID(dataProtection: true)
|
|
41
|
-
}
|
|
42
|
-
return groupID
|
|
43
|
-
}
|
|
44
|
-
return nil
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
func storeGroupID(_ groupID: String?) throws {
|
|
48
|
-
let cleaned = groupID?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
-
if cleaned == nil || cleaned?.isEmpty == true {
|
|
50
|
-
try? self.deleteGroupID(dataProtection: false)
|
|
51
|
-
try? self.deleteGroupID(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.deleteGroupID(dataProtection: false)
|
|
61
|
-
try? self.deleteGroupID(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 MiniMaxGroupIDStoreError.keychainStatus(addStatus)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// MARK: - Private
|
|
76
|
-
|
|
77
|
-
private func readGroupID(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 MiniMaxGroupIDStoreError.keychainStatus(status)
|
|
91
|
-
}
|
|
92
|
-
guard let data = result as? Data else {
|
|
93
|
-
throw MiniMaxGroupIDStoreError.invalidData
|
|
94
|
-
}
|
|
95
|
-
let groupID = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
-
return (groupID?.isEmpty ?? true) ? nil : groupID
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func deleteGroupID(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 MiniMaxGroupIDStoreError.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
|
-
}
|