@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,27 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
// EnhancedUsageCommand.swift
|
|
2
|
+
// Runic CLI - Enhanced Usage Display
|
|
3
|
+
//
|
|
4
|
+
// Enhanced version of the usage command with additional features:
|
|
5
|
+
// - Multiple display modes (summary, detailed, breakdown)
|
|
6
|
+
// - Historical trending
|
|
7
|
+
// - Cost projections
|
|
8
|
+
// - Comparative analysis across providers
|
|
9
|
+
//
|
|
10
|
+
// Usage:
|
|
11
|
+
// runic usage-enhanced [options]
|
|
12
|
+
//
|
|
13
|
+
// Examples:
|
|
14
|
+
// runic usage-enhanced --mode summary # Quick overview
|
|
15
|
+
// runic usage-enhanced --mode detailed # Detailed breakdown
|
|
16
|
+
// runic usage-enhanced --mode trending # Usage trends
|
|
17
17
|
|
|
18
|
-
import RunicCore
|
|
19
|
-
import Helix
|
|
20
18
|
import Foundation
|
|
19
|
+
import Helix
|
|
20
|
+
import RunicCore
|
|
21
21
|
|
|
22
22
|
/// Enhanced usage command with additional analytics
|
|
23
23
|
public enum EnhancedUsageCommand {
|
|
24
|
-
|
|
25
24
|
/// Command signature defining available options and flags
|
|
26
25
|
public static var signature: CommandSignature {
|
|
27
26
|
CommandSignature(
|
|
@@ -106,7 +105,7 @@ public enum EnhancedUsageCommand {
|
|
|
106
105
|
|
|
107
106
|
/// Execute the enhanced usage command
|
|
108
107
|
public static func run(_ invocation: CommandInvocation) async {
|
|
109
|
-
let config = parseConfiguration(invocation)
|
|
108
|
+
let config = self.parseConfiguration(invocation)
|
|
110
109
|
|
|
111
110
|
do {
|
|
112
111
|
let data = try await loadUsageData(
|
|
@@ -115,12 +114,12 @@ public enum EnhancedUsageCommand {
|
|
|
115
114
|
includeCost: config.showCost)
|
|
116
115
|
|
|
117
116
|
if config.isJSON {
|
|
118
|
-
outputJSON(data, config: config)
|
|
117
|
+
self.outputJSON(data, config: config)
|
|
119
118
|
} else {
|
|
120
|
-
outputText(data, config: config)
|
|
119
|
+
self.outputText(data, config: config)
|
|
121
120
|
}
|
|
122
121
|
} catch {
|
|
123
|
-
exitWithError("Failed to load usage data: \(error.localizedDescription)")
|
|
122
|
+
self.exitWithError("Failed to load usage data: \(error.localizedDescription)")
|
|
124
123
|
}
|
|
125
124
|
}
|
|
126
125
|
|
|
@@ -161,7 +160,7 @@ public enum EnhancedUsageCommand {
|
|
|
161
160
|
|
|
162
161
|
let mode = Configuration.DisplayMode(rawValue: modeArg.lowercased()) ?? .summary
|
|
163
162
|
let days = daysArg.flatMap(Int.init) ?? 7
|
|
164
|
-
let providers = resolveProviders(providerArg)
|
|
163
|
+
let providers = self.resolveProviders(providerArg)
|
|
165
164
|
|
|
166
165
|
return Configuration(
|
|
167
166
|
providers: providers,
|
|
@@ -185,7 +184,7 @@ public enum EnhancedUsageCommand {
|
|
|
185
184
|
}
|
|
186
185
|
|
|
187
186
|
guard let provider = UsageProvider(rawValue: arg) else {
|
|
188
|
-
exitWithError("Unknown provider: \(arg)")
|
|
187
|
+
self.exitWithError("Unknown provider: \(arg)")
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
return [provider]
|
|
@@ -269,8 +268,8 @@ public enum EnhancedUsageCommand {
|
|
|
269
268
|
private static func loadUsageData(
|
|
270
269
|
providers: [UsageProvider],
|
|
271
270
|
days: Int,
|
|
272
|
-
includeCost: Bool
|
|
273
|
-
|
|
271
|
+
includeCost: Bool) async throws -> EnhancedUsageData
|
|
272
|
+
{
|
|
274
273
|
let fetcher = UsageFetcher()
|
|
275
274
|
let _ = includeCost ? CostUsageFetcher() : nil
|
|
276
275
|
var providerData: [ProviderUsageData] = []
|
|
@@ -291,12 +290,12 @@ public enum EnhancedUsageCommand {
|
|
|
291
290
|
|
|
292
291
|
let outcome = await descriptor.fetchOutcome(context: context)
|
|
293
292
|
|
|
294
|
-
if case .success(
|
|
293
|
+
if case let .success(result) = outcome.result {
|
|
295
294
|
let trending = try? await loadTrendingData(
|
|
296
295
|
provider: provider,
|
|
297
296
|
days: days)
|
|
298
297
|
|
|
299
|
-
let data = buildProviderData(
|
|
298
|
+
let data = self.buildProviderData(
|
|
300
299
|
provider: provider,
|
|
301
300
|
snapshot: result.usage,
|
|
302
301
|
credits: result.credits,
|
|
@@ -307,8 +306,8 @@ public enum EnhancedUsageCommand {
|
|
|
307
306
|
}
|
|
308
307
|
}
|
|
309
308
|
|
|
310
|
-
let summary = buildSummary(providerData: providerData)
|
|
311
|
-
let comparison = buildComparison(providerData: providerData)
|
|
309
|
+
let summary = self.buildSummary(providerData: providerData)
|
|
310
|
+
let comparison = self.buildComparison(providerData: providerData)
|
|
312
311
|
|
|
313
312
|
return EnhancedUsageData(
|
|
314
313
|
providers: providerData,
|
|
@@ -321,8 +320,8 @@ public enum EnhancedUsageCommand {
|
|
|
321
320
|
snapshot: UsageSnapshot,
|
|
322
321
|
credits: CreditsSnapshot?,
|
|
323
322
|
metadata: ProviderMetadata,
|
|
324
|
-
trending: [DailyTrend]?
|
|
325
|
-
|
|
323
|
+
trending: [DailyTrend]?) -> ProviderUsageData
|
|
324
|
+
{
|
|
326
325
|
var windows: [WindowData] = []
|
|
327
326
|
|
|
328
327
|
windows.append(WindowData(
|
|
@@ -356,8 +355,8 @@ public enum EnhancedUsageCommand {
|
|
|
356
355
|
|
|
357
356
|
private static func loadTrendingData(
|
|
358
357
|
provider: UsageProvider,
|
|
359
|
-
days: Int
|
|
360
|
-
|
|
358
|
+
days: Int) async throws -> [DailyTrend]?
|
|
359
|
+
{
|
|
361
360
|
let source = UsageLedgerSourceFactory.source(for: provider, now: Date(), maxAgeDays: days)
|
|
362
361
|
|
|
363
362
|
guard let source else { return nil }
|
|
@@ -377,8 +376,8 @@ public enum EnhancedUsageCommand {
|
|
|
377
376
|
}
|
|
378
377
|
|
|
379
378
|
private static func buildSummary(
|
|
380
|
-
providerData: [ProviderUsageData]
|
|
381
|
-
|
|
379
|
+
providerData: [ProviderUsageData]) -> UsageSummary?
|
|
380
|
+
{
|
|
382
381
|
guard !providerData.isEmpty else { return nil }
|
|
383
382
|
|
|
384
383
|
let totalTokens = providerData.reduce(0) { sum, data in
|
|
@@ -405,8 +404,8 @@ public enum EnhancedUsageCommand {
|
|
|
405
404
|
}
|
|
406
405
|
|
|
407
406
|
private static func buildComparison(
|
|
408
|
-
providerData: [ProviderUsageData]
|
|
409
|
-
|
|
407
|
+
providerData: [ProviderUsageData]) -> ProviderComparison?
|
|
408
|
+
{
|
|
410
409
|
guard providerData.count > 1 else { return nil }
|
|
411
410
|
|
|
412
411
|
let comparisons = providerData.map { data in
|
|
@@ -439,7 +438,8 @@ public enum EnhancedUsageCommand {
|
|
|
439
438
|
}
|
|
440
439
|
|
|
441
440
|
guard let jsonData = try? encoder.encode(data),
|
|
442
|
-
let text = String(data: jsonData, encoding: .utf8)
|
|
441
|
+
let text = String(data: jsonData, encoding: .utf8)
|
|
442
|
+
else {
|
|
443
443
|
print("{}")
|
|
444
444
|
return
|
|
445
445
|
}
|
|
@@ -450,23 +450,23 @@ public enum EnhancedUsageCommand {
|
|
|
450
450
|
private static func outputText(_ data: EnhancedUsageData, config: Configuration) {
|
|
451
451
|
switch config.mode {
|
|
452
452
|
case .summary:
|
|
453
|
-
outputSummaryMode(data, config: config)
|
|
453
|
+
self.outputSummaryMode(data, config: config)
|
|
454
454
|
case .detailed:
|
|
455
|
-
outputDetailedMode(data, config: config)
|
|
455
|
+
self.outputDetailedMode(data, config: config)
|
|
456
456
|
case .breakdown:
|
|
457
|
-
outputBreakdownMode(data, config: config)
|
|
457
|
+
self.outputBreakdownMode(data, config: config)
|
|
458
458
|
case .trending:
|
|
459
|
-
outputTrendingMode(data, config: config)
|
|
459
|
+
self.outputTrendingMode(data, config: config)
|
|
460
460
|
}
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
private static func outputSummaryMode(_ data: EnhancedUsageData, config: Configuration) {
|
|
464
|
-
printHeader("Usage Summary", useColor: config.useColor)
|
|
464
|
+
self.printHeader("Usage Summary", useColor: config.useColor)
|
|
465
465
|
print("")
|
|
466
466
|
|
|
467
467
|
if let summary = data.summary {
|
|
468
468
|
print("Providers: \(summary.totalProviders)")
|
|
469
|
-
print("Total Tokens: \(formatNumber(summary.totalTokens))")
|
|
469
|
+
print("Total Tokens: \(self.formatNumber(summary.totalTokens))")
|
|
470
470
|
print("Average Usage: \(String(format: "%.1f", summary.averageUsagePercent))%")
|
|
471
471
|
print("Highest Usage: \(summary.highestUsageProvider)")
|
|
472
472
|
print("")
|
|
@@ -475,7 +475,7 @@ public enum EnhancedUsageCommand {
|
|
|
475
475
|
for providerData in data.providers {
|
|
476
476
|
print(providerData.providerName)
|
|
477
477
|
for window in providerData.windows {
|
|
478
|
-
let bar = progressBar(
|
|
478
|
+
let bar = self.progressBar(
|
|
479
479
|
used: window.usedPercent,
|
|
480
480
|
width: 20,
|
|
481
481
|
useColor: config.useColor)
|
|
@@ -487,14 +487,14 @@ public enum EnhancedUsageCommand {
|
|
|
487
487
|
}
|
|
488
488
|
|
|
489
489
|
private static func outputDetailedMode(_ data: EnhancedUsageData, config: Configuration) {
|
|
490
|
-
printHeader("Detailed Usage Information", useColor: config.useColor)
|
|
490
|
+
self.printHeader("Detailed Usage Information", useColor: config.useColor)
|
|
491
491
|
print("")
|
|
492
492
|
|
|
493
493
|
for (index, providerData) in data.providers.enumerated() {
|
|
494
494
|
if index > 0 { print("") }
|
|
495
495
|
|
|
496
496
|
if config.useColor {
|
|
497
|
-
print(ansi("1;36", providerData.providerName))
|
|
497
|
+
print(self.ansi("1;36", providerData.providerName))
|
|
498
498
|
} else {
|
|
499
499
|
print(providerData.providerName)
|
|
500
500
|
}
|
|
@@ -503,7 +503,7 @@ public enum EnhancedUsageCommand {
|
|
|
503
503
|
print("")
|
|
504
504
|
print(" \(window.name):")
|
|
505
505
|
if let used = window.used, let limit = window.limit {
|
|
506
|
-
print(" Used: \(formatNumber(used)) / \(formatNumber(limit))")
|
|
506
|
+
print(" Used: \(self.formatNumber(used)) / \(self.formatNumber(limit))")
|
|
507
507
|
}
|
|
508
508
|
print(" Percent: \(String(format: "%.1f", window.usedPercent))%")
|
|
509
509
|
if let reset = window.resetDescription {
|
|
@@ -514,14 +514,14 @@ public enum EnhancedUsageCommand {
|
|
|
514
514
|
}
|
|
515
515
|
|
|
516
516
|
private static func outputBreakdownMode(_ data: EnhancedUsageData, config: Configuration) {
|
|
517
|
-
printHeader("Token Breakdown", useColor: config.useColor)
|
|
517
|
+
self.printHeader("Token Breakdown", useColor: config.useColor)
|
|
518
518
|
print("")
|
|
519
519
|
print("Note: Detailed token breakdown requires historical log data.")
|
|
520
520
|
print("Use 'runic insights --view models' for model-level breakdown.")
|
|
521
521
|
}
|
|
522
522
|
|
|
523
523
|
private static func outputTrendingMode(_ data: EnhancedUsageData, config: Configuration) {
|
|
524
|
-
printHeader("Usage Trends", useColor: config.useColor)
|
|
524
|
+
self.printHeader("Usage Trends", useColor: config.useColor)
|
|
525
525
|
print("")
|
|
526
526
|
|
|
527
527
|
for providerData in data.providers {
|
|
@@ -529,7 +529,7 @@ public enum EnhancedUsageCommand {
|
|
|
529
529
|
|
|
530
530
|
if let trends = providerData.trending, !trends.isEmpty {
|
|
531
531
|
for trend in trends.suffix(10) {
|
|
532
|
-
let tokens = formatNumber(trend.totalTokens)
|
|
532
|
+
let tokens = self.formatNumber(trend.totalTokens)
|
|
533
533
|
let cost = trend.cost.map { String(format: "$%.4f", $0) } ?? "n/a"
|
|
534
534
|
print(" \(trend.date): \(tokens) tokens (\(cost))")
|
|
535
535
|
}
|
|
@@ -548,26 +548,26 @@ public enum EnhancedUsageCommand {
|
|
|
548
548
|
guard useColor else { return "[\(bar)]" }
|
|
549
549
|
|
|
550
550
|
let color = switch used {
|
|
551
|
-
case 90...: "31"
|
|
552
|
-
case 75...: "33"
|
|
553
|
-
default: "32"
|
|
551
|
+
case 90...: "31" // red
|
|
552
|
+
case 75...: "33" // yellow
|
|
553
|
+
default: "32" // green
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
-
return "[\(ansi(color, bar))]"
|
|
556
|
+
return "[\(self.ansi(color, bar))]"
|
|
557
557
|
}
|
|
558
558
|
|
|
559
559
|
private static func formatNumber(_ n: Int) -> String {
|
|
560
560
|
if n >= 1_000_000 {
|
|
561
561
|
return String(format: "%.1fM", Double(n) / 1_000_000)
|
|
562
|
-
} else if n >=
|
|
563
|
-
return String(format: "%.1fK", Double(n) /
|
|
562
|
+
} else if n >= 1000 {
|
|
563
|
+
return String(format: "%.1fK", Double(n) / 1000)
|
|
564
564
|
}
|
|
565
565
|
return "\(n)"
|
|
566
566
|
}
|
|
567
567
|
|
|
568
568
|
private static func printHeader(_ text: String, useColor: Bool) {
|
|
569
569
|
if useColor {
|
|
570
|
-
print(ansi("1;36", text))
|
|
570
|
+
print(self.ansi("1;36", text))
|
|
571
571
|
} else {
|
|
572
572
|
print(text)
|
|
573
573
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
// ModelsCommand.swift
|
|
2
|
+
// Runic CLI - Model Usage Breakdown
|
|
3
|
+
//
|
|
4
|
+
// Displays which AI models were used across different providers,
|
|
5
|
+
// showing token consumption and cost breakdown by model type.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// runic models [--provider <name>] [--days <n>] [--json] [--pretty] [--no-color]
|
|
9
|
+
//
|
|
10
|
+
// Examples:
|
|
11
|
+
// runic models # Show all models used
|
|
12
|
+
// runic models --provider claude # Claude models only
|
|
13
|
+
// runic models --days 7 # Last 7 days
|
|
14
|
+
// runic models --json --pretty # JSON output
|
|
15
15
|
|
|
16
|
-
import RunicCore
|
|
17
|
-
import Helix
|
|
18
16
|
import Foundation
|
|
17
|
+
import Helix
|
|
18
|
+
import RunicCore
|
|
19
19
|
|
|
20
20
|
/// Main entry point for the models command
|
|
21
21
|
public enum ModelsCommand {
|
|
@@ -112,7 +112,7 @@ public enum ModelsCommand {
|
|
|
112
112
|
|
|
113
113
|
/// Execute the models command
|
|
114
114
|
public static func run(_ invocation: CommandInvocation) async {
|
|
115
|
-
let config = parseConfiguration(invocation)
|
|
115
|
+
let config = self.parseConfiguration(invocation)
|
|
116
116
|
|
|
117
117
|
do {
|
|
118
118
|
let entries = try await loadUsageEntries(
|
|
@@ -120,7 +120,7 @@ public enum ModelsCommand {
|
|
|
120
120
|
maxAgeDays: config.days)
|
|
121
121
|
|
|
122
122
|
guard !entries.isEmpty else {
|
|
123
|
-
exitWithError("No model usage data found for the specified criteria.")
|
|
123
|
+
self.exitWithError("No model usage data found for the specified criteria.")
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
let summaries = UsageLedgerAggregator.modelSummaries(
|
|
@@ -128,12 +128,12 @@ public enum ModelsCommand {
|
|
|
128
128
|
groupByProject: config.groupByProject)
|
|
129
129
|
|
|
130
130
|
if config.isJSON {
|
|
131
|
-
outputJSON(summaries, pretty: config.isPretty)
|
|
131
|
+
self.outputJSON(summaries, pretty: config.isPretty)
|
|
132
132
|
} else {
|
|
133
|
-
outputText(summaries, useColor: config.useColor)
|
|
133
|
+
self.outputText(summaries, useColor: config.useColor)
|
|
134
134
|
}
|
|
135
135
|
} catch {
|
|
136
|
-
exitWithError("Failed to load usage data: \(error.localizedDescription)")
|
|
136
|
+
self.exitWithError("Failed to load usage data: \(error.localizedDescription)")
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -160,7 +160,7 @@ public enum ModelsCommand {
|
|
|
160
160
|
let groupByProject = invocation.parsedValues.flags.contains("groupByProject")
|
|
161
161
|
|
|
162
162
|
let days = daysArg.flatMap(Int.init) ?? 30
|
|
163
|
-
let providers = resolveProviders(providerArg)
|
|
163
|
+
let providers = self.resolveProviders(providerArg)
|
|
164
164
|
|
|
165
165
|
return Configuration(
|
|
166
166
|
providers: providers,
|
|
@@ -181,7 +181,7 @@ public enum ModelsCommand {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
guard let provider = UsageProvider(rawValue: arg) else {
|
|
184
|
-
exitWithError("Unknown provider: \(arg)")
|
|
184
|
+
self.exitWithError("Unknown provider: \(arg)")
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
return [provider]
|
|
@@ -191,8 +191,8 @@ public enum ModelsCommand {
|
|
|
191
191
|
|
|
192
192
|
private static func loadUsageEntries(
|
|
193
193
|
providers: [UsageProvider],
|
|
194
|
-
maxAgeDays: Int
|
|
195
|
-
|
|
194
|
+
maxAgeDays: Int) async throws -> [UsageLedgerEntry]
|
|
195
|
+
{
|
|
196
196
|
let now = Date()
|
|
197
197
|
var allEntries: [UsageLedgerEntry] = []
|
|
198
198
|
|
|
@@ -221,7 +221,8 @@ public enum ModelsCommand {
|
|
|
221
221
|
let enriched = summaries.map(EnrichedModelSummary.init)
|
|
222
222
|
|
|
223
223
|
guard let data = try? encoder.encode(enriched),
|
|
224
|
-
let text = String(data: data, encoding: .utf8)
|
|
224
|
+
let text = String(data: data, encoding: .utf8)
|
|
225
|
+
else {
|
|
225
226
|
print("[]")
|
|
226
227
|
return
|
|
227
228
|
}
|
|
@@ -235,11 +236,11 @@ public enum ModelsCommand {
|
|
|
235
236
|
return
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
printHeader("Model Usage Breakdown", useColor: useColor)
|
|
239
|
+
self.printHeader("Model Usage Breakdown", useColor: useColor)
|
|
239
240
|
print("")
|
|
240
241
|
|
|
241
242
|
// Calculate column widths
|
|
242
|
-
let maxModelWidth = summaries.map
|
|
243
|
+
let maxModelWidth = summaries.map(\.model.count).max() ?? 20
|
|
243
244
|
let modelWidth = min(max(maxModelWidth, 15), 40)
|
|
244
245
|
let contextWidth = 11
|
|
245
246
|
|
|
@@ -253,7 +254,7 @@ public enum ModelsCommand {
|
|
|
253
254
|
"COST",
|
|
254
255
|
"CONTEXT")
|
|
255
256
|
if useColor {
|
|
256
|
-
print(ansi("1;37", header))
|
|
257
|
+
print(self.ansi("1;37", header))
|
|
257
258
|
print(String(repeating: "-", count: modelWidth + 58))
|
|
258
259
|
} else {
|
|
259
260
|
print(header)
|
|
@@ -265,11 +266,11 @@ public enum ModelsCommand {
|
|
|
265
266
|
var totalCost = 0.0
|
|
266
267
|
|
|
267
268
|
for summary in summaries {
|
|
268
|
-
let model = truncate(summary.model, maxWidth: modelWidth)
|
|
269
|
+
let model = self.truncate(summary.model, maxWidth: modelWidth)
|
|
269
270
|
let provider = summary.provider.rawValue
|
|
270
|
-
let tokens = formatNumber(summary.totals.totalTokens)
|
|
271
|
+
let tokens = self.formatNumber(summary.totals.totalTokens)
|
|
271
272
|
let requests = "\(summary.entryCount)"
|
|
272
|
-
let cost = formatCost(summary.totals.costUSD)
|
|
273
|
+
let cost = self.formatCost(summary.totals.costUSD)
|
|
273
274
|
let context = UsageFormatter.modelContextLabel(for: summary.model) ?? "n/a"
|
|
274
275
|
|
|
275
276
|
let line = String(
|
|
@@ -277,7 +278,7 @@ public enum ModelsCommand {
|
|
|
277
278
|
model, provider, tokens, requests, cost, context)
|
|
278
279
|
|
|
279
280
|
if useColor {
|
|
280
|
-
print(colorizeByUsage(line, tokens: summary.totals.totalTokens))
|
|
281
|
+
print(self.colorizeByUsage(line, tokens: summary.totals.totalTokens))
|
|
281
282
|
} else {
|
|
282
283
|
print(line)
|
|
283
284
|
}
|
|
@@ -294,10 +295,10 @@ public enum ModelsCommand {
|
|
|
294
295
|
let totalLine = String(
|
|
295
296
|
format: "%-\(modelWidth)s %-12s %15s %12s %10s %-\(contextWidth)s",
|
|
296
297
|
"TOTAL", "", formatNumber(totalTokens), "\(totalRequests)",
|
|
297
|
-
|
|
298
|
+
formatCost(totalCost > 0 ? totalCost : nil), " ")
|
|
298
299
|
|
|
299
300
|
if useColor {
|
|
300
|
-
print(ansi("1;36", totalLine))
|
|
301
|
+
print(self.ansi("1;36", totalLine))
|
|
301
302
|
} else {
|
|
302
303
|
print(totalLine)
|
|
303
304
|
}
|
|
@@ -308,14 +309,14 @@ public enum ModelsCommand {
|
|
|
308
309
|
private static func formatNumber(_ n: Int) -> String {
|
|
309
310
|
if n >= 1_000_000 {
|
|
310
311
|
return String(format: "%.1fM", Double(n) / 1_000_000)
|
|
311
|
-
} else if n >=
|
|
312
|
-
return String(format: "%.1fK", Double(n) /
|
|
312
|
+
} else if n >= 1000 {
|
|
313
|
+
return String(format: "%.1fK", Double(n) / 1000)
|
|
313
314
|
}
|
|
314
315
|
return "\(n)"
|
|
315
316
|
}
|
|
316
317
|
|
|
317
318
|
private static func formatCost(_ cost: Double?) -> String {
|
|
318
|
-
guard let cost
|
|
319
|
+
guard let cost, cost > 0 else {
|
|
319
320
|
return "n/a"
|
|
320
321
|
}
|
|
321
322
|
return String(format: "$%.4f", cost)
|
|
@@ -331,7 +332,7 @@ public enum ModelsCommand {
|
|
|
331
332
|
|
|
332
333
|
private static func printHeader(_ text: String, useColor: Bool) {
|
|
333
334
|
if useColor {
|
|
334
|
-
print(ansi("1;36", text))
|
|
335
|
+
print(self.ansi("1;36", text))
|
|
335
336
|
} else {
|
|
336
337
|
print(text)
|
|
337
338
|
}
|
|
@@ -339,12 +340,12 @@ public enum ModelsCommand {
|
|
|
339
340
|
|
|
340
341
|
private static func colorizeByUsage(_ text: String, tokens: Int) -> String {
|
|
341
342
|
let code = switch tokens {
|
|
342
|
-
case 1_000_000...: "35"
|
|
343
|
-
case 500_000...: "33"
|
|
344
|
-
case 100_000...: "32"
|
|
345
|
-
default: "0"
|
|
343
|
+
case 1_000_000...: "35" // magenta - very high
|
|
344
|
+
case 500_000...: "33" // yellow - high
|
|
345
|
+
case 100_000...: "32" // green - medium
|
|
346
|
+
default: "0" // default
|
|
346
347
|
}
|
|
347
|
-
return ansi(code, text)
|
|
348
|
+
return self.ansi(code, text)
|
|
348
349
|
}
|
|
349
350
|
|
|
350
351
|
private static func ansi(_ code: String, _ text: String) -> String {
|