@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
|
@@ -19,13 +19,15 @@ struct AlertsPane: View {
|
|
|
19
19
|
case discord
|
|
20
20
|
case generic
|
|
21
21
|
|
|
22
|
-
var id: String {
|
|
22
|
+
var id: String {
|
|
23
|
+
self.rawValue
|
|
24
|
+
}
|
|
23
25
|
|
|
24
26
|
var label: String {
|
|
25
27
|
switch self {
|
|
26
|
-
case .slack:
|
|
27
|
-
case .discord:
|
|
28
|
-
case .generic:
|
|
28
|
+
case .slack: "Slack"
|
|
29
|
+
case .discord: "Discord"
|
|
30
|
+
case .generic: "Generic"
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -34,14 +36,14 @@ struct AlertsPane: View {
|
|
|
34
36
|
PreferencesPane {
|
|
35
37
|
SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
|
|
36
38
|
Text("Alert Rules")
|
|
37
|
-
.font(.caption)
|
|
39
|
+
.font(RunicFont.caption)
|
|
38
40
|
.foregroundStyle(.secondary)
|
|
39
41
|
.textCase(.uppercase)
|
|
40
42
|
|
|
41
43
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
42
44
|
HStack {
|
|
43
45
|
Text("\(self.alertsData.rules.count) rules configured")
|
|
44
|
-
.font(.footnote)
|
|
46
|
+
.font(RunicFont.footnote)
|
|
45
47
|
.foregroundStyle(.secondary)
|
|
46
48
|
|
|
47
49
|
Spacer()
|
|
@@ -57,7 +59,7 @@ struct AlertsPane: View {
|
|
|
57
59
|
|
|
58
60
|
if self.alertsData.rules.isEmpty {
|
|
59
61
|
Text("No alert rules configured. Add a rule to get started.")
|
|
60
|
-
.font(.footnote)
|
|
62
|
+
.font(RunicFont.footnote)
|
|
61
63
|
.foregroundStyle(.tertiary)
|
|
62
64
|
.padding(.vertical, RunicSpacing.md)
|
|
63
65
|
.frame(maxWidth: .infinity)
|
|
@@ -76,24 +78,24 @@ struct AlertsPane: View {
|
|
|
76
78
|
|
|
77
79
|
SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
|
|
78
80
|
Text("Webhook Configuration")
|
|
79
|
-
.font(.caption)
|
|
81
|
+
.font(RunicFont.caption)
|
|
80
82
|
.foregroundStyle(.secondary)
|
|
81
83
|
.textCase(.uppercase)
|
|
82
84
|
|
|
83
85
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
84
86
|
VStack(alignment: .leading, spacing: 4) {
|
|
85
87
|
Text("Default webhook URL")
|
|
86
|
-
.font(.body)
|
|
88
|
+
.font(RunicFont.body)
|
|
87
89
|
TextField("https://hooks.slack.com/services/...", text: self.$defaultWebhookURL)
|
|
88
90
|
.textFieldStyle(.roundedBorder)
|
|
89
91
|
Text("Used for all alerts unless overridden in rule settings.")
|
|
90
|
-
.font(.footnote)
|
|
92
|
+
.font(RunicFont.footnote)
|
|
91
93
|
.foregroundStyle(.tertiary)
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
VStack(alignment: .leading, spacing: 4) {
|
|
95
97
|
Text("Webhook format")
|
|
96
|
-
.font(.body)
|
|
98
|
+
.font(RunicFont.body)
|
|
97
99
|
Picker("", selection: self.$webhookFormat) {
|
|
98
100
|
ForEach(WebhookFormat.allCases) { format in
|
|
99
101
|
Text(format.label).tag(format)
|
|
@@ -112,7 +114,7 @@ struct AlertsPane: View {
|
|
|
112
114
|
|
|
113
115
|
if let result = self.showingTestResult {
|
|
114
116
|
Text(result)
|
|
115
|
-
.font(.footnote)
|
|
117
|
+
.font(RunicFont.footnote)
|
|
116
118
|
.foregroundStyle(result.contains("Success") ? .green : .red)
|
|
117
119
|
}
|
|
118
120
|
}
|
|
@@ -123,14 +125,14 @@ struct AlertsPane: View {
|
|
|
123
125
|
|
|
124
126
|
SettingsSection(contentSpacing: PreferencesLayoutMetrics.sectionSpacing) {
|
|
125
127
|
Text("Alert History")
|
|
126
|
-
.font(.caption)
|
|
128
|
+
.font(RunicFont.caption)
|
|
127
129
|
.foregroundStyle(.secondary)
|
|
128
130
|
.textCase(.uppercase)
|
|
129
131
|
|
|
130
132
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
131
133
|
HStack {
|
|
132
134
|
Text("Last 20 triggered alerts")
|
|
133
|
-
.font(.footnote)
|
|
135
|
+
.font(RunicFont.footnote)
|
|
134
136
|
.foregroundStyle(.secondary)
|
|
135
137
|
|
|
136
138
|
Spacer()
|
|
@@ -147,7 +149,7 @@ struct AlertsPane: View {
|
|
|
147
149
|
|
|
148
150
|
if recentHistory.isEmpty {
|
|
149
151
|
Text("No alerts triggered yet.")
|
|
150
|
-
.font(.footnote)
|
|
152
|
+
.font(RunicFont.footnote)
|
|
151
153
|
.foregroundStyle(.tertiary)
|
|
152
154
|
.padding(.vertical, RunicSpacing.md)
|
|
153
155
|
.frame(maxWidth: .infinity)
|
|
@@ -174,8 +176,7 @@ struct AlertsPane: View {
|
|
|
174
176
|
} catch {
|
|
175
177
|
print("Failed to add rule: \(error)")
|
|
176
178
|
}
|
|
177
|
-
}
|
|
178
|
-
)
|
|
179
|
+
})
|
|
179
180
|
}
|
|
180
181
|
.sheet(item: self.$editingRule) { rule in
|
|
181
182
|
RuleEditorSheet(
|
|
@@ -187,8 +188,7 @@ struct AlertsPane: View {
|
|
|
187
188
|
} catch {
|
|
188
189
|
print("Failed to update rule: \(error)")
|
|
189
190
|
}
|
|
190
|
-
}
|
|
191
|
-
)
|
|
191
|
+
})
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -201,21 +201,20 @@ struct AlertsPane: View {
|
|
|
201
201
|
updatedRule.enabled = enabled
|
|
202
202
|
try? AlertRuleStore.updateRule(updatedRule)
|
|
203
203
|
self.alertsData = AlertRuleStore.load()
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.controlSize(.small)
|
|
204
|
+
}))
|
|
205
|
+
.toggleStyle(.switch)
|
|
206
|
+
.controlSize(.small)
|
|
208
207
|
|
|
209
208
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
210
209
|
HStack(spacing: RunicSpacing.xs) {
|
|
211
210
|
Text(self.alertTypeLabel(rule.type))
|
|
212
|
-
.font(.body)
|
|
211
|
+
.font(RunicFont.body)
|
|
213
212
|
|
|
214
213
|
self.severityBadge(rule.severity)
|
|
215
214
|
}
|
|
216
215
|
|
|
217
216
|
Text("Threshold: \(Int(rule.threshold))%")
|
|
218
|
-
.font(.footnote)
|
|
217
|
+
.font(RunicFont.footnote)
|
|
219
218
|
.foregroundStyle(.secondary)
|
|
220
219
|
}
|
|
221
220
|
|
|
@@ -251,11 +250,11 @@ struct AlertsPane: View {
|
|
|
251
250
|
|
|
252
251
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
253
252
|
Text(entry.message)
|
|
254
|
-
.font(.footnote)
|
|
253
|
+
.font(RunicFont.footnote)
|
|
255
254
|
.lineLimit(2)
|
|
256
255
|
|
|
257
256
|
Text(self.relativeTime(entry.triggeredAt))
|
|
258
|
-
.font(.caption2)
|
|
257
|
+
.font(RunicFont.caption2)
|
|
259
258
|
.foregroundStyle(.tertiary)
|
|
260
259
|
}
|
|
261
260
|
|
|
@@ -277,16 +276,14 @@ struct AlertsPane: View {
|
|
|
277
276
|
}
|
|
278
277
|
|
|
279
278
|
private func severityBadge(_ severity: AlertRuleStore.AlertSeverity) -> some View {
|
|
280
|
-
let color: Color = {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
}()
|
|
279
|
+
let color: Color = switch severity {
|
|
280
|
+
case .info: .blue
|
|
281
|
+
case .warning: .orange
|
|
282
|
+
case .critical: .red
|
|
283
|
+
}
|
|
287
284
|
|
|
288
285
|
return Text(severity.rawValue.uppercased())
|
|
289
|
-
.font(.caption2.weight(.semibold))
|
|
286
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
290
287
|
.padding(.horizontal, RunicSpacing.xs)
|
|
291
288
|
.padding(.vertical, RunicSpacing.xxs)
|
|
292
289
|
.background(color.opacity(0.2))
|
|
@@ -296,10 +293,10 @@ struct AlertsPane: View {
|
|
|
296
293
|
|
|
297
294
|
private func alertTypeLabel(_ type: AlertRuleStore.AlertType) -> String {
|
|
298
295
|
switch type {
|
|
299
|
-
case .projectBudget:
|
|
300
|
-
case .usageVelocity:
|
|
301
|
-
case .costAnomaly:
|
|
302
|
-
case .quotaThreshold:
|
|
296
|
+
case .projectBudget: "Project Budget"
|
|
297
|
+
case .usageVelocity: "Usage Velocity"
|
|
298
|
+
case .costAnomaly: "Cost Anomaly"
|
|
299
|
+
case .quotaThreshold: "Quota Threshold"
|
|
303
300
|
}
|
|
304
301
|
}
|
|
305
302
|
|
|
@@ -365,7 +362,7 @@ private struct RuleEditorSheet: View {
|
|
|
365
362
|
var body: some View {
|
|
366
363
|
VStack(spacing: RunicSpacing.lg) {
|
|
367
364
|
Text(self.rule == nil ? "Add Alert Rule" : "Edit Alert Rule")
|
|
368
|
-
.font(.headline)
|
|
365
|
+
.font(RunicFont.headline)
|
|
369
366
|
|
|
370
367
|
Form {
|
|
371
368
|
Picker("Alert Type", selection: self.$alertType) {
|
|
@@ -408,8 +405,7 @@ private struct RuleEditorSheet: View {
|
|
|
408
405
|
severity: self.severity,
|
|
409
406
|
notifyWebhook: self.notifyWebhook,
|
|
410
407
|
webhookURL: self.webhookURL.isEmpty ? nil : self.webhookURL,
|
|
411
|
-
createdAt: self.rule?.createdAt ?? Date()
|
|
412
|
-
)
|
|
408
|
+
createdAt: self.rule?.createdAt ?? Date())
|
|
413
409
|
self.onSave(newRule)
|
|
414
410
|
self.dismiss()
|
|
415
411
|
}
|
|
@@ -45,7 +45,7 @@ struct AnalyticsPane: View {
|
|
|
45
45
|
binding: self.$settings.usageBarsShowUsed)
|
|
46
46
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
47
47
|
Text("Usage metrics")
|
|
48
|
-
.font(.body)
|
|
48
|
+
.font(RunicFont.body)
|
|
49
49
|
Picker("", selection: self.$settings.usageMetricDisplayMode) {
|
|
50
50
|
ForEach(UsageMetricDisplayMode.allCases) { mode in
|
|
51
51
|
Text(mode.label).tag(mode)
|
|
@@ -53,13 +53,13 @@ struct AnalyticsPane: View {
|
|
|
53
53
|
}
|
|
54
54
|
.pickerStyle(.segmented)
|
|
55
55
|
Text("Pick bars, percent, or both in the menu card.")
|
|
56
|
-
.font(.footnote)
|
|
56
|
+
.font(RunicFont.footnote)
|
|
57
57
|
.foregroundStyle(.tertiary)
|
|
58
58
|
.fixedSize(horizontal: false, vertical: true)
|
|
59
59
|
}
|
|
60
60
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
61
61
|
Text("Menu mode")
|
|
62
|
-
.font(.body)
|
|
62
|
+
.font(RunicFont.body)
|
|
63
63
|
Picker("", selection: self.$settings.menuMode) {
|
|
64
64
|
ForEach(MenuMode.allCases) { mode in
|
|
65
65
|
Text(mode.label).tag(mode)
|
|
@@ -67,7 +67,7 @@ struct AnalyticsPane: View {
|
|
|
67
67
|
}
|
|
68
68
|
.pickerStyle(.segmented)
|
|
69
69
|
Text("Glance: usage only. Analyst: usage + credits/cost. Operator: full insights and actions.")
|
|
70
|
-
.font(.footnote)
|
|
70
|
+
.font(RunicFont.footnote)
|
|
71
71
|
.foregroundStyle(.tertiary)
|
|
72
72
|
.fixedSize(horizontal: false, vertical: true)
|
|
73
73
|
}
|
|
@@ -81,7 +81,7 @@ struct AnalyticsPane: View {
|
|
|
81
81
|
binding: self.$settings.mergeIcons)
|
|
82
82
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
83
83
|
Text("Provider switcher layout")
|
|
84
|
-
.font(.body)
|
|
84
|
+
.font(RunicFont.body)
|
|
85
85
|
Picker("", selection: self.$settings.providerSwitcherLayout) {
|
|
86
86
|
ForEach(ProviderSwitcherLayout.allCases) { layout in
|
|
87
87
|
Text(layout.label).tag(layout)
|
|
@@ -91,13 +91,13 @@ struct AnalyticsPane: View {
|
|
|
91
91
|
.disabled(!self.settings.mergeIcons)
|
|
92
92
|
.opacity(self.settings.mergeIcons ? 1 : 0.5)
|
|
93
93
|
Text("Choose whether providers appear on top or in a left sidebar.")
|
|
94
|
-
.font(.footnote)
|
|
94
|
+
.font(RunicFont.footnote)
|
|
95
95
|
.foregroundStyle(.tertiary)
|
|
96
96
|
.fixedSize(horizontal: false, vertical: true)
|
|
97
97
|
}
|
|
98
98
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
99
99
|
Text("Switcher icon size")
|
|
100
|
-
.font(.body)
|
|
100
|
+
.font(RunicFont.body)
|
|
101
101
|
Picker("", selection: self.$settings.providerSwitcherIconSize) {
|
|
102
102
|
ForEach(ProviderSwitcherIconSize.allCases) { size in
|
|
103
103
|
Text(size.label).tag(size)
|
|
@@ -107,7 +107,7 @@ struct AnalyticsPane: View {
|
|
|
107
107
|
.disabled(!self.settings.mergeIcons)
|
|
108
108
|
.opacity(self.settings.mergeIcons ? 1 : 0.5)
|
|
109
109
|
Text("Small or medium icons for the provider switcher.")
|
|
110
|
-
.font(.footnote)
|
|
110
|
+
.font(RunicFont.footnote)
|
|
111
111
|
.foregroundStyle(.tertiary)
|
|
112
112
|
.fixedSize(horizontal: false, vertical: true)
|
|
113
113
|
}
|
|
@@ -164,7 +164,7 @@ struct AnalyticsPane: View {
|
|
|
164
164
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
165
165
|
HStack {
|
|
166
166
|
Text("\(self.alertsData.rules.count) rules configured")
|
|
167
|
-
.font(.footnote)
|
|
167
|
+
.font(RunicFont.footnote)
|
|
168
168
|
.foregroundStyle(.secondary)
|
|
169
169
|
|
|
170
170
|
Spacer()
|
|
@@ -180,7 +180,7 @@ struct AnalyticsPane: View {
|
|
|
180
180
|
|
|
181
181
|
if self.alertsData.rules.isEmpty {
|
|
182
182
|
Text("No alert rules configured. Add a rule to get started.")
|
|
183
|
-
.font(.footnote)
|
|
183
|
+
.font(RunicFont.footnote)
|
|
184
184
|
.foregroundStyle(.tertiary)
|
|
185
185
|
.padding(.vertical, RunicSpacing.md)
|
|
186
186
|
.frame(maxWidth: .infinity)
|
|
@@ -207,7 +207,7 @@ struct AnalyticsPane: View {
|
|
|
207
207
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
208
208
|
HStack {
|
|
209
209
|
Text("\(self.budgets.count) budgets configured")
|
|
210
|
-
.font(.footnote)
|
|
210
|
+
.font(RunicFont.footnote)
|
|
211
211
|
.foregroundStyle(.secondary)
|
|
212
212
|
|
|
213
213
|
Spacer()
|
|
@@ -223,7 +223,7 @@ struct AnalyticsPane: View {
|
|
|
223
223
|
|
|
224
224
|
if self.budgets.isEmpty {
|
|
225
225
|
Text("No budgets configured. Add a budget to track project spending.")
|
|
226
|
-
.font(.footnote)
|
|
226
|
+
.font(RunicFont.footnote)
|
|
227
227
|
.foregroundStyle(.tertiary)
|
|
228
228
|
.padding(.vertical, RunicSpacing.md)
|
|
229
229
|
.frame(maxWidth: .infinity)
|
|
@@ -239,10 +239,10 @@ struct AnalyticsPane: View {
|
|
|
239
239
|
if let error = self.budgetErrorMessage {
|
|
240
240
|
HStack(alignment: .center, spacing: RunicSpacing.xs) {
|
|
241
241
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
242
|
-
.font(.caption)
|
|
242
|
+
.font(RunicFont.caption)
|
|
243
243
|
.foregroundStyle(.red)
|
|
244
244
|
Text(error)
|
|
245
|
-
.font(.caption)
|
|
245
|
+
.font(RunicFont.caption)
|
|
246
246
|
.foregroundStyle(.red)
|
|
247
247
|
}
|
|
248
248
|
.padding(RunicSpacing.xs)
|
|
@@ -311,21 +311,20 @@ struct AnalyticsPane: View {
|
|
|
311
311
|
updatedRule.enabled = enabled
|
|
312
312
|
try? AlertRuleStore.updateRule(updatedRule)
|
|
313
313
|
self.alertsData = AlertRuleStore.load()
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
.controlSize(.small)
|
|
314
|
+
}))
|
|
315
|
+
.toggleStyle(.switch)
|
|
316
|
+
.controlSize(.small)
|
|
318
317
|
|
|
319
318
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
320
319
|
HStack(spacing: RunicSpacing.xs) {
|
|
321
320
|
Text(self.alertTypeLabel(rule.type))
|
|
322
|
-
.font(.body)
|
|
321
|
+
.font(RunicFont.body)
|
|
323
322
|
|
|
324
323
|
self.severityBadge(rule.severity)
|
|
325
324
|
}
|
|
326
325
|
|
|
327
326
|
Text("Threshold: \(Int(rule.threshold))%")
|
|
328
|
-
.font(.footnote)
|
|
327
|
+
.font(RunicFont.footnote)
|
|
329
328
|
.foregroundStyle(.secondary)
|
|
330
329
|
}
|
|
331
330
|
|
|
@@ -361,16 +360,16 @@ struct AnalyticsPane: View {
|
|
|
361
360
|
HStack(spacing: RunicSpacing.sm) {
|
|
362
361
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
363
362
|
Text(budget.projectName ?? budget.projectID)
|
|
364
|
-
.font(.body)
|
|
363
|
+
.font(RunicFont.body)
|
|
365
364
|
.lineLimit(1)
|
|
366
365
|
.truncationMode(.tail)
|
|
367
366
|
|
|
368
367
|
HStack(spacing: RunicSpacing.xs) {
|
|
369
368
|
Text(UsageFormatter.usdString(budget.monthlyLimit))
|
|
370
|
-
.font(.footnote)
|
|
369
|
+
.font(RunicFont.footnote)
|
|
371
370
|
.foregroundStyle(.secondary)
|
|
372
371
|
Text("Alert at \(String(format: "%.0f%%", budget.alertThreshold * 100))")
|
|
373
|
-
.font(.footnote)
|
|
372
|
+
.font(RunicFont.footnote)
|
|
374
373
|
.foregroundStyle(.secondary)
|
|
375
374
|
}
|
|
376
375
|
}
|
|
@@ -378,14 +377,14 @@ struct AnalyticsPane: View {
|
|
|
378
377
|
Spacer()
|
|
379
378
|
|
|
380
379
|
Text(budget.enabled ? "Enabled" : "Disabled")
|
|
381
|
-
.font(.caption)
|
|
380
|
+
.font(RunicFont.caption)
|
|
382
381
|
.foregroundStyle(budget.enabled ? .green : .secondary)
|
|
383
382
|
|
|
384
383
|
Button {
|
|
385
384
|
self.deleteBudget(budget)
|
|
386
385
|
} label: {
|
|
387
386
|
Image(systemName: "trash")
|
|
388
|
-
.font(.caption)
|
|
387
|
+
.font(RunicFont.caption)
|
|
389
388
|
.foregroundStyle(.red)
|
|
390
389
|
}
|
|
391
390
|
.buttonStyle(.plain)
|
|
@@ -398,16 +397,14 @@ struct AnalyticsPane: View {
|
|
|
398
397
|
// MARK: - Helpers
|
|
399
398
|
|
|
400
399
|
private func severityBadge(_ severity: AlertRuleStore.AlertSeverity) -> some View {
|
|
401
|
-
let color: Color = {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
}
|
|
407
|
-
}()
|
|
400
|
+
let color: Color = switch severity {
|
|
401
|
+
case .info: .blue
|
|
402
|
+
case .warning: .orange
|
|
403
|
+
case .critical: .red
|
|
404
|
+
}
|
|
408
405
|
|
|
409
406
|
return Text(severity.rawValue.uppercased())
|
|
410
|
-
.font(.caption2.weight(.semibold))
|
|
407
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
411
408
|
.padding(.horizontal, RunicSpacing.xs)
|
|
412
409
|
.padding(.vertical, RunicSpacing.xxs)
|
|
413
410
|
.background(color.opacity(0.2))
|
|
@@ -417,10 +414,10 @@ struct AnalyticsPane: View {
|
|
|
417
414
|
|
|
418
415
|
private func alertTypeLabel(_ type: AlertRuleStore.AlertType) -> String {
|
|
419
416
|
switch type {
|
|
420
|
-
case .projectBudget:
|
|
421
|
-
case .usageVelocity:
|
|
422
|
-
case .costAnomaly:
|
|
423
|
-
case .quotaThreshold:
|
|
417
|
+
case .projectBudget: "Project Budget"
|
|
418
|
+
case .usageVelocity: "Usage Velocity"
|
|
419
|
+
case .costAnomaly: "Cost Anomaly"
|
|
420
|
+
case .quotaThreshold: "Quota Threshold"
|
|
424
421
|
}
|
|
425
422
|
}
|
|
426
423
|
|
|
@@ -493,11 +490,11 @@ private struct AnalyticsSectionDisclosureStyle: DisclosureGroupStyle {
|
|
|
493
490
|
} label: {
|
|
494
491
|
HStack(spacing: RunicSpacing.xs) {
|
|
495
492
|
Image(systemName: configuration.isExpanded ? "chevron.down" : "chevron.right")
|
|
496
|
-
.font(.caption.weight(.semibold))
|
|
493
|
+
.font(RunicFont.caption.weight(.semibold))
|
|
497
494
|
.foregroundStyle(.secondary)
|
|
498
495
|
.frame(width: 12)
|
|
499
496
|
configuration.label
|
|
500
|
-
.font(.caption)
|
|
497
|
+
.font(RunicFont.caption)
|
|
501
498
|
.foregroundStyle(.secondary)
|
|
502
499
|
.textCase(.uppercase)
|
|
503
500
|
Spacer()
|
|
@@ -542,7 +539,7 @@ private struct AnalyticsRuleEditorSheet: View {
|
|
|
542
539
|
var body: some View {
|
|
543
540
|
VStack(spacing: RunicSpacing.lg) {
|
|
544
541
|
Text(self.rule == nil ? "Add Alert Rule" : "Edit Alert Rule")
|
|
545
|
-
.font(.headline)
|
|
542
|
+
.font(RunicFont.headline)
|
|
546
543
|
|
|
547
544
|
Form {
|
|
548
545
|
Picker("Alert Type", selection: self.$alertType) {
|
|
@@ -586,8 +583,7 @@ private struct AnalyticsRuleEditorSheet: View {
|
|
|
586
583
|
notifyWebhook: self.notifyWebhook,
|
|
587
584
|
webhookURL: self.webhookURL.isEmpty ? nil : self.webhookURL,
|
|
588
585
|
enabled: self.rule?.enabled ?? true,
|
|
589
|
-
createdAt: self.rule?.createdAt ?? Date()
|
|
590
|
-
)
|
|
586
|
+
createdAt: self.rule?.createdAt ?? Date())
|
|
591
587
|
self.onSave(newRule)
|
|
592
588
|
self.dismiss()
|
|
593
589
|
}
|
|
@@ -613,7 +609,7 @@ private struct AnalyticsAddBudgetSheet: View {
|
|
|
613
609
|
var body: some View {
|
|
614
610
|
VStack(alignment: .leading, spacing: RunicSpacing.md) {
|
|
615
611
|
Text("Add Budget")
|
|
616
|
-
.font(.title2)
|
|
612
|
+
.font(RunicFont.title2)
|
|
617
613
|
.fontWeight(.semibold)
|
|
618
614
|
|
|
619
615
|
Form {
|
|
@@ -16,17 +16,17 @@ struct PreferencesBudgetsPane: View {
|
|
|
16
16
|
VStack(alignment: .leading, spacing: RunicSpacing.md) {
|
|
17
17
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
18
18
|
Text("Project Budgets")
|
|
19
|
-
.font(.title2)
|
|
19
|
+
.font(RunicFont.title2)
|
|
20
20
|
.fontWeight(.semibold)
|
|
21
21
|
Text("Set monthly spending limits and alerts for projects.")
|
|
22
|
-
.font(.subheadline)
|
|
22
|
+
.font(RunicFont.subheadline)
|
|
23
23
|
.foregroundStyle(.secondary)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if self.budgets.isEmpty {
|
|
27
27
|
VStack(spacing: RunicSpacing.md) {
|
|
28
28
|
Text("No budgets configured")
|
|
29
|
-
.font(.body)
|
|
29
|
+
.font(RunicFont.body)
|
|
30
30
|
.foregroundStyle(.secondary)
|
|
31
31
|
.padding(.vertical, RunicSpacing.lg)
|
|
32
32
|
|
|
@@ -44,19 +44,19 @@ struct PreferencesBudgetsPane: View {
|
|
|
44
44
|
// Header
|
|
45
45
|
HStack(alignment: .center, spacing: RunicSpacing.sm) {
|
|
46
46
|
Text("Project")
|
|
47
|
-
.font(.caption)
|
|
47
|
+
.font(RunicFont.caption)
|
|
48
48
|
.foregroundStyle(.secondary)
|
|
49
49
|
.frame(width: 150, alignment: .leading)
|
|
50
50
|
Text("Monthly Limit")
|
|
51
|
-
.font(.caption)
|
|
51
|
+
.font(RunicFont.caption)
|
|
52
52
|
.foregroundStyle(.secondary)
|
|
53
53
|
.frame(width: 120, alignment: .leading)
|
|
54
54
|
Text("Alert at")
|
|
55
|
-
.font(.caption)
|
|
55
|
+
.font(RunicFont.caption)
|
|
56
56
|
.foregroundStyle(.secondary)
|
|
57
57
|
.frame(width: 80, alignment: .leading)
|
|
58
58
|
Text("Status")
|
|
59
|
-
.font(.caption)
|
|
59
|
+
.font(RunicFont.caption)
|
|
60
60
|
.foregroundStyle(.secondary)
|
|
61
61
|
.frame(width: 80, alignment: .leading)
|
|
62
62
|
Spacer()
|
|
@@ -107,11 +107,11 @@ struct PreferencesBudgetsPane: View {
|
|
|
107
107
|
if let error = self.errorMessage {
|
|
108
108
|
HStack(alignment: .center, spacing: RunicSpacing.xs) {
|
|
109
109
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
110
|
-
.font(.caption)
|
|
110
|
+
.font(RunicFont.caption)
|
|
111
111
|
.foregroundStyle(.red)
|
|
112
112
|
.alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
|
|
113
113
|
Text(error)
|
|
114
|
-
.font(.caption)
|
|
114
|
+
.font(RunicFont.caption)
|
|
115
115
|
.foregroundStyle(.red)
|
|
116
116
|
}
|
|
117
117
|
.padding(RunicSpacing.xs)
|
|
@@ -262,20 +262,20 @@ private struct BudgetRow: View {
|
|
|
262
262
|
.controlSize(.small)
|
|
263
263
|
}
|
|
264
264
|
} else {
|
|
265
|
-
Text(budget.projectName ?? budget.projectID)
|
|
266
|
-
.font(.body)
|
|
265
|
+
Text(self.budget.projectName ?? self.budget.projectID)
|
|
266
|
+
.font(RunicFont.body)
|
|
267
267
|
.frame(width: 150, alignment: .leading)
|
|
268
268
|
.lineLimit(1)
|
|
269
269
|
.truncationMode(.tail)
|
|
270
|
-
Text(UsageFormatter.usdString(budget.monthlyLimit))
|
|
271
|
-
.font(.body)
|
|
270
|
+
Text(UsageFormatter.usdString(self.budget.monthlyLimit))
|
|
271
|
+
.font(RunicFont.body)
|
|
272
272
|
.frame(width: 120, alignment: .leading)
|
|
273
|
-
Text(String(format: "%.0f%%", budget.alertThreshold * 100))
|
|
274
|
-
.font(.body)
|
|
273
|
+
Text(String(format: "%.0f%%", self.budget.alertThreshold * 100))
|
|
274
|
+
.font(RunicFont.body)
|
|
275
275
|
.frame(width: 80, alignment: .leading)
|
|
276
|
-
Text(budget.enabled ? "Enabled" : "Disabled")
|
|
277
|
-
.font(.caption)
|
|
278
|
-
.foregroundStyle(budget.enabled ? .green : .secondary)
|
|
276
|
+
Text(self.budget.enabled ? "Enabled" : "Disabled")
|
|
277
|
+
.font(RunicFont.caption)
|
|
278
|
+
.foregroundStyle(self.budget.enabled ? .green : .secondary)
|
|
279
279
|
.frame(width: 80, alignment: .leading)
|
|
280
280
|
.alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
|
|
281
281
|
Spacer()
|
|
@@ -284,14 +284,14 @@ private struct BudgetRow: View {
|
|
|
284
284
|
self.onEdit()
|
|
285
285
|
} label: {
|
|
286
286
|
Image(systemName: "pencil")
|
|
287
|
-
.font(.caption)
|
|
287
|
+
.font(RunicFont.caption)
|
|
288
288
|
}
|
|
289
289
|
.buttonStyle(.plain)
|
|
290
290
|
Button {
|
|
291
291
|
self.onDelete()
|
|
292
292
|
} label: {
|
|
293
293
|
Image(systemName: "trash")
|
|
294
|
-
.font(.caption)
|
|
294
|
+
.font(RunicFont.caption)
|
|
295
295
|
.foregroundStyle(.red)
|
|
296
296
|
}
|
|
297
297
|
.buttonStyle(.plain)
|
|
@@ -316,7 +316,7 @@ private struct AddBudgetSheet: View {
|
|
|
316
316
|
var body: some View {
|
|
317
317
|
VStack(alignment: .leading, spacing: RunicSpacing.md) {
|
|
318
318
|
Text("Add Budget")
|
|
319
|
-
.font(.title2)
|
|
319
|
+
.font(RunicFont.title2)
|
|
320
320
|
.fontWeight(.semibold)
|
|
321
321
|
|
|
322
322
|
Form {
|
|
@@ -71,7 +71,7 @@ struct PreferenceToggleRow: View {
|
|
|
71
71
|
HStack(alignment: .center, spacing: RunicSpacing.xs) {
|
|
72
72
|
Toggle(isOn: self.$binding) {
|
|
73
73
|
Text(self.title)
|
|
74
|
-
.font(.body)
|
|
74
|
+
.font(RunicFont.body)
|
|
75
75
|
}
|
|
76
76
|
.toggleStyle(.checkbox)
|
|
77
77
|
.alignmentGuide(.firstTextBaseline) { d in d[VerticalAlignment.center] }
|
|
@@ -79,7 +79,7 @@ struct PreferenceToggleRow: View {
|
|
|
79
79
|
|
|
80
80
|
if let subtitle, !subtitle.isEmpty {
|
|
81
81
|
Text(subtitle)
|
|
82
|
-
.font(.footnote)
|
|
82
|
+
.font(RunicFont.footnote)
|
|
83
83
|
.foregroundStyle(.tertiary)
|
|
84
84
|
.fixedSize(horizontal: false, vertical: true)
|
|
85
85
|
}
|
|
@@ -100,7 +100,7 @@ struct PreferenceStepperRow: View {
|
|
|
100
100
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
101
101
|
HStack(spacing: RunicSpacing.sm) {
|
|
102
102
|
Text(self.title)
|
|
103
|
-
.font(.body)
|
|
103
|
+
.font(RunicFont.body)
|
|
104
104
|
Spacer()
|
|
105
105
|
PreferenceStepperControl(
|
|
106
106
|
valueLabel: self.valueLabel(self.value),
|
|
@@ -112,7 +112,7 @@ struct PreferenceStepperRow: View {
|
|
|
112
112
|
|
|
113
113
|
if let subtitle, !subtitle.isEmpty {
|
|
114
114
|
Text(subtitle)
|
|
115
|
-
.font(.footnote)
|
|
115
|
+
.font(RunicFont.footnote)
|
|
116
116
|
.foregroundStyle(.tertiary)
|
|
117
117
|
.fixedSize(horizontal: false, vertical: true)
|
|
118
118
|
}
|
|
@@ -146,7 +146,7 @@ private struct PreferenceStepperControl: View {
|
|
|
146
146
|
.disabled(!self.canDecrement)
|
|
147
147
|
|
|
148
148
|
Text(self.valueLabel)
|
|
149
|
-
.font(.footnote.weight(.semibold))
|
|
149
|
+
.font(RunicFont.footnote.weight(.semibold))
|
|
150
150
|
.foregroundStyle(.secondary)
|
|
151
151
|
.padding(.horizontal, RunicSpacing.sm)
|
|
152
152
|
.padding(.vertical, RunicSpacing.xxs)
|
|
@@ -187,11 +187,11 @@ struct SettingsSection<Content: View>: View {
|
|
|
187
187
|
VStack(alignment: .leading, spacing: PreferencesLayoutMetrics.sectionHeaderSpacing) {
|
|
188
188
|
if let title, !title.isEmpty {
|
|
189
189
|
Text(title)
|
|
190
|
-
.font(.subheadline.weight(.semibold))
|
|
190
|
+
.font(RunicFont.subheadline.weight(.semibold))
|
|
191
191
|
}
|
|
192
192
|
if let caption {
|
|
193
193
|
Text(caption)
|
|
194
|
-
.font(.footnote)
|
|
194
|
+
.font(RunicFont.footnote)
|
|
195
195
|
.foregroundStyle(.tertiary)
|
|
196
196
|
.fixedSize(horizontal: false, vertical: true)
|
|
197
197
|
}
|