@sriinnu/runic 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +8 -1
- package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
- package/RuniciOS/Views/ProviderDetailView.swift +9 -9
- package/RuniciOS/Views/ProviderListView.swift +10 -10
- package/RuniciOS/Widgets/LargeWidget.swift +14 -14
- package/RuniciOS/Widgets/LiveActivity.swift +19 -19
- package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
- package/RuniciOS/Widgets/MediumWidget.swift +10 -10
- package/RuniciOS/Widgets/RunicWidget.swift +5 -5
- package/RuniciOS/Widgets/SmallWidget.swift +9 -9
- package/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +7 -3
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +18 -13
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
- package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
- package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
- package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
- package/Sources/Runic/Core/RunicColors.swift +6 -6
- package/Sources/Runic/Core/RunicSpacing.swift +7 -7
- package/Sources/Runic/Core/RunicTypography.swift +206 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SettingsStore.swift +89 -51
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore.swift +187 -161
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +1 -2
- package/Sources/Runic/Core/UsageExporter.swift +8 -4
- package/Sources/Runic/ProviderRegistry.swift +1 -1
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
- package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
- package/Sources/Runic/RunicApp.swift +46 -36
- package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
- package/Sources/Runic/UsagePaceText.swift +1 -1
- package/Sources/Runic/UsageProgressBar.swift +13 -9
- package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
- package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +12 -10
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
- package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
- package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +18 -15
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +22 -17
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +44 -39
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
- package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
- package/Sources/RunicCLI/CLIEntry.swift +83 -42
- package/Sources/RunicCLI/CLIRenderer.swift +1 -1
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
- package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
- package/Sources/RunicClaudeWebProbe/main.swift +1 -1
- package/Sources/RunicCore/AlertRuleStore.swift +20 -20
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
- package/Sources/RunicCore/CostUsageModels.swift +1 -1
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +73 -75
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
- package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
- package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +5 -2
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +7 -7
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +23 -10
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +26 -13
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +48 -21
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
- package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
- package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
- package/Sources/RunicCore/Team/TeamModels.swift +111 -110
- package/Sources/RunicCore/Team/TeamStore.swift +126 -109
- package/Sources/RunicCore/UsageFetcher.swift +2 -2
- package/Sources/RunicCore/UsageFormatter.swift +25 -25
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +27 -13
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +11 -3
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +5 -2
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
- package/Tests/RunicTests/AppDelegateTests.swift +8 -3
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
- package/Tests/RunicTests/CLICostTests.swift +6 -7
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
- package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
- package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
- package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
- package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
- package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
- package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
- package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
- package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
- package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
- package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
- package/Tests/RunicTests/InstallOriginTests.swift +1 -2
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
- package/Tests/RunicTests/LiveAccountTests.swift +3 -3
- package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
- package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
- package/Tests/RunicTests/PathBuilderTests.swift +14 -15
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
- package/Tests/RunicTests/RunicTests.swift +6 -7
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
- package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
- package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
- package/Tests/RunicTests/StatusMenuTests.swift +63 -22
- package/Tests/RunicTests/StatusProbeTests.swift +28 -29
- package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
- package/Tests/RunicTests/TextParsingTests.swift +1 -2
- package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
- package/Tests/RunicTests/UsagePaceTests.swift +5 -6
- package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
- package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
- package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
- package/appcast.xml +36 -76
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
import AppKit
|
|
2
|
-
import
|
|
2
|
+
import CoreGraphics
|
|
3
3
|
import Foundation
|
|
4
4
|
import Observation
|
|
5
|
+
import RunicCore
|
|
5
6
|
import Silo
|
|
6
|
-
import CoreGraphics
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
8
|
+
// **UsageStore** - Main state management for AI provider usage tracking
|
|
9
|
+
//
|
|
10
|
+
// **Purpose:**
|
|
11
|
+
// Central store managing all provider usage data, snapshots, errors, and refresh logic.
|
|
12
|
+
// This is the single source of truth for the app's state.
|
|
13
|
+
//
|
|
14
|
+
// **Responsibilities:**
|
|
15
|
+
// - Fetch usage snapshots from all providers (Claude, Codex, Cursor, Gemini, etc.)
|
|
16
|
+
// - Manage authentication state and cookies via Silo
|
|
17
|
+
// - Track token usage across all providers
|
|
18
|
+
// - Coordinate "ping" operations (manual and automatic)
|
|
19
|
+
// - Handle errors and stale data detection
|
|
20
|
+
// - Provide Observable state for SwiftUI views
|
|
21
|
+
//
|
|
22
|
+
// **Performance Philosophy:**
|
|
23
|
+
// - **Zero Token Leakage** - Always use cookies/cached data first before API calls
|
|
24
|
+
// - **Single Ping Strategy** - One ping per provider per session, cache rest
|
|
25
|
+
// - **Stale Detection** - Mark data stale after 5 minutes (PerformanceConstants.staleDuration)
|
|
26
|
+
//
|
|
27
|
+
// **Known Issues:**
|
|
28
|
+
// - **God Class** - 2089 lines, needs refactoring (see PLAN.md)
|
|
29
|
+
// - Planned split: `UsageStateStore`, `UsageFetchingActor`, `TokenUsageService`
|
|
30
|
+
//
|
|
31
|
+
// **Dependencies:**
|
|
32
|
+
// - `RunicCore` - Provider descriptors and status probes
|
|
33
|
+
// - `Silo` - Browser cookie access
|
|
34
|
+
// - `Observation` - SwiftUI state updates
|
|
35
|
+
//
|
|
36
|
+
// **Usage:**
|
|
37
|
+
// ```swift
|
|
38
|
+
// @Observable
|
|
39
|
+
// final class UsageStore {
|
|
40
|
+
// var snapshots: [UsageProvider: ProviderSnapshot] = [:]
|
|
41
|
+
// var errors: [UsageProvider: Error] = [:]
|
|
42
|
+
//
|
|
43
|
+
// func ping(provider: UsageProvider) async { ... }
|
|
44
|
+
// }
|
|
45
|
+
// ```
|
|
46
46
|
|
|
47
47
|
// MARK: - Observation helpers
|
|
48
48
|
|
|
@@ -305,8 +305,8 @@ struct ConsecutiveFailureGate {
|
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
-
struct UsageLedgerAnomalySummary:
|
|
309
|
-
enum Severity: Int,
|
|
308
|
+
struct UsageLedgerAnomalySummary: Hashable {
|
|
309
|
+
enum Severity: Int, Hashable {
|
|
310
310
|
case elevated = 1
|
|
311
311
|
case high = 2
|
|
312
312
|
case critical = 3
|
|
@@ -320,8 +320,8 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
|
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
323
|
-
struct MetricAnomaly:
|
|
324
|
-
enum Metric: String,
|
|
323
|
+
struct MetricAnomaly: Hashable {
|
|
324
|
+
enum Metric: String, Hashable {
|
|
325
325
|
case tokens
|
|
326
326
|
case spend
|
|
327
327
|
|
|
@@ -340,8 +340,8 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
|
|
|
340
340
|
let percentIncrease: Double
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
struct Explanation:
|
|
344
|
-
struct Factor:
|
|
343
|
+
struct Explanation: Hashable {
|
|
344
|
+
struct Factor: Hashable {
|
|
345
345
|
let metric: MetricAnomaly.Metric
|
|
346
346
|
let severity: Severity
|
|
347
347
|
let percentIncrease: Double
|
|
@@ -353,6 +353,7 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
|
|
|
353
353
|
let primaryFactor: Factor
|
|
354
354
|
let contributingFactors: [Factor]
|
|
355
355
|
}
|
|
356
|
+
|
|
356
357
|
let provider: UsageProvider
|
|
357
358
|
let baselineDays: Int
|
|
358
359
|
let tokenAnomaly: MetricAnomaly?
|
|
@@ -360,7 +361,7 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
|
|
|
360
361
|
|
|
361
362
|
var primaryAnomaly: MetricAnomaly? {
|
|
362
363
|
[self.tokenAnomaly, self.spendAnomaly]
|
|
363
|
-
.compactMap
|
|
364
|
+
.compactMap(\.self)
|
|
364
365
|
.max { lhs, rhs in
|
|
365
366
|
if lhs.severity != rhs.severity {
|
|
366
367
|
return lhs.severity.rawValue < rhs.severity.rawValue
|
|
@@ -370,11 +371,11 @@ struct UsageLedgerAnomalySummary: Sendable, Hashable {
|
|
|
370
371
|
}
|
|
371
372
|
|
|
372
373
|
func secondaryAnomaly(excluding metric: MetricAnomaly.Metric) -> MetricAnomaly? {
|
|
373
|
-
let candidates = [self.tokenAnomaly, self.spendAnomaly].compactMap
|
|
374
|
+
let candidates = [self.tokenAnomaly, self.spendAnomaly].compactMap(\.self)
|
|
374
375
|
guard !candidates.isEmpty else { return nil }
|
|
375
376
|
return candidates.first { $0.metric != metric }
|
|
376
377
|
}
|
|
377
|
-
|
|
378
|
+
|
|
378
379
|
var explanation: Explanation? {
|
|
379
380
|
guard let primary = self.primaryAnomaly else { return nil }
|
|
380
381
|
let primaryFactor = self.factor(from: primary)
|
|
@@ -522,7 +523,7 @@ enum UsageLedgerAnomalyDetector {
|
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
|
|
525
|
-
struct ProviderHistoryDaySnapshot:
|
|
526
|
+
struct ProviderHistoryDaySnapshot: Hashable, Identifiable {
|
|
526
527
|
let dayStart: Date
|
|
527
528
|
let totals: UsageLedgerTotals
|
|
528
529
|
let requestCount: Int
|
|
@@ -532,10 +533,12 @@ struct ProviderHistoryDaySnapshot: Sendable, Hashable, Identifiable {
|
|
|
532
533
|
let modelSummaries: [UsageLedgerModelSummary]
|
|
533
534
|
let projectSummaries: [UsageLedgerProjectSummary]
|
|
534
535
|
|
|
535
|
-
var id: Date {
|
|
536
|
+
var id: Date {
|
|
537
|
+
self.dayStart
|
|
538
|
+
}
|
|
536
539
|
}
|
|
537
540
|
|
|
538
|
-
struct ProviderHistoryMonthSnapshot:
|
|
541
|
+
struct ProviderHistoryMonthSnapshot: Hashable {
|
|
539
542
|
let provider: UsageProvider
|
|
540
543
|
let monthStart: Date
|
|
541
544
|
let generatedAt: Date
|
|
@@ -544,6 +547,7 @@ struct ProviderHistoryMonthSnapshot: Sendable, Hashable {
|
|
|
544
547
|
let note: String?
|
|
545
548
|
let error: String?
|
|
546
549
|
}
|
|
550
|
+
|
|
547
551
|
@MainActor
|
|
548
552
|
@Observable
|
|
549
553
|
final class UsageStore {
|
|
@@ -629,16 +633,22 @@ final class UsageStore {
|
|
|
629
633
|
@ObservationIgnored private var tokenTimerTask: Task<Void, Never>?
|
|
630
634
|
@ObservationIgnored private var tokenRefreshSequenceTask: Task<Void, Never>?
|
|
631
635
|
@ObservationIgnored private var ledgerRefreshTask: Task<Void, Never>?
|
|
632
|
-
@ObservationIgnored private var providerHistoryMonthCache: [
|
|
636
|
+
@ObservationIgnored private var providerHistoryMonthCache: [
|
|
637
|
+
UsageProvider: [String: ProviderHistoryMonthCacheEntry]
|
|
638
|
+
] =
|
|
639
|
+
[:]
|
|
633
640
|
@ObservationIgnored private var lastKnownSessionRemaining: [UsageProvider: Double] = [:]
|
|
634
641
|
@ObservationIgnored private(set) var lastTokenFetchAt: [UsageProvider: Date] = [:]
|
|
635
642
|
@ObservationIgnored private let tokenFetchTTL: TimeInterval = 60 * 60
|
|
636
643
|
@ObservationIgnored private let tokenFetchTimeout: TimeInterval = 10 * 60
|
|
637
644
|
@ObservationIgnored private let ledgerRefreshTTL: TimeInterval = 90
|
|
638
|
-
@ObservationIgnored private var ledgerMaxAgeDays: Int {
|
|
645
|
+
@ObservationIgnored private var ledgerMaxAgeDays: Int {
|
|
646
|
+
self.settings.ledgerMaxAgeDays
|
|
647
|
+
}
|
|
648
|
+
|
|
639
649
|
@ObservationIgnored private let providerHistoryCacheTTL: TimeInterval = 90
|
|
640
650
|
@ObservationIgnored private let providerHistoryMaxScanDays: Int = 180
|
|
641
|
-
@ObservationIgnored nonisolated(unsafe)
|
|
651
|
+
@ObservationIgnored private nonisolated(unsafe) let performanceStorage: PerformanceStorageImpl?
|
|
642
652
|
|
|
643
653
|
init(
|
|
644
654
|
fetcher: UsageFetcher,
|
|
@@ -928,7 +938,7 @@ final class UsageStore {
|
|
|
928
938
|
func ledgerProjectBreakdown(for provider: UsageProvider) -> [UsageLedgerProjectSummary] {
|
|
929
939
|
self.ledgerProjectBreakdowns[provider] ?? []
|
|
930
940
|
}
|
|
931
|
-
|
|
941
|
+
|
|
932
942
|
func ledgerSpendForecast(for provider: UsageProvider) -> UsageLedgerSpendForecast? {
|
|
933
943
|
self.ledgerSpendForecasts[provider]
|
|
934
944
|
}
|
|
@@ -945,7 +955,6 @@ final class UsageStore {
|
|
|
945
955
|
self.ledgerAnomalies[provider]
|
|
946
956
|
}
|
|
947
957
|
|
|
948
|
-
|
|
949
958
|
func ledgerError(for provider: UsageProvider) -> String? {
|
|
950
959
|
self.ledgerErrors[provider]
|
|
951
960
|
}
|
|
@@ -1084,27 +1093,27 @@ final class UsageStore {
|
|
|
1084
1093
|
case .codex:
|
|
1085
1094
|
return CodexUsageLogSource(maxAgeDays: maxAgeDays, now: now)
|
|
1086
1095
|
case .copilot,
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1096
|
+
.gemini,
|
|
1097
|
+
.antigravity,
|
|
1098
|
+
.cursor,
|
|
1099
|
+
.factory,
|
|
1100
|
+
.zai,
|
|
1101
|
+
.minimax,
|
|
1102
|
+
.openrouter,
|
|
1103
|
+
.groq,
|
|
1104
|
+
.deepseek,
|
|
1105
|
+
.fireworks,
|
|
1106
|
+
.mistral,
|
|
1107
|
+
.perplexity,
|
|
1108
|
+
.kimi,
|
|
1109
|
+
.auggie,
|
|
1110
|
+
.together,
|
|
1111
|
+
.cohere,
|
|
1112
|
+
.xai,
|
|
1113
|
+
.cerebras,
|
|
1114
|
+
.sambanova,
|
|
1115
|
+
.azure,
|
|
1116
|
+
.bedrock:
|
|
1108
1117
|
return self.providerOTelHistorySource(provider: provider, now: now, maxAgeDays: maxAgeDays)
|
|
1109
1118
|
@unknown default:
|
|
1110
1119
|
return nil
|
|
@@ -1131,7 +1140,7 @@ final class UsageStore {
|
|
|
1131
1140
|
minTimestamp: cutoff)
|
|
1132
1141
|
}
|
|
1133
1142
|
|
|
1134
|
-
private struct UsageLedgerProviderFilterSource: UsageLedgerSource
|
|
1143
|
+
private struct UsageLedgerProviderFilterSource: UsageLedgerSource {
|
|
1135
1144
|
private let source: any UsageLedgerSource
|
|
1136
1145
|
private let provider: UsageProvider
|
|
1137
1146
|
private let minTimestamp: Date?
|
|
@@ -1165,7 +1174,7 @@ final class UsageStore {
|
|
|
1165
1174
|
Self.splitPathList(env["RUNIC_\(providerKey)_OTEL_GENAI_LOG_PATH"]),
|
|
1166
1175
|
Self.splitPathList(env["RUNIC_\(providerKey)_OTEL_LOG_PATHS"]),
|
|
1167
1176
|
Self.splitPathList(env["RUNIC_\(providerKey)_OTEL_LOG_PATH"]),
|
|
1168
|
-
].flatMap
|
|
1177
|
+
].flatMap(\.self)
|
|
1169
1178
|
|
|
1170
1179
|
let urls = candidatePaths.compactMap(Self.expandTildePath)
|
|
1171
1180
|
return Self.discoverOTelLedgerFiles(from: urls)
|
|
@@ -1250,7 +1259,7 @@ final class UsageStore {
|
|
|
1250
1259
|
error: nil)
|
|
1251
1260
|
}
|
|
1252
1261
|
|
|
1253
|
-
nonisolated
|
|
1262
|
+
private nonisolated static func providerHistoryDays(
|
|
1254
1263
|
entries: [UsageLedgerEntry],
|
|
1255
1264
|
timeZone: TimeZone) -> [ProviderHistoryDaySnapshot]
|
|
1256
1265
|
{
|
|
@@ -1377,12 +1386,11 @@ final class UsageStore {
|
|
|
1377
1386
|
|
|
1378
1387
|
// Track successful latency
|
|
1379
1388
|
await self.trackLatency(
|
|
1380
|
-
provider: .codex,
|
|
1389
|
+
provider: .codex, // TODO: Add custom provider tracking
|
|
1381
1390
|
requestID: requestID,
|
|
1382
1391
|
startTime: startTime,
|
|
1383
1392
|
endTime: endTime,
|
|
1384
|
-
success: true
|
|
1385
|
-
)
|
|
1393
|
+
success: true)
|
|
1386
1394
|
|
|
1387
1395
|
let snapshot = CustomProviderSnapshot.from(usageData: usageData.toCustomUsageData(), config: config)
|
|
1388
1396
|
await MainActor.run {
|
|
@@ -1394,13 +1402,12 @@ final class UsageStore {
|
|
|
1394
1402
|
|
|
1395
1403
|
// Track failed latency and error
|
|
1396
1404
|
await self.trackLatency(
|
|
1397
|
-
provider: .codex,
|
|
1405
|
+
provider: .codex, // TODO: Add custom provider tracking
|
|
1398
1406
|
requestID: requestID,
|
|
1399
1407
|
startTime: startTime,
|
|
1400
1408
|
endTime: endTime,
|
|
1401
|
-
success: false
|
|
1402
|
-
)
|
|
1403
|
-
await self.trackError(provider: .codex, error: error) // TODO: Add custom provider tracking
|
|
1409
|
+
success: false)
|
|
1410
|
+
await self.trackError(provider: .codex, error: error) // TODO: Add custom provider tracking
|
|
1404
1411
|
|
|
1405
1412
|
await MainActor.run {
|
|
1406
1413
|
self.customProviderErrors[id] = error.localizedDescription
|
|
@@ -1534,11 +1541,11 @@ final class UsageStore {
|
|
|
1534
1541
|
private func systemPauseDetailText(_ reason: AutoRefreshSuspensionReason) -> String {
|
|
1535
1542
|
switch reason {
|
|
1536
1543
|
case .systemSleep:
|
|
1537
|
-
|
|
1544
|
+
"your Mac went to sleep"
|
|
1538
1545
|
case .screenSleep:
|
|
1539
|
-
|
|
1546
|
+
"the display went to sleep"
|
|
1540
1547
|
case .sessionInactive:
|
|
1541
|
-
|
|
1548
|
+
"your session became inactive"
|
|
1542
1549
|
}
|
|
1543
1550
|
}
|
|
1544
1551
|
|
|
@@ -1628,11 +1635,11 @@ final class UsageStore {
|
|
|
1628
1635
|
func windowChanged(_ lhs: RateWindow?, _ rhs: RateWindow?) -> Bool {
|
|
1629
1636
|
switch (lhs, rhs) {
|
|
1630
1637
|
case (nil, nil):
|
|
1631
|
-
|
|
1638
|
+
false
|
|
1632
1639
|
case let (l?, r?):
|
|
1633
|
-
|
|
1640
|
+
abs(l.usedPercent - r.usedPercent) >= 0.1
|
|
1634
1641
|
case (nil, _), (_, nil):
|
|
1635
|
-
|
|
1642
|
+
true
|
|
1636
1643
|
}
|
|
1637
1644
|
}
|
|
1638
1645
|
|
|
@@ -1670,7 +1677,8 @@ final class UsageStore {
|
|
|
1670
1677
|
if self.ledgerRefreshTask != nil { return }
|
|
1671
1678
|
|
|
1672
1679
|
// Immediately populate from cache so the UI has data before the background scan.
|
|
1673
|
-
let providersToCache = providers
|
|
1680
|
+
let providersToCache = providers
|
|
1681
|
+
.filter { self.ledgerAllDailySummaries[$0] == nil || self.ledgerAllDailySummaries[$0]?.isEmpty == true }
|
|
1674
1682
|
if !providersToCache.isEmpty {
|
|
1675
1683
|
Task {
|
|
1676
1684
|
let cache = LedgerCache.shared
|
|
@@ -1681,7 +1689,9 @@ final class UsageStore {
|
|
|
1681
1689
|
if !summaries.isEmpty {
|
|
1682
1690
|
await MainActor.run { [weak self] in
|
|
1683
1691
|
guard let self else { return }
|
|
1684
|
-
if self.ledgerAllDailySummaries[provider] == nil || self.ledgerAllDailySummaries[provider]
|
|
1692
|
+
if self.ledgerAllDailySummaries[provider] == nil || self.ledgerAllDailySummaries[provider]?
|
|
1693
|
+
.isEmpty == true
|
|
1694
|
+
{
|
|
1685
1695
|
self.ledgerAllDailySummaries[provider] = summaries
|
|
1686
1696
|
// Also populate today's summary from cache.
|
|
1687
1697
|
let todayStart = Calendar.current.startOfDay(for: now)
|
|
@@ -1842,7 +1852,7 @@ final class UsageStore {
|
|
|
1842
1852
|
self.ledgerRefreshTask?.cancel()
|
|
1843
1853
|
}
|
|
1844
1854
|
|
|
1845
|
-
private struct LedgerRefreshResult
|
|
1855
|
+
private struct LedgerRefreshResult {
|
|
1846
1856
|
let dailyByProvider: [UsageProvider: UsageLedgerDailySummary]
|
|
1847
1857
|
let allDailySummariesByProvider: [UsageProvider: [UsageLedgerDailySummary]]
|
|
1848
1858
|
let hourlySummariesByProvider: [UsageProvider: [UsageLedgerHourlySummary]]
|
|
@@ -1870,7 +1880,7 @@ final class UsageStore {
|
|
|
1870
1880
|
now: Date,
|
|
1871
1881
|
inactiveProviders: Set<UsageProvider>) -> [(UsageProvider, any UsageLedgerSource)]
|
|
1872
1882
|
{
|
|
1873
|
-
|
|
1883
|
+
UsageProvider.allCases.compactMap { provider in
|
|
1874
1884
|
guard self.isEnabled(provider), !inactiveProviders.contains(provider) else { return nil }
|
|
1875
1885
|
guard let source = self.providerHistorySource(
|
|
1876
1886
|
provider: provider,
|
|
@@ -1886,23 +1896,23 @@ final class UsageStore {
|
|
|
1886
1896
|
now: Date) async -> LedgerRefreshResult
|
|
1887
1897
|
{
|
|
1888
1898
|
guard !sources.isEmpty else {
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1899
|
+
return LedgerRefreshResult(
|
|
1900
|
+
dailyByProvider: [:],
|
|
1901
|
+
allDailySummariesByProvider: [:],
|
|
1902
|
+
hourlySummariesByProvider: [:],
|
|
1903
|
+
activeBlocksByProvider: [:],
|
|
1904
|
+
topModelsByProvider: [:],
|
|
1905
|
+
topProjectsByProvider: [:],
|
|
1906
|
+
modelBreakdownsByProvider: [:],
|
|
1907
|
+
projectBreakdownsByProvider: [:],
|
|
1908
|
+
spendForecastsByProvider: [:],
|
|
1909
|
+
projectSpendForecastsByProvider: [:],
|
|
1910
|
+
topProjectSpendForecastsByProvider: [:],
|
|
1911
|
+
anomaliesByProvider: [:],
|
|
1912
|
+
errorsByProvider: [:],
|
|
1913
|
+
lastActivityByProvider: [:],
|
|
1914
|
+
updatedAt: now,
|
|
1915
|
+
providers: [])
|
|
1906
1916
|
}
|
|
1907
1917
|
|
|
1908
1918
|
let providers = sources.map(\.0)
|
|
@@ -2006,10 +2016,10 @@ final class UsageStore {
|
|
|
2006
2016
|
projectBreakdownsByProvider[summary.provider, default: []].append(summary)
|
|
2007
2017
|
}
|
|
2008
2018
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2019
|
+
let providerForecasts = UsageLedgerAggregator.providerSpendForecasts(
|
|
2020
|
+
entries: entries,
|
|
2021
|
+
now: now,
|
|
2022
|
+
timeZone: timeZone)
|
|
2013
2023
|
var spendForecastsByProvider: [UsageProvider: UsageLedgerSpendForecast] = [:]
|
|
2014
2024
|
for forecast in providerForecasts where spendForecastsByProvider[forecast.provider] == nil {
|
|
2015
2025
|
spendForecastsByProvider[forecast.provider] = forecast
|
|
@@ -2032,34 +2042,34 @@ final class UsageStore {
|
|
|
2032
2042
|
}
|
|
2033
2043
|
|
|
2034
2044
|
var topProjectSpendForecastsByProvider: [UsageProvider: UsageLedgerSpendForecast] = [:]
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2045
|
+
for (provider, summary) in topProjectsByProvider {
|
|
2046
|
+
guard let forecasts = projectSpendForecastsByProvider[provider] else { continue }
|
|
2047
|
+
if let matched = self.matchingProjectForecast(for: summary, forecasts: forecasts) {
|
|
2048
|
+
topProjectSpendForecastsByProvider[provider] = matched
|
|
2049
|
+
}
|
|
2039
2050
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
calendar: calendar)
|
|
2051
|
+
let anomaliesByProvider = UsageLedgerAnomalyDetector.summaries(
|
|
2052
|
+
dailySummaries: dailySummaries,
|
|
2053
|
+
now: now,
|
|
2054
|
+
calendar: calendar)
|
|
2045
2055
|
|
|
2046
2056
|
return LedgerRefreshResult(
|
|
2047
2057
|
dailyByProvider: dailyByProvider,
|
|
2048
2058
|
allDailySummariesByProvider: allDailySummariesByProvider,
|
|
2049
2059
|
hourlySummariesByProvider: hourlySummariesByProvider,
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2060
|
+
activeBlocksByProvider: activeByProvider,
|
|
2061
|
+
topModelsByProvider: topModelsByProvider,
|
|
2062
|
+
topProjectsByProvider: topProjectsByProvider,
|
|
2063
|
+
modelBreakdownsByProvider: modelBreakdownsByProvider,
|
|
2064
|
+
projectBreakdownsByProvider: projectBreakdownsByProvider,
|
|
2065
|
+
spendForecastsByProvider: spendForecastsByProvider,
|
|
2066
|
+
projectSpendForecastsByProvider: projectSpendForecastsByProvider,
|
|
2067
|
+
topProjectSpendForecastsByProvider: topProjectSpendForecastsByProvider,
|
|
2068
|
+
anomaliesByProvider: anomaliesByProvider,
|
|
2069
|
+
errorsByProvider: errors,
|
|
2070
|
+
lastActivityByProvider: lastActivityByProvider,
|
|
2071
|
+
updatedAt: now,
|
|
2072
|
+
providers: providers)
|
|
2063
2073
|
}
|
|
2064
2074
|
|
|
2065
2075
|
private func projectNameOverridesFromBudgets() -> [String: String] {
|
|
@@ -2806,9 +2816,11 @@ extension UsageStore {
|
|
|
2806
2816
|
? nil : self.settings.azureOpenAIAPIToken,
|
|
2807
2817
|
endpoint: self.settings.azureOpenAIEndpoint.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
2808
2818
|
? nil : self.settings.azureOpenAIEndpoint,
|
|
2809
|
-
deployment: self.settings.azureOpenAIDeployment.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2819
|
+
deployment: self.settings.azureOpenAIDeployment.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2820
|
+
.isEmpty
|
|
2810
2821
|
? nil : self.settings.azureOpenAIDeployment,
|
|
2811
|
-
apiVersion: self.settings.azureOpenAIAPIVersion.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2822
|
+
apiVersion: self.settings.azureOpenAIAPIVersion.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2823
|
+
.isEmpty
|
|
2812
2824
|
? nil : self.settings.azureOpenAIAPIVersion),
|
|
2813
2825
|
bedrock: ProviderSettingsSnapshot.BedrockProviderSettings(
|
|
2814
2826
|
region: self.settings.bedrockRegion.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
@@ -3140,8 +3152,8 @@ extension UsageStore {
|
|
|
3140
3152
|
context: ProviderFetchContext,
|
|
3141
3153
|
settings: ProviderSettingsSnapshot) async -> String
|
|
3142
3154
|
{
|
|
3143
|
-
var lines
|
|
3144
|
-
"provider=\(provider.rawValue)"
|
|
3155
|
+
var lines = [
|
|
3156
|
+
"provider=\(provider.rawValue)",
|
|
3145
3157
|
]
|
|
3146
3158
|
|
|
3147
3159
|
lines.append(contentsOf: self.debugCredentialLines(for: provider, settings: settings))
|
|
@@ -3186,38 +3198,54 @@ extension UsageStore {
|
|
|
3186
3198
|
resolution: ProviderTokenResolver.minimaxGroupResolution()),
|
|
3187
3199
|
]
|
|
3188
3200
|
case .openrouter:
|
|
3189
|
-
return [self.debugTokenSummary(
|
|
3201
|
+
return [self.debugTokenSummary(
|
|
3202
|
+
provider: "openrouter",
|
|
3203
|
+
resolution: ProviderTokenResolver.openRouterResolution())]
|
|
3190
3204
|
case .groq:
|
|
3191
3205
|
return [self.debugTokenSummary(provider: "groq", resolution: ProviderTokenResolver.groqResolution())]
|
|
3192
3206
|
case .deepseek:
|
|
3193
|
-
return [self.debugTokenSummary(
|
|
3207
|
+
return [self.debugTokenSummary(
|
|
3208
|
+
provider: "deepseek",
|
|
3209
|
+
resolution: ProviderTokenResolver.deepSeekResolution())]
|
|
3194
3210
|
case .fireworks:
|
|
3195
|
-
return [self.debugTokenSummary(
|
|
3211
|
+
return [self.debugTokenSummary(
|
|
3212
|
+
provider: "fireworks",
|
|
3213
|
+
resolution: ProviderTokenResolver.fireworksResolution())]
|
|
3196
3214
|
case .mistral:
|
|
3197
3215
|
return [self.debugTokenSummary(provider: "mistral", resolution: ProviderTokenResolver.mistralResolution())]
|
|
3198
3216
|
case .perplexity:
|
|
3199
|
-
return [self.debugTokenSummary(
|
|
3217
|
+
return [self.debugTokenSummary(
|
|
3218
|
+
provider: "perplexity",
|
|
3219
|
+
resolution: ProviderTokenResolver.perplexityResolution())]
|
|
3200
3220
|
case .kimi:
|
|
3201
3221
|
return [self.debugTokenSummary(provider: "kimi", resolution: ProviderTokenResolver.kimiResolution())]
|
|
3202
3222
|
case .auggie:
|
|
3203
3223
|
return [self.debugTokenSummary(provider: "auggie", resolution: ProviderTokenResolver.auggieResolution())]
|
|
3204
3224
|
case .together:
|
|
3205
|
-
return [self.debugTokenSummary(
|
|
3225
|
+
return [self.debugTokenSummary(
|
|
3226
|
+
provider: "together",
|
|
3227
|
+
resolution: ProviderTokenResolver.togetherResolution())]
|
|
3206
3228
|
case .cohere:
|
|
3207
3229
|
return [self.debugTokenSummary(provider: "cohere", resolution: ProviderTokenResolver.cohereResolution())]
|
|
3208
3230
|
case .xai:
|
|
3209
3231
|
return [self.debugTokenSummary(provider: "xai", resolution: ProviderTokenResolver.xaiResolution())]
|
|
3210
3232
|
case .cerebras:
|
|
3211
|
-
return [self.debugTokenSummary(
|
|
3233
|
+
return [self.debugTokenSummary(
|
|
3234
|
+
provider: "cerebras",
|
|
3235
|
+
resolution: ProviderTokenResolver.cerebrasResolution())]
|
|
3212
3236
|
case .sambanova:
|
|
3213
|
-
return [self.debugTokenSummary(
|
|
3237
|
+
return [self.debugTokenSummary(
|
|
3238
|
+
provider: "sambanova",
|
|
3239
|
+
resolution: ProviderTokenResolver.sambaNovaResolution())]
|
|
3214
3240
|
case .azure:
|
|
3215
3241
|
var lines: [String] = [
|
|
3216
3242
|
"azure.endpoint=\(self.debugFieldValue(settings.azure?.endpoint))",
|
|
3217
3243
|
"azure.deployment=\(self.debugFieldValue(settings.azure?.deployment))",
|
|
3218
|
-
"azure.apiVersion=\(self.debugFieldValue(settings.azure?.apiVersion))"
|
|
3244
|
+
"azure.apiVersion=\(self.debugFieldValue(settings.azure?.apiVersion))",
|
|
3219
3245
|
]
|
|
3220
|
-
lines.append(self.debugTokenSummary(
|
|
3246
|
+
lines.append(self.debugTokenSummary(
|
|
3247
|
+
provider: "azure.token",
|
|
3248
|
+
resolution: ProviderTokenResolver.azureOpenAIResolution()))
|
|
3221
3249
|
return lines
|
|
3222
3250
|
case .bedrock:
|
|
3223
3251
|
return [
|
|
@@ -3277,7 +3305,7 @@ extension UsageStore {
|
|
|
3277
3305
|
let resetsAt = providerCost.resetsAt?.description ?? "nil"
|
|
3278
3306
|
lines.append(
|
|
3279
3307
|
"provider_cost.used=\(providerCost.used) limit=\(providerCost.limit) " +
|
|
3280
|
-
|
|
3308
|
+
"currency=\(providerCost.currencyCode) period=\(providerCost.period ?? "nil") resetsAt=\(resetsAt)")
|
|
3281
3309
|
}
|
|
3282
3310
|
return lines
|
|
3283
3311
|
}
|
|
@@ -3526,8 +3554,8 @@ extension UsageStore {
|
|
|
3526
3554
|
requestID: String,
|
|
3527
3555
|
startTime: Date,
|
|
3528
3556
|
endTime: Date,
|
|
3529
|
-
success: Bool
|
|
3530
|
-
|
|
3557
|
+
success: Bool) async
|
|
3558
|
+
{
|
|
3531
3559
|
guard let storage = self.performanceStorage else { return }
|
|
3532
3560
|
|
|
3533
3561
|
let metric = LatencyMetric(
|
|
@@ -3539,8 +3567,7 @@ extension UsageStore {
|
|
|
3539
3567
|
endTime: endTime,
|
|
3540
3568
|
durationMs: Int(endTime.timeIntervalSince(startTime) * 1000),
|
|
3541
3569
|
success: success,
|
|
3542
|
-
createdAt: Date()
|
|
3543
|
-
)
|
|
3570
|
+
createdAt: Date())
|
|
3544
3571
|
|
|
3545
3572
|
try? await storage.save(latency: metric)
|
|
3546
3573
|
}
|
|
@@ -3556,8 +3583,7 @@ extension UsageStore {
|
|
|
3556
3583
|
errorType: errorType,
|
|
3557
3584
|
errorMessage: error.localizedDescription,
|
|
3558
3585
|
retryCount: 0,
|
|
3559
|
-
timestamp: Date()
|
|
3560
|
-
)
|
|
3586
|
+
timestamp: Date())
|
|
3561
3587
|
|
|
3562
3588
|
try? await storage.save(error: errorEvent)
|
|
3563
3589
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
import Security
|
|
4
4
|
|
|
5
5
|
protocol XAITokenStoring: Sendable {
|
|
@@ -64,7 +64,6 @@ struct KeychainXAITokenStore: XAITokenStoring {
|
|
|
64
64
|
addQuery[kSecValueData as String] = data
|
|
65
65
|
addQuery[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
|
66
66
|
|
|
67
|
-
|
|
68
67
|
let addStatus = SecItemAdd(addQuery as CFDictionary, nil)
|
|
69
68
|
guard addStatus == errSecSuccess else {
|
|
70
69
|
Self.log.error("Keychain add failed: \(addStatus)")
|
|
@@ -115,5 +114,4 @@ struct KeychainXAITokenStore: XAITokenStoring {
|
|
|
115
114
|
}
|
|
116
115
|
return q
|
|
117
116
|
}
|
|
118
|
-
|
|
119
117
|
}
|