@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,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
|
|
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
|
{
|
|
@@ -1289,6 +1298,15 @@ final class UsageStore {
|
|
|
1289
1298
|
}
|
|
1290
1299
|
}
|
|
1291
1300
|
|
|
1301
|
+
/// Refresh a single provider (used when user clicks Ping on a specific provider tab).
|
|
1302
|
+
func refreshSingleProvider(_ provider: UsageProvider) async {
|
|
1303
|
+
self.isRefreshing = true
|
|
1304
|
+
defer { self.isRefreshing = false }
|
|
1305
|
+
await self.refreshProvider(provider, trigger: .manual)
|
|
1306
|
+
await self.refreshStatus(provider, trigger: .manual)
|
|
1307
|
+
self.scheduleLedgerRefresh(force: true, inactiveProviders: [])
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1292
1310
|
func refresh(trigger: RefreshTrigger = .manual, forceTokenUsage: Bool = false) async {
|
|
1293
1311
|
guard !self.isRefreshing else { return }
|
|
1294
1312
|
let now = Date()
|
|
@@ -1332,7 +1350,7 @@ final class UsageStore {
|
|
|
1332
1350
|
await self.refreshCreditsIfNeeded()
|
|
1333
1351
|
}
|
|
1334
1352
|
|
|
1335
|
-
self.scheduleLedgerRefresh(force:
|
|
1353
|
+
self.scheduleLedgerRefresh(force: true, inactiveProviders: inactiveProviders)
|
|
1336
1354
|
|
|
1337
1355
|
// Refresh custom providers
|
|
1338
1356
|
await self.refreshCustomProviders()
|
|
@@ -1368,12 +1386,11 @@ final class UsageStore {
|
|
|
1368
1386
|
|
|
1369
1387
|
// Track successful latency
|
|
1370
1388
|
await self.trackLatency(
|
|
1371
|
-
provider: .codex,
|
|
1389
|
+
provider: .codex, // TODO: Add custom provider tracking
|
|
1372
1390
|
requestID: requestID,
|
|
1373
1391
|
startTime: startTime,
|
|
1374
1392
|
endTime: endTime,
|
|
1375
|
-
success: true
|
|
1376
|
-
)
|
|
1393
|
+
success: true)
|
|
1377
1394
|
|
|
1378
1395
|
let snapshot = CustomProviderSnapshot.from(usageData: usageData.toCustomUsageData(), config: config)
|
|
1379
1396
|
await MainActor.run {
|
|
@@ -1385,13 +1402,12 @@ final class UsageStore {
|
|
|
1385
1402
|
|
|
1386
1403
|
// Track failed latency and error
|
|
1387
1404
|
await self.trackLatency(
|
|
1388
|
-
provider: .codex,
|
|
1405
|
+
provider: .codex, // TODO: Add custom provider tracking
|
|
1389
1406
|
requestID: requestID,
|
|
1390
1407
|
startTime: startTime,
|
|
1391
1408
|
endTime: endTime,
|
|
1392
|
-
success: false
|
|
1393
|
-
)
|
|
1394
|
-
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
|
|
1395
1411
|
|
|
1396
1412
|
await MainActor.run {
|
|
1397
1413
|
self.customProviderErrors[id] = error.localizedDescription
|
|
@@ -1525,11 +1541,11 @@ final class UsageStore {
|
|
|
1525
1541
|
private func systemPauseDetailText(_ reason: AutoRefreshSuspensionReason) -> String {
|
|
1526
1542
|
switch reason {
|
|
1527
1543
|
case .systemSleep:
|
|
1528
|
-
|
|
1544
|
+
"your Mac went to sleep"
|
|
1529
1545
|
case .screenSleep:
|
|
1530
|
-
|
|
1546
|
+
"the display went to sleep"
|
|
1531
1547
|
case .sessionInactive:
|
|
1532
|
-
|
|
1548
|
+
"your session became inactive"
|
|
1533
1549
|
}
|
|
1534
1550
|
}
|
|
1535
1551
|
|
|
@@ -1619,11 +1635,11 @@ final class UsageStore {
|
|
|
1619
1635
|
func windowChanged(_ lhs: RateWindow?, _ rhs: RateWindow?) -> Bool {
|
|
1620
1636
|
switch (lhs, rhs) {
|
|
1621
1637
|
case (nil, nil):
|
|
1622
|
-
|
|
1638
|
+
false
|
|
1623
1639
|
case let (l?, r?):
|
|
1624
|
-
|
|
1640
|
+
abs(l.usedPercent - r.usedPercent) >= 0.1
|
|
1625
1641
|
case (nil, _), (_, nil):
|
|
1626
|
-
|
|
1642
|
+
true
|
|
1627
1643
|
}
|
|
1628
1644
|
}
|
|
1629
1645
|
|
|
@@ -1660,6 +1676,37 @@ final class UsageStore {
|
|
|
1660
1676
|
}
|
|
1661
1677
|
if self.ledgerRefreshTask != nil { return }
|
|
1662
1678
|
|
|
1679
|
+
// Immediately populate from cache so the UI has data before the background scan.
|
|
1680
|
+
let providersToCache = providers
|
|
1681
|
+
.filter { self.ledgerAllDailySummaries[$0] == nil || self.ledgerAllDailySummaries[$0]?.isEmpty == true }
|
|
1682
|
+
if !providersToCache.isEmpty {
|
|
1683
|
+
Task {
|
|
1684
|
+
let cache = LedgerCache.shared
|
|
1685
|
+
for provider in providersToCache {
|
|
1686
|
+
let providerKey = provider.rawValue
|
|
1687
|
+
guard let cached = await cache.loadCachedDailies(provider: providerKey) else { continue }
|
|
1688
|
+
let summaries = cached.dailies.compactMap { $0.toLedgerDailySummary(provider: provider) }
|
|
1689
|
+
if !summaries.isEmpty {
|
|
1690
|
+
await MainActor.run { [weak self] in
|
|
1691
|
+
guard let self else { return }
|
|
1692
|
+
if self.ledgerAllDailySummaries[provider] == nil || self.ledgerAllDailySummaries[provider]?
|
|
1693
|
+
.isEmpty == true
|
|
1694
|
+
{
|
|
1695
|
+
self.ledgerAllDailySummaries[provider] = summaries
|
|
1696
|
+
// Also populate today's summary from cache.
|
|
1697
|
+
let todayStart = Calendar.current.startOfDay(for: now)
|
|
1698
|
+
if self.ledgerDailySummaries[provider] == nil,
|
|
1699
|
+
let todaySummary = summaries.first(where: { $0.dayStart == todayStart })
|
|
1700
|
+
{
|
|
1701
|
+
self.ledgerDailySummaries[provider] = todaySummary
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1663
1710
|
self.ledgerRefreshTask = Task.detached(priority: .utility) { [weak self] in
|
|
1664
1711
|
guard let self else { return }
|
|
1665
1712
|
let result = await self.loadLedgerInsights(sources: sources, now: now)
|
|
@@ -1783,13 +1830,17 @@ final class UsageStore {
|
|
|
1783
1830
|
self?.tokenRefreshSequenceTask = nil
|
|
1784
1831
|
}
|
|
1785
1832
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1833
|
+
await withTaskGroup(of: Void.self) { group in
|
|
1834
|
+
for provider in UsageProvider.allCases {
|
|
1835
|
+
group.addTask {
|
|
1836
|
+
guard !Task.isCancelled else { return }
|
|
1837
|
+
await self.refreshTokenUsage(
|
|
1838
|
+
provider,
|
|
1839
|
+
force: force,
|
|
1840
|
+
trigger: trigger,
|
|
1841
|
+
inactiveProviders: inactiveProviders)
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1793
1844
|
}
|
|
1794
1845
|
}
|
|
1795
1846
|
}
|
|
@@ -1801,7 +1852,7 @@ final class UsageStore {
|
|
|
1801
1852
|
self.ledgerRefreshTask?.cancel()
|
|
1802
1853
|
}
|
|
1803
1854
|
|
|
1804
|
-
private struct LedgerRefreshResult
|
|
1855
|
+
private struct LedgerRefreshResult {
|
|
1805
1856
|
let dailyByProvider: [UsageProvider: UsageLedgerDailySummary]
|
|
1806
1857
|
let allDailySummariesByProvider: [UsageProvider: [UsageLedgerDailySummary]]
|
|
1807
1858
|
let hourlySummariesByProvider: [UsageProvider: [UsageLedgerHourlySummary]]
|
|
@@ -1829,7 +1880,7 @@ final class UsageStore {
|
|
|
1829
1880
|
now: Date,
|
|
1830
1881
|
inactiveProviders: Set<UsageProvider>) -> [(UsageProvider, any UsageLedgerSource)]
|
|
1831
1882
|
{
|
|
1832
|
-
|
|
1883
|
+
UsageProvider.allCases.compactMap { provider in
|
|
1833
1884
|
guard self.isEnabled(provider), !inactiveProviders.contains(provider) else { return nil }
|
|
1834
1885
|
guard let source = self.providerHistorySource(
|
|
1835
1886
|
provider: provider,
|
|
@@ -1845,23 +1896,23 @@ final class UsageStore {
|
|
|
1845
1896
|
now: Date) async -> LedgerRefreshResult
|
|
1846
1897
|
{
|
|
1847
1898
|
guard !sources.isEmpty else {
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
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: [])
|
|
1865
1916
|
}
|
|
1866
1917
|
|
|
1867
1918
|
let providers = sources.map(\.0)
|
|
@@ -1965,10 +2016,10 @@ final class UsageStore {
|
|
|
1965
2016
|
projectBreakdownsByProvider[summary.provider, default: []].append(summary)
|
|
1966
2017
|
}
|
|
1967
2018
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2019
|
+
let providerForecasts = UsageLedgerAggregator.providerSpendForecasts(
|
|
2020
|
+
entries: entries,
|
|
2021
|
+
now: now,
|
|
2022
|
+
timeZone: timeZone)
|
|
1972
2023
|
var spendForecastsByProvider: [UsageProvider: UsageLedgerSpendForecast] = [:]
|
|
1973
2024
|
for forecast in providerForecasts where spendForecastsByProvider[forecast.provider] == nil {
|
|
1974
2025
|
spendForecastsByProvider[forecast.provider] = forecast
|
|
@@ -1991,34 +2042,34 @@ final class UsageStore {
|
|
|
1991
2042
|
}
|
|
1992
2043
|
|
|
1993
2044
|
var topProjectSpendForecastsByProvider: [UsageProvider: UsageLedgerSpendForecast] = [:]
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
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
|
+
}
|
|
1998
2050
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
calendar: calendar)
|
|
2051
|
+
let anomaliesByProvider = UsageLedgerAnomalyDetector.summaries(
|
|
2052
|
+
dailySummaries: dailySummaries,
|
|
2053
|
+
now: now,
|
|
2054
|
+
calendar: calendar)
|
|
2004
2055
|
|
|
2005
2056
|
return LedgerRefreshResult(
|
|
2006
2057
|
dailyByProvider: dailyByProvider,
|
|
2007
2058
|
allDailySummariesByProvider: allDailySummariesByProvider,
|
|
2008
2059
|
hourlySummariesByProvider: hourlySummariesByProvider,
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
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)
|
|
2022
2073
|
}
|
|
2023
2074
|
|
|
2024
2075
|
private func projectNameOverridesFromBudgets() -> [String: String] {
|
|
@@ -2765,9 +2816,11 @@ extension UsageStore {
|
|
|
2765
2816
|
? nil : self.settings.azureOpenAIAPIToken,
|
|
2766
2817
|
endpoint: self.settings.azureOpenAIEndpoint.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
2767
2818
|
? nil : self.settings.azureOpenAIEndpoint,
|
|
2768
|
-
deployment: self.settings.azureOpenAIDeployment.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2819
|
+
deployment: self.settings.azureOpenAIDeployment.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2820
|
+
.isEmpty
|
|
2769
2821
|
? nil : self.settings.azureOpenAIDeployment,
|
|
2770
|
-
apiVersion: self.settings.azureOpenAIAPIVersion.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2822
|
+
apiVersion: self.settings.azureOpenAIAPIVersion.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
2823
|
+
.isEmpty
|
|
2771
2824
|
? nil : self.settings.azureOpenAIAPIVersion),
|
|
2772
2825
|
bedrock: ProviderSettingsSnapshot.BedrockProviderSettings(
|
|
2773
2826
|
region: self.settings.bedrockRegion.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
@@ -3099,8 +3152,8 @@ extension UsageStore {
|
|
|
3099
3152
|
context: ProviderFetchContext,
|
|
3100
3153
|
settings: ProviderSettingsSnapshot) async -> String
|
|
3101
3154
|
{
|
|
3102
|
-
var lines
|
|
3103
|
-
"provider=\(provider.rawValue)"
|
|
3155
|
+
var lines = [
|
|
3156
|
+
"provider=\(provider.rawValue)",
|
|
3104
3157
|
]
|
|
3105
3158
|
|
|
3106
3159
|
lines.append(contentsOf: self.debugCredentialLines(for: provider, settings: settings))
|
|
@@ -3145,38 +3198,54 @@ extension UsageStore {
|
|
|
3145
3198
|
resolution: ProviderTokenResolver.minimaxGroupResolution()),
|
|
3146
3199
|
]
|
|
3147
3200
|
case .openrouter:
|
|
3148
|
-
return [self.debugTokenSummary(
|
|
3201
|
+
return [self.debugTokenSummary(
|
|
3202
|
+
provider: "openrouter",
|
|
3203
|
+
resolution: ProviderTokenResolver.openRouterResolution())]
|
|
3149
3204
|
case .groq:
|
|
3150
3205
|
return [self.debugTokenSummary(provider: "groq", resolution: ProviderTokenResolver.groqResolution())]
|
|
3151
3206
|
case .deepseek:
|
|
3152
|
-
return [self.debugTokenSummary(
|
|
3207
|
+
return [self.debugTokenSummary(
|
|
3208
|
+
provider: "deepseek",
|
|
3209
|
+
resolution: ProviderTokenResolver.deepSeekResolution())]
|
|
3153
3210
|
case .fireworks:
|
|
3154
|
-
return [self.debugTokenSummary(
|
|
3211
|
+
return [self.debugTokenSummary(
|
|
3212
|
+
provider: "fireworks",
|
|
3213
|
+
resolution: ProviderTokenResolver.fireworksResolution())]
|
|
3155
3214
|
case .mistral:
|
|
3156
3215
|
return [self.debugTokenSummary(provider: "mistral", resolution: ProviderTokenResolver.mistralResolution())]
|
|
3157
3216
|
case .perplexity:
|
|
3158
|
-
return [self.debugTokenSummary(
|
|
3217
|
+
return [self.debugTokenSummary(
|
|
3218
|
+
provider: "perplexity",
|
|
3219
|
+
resolution: ProviderTokenResolver.perplexityResolution())]
|
|
3159
3220
|
case .kimi:
|
|
3160
3221
|
return [self.debugTokenSummary(provider: "kimi", resolution: ProviderTokenResolver.kimiResolution())]
|
|
3161
3222
|
case .auggie:
|
|
3162
3223
|
return [self.debugTokenSummary(provider: "auggie", resolution: ProviderTokenResolver.auggieResolution())]
|
|
3163
3224
|
case .together:
|
|
3164
|
-
return [self.debugTokenSummary(
|
|
3225
|
+
return [self.debugTokenSummary(
|
|
3226
|
+
provider: "together",
|
|
3227
|
+
resolution: ProviderTokenResolver.togetherResolution())]
|
|
3165
3228
|
case .cohere:
|
|
3166
3229
|
return [self.debugTokenSummary(provider: "cohere", resolution: ProviderTokenResolver.cohereResolution())]
|
|
3167
3230
|
case .xai:
|
|
3168
3231
|
return [self.debugTokenSummary(provider: "xai", resolution: ProviderTokenResolver.xaiResolution())]
|
|
3169
3232
|
case .cerebras:
|
|
3170
|
-
return [self.debugTokenSummary(
|
|
3233
|
+
return [self.debugTokenSummary(
|
|
3234
|
+
provider: "cerebras",
|
|
3235
|
+
resolution: ProviderTokenResolver.cerebrasResolution())]
|
|
3171
3236
|
case .sambanova:
|
|
3172
|
-
return [self.debugTokenSummary(
|
|
3237
|
+
return [self.debugTokenSummary(
|
|
3238
|
+
provider: "sambanova",
|
|
3239
|
+
resolution: ProviderTokenResolver.sambaNovaResolution())]
|
|
3173
3240
|
case .azure:
|
|
3174
3241
|
var lines: [String] = [
|
|
3175
3242
|
"azure.endpoint=\(self.debugFieldValue(settings.azure?.endpoint))",
|
|
3176
3243
|
"azure.deployment=\(self.debugFieldValue(settings.azure?.deployment))",
|
|
3177
|
-
"azure.apiVersion=\(self.debugFieldValue(settings.azure?.apiVersion))"
|
|
3244
|
+
"azure.apiVersion=\(self.debugFieldValue(settings.azure?.apiVersion))",
|
|
3178
3245
|
]
|
|
3179
|
-
lines.append(self.debugTokenSummary(
|
|
3246
|
+
lines.append(self.debugTokenSummary(
|
|
3247
|
+
provider: "azure.token",
|
|
3248
|
+
resolution: ProviderTokenResolver.azureOpenAIResolution()))
|
|
3180
3249
|
return lines
|
|
3181
3250
|
case .bedrock:
|
|
3182
3251
|
return [
|
|
@@ -3236,7 +3305,7 @@ extension UsageStore {
|
|
|
3236
3305
|
let resetsAt = providerCost.resetsAt?.description ?? "nil"
|
|
3237
3306
|
lines.append(
|
|
3238
3307
|
"provider_cost.used=\(providerCost.used) limit=\(providerCost.limit) " +
|
|
3239
|
-
|
|
3308
|
+
"currency=\(providerCost.currencyCode) period=\(providerCost.period ?? "nil") resetsAt=\(resetsAt)")
|
|
3240
3309
|
}
|
|
3241
3310
|
return lines
|
|
3242
3311
|
}
|
|
@@ -3485,8 +3554,8 @@ extension UsageStore {
|
|
|
3485
3554
|
requestID: String,
|
|
3486
3555
|
startTime: Date,
|
|
3487
3556
|
endTime: Date,
|
|
3488
|
-
success: Bool
|
|
3489
|
-
|
|
3557
|
+
success: Bool) async
|
|
3558
|
+
{
|
|
3490
3559
|
guard let storage = self.performanceStorage else { return }
|
|
3491
3560
|
|
|
3492
3561
|
let metric = LatencyMetric(
|
|
@@ -3498,8 +3567,7 @@ extension UsageStore {
|
|
|
3498
3567
|
endTime: endTime,
|
|
3499
3568
|
durationMs: Int(endTime.timeIntervalSince(startTime) * 1000),
|
|
3500
3569
|
success: success,
|
|
3501
|
-
createdAt: Date()
|
|
3502
|
-
)
|
|
3570
|
+
createdAt: Date())
|
|
3503
3571
|
|
|
3504
3572
|
try? await storage.save(latency: metric)
|
|
3505
3573
|
}
|
|
@@ -3515,8 +3583,7 @@ extension UsageStore {
|
|
|
3515
3583
|
errorType: errorType,
|
|
3516
3584
|
errorMessage: error.localizedDescription,
|
|
3517
3585
|
retryCount: 0,
|
|
3518
|
-
timestamp: Date()
|
|
3519
|
-
)
|
|
3586
|
+
timestamp: Date())
|
|
3520
3587
|
|
|
3521
3588
|
try? await storage.save(error: errorEvent)
|
|
3522
3589
|
}
|