@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
|
@@ -80,18 +80,18 @@ struct MediumWidgetView: View {
|
|
|
80
80
|
HStack(spacing: 8) {
|
|
81
81
|
// Status icon
|
|
82
82
|
Image(systemName: entry.systemStatus.iconName)
|
|
83
|
-
.font(.system(size: 16, weight: .semibold))
|
|
83
|
+
.font(RunicFont.system(size: 16, weight: .semibold))
|
|
84
84
|
.foregroundColor(statusColor)
|
|
85
85
|
|
|
86
86
|
Text("Runic")
|
|
87
|
-
.font(.system(size: 16, weight: .bold))
|
|
87
|
+
.font(RunicFont.system(size: 16, weight: .bold))
|
|
88
88
|
.foregroundColor(.white)
|
|
89
89
|
|
|
90
90
|
Spacer()
|
|
91
91
|
|
|
92
92
|
// Provider count
|
|
93
93
|
Text("\(providers.count) active")
|
|
94
|
-
.font(.system(size: 12, weight: .medium))
|
|
94
|
+
.font(RunicFont.system(size: 12, weight: .medium))
|
|
95
95
|
.foregroundColor(.white.opacity(0.7))
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -110,18 +110,18 @@ struct MediumWidgetView: View {
|
|
|
110
110
|
.frame(width: 10, height: 10)
|
|
111
111
|
|
|
112
112
|
Text(provider.displayName)
|
|
113
|
-
.font(.system(size: 13, weight: .semibold))
|
|
113
|
+
.font(RunicFont.system(size: 13, weight: .semibold))
|
|
114
114
|
.foregroundColor(.white)
|
|
115
115
|
|
|
116
116
|
Spacer()
|
|
117
117
|
|
|
118
118
|
// Remaining percentage
|
|
119
119
|
Text("\(Int(provider.remainingPercent))%")
|
|
120
|
-
.font(.system(size: 15, weight: .bold
|
|
120
|
+
.font(RunicFont.system(size: 15, weight: .bold))
|
|
121
121
|
.foregroundColor(.white)
|
|
122
122
|
|
|
123
123
|
Text("left")
|
|
124
|
-
.font(.system(size: 11, weight: .medium))
|
|
124
|
+
.font(RunicFont.system(size: 11, weight: .medium))
|
|
125
125
|
.foregroundColor(.white.opacity(0.6))
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -144,7 +144,7 @@ struct MediumWidgetView: View {
|
|
|
144
144
|
if let countdown = provider.resetCountdown {
|
|
145
145
|
HStack {
|
|
146
146
|
Text("Resets \(countdown)")
|
|
147
|
-
.font(.system(size: 10, weight: .medium))
|
|
147
|
+
.font(RunicFont.system(size: 10, weight: .medium))
|
|
148
148
|
.foregroundColor(.white.opacity(0.5))
|
|
149
149
|
|
|
150
150
|
Spacer()
|
|
@@ -228,16 +228,16 @@ struct MediumWidgetView: View {
|
|
|
228
228
|
private var emptyStateView: some View {
|
|
229
229
|
HStack(spacing: 12) {
|
|
230
230
|
Image(systemName: "chart.bar.xaxis")
|
|
231
|
-
.font(.system(size: 28))
|
|
231
|
+
.font(RunicFont.system(size: 28))
|
|
232
232
|
.foregroundColor(.white.opacity(0.4))
|
|
233
233
|
|
|
234
234
|
VStack(alignment: .leading, spacing: 4) {
|
|
235
235
|
Text("No Providers Active")
|
|
236
|
-
.font(.system(size: 14, weight: .semibold))
|
|
236
|
+
.font(RunicFont.system(size: 14, weight: .semibold))
|
|
237
237
|
.foregroundColor(.white.opacity(0.7))
|
|
238
238
|
|
|
239
239
|
Text("Open Runic to enable providers")
|
|
240
|
-
.font(.system(size: 11))
|
|
240
|
+
.font(RunicFont.system(size: 11))
|
|
241
241
|
.foregroundColor(.white.opacity(0.5))
|
|
242
242
|
}
|
|
243
243
|
|
|
@@ -142,11 +142,11 @@ struct UsageProgressRing: View {
|
|
|
142
142
|
// Center content
|
|
143
143
|
VStack(spacing: size * 0.05) {
|
|
144
144
|
Text("\(Int(provider.remainingPercent))%")
|
|
145
|
-
.font(.system(size: size * 0.25, weight: .bold
|
|
145
|
+
.font(RunicFont.system(size: size * 0.25, weight: .bold))
|
|
146
146
|
.foregroundColor(.white)
|
|
147
147
|
|
|
148
148
|
Text("left")
|
|
149
|
-
.font(.system(size: size * 0.12, weight: .medium))
|
|
149
|
+
.font(RunicFont.system(size: size * 0.12, weight: .medium))
|
|
150
150
|
.foregroundColor(.white.opacity(0.7))
|
|
151
151
|
}
|
|
152
152
|
}
|
|
@@ -184,7 +184,7 @@ struct ProviderHeader: View {
|
|
|
184
184
|
.frame(width: iconSize, height: iconSize)
|
|
185
185
|
|
|
186
186
|
Text(provider.displayName)
|
|
187
|
-
.font(.system(size: iconSize * 0.8, weight: .semibold))
|
|
187
|
+
.font(RunicFont.system(size: iconSize * 0.8, weight: .semibold))
|
|
188
188
|
.foregroundColor(.white)
|
|
189
189
|
|
|
190
190
|
Spacer()
|
|
@@ -201,13 +201,13 @@ struct CompactUsageBar: View {
|
|
|
201
201
|
VStack(alignment: .leading, spacing: 4) {
|
|
202
202
|
HStack {
|
|
203
203
|
Text(provider.displayName)
|
|
204
|
-
.font(.system(size: 12, weight: .medium))
|
|
204
|
+
.font(RunicFont.system(size: 12, weight: .medium))
|
|
205
205
|
.foregroundColor(.white)
|
|
206
206
|
|
|
207
207
|
Spacer()
|
|
208
208
|
|
|
209
209
|
Text("\(Int(provider.remainingPercent))%")
|
|
210
|
-
.font(.system(size: 12, weight: .bold
|
|
210
|
+
.font(RunicFont.system(size: 12, weight: .bold))
|
|
211
211
|
.foregroundColor(.white)
|
|
212
212
|
}
|
|
213
213
|
|
|
@@ -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
|
}
|
package/Scripts/package_app.sh
CHANGED
|
@@ -218,9 +218,9 @@ cat > "$APP/Contents/Info.plist" <<PLIST
|
|
|
218
218
|
<key>LSMinimumSystemVersion</key><string>14.0</string>
|
|
219
219
|
<key>LSUIElement</key><true/>
|
|
220
220
|
<key>CFBundleIconFile</key><string>Icon</string>
|
|
221
|
-
<key>NSHumanReadableCopyright</key><string>© 2026 Srinivas Pendela
|
|
221
|
+
<key>NSHumanReadableCopyright</key><string>© 2025-2026 Srinivas Pendela. MIT License.</string>
|
|
222
222
|
<key>SUFeedURL</key><string>${FEED_URL}</string>
|
|
223
|
-
<key>SUPublicEDKey</key><string>
|
|
223
|
+
<key>SUPublicEDKey</key><string>1Ay3r9lgXQtyOjzVSCjlT5dvQuRlJghNKa8Goqvhfz4=</string>
|
|
224
224
|
<key>SUEnableAutomaticChecks</key><${AUTO_CHECKS}/>
|
|
225
225
|
<key>CodexBuildTimestamp</key><string>${BUILD_TIMESTAMP}</string>
|
|
226
226
|
<key>CodexGitCommit</key><string>${GIT_COMMIT}</string>
|
|
@@ -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
|
}
|
|
@@ -6,7 +6,16 @@ extension StatusItemController {
|
|
|
6
6
|
// MARK: - Actions reachable from menus
|
|
7
7
|
|
|
8
8
|
@objc func refreshNow() {
|
|
9
|
-
|
|
9
|
+
let provider = self.selectedMenuProvider
|
|
10
|
+
if let provider {
|
|
11
|
+
// Single provider selected — only refresh that one.
|
|
12
|
+
Task {
|
|
13
|
+
await self.store.refreshSingleProvider(provider)
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
// Overview — refresh all.
|
|
17
|
+
Task { await self.store.refresh(trigger: .manual, forceTokenUsage: true) }
|
|
18
|
+
}
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
@objc func installUpdate() {
|
|
@@ -149,9 +158,13 @@ extension StatusItemController {
|
|
|
149
158
|
}
|
|
150
159
|
}
|
|
151
160
|
|
|
152
|
-
@objc func showSettingsGeneral() {
|
|
161
|
+
@objc func showSettingsGeneral() {
|
|
162
|
+
self.openSettings(tab: .general)
|
|
163
|
+
}
|
|
153
164
|
|
|
154
|
-
@objc func showSettingsAbout() {
|
|
165
|
+
@objc func showSettingsAbout() {
|
|
166
|
+
self.openSettings(tab: .about)
|
|
167
|
+
}
|
|
155
168
|
|
|
156
169
|
func openMenuFromShortcut() {
|
|
157
170
|
if self.shouldMergeIcons {
|
|
@@ -593,7 +606,7 @@ extension StatusItemController {
|
|
|
593
606
|
self.presentLoginAlert(title: info.title, message: info.message)
|
|
594
607
|
}
|
|
595
608
|
|
|
596
|
-
struct LoginAlertInfo: Equatable
|
|
609
|
+
struct LoginAlertInfo: Equatable {
|
|
597
610
|
let title: String
|
|
598
611
|
let message: String
|
|
599
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])
|