@sriinnu/runic 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +8 -1
- package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
- package/RuniciOS/Views/ProviderDetailView.swift +9 -9
- package/RuniciOS/Views/ProviderListView.swift +10 -10
- package/RuniciOS/Widgets/LargeWidget.swift +14 -14
- package/RuniciOS/Widgets/LiveActivity.swift +19 -19
- package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
- package/RuniciOS/Widgets/MediumWidget.swift +10 -10
- package/RuniciOS/Widgets/RunicWidget.swift +5 -5
- package/RuniciOS/Widgets/SmallWidget.swift +9 -9
- package/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +7 -3
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +18 -13
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
- package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
- package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
- package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
- package/Sources/Runic/Core/RunicColors.swift +6 -6
- package/Sources/Runic/Core/RunicSpacing.swift +7 -7
- package/Sources/Runic/Core/RunicTypography.swift +206 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SettingsStore.swift +89 -51
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore.swift +187 -161
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +1 -2
- package/Sources/Runic/Core/UsageExporter.swift +8 -4
- package/Sources/Runic/ProviderRegistry.swift +1 -1
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
- package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
- package/Sources/Runic/RunicApp.swift +46 -36
- package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
- package/Sources/Runic/UsagePaceText.swift +1 -1
- package/Sources/Runic/UsageProgressBar.swift +13 -9
- package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
- package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +12 -10
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
- package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
- package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +18 -15
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +22 -17
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +44 -39
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
- package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
- package/Sources/RunicCLI/CLIEntry.swift +83 -42
- package/Sources/RunicCLI/CLIRenderer.swift +1 -1
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
- package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
- package/Sources/RunicClaudeWebProbe/main.swift +1 -1
- package/Sources/RunicCore/AlertRuleStore.swift +20 -20
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
- package/Sources/RunicCore/CostUsageModels.swift +1 -1
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +73 -75
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
- package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
- package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +5 -2
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +7 -7
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +23 -10
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +26 -13
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +48 -21
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
- package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
- package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
- package/Sources/RunicCore/Team/TeamModels.swift +111 -110
- package/Sources/RunicCore/Team/TeamStore.swift +126 -109
- package/Sources/RunicCore/UsageFetcher.swift +2 -2
- package/Sources/RunicCore/UsageFormatter.swift +25 -25
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +27 -13
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +11 -3
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +5 -2
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
- package/Tests/RunicTests/AppDelegateTests.swift +8 -3
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
- package/Tests/RunicTests/CLICostTests.swift +6 -7
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
- package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
- package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
- package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
- package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
- package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
- package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
- package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
- package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
- package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
- package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
- package/Tests/RunicTests/InstallOriginTests.swift +1 -2
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
- package/Tests/RunicTests/LiveAccountTests.swift +3 -3
- package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
- package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
- package/Tests/RunicTests/PathBuilderTests.swift +14 -15
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
- package/Tests/RunicTests/RunicTests.swift +6 -7
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
- package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
- package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
- package/Tests/RunicTests/StatusMenuTests.swift +63 -22
- package/Tests/RunicTests/StatusProbeTests.swift +28 -29
- package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
- package/Tests/RunicTests/TextParsingTests.swift +1 -2
- package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
- package/Tests/RunicTests/UsagePaceTests.swift +5 -6
- package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
- package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
- package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
- package/appcast.xml +36 -76
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -61,14 +61,14 @@ struct SmallWidgetView: View {
|
|
|
61
61
|
.frame(width: 8, height: 8)
|
|
62
62
|
|
|
63
63
|
Text(provider.displayName)
|
|
64
|
-
.font(.system(size: 13, weight: .semibold))
|
|
64
|
+
.font(RunicFont.system(size: 13, weight: .semibold))
|
|
65
65
|
.foregroundColor(.white)
|
|
66
66
|
|
|
67
67
|
Spacer()
|
|
68
68
|
|
|
69
69
|
// Status indicator
|
|
70
70
|
Image(systemName: statusIcon(for: provider))
|
|
71
|
-
.font(.system(size: 12))
|
|
71
|
+
.font(RunicFont.system(size: 12))
|
|
72
72
|
.foregroundColor(statusColor(for: provider))
|
|
73
73
|
}
|
|
74
74
|
.padding(.horizontal, 16)
|
|
@@ -85,19 +85,19 @@ struct SmallWidgetView: View {
|
|
|
85
85
|
VStack(spacing: 2) {
|
|
86
86
|
if let countdown = provider.resetCountdown {
|
|
87
87
|
Text("Resets")
|
|
88
|
-
.font(.system(size: 10, weight: .medium))
|
|
88
|
+
.font(RunicFont.system(size: 10, weight: .medium))
|
|
89
89
|
.foregroundColor(.white.opacity(0.6))
|
|
90
90
|
|
|
91
91
|
Text(countdown)
|
|
92
|
-
.font(.system(size: 12, weight: .semibold
|
|
92
|
+
.font(RunicFont.system(size: 12, weight: .semibold))
|
|
93
93
|
.foregroundColor(.white.opacity(0.9))
|
|
94
94
|
} else {
|
|
95
95
|
Text("Updated")
|
|
96
|
-
.font(.system(size: 10, weight: .medium))
|
|
96
|
+
.font(RunicFont.system(size: 10, weight: .medium))
|
|
97
97
|
.foregroundColor(.white.opacity(0.6))
|
|
98
98
|
|
|
99
99
|
Text(relativeTime(from: provider.updatedAt))
|
|
100
|
-
.font(.system(size: 11, weight: .medium))
|
|
100
|
+
.font(RunicFont.system(size: 11, weight: .medium))
|
|
101
101
|
.foregroundColor(.white.opacity(0.8))
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -160,15 +160,15 @@ struct SmallWidgetView: View {
|
|
|
160
160
|
private var emptyStateView: some View {
|
|
161
161
|
VStack(spacing: 8) {
|
|
162
162
|
Image(systemName: "exclamationmark.triangle")
|
|
163
|
-
.font(.system(size: 24))
|
|
163
|
+
.font(RunicFont.system(size: 24))
|
|
164
164
|
.foregroundColor(.white.opacity(0.5))
|
|
165
165
|
|
|
166
166
|
Text("No Data")
|
|
167
|
-
.font(.system(size: 14, weight: .semibold))
|
|
167
|
+
.font(RunicFont.system(size: 14, weight: .semibold))
|
|
168
168
|
.foregroundColor(.white.opacity(0.7))
|
|
169
169
|
|
|
170
170
|
Text("Open app to refresh")
|
|
171
|
-
.font(.system(size: 11))
|
|
171
|
+
.font(RunicFont.system(size: 11))
|
|
172
172
|
.foregroundColor(.white.opacity(0.5))
|
|
173
173
|
.multilineTextAlignment(.center)
|
|
174
174
|
}
|
|
@@ -24,9 +24,9 @@ func showAbout() {
|
|
|
24
24
|
let description = NSAttributedString(
|
|
25
25
|
string: "Runic tracks AI usage, tokens, and spend across providers from your menu bar.\n\n",
|
|
26
26
|
attributes: [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
.font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),
|
|
28
|
+
.foregroundColor: NSColor.secondaryLabelColor,
|
|
29
|
+
])
|
|
30
30
|
let credits = NSMutableAttributedString(attributedString: description)
|
|
31
31
|
credits.append(NSAttributedString(string: "MIT License\n", attributes: [
|
|
32
32
|
.font: NSFont.systemFont(ofSize: NSFont.smallSystemFontSize),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import RunicCore
|
|
2
1
|
import Foundation
|
|
2
|
+
import RunicCore
|
|
3
3
|
@preconcurrency import UserNotifications
|
|
4
4
|
|
|
5
5
|
@MainActor
|
|
@@ -10,7 +10,9 @@ final class AppNotifications {
|
|
|
10
10
|
private let logger = RunicLog.logger("notifications")
|
|
11
11
|
private var authorizationTask: Task<Bool, Never>?
|
|
12
12
|
|
|
13
|
-
init(centerProvider: @escaping @Sendable ()
|
|
13
|
+
init(centerProvider: @escaping @Sendable ()
|
|
14
|
+
-> UNUserNotificationCenter? = { AppNotifications.safeCurrentCenter() })
|
|
15
|
+
{
|
|
14
16
|
self.centerProvider = centerProvider
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -60,7 +62,7 @@ final class AppNotifications {
|
|
|
60
62
|
/// Safely obtain the current UNUserNotificationCenter, returning nil when the
|
|
61
63
|
/// bundle proxy is not available (e.g. when running a bare executable outside
|
|
62
64
|
/// a proper .app bundle).
|
|
63
|
-
nonisolated
|
|
65
|
+
private nonisolated static func safeCurrentCenter() -> UNUserNotificationCenter? {
|
|
64
66
|
guard Bundle.main.bundleIdentifier != nil else { return nil }
|
|
65
67
|
return UNUserNotificationCenter.current()
|
|
66
68
|
}
|
|
@@ -158,9 +158,13 @@ extension StatusItemController {
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
@objc func showSettingsGeneral() {
|
|
161
|
+
@objc func showSettingsGeneral() {
|
|
162
|
+
self.openSettings(tab: .general)
|
|
163
|
+
}
|
|
162
164
|
|
|
163
|
-
@objc func showSettingsAbout() {
|
|
165
|
+
@objc func showSettingsAbout() {
|
|
166
|
+
self.openSettings(tab: .about)
|
|
167
|
+
}
|
|
164
168
|
|
|
165
169
|
func openMenuFromShortcut() {
|
|
166
170
|
if self.shouldMergeIcons {
|
|
@@ -602,7 +606,7 @@ extension StatusItemController {
|
|
|
602
606
|
self.presentLoginAlert(title: info.title, message: info.message)
|
|
603
607
|
}
|
|
604
608
|
|
|
605
|
-
struct LoginAlertInfo: Equatable
|
|
609
|
+
struct LoginAlertInfo: Equatable {
|
|
606
610
|
let title: String
|
|
607
611
|
let message: String
|
|
608
612
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import AppKit
|
|
2
|
-
import RunicCore
|
|
3
2
|
import QuartzCore
|
|
3
|
+
import RunicCore
|
|
4
4
|
|
|
5
5
|
// MARK: - Animation & Performance Optimization
|
|
6
|
+
|
|
6
7
|
//
|
|
7
8
|
// **Zero Token Leakage Policy**:
|
|
8
9
|
// - Use cookies/local storage from providers FIRST
|
|
@@ -433,14 +434,14 @@ extension StatusItemController {
|
|
|
433
434
|
let isMerged = mergeIcons ?? self.shouldMergeIcons
|
|
434
435
|
let isVisible = isMerged ? self.isEnabled(provider) : self.isVisible(provider)
|
|
435
436
|
guard isVisible else { return false }
|
|
436
|
-
|
|
437
|
+
|
|
437
438
|
// Critical: Only animate while actively fetching
|
|
438
439
|
guard self.store.refreshingProviders.contains(provider) else { return false }
|
|
439
|
-
|
|
440
|
+
|
|
440
441
|
// Stop animation if we have data OR reached error state (stale = error/cached)
|
|
441
442
|
let isStale = self.store.isStale(provider: provider)
|
|
442
443
|
let hasData = self.store.snapshot(for: provider) != nil
|
|
443
|
-
|
|
444
|
+
|
|
444
445
|
// Animation only runs when: no data yet AND not in error/stale state
|
|
445
446
|
return !hasData && !isStale
|
|
446
447
|
}
|
|
@@ -59,7 +59,9 @@ extension StatusItemController {
|
|
|
59
59
|
let reliability = self.store.ledgerReliabilityScore(for: provider)
|
|
60
60
|
let routing = self.store.ledgerRoutingRecommendation(for: provider)
|
|
61
61
|
let hasActiveBlock = activeBlock?.isActive == true
|
|
62
|
-
guard daily != nil || hasActiveBlock || !modelBreakdown.isEmpty || !projectBreakdown
|
|
62
|
+
guard daily != nil || hasActiveBlock || !modelBreakdown.isEmpty || !projectBreakdown
|
|
63
|
+
.isEmpty || reliability != nil || routing != nil
|
|
64
|
+
else {
|
|
63
65
|
return nil
|
|
64
66
|
}
|
|
65
67
|
|
|
@@ -230,22 +232,21 @@ extension StatusItemController {
|
|
|
230
232
|
includeAttribution: Bool = false) -> String
|
|
231
233
|
{
|
|
232
234
|
let trimmedProjectName = projectName?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
233
|
-
let displayName: String
|
|
234
|
-
|
|
235
|
-
displayName = trimmedProjectName
|
|
235
|
+
let displayName: String = if let trimmedProjectName, !trimmedProjectName.isEmpty {
|
|
236
|
+
trimmedProjectName
|
|
236
237
|
} else if let projectID, !projectID.isEmpty {
|
|
237
238
|
if let budgetName = ProjectBudgetStore.getBudget(projectID: projectID)?.projectName?
|
|
238
239
|
.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
239
240
|
!budgetName.isEmpty
|
|
240
241
|
{
|
|
241
|
-
|
|
242
|
+
budgetName
|
|
242
243
|
} else if let fallback = UsageLedgerProjectIdentityResolver.fallbackDisplayName(projectID: projectID) {
|
|
243
|
-
|
|
244
|
+
fallback
|
|
244
245
|
} else {
|
|
245
|
-
|
|
246
|
+
"Unknown project"
|
|
246
247
|
}
|
|
247
248
|
} else {
|
|
248
|
-
|
|
249
|
+
"Unknown project"
|
|
249
250
|
}
|
|
250
251
|
guard includeAttribution else { return displayName }
|
|
251
252
|
guard let annotation = self.projectNameAnnotation(
|
|
@@ -318,10 +319,10 @@ extension StatusItemController {
|
|
|
318
319
|
return nil
|
|
319
320
|
}
|
|
320
321
|
|
|
321
|
-
var hash: UInt64 =
|
|
322
|
+
var hash: UInt64 = 0xCBF2_9CE4_8422_2325
|
|
322
323
|
for byte in projectID.lowercased().utf8 {
|
|
323
324
|
hash ^= UInt64(byte)
|
|
324
|
-
hash = hash &*
|
|
325
|
+
hash = hash &* 0x100_0000_01B3
|
|
325
326
|
}
|
|
326
327
|
return String(format: "%08llx", hash)
|
|
327
328
|
}
|
|
@@ -336,6 +337,7 @@ extension StatusItemController {
|
|
|
336
337
|
let submenu = NSMenu()
|
|
337
338
|
submenu.delegate = self
|
|
338
339
|
let chartView = UsageBreakdownChartMenuView(breakdown: breakdown, width: width)
|
|
340
|
+
.runicTypography()
|
|
339
341
|
let hosting = MenuHostingView(rootView: chartView)
|
|
340
342
|
let controller = NSHostingController(rootView: chartView)
|
|
341
343
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -357,6 +359,7 @@ extension StatusItemController {
|
|
|
357
359
|
let submenu = NSMenu()
|
|
358
360
|
submenu.delegate = self
|
|
359
361
|
let chartView = CreditsHistoryChartMenuView(breakdown: breakdown, width: width)
|
|
362
|
+
.runicTypography()
|
|
360
363
|
let hosting = MenuHostingView(rootView: chartView)
|
|
361
364
|
let controller = NSHostingController(rootView: chartView)
|
|
362
365
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -383,6 +386,7 @@ extension StatusItemController {
|
|
|
383
386
|
daily: tokenSnapshot.daily,
|
|
384
387
|
totalCostUSD: tokenSnapshot.last30DaysCostUSD,
|
|
385
388
|
width: width)
|
|
389
|
+
.runicTypography()
|
|
386
390
|
let hosting = MenuHostingView(rootView: chartView)
|
|
387
391
|
let controller = NSHostingController(rootView: chartView)
|
|
388
392
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -411,6 +415,7 @@ extension StatusItemController {
|
|
|
411
415
|
dailySummaries: dailySummaries,
|
|
412
416
|
hourlySummaries: hourlySummaries,
|
|
413
417
|
width: width)
|
|
418
|
+
.runicTypography()
|
|
414
419
|
let hosting = MenuHostingView(rootView: chartView)
|
|
415
420
|
let controller = NSHostingController(rootView: chartView)
|
|
416
421
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -438,6 +443,7 @@ extension StatusItemController {
|
|
|
438
443
|
let submenu = NSMenu()
|
|
439
444
|
submenu.delegate = self
|
|
440
445
|
let chartView = HourlyActivityChartMenuView(hourlySummaries: hourlySummaries, width: width)
|
|
446
|
+
.runicTypography()
|
|
441
447
|
let hosting = MenuHostingView(rootView: chartView)
|
|
442
448
|
let controller = NSHostingController(rootView: chartView)
|
|
443
449
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -465,6 +471,7 @@ extension StatusItemController {
|
|
|
465
471
|
let submenu = NSMenu()
|
|
466
472
|
submenu.delegate = self
|
|
467
473
|
let chartView = WeeklyActivityChartMenuView(dailySummaries: dailySummaries, width: width)
|
|
474
|
+
.runicTypography()
|
|
468
475
|
let hosting = MenuHostingView(rootView: chartView)
|
|
469
476
|
let controller = NSHostingController(rootView: chartView)
|
|
470
477
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -502,6 +509,7 @@ extension StatusItemController {
|
|
|
502
509
|
currentUsedPercent: currentUsedPercent,
|
|
503
510
|
todayTokens: todayTokens,
|
|
504
511
|
width: width)
|
|
512
|
+
.runicTypography()
|
|
505
513
|
let hosting = MenuHostingView(rootView: chartView)
|
|
506
514
|
let controller = NSHostingController(rootView: chartView)
|
|
507
515
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -544,6 +552,7 @@ extension StatusItemController {
|
|
|
544
552
|
primaryPercent: primaryPercent,
|
|
545
553
|
secondaryPercent: secondaryPercent,
|
|
546
554
|
width: width)
|
|
555
|
+
.runicTypography()
|
|
547
556
|
let hosting = MenuHostingView(rootView: chartView)
|
|
548
557
|
let controller = NSHostingController(rootView: chartView)
|
|
549
558
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -572,6 +581,7 @@ extension StatusItemController {
|
|
|
572
581
|
let submenu = NSMenu()
|
|
573
582
|
submenu.delegate = self
|
|
574
583
|
let chartView = ProjectBreakdownMenuView(breakdown: breakdown, width: width)
|
|
584
|
+
.runicTypography()
|
|
575
585
|
let hosting = MenuHostingView(rootView: chartView)
|
|
576
586
|
let controller = NSHostingController(rootView: chartView)
|
|
577
587
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -598,6 +608,7 @@ extension StatusItemController {
|
|
|
598
608
|
if !breakdown.isEmpty {
|
|
599
609
|
let width = Self.menuCardBaseWidth
|
|
600
610
|
let chartView = ModelBreakdownMenuView(breakdown: breakdown, width: width)
|
|
611
|
+
.runicTypography()
|
|
601
612
|
let hosting = MenuHostingView(rootView: chartView)
|
|
602
613
|
let controller = NSHostingController(rootView: chartView)
|
|
603
614
|
let size = controller.sizeThatFits(in: CGSize(width: width, height: .greatestFiniteMagnitude))
|
|
@@ -663,7 +674,7 @@ extension StatusItemController {
|
|
|
663
674
|
|
|
664
675
|
private func modelQuotaWindows(for provider: UsageProvider) -> [RateWindow] {
|
|
665
676
|
guard let snapshot = self.store.snapshot(for: provider) else { return [] }
|
|
666
|
-
let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap
|
|
677
|
+
let windows = [snapshot.primary, snapshot.secondary, snapshot.tertiary].compactMap(\.self)
|
|
667
678
|
var seen: Set<String> = []
|
|
668
679
|
var result: [RateWindow] = []
|
|
669
680
|
|
|
@@ -691,8 +702,9 @@ extension StatusItemController {
|
|
|
691
702
|
submenu.delegate = self
|
|
692
703
|
|
|
693
704
|
// MARK: Model usage (24h) — tokens, prompts, estimated cost per model
|
|
705
|
+
|
|
694
706
|
if let modelUsage = zai.modelUsage, !modelUsage.entries.isEmpty {
|
|
695
|
-
let headerFont =
|
|
707
|
+
let headerFont = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
|
|
696
708
|
let header = NSMenuItem(title: "Models (24h)", action: nil, keyEquivalent: "")
|
|
697
709
|
header.attributedTitle = NSAttributedString(string: "Models (24h)", attributes: [.font: headerFont])
|
|
698
710
|
header.isEnabled = false
|
|
@@ -706,7 +718,7 @@ extension StatusItemController {
|
|
|
706
718
|
title: "\(totalTokensStr) tokens · \(totalPromptsStr)\(totalCostStr)",
|
|
707
719
|
action: nil, keyEquivalent: "")
|
|
708
720
|
summaryItem.isEnabled = false
|
|
709
|
-
let summaryFont =
|
|
721
|
+
let summaryFont = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
|
|
710
722
|
summaryItem.attributedTitle = NSAttributedString(
|
|
711
723
|
string: summaryItem.title,
|
|
712
724
|
attributes: [.font: summaryFont, .foregroundColor: NSColor.secondaryLabelColor])
|
|
@@ -732,8 +744,9 @@ extension StatusItemController {
|
|
|
732
744
|
}
|
|
733
745
|
|
|
734
746
|
// MARK: Tool usage (24h) — MCP tool call counts
|
|
747
|
+
|
|
735
748
|
if let toolUsage = zai.toolUsage, !toolUsage.entries.isEmpty {
|
|
736
|
-
let headerFont =
|
|
749
|
+
let headerFont = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
|
|
737
750
|
let header = NSMenuItem(title: "MCP Tools (24h)", action: nil, keyEquivalent: "")
|
|
738
751
|
header.attributedTitle = NSAttributedString(string: "MCP Tools (24h)", attributes: [.font: headerFont])
|
|
739
752
|
header.isEnabled = false
|
|
@@ -743,7 +756,7 @@ extension StatusItemController {
|
|
|
743
756
|
title: "\(toolUsage.totalCalls) total calls",
|
|
744
757
|
action: nil, keyEquivalent: "")
|
|
745
758
|
totalItem.isEnabled = false
|
|
746
|
-
let totalFont =
|
|
759
|
+
let totalFont = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
|
|
747
760
|
totalItem.attributedTitle = NSAttributedString(
|
|
748
761
|
string: totalItem.title,
|
|
749
762
|
attributes: [.font: totalFont, .foregroundColor: NSColor.secondaryLabelColor])
|
|
@@ -764,8 +777,9 @@ extension StatusItemController {
|
|
|
764
777
|
}
|
|
765
778
|
|
|
766
779
|
// MARK: Quota MCP details (existing — from timeLimit.usageDetails)
|
|
780
|
+
|
|
767
781
|
if let timeLimit = zai.timeLimit, !timeLimit.usageDetails.isEmpty {
|
|
768
|
-
let headerFont =
|
|
782
|
+
let headerFont = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
|
|
769
783
|
let header = NSMenuItem(title: "Quota Window", action: nil, keyEquivalent: "")
|
|
770
784
|
header.attributedTitle = NSAttributedString(string: "Quota Window", attributes: [.font: headerFont])
|
|
771
785
|
header.isEnabled = false
|
|
@@ -13,7 +13,7 @@ extension StatusItemController {
|
|
|
13
13
|
|
|
14
14
|
let headerItem = NSMenuItem(title: "Custom Providers", action: nil, keyEquivalent: "")
|
|
15
15
|
headerItem.isEnabled = false
|
|
16
|
-
let font =
|
|
16
|
+
let font = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
|
|
17
17
|
headerItem.attributedTitle = NSAttributedString(string: "Custom Providers", attributes: [.font: font])
|
|
18
18
|
menu.addItem(headerItem)
|
|
19
19
|
|
|
@@ -28,7 +28,10 @@ extension StatusItemController {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
private func makeCustomProviderMenuItem(
|
|
31
|
+
private func makeCustomProviderMenuItem(
|
|
32
|
+
provider: CustomProviderConfig,
|
|
33
|
+
snapshot: CustomProviderSnapshot) -> NSMenuItem
|
|
34
|
+
{
|
|
32
35
|
let title: String
|
|
33
36
|
if let quota = snapshot.usageData.quota, let used = snapshot.usageData.used, quota > 0 {
|
|
34
37
|
let percent = Int((used / quota) * 100)
|
|
@@ -61,7 +64,7 @@ extension StatusItemController {
|
|
|
61
64
|
item.image = image
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
let font =
|
|
67
|
+
let font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
|
|
65
68
|
let subtitle = NSAttributedString(
|
|
66
69
|
string: error,
|
|
67
70
|
attributes: [.font: font, .foregroundColor: NSColor.systemRed])
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import AppKit
|
|
2
|
-
import RunicCore
|
|
3
2
|
import Observation
|
|
3
|
+
import RunicCore
|
|
4
4
|
import SwiftUI
|
|
5
5
|
|
|
6
6
|
// MARK: - NSMenu construction
|
|
7
7
|
|
|
8
8
|
extension StatusItemController {
|
|
9
|
-
static let menuCardBaseWidth: CGFloat =
|
|
9
|
+
static let menuCardBaseWidth: CGFloat = 340
|
|
10
10
|
private static let menuOpenPingDelay: Duration = PerformanceConstants.menuOpenPingDelay
|
|
11
11
|
|
|
12
12
|
func menuCardWidth(for providers: [UsageProvider], menu: NSMenu? = nil) -> CGFloat {
|
|
@@ -148,8 +148,9 @@ extension StatusItemController {
|
|
|
148
148
|
width: menuWidth,
|
|
149
149
|
menu: menu)
|
|
150
150
|
if let tabBarView {
|
|
151
|
-
let
|
|
152
|
-
let
|
|
151
|
+
let monoTabBar = tabBarView.runicTypography()
|
|
152
|
+
let hosting = MenuHostingView(rootView: monoTabBar)
|
|
153
|
+
let controller = NSHostingController(rootView: monoTabBar)
|
|
153
154
|
let size = controller.sizeThatFits(in: CGSize(width: menuWidth, height: .greatestFiniteMagnitude))
|
|
154
155
|
hosting.frame = NSRect(origin: .zero, size: NSSize(width: menuWidth, height: size.height))
|
|
155
156
|
let tabItem = NSMenuItem()
|
|
@@ -172,8 +173,9 @@ extension StatusItemController {
|
|
|
172
173
|
let overviewView = self.makeOverviewView(
|
|
173
174
|
providers: enabledProviders,
|
|
174
175
|
width: menuWidth)
|
|
175
|
-
let
|
|
176
|
-
let
|
|
176
|
+
let monoOverview = overviewView.runicTypography()
|
|
177
|
+
let hosting = MenuHostingView(rootView: monoOverview)
|
|
178
|
+
let controller = NSHostingController(rootView: monoOverview)
|
|
177
179
|
let size = controller.sizeThatFits(in: CGSize(width: menuWidth, height: .greatestFiniteMagnitude))
|
|
178
180
|
hosting.frame = NSRect(origin: .zero, size: NSSize(width: menuWidth, height: size.height))
|
|
179
181
|
let overviewItem = NSMenuItem()
|
|
@@ -266,10 +268,10 @@ extension StatusItemController {
|
|
|
266
268
|
let item = NSMenuItem(title: text, action: nil, keyEquivalent: "")
|
|
267
269
|
item.isEnabled = false
|
|
268
270
|
if style == .headline {
|
|
269
|
-
let font =
|
|
271
|
+
let font = RunicFont.nsFont(size: NSFont.systemFontSize, weight: .semibold)
|
|
270
272
|
item.attributedTitle = NSAttributedString(string: text, attributes: [.font: font])
|
|
271
273
|
} else if style == .secondary {
|
|
272
|
-
let font =
|
|
274
|
+
let font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
|
|
273
275
|
item.attributedTitle = NSAttributedString(
|
|
274
276
|
string: text,
|
|
275
277
|
attributes: [.font: font, .foregroundColor: NSColor.secondaryLabelColor])
|
|
@@ -306,6 +308,8 @@ extension StatusItemController {
|
|
|
306
308
|
menu.addItem(.separator())
|
|
307
309
|
}
|
|
308
310
|
}
|
|
311
|
+
|
|
312
|
+
self.applyRunicFont(to: menu)
|
|
309
313
|
}
|
|
310
314
|
|
|
311
315
|
func makeMenu(for provider: UsageProvider?) -> NSMenu {
|
|
@@ -502,7 +506,7 @@ extension StatusItemController {
|
|
|
502
506
|
|
|
503
507
|
private static func defaultContextLabel(for provider: UsageProvider) -> String? {
|
|
504
508
|
guard let k = self.defaultContextWindows[provider.rawValue] else { return nil }
|
|
505
|
-
return k >=
|
|
509
|
+
return k >= 1000 ? "ctx \(k / 1000)M" : "ctx \(k)K"
|
|
506
510
|
}
|
|
507
511
|
|
|
508
512
|
private func resolvedMenuProvider() -> UsageProvider? {
|
|
@@ -678,7 +682,7 @@ extension StatusItemController {
|
|
|
678
682
|
}
|
|
679
683
|
|
|
680
684
|
private func isHostedSubviewMenu(_ menu: NSMenu) -> Bool {
|
|
681
|
-
let ids: Set
|
|
685
|
+
let ids: Set = [
|
|
682
686
|
"usageBreakdownChart",
|
|
683
687
|
"creditsHistoryChart",
|
|
684
688
|
"costHistoryChart",
|
|
@@ -838,7 +842,7 @@ private final class ProviderSwitcherView: NSView {
|
|
|
838
842
|
button.bezelStyle = .regularSquare
|
|
839
843
|
button.isBordered = false
|
|
840
844
|
button.controlSize = .small
|
|
841
|
-
button.font =
|
|
845
|
+
button.font = RunicFont.nsFont(size: 12, weight: .medium)
|
|
842
846
|
button.setButtonType(.toggle)
|
|
843
847
|
button.contentTintColor = self.unselectedTextColor
|
|
844
848
|
button.alignment = .center
|
|
@@ -1224,7 +1228,7 @@ private final class ProviderSwitcherView: NSView {
|
|
|
1224
1228
|
if self.stackedIcons,
|
|
1225
1229
|
self.segments.indices.contains(index)
|
|
1226
1230
|
{
|
|
1227
|
-
let font =
|
|
1231
|
+
let font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
|
|
1228
1232
|
let titleWidth = ceil((self.segments[index].title as NSString).size(withAttributes: [.font: font])
|
|
1229
1233
|
.width)
|
|
1230
1234
|
let contentPadding: CGFloat = 6 + 6
|
|
@@ -1358,7 +1362,8 @@ private final class ProviderSwitcherView: NSView {
|
|
|
1358
1362
|
|
|
1359
1363
|
let track = NSView()
|
|
1360
1364
|
track.wantsLayer = true
|
|
1361
|
-
track.layer?.backgroundColor = NSColor.tertiaryLabelColor
|
|
1365
|
+
track.layer?.backgroundColor = NSColor.tertiaryLabelColor
|
|
1366
|
+
.withAlphaComponent(CGFloat(RunicColors.Opacity.medium)).cgColor
|
|
1362
1367
|
track.layer?.cornerRadius = 3
|
|
1363
1368
|
track.layer?.masksToBounds = true
|
|
1364
1369
|
track.translatesAutoresizingMaskIntoConstraints = false
|
|
@@ -64,8 +64,8 @@ extension StatusItemController {
|
|
|
64
64
|
{
|
|
65
65
|
let menuMode = self.settings.menuMode
|
|
66
66
|
let includeSummarySections = menuMode != .glance
|
|
67
|
-
let includeInsights = menuMode ==
|
|
68
|
-
let includeActions = menuMode ==
|
|
67
|
+
let includeInsights = menuMode == .operator
|
|
68
|
+
let includeActions = menuMode == .operator
|
|
69
69
|
let hasUsageBlock = !model.metrics.isEmpty || model.placeholder != nil
|
|
70
70
|
let hasCredits = includeSummarySections && model.creditsText != nil
|
|
71
71
|
let hasExtraUsage = includeSummarySections && model.providerCost != nil
|
|
@@ -282,7 +282,9 @@ extension StatusItemController {
|
|
|
282
282
|
formatter.dateFormat = "yyyy-MM-dd"
|
|
283
283
|
formatter.timeZone = TimeZone.current
|
|
284
284
|
var tokensByDay: [String: Int] = [:]
|
|
285
|
-
for d in allDaily {
|
|
285
|
+
for d in allDaily {
|
|
286
|
+
tokensByDay[d.dayKey, default: 0] += d.totals.totalTokens
|
|
287
|
+
}
|
|
286
288
|
return (0..<7).map { offset in
|
|
287
289
|
guard let date = calendar.date(byAdding: .day, value: offset - 6, to: todayStart) else { return 0 }
|
|
288
290
|
return tokensByDay[formatter.string(from: date)] ?? 0
|
|
@@ -306,7 +308,7 @@ extension StatusItemController {
|
|
|
306
308
|
let updatedAt: Date? = [
|
|
307
309
|
self.store.snapshot(for: provider)?.updatedAt,
|
|
308
310
|
self.store.ledgerUpdatedAt[provider],
|
|
309
|
-
].compactMap
|
|
311
|
+
].compactMap(\.self).max()
|
|
310
312
|
|
|
311
313
|
if let updatedAt {
|
|
312
314
|
let timestampView = self.menuCardContent(width: width, sidebar: sidebar, showIcons: true) {
|
|
@@ -36,16 +36,26 @@ extension StatusItemController {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
final class MenuHostingView<Content: View>: NSHostingView<Content> {
|
|
39
|
-
override var allowsVibrancy: Bool {
|
|
40
|
-
|
|
39
|
+
override var allowsVibrancy: Bool {
|
|
40
|
+
true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override var isOpaque: Bool {
|
|
44
|
+
false
|
|
45
|
+
}
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
@MainActor
|
|
44
49
|
final class MenuCardItemHostingView<Content: View>: NSHostingView<Content>, MenuCardHighlighting,
|
|
45
50
|
MenuCardMeasuring {
|
|
46
51
|
private let highlightState: MenuCardHighlightState
|
|
47
|
-
override var allowsVibrancy: Bool {
|
|
48
|
-
|
|
52
|
+
override var allowsVibrancy: Bool {
|
|
53
|
+
true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override var isOpaque: Bool {
|
|
57
|
+
false
|
|
58
|
+
}
|
|
49
59
|
|
|
50
60
|
override var intrinsicContentSize: NSSize {
|
|
51
61
|
let size = super.intrinsicContentSize
|
|
@@ -98,6 +108,7 @@ extension StatusItemController {
|
|
|
98
108
|
|
|
99
109
|
var body: some View {
|
|
100
110
|
self.content
|
|
111
|
+
.runicTypography()
|
|
101
112
|
.environment(\.menuItemHighlighted, self.highlightState.isHighlighted)
|
|
102
113
|
.foregroundStyle(MenuHighlightStyle.primary(self.highlightState.isHighlighted))
|
|
103
114
|
.background(alignment: .topLeading) {
|
|
@@ -111,7 +122,7 @@ extension StatusItemController {
|
|
|
111
122
|
.overlay(alignment: .topTrailing) {
|
|
112
123
|
if self.showsSubmenuIndicator {
|
|
113
124
|
Image(systemName: "chevron.right")
|
|
114
|
-
.font(.caption2.weight(.semibold))
|
|
125
|
+
.font(RunicFont.caption2.weight(.semibold))
|
|
115
126
|
.foregroundStyle(MenuHighlightStyle.secondary(self.highlightState.isHighlighted))
|
|
116
127
|
.padding(.top, RunicSpacing.xs)
|
|
117
128
|
.padding(.trailing, RunicSpacing.xs)
|
|
@@ -142,7 +153,7 @@ extension StatusItemController {
|
|
|
142
153
|
self.iconView.imageScaling = .scaleNone
|
|
143
154
|
self.iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
144
155
|
|
|
145
|
-
self.titleField.font =
|
|
156
|
+
self.titleField.font = RunicFont.nsFont(size: NSFont.systemFontSize)
|
|
146
157
|
self.titleField.lineBreakMode = .byTruncatingTail
|
|
147
158
|
self.titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
148
159
|
|
|
@@ -217,11 +228,11 @@ extension StatusItemController {
|
|
|
217
228
|
// MARK: - Sidebar content builder
|
|
218
229
|
|
|
219
230
|
@ViewBuilder
|
|
220
|
-
func menuCardContent
|
|
231
|
+
func menuCardContent(
|
|
221
232
|
width: CGFloat,
|
|
222
233
|
sidebar: MenuCardSidebarConfig?,
|
|
223
234
|
showIcons: Bool,
|
|
224
|
-
@ViewBuilder content: @escaping (CGFloat) ->
|
|
235
|
+
@ViewBuilder content: @escaping (CGFloat) -> some View) -> some View
|
|
225
236
|
{
|
|
226
237
|
if let sidebar {
|
|
227
238
|
ProviderSidebarMenuCardView(
|
|
@@ -325,8 +336,8 @@ extension StatusItemController {
|
|
|
325
336
|
let image = NSImage(
|
|
326
337
|
systemSymbolName: MenuDescriptor.MenuActionSystemImage.refresh.rawValue,
|
|
327
338
|
accessibilityDescription: nil) ?? NSImage(
|
|
328
|
-
|
|
329
|
-
|
|
339
|
+
systemSymbolName: "arrow.triangle.2.circlepath",
|
|
340
|
+
accessibilityDescription: nil)
|
|
330
341
|
image?.isTemplate = true
|
|
331
342
|
image?.size = NSSize(width: 16, height: 16)
|
|
332
343
|
let view = MenuActionButtonView(title: title, image: image) { [weak self] in
|
|
@@ -363,14 +374,14 @@ extension StatusItemController {
|
|
|
363
374
|
container.alphaValue = isEnabled ? 1.0 : 0.7
|
|
364
375
|
|
|
365
376
|
let titleField = NSTextField(labelWithString: title)
|
|
366
|
-
titleField.font =
|
|
377
|
+
titleField.font = RunicFont.nsFont(size: NSFont.systemFontSize)
|
|
367
378
|
titleField.textColor = NSColor.labelColor
|
|
368
379
|
titleField.lineBreakMode = .byTruncatingTail
|
|
369
380
|
titleField.maximumNumberOfLines = 1
|
|
370
381
|
titleField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
371
382
|
|
|
372
383
|
let subtitleField = NSTextField(labelWithString: subtitle)
|
|
373
|
-
subtitleField.font =
|
|
384
|
+
subtitleField.font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
|
|
374
385
|
subtitleField.textColor = NSColor.secondaryLabelColor
|
|
375
386
|
subtitleField.lineBreakMode = .byTruncatingTail
|
|
376
387
|
subtitleField.maximumNumberOfLines = 1
|
|
@@ -392,4 +403,21 @@ extension StatusItemController {
|
|
|
392
403
|
|
|
393
404
|
return container
|
|
394
405
|
}
|
|
406
|
+
|
|
407
|
+
// MARK: - Fira Code menu font post-processor
|
|
408
|
+
|
|
409
|
+
/// Recursively apply Fira Code to every NSMenuItem that uses a plain title (no custom view).
|
|
410
|
+
func applyRunicFont(to menu: NSMenu) {
|
|
411
|
+
for item in menu.items {
|
|
412
|
+
guard !item.isSeparatorItem, item.view == nil, !item.title.isEmpty else { continue }
|
|
413
|
+
if item.attributedTitle == nil {
|
|
414
|
+
item.attributedTitle = NSAttributedString(
|
|
415
|
+
string: item.title,
|
|
416
|
+
attributes: [.font: RunicFont.nsFont(size: NSFont.systemFontSize)])
|
|
417
|
+
}
|
|
418
|
+
if let submenu = item.submenu {
|
|
419
|
+
self.applyRunicFont(to: submenu)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
395
423
|
}
|