@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
|
@@ -33,7 +33,7 @@ public actor GenericProviderFetcher {
|
|
|
33
33
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
34
34
|
|
|
35
35
|
// Add auth headers
|
|
36
|
-
try await addAuthHeaders(to: &request)
|
|
36
|
+
try await self.addAuthHeaders(to: &request)
|
|
37
37
|
|
|
38
38
|
// 3. Make request
|
|
39
39
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
@@ -52,7 +52,7 @@ public actor GenericProviderFetcher {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// 5. Extract fields using mapping
|
|
55
|
-
return try extractUsageData(from: json, mapping: endpoint.mapping)
|
|
55
|
+
return try self.extractUsageData(from: json, mapping: endpoint.mapping)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/// Fetch balance data from the custom provider
|
|
@@ -70,7 +70,7 @@ public actor GenericProviderFetcher {
|
|
|
70
70
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
71
71
|
|
|
72
72
|
// Add auth headers
|
|
73
|
-
try await addAuthHeaders(to: &request)
|
|
73
|
+
try await self.addAuthHeaders(to: &request)
|
|
74
74
|
|
|
75
75
|
// 3. Make request
|
|
76
76
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
@@ -89,34 +89,31 @@ public actor GenericProviderFetcher {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
// 5. Extract fields using mapping
|
|
92
|
-
return try extractBalanceData(from: json, mapping: endpoint.mapping)
|
|
92
|
+
return try self.extractBalanceData(from: json, mapping: endpoint.mapping)
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/// Convert usage data to Runic's UsageSnapshot format
|
|
96
96
|
public func toUsageSnapshot(_ data: UsageData) -> UsageSnapshot {
|
|
97
|
-
let usedPercent: Double
|
|
98
|
-
|
|
99
|
-
usedPercent = (used / quota) * 100.0
|
|
97
|
+
let usedPercent: Double = if let quota = data.quota, let used = data.used, quota > 0 {
|
|
98
|
+
(used / quota) * 100.0
|
|
100
99
|
} else if let remaining = data.remaining, let quota = data.quota, quota > 0 {
|
|
101
|
-
|
|
100
|
+
((quota - remaining) / quota) * 100.0
|
|
102
101
|
} else {
|
|
103
|
-
|
|
102
|
+
0
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
let primary = RateWindow(
|
|
107
106
|
usedPercent: max(0, min(100, usedPercent)),
|
|
108
107
|
windowMinutes: nil,
|
|
109
108
|
resetsAt: data.resetDate,
|
|
110
|
-
resetDescription: data.resetDate.map { resetDescription(from: $0) }
|
|
111
|
-
)
|
|
109
|
+
resetDescription: data.resetDate.map { self.resetDescription(from: $0) })
|
|
112
110
|
|
|
113
111
|
return UsageSnapshot(
|
|
114
112
|
primary: primary,
|
|
115
113
|
secondary: nil,
|
|
116
114
|
tertiary: nil,
|
|
117
115
|
updatedAt: Date(),
|
|
118
|
-
identity: nil
|
|
119
|
-
)
|
|
116
|
+
identity: nil)
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
// MARK: - URL Building
|
|
@@ -126,7 +123,7 @@ public actor GenericProviderFetcher {
|
|
|
126
123
|
var urlString = template
|
|
127
124
|
|
|
128
125
|
// Replace date variables
|
|
129
|
-
urlString = try substituteDateVariables(in: urlString)
|
|
126
|
+
urlString = try self.substituteDateVariables(in: urlString)
|
|
130
127
|
|
|
131
128
|
// Build URL components
|
|
132
129
|
guard var components = URLComponents(string: urlString) else {
|
|
@@ -134,7 +131,7 @@ public actor GenericProviderFetcher {
|
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
// Add query parameters
|
|
137
|
-
if let params
|
|
134
|
+
if let params, !params.isEmpty {
|
|
138
135
|
var queryItems = components.queryItems ?? []
|
|
139
136
|
queryItems.append(contentsOf: params.map { URLQueryItem(name: $0.key, value: $0.value) })
|
|
140
137
|
components.queryItems = queryItems
|
|
@@ -144,6 +141,10 @@ public actor GenericProviderFetcher {
|
|
|
144
141
|
throw FetchError.invalidURL(urlString)
|
|
145
142
|
}
|
|
146
143
|
|
|
144
|
+
guard url.scheme?.lowercased() == "https" else {
|
|
145
|
+
throw FetchError.insecureURL(urlString)
|
|
146
|
+
}
|
|
147
|
+
|
|
147
148
|
return url
|
|
148
149
|
}
|
|
149
150
|
|
|
@@ -201,10 +202,10 @@ public actor GenericProviderFetcher {
|
|
|
201
202
|
let token = try await loadToken()
|
|
202
203
|
|
|
203
204
|
// Add auth header based on type
|
|
204
|
-
switch config.auth.type {
|
|
205
|
+
switch self.config.auth.type {
|
|
205
206
|
case .apiKey:
|
|
206
207
|
let value = (config.auth.headerPrefix ?? "") + token
|
|
207
|
-
request.setValue(value, forHTTPHeaderField: config.auth.headerName)
|
|
208
|
+
request.setValue(value, forHTTPHeaderField: self.config.auth.headerName)
|
|
208
209
|
|
|
209
210
|
case .bearer:
|
|
210
211
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
@@ -220,11 +221,11 @@ public actor GenericProviderFetcher {
|
|
|
220
221
|
case .custom:
|
|
221
222
|
// Custom auth uses the exact header name and prefix from config
|
|
222
223
|
let value = (config.auth.headerPrefix ?? "") + token
|
|
223
|
-
request.setValue(value, forHTTPHeaderField: config.auth.headerName)
|
|
224
|
+
request.setValue(value, forHTTPHeaderField: self.config.auth.headerName)
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
// Add additional headers
|
|
227
|
-
config.auth.additionalHeaders?.forEach { key, value in
|
|
228
|
+
self.config.auth.additionalHeaders?.forEach { key, value in
|
|
228
229
|
request.setValue(value, forHTTPHeaderField: key)
|
|
229
230
|
}
|
|
230
231
|
}
|
|
@@ -237,12 +238,12 @@ public actor GenericProviderFetcher {
|
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
// Try environment variable
|
|
240
|
-
let envKey = config.auth.tokenKeychain.uppercased().replacingOccurrences(of: "-", with: "_")
|
|
241
|
+
let envKey = self.config.auth.tokenKeychain.uppercased().replacingOccurrences(of: "-", with: "_")
|
|
241
242
|
if let token = ProcessInfo.processInfo.environment[envKey] {
|
|
242
243
|
return token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
throw FetchError.missingToken(config.auth.tokenKeychain)
|
|
246
|
+
throw FetchError.missingToken(self.config.auth.tokenKeychain)
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
/// Read token from keychain
|
|
@@ -251,18 +252,18 @@ public actor GenericProviderFetcher {
|
|
|
251
252
|
var result: CFTypeRef?
|
|
252
253
|
var query: [String: Any] = [
|
|
253
254
|
kSecClass as String: kSecClassGenericPassword,
|
|
254
|
-
kSecAttrService as String: keychainService,
|
|
255
|
+
kSecAttrService as String: self.keychainService,
|
|
255
256
|
kSecAttrAccount as String: account,
|
|
256
257
|
kSecUseDataProtectionKeychain as String: true,
|
|
257
258
|
kSecUseAuthenticationUI as String: "kSecUseAuthenticationUIFail" as CFString,
|
|
258
259
|
kSecMatchLimit as String: kSecMatchLimitOne,
|
|
259
260
|
kSecReturnData as String: true,
|
|
260
261
|
]
|
|
261
|
-
#if canImport(LocalAuthentication)
|
|
262
|
+
#if canImport(LocalAuthentication)
|
|
262
263
|
let authContext = LAContext()
|
|
263
264
|
authContext.interactionNotAllowed = true
|
|
264
265
|
query[kSecUseAuthenticationContext as String] = authContext
|
|
265
|
-
#endif
|
|
266
|
+
#endif
|
|
266
267
|
|
|
267
268
|
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
268
269
|
if status == errSecItemNotFound {
|
|
@@ -272,7 +273,7 @@ public actor GenericProviderFetcher {
|
|
|
272
273
|
return nil
|
|
273
274
|
}
|
|
274
275
|
guard status == errSecSuccess else {
|
|
275
|
-
log.error("Keychain read failed for \(account): \(status)")
|
|
276
|
+
self.log.error("Keychain read failed for \(account): \(status)")
|
|
276
277
|
return nil
|
|
277
278
|
}
|
|
278
279
|
|
|
@@ -299,32 +300,32 @@ public actor GenericProviderFetcher {
|
|
|
299
300
|
|
|
300
301
|
// Extract quota
|
|
301
302
|
if let quotaPath = mapping.quota {
|
|
302
|
-
data.quota = extractDouble(from: json, path: quotaPath, nested: mapping.nestedPaths)
|
|
303
|
+
data.quota = self.extractDouble(from: json, path: quotaPath, nested: mapping.nestedPaths)
|
|
303
304
|
}
|
|
304
305
|
|
|
305
306
|
// Extract used amount
|
|
306
307
|
if let usedPath = mapping.used {
|
|
307
|
-
data.used = extractDouble(from: json, path: usedPath, nested: mapping.nestedPaths)
|
|
308
|
+
data.used = self.extractDouble(from: json, path: usedPath, nested: mapping.nestedPaths)
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
// Extract remaining amount
|
|
311
312
|
if let remainingPath = mapping.remaining {
|
|
312
|
-
data.remaining = extractDouble(from: json, path: remainingPath, nested: mapping.nestedPaths)
|
|
313
|
+
data.remaining = self.extractDouble(from: json, path: remainingPath, nested: mapping.nestedPaths)
|
|
313
314
|
}
|
|
314
315
|
|
|
315
316
|
// Extract cost
|
|
316
317
|
if let costPath = mapping.cost {
|
|
317
|
-
data.cost = extractDouble(from: json, path: costPath, nested: mapping.nestedPaths)
|
|
318
|
+
data.cost = self.extractDouble(from: json, path: costPath, nested: mapping.nestedPaths)
|
|
318
319
|
}
|
|
319
320
|
|
|
320
321
|
// Extract reset date
|
|
321
322
|
if let resetPath = mapping.resetDate {
|
|
322
|
-
data.resetDate = extractDate(from: json, path: resetPath, nested: mapping.nestedPaths)
|
|
323
|
+
data.resetDate = self.extractDate(from: json, path: resetPath, nested: mapping.nestedPaths)
|
|
323
324
|
}
|
|
324
325
|
|
|
325
326
|
// Extract tokens
|
|
326
327
|
if let tokensPath = mapping.tokens {
|
|
327
|
-
data.tokens = extractInt(from: json, path: tokensPath, nested: mapping.nestedPaths)
|
|
328
|
+
data.tokens = self.extractInt(from: json, path: tokensPath, nested: mapping.nestedPaths)
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
return data
|
|
@@ -336,11 +337,11 @@ public actor GenericProviderFetcher {
|
|
|
336
337
|
|
|
337
338
|
// Extract balance (use 'remaining' or 'used' mapping)
|
|
338
339
|
if let balancePath = mapping.remaining ?? mapping.used {
|
|
339
|
-
data.balance = extractDouble(from: json, path: balancePath, nested: mapping.nestedPaths)
|
|
340
|
+
data.balance = self.extractDouble(from: json, path: balancePath, nested: mapping.nestedPaths)
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
// Try to detect currency from response
|
|
343
|
-
data.currency = extractString(from: json, path: "currency", nested: true) ?? "USD"
|
|
344
|
+
data.currency = self.extractString(from: json, path: "currency", nested: true) ?? "USD"
|
|
344
345
|
|
|
345
346
|
return data
|
|
346
347
|
}
|
|
@@ -359,14 +360,16 @@ public actor GenericProviderFetcher {
|
|
|
359
360
|
for component in components {
|
|
360
361
|
// Handle array indexing: field[0]
|
|
361
362
|
if let bracketIndex = component.firstIndex(of: "["),
|
|
362
|
-
let closeBracket = component.firstIndex(of: "]")
|
|
363
|
+
let closeBracket = component.firstIndex(of: "]")
|
|
364
|
+
{
|
|
363
365
|
let fieldName = String(component[..<bracketIndex])
|
|
364
366
|
let indexStr = String(component[component.index(after: bracketIndex)..<closeBracket])
|
|
365
367
|
|
|
366
368
|
guard let dict = current as? [String: Any],
|
|
367
369
|
let array = dict[fieldName] as? [Any],
|
|
368
370
|
let index = Int(indexStr),
|
|
369
|
-
index < array.count
|
|
371
|
+
index < array.count
|
|
372
|
+
else {
|
|
370
373
|
return nil
|
|
371
374
|
}
|
|
372
375
|
current = array[index]
|
|
@@ -516,8 +519,8 @@ public struct UsageData: Sendable {
|
|
|
516
519
|
remaining: Double? = nil,
|
|
517
520
|
cost: Double? = nil,
|
|
518
521
|
resetDate: Date? = nil,
|
|
519
|
-
tokens: Int? = nil
|
|
520
|
-
|
|
522
|
+
tokens: Int? = nil)
|
|
523
|
+
{
|
|
521
524
|
self.quota = quota
|
|
522
525
|
self.used = used
|
|
523
526
|
self.remaining = remaining
|
|
@@ -527,14 +530,13 @@ public struct UsageData: Sendable {
|
|
|
527
530
|
}
|
|
528
531
|
|
|
529
532
|
public func toCustomUsageData() -> CustomUsageData {
|
|
530
|
-
|
|
533
|
+
CustomUsageData(
|
|
531
534
|
quota: self.quota,
|
|
532
535
|
used: self.used,
|
|
533
536
|
remaining: self.remaining,
|
|
534
537
|
cost: self.cost,
|
|
535
538
|
resetDate: self.resetDate,
|
|
536
|
-
tokens: self.tokens
|
|
537
|
-
)
|
|
539
|
+
tokens: self.tokens)
|
|
538
540
|
}
|
|
539
541
|
}
|
|
540
542
|
|
|
@@ -562,61 +564,68 @@ public enum FetchError: LocalizedError, Sendable {
|
|
|
562
564
|
case missingToken(String)
|
|
563
565
|
case invalidDateRange
|
|
564
566
|
case extractionFailed(String)
|
|
567
|
+
case insecureURL(String)
|
|
565
568
|
|
|
566
569
|
public var errorDescription: String? {
|
|
567
570
|
switch self {
|
|
568
571
|
case .noUsageEndpoint:
|
|
569
|
-
|
|
572
|
+
"No usage endpoint configured for this provider"
|
|
570
573
|
case .noBalanceEndpoint:
|
|
571
|
-
|
|
572
|
-
case .invalidURL(
|
|
573
|
-
|
|
574
|
+
"No balance endpoint configured for this provider"
|
|
575
|
+
case let .invalidURL(url):
|
|
576
|
+
"Invalid URL: \(url)"
|
|
574
577
|
case .invalidResponse:
|
|
575
|
-
|
|
576
|
-
case .httpError(
|
|
577
|
-
|
|
578
|
+
"Invalid response from provider API"
|
|
579
|
+
case let .httpError(code):
|
|
580
|
+
"HTTP error \(code) from provider API"
|
|
578
581
|
case .invalidJSON:
|
|
579
|
-
|
|
580
|
-
case .missingToken(
|
|
581
|
-
|
|
582
|
+
"Failed to parse JSON response from provider"
|
|
583
|
+
case let .missingToken(account):
|
|
584
|
+
"No API token found for '\(account)'. Store it in Keychain or set as environment variable."
|
|
582
585
|
case .invalidDateRange:
|
|
583
|
-
|
|
584
|
-
case .extractionFailed(
|
|
585
|
-
|
|
586
|
+
"Failed to calculate date range for URL template"
|
|
587
|
+
case let .extractionFailed(field):
|
|
588
|
+
"Failed to extract field '\(field)' from response"
|
|
589
|
+
case let .insecureURL(url):
|
|
590
|
+
"Only HTTPS URLs are allowed. Got: \(url)"
|
|
586
591
|
}
|
|
587
592
|
}
|
|
588
593
|
|
|
589
594
|
public var failureReason: String? {
|
|
590
595
|
switch self {
|
|
591
|
-
case .missingToken(
|
|
592
|
-
|
|
593
|
-
case .httpError(
|
|
594
|
-
|
|
596
|
+
case let .missingToken(account):
|
|
597
|
+
"The token for account '\(account)' is not configured in Keychain or environment variables"
|
|
598
|
+
case let .httpError(code):
|
|
599
|
+
"The server returned HTTP status code \(code)"
|
|
595
600
|
case .invalidJSON:
|
|
596
|
-
|
|
597
|
-
case .extractionFailed(
|
|
598
|
-
|
|
601
|
+
"The response is not valid JSON or has unexpected structure"
|
|
602
|
+
case let .extractionFailed(field):
|
|
603
|
+
"The field '\(field)' was not found in the response or has wrong type"
|
|
604
|
+
case .insecureURL:
|
|
605
|
+
"The URL scheme is not HTTPS"
|
|
599
606
|
default:
|
|
600
|
-
|
|
607
|
+
nil
|
|
601
608
|
}
|
|
602
609
|
}
|
|
603
610
|
|
|
604
611
|
public var recoverySuggestion: String? {
|
|
605
612
|
switch self {
|
|
606
|
-
case .missingToken(
|
|
607
|
-
|
|
613
|
+
case let .missingToken(account):
|
|
614
|
+
"Store your API token in Keychain with account name '\(account)' or set it as environment variable"
|
|
608
615
|
case .noUsageEndpoint:
|
|
609
|
-
|
|
616
|
+
"Configure a usage endpoint in the provider settings"
|
|
610
617
|
case .noBalanceEndpoint:
|
|
611
|
-
|
|
612
|
-
case .httpError(
|
|
613
|
-
|
|
614
|
-
case .httpError(
|
|
615
|
-
|
|
618
|
+
"Configure a balance endpoint in the provider settings"
|
|
619
|
+
case let .httpError(code) where code == 401:
|
|
620
|
+
"Check that your API token is valid and has not expired"
|
|
621
|
+
case let .httpError(code) where code == 429:
|
|
622
|
+
"You have been rate limited. Try again later."
|
|
616
623
|
case .invalidJSON:
|
|
617
|
-
|
|
624
|
+
"Check the API endpoint URL and response mapping configuration"
|
|
625
|
+
case .insecureURL:
|
|
626
|
+
"Change the provider URL to use HTTPS (e.g., https://api.example.com)"
|
|
618
627
|
default:
|
|
619
|
-
|
|
628
|
+
nil
|
|
620
629
|
}
|
|
621
630
|
}
|
|
622
631
|
}
|