@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,12 +1,10 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import Testing
|
|
3
|
-
|
|
4
3
|
@testable import RunicCore
|
|
5
4
|
|
|
6
|
-
@Suite
|
|
7
5
|
struct CostUsageScannerTests {
|
|
8
6
|
@Test
|
|
9
|
-
func
|
|
7
|
+
func `codex daily report parses token counts and caches`() throws {
|
|
10
8
|
let env = try CostUsageTestEnvironment()
|
|
11
9
|
defer { env.cleanup() }
|
|
12
10
|
|
|
@@ -91,7 +89,7 @@ struct CostUsageScannerTests {
|
|
|
91
89
|
}
|
|
92
90
|
|
|
93
91
|
@Test
|
|
94
|
-
func
|
|
92
|
+
func `claude daily report parses usage and caches`() throws {
|
|
95
93
|
let env = try CostUsageTestEnvironment()
|
|
96
94
|
defer { env.cleanup() }
|
|
97
95
|
|
|
@@ -138,7 +136,7 @@ struct CostUsageScannerTests {
|
|
|
138
136
|
}
|
|
139
137
|
|
|
140
138
|
@Test
|
|
141
|
-
func
|
|
139
|
+
func `claude parses large lines with usage at tail`() throws {
|
|
142
140
|
let env = try CostUsageTestEnvironment()
|
|
143
141
|
defer { env.cleanup() }
|
|
144
142
|
|
|
@@ -183,7 +181,7 @@ struct CostUsageScannerTests {
|
|
|
183
181
|
}
|
|
184
182
|
|
|
185
183
|
@Test
|
|
186
|
-
func
|
|
184
|
+
func `claude daily report refreshes when file changes`() throws {
|
|
187
185
|
let env = try CostUsageTestEnvironment()
|
|
188
186
|
defer { env.cleanup() }
|
|
189
187
|
|
|
@@ -248,7 +246,7 @@ struct CostUsageScannerTests {
|
|
|
248
246
|
}
|
|
249
247
|
|
|
250
248
|
@Test
|
|
251
|
-
func
|
|
249
|
+
func `codex incremental parsing uses previous totals`() throws {
|
|
252
250
|
let env = try CostUsageTestEnvironment()
|
|
253
251
|
defer { env.cleanup() }
|
|
254
252
|
|
|
@@ -327,7 +325,7 @@ struct CostUsageScannerTests {
|
|
|
327
325
|
}
|
|
328
326
|
|
|
329
327
|
@Test
|
|
330
|
-
func
|
|
328
|
+
func `claude incremental parsing reads appended lines only`() throws {
|
|
331
329
|
let env = try CostUsageTestEnvironment()
|
|
332
330
|
defer { env.cleanup() }
|
|
333
331
|
|
|
@@ -387,7 +385,7 @@ struct CostUsageScannerTests {
|
|
|
387
385
|
}
|
|
388
386
|
|
|
389
387
|
@Test
|
|
390
|
-
func
|
|
388
|
+
func `day key from timestamp matches ISO parsing`() {
|
|
391
389
|
let timestamps = [
|
|
392
390
|
"2025-12-20T23:59:59Z",
|
|
393
391
|
"2025-12-20T23:59:59+02:00",
|
|
@@ -401,7 +399,7 @@ struct CostUsageScannerTests {
|
|
|
401
399
|
}
|
|
402
400
|
|
|
403
401
|
@Test
|
|
404
|
-
func
|
|
402
|
+
func `claude deduplicates streaming chunks`() throws {
|
|
405
403
|
let env = try CostUsageTestEnvironment()
|
|
406
404
|
defer { env.cleanup() }
|
|
407
405
|
|
|
@@ -489,7 +487,7 @@ struct CostUsageScannerTests {
|
|
|
489
487
|
}
|
|
490
488
|
|
|
491
489
|
@Test
|
|
492
|
-
func
|
|
490
|
+
func `claude counts entries without ids as separate`() throws {
|
|
493
491
|
let env = try CostUsageTestEnvironment()
|
|
494
492
|
defer { env.cleanup() }
|
|
495
493
|
|
|
@@ -553,7 +551,7 @@ struct CostUsageScannerTests {
|
|
|
553
551
|
}
|
|
554
552
|
|
|
555
553
|
@Test
|
|
556
|
-
func
|
|
554
|
+
func `claude counts different request ids separately`() throws {
|
|
557
555
|
let env = try CostUsageTestEnvironment()
|
|
558
556
|
defer { env.cleanup() }
|
|
559
557
|
|
|
@@ -2,12 +2,11 @@ import Foundation
|
|
|
2
2
|
import Testing
|
|
3
3
|
@testable import RunicCore
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct CursorStatusProbeTests {
|
|
7
6
|
// MARK: - Usage Summary Parsing
|
|
8
7
|
|
|
9
8
|
@Test
|
|
10
|
-
func
|
|
9
|
+
func `parses basic usage summary`() throws {
|
|
11
10
|
let json = """
|
|
12
11
|
{
|
|
13
12
|
"billingCycleStart": "2025-01-01T00:00:00.000Z",
|
|
@@ -38,7 +37,7 @@ struct CursorStatusProbeTests {
|
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
"""
|
|
41
|
-
let data = json.data(using: .utf8)
|
|
40
|
+
let data = try #require(json.data(using: .utf8))
|
|
42
41
|
let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)
|
|
43
42
|
|
|
44
43
|
#expect(summary.membershipType == "pro")
|
|
@@ -51,7 +50,7 @@ struct CursorStatusProbeTests {
|
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
@Test
|
|
54
|
-
func
|
|
53
|
+
func `parses minimal usage summary`() throws {
|
|
55
54
|
let json = """
|
|
56
55
|
{
|
|
57
56
|
"membershipType": "hobby",
|
|
@@ -63,7 +62,7 @@ struct CursorStatusProbeTests {
|
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
64
|
"""
|
|
66
|
-
let data = json.data(using: .utf8)
|
|
65
|
+
let data = try #require(json.data(using: .utf8))
|
|
67
66
|
let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)
|
|
68
67
|
|
|
69
68
|
#expect(summary.membershipType == "hobby")
|
|
@@ -73,7 +72,7 @@ struct CursorStatusProbeTests {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
@Test
|
|
76
|
-
func
|
|
75
|
+
func `parses enterprise usage summary`() throws {
|
|
77
76
|
let json = """
|
|
78
77
|
{
|
|
79
78
|
"membershipType": "enterprise",
|
|
@@ -88,7 +87,7 @@ struct CursorStatusProbeTests {
|
|
|
88
87
|
}
|
|
89
88
|
}
|
|
90
89
|
"""
|
|
91
|
-
let data = json.data(using: .utf8)
|
|
90
|
+
let data = try #require(json.data(using: .utf8))
|
|
92
91
|
let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)
|
|
93
92
|
|
|
94
93
|
#expect(summary.membershipType == "enterprise")
|
|
@@ -99,7 +98,7 @@ struct CursorStatusProbeTests {
|
|
|
99
98
|
// MARK: - User Info Parsing
|
|
100
99
|
|
|
101
100
|
@Test
|
|
102
|
-
func
|
|
101
|
+
func `parses user info`() throws {
|
|
103
102
|
let json = """
|
|
104
103
|
{
|
|
105
104
|
"email": "user@example.com",
|
|
@@ -108,7 +107,7 @@ struct CursorStatusProbeTests {
|
|
|
108
107
|
"sub": "auth0|12345"
|
|
109
108
|
}
|
|
110
109
|
"""
|
|
111
|
-
let data = json.data(using: .utf8)
|
|
110
|
+
let data = try #require(json.data(using: .utf8))
|
|
112
111
|
let userInfo = try JSONDecoder().decode(CursorUserInfo.self, from: data)
|
|
113
112
|
|
|
114
113
|
#expect(userInfo.email == "user@example.com")
|
|
@@ -120,7 +119,7 @@ struct CursorStatusProbeTests {
|
|
|
120
119
|
// MARK: - Snapshot Conversion
|
|
121
120
|
|
|
122
121
|
@Test
|
|
123
|
-
func
|
|
122
|
+
func `prefers plan ratio over percent field`() {
|
|
124
123
|
let snapshot = CursorStatusProbe()
|
|
125
124
|
.parseUsageSummary(
|
|
126
125
|
CursorUsageSummary(
|
|
@@ -149,7 +148,7 @@ struct CursorStatusProbeTests {
|
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
@Test
|
|
152
|
-
func
|
|
151
|
+
func `uses percent field when limit missing`() {
|
|
153
152
|
let snapshot = CursorStatusProbe()
|
|
154
153
|
.parseUsageSummary(
|
|
155
154
|
CursorUsageSummary(
|
|
@@ -178,7 +177,7 @@ struct CursorStatusProbeTests {
|
|
|
178
177
|
}
|
|
179
178
|
|
|
180
179
|
@Test
|
|
181
|
-
func
|
|
180
|
+
func `converts snapshot to usage snapshot`() {
|
|
182
181
|
let snapshot = CursorStatusSnapshot(
|
|
183
182
|
planPercentUsed: 45.0,
|
|
184
183
|
planUsedUSD: 22.50,
|
|
@@ -206,7 +205,7 @@ struct CursorStatusProbeTests {
|
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
@Test
|
|
209
|
-
func
|
|
208
|
+
func `uses individual on demand when no team usage`() {
|
|
210
209
|
let snapshot = CursorStatusSnapshot(
|
|
211
210
|
planPercentUsed: 10.0,
|
|
212
211
|
planUsedUSD: 5.0,
|
|
@@ -229,7 +228,7 @@ struct CursorStatusProbeTests {
|
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
@Test
|
|
232
|
-
func
|
|
231
|
+
func `formats membership types`() {
|
|
233
232
|
let testCases: [(input: String, expected: String)] = [
|
|
234
233
|
("pro", "Cursor Pro"),
|
|
235
234
|
("hobby", "Cursor Hobby"),
|
|
@@ -259,7 +258,7 @@ struct CursorStatusProbeTests {
|
|
|
259
258
|
}
|
|
260
259
|
|
|
261
260
|
@Test
|
|
262
|
-
func
|
|
261
|
+
func `handles nil on demand limit`() {
|
|
263
262
|
let snapshot = CursorStatusSnapshot(
|
|
264
263
|
planPercentUsed: 50.0,
|
|
265
264
|
planUsedUSD: 25.0,
|
|
@@ -287,7 +286,7 @@ struct CursorStatusProbeTests {
|
|
|
287
286
|
// MARK: - Session Store Serialization
|
|
288
287
|
|
|
289
288
|
@Test
|
|
290
|
-
func
|
|
289
|
+
func `session store saves and loads cookies`() async {
|
|
291
290
|
let store = CursorSessionStore.shared
|
|
292
291
|
|
|
293
292
|
// Clear any existing cookies
|
|
@@ -2,10 +2,9 @@ import Foundation
|
|
|
2
2
|
import Testing
|
|
3
3
|
@testable import RunicCore
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct FactoryStatusSnapshotTests {
|
|
7
6
|
@Test
|
|
8
|
-
func
|
|
7
|
+
func `maps usage snapshot windows and login method`() {
|
|
9
8
|
let periodEnd = Date(timeIntervalSince1970: 1_738_368_000) // Feb 1, 2025
|
|
10
9
|
let snapshot = FactoryStatusSnapshot(
|
|
11
10
|
standardUserTokens: 50,
|
|
@@ -33,7 +32,7 @@ struct FactoryStatusSnapshotTests {
|
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
@Test
|
|
36
|
-
func
|
|
35
|
+
func `treats large allowances as unlimited`() {
|
|
37
36
|
let snapshot = FactoryStatusSnapshot(
|
|
38
37
|
standardUserTokens: 50_000_000,
|
|
39
38
|
standardOrgTokens: 0,
|
|
@@ -56,10 +55,9 @@ struct FactoryStatusSnapshotTests {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
@Suite
|
|
60
58
|
struct FactoryStatusProbeWorkOSTests {
|
|
61
59
|
@Test
|
|
62
|
-
func
|
|
60
|
+
func `detects missing refresh token payload`() {
|
|
63
61
|
let payload = Data("""
|
|
64
62
|
{"error":"invalid_request","error_description":"Missing refresh token."}
|
|
65
63
|
""".utf8)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import Testing
|
|
2
2
|
@testable import Runic
|
|
3
3
|
|
|
4
|
-
@Suite
|
|
5
4
|
struct GeminiLoginAlertTests {
|
|
6
5
|
@Test
|
|
7
|
-
func
|
|
6
|
+
func `returns alert for missing binary`() {
|
|
8
7
|
let result = GeminiLoginRunner.Result(outcome: .missingBinary)
|
|
9
8
|
let info = StatusItemController.geminiLoginAlertInfo(for: result)
|
|
10
9
|
#expect(info?.title == "Gemini CLI not found")
|
|
@@ -12,7 +11,7 @@ struct GeminiLoginAlertTests {
|
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
@Test
|
|
15
|
-
func
|
|
14
|
+
func `returns alert for launch failure`() {
|
|
16
15
|
let result = GeminiLoginRunner.Result(outcome: .launchFailed("Boom"))
|
|
17
16
|
let info = StatusItemController.geminiLoginAlertInfo(for: result)
|
|
18
17
|
#expect(info?.title == "Could not open Terminal for Gemini")
|
|
@@ -20,7 +19,7 @@ struct GeminiLoginAlertTests {
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
@Test
|
|
23
|
-
func
|
|
22
|
+
func `returns nil on success`() {
|
|
24
23
|
let result = GeminiLoginRunner.Result(outcome: .success)
|
|
25
24
|
let info = StatusItemController.geminiLoginAlertInfo(for: result)
|
|
26
25
|
#expect(info == nil)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Testing
|
|
4
4
|
|
|
5
|
-
@Suite(
|
|
5
|
+
@Suite(.serialized)
|
|
6
6
|
struct GeminiStatusProbeAPITests {
|
|
7
7
|
@Test
|
|
8
|
-
func
|
|
8
|
+
func `missing credentials throws not logged in`() async throws {
|
|
9
9
|
let env = try GeminiTestEnvironment()
|
|
10
10
|
defer { env.cleanup() }
|
|
11
11
|
|
|
@@ -16,7 +16,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
@Test
|
|
19
|
-
func
|
|
19
|
+
func `rejects api key auth type`() async throws {
|
|
20
20
|
let env = try GeminiTestEnvironment()
|
|
21
21
|
defer { env.cleanup() }
|
|
22
22
|
try env.writeSettings(authType: "api-key")
|
|
@@ -28,7 +28,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
@Test
|
|
31
|
-
func
|
|
31
|
+
func `rejects vertex auth type`() async throws {
|
|
32
32
|
let env = try GeminiTestEnvironment()
|
|
33
33
|
defer { env.cleanup() }
|
|
34
34
|
try env.writeSettings(authType: "vertex-ai")
|
|
@@ -40,7 +40,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
@Test
|
|
43
|
-
func
|
|
43
|
+
func `refreshes expired token and updates stored credentials`() async throws {
|
|
44
44
|
let env = try GeminiTestEnvironment()
|
|
45
45
|
defer { env.cleanup() }
|
|
46
46
|
try env.writeCredentials(
|
|
@@ -112,7 +112,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
@Test
|
|
115
|
-
func
|
|
115
|
+
func `fails refresh when O auth config missing`() async throws {
|
|
116
116
|
let env = try GeminiTestEnvironment()
|
|
117
117
|
defer { env.cleanup() }
|
|
118
118
|
try env.writeCredentials(
|
|
@@ -139,7 +139,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
@Test
|
|
142
|
-
func
|
|
142
|
+
func `reports api errors`() async throws {
|
|
143
143
|
let env = try GeminiTestEnvironment()
|
|
144
144
|
defer { env.cleanup() }
|
|
145
145
|
try env.writeCredentials(
|
|
@@ -175,7 +175,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
@Test
|
|
178
|
-
func
|
|
178
|
+
func `reports not logged in when access token missing`() async throws {
|
|
179
179
|
let env = try GeminiTestEnvironment()
|
|
180
180
|
defer { env.cleanup() }
|
|
181
181
|
try env.writeCredentials(
|
|
@@ -191,7 +191,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
@Test
|
|
194
|
-
func
|
|
194
|
+
func `reports not logged in on401`() async throws {
|
|
195
195
|
let env = try GeminiTestEnvironment()
|
|
196
196
|
defer { env.cleanup() }
|
|
197
197
|
try env.writeCredentials(
|
|
@@ -227,7 +227,7 @@ struct GeminiStatusProbeAPITests {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
@Test
|
|
230
|
-
func
|
|
230
|
+
func `reports parse errors for invalid payload`() async throws {
|
|
231
231
|
let env = try GeminiTestEnvironment()
|
|
232
232
|
defer { env.cleanup() }
|
|
233
233
|
try env.writeCredentials(
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Testing
|
|
4
4
|
|
|
5
|
-
@Suite(
|
|
5
|
+
@Suite(.serialized)
|
|
6
6
|
struct GeminiStatusProbePlanTests {
|
|
7
7
|
@Test
|
|
8
|
-
func
|
|
8
|
+
func `selects project id for quota requests`() async throws {
|
|
9
9
|
let env = try GeminiTestEnvironment()
|
|
10
10
|
defer { env.cleanup() }
|
|
11
11
|
try env.writeCredentials(
|
|
@@ -56,7 +56,7 @@ struct GeminiStatusProbePlanTests {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
@Test
|
|
59
|
-
func
|
|
59
|
+
func `detects paid from standard tier`() async throws {
|
|
60
60
|
let env = try GeminiTestEnvironment()
|
|
61
61
|
defer { env.cleanup() }
|
|
62
62
|
try env.writeCredentials(
|
|
@@ -100,7 +100,7 @@ struct GeminiStatusProbePlanTests {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
@Test
|
|
103
|
-
func
|
|
103
|
+
func `detects workspace from free tier with hosted domain`() async throws {
|
|
104
104
|
let env = try GeminiTestEnvironment()
|
|
105
105
|
defer { env.cleanup() }
|
|
106
106
|
let idToken = GeminiAPITestHelpers.makeIDToken(email: "user@company.com", hostedDomain: "company.com")
|
|
@@ -145,7 +145,7 @@ struct GeminiStatusProbePlanTests {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
@Test
|
|
148
|
-
func
|
|
148
|
+
func `detects free from free tier without hosted domain`() async throws {
|
|
149
149
|
let env = try GeminiTestEnvironment()
|
|
150
150
|
defer { env.cleanup() }
|
|
151
151
|
let idToken = GeminiAPITestHelpers.makeIDToken(email: "user@gmail.com")
|
|
@@ -190,7 +190,7 @@ struct GeminiStatusProbePlanTests {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
@Test
|
|
193
|
-
func
|
|
193
|
+
func `detects legacy from legacy tier`() async throws {
|
|
194
194
|
let env = try GeminiTestEnvironment()
|
|
195
195
|
defer { env.cleanup() }
|
|
196
196
|
try env.writeCredentials(
|
|
@@ -234,7 +234,7 @@ struct GeminiStatusProbePlanTests {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
@Test
|
|
237
|
-
func
|
|
237
|
+
func `leaves blank when load code assist fails`() async throws {
|
|
238
238
|
let env = try GeminiTestEnvironment()
|
|
239
239
|
defer { env.cleanup() }
|
|
240
240
|
try env.writeCredentials(
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Testing
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
struct GeminiStatusProbeTests {
|
|
7
|
-
|
|
6
|
+
/// Sample /stats output from Gemini CLI (actual format with box-drawing chars)
|
|
8
7
|
static let sampleStatsOutput = """
|
|
9
8
|
│ Model Usage Reqs Usage left │
|
|
10
9
|
│ ─────────────────────────────────────────────────────────────────────── │
|
|
@@ -17,14 +16,14 @@ struct GeminiStatusProbeTests {
|
|
|
17
16
|
// MARK: - Legacy CLI parsing tests (kept for fallback support)
|
|
18
17
|
|
|
19
18
|
@Test
|
|
20
|
-
func
|
|
19
|
+
func `parses minimum percent from multiple models`() throws {
|
|
21
20
|
let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
|
|
22
21
|
#expect(snap.dailyPercentLeft == 99.8)
|
|
23
22
|
#expect(snap.resetDescription == "Resets in 20h 37m")
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
@Test
|
|
27
|
-
func
|
|
26
|
+
func `parses lower percent correctly`() throws {
|
|
28
27
|
let output = """
|
|
29
28
|
│ Model Usage Reqs Usage left │
|
|
30
29
|
│ gemini-2.5-flash 10 85.5% (Resets in 12h) │
|
|
@@ -36,7 +35,7 @@ struct GeminiStatusProbeTests {
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
@Test
|
|
39
|
-
func
|
|
38
|
+
func `handles zero percent usage`() throws {
|
|
40
39
|
let output = """
|
|
41
40
|
│ gemini-2.5-flash 50 0.0% (Resets in 6h) │
|
|
42
41
|
│ gemini-2.5-pro 20 15.0% (Resets in 6h) │
|
|
@@ -47,7 +46,7 @@ struct GeminiStatusProbeTests {
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
@Test
|
|
50
|
-
func
|
|
49
|
+
func `handles100 percent remaining`() throws {
|
|
51
50
|
let output = """
|
|
52
51
|
│ gemini-2.5-flash - 100.0% (Resets in 24h) │
|
|
53
52
|
"""
|
|
@@ -56,14 +55,14 @@ struct GeminiStatusProbeTests {
|
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
@Test
|
|
59
|
-
func
|
|
58
|
+
func `throws on empty output`() {
|
|
60
59
|
#expect(throws: GeminiStatusProbeError.self) {
|
|
61
60
|
try GeminiStatusProbe.parse(text: "")
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
@Test
|
|
66
|
-
func
|
|
65
|
+
func `throws on no usage data`() {
|
|
67
66
|
let output = """
|
|
68
67
|
Welcome to Gemini CLI!
|
|
69
68
|
Type /help for available commands.
|
|
@@ -74,7 +73,7 @@ struct GeminiStatusProbeTests {
|
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
@Test
|
|
77
|
-
func
|
|
76
|
+
func `strips ANSI codes before parsing`() throws {
|
|
78
77
|
// swiftlint:disable:next line_length
|
|
79
78
|
let output = "\u{1B}[32m│\u{1B}[0m gemini-2.5-flash - 75.5% (Resets in 18h) │"
|
|
80
79
|
let snap = try GeminiStatusProbe.parse(text: output)
|
|
@@ -82,7 +81,7 @@ struct GeminiStatusProbeTests {
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
@Test
|
|
85
|
-
func
|
|
84
|
+
func `preserves raw text`() throws {
|
|
86
85
|
let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
|
|
87
86
|
#expect(snap.rawText == Self.sampleStatsOutput)
|
|
88
87
|
#expect(snap.accountEmail == nil) // Legacy parse doesn't extract email
|
|
@@ -90,7 +89,7 @@ struct GeminiStatusProbeTests {
|
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
@Test
|
|
93
|
-
func
|
|
92
|
+
func `parses various reset descriptions`() throws {
|
|
94
93
|
let cases: [(String, String)] = [
|
|
95
94
|
("Resets in 24h", "Resets in 24h"),
|
|
96
95
|
("Resets in 1h 30m", "Resets in 1h 30m"),
|
|
@@ -106,7 +105,7 @@ struct GeminiStatusProbeTests {
|
|
|
106
105
|
}
|
|
107
106
|
|
|
108
107
|
@Test
|
|
109
|
-
func
|
|
108
|
+
func `throws not logged in on auth prompt`() {
|
|
110
109
|
let authOutputs = [
|
|
111
110
|
"Waiting for auth... (Press ESC or CTRL+C to cancel)",
|
|
112
111
|
"Login with Google\nUse Gemini API key",
|
|
@@ -122,7 +121,7 @@ struct GeminiStatusProbeTests {
|
|
|
122
121
|
// MARK: - Model quota grouping tests
|
|
123
122
|
|
|
124
123
|
@Test
|
|
125
|
-
func
|
|
124
|
+
func `parses models into quota array`() throws {
|
|
126
125
|
let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
|
|
127
126
|
// Should parse multiple models (exact count may change as Google adds/removes models)
|
|
128
127
|
#expect(snap.modelQuotas.count >= 2)
|
|
@@ -134,14 +133,14 @@ struct GeminiStatusProbeTests {
|
|
|
134
133
|
}
|
|
135
134
|
|
|
136
135
|
@Test
|
|
137
|
-
func
|
|
136
|
+
func `lowest percent left returns minimum`() throws {
|
|
138
137
|
let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
|
|
139
138
|
// Flash models are 99.8%, Pro models are 100%, so min should be 99.8
|
|
140
139
|
#expect(snap.lowestPercentLeft == 99.8)
|
|
141
140
|
}
|
|
142
141
|
|
|
143
142
|
@Test
|
|
144
|
-
func
|
|
143
|
+
func `tier grouping by keyword`() throws {
|
|
145
144
|
// Test that flash/pro keyword filtering works (model names may change)
|
|
146
145
|
let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
|
|
147
146
|
|
|
@@ -154,7 +153,7 @@ struct GeminiStatusProbeTests {
|
|
|
154
153
|
}
|
|
155
154
|
|
|
156
155
|
@Test
|
|
157
|
-
func
|
|
156
|
+
func `tier minimum calculation`() throws {
|
|
158
157
|
// Use controlled test data to verify min-per-tier logic
|
|
159
158
|
// Model names must start with "gemini-" to match the parser regex
|
|
160
159
|
let output = """
|
|
@@ -176,7 +175,7 @@ struct GeminiStatusProbeTests {
|
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
@Test
|
|
179
|
-
func
|
|
178
|
+
func `quotas have reset descriptions`() throws {
|
|
180
179
|
let snap = try GeminiStatusProbe.parse(text: Self.sampleStatsOutput)
|
|
181
180
|
|
|
182
181
|
// At least some quotas should have reset descriptions
|
|
@@ -185,7 +184,7 @@ struct GeminiStatusProbeTests {
|
|
|
185
184
|
}
|
|
186
185
|
|
|
187
186
|
@Test
|
|
188
|
-
func
|
|
187
|
+
func `to usage snapshot preserves model labels and tertiary window`() throws {
|
|
189
188
|
let output = """
|
|
190
189
|
│ gemini-2.5-pro 2 40.0% (Resets in 6h) │
|
|
191
190
|
│ gemini-2.5-flash 5 65.0% (Resets in 3h) │
|
|
@@ -202,7 +201,7 @@ struct GeminiStatusProbeTests {
|
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
@Test
|
|
205
|
-
func
|
|
204
|
+
func `to usage snapshot falls back when pro tier is missing`() throws {
|
|
206
205
|
let output = """
|
|
207
206
|
│ gemini-2.5-flash 2 90.0% (Resets in 6h) │
|
|
208
207
|
│ gemini-2.5-flash-lite 3 25.0% (Resets in 6h) │
|
|
@@ -216,7 +215,7 @@ struct GeminiStatusProbeTests {
|
|
|
216
215
|
// MARK: - Live API test
|
|
217
216
|
|
|
218
217
|
@Test
|
|
219
|
-
func
|
|
218
|
+
func `live gemini fetch`() async throws {
|
|
220
219
|
guard ProcessInfo.processInfo.environment["LIVE_GEMINI_FETCH"] == "1" else {
|
|
221
220
|
return
|
|
222
221
|
}
|
|
@@ -2,13 +2,12 @@ import Foundation
|
|
|
2
2
|
import Testing
|
|
3
3
|
@testable import Runic
|
|
4
4
|
|
|
5
|
-
@Suite
|
|
6
5
|
@MainActor
|
|
7
6
|
struct GoogleWorkspaceStatusTests {
|
|
8
7
|
private let productID = "npdyhgECDJ6tB66MxXyo"
|
|
9
8
|
|
|
10
9
|
@Test
|
|
11
|
-
func
|
|
10
|
+
func `parse workspace status selects worst incident`() throws {
|
|
12
11
|
let data = Data(#"""
|
|
13
12
|
[
|
|
14
13
|
{
|
|
@@ -47,7 +46,7 @@ struct GoogleWorkspaceStatusTests {
|
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
@Test
|
|
50
|
-
func
|
|
49
|
+
func `parse workspace status ignores resolved incidents`() throws {
|
|
51
50
|
let data = Data(#"""
|
|
52
51
|
[
|
|
53
52
|
{
|
|
@@ -2,8 +2,8 @@ import KeyboardShortcuts
|
|
|
2
2
|
import Testing
|
|
3
3
|
|
|
4
4
|
@MainActor
|
|
5
|
-
|
|
6
|
-
@Test func
|
|
5
|
+
struct KeyboardShortcutsBundleTests {
|
|
6
|
+
@Test func `recorder initializes without crashing`() {
|
|
7
7
|
_ = KeyboardShortcuts.RecorderCocoa(for: .init("test.keyboardshortcuts.bundle"))
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Testing
|
|
4
4
|
import XCTest
|
|
5
5
|
@testable import Runic
|
|
6
6
|
|
|
7
|
-
@Suite(
|
|
7
|
+
@Suite(.serialized)
|
|
8
8
|
struct LiveAccountTests {
|
|
9
9
|
@Test(.disabled("Set LIVE_TEST=1 to run live Codex account checks."))
|
|
10
|
-
func
|
|
10
|
+
func `codex account email is present`() async throws {
|
|
11
11
|
guard ProcessInfo.processInfo.environment["LIVE_TEST"] == "1" else { return }
|
|
12
12
|
|
|
13
13
|
let fetcher = UsageFetcher()
|