@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
|
@@ -4,19 +4,19 @@ import SwiftUI
|
|
|
4
4
|
|
|
5
5
|
enum MenuContentMetrics {
|
|
6
6
|
/// Horizontal padding for menu content
|
|
7
|
-
static let horizontalPadding: CGFloat = RunicSpacing.sm
|
|
7
|
+
static let horizontalPadding: CGFloat = RunicSpacing.sm // 12
|
|
8
8
|
|
|
9
9
|
/// Vertical padding for menu content
|
|
10
|
-
static let verticalPadding: CGFloat = RunicSpacing.xs
|
|
10
|
+
static let verticalPadding: CGFloat = RunicSpacing.xs // 8
|
|
11
11
|
|
|
12
12
|
/// Spacing between menu sections
|
|
13
|
-
static let sectionSpacing: CGFloat = RunicSpacing.xs
|
|
13
|
+
static let sectionSpacing: CGFloat = RunicSpacing.xs // 8
|
|
14
14
|
|
|
15
15
|
/// Spacing between menu entries
|
|
16
|
-
static let entrySpacing: CGFloat = RunicSpacing.xxs
|
|
16
|
+
static let entrySpacing: CGFloat = RunicSpacing.xxs // 4
|
|
17
17
|
|
|
18
18
|
/// Minimum menu width
|
|
19
|
-
static let minWidth: CGFloat =
|
|
19
|
+
static let minWidth: CGFloat = 290
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
@MainActor
|
|
@@ -52,6 +52,7 @@ struct MenuContent: View {
|
|
|
52
52
|
.padding(.horizontal, MenuContentMetrics.horizontalPadding)
|
|
53
53
|
.padding(.vertical, MenuContentMetrics.verticalPadding)
|
|
54
54
|
.frame(minWidth: MenuContentMetrics.minWidth, alignment: .leading)
|
|
55
|
+
.runicTypography()
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
@ViewBuilder
|
|
@@ -60,11 +61,11 @@ struct MenuContent: View {
|
|
|
60
61
|
case let .text(text, style):
|
|
61
62
|
switch style {
|
|
62
63
|
case .headline:
|
|
63
|
-
Text(text).font(.headline)
|
|
64
|
+
Text(text).font(RunicFont.headline)
|
|
64
65
|
case .primary:
|
|
65
66
|
Text(text)
|
|
66
67
|
case .secondary:
|
|
67
|
-
Text(text).foregroundStyle(.secondary).font(.footnote)
|
|
68
|
+
Text(text).foregroundStyle(.secondary).font(RunicFont.footnote)
|
|
68
69
|
}
|
|
69
70
|
case let .action(title, action):
|
|
70
71
|
Button {
|
|
@@ -89,7 +90,9 @@ struct MenuContent: View {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
private func iconName(for action: MenuDescriptor.MenuAction) -> String? {
|
|
93
|
+
private func iconName(for action: MenuDescriptor.MenuAction) -> String? {
|
|
94
|
+
action.systemImageName
|
|
95
|
+
}
|
|
93
96
|
|
|
94
97
|
private func perform(_ action: MenuDescriptor.MenuAction) {
|
|
95
98
|
switch action {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
|
|
4
4
|
@MainActor
|
|
5
5
|
struct MenuDescriptor {
|
|
@@ -351,7 +351,9 @@ private enum AccountFormatter {
|
|
|
351
351
|
return cleaned.isEmpty ? text : cleaned
|
|
352
352
|
}
|
|
353
353
|
|
|
354
|
-
static func email(_ text: String) -> String {
|
|
354
|
+
static func email(_ text: String) -> String {
|
|
355
|
+
text
|
|
356
|
+
}
|
|
355
357
|
}
|
|
356
358
|
|
|
357
359
|
extension MenuDescriptor.MenuAction {
|
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import SwiftUI
|
|
2
2
|
|
|
3
|
-
private struct MenuItemHighlightedKey: EnvironmentKey {
|
|
4
|
-
static let defaultValue = false
|
|
5
|
-
}
|
|
6
|
-
|
|
7
3
|
extension EnvironmentValues {
|
|
8
|
-
var menuItemHighlighted: Bool
|
|
9
|
-
get { self[MenuItemHighlightedKey.self] }
|
|
10
|
-
set { self[MenuItemHighlightedKey.self] = newValue }
|
|
11
|
-
}
|
|
4
|
+
@Entry var menuItemHighlighted: Bool = false
|
|
12
5
|
}
|
|
13
6
|
|
|
14
7
|
enum MenuHighlightStyle {
|
|
@@ -20,16 +20,18 @@ struct ModelBreakdownMenuView: View {
|
|
|
20
20
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
21
21
|
if model.items.isEmpty {
|
|
22
22
|
Text("No model data.")
|
|
23
|
-
.font(.footnote)
|
|
23
|
+
.font(RunicFont.footnote)
|
|
24
24
|
.foregroundStyle(.secondary)
|
|
25
25
|
} else {
|
|
26
26
|
// MARK: - Title
|
|
27
|
+
|
|
27
28
|
Text("Models")
|
|
28
|
-
.font(.caption)
|
|
29
|
+
.font(RunicFont.caption)
|
|
29
30
|
.fontWeight(.semibold)
|
|
30
31
|
.foregroundStyle(.primary)
|
|
31
32
|
|
|
32
33
|
// MARK: - Donut chart
|
|
34
|
+
|
|
33
35
|
Chart {
|
|
34
36
|
ForEach(model.chartItems) { item in
|
|
35
37
|
SectorMark(
|
|
@@ -45,6 +47,7 @@ struct ModelBreakdownMenuView: View {
|
|
|
45
47
|
.accessibilityLabel(Self.chartAccessibilityLabel(model: model))
|
|
46
48
|
|
|
47
49
|
// MARK: - Model list
|
|
50
|
+
|
|
48
51
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
49
52
|
ForEach(model.chartItems) { item in
|
|
50
53
|
Self.modelRow(item: item)
|
|
@@ -52,28 +55,31 @@ struct ModelBreakdownMenuView: View {
|
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
// MARK: - Overflow note
|
|
58
|
+
|
|
55
59
|
if model.overflowCount > 0 {
|
|
56
60
|
Text("and \(model.overflowCount) more")
|
|
57
|
-
.font(.caption2)
|
|
61
|
+
.font(RunicFont.caption2)
|
|
58
62
|
.foregroundStyle(.tertiary)
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
// MARK: - Cache hit rate
|
|
66
|
+
|
|
62
67
|
if let cacheRate = model.cacheHitRateText {
|
|
63
68
|
Divider()
|
|
64
69
|
HStack(spacing: RunicSpacing.xxs) {
|
|
65
70
|
Text("Cache hit rate")
|
|
66
|
-
.font(.caption)
|
|
71
|
+
.font(RunicFont.caption)
|
|
67
72
|
.foregroundStyle(.secondary)
|
|
68
73
|
Spacer(minLength: RunicSpacing.xxs)
|
|
69
74
|
Text(cacheRate)
|
|
70
|
-
.font(.caption)
|
|
75
|
+
.font(RunicFont.caption)
|
|
71
76
|
.fontWeight(.medium)
|
|
72
77
|
.foregroundStyle(.primary)
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
// MARK: - Total summary
|
|
82
|
+
|
|
77
83
|
Divider()
|
|
78
84
|
Self.totalRow(model: model)
|
|
79
85
|
}
|
|
@@ -91,27 +97,27 @@ struct ModelBreakdownMenuView: View {
|
|
|
91
97
|
.fill(item.color)
|
|
92
98
|
.frame(width: RunicSpacing.chartLegendDot, height: RunicSpacing.chartLegendDot)
|
|
93
99
|
Text(item.displayName)
|
|
94
|
-
.font(.caption)
|
|
100
|
+
.font(RunicFont.caption)
|
|
95
101
|
.foregroundStyle(.primary)
|
|
96
102
|
.lineLimit(1)
|
|
97
103
|
.truncationMode(.tail)
|
|
98
104
|
Spacer(minLength: RunicSpacing.xxs)
|
|
99
105
|
VStack(alignment: .trailing, spacing: 0) {
|
|
100
106
|
Text("\(UsageFormatter.tokenCountString(item.totalTokens)) tokens")
|
|
101
|
-
.font(.
|
|
107
|
+
.font(RunicFont.caption2)
|
|
102
108
|
.foregroundStyle(.secondary)
|
|
103
109
|
if let context = UsageFormatter.modelContextLabel(for: item.model) {
|
|
104
110
|
Text(context)
|
|
105
|
-
.font(.
|
|
111
|
+
.font(RunicFont.caption2)
|
|
106
112
|
.foregroundStyle(.tertiary)
|
|
107
113
|
}
|
|
108
114
|
HStack(spacing: RunicSpacing.xxxs) {
|
|
109
115
|
Text("\(item.requestCount) req\(item.requestCount == 1 ? "" : "s")")
|
|
110
|
-
.font(.
|
|
116
|
+
.font(RunicFont.caption2)
|
|
111
117
|
.foregroundStyle(.tertiary)
|
|
112
118
|
if let cost = item.costText {
|
|
113
119
|
Text(cost)
|
|
114
|
-
.font(.
|
|
120
|
+
.font(RunicFont.caption2)
|
|
115
121
|
.foregroundStyle(.tertiary)
|
|
116
122
|
}
|
|
117
123
|
}
|
|
@@ -122,21 +128,21 @@ struct ModelBreakdownMenuView: View {
|
|
|
122
128
|
private static func totalRow(model: ModelModel) -> some View {
|
|
123
129
|
HStack {
|
|
124
130
|
Text("Total")
|
|
125
|
-
.font(.caption)
|
|
131
|
+
.font(RunicFont.caption)
|
|
126
132
|
.fontWeight(.medium)
|
|
127
133
|
.foregroundStyle(.primary)
|
|
128
134
|
Spacer(minLength: RunicSpacing.xxs)
|
|
129
135
|
VStack(alignment: .trailing, spacing: 0) {
|
|
130
136
|
Text("\(UsageFormatter.tokenCountString(model.grandTotalTokens)) tokens")
|
|
131
|
-
.font(.
|
|
137
|
+
.font(RunicFont.caption2)
|
|
132
138
|
.foregroundStyle(.secondary)
|
|
133
139
|
HStack(spacing: RunicSpacing.xxxs) {
|
|
134
140
|
Text("\(model.grandTotalRequests) requests")
|
|
135
|
-
.font(.
|
|
141
|
+
.font(RunicFont.caption2)
|
|
136
142
|
.foregroundStyle(.tertiary)
|
|
137
143
|
if let cost = model.grandTotalCostText {
|
|
138
144
|
Text(cost)
|
|
139
|
-
.font(.
|
|
145
|
+
.font(RunicFont.caption2)
|
|
140
146
|
.foregroundStyle(.tertiary)
|
|
141
147
|
}
|
|
142
148
|
}
|
|
@@ -3,7 +3,8 @@ import Charts
|
|
|
3
3
|
import RunicCore
|
|
4
4
|
import SwiftUI
|
|
5
5
|
|
|
6
|
-
/// Overview showing all enabled providers at a glance
|
|
6
|
+
/// Overview dashboard showing all enabled providers at a glance.
|
|
7
|
+
/// Inspired by Apple Activity app rings, Awwwards dashboard layouts, and Tokex stat cards.
|
|
7
8
|
@MainActor
|
|
8
9
|
struct OverviewMenuView: View {
|
|
9
10
|
struct ProviderSummary: Identifiable {
|
|
@@ -15,6 +16,8 @@ struct OverviewMenuView: View {
|
|
|
15
16
|
let todayTokens: Int
|
|
16
17
|
let brandColor: Color
|
|
17
18
|
let resetDescription: String?
|
|
19
|
+
let windowLabel: String? // e.g. "5h" or "Weekly"
|
|
20
|
+
let topModelContext: String? // e.g. "200K ctx"
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
struct DailyPoint: Identifiable {
|
|
@@ -22,82 +25,98 @@ struct OverviewMenuView: View {
|
|
|
22
25
|
let date: Date
|
|
23
26
|
let tokens: Int
|
|
24
27
|
let provider: String
|
|
28
|
+
let color: Color
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
let summaries: [ProviderSummary]
|
|
28
32
|
let chartPoints: [DailyPoint]
|
|
29
33
|
let totalTodayTokens: Int
|
|
34
|
+
let totalProviders: Int
|
|
30
35
|
let width: CGFloat
|
|
31
36
|
|
|
32
37
|
var body: some View {
|
|
33
38
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
// MARK: - Hero header
|
|
40
|
+
|
|
41
|
+
HStack(alignment: .center, spacing: RunicSpacing.xs) {
|
|
42
|
+
// Ring indicator showing overall usage
|
|
43
|
+
ZStack {
|
|
44
|
+
Circle()
|
|
45
|
+
.stroke(Color(nsColor: .separatorColor).opacity(0.15), lineWidth: 3)
|
|
46
|
+
Circle()
|
|
47
|
+
.trim(from: 0, to: min(1, self.averageUsedPercent / 100))
|
|
48
|
+
.stroke(
|
|
49
|
+
AngularGradient(
|
|
50
|
+
colors: [RunicColors.chartColor(at: 4), RunicColors.chartColor(at: 0)],
|
|
51
|
+
center: .center),
|
|
52
|
+
style: StrokeStyle(lineWidth: 3, lineCap: .round))
|
|
53
|
+
.rotationEffect(.degrees(-90))
|
|
54
|
+
}
|
|
55
|
+
.frame(width: 32, height: 32)
|
|
56
|
+
|
|
57
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
58
|
+
Text(UsageFormatter.tokenCountString(self.totalTodayTokens))
|
|
59
|
+
.font(RunicFont.system(size: 22, weight: .bold))
|
|
60
|
+
Text("\(self.summaries.count) of \(self.totalProviders) active")
|
|
61
|
+
.font(RunicFont.caption2)
|
|
62
|
+
.foregroundStyle(.tertiary)
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
Spacer()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
66
|
+
|
|
67
|
+
VStack(alignment: .trailing, spacing: 2) {
|
|
68
|
+
Text("today")
|
|
69
|
+
.font(RunicFont.caption2)
|
|
70
|
+
.foregroundStyle(.tertiary)
|
|
71
|
+
Text("\(Int(self.averageUsedPercent))% avg")
|
|
72
|
+
.font(RunicFont.caption)
|
|
73
|
+
.fontWeight(.medium)
|
|
43
74
|
.foregroundStyle(.secondary)
|
|
44
75
|
}
|
|
45
76
|
}
|
|
46
77
|
|
|
47
|
-
|
|
78
|
+
Divider().opacity(0.5)
|
|
79
|
+
|
|
80
|
+
// MARK: - Provider cards
|
|
81
|
+
|
|
48
82
|
if self.summaries.isEmpty {
|
|
49
|
-
Text("No providers
|
|
50
|
-
.font(.
|
|
83
|
+
Text("No active providers.")
|
|
84
|
+
.font(RunicFont.footnote)
|
|
51
85
|
.foregroundStyle(.secondary)
|
|
86
|
+
.frame(maxWidth: .infinity, alignment: .center)
|
|
87
|
+
.padding(.vertical, RunicSpacing.md)
|
|
52
88
|
} else {
|
|
53
|
-
VStack(spacing: RunicSpacing.
|
|
89
|
+
VStack(spacing: RunicSpacing.compact) {
|
|
54
90
|
ForEach(self.summaries) { summary in
|
|
55
|
-
|
|
56
|
-
// Provider icon
|
|
57
|
-
if let icon = summary.icon {
|
|
58
|
-
Image(nsImage: icon)
|
|
59
|
-
.resizable()
|
|
60
|
-
.aspectRatio(contentMode: .fit)
|
|
61
|
-
.frame(width: 16, height: 16)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Name
|
|
65
|
-
Text(summary.name)
|
|
66
|
-
.font(.system(.caption, design: .rounded))
|
|
67
|
-
.fontWeight(.medium)
|
|
68
|
-
.frame(width: 60, alignment: .leading)
|
|
69
|
-
.lineLimit(1)
|
|
70
|
-
|
|
71
|
-
// Mini progress bar
|
|
72
|
-
GeometryReader { geo in
|
|
73
|
-
ZStack(alignment: .leading) {
|
|
74
|
-
Capsule()
|
|
75
|
-
.fill(Color(nsColor: .separatorColor).opacity(0.2))
|
|
76
|
-
Capsule()
|
|
77
|
-
.fill(summary.brandColor)
|
|
78
|
-
.frame(width: max(0, geo.size.width * min(1, summary.usedPercent / 100)))
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
.frame(height: 6)
|
|
82
|
-
|
|
83
|
-
// Percentage
|
|
84
|
-
Text("\(Int(summary.usedPercent))%")
|
|
85
|
-
.font(.system(size: 10, weight: .medium, design: .rounded))
|
|
86
|
-
.foregroundStyle(.secondary)
|
|
87
|
-
.frame(width: 30, alignment: .trailing)
|
|
88
|
-
}
|
|
91
|
+
ProviderRow(summary: summary)
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
// Combined 7-day chart
|
|
96
|
+
// MARK: - Combined 7-day chart
|
|
97
|
+
|
|
94
98
|
if !self.chartPoints.isEmpty {
|
|
95
|
-
Divider()
|
|
96
|
-
.padding(.vertical, RunicSpacing.xxxs)
|
|
99
|
+
Divider().opacity(0.5)
|
|
97
100
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
HStack {
|
|
102
|
+
Text("7-day activity")
|
|
103
|
+
.font(RunicFont.caption2)
|
|
104
|
+
.foregroundStyle(.tertiary)
|
|
105
|
+
Spacer()
|
|
106
|
+
// Mini legend dots
|
|
107
|
+
HStack(spacing: RunicSpacing.xxs) {
|
|
108
|
+
ForEach(self.summaries.prefix(4)) { s in
|
|
109
|
+
Circle()
|
|
110
|
+
.fill(s.brandColor)
|
|
111
|
+
.frame(width: 5, height: 5)
|
|
112
|
+
}
|
|
113
|
+
if self.summaries.count > 4 {
|
|
114
|
+
Text("+\(self.summaries.count - 4)")
|
|
115
|
+
.font(RunicFont.system(size: 8))
|
|
116
|
+
.foregroundStyle(.quaternary)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
101
120
|
|
|
102
121
|
Chart {
|
|
103
122
|
ForEach(self.chartPoints) { point in
|
|
@@ -105,29 +124,32 @@ struct OverviewMenuView: View {
|
|
|
105
124
|
x: .value("Date", point.date, unit: .day),
|
|
106
125
|
y: .value("Tokens", point.tokens))
|
|
107
126
|
.foregroundStyle(by: .value("Provider", point.provider))
|
|
127
|
+
.cornerRadius(2)
|
|
108
128
|
}
|
|
109
129
|
}
|
|
110
130
|
.chartForegroundStyleScale(range: self.chartColors)
|
|
111
131
|
.chartLegend(.hidden)
|
|
112
132
|
.chartYAxis {
|
|
113
133
|
AxisMarks(position: .trailing, values: .automatic(desiredCount: 3)) { value in
|
|
134
|
+
AxisGridLine(stroke: StrokeStyle(lineWidth: 0.3, dash: [3, 3]))
|
|
135
|
+
.foregroundStyle(Color(nsColor: .separatorColor).opacity(0.3))
|
|
114
136
|
AxisValueLabel {
|
|
115
137
|
if let tokens = value.as(Int.self) {
|
|
116
138
|
Text(UsageFormatter.tokenCountString(tokens))
|
|
117
|
-
.font(.system(size: 8
|
|
139
|
+
.font(RunicFont.system(size: 8))
|
|
118
140
|
.foregroundStyle(Color(nsColor: .tertiaryLabelColor))
|
|
119
141
|
}
|
|
120
142
|
}
|
|
121
143
|
}
|
|
122
144
|
}
|
|
123
145
|
.chartXAxis {
|
|
124
|
-
AxisMarks(values: .automatic(desiredCount:
|
|
125
|
-
AxisValueLabel(format: .dateTime.weekday(.
|
|
126
|
-
.font(.system(size: 8,
|
|
146
|
+
AxisMarks(values: .automatic(desiredCount: 7)) { _ in
|
|
147
|
+
AxisValueLabel(format: .dateTime.weekday(.narrow))
|
|
148
|
+
.font(RunicFont.system(size: 8, weight: .medium))
|
|
127
149
|
.foregroundStyle(Color(nsColor: .tertiaryLabelColor))
|
|
128
150
|
}
|
|
129
151
|
}
|
|
130
|
-
.frame(height:
|
|
152
|
+
.frame(height: 80)
|
|
131
153
|
}
|
|
132
154
|
}
|
|
133
155
|
.padding(.horizontal, MenuCardMetrics.horizontalPadding)
|
|
@@ -135,7 +157,125 @@ struct OverviewMenuView: View {
|
|
|
135
157
|
.frame(minWidth: self.width, maxWidth: .infinity, alignment: .leading)
|
|
136
158
|
}
|
|
137
159
|
|
|
160
|
+
private var averageUsedPercent: Double {
|
|
161
|
+
let active = self.summaries.filter { $0.usedPercent > 0 }
|
|
162
|
+
guard !active.isEmpty else { return 0 }
|
|
163
|
+
return active.reduce(0) { $0 + $1.usedPercent } / Double(active.count)
|
|
164
|
+
}
|
|
165
|
+
|
|
138
166
|
private var chartColors: [Color] {
|
|
139
167
|
self.summaries.map(\.brandColor)
|
|
140
168
|
}
|
|
141
169
|
}
|
|
170
|
+
|
|
171
|
+
// MARK: - Provider row
|
|
172
|
+
|
|
173
|
+
private struct ProviderRow: View {
|
|
174
|
+
let summary: OverviewMenuView.ProviderSummary
|
|
175
|
+
|
|
176
|
+
var body: some View {
|
|
177
|
+
HStack(spacing: RunicSpacing.xs) {
|
|
178
|
+
// Icon with brand tint
|
|
179
|
+
if let icon = self.summary.icon {
|
|
180
|
+
Image(nsImage: icon)
|
|
181
|
+
.resizable()
|
|
182
|
+
.aspectRatio(contentMode: .fit)
|
|
183
|
+
.frame(width: 14, height: 14)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Name
|
|
187
|
+
Text(self.summary.name)
|
|
188
|
+
.font(RunicFont.caption)
|
|
189
|
+
.fontWeight(.medium)
|
|
190
|
+
.frame(width: 58, alignment: .leading)
|
|
191
|
+
.lineLimit(1)
|
|
192
|
+
|
|
193
|
+
// Progress bar with gradient fill
|
|
194
|
+
GeometryReader { geo in
|
|
195
|
+
let fillWidth = max(0, geo.size.width * min(1, self.summary.usedPercent / 100))
|
|
196
|
+
ZStack(alignment: .leading) {
|
|
197
|
+
// Track
|
|
198
|
+
Capsule()
|
|
199
|
+
.fill(Color(nsColor: .separatorColor).opacity(0.12))
|
|
200
|
+
|
|
201
|
+
// Fill with gradient
|
|
202
|
+
Capsule()
|
|
203
|
+
.fill(
|
|
204
|
+
LinearGradient(
|
|
205
|
+
colors: [
|
|
206
|
+
self.summary.brandColor.opacity(0.9),
|
|
207
|
+
self.summary.brandColor,
|
|
208
|
+
],
|
|
209
|
+
startPoint: .leading,
|
|
210
|
+
endPoint: .trailing))
|
|
211
|
+
.frame(width: fillWidth)
|
|
212
|
+
|
|
213
|
+
// Gloss overlay on fill
|
|
214
|
+
Capsule()
|
|
215
|
+
.fill(
|
|
216
|
+
LinearGradient(
|
|
217
|
+
stops: [
|
|
218
|
+
.init(color: .white.opacity(0.2), location: 0),
|
|
219
|
+
.init(color: .clear, location: 0.5),
|
|
220
|
+
],
|
|
221
|
+
startPoint: .top,
|
|
222
|
+
endPoint: .bottom))
|
|
223
|
+
.frame(width: fillWidth)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
.frame(height: 7)
|
|
227
|
+
|
|
228
|
+
// Percentage
|
|
229
|
+
Text("\(Int(self.summary.usedPercent))%")
|
|
230
|
+
.font(RunicFont.system(size: 9, weight: .semibold))
|
|
231
|
+
.foregroundStyle(self.summary.usedPercent > 80 ? .primary : .secondary)
|
|
232
|
+
.frame(width: 28, alignment: .trailing)
|
|
233
|
+
|
|
234
|
+
// Today's tokens (if any)
|
|
235
|
+
if self.summary.todayTokens > 0 {
|
|
236
|
+
Text(UsageFormatter.tokenCountString(self.summary.todayTokens))
|
|
237
|
+
.font(RunicFont.system(size: 8))
|
|
238
|
+
.foregroundStyle(.tertiary)
|
|
239
|
+
.frame(width: 32, alignment: .trailing)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Second line: window + reset + context
|
|
244
|
+
let hasSecondLine = self.summary.windowLabel != nil ||
|
|
245
|
+
self.summary.resetDescription != nil ||
|
|
246
|
+
self.summary.topModelContext != nil
|
|
247
|
+
|
|
248
|
+
if hasSecondLine {
|
|
249
|
+
HStack(spacing: RunicSpacing.xxs) {
|
|
250
|
+
// Spacer for icon + name width
|
|
251
|
+
Color.clear.frame(width: 14 + 58 + RunicSpacing.xs * 2, height: 0)
|
|
252
|
+
|
|
253
|
+
if let window = self.summary.windowLabel {
|
|
254
|
+
InfoPill(text: window)
|
|
255
|
+
}
|
|
256
|
+
if let reset = self.summary.resetDescription {
|
|
257
|
+
InfoPill(text: reset)
|
|
258
|
+
}
|
|
259
|
+
if let ctx = self.summary.topModelContext {
|
|
260
|
+
InfoPill(text: ctx)
|
|
261
|
+
}
|
|
262
|
+
Spacer()
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private struct InfoPill: View {
|
|
269
|
+
let text: String
|
|
270
|
+
|
|
271
|
+
var body: some View {
|
|
272
|
+
Text(self.text)
|
|
273
|
+
.font(RunicFont.system(size: 8, weight: .medium))
|
|
274
|
+
.foregroundStyle(.tertiary)
|
|
275
|
+
.padding(.horizontal, 4)
|
|
276
|
+
.padding(.vertical, 1)
|
|
277
|
+
.background(
|
|
278
|
+
Capsule(style: .continuous)
|
|
279
|
+
.fill(Color(nsColor: .controlBackgroundColor).opacity(RunicColors.Opacity.subtle)))
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -20,16 +20,18 @@ struct ProjectBreakdownMenuView: View {
|
|
|
20
20
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
21
21
|
if model.items.isEmpty {
|
|
22
22
|
Text("No project data.")
|
|
23
|
-
.font(.footnote)
|
|
23
|
+
.font(RunicFont.footnote)
|
|
24
24
|
.foregroundStyle(.secondary)
|
|
25
25
|
} else {
|
|
26
26
|
// MARK: - Title
|
|
27
|
+
|
|
27
28
|
Text("Projects")
|
|
28
|
-
.font(.caption)
|
|
29
|
+
.font(RunicFont.caption)
|
|
29
30
|
.fontWeight(.semibold)
|
|
30
31
|
.foregroundStyle(.primary)
|
|
31
32
|
|
|
32
33
|
// MARK: - Horizontal bar chart
|
|
34
|
+
|
|
33
35
|
Chart {
|
|
34
36
|
ForEach(model.chartItems) { item in
|
|
35
37
|
BarMark(
|
|
@@ -43,7 +45,7 @@ struct ProjectBreakdownMenuView: View {
|
|
|
43
45
|
.chartYAxis {
|
|
44
46
|
AxisMarks { _ in
|
|
45
47
|
AxisValueLabel()
|
|
46
|
-
.font(.caption2)
|
|
48
|
+
.font(RunicFont.caption2)
|
|
47
49
|
.foregroundStyle(Color(nsColor: .secondaryLabelColor))
|
|
48
50
|
}
|
|
49
51
|
}
|
|
@@ -53,6 +55,7 @@ struct ProjectBreakdownMenuView: View {
|
|
|
53
55
|
.accessibilityLabel(Self.chartAccessibilityLabel(model: model))
|
|
54
56
|
|
|
55
57
|
// MARK: - Project list
|
|
58
|
+
|
|
56
59
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
57
60
|
ForEach(model.chartItems) { item in
|
|
58
61
|
Self.projectRow(item: item)
|
|
@@ -60,13 +63,15 @@ struct ProjectBreakdownMenuView: View {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
// MARK: - Overflow note
|
|
66
|
+
|
|
63
67
|
if model.overflowCount > 0 {
|
|
64
68
|
Text("and \(model.overflowCount) more")
|
|
65
|
-
.font(.caption2)
|
|
69
|
+
.font(RunicFont.caption2)
|
|
66
70
|
.foregroundStyle(.tertiary)
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
// MARK: - Total summary
|
|
74
|
+
|
|
70
75
|
Divider()
|
|
71
76
|
Self.totalRow(model: model)
|
|
72
77
|
}
|
|
@@ -84,24 +89,24 @@ struct ProjectBreakdownMenuView: View {
|
|
|
84
89
|
.fill(item.color)
|
|
85
90
|
.frame(width: RunicSpacing.chartLegendDot, height: RunicSpacing.chartLegendDot)
|
|
86
91
|
Text(item.name)
|
|
87
|
-
.font(.caption)
|
|
92
|
+
.font(RunicFont.caption)
|
|
88
93
|
.foregroundStyle(.primary)
|
|
89
94
|
.lineLimit(1)
|
|
90
95
|
.truncationMode(.middle)
|
|
91
96
|
Spacer(minLength: RunicSpacing.xxs)
|
|
92
97
|
VStack(alignment: .trailing, spacing: 0) {
|
|
93
98
|
Text("\(UsageFormatter.tokenCountString(item.totalTokens)) tokens")
|
|
94
|
-
.font(.
|
|
99
|
+
.font(RunicFont.caption2)
|
|
95
100
|
.foregroundStyle(.secondary)
|
|
96
101
|
HStack(spacing: RunicSpacing.xxxs) {
|
|
97
102
|
if let cost = item.costText {
|
|
98
103
|
Text(cost)
|
|
99
|
-
.font(.
|
|
104
|
+
.font(RunicFont.caption2)
|
|
100
105
|
.foregroundStyle(.tertiary)
|
|
101
106
|
}
|
|
102
107
|
if item.modelCount > 0 {
|
|
103
108
|
Text("\(item.modelCount) model\(item.modelCount == 1 ? "" : "s")")
|
|
104
|
-
.font(.
|
|
109
|
+
.font(RunicFont.caption2)
|
|
105
110
|
.foregroundStyle(.tertiary)
|
|
106
111
|
}
|
|
107
112
|
}
|
|
@@ -112,17 +117,17 @@ struct ProjectBreakdownMenuView: View {
|
|
|
112
117
|
private static func totalRow(model: ProjectModel) -> some View {
|
|
113
118
|
HStack {
|
|
114
119
|
Text("Total")
|
|
115
|
-
.font(.caption)
|
|
120
|
+
.font(RunicFont.caption)
|
|
116
121
|
.fontWeight(.medium)
|
|
117
122
|
.foregroundStyle(.primary)
|
|
118
123
|
Spacer(minLength: RunicSpacing.xxs)
|
|
119
124
|
VStack(alignment: .trailing, spacing: 0) {
|
|
120
125
|
Text("\(UsageFormatter.tokenCountString(model.grandTotalTokens)) tokens")
|
|
121
|
-
.font(.
|
|
126
|
+
.font(RunicFont.caption2)
|
|
122
127
|
.foregroundStyle(.secondary)
|
|
123
128
|
if let cost = model.grandTotalCostText {
|
|
124
129
|
Text(cost)
|
|
125
|
-
.font(.
|
|
130
|
+
.font(RunicFont.caption2)
|
|
126
131
|
.foregroundStyle(.tertiary)
|
|
127
132
|
}
|
|
128
133
|
}
|