@sriinnu/runic 1.0.0 → 2.0.0
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 +11 -0
- package/LICENSE +1 -1
- package/README.md +47 -29
- package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
- package/RuniciOS/Views/ProviderDetailView.swift +9 -9
- package/RuniciOS/Views/ProviderListView.swift +10 -10
- package/RuniciOS/Widgets/LargeWidget.swift +14 -14
- package/RuniciOS/Widgets/LiveActivity.swift +19 -19
- package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
- package/RuniciOS/Widgets/MediumWidget.swift +10 -10
- package/RuniciOS/Widgets/RunicWidget.swift +5 -5
- package/RuniciOS/Widgets/SmallWidget.swift +9 -9
- package/Scripts/package_app.sh +2 -2
- package/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +17 -4
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +47 -14
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
- package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
- package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +3 -6
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
- package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
- package/Sources/Runic/Core/RunicColors.swift +6 -6
- package/Sources/Runic/Core/RunicSpacing.swift +7 -7
- package/Sources/Runic/Core/RunicTypography.swift +206 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SettingsStore.swift +99 -52
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore.swift +234 -167
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +10 -36
- package/Sources/Runic/Core/UsageExporter.swift +8 -4
- package/Sources/Runic/ProviderRegistry.swift +1 -1
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
- package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/provider-context-windows.json +20 -0
- package/Sources/Runic/RunicApp.swift +46 -36
- package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
- package/Sources/Runic/UsagePaceText.swift +1 -1
- package/Sources/Runic/UsageProgressBar.swift +13 -9
- package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
- package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +15 -20
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
- package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
- package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +198 -58
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +28 -76
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +108 -33
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
- package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
- package/Sources/RunicCLI/CLIEntry.swift +83 -42
- package/Sources/RunicCLI/CLIRenderer.swift +1 -1
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
- package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
- package/Sources/RunicClaudeWebProbe/main.swift +1 -1
- package/Sources/RunicCore/AlertRuleStore.swift +20 -20
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
- package/Sources/RunicCore/CostUsageModels.swift +1 -1
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +80 -71
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +6 -6
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
- package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
- package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +1 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +12 -1
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +1 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +3 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +6 -2
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +8 -11
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +25 -16
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +27 -16
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +51 -30
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
- package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
- package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
- package/Sources/RunicCore/Team/TeamModels.swift +111 -110
- package/Sources/RunicCore/Team/TeamStore.swift +126 -109
- package/Sources/RunicCore/UsageFetcher.swift +2 -2
- package/Sources/RunicCore/UsageFormatter.swift +25 -25
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +141 -29
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +59 -5
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +161 -0
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
- package/Tests/RunicTests/AppDelegateTests.swift +8 -3
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
- package/Tests/RunicTests/CLICostTests.swift +6 -7
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
- package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
- package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
- package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
- package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
- package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
- package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
- package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
- package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
- package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
- package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
- package/Tests/RunicTests/InstallOriginTests.swift +1 -2
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
- package/Tests/RunicTests/LiveAccountTests.swift +3 -3
- package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
- package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
- package/Tests/RunicTests/PathBuilderTests.swift +14 -15
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
- package/Tests/RunicTests/RunicTests.swift +6 -7
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
- package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
- package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
- package/Tests/RunicTests/StatusMenuTests.swift +63 -22
- package/Tests/RunicTests/StatusProbeTests.swift +28 -29
- package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
- package/Tests/RunicTests/TextParsingTests.swift +1 -2
- package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
- package/Tests/RunicTests/UsagePaceTests.swift +5 -6
- package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
- package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
- package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
- package/appcast.xml +36 -37
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol CerebrasTokenStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainCerebrasTokenStore: CerebrasTokenStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteToken(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainCerebrasTokenStore: CerebrasTokenStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainCerebrasTokenStore: CerebrasTokenStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol CohereTokenStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainCohereTokenStore: CohereTokenStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteToken(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainCohereTokenStore: CohereTokenStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainCohereTokenStore: CohereTokenStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol CopilotTokenStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainCopilotTokenStore: CopilotTokenStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteToken(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainCopilotTokenStore: CopilotTokenStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainCopilotTokenStore: CopilotTokenStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|
|
@@ -12,20 +12,20 @@ enum CustomProviderStoreError: LocalizedError {
|
|
|
12
12
|
var errorDescription: String? {
|
|
13
13
|
switch self {
|
|
14
14
|
case let .providerNotFound(id):
|
|
15
|
-
|
|
15
|
+
"Provider with ID '\(id)' not found."
|
|
16
16
|
case let .duplicateProvider(id):
|
|
17
|
-
|
|
17
|
+
"Provider with ID '\(id)' already exists."
|
|
18
18
|
case .invalidData:
|
|
19
|
-
|
|
19
|
+
"Invalid provider data."
|
|
20
20
|
case let .fileSystemError(message):
|
|
21
|
-
|
|
21
|
+
"File system error: \(message)"
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// MARK: - Store
|
|
27
27
|
|
|
28
|
-
public
|
|
28
|
+
public enum CustomProviderStore {
|
|
29
29
|
private static let log = RunicLog.logger("custom-provider-store")
|
|
30
30
|
|
|
31
31
|
// MARK: - Storage Location
|
|
@@ -41,8 +41,8 @@ public struct CustomProviderStore {
|
|
|
41
41
|
|
|
42
42
|
/// Load all custom providers from disk.
|
|
43
43
|
public static func load() -> CustomProvidersData {
|
|
44
|
-
guard FileManager.default.fileExists(atPath: storageURL.path) else {
|
|
45
|
-
|
|
44
|
+
guard FileManager.default.fileExists(atPath: self.storageURL.path) else {
|
|
45
|
+
self.log.info("No custom providers file found, returning empty data.")
|
|
46
46
|
return CustomProvidersData()
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -54,7 +54,7 @@ public struct CustomProviderStore {
|
|
|
54
54
|
Self.log.info("Loaded \(providersData.providers.count) custom provider(s).")
|
|
55
55
|
return providersData
|
|
56
56
|
} catch {
|
|
57
|
-
|
|
57
|
+
self.log.error("Failed to load custom providers: \(error)")
|
|
58
58
|
return CustomProvidersData()
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -67,7 +67,7 @@ public struct CustomProviderStore {
|
|
|
67
67
|
|
|
68
68
|
do {
|
|
69
69
|
let data = try encoder.encode(providersData)
|
|
70
|
-
try data.write(to: storageURL, options: .atomic)
|
|
70
|
+
try data.write(to: self.storageURL, options: .atomic)
|
|
71
71
|
Self.log.info("Saved \(providersData.providers.count) custom provider(s).")
|
|
72
72
|
} catch {
|
|
73
73
|
Self.log.error("Failed to save custom providers: \(error)")
|
|
@@ -153,6 +153,6 @@ public struct CustomProviderStore {
|
|
|
153
153
|
/// - Returns: Array of enabled custom provider configurations.
|
|
154
154
|
public static func getEnabledProviders() -> [CustomProviderConfig] {
|
|
155
155
|
let data = Self.load()
|
|
156
|
-
return data.providers.filter
|
|
156
|
+
return data.providers.filter(\.enabled)
|
|
157
157
|
}
|
|
158
158
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol DeepSeekTokenStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainDeepSeekTokenStore: DeepSeekTokenStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteToken(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainDeepSeekTokenStore: DeepSeekTokenStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainDeepSeekTokenStore: DeepSeekTokenStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol FireworksTokenStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainFireworksTokenStore: FireworksTokenStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteToken(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainFireworksTokenStore: FireworksTokenStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainFireworksTokenStore: FireworksTokenStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol GroqTokenStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainGroqTokenStore: GroqTokenStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteToken(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainGroqTokenStore: GroqTokenStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainGroqTokenStore: GroqTokenStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol KimiTokenStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainKimiTokenStore: KimiTokenStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteToken(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainKimiTokenStore: KimiTokenStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainKimiTokenStore: KimiTokenStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol MiniMaxCookieHeaderStoring: Sendable {
|
|
@@ -53,29 +53,16 @@ struct KeychainMiniMaxCookieHeaderStore: MiniMaxCookieHeaderStoring {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let data = cleaned!.data(using: .utf8)!
|
|
56
|
-
let query = self.baseQuery(dataProtection: false)
|
|
57
|
-
let attributes: [String: Any] = [
|
|
58
|
-
kSecValueData as String: data,
|
|
59
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
// Try update first.
|
|
63
|
-
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
|
64
|
-
if updateStatus == errSecSuccess { return }
|
|
65
|
-
|
|
66
|
-
if updateStatus != errSecItemNotFound {
|
|
67
|
-
// Item exists but update failed — delete and re-add to reset ACL.
|
|
68
|
-
try? self.deleteHeader(dataProtection: false)
|
|
69
|
-
}
|
|
70
56
|
|
|
71
|
-
|
|
72
|
-
|
|
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)
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
addQuery[kSecAttrAccess as String] = access
|
|
78
|
-
}
|
|
63
|
+
var addQuery = self.baseQuery(dataProtection: false)
|
|
64
|
+
addQuery[kSecValueData as String] = data
|
|
65
|
+
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
79
66
|
|
|
80
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
81
68
|
guard addStatus == errSecSuccess else {
|
|
@@ -91,6 +78,7 @@ struct KeychainMiniMaxCookieHeaderStore: MiniMaxCookieHeaderStoring {
|
|
|
91
78
|
var query = self.baseQuery(dataProtection: dataProtection)
|
|
92
79
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
|
93
80
|
query[kSecReturnData as String] = true
|
|
81
|
+
query[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail
|
|
94
82
|
|
|
95
83
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
96
84
|
if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
|
|
@@ -126,18 +114,4 @@ struct KeychainMiniMaxCookieHeaderStore: MiniMaxCookieHeaderStoring {
|
|
|
126
114
|
}
|
|
127
115
|
return q
|
|
128
116
|
}
|
|
129
|
-
|
|
130
|
-
/// Creates a SecAccess that grants the calling application permanent
|
|
131
|
-
/// read/write access without triggering the macOS Keychain password dialog.
|
|
132
|
-
private static func createSelfAccess() -> SecAccess? {
|
|
133
|
-
var trustedSelf: SecTrustedApplication?
|
|
134
|
-
guard SecTrustedApplicationCreateFromPath(nil, &trustedSelf) == errSecSuccess,
|
|
135
|
-
let trustedSelf else { return nil }
|
|
136
|
-
var access: SecAccess?
|
|
137
|
-
guard SecAccessCreate(
|
|
138
|
-
"Runic API Token" as CFString,
|
|
139
|
-
[trustedSelf] as CFArray,
|
|
140
|
-
&access) == errSecSuccess else { return nil }
|
|
141
|
-
return access
|
|
142
|
-
}
|
|
143
117
|
}
|