@sriinnu/runic 1.2.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/README.md +8 -1
- 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/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +7 -3
- 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 +18 -13
- 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 +1 -1
- 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 +1 -3
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SettingsStore.swift +89 -51
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +1 -3
- 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 +187 -161
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +1 -2
- 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/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 +12 -10
- 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 +18 -15
- 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 +22 -17
- 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 +44 -39
- 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 +73 -75
- 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/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 +3 -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/CodexOAuthUsageFetcher.swift +1 -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/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 +2 -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 +5 -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 +7 -7
- 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 +23 -10
- 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 +26 -13
- 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 +48 -21
- 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 +27 -13
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +11 -3
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +5 -2
- 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 -76
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
enum CostUsagePricing {
|
|
4
|
-
struct CodexPricing
|
|
4
|
+
struct CodexPricing {
|
|
5
5
|
let inputCostPerToken: Double
|
|
6
6
|
let outputCostPerToken: Double
|
|
7
7
|
let cacheReadInputCostPerToken: Double
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
struct ClaudePricing
|
|
10
|
+
struct ClaudePricing {
|
|
11
11
|
let inputCostPerToken: Double
|
|
12
12
|
let outputCostPerToken: Double
|
|
13
13
|
let cacheCreationInputCostPerToken: Double
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
enum CostUsageScanner {
|
|
4
|
-
struct Options
|
|
4
|
+
struct Options {
|
|
5
5
|
var codexSessionsRoot: URL?
|
|
6
6
|
var claudeProjectsRoots: [URL]?
|
|
7
7
|
var cacheRoot: URL?
|
|
8
8
|
var refreshMinIntervalSeconds: TimeInterval = 60
|
|
9
|
-
|
|
9
|
+
/// Force a full rescan, ignoring per-file cache and incremental offsets.
|
|
10
10
|
var forceRescan: Bool = false
|
|
11
11
|
|
|
12
12
|
init(
|
|
@@ -22,14 +22,14 @@ enum CostUsageScanner {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
struct CodexParseResult
|
|
25
|
+
struct CodexParseResult {
|
|
26
26
|
let days: [String: [String: [Int]]]
|
|
27
27
|
let parsedBytes: Int64
|
|
28
28
|
let lastModel: String?
|
|
29
29
|
let lastTotals: CostUsageCodexTotals?
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
struct ClaudeParseResult
|
|
32
|
+
struct ClaudeParseResult {
|
|
33
33
|
let days: [String: [String: [Int]]]
|
|
34
34
|
let parsedBytes: Int64
|
|
35
35
|
}
|
|
@@ -101,7 +101,7 @@ enum CostUsageScanner {
|
|
|
101
101
|
|
|
102
102
|
// MARK: - Day keys
|
|
103
103
|
|
|
104
|
-
struct CostUsageDayRange
|
|
104
|
+
struct CostUsageDayRange {
|
|
105
105
|
let sinceKey: String
|
|
106
106
|
let untilKey: String
|
|
107
107
|
let scanSinceKey: String
|
|
@@ -4,10 +4,9 @@ import Testing
|
|
|
4
4
|
@testable import Runic
|
|
5
5
|
|
|
6
6
|
@MainActor
|
|
7
|
-
@Suite
|
|
8
7
|
struct AppDelegateTests {
|
|
9
8
|
@Test
|
|
10
|
-
func
|
|
9
|
+
func `builds status controller after launch`() {
|
|
11
10
|
let appDelegate = AppDelegate()
|
|
12
11
|
var factoryCalls = 0
|
|
13
12
|
|
|
@@ -18,7 +17,13 @@ struct AppDelegateTests {
|
|
|
18
17
|
}
|
|
19
18
|
defer { StatusItemController.factory = StatusItemController.defaultFactory }
|
|
20
19
|
|
|
21
|
-
let settings = SettingsStore(
|
|
20
|
+
let settings = SettingsStore(
|
|
21
|
+
zaiTokenStore: NoopZaiTokenStore(),
|
|
22
|
+
minimaxTokenStore: NoopMiniMaxTokenStore(),
|
|
23
|
+
minimaxCookieHeaderStore: NoopMiniMaxCookieHeaderStore(),
|
|
24
|
+
minimaxGroupIDStore: NoopMiniMaxGroupIDStore(),
|
|
25
|
+
openRouterTokenStore: NoopOpenRouterTokenStore(),
|
|
26
|
+
groqTokenStore: NoopGroqTokenStore())
|
|
22
27
|
let fetcher = UsageFetcher()
|
|
23
28
|
let store = UsageStore(fetcher: fetcher, settings: settings)
|
|
24
29
|
let account = fetcher.loadAccountInfo()
|
|
@@ -2,18 +2,17 @@ import RunicCore
|
|
|
2
2
|
import Silo
|
|
3
3
|
import Testing
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct BrowserCookieOrderStatusStringTests {
|
|
7
6
|
#if os(macOS)
|
|
8
7
|
@Test
|
|
9
|
-
func
|
|
8
|
+
func `cursor no session includes browser login hint`() {
|
|
10
9
|
let order = ProviderDefaults.metadata[.cursor]?.browserCookieOrder ?? Browser.defaultImportOrder
|
|
11
10
|
let message = CursorStatusProbeError.noSessionCookie.errorDescription ?? ""
|
|
12
11
|
#expect(message.contains(order.loginHint))
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
@Test
|
|
16
|
-
func
|
|
15
|
+
func `factory no session includes browser login hint`() {
|
|
17
16
|
let order = ProviderDefaults.metadata[.factory]?.browserCookieOrder ?? Browser.defaultImportOrder
|
|
18
17
|
let message = FactoryStatusProbeError.noSessionCookie.errorDescription ?? ""
|
|
19
18
|
#expect(message.contains(order.loginHint))
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import Helix
|
|
2
2
|
import Testing
|
|
3
3
|
|
|
4
|
-
@Suite
|
|
5
4
|
struct CLIArgumentParsingTests {
|
|
6
5
|
@Test
|
|
7
|
-
func
|
|
6
|
+
func `usage command parses provider and json flags`() throws {
|
|
8
7
|
let signature = CommandSignature(
|
|
9
8
|
options: [
|
|
10
9
|
OptionDefinition(label: "provider", names: [.long("provider")], help: nil),
|
|
@@ -25,7 +24,7 @@ struct CLIArgumentParsingTests {
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
@Test
|
|
28
|
-
func
|
|
27
|
+
func `cost command parses refresh flag`() throws {
|
|
29
28
|
let signature = CommandSignature(
|
|
30
29
|
options: [
|
|
31
30
|
OptionDefinition(label: "provider", names: [.long("provider")], help: nil),
|
|
@@ -41,7 +40,7 @@ struct CLIArgumentParsingTests {
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
@Test
|
|
44
|
-
func
|
|
43
|
+
func `insights command parses view and budget flags`() throws {
|
|
45
44
|
let signature = CommandSignature(
|
|
46
45
|
options: [
|
|
47
46
|
OptionDefinition(label: "view", names: [.long("view")], help: nil),
|
|
@@ -2,26 +2,25 @@ import Foundation
|
|
|
2
2
|
import RunicCore
|
|
3
3
|
import Testing
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct CLICostTests {
|
|
7
6
|
@Test
|
|
8
|
-
func
|
|
7
|
+
func `cost snapshot stores session and month fields`() {
|
|
9
8
|
let snapshot = CostUsageTokenSnapshot(
|
|
10
|
-
sessionTokens:
|
|
9
|
+
sessionTokens: 1200,
|
|
11
10
|
sessionCostUSD: 1.25,
|
|
12
|
-
last30DaysTokens:
|
|
11
|
+
last30DaysTokens: 9000,
|
|
13
12
|
last30DaysCostUSD: 9.99,
|
|
14
13
|
daily: [],
|
|
15
14
|
updatedAt: Date(timeIntervalSince1970: 1_700_000_000))
|
|
16
15
|
|
|
17
|
-
#expect(snapshot.sessionTokens ==
|
|
16
|
+
#expect(snapshot.sessionTokens == 1200)
|
|
18
17
|
#expect(snapshot.sessionCostUSD == 1.25)
|
|
19
|
-
#expect(snapshot.last30DaysTokens ==
|
|
18
|
+
#expect(snapshot.last30DaysTokens == 9000)
|
|
20
19
|
#expect(snapshot.last30DaysCostUSD == 9.99)
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
@Test
|
|
24
|
-
func
|
|
23
|
+
func `daily report decodes legacy cache token keys`() throws {
|
|
25
24
|
let json = """
|
|
26
25
|
{
|
|
27
26
|
"daily": [
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import RunicCore
|
|
2
2
|
import Testing
|
|
3
3
|
|
|
4
|
-
@Suite
|
|
5
4
|
struct CLIProviderSelectionTests {
|
|
6
5
|
@Test
|
|
7
|
-
func
|
|
6
|
+
func `provider registry includes core providers`() {
|
|
8
7
|
let providers = Set(ProviderDescriptorRegistry.all.map(\.id))
|
|
9
8
|
|
|
10
9
|
#expect(providers.contains(.codex))
|
|
@@ -15,7 +14,7 @@ struct CLIProviderSelectionTests {
|
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
@Test
|
|
18
|
-
func
|
|
17
|
+
func `provider enum resolves insights supported values`() {
|
|
19
18
|
#expect(UsageProvider(rawValue: "codex") == .codex)
|
|
20
19
|
#expect(UsageProvider(rawValue: "claude") == .claude)
|
|
21
20
|
}
|
|
@@ -3,10 +3,9 @@ import RunicCore
|
|
|
3
3
|
import Testing
|
|
4
4
|
@testable import RunicCLI
|
|
5
5
|
|
|
6
|
-
@Suite
|
|
7
6
|
struct CLISnapshotTests {
|
|
8
7
|
@Test
|
|
9
|
-
func
|
|
8
|
+
func `renders codex text snapshot`() {
|
|
10
9
|
let identity = ProviderIdentitySnapshot(
|
|
11
10
|
providerID: .codex,
|
|
12
11
|
accountEmail: "user@example.com",
|
|
@@ -34,7 +33,7 @@ struct CLISnapshotTests {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
@Test
|
|
37
|
-
func
|
|
36
|
+
func `renders claude snapshot without weekly when missing`() {
|
|
38
37
|
let snapshot = UsageSnapshot(
|
|
39
38
|
primary: .init(usedPercent: 2, windowMinutes: nil, resetsAt: nil, resetDescription: "3pm (Europe/Vienna)"),
|
|
40
39
|
secondary: nil,
|
|
@@ -52,7 +51,7 @@ struct CLISnapshotTests {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
@Test
|
|
55
|
-
func
|
|
54
|
+
func `applies ansi colors when enabled`() {
|
|
56
55
|
let snapshot = UsageSnapshot(
|
|
57
56
|
primary: .init(usedPercent: 95, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
|
|
58
57
|
secondary: .init(usedPercent: 80, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
|
|
@@ -2,7 +2,6 @@ import Testing
|
|
|
2
2
|
@testable import RunicCLI
|
|
3
3
|
@testable import RunicCore
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct CLIWebFallbackTests {
|
|
7
6
|
private func makeContext(sourceMode: ProviderSourceMode = .auto) -> ProviderFetchContext {
|
|
8
7
|
ProviderFetchContext(
|
|
@@ -19,7 +18,7 @@ struct CLIWebFallbackTests {
|
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
@Test
|
|
22
|
-
func
|
|
21
|
+
func `codex falls back when cookies missing`() {
|
|
23
22
|
let context = self.makeContext()
|
|
24
23
|
let strategy = CodexWebDashboardStrategy()
|
|
25
24
|
#expect(strategy.shouldFallback(
|
|
@@ -40,7 +39,7 @@ struct CLIWebFallbackTests {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
@Test
|
|
43
|
-
func
|
|
42
|
+
func `codex does not fallback for dashboard data errors`() {
|
|
44
43
|
let context = self.makeContext()
|
|
45
44
|
let strategy = CodexWebDashboardStrategy()
|
|
46
45
|
#expect(!strategy.shouldFallback(
|
|
@@ -49,7 +48,7 @@ struct CLIWebFallbackTests {
|
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
@Test
|
|
52
|
-
func
|
|
51
|
+
func `claude falls back when no session key`() {
|
|
53
52
|
let context = self.makeContext()
|
|
54
53
|
let strategy = ClaudeWebFetchStrategy()
|
|
55
54
|
#expect(strategy.shouldFallback(on: ClaudeWebAPIFetcher.FetchError.noSessionKeyFound, context: context))
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Testing
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct ClaudeOAuthTests {
|
|
7
6
|
@Test
|
|
8
|
-
func
|
|
7
|
+
func `parses O auth credentials`() throws {
|
|
9
8
|
let json = """
|
|
10
9
|
{
|
|
11
10
|
"claudeAiOauth": {
|
|
@@ -26,7 +25,7 @@ struct ClaudeOAuthTests {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
@Test
|
|
29
|
-
func
|
|
28
|
+
func `missing access token throws`() {
|
|
30
29
|
let json = """
|
|
31
30
|
{
|
|
32
31
|
"claudeAiOauth": {
|
|
@@ -42,7 +41,7 @@ struct ClaudeOAuthTests {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
@Test
|
|
45
|
-
func
|
|
44
|
+
func `missing O auth block throws`() {
|
|
46
45
|
let json = """
|
|
47
46
|
{ "other": { "accessToken": "nope" } }
|
|
48
47
|
"""
|
|
@@ -52,7 +51,7 @@ struct ClaudeOAuthTests {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
@Test
|
|
55
|
-
func
|
|
54
|
+
func `treats missing expiry as expired`() {
|
|
56
55
|
let creds = ClaudeOAuthCredentials(
|
|
57
56
|
accessToken: "token",
|
|
58
57
|
refreshToken: nil,
|
|
@@ -63,7 +62,7 @@ struct ClaudeOAuthTests {
|
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
@Test
|
|
66
|
-
func
|
|
65
|
+
func `maps O auth usage to snapshot`() throws {
|
|
67
66
|
let json = """
|
|
68
67
|
{
|
|
69
68
|
"five_hour": { "utilization": 12.5, "resets_at": "2025-12-25T12:00:00.000Z" },
|
|
@@ -83,7 +82,7 @@ struct ClaudeOAuthTests {
|
|
|
83
82
|
}
|
|
84
83
|
|
|
85
84
|
@Test
|
|
86
|
-
func
|
|
85
|
+
func `maps O auth extra usage`() throws {
|
|
87
86
|
let json = """
|
|
88
87
|
{
|
|
89
88
|
"five_hour": { "utilization": 1, "resets_at": "2025-12-25T12:00:00.000Z" },
|
|
@@ -101,7 +100,7 @@ struct ClaudeOAuthTests {
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
@Test
|
|
104
|
-
func
|
|
103
|
+
func `prefers opus when sonnet missing`() throws {
|
|
105
104
|
let json = """
|
|
106
105
|
{
|
|
107
106
|
"five_hour": { "utilization": 10, "resets_at": "2025-12-25T12:00:00.000Z" },
|
|
@@ -113,7 +112,7 @@ struct ClaudeOAuthTests {
|
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
@Test
|
|
116
|
-
func
|
|
115
|
+
func `skips extra usage when disabled`() throws {
|
|
117
116
|
let json = """
|
|
118
117
|
{
|
|
119
118
|
"five_hour": { "utilization": 1, "resets_at": "2025-12-25T12:00:00.000Z" },
|
|
@@ -131,7 +130,7 @@ struct ClaudeOAuthTests {
|
|
|
131
130
|
// MARK: - Scope-based strategy resolution
|
|
132
131
|
|
|
133
132
|
@Test
|
|
134
|
-
func
|
|
133
|
+
func `oauth requires user profile scope`() {
|
|
135
134
|
// With user:profile scope, OAuth should be selected.
|
|
136
135
|
let withProfile = ClaudeProviderDescriptor.resolveUsageStrategy(
|
|
137
136
|
debugMenuEnabled: false,
|
|
@@ -152,7 +151,7 @@ struct ClaudeOAuthTests {
|
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
@Test
|
|
155
|
-
func
|
|
154
|
+
func `falls back to CLI when no web or O auth`() {
|
|
156
155
|
let strategy = ClaudeProviderDescriptor.resolveUsageStrategy(
|
|
157
156
|
debugMenuEnabled: false,
|
|
158
157
|
selectedDataSource: .oauth,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import Testing
|
|
2
2
|
@testable import Runic
|
|
3
3
|
|
|
4
|
-
@Suite
|
|
5
4
|
struct ClaudeResilienceTests {
|
|
6
5
|
@Test
|
|
7
|
-
func
|
|
6
|
+
func `suppresses single flake when prior data exists`() {
|
|
8
7
|
var gate = ConsecutiveFailureGate()
|
|
9
8
|
let firstFailure = gate.shouldSurfaceError(onFailureWithPriorData: true)
|
|
10
9
|
let secondFailure = gate.shouldSurfaceError(onFailureWithPriorData: true)
|
|
@@ -13,14 +12,14 @@ struct ClaudeResilienceTests {
|
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
@Test
|
|
16
|
-
func
|
|
15
|
+
func `surfaces failure without prior data`() {
|
|
17
16
|
var gate = ConsecutiveFailureGate()
|
|
18
17
|
let shouldSurface = gate.shouldSurfaceError(onFailureWithPriorData: false)
|
|
19
18
|
#expect(shouldSurface)
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
@Test
|
|
23
|
-
func
|
|
22
|
+
func `resets after success`() {
|
|
24
23
|
var gate = ConsecutiveFailureGate()
|
|
25
24
|
_ = gate.shouldSurfaceError(onFailureWithPriorData: true)
|
|
26
25
|
gate.recordSuccess()
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Testing
|
|
4
4
|
@testable import Runic
|
|
5
5
|
|
|
6
|
-
@Suite
|
|
7
6
|
struct ClaudeUsageTests {
|
|
8
7
|
@Test
|
|
9
|
-
func
|
|
8
|
+
func `parses usage JSON with sonnet limit`() {
|
|
10
9
|
let json = """
|
|
11
10
|
{
|
|
12
11
|
"ok": true,
|
|
@@ -24,7 +23,7 @@ struct ClaudeUsageTests {
|
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
@Test
|
|
27
|
-
func
|
|
26
|
+
func `parses usage JSON when weekly missing`() {
|
|
28
27
|
let json = """
|
|
29
28
|
{
|
|
30
29
|
"ok": true,
|
|
@@ -39,7 +38,7 @@ struct ClaudeUsageTests {
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
@Test
|
|
42
|
-
func
|
|
41
|
+
func `parses legacy opus and account`() {
|
|
43
42
|
let json = """
|
|
44
43
|
{
|
|
45
44
|
"ok": true,
|
|
@@ -59,7 +58,7 @@ struct ClaudeUsageTests {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
@Test
|
|
62
|
-
func
|
|
61
|
+
func `parses usage JSON when only sonnet limit is present`() {
|
|
63
62
|
let json = """
|
|
64
63
|
{
|
|
65
64
|
"ok": true,
|
|
@@ -76,7 +75,7 @@ struct ClaudeUsageTests {
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
@Test
|
|
79
|
-
func
|
|
78
|
+
func `trims account fields`() throws {
|
|
80
79
|
let cases: [[String: String?]] = [
|
|
81
80
|
["email": " sriinnu@icloud.com ", "org": " Org "],
|
|
82
81
|
["email": "", "org": " Claude Max Account "],
|
|
@@ -93,11 +92,11 @@ struct ClaudeUsageTests {
|
|
|
93
92
|
if let org = entry["org"] { payload["account_org"] = org }
|
|
94
93
|
let data = try JSONSerialization.data(withJSONObject: payload)
|
|
95
94
|
let snap = ClaudeUsageFetcher.parse(json: data)
|
|
96
|
-
let emailRaw: String? = entry["email"] ??
|
|
95
|
+
let emailRaw: String? = entry["email"] ?? String?.none
|
|
97
96
|
let expectedEmail = emailRaw?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
98
97
|
let normalizedEmail = (expectedEmail?.isEmpty ?? true) ? nil : expectedEmail
|
|
99
98
|
#expect(snap?.accountEmail == normalizedEmail)
|
|
100
|
-
let orgRaw: String? = entry["org"] ??
|
|
99
|
+
let orgRaw: String? = entry["org"] ?? String?.none
|
|
101
100
|
let expectedOrg = orgRaw?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
102
101
|
let normalizedOrg = (expectedOrg?.isEmpty ?? true) ? nil : expectedOrg
|
|
103
102
|
#expect(snap?.accountOrganization == normalizedOrg)
|
|
@@ -105,7 +104,7 @@ struct ClaudeUsageTests {
|
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
@Test
|
|
108
|
-
func
|
|
107
|
+
func `live claude fetch PTY`() async throws {
|
|
109
108
|
guard ProcessInfo.processInfo.environment["LIVE_CLAUDE_FETCH"] == "1" else {
|
|
110
109
|
return
|
|
111
110
|
}
|
|
@@ -162,7 +161,7 @@ struct ClaudeUsageTests {
|
|
|
162
161
|
// MARK: - Web API tests
|
|
163
162
|
|
|
164
163
|
@Test
|
|
165
|
-
func
|
|
164
|
+
func `live claude fetch web API`() async throws {
|
|
166
165
|
// Set LIVE_CLAUDE_WEB_FETCH=1 to run this test with real browser cookies
|
|
167
166
|
guard ProcessInfo.processInfo.environment["LIVE_CLAUDE_WEB_FETCH"] == "1" else {
|
|
168
167
|
return
|
|
@@ -183,7 +182,7 @@ struct ClaudeUsageTests {
|
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
@Test
|
|
186
|
-
func
|
|
185
|
+
func `claude web API has session key check`() {
|
|
187
186
|
// Quick check that hasSessionKey returns a boolean (doesn't crash)
|
|
188
187
|
let hasKey = ClaudeWebAPIFetcher.hasSessionKey()
|
|
189
188
|
// We can't assert the value since it depends on the test environment
|
|
@@ -191,7 +190,7 @@ struct ClaudeUsageTests {
|
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
@Test
|
|
194
|
-
func
|
|
193
|
+
func `parses claude web API usage response`() throws {
|
|
195
194
|
let json = """
|
|
196
195
|
{
|
|
197
196
|
"five_hour": { "utilization": 9, "resets_at": "2025-12-23T16:00:00.000Z" },
|
|
@@ -209,7 +208,7 @@ struct ClaudeUsageTests {
|
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
@Test
|
|
212
|
-
func
|
|
211
|
+
func `parses claude web API usage response when weekly missing`() throws {
|
|
213
212
|
let json = """
|
|
214
213
|
{
|
|
215
214
|
"five_hour": { "utilization": 9, "resets_at": "2025-12-23T16:00:00.000Z" }
|
|
@@ -222,7 +221,7 @@ struct ClaudeUsageTests {
|
|
|
222
221
|
}
|
|
223
222
|
|
|
224
223
|
@Test
|
|
225
|
-
func
|
|
224
|
+
func `parses claude web API overage spend limit`() {
|
|
226
225
|
let json = """
|
|
227
226
|
{
|
|
228
227
|
"monthly_credit_limit": 2000,
|
|
@@ -241,7 +240,7 @@ struct ClaudeUsageTests {
|
|
|
241
240
|
}
|
|
242
241
|
|
|
243
242
|
@Test
|
|
244
|
-
func
|
|
243
|
+
func `parses claude web API overage spend limit cents`() {
|
|
245
244
|
let json = """
|
|
246
245
|
{
|
|
247
246
|
"monthly_credit_limit": 12345,
|
|
@@ -258,7 +257,7 @@ struct ClaudeUsageTests {
|
|
|
258
257
|
}
|
|
259
258
|
|
|
260
259
|
@Test
|
|
261
|
-
func
|
|
260
|
+
func `parses claude web API organizations response`() throws {
|
|
262
261
|
let json = """
|
|
263
262
|
[
|
|
264
263
|
{ "uuid": "org-123", "name": "Example Org", "capabilities": [] }
|
|
@@ -271,7 +270,7 @@ struct ClaudeUsageTests {
|
|
|
271
270
|
}
|
|
272
271
|
|
|
273
272
|
@Test
|
|
274
|
-
func
|
|
273
|
+
func `parses claude web API account info`() {
|
|
275
274
|
let json = """
|
|
276
275
|
{
|
|
277
276
|
"email_address": "sriinnu@icloud.com",
|
|
@@ -294,7 +293,7 @@ struct ClaudeUsageTests {
|
|
|
294
293
|
}
|
|
295
294
|
|
|
296
295
|
@Test
|
|
297
|
-
func
|
|
296
|
+
func `parses claude web API account info selects matching org`() {
|
|
298
297
|
let json = """
|
|
299
298
|
{
|
|
300
299
|
"email_address": "sriinnu@icloud.com",
|
|
@@ -324,7 +323,7 @@ struct ClaudeUsageTests {
|
|
|
324
323
|
}
|
|
325
324
|
|
|
326
325
|
@Test
|
|
327
|
-
func
|
|
326
|
+
func `parses claude web API account info falls back to first membership`() {
|
|
328
327
|
let json = """
|
|
329
328
|
{
|
|
330
329
|
"email_address": "sriinnu@icloud.com",
|
|
@@ -354,7 +353,7 @@ struct ClaudeUsageTests {
|
|
|
354
353
|
}
|
|
355
354
|
|
|
356
355
|
@Test
|
|
357
|
-
func
|
|
356
|
+
func `claude usage fetcher init with data sources`() {
|
|
358
357
|
// Verify we can create fetchers with both configurations
|
|
359
358
|
let defaultFetcher = ClaudeUsageFetcher()
|
|
360
359
|
let webFetcher = ClaudeUsageFetcher(dataSource: .web)
|
|
@@ -2,10 +2,9 @@ import Foundation
|
|
|
2
2
|
import Testing
|
|
3
3
|
@testable import RunicCore
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct CodexOAuthTests {
|
|
7
6
|
@Test
|
|
8
|
-
func
|
|
7
|
+
func `parses O auth credentials`() throws {
|
|
9
8
|
let json = """
|
|
10
9
|
{
|
|
11
10
|
"OPENAI_API_KEY": null,
|
|
@@ -27,7 +26,7 @@ struct CodexOAuthTests {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
@Test
|
|
30
|
-
func
|
|
29
|
+
func `parses API key credentials`() throws {
|
|
31
30
|
let json = """
|
|
32
31
|
{
|
|
33
32
|
"OPENAI_API_KEY": "sk-test"
|
|
@@ -41,7 +40,7 @@ struct CodexOAuthTests {
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
@Test
|
|
44
|
-
func
|
|
43
|
+
func `decodes credits balance string`() throws {
|
|
45
44
|
let json = """
|
|
46
45
|
{
|
|
47
46
|
"plan_type": "pro",
|
|
@@ -67,7 +66,7 @@ struct CodexOAuthTests {
|
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
@Test
|
|
70
|
-
func
|
|
69
|
+
func `maps usage windows from O auth`() throws {
|
|
71
70
|
let json = """
|
|
72
71
|
{
|
|
73
72
|
"rate_limit": {
|
|
@@ -100,21 +99,21 @@ struct CodexOAuthTests {
|
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
@Test
|
|
103
|
-
func
|
|
102
|
+
func `resolves chat GPT usage URL from config`() {
|
|
104
103
|
let config = "chatgpt_base_url = \"https://chatgpt.com/backend-api/\"\n"
|
|
105
104
|
let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)
|
|
106
105
|
#expect(url.absoluteString == "https://chatgpt.com/backend-api/wham/usage")
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
@Test
|
|
110
|
-
func
|
|
109
|
+
func `resolves codex usage URL from config`() {
|
|
111
110
|
let config = "chatgpt_base_url = \"https://api.openai.com\"\n"
|
|
112
111
|
let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)
|
|
113
112
|
#expect(url.absoluteString == "https://api.openai.com/api/codex/usage")
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
@Test
|
|
117
|
-
func
|
|
116
|
+
func `normalizes chat GPT base URL without backend API`() {
|
|
118
117
|
let config = "chatgpt_base_url = \"https://chat.openai.com\"\n"
|
|
119
118
|
let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config)
|
|
120
119
|
#expect(url.absoluteString == "https://chat.openai.com/backend-api/wham/usage")
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import Testing
|
|
3
|
-
|
|
4
3
|
@testable import RunicCore
|
|
5
4
|
|
|
6
|
-
@Suite
|
|
7
5
|
struct CodexUsageLogSourceTests {
|
|
8
6
|
@Test
|
|
9
|
-
func
|
|
7
|
+
func `codex usage falls back to workspace path from turn context`() async throws {
|
|
10
8
|
let fm = FileManager.default
|
|
11
9
|
let root = fm.temporaryDirectory
|
|
12
10
|
.appendingPathComponent("runic-codex-usage-tests", isDirectory: true)
|
|
@@ -2,10 +2,9 @@ import Foundation
|
|
|
2
2
|
import Testing
|
|
3
3
|
@testable import RunicCore
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct CopilotTokenResolverTests {
|
|
7
6
|
@Test
|
|
8
|
-
func
|
|
7
|
+
func `copilot token prefers copilot api environment variable`() {
|
|
9
8
|
let env = [
|
|
10
9
|
"COPILOT_API_TOKEN": "copilot-env-token",
|
|
11
10
|
"GITHUB_TOKEN": "github-token",
|
|
@@ -18,7 +17,7 @@ struct CopilotTokenResolverTests {
|
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
@Test
|
|
21
|
-
func
|
|
20
|
+
func `copilot token falls back to github token`() {
|
|
22
21
|
let env = [
|
|
23
22
|
"GITHUB_TOKEN": "github-token",
|
|
24
23
|
"GH_TOKEN": "gh-fallback-token",
|
|
@@ -29,7 +28,7 @@ struct CopilotTokenResolverTests {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
@Test
|
|
32
|
-
func
|
|
31
|
+
func `copilot token falls back to gh token`() {
|
|
33
32
|
let env = [
|
|
34
33
|
"GH_TOKEN": "gh-fallback-token",
|
|
35
34
|
"GITHUB_TOKEN": "",
|
|
@@ -40,7 +39,7 @@ struct CopilotTokenResolverTests {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
@Test
|
|
43
|
-
func
|
|
42
|
+
func `copilot token falls back to github CLI token`() throws {
|
|
44
43
|
let token = "gho_abcdefghijklmnopqrstuvwxyz1234"
|
|
45
44
|
let tempDir = FileManager.default.temporaryDirectory
|
|
46
45
|
.appendingPathComponent("runic-copilot-gh-cli-\(UUID().uuidString)", isDirectory: true)
|
|
@@ -65,7 +64,7 @@ struct CopilotTokenResolverTests {
|
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
@Test
|
|
68
|
-
func
|
|
67
|
+
func `copilot token strips quoted values`() {
|
|
69
68
|
let env = [
|
|
70
69
|
"COPILOT_API_TOKEN": "\"quoted-token\"",
|
|
71
70
|
]
|