@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,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
|
]
|
|
@@ -2,10 +2,9 @@ import Foundation
|
|
|
2
2
|
import Testing
|
|
3
3
|
@testable import RunicCore
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct CopilotVSCodeTokenReaderTests {
|
|
7
6
|
@Test
|
|
8
|
-
func
|
|
7
|
+
func `extracts token from session array payload`() {
|
|
9
8
|
let token = "ghu_abcdefghijklmnopqrstuvwxyz1234567890"
|
|
10
9
|
let json = """
|
|
11
10
|
[
|
|
@@ -26,7 +25,7 @@ struct CopilotVSCodeTokenReaderTests {
|
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
@Test
|
|
29
|
-
func
|
|
28
|
+
func `extracts token from nested object payload`() {
|
|
30
29
|
let token = "github_pat_abcdefghijklmnopqrstuvwxyz1234567890"
|
|
31
30
|
let json = """
|
|
32
31
|
{
|
|
@@ -45,7 +44,7 @@ struct CopilotVSCodeTokenReaderTests {
|
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
@Test
|
|
48
|
-
func
|
|
47
|
+
func `ignores short token values`() {
|
|
49
48
|
let json = """
|
|
50
49
|
{
|
|
51
50
|
"sessions": [
|
|
@@ -61,7 +60,7 @@ struct CopilotVSCodeTokenReaderTests {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
@Test
|
|
64
|
-
func
|
|
63
|
+
func `normalizes node buffer secret wrapper`() {
|
|
65
64
|
let raw = Data("""
|
|
66
65
|
{
|
|
67
66
|
"type": "Buffer",
|
|
@@ -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 CostUsageDecodingTests {
|
|
8
6
|
@Test
|
|
9
|
-
func
|
|
7
|
+
func `decodes daily report type format`() throws {
|
|
10
8
|
let json = """
|
|
11
9
|
{
|
|
12
10
|
"type": "daily",
|
|
@@ -45,7 +43,7 @@ struct CostUsageDecodingTests {
|
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
@Test
|
|
48
|
-
func
|
|
46
|
+
func `decodes daily report legacy format`() throws {
|
|
49
47
|
let json = """
|
|
50
48
|
{
|
|
51
49
|
"daily": [
|
|
@@ -81,7 +79,7 @@ struct CostUsageDecodingTests {
|
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
@Test
|
|
84
|
-
func
|
|
82
|
+
func `decodes legacy cache token keys`() throws {
|
|
85
83
|
let json = """
|
|
86
84
|
{
|
|
87
85
|
"type": "daily",
|
|
@@ -109,7 +107,7 @@ struct CostUsageDecodingTests {
|
|
|
109
107
|
}
|
|
110
108
|
|
|
111
109
|
@Test
|
|
112
|
-
func
|
|
110
|
+
func `decodes daily report legacy format with model map`() throws {
|
|
113
111
|
let json = """
|
|
114
112
|
{
|
|
115
113
|
"daily": [
|
|
@@ -143,7 +141,7 @@ struct CostUsageDecodingTests {
|
|
|
143
141
|
}
|
|
144
142
|
|
|
145
143
|
@Test
|
|
146
|
-
func
|
|
144
|
+
func `decodes daily report legacy format with model map sorted`() throws {
|
|
147
145
|
let json = """
|
|
148
146
|
{
|
|
149
147
|
"daily": [
|
|
@@ -166,7 +164,7 @@ struct CostUsageDecodingTests {
|
|
|
166
164
|
}
|
|
167
165
|
|
|
168
166
|
@Test
|
|
169
|
-
func
|
|
167
|
+
func `decodes daily report legacy format with empty model map as nil`() throws {
|
|
170
168
|
let json = """
|
|
171
169
|
{
|
|
172
170
|
"daily": [
|
|
@@ -185,7 +183,7 @@ struct CostUsageDecodingTests {
|
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
@Test
|
|
188
|
-
func
|
|
186
|
+
func `decodes daily report legacy format prefers models used list over models map`() throws {
|
|
189
187
|
let json = """
|
|
190
188
|
{
|
|
191
189
|
"daily": [
|
|
@@ -207,7 +205,7 @@ struct CostUsageDecodingTests {
|
|
|
207
205
|
}
|
|
208
206
|
|
|
209
207
|
@Test
|
|
210
|
-
func
|
|
208
|
+
func `decodes daily report legacy format with models list`() throws {
|
|
211
209
|
let json = """
|
|
212
210
|
{
|
|
213
211
|
"daily": [
|
|
@@ -226,7 +224,7 @@ struct CostUsageDecodingTests {
|
|
|
226
224
|
}
|
|
227
225
|
|
|
228
226
|
@Test
|
|
229
|
-
func
|
|
227
|
+
func `decodes daily report legacy format with invalid models field`() throws {
|
|
230
228
|
let json = """
|
|
231
229
|
{
|
|
232
230
|
"daily": [
|
|
@@ -245,7 +243,7 @@ struct CostUsageDecodingTests {
|
|
|
245
243
|
}
|
|
246
244
|
|
|
247
245
|
@Test
|
|
248
|
-
func
|
|
246
|
+
func `decodes monthly report legacy format`() throws {
|
|
249
247
|
let json = """
|
|
250
248
|
{
|
|
251
249
|
"monthly": [
|
|
@@ -270,7 +268,7 @@ struct CostUsageDecodingTests {
|
|
|
270
268
|
}
|
|
271
269
|
|
|
272
270
|
@Test
|
|
273
|
-
func
|
|
271
|
+
func `selects most recent session`() throws {
|
|
274
272
|
let json = """
|
|
275
273
|
{
|
|
276
274
|
"type": "session",
|
|
@@ -306,7 +304,7 @@ struct CostUsageDecodingTests {
|
|
|
306
304
|
}
|
|
307
305
|
|
|
308
306
|
@Test
|
|
309
|
-
func
|
|
307
|
+
func `token snapshot selects most recent day`() throws {
|
|
310
308
|
let json = """
|
|
311
309
|
{
|
|
312
310
|
"type": "daily",
|
|
@@ -339,7 +337,7 @@ struct CostUsageDecodingTests {
|
|
|
339
337
|
}
|
|
340
338
|
|
|
341
339
|
@Test
|
|
342
|
-
func
|
|
340
|
+
func `token snapshot uses summary total cost when available`() throws {
|
|
343
341
|
let json = """
|
|
344
342
|
{
|
|
345
343
|
"type": "daily",
|
|
@@ -359,7 +357,7 @@ struct CostUsageDecodingTests {
|
|
|
359
357
|
}
|
|
360
358
|
|
|
361
359
|
@Test
|
|
362
|
-
func
|
|
360
|
+
func `token snapshot falls back to summed entries when summary missing`() throws {
|
|
363
361
|
let json = """
|
|
364
362
|
{
|
|
365
363
|
"type": "daily",
|
|
@@ -376,7 +374,7 @@ struct CostUsageDecodingTests {
|
|
|
376
374
|
}
|
|
377
375
|
|
|
378
376
|
@Test
|
|
379
|
-
func
|
|
377
|
+
func `token snapshot returns nil total when no costs present`() throws {
|
|
380
378
|
let json = """
|
|
381
379
|
{
|
|
382
380
|
"type": "daily",
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import Testing
|
|
2
|
-
|
|
3
2
|
@testable import RunicCore
|
|
4
3
|
|
|
5
|
-
@Suite
|
|
6
4
|
struct CostUsagePricingTests {
|
|
7
5
|
@Test
|
|
8
|
-
func
|
|
6
|
+
func `normalizes codex model variants`() {
|
|
9
7
|
#expect(CostUsagePricing.normalizeCodexModel("openai/gpt-5-codex") == "gpt-5")
|
|
10
8
|
#expect(CostUsagePricing.normalizeCodexModel("gpt-5.2-codex") == "gpt-5.2")
|
|
11
9
|
#expect(CostUsagePricing.normalizeCodexModel("gpt-5.1-codex-max") == "gpt-5.1")
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
@Test
|
|
15
|
-
func
|
|
13
|
+
func `codex cost supports gpt51 codex max`() {
|
|
16
14
|
let cost = CostUsagePricing.codexCostUSD(
|
|
17
15
|
model: "gpt-5.1-codex-max",
|
|
18
16
|
inputTokens: 100,
|
|
@@ -22,12 +20,12 @@ struct CostUsagePricingTests {
|
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
@Test
|
|
25
|
-
func
|
|
23
|
+
func `normalizes claude opus41 dated variants`() {
|
|
26
24
|
#expect(CostUsagePricing.normalizeClaudeModel("claude-opus-4-1-20250805") == "claude-opus-4-1")
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
@Test
|
|
30
|
-
func
|
|
28
|
+
func `claude cost supports opus41 dated variant`() {
|
|
31
29
|
let cost = CostUsagePricing.claudeCostUSD(
|
|
32
30
|
model: "claude-opus-4-1-20250805",
|
|
33
31
|
inputTokens: 10,
|
|
@@ -38,7 +36,7 @@ struct CostUsagePricingTests {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
@Test
|
|
41
|
-
func
|
|
39
|
+
func `claude cost returns nil for unknown models`() {
|
|
42
40
|
let cost = CostUsagePricing.claudeCostUSD(
|
|
43
41
|
model: "glm-4.6",
|
|
44
42
|
inputTokens: 100,
|