@sriinnu/runic 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +1 -1
- package/README.md +47 -29
- package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
- package/RuniciOS/Views/ProviderDetailView.swift +9 -9
- package/RuniciOS/Views/ProviderListView.swift +10 -10
- package/RuniciOS/Widgets/LargeWidget.swift +14 -14
- package/RuniciOS/Widgets/LiveActivity.swift +19 -19
- package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
- package/RuniciOS/Widgets/MediumWidget.swift +10 -10
- package/RuniciOS/Widgets/RunicWidget.swift +5 -5
- package/RuniciOS/Widgets/SmallWidget.swift +9 -9
- package/Scripts/package_app.sh +2 -2
- package/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +17 -4
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +47 -14
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
- package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
- package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +3 -6
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
- package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
- package/Sources/Runic/Core/RunicColors.swift +6 -6
- package/Sources/Runic/Core/RunicSpacing.swift +7 -7
- package/Sources/Runic/Core/RunicTypography.swift +206 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SettingsStore.swift +99 -52
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore.swift +234 -167
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +10 -36
- package/Sources/Runic/Core/UsageExporter.swift +8 -4
- package/Sources/Runic/ProviderRegistry.swift +1 -1
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
- package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/provider-context-windows.json +20 -0
- package/Sources/Runic/RunicApp.swift +46 -36
- package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
- package/Sources/Runic/UsagePaceText.swift +1 -1
- package/Sources/Runic/UsageProgressBar.swift +13 -9
- package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
- package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +15 -20
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
- package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
- package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +198 -58
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +28 -76
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +108 -33
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
- package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
- package/Sources/RunicCLI/CLIEntry.swift +83 -42
- package/Sources/RunicCLI/CLIRenderer.swift +1 -1
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
- package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
- package/Sources/RunicClaudeWebProbe/main.swift +1 -1
- package/Sources/RunicCore/AlertRuleStore.swift +20 -20
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
- package/Sources/RunicCore/CostUsageModels.swift +1 -1
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +80 -71
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +6 -6
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
- package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
- package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +1 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +12 -1
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +1 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +3 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +6 -2
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +8 -11
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +25 -16
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +27 -16
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +51 -30
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
- package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
- package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
- package/Sources/RunicCore/Team/TeamModels.swift +111 -110
- package/Sources/RunicCore/Team/TeamStore.swift +126 -109
- package/Sources/RunicCore/UsageFetcher.swift +2 -2
- package/Sources/RunicCore/UsageFormatter.swift +25 -25
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +141 -29
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +59 -5
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +161 -0
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
- package/Tests/RunicTests/AppDelegateTests.swift +8 -3
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
- package/Tests/RunicTests/CLICostTests.swift +6 -7
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
- package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
- package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
- package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
- package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
- package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
- package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
- package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
- package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
- package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
- package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
- package/Tests/RunicTests/InstallOriginTests.swift +1 -2
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
- package/Tests/RunicTests/LiveAccountTests.swift +3 -3
- package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
- package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
- package/Tests/RunicTests/PathBuilderTests.swift +14 -15
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
- package/Tests/RunicTests/RunicTests.swift +6 -7
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
- package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
- package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
- package/Tests/RunicTests/StatusMenuTests.swift +63 -22
- package/Tests/RunicTests/StatusProbeTests.swift +28 -29
- package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
- package/Tests/RunicTests/TextParsingTests.swift +1 -2
- package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
- package/Tests/RunicTests/UsagePaceTests.swift +5 -6
- package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
- package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
- package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
- package/appcast.xml +36 -37
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -1,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 {
|
|
@@ -432,6 +436,13 @@ extension StatusItemController {
|
|
|
432
436
|
totalToday += todayTokens
|
|
433
437
|
|
|
434
438
|
let resetDesc = snapshot?.primary.resetDescription
|
|
439
|
+
let windowLabel = snapshot?.primary.label?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
440
|
+
let topModel = self.store.ledgerTopModel(for: provider)
|
|
441
|
+
let topModelContext: String? = if let model = topModel?.model {
|
|
442
|
+
UsageFormatter.modelContextLabel(for: model)
|
|
443
|
+
} else {
|
|
444
|
+
Self.defaultContextLabel(for: provider)
|
|
445
|
+
}
|
|
435
446
|
|
|
436
447
|
summaries.append(OverviewMenuView.ProviderSummary(
|
|
437
448
|
id: provider.rawValue,
|
|
@@ -441,7 +452,9 @@ extension StatusItemController {
|
|
|
441
452
|
usedPercent: usedPercent,
|
|
442
453
|
todayTokens: todayTokens,
|
|
443
454
|
brandColor: brandColor,
|
|
444
|
-
resetDescription: resetDesc
|
|
455
|
+
resetDescription: resetDesc,
|
|
456
|
+
windowLabel: windowLabel,
|
|
457
|
+
topModelContext: topModelContext))
|
|
445
458
|
|
|
446
459
|
// Chart data — last 7 days
|
|
447
460
|
let dailySummaries = self.store.ledgerAllDailySummary(for: provider)
|
|
@@ -450,7 +463,8 @@ extension StatusItemController {
|
|
|
450
463
|
id: "\(provider.rawValue)-\(summary.dayKey)",
|
|
451
464
|
date: summary.dayStart,
|
|
452
465
|
tokens: summary.totals.totalTokens,
|
|
453
|
-
provider: meta.displayName
|
|
466
|
+
provider: meta.displayName,
|
|
467
|
+
color: brandColor))
|
|
454
468
|
}
|
|
455
469
|
}
|
|
456
470
|
|
|
@@ -461,6 +475,7 @@ extension StatusItemController {
|
|
|
461
475
|
summaries: activeSummaries.isEmpty ? summaries : activeSummaries,
|
|
462
476
|
chartPoints: chartPoints,
|
|
463
477
|
totalTodayTokens: totalToday,
|
|
478
|
+
totalProviders: UsageProvider.allCases.count,
|
|
464
479
|
width: width)
|
|
465
480
|
}
|
|
466
481
|
|
|
@@ -477,6 +492,23 @@ extension StatusItemController {
|
|
|
477
492
|
return abbreviations[name] ?? String(name.prefix(6))
|
|
478
493
|
}
|
|
479
494
|
|
|
495
|
+
/// Default flagship model context window per provider, loaded from
|
|
496
|
+
/// `Resources/provider-context-windows.json`. Update the JSON to add/change
|
|
497
|
+
/// context windows without recompiling.
|
|
498
|
+
private static let defaultContextWindows: [String: Int] = {
|
|
499
|
+
struct Entry: Decodable { let contextK: Int }
|
|
500
|
+
guard let url = Bundle.main.url(forResource: "provider-context-windows", withExtension: "json"),
|
|
501
|
+
let data = try? Data(contentsOf: url),
|
|
502
|
+
let dict = try? JSONDecoder().decode([String: Entry].self, from: data)
|
|
503
|
+
else { return [:] }
|
|
504
|
+
return dict.mapValues(\.contextK)
|
|
505
|
+
}()
|
|
506
|
+
|
|
507
|
+
private static func defaultContextLabel(for provider: UsageProvider) -> String? {
|
|
508
|
+
guard let k = self.defaultContextWindows[provider.rawValue] else { return nil }
|
|
509
|
+
return k >= 1000 ? "ctx \(k / 1000)M" : "ctx \(k)K"
|
|
510
|
+
}
|
|
511
|
+
|
|
480
512
|
private func resolvedMenuProvider() -> UsageProvider? {
|
|
481
513
|
let enabled = self.store.enabledProviders()
|
|
482
514
|
if enabled.isEmpty { return .codex }
|
|
@@ -650,7 +682,7 @@ extension StatusItemController {
|
|
|
650
682
|
}
|
|
651
683
|
|
|
652
684
|
private func isHostedSubviewMenu(_ menu: NSMenu) -> Bool {
|
|
653
|
-
let ids: Set
|
|
685
|
+
let ids: Set = [
|
|
654
686
|
"usageBreakdownChart",
|
|
655
687
|
"creditsHistoryChart",
|
|
656
688
|
"costHistoryChart",
|
|
@@ -810,7 +842,7 @@ private final class ProviderSwitcherView: NSView {
|
|
|
810
842
|
button.bezelStyle = .regularSquare
|
|
811
843
|
button.isBordered = false
|
|
812
844
|
button.controlSize = .small
|
|
813
|
-
button.font =
|
|
845
|
+
button.font = RunicFont.nsFont(size: 12, weight: .medium)
|
|
814
846
|
button.setButtonType(.toggle)
|
|
815
847
|
button.contentTintColor = self.unselectedTextColor
|
|
816
848
|
button.alignment = .center
|
|
@@ -1196,7 +1228,7 @@ private final class ProviderSwitcherView: NSView {
|
|
|
1196
1228
|
if self.stackedIcons,
|
|
1197
1229
|
self.segments.indices.contains(index)
|
|
1198
1230
|
{
|
|
1199
|
-
let font =
|
|
1231
|
+
let font = RunicFont.nsFont(size: NSFont.smallSystemFontSize)
|
|
1200
1232
|
let titleWidth = ceil((self.segments[index].title as NSString).size(withAttributes: [.font: font])
|
|
1201
1233
|
.width)
|
|
1202
1234
|
let contentPadding: CGFloat = 6 + 6
|
|
@@ -1330,7 +1362,8 @@ private final class ProviderSwitcherView: NSView {
|
|
|
1330
1362
|
|
|
1331
1363
|
let track = NSView()
|
|
1332
1364
|
track.wantsLayer = true
|
|
1333
|
-
track.layer?.backgroundColor = NSColor.tertiaryLabelColor
|
|
1365
|
+
track.layer?.backgroundColor = NSColor.tertiaryLabelColor
|
|
1366
|
+
.withAlphaComponent(CGFloat(RunicColors.Opacity.medium)).cgColor
|
|
1334
1367
|
track.layer?.cornerRadius = 3
|
|
1335
1368
|
track.layer?.masksToBounds = true
|
|
1336
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
|
}
|
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
import AppKit
|
|
2
|
-
import RunicCore
|
|
3
2
|
import Observation
|
|
4
3
|
import QuartzCore
|
|
4
|
+
import RunicCore
|
|
5
5
|
import SwiftUI
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
7
|
+
// **StatusItemController** - Manages macOS menubar status item and dropdown menu
|
|
8
|
+
//
|
|
9
|
+
// **Purpose:**
|
|
10
|
+
// Coordinates the menubar icon, animations, menu construction, and user interactions.
|
|
11
|
+
// Acts as the bridge between AppKit (NSStatusItem) and SwiftUI (menu cards).
|
|
12
|
+
//
|
|
13
|
+
// **Responsibilities:**
|
|
14
|
+
// - Create and manage NSStatusItem in menubar
|
|
15
|
+
// - Coordinate icon rendering via IconRenderer
|
|
16
|
+
// - Build rich NSMenu with embedded SwiftUI views
|
|
17
|
+
// - Handle animation lifecycle (60 FPS during pings)
|
|
18
|
+
// - Schedule background "ping" operations when menu opens
|
|
19
|
+
// - Manage keyboard shortcuts and user actions
|
|
20
|
+
//
|
|
21
|
+
// **Architecture Pattern:**
|
|
22
|
+
// This file is split across 4 files via extensions:
|
|
23
|
+
// - `StatusItemController.swift` - Core setup (318 lines)
|
|
24
|
+
// - `StatusItemController+Menu.swift` - Menu construction (1878 lines)
|
|
25
|
+
// - `StatusItemController+Actions.swift` - User actions (200 lines)
|
|
26
|
+
// - `StatusItemController+Animation.swift` - Animation system (469 lines)
|
|
27
|
+
//
|
|
28
|
+
// **Performance:**
|
|
29
|
+
// - **60 FPS cap** during active pings (PerformanceConstants.menubarFPS)
|
|
30
|
+
// - **Auto-stop animations** when data loaded or error encountered
|
|
31
|
+
// - **1.2s delay** before background ping when menu opens (reduce perceived lag)
|
|
32
|
+
//
|
|
33
|
+
// **Dependencies:**
|
|
34
|
+
// - `UsageStore` - Data source
|
|
35
|
+
// - `SettingsStore` - User preferences
|
|
36
|
+
// - `IconRenderer` - Icon generation
|
|
37
|
+
// - `MenuCardView` - SwiftUI menu content
|
|
38
|
+
//
|
|
39
|
+
// **Usage:**
|
|
40
|
+
// ```swift
|
|
41
|
+
// let controller = StatusItemController(
|
|
42
|
+
// store: usageStore,
|
|
43
|
+
// settings: settingsStore,
|
|
44
|
+
// account: accountInfo,
|
|
45
|
+
// updater: updater,
|
|
46
|
+
// preferencesSelection: selection
|
|
47
|
+
// )
|
|
48
|
+
// ```
|
|
49
49
|
|
|
50
50
|
// MARK: - Status item controller (AppKit-hosted icons, SwiftUI popovers)
|
|
51
51
|
|
|
@@ -59,7 +59,8 @@ final class BudgetNotificationManager {
|
|
|
59
59
|
let title = "Budget Alert: \(projectName)"
|
|
60
60
|
let body = "Projected to exceed \(limitStr) limit by \(overshootStr) this month"
|
|
61
61
|
|
|
62
|
-
self.logger.info(
|
|
62
|
+
self.logger.info(
|
|
63
|
+
"posting budget breach notification",
|
|
63
64
|
metadata: [
|
|
64
65
|
"provider": provider.rawValue,
|
|
65
66
|
"project": projectKey,
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import AppKit
|
|
2
|
-
import RunicCore
|
|
3
2
|
import Foundation
|
|
3
|
+
import RunicCore
|
|
4
4
|
import WebKit
|
|
5
5
|
|
|
6
6
|
/// Handles Cursor login flow using a WebKit-based browser window.
|
|
7
7
|
/// Captures session cookies after successful authentication.
|
|
8
8
|
@MainActor
|
|
9
9
|
final class CursorLoginRunner: NSObject {
|
|
10
|
-
enum Phase
|
|
10
|
+
enum Phase {
|
|
11
11
|
case loading
|
|
12
12
|
case waitingLogin
|
|
13
13
|
case success
|
|
14
14
|
case failed(String)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
struct Result
|
|
18
|
-
enum Outcome
|
|
17
|
+
struct Result {
|
|
18
|
+
enum Outcome {
|
|
19
19
|
case success
|
|
20
20
|
case cancelled
|
|
21
21
|
case failed(String)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import AppKit
|
|
2
|
-
import RunicCore
|
|
3
2
|
import Foundation
|
|
3
|
+
import RunicCore
|
|
4
4
|
|
|
5
5
|
enum GeminiLoginRunner {
|
|
6
6
|
private static let geminiConfigDir = FileManager.default.homeDirectoryForCurrentUser
|
|
@@ -45,11 +45,8 @@ enum GeminiLoginRunner {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Create a temporary shell script that runs gemini (auto-prompts for auth when no creds)
|
|
48
|
-
let
|
|
49
|
-
#!/bin/bash
|
|
50
|
-
cd ~
|
|
51
|
-
"\(binary)"
|
|
52
|
-
"""
|
|
48
|
+
let escaped = binary.replacingOccurrences(of: "'", with: "'\\''")
|
|
49
|
+
let scriptContent = "#!/bin/bash\ncd ~\n'\(escaped)'\n"
|
|
53
50
|
|
|
54
51
|
let tempDir = FileManager.default.temporaryDirectory
|
|
55
52
|
let scriptURL = tempDir.appendingPathComponent("gemini_login_\(UUID().uuidString).command")
|