@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,24 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
// ProjectsCommand.swift
|
|
2
|
+
// Runic CLI - Project Usage Tracking
|
|
3
|
+
//
|
|
4
|
+
// Lists projects and displays usage statistics for specific projects,
|
|
5
|
+
// helping you understand token consumption across different codebases.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// runic projects [--list | --stats <project-id>] [--json] [--pretty] [--no-color]
|
|
9
|
+
//
|
|
10
|
+
// Examples:
|
|
11
|
+
// runic projects --list # List all projects
|
|
12
|
+
// runic projects --stats my-project # Show stats for specific project
|
|
13
|
+
// runic projects --json --pretty # JSON output of all projects
|
|
14
14
|
|
|
15
|
-
import RunicCore
|
|
16
|
-
import Helix
|
|
17
15
|
import Foundation
|
|
16
|
+
import Helix
|
|
17
|
+
import RunicCore
|
|
18
18
|
|
|
19
19
|
/// Main entry point for the projects command
|
|
20
20
|
public enum ProjectsCommand {
|
|
21
|
-
|
|
22
21
|
/// Command signature defining available options and flags
|
|
23
22
|
public static var signature: CommandSignature {
|
|
24
23
|
CommandSignature(
|
|
@@ -97,7 +96,7 @@ public enum ProjectsCommand {
|
|
|
97
96
|
|
|
98
97
|
/// Execute the projects command
|
|
99
98
|
public static func run(_ invocation: CommandInvocation) async {
|
|
100
|
-
let config = parseConfiguration(invocation)
|
|
99
|
+
let config = self.parseConfiguration(invocation)
|
|
101
100
|
|
|
102
101
|
do {
|
|
103
102
|
let entries = try await loadUsageEntries(
|
|
@@ -105,16 +104,16 @@ public enum ProjectsCommand {
|
|
|
105
104
|
maxAgeDays: config.days)
|
|
106
105
|
|
|
107
106
|
guard !entries.isEmpty else {
|
|
108
|
-
exitWithError("No project usage data found for the specified criteria.")
|
|
107
|
+
self.exitWithError("No project usage data found for the specified criteria.")
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
if let projectID = config.statsProjectID {
|
|
112
|
-
await showProjectStats(projectID, entries: entries, config: config)
|
|
111
|
+
await self.showProjectStats(projectID, entries: entries, config: config)
|
|
113
112
|
} else {
|
|
114
|
-
await showProjectList(entries: entries, config: config)
|
|
113
|
+
await self.showProjectList(entries: entries, config: config)
|
|
115
114
|
}
|
|
116
115
|
} catch {
|
|
117
|
-
exitWithError("Failed to load usage data: \(error.localizedDescription)")
|
|
116
|
+
self.exitWithError("Failed to load usage data: \(error.localizedDescription)")
|
|
118
117
|
}
|
|
119
118
|
}
|
|
120
119
|
|
|
@@ -143,7 +142,7 @@ public enum ProjectsCommand {
|
|
|
143
142
|
let sortByName = invocation.parsedValues.flags.contains("sortByName")
|
|
144
143
|
|
|
145
144
|
let days = daysArg.flatMap(Int.init) ?? 30
|
|
146
|
-
let providers = resolveProviders(providerArg)
|
|
145
|
+
let providers = self.resolveProviders(providerArg)
|
|
147
146
|
|
|
148
147
|
return Configuration(
|
|
149
148
|
providers: providers,
|
|
@@ -165,7 +164,7 @@ public enum ProjectsCommand {
|
|
|
165
164
|
}
|
|
166
165
|
|
|
167
166
|
guard let provider = UsageProvider(rawValue: arg) else {
|
|
168
|
-
exitWithError("Unknown provider: \(arg)")
|
|
167
|
+
self.exitWithError("Unknown provider: \(arg)")
|
|
169
168
|
}
|
|
170
169
|
|
|
171
170
|
return [provider]
|
|
@@ -175,8 +174,8 @@ public enum ProjectsCommand {
|
|
|
175
174
|
|
|
176
175
|
private static func loadUsageEntries(
|
|
177
176
|
providers: [UsageProvider],
|
|
178
|
-
maxAgeDays: Int
|
|
179
|
-
|
|
177
|
+
maxAgeDays: Int) async throws -> [UsageLedgerEntry]
|
|
178
|
+
{
|
|
180
179
|
let now = Date()
|
|
181
180
|
var allEntries: [UsageLedgerEntry] = []
|
|
182
181
|
|
|
@@ -196,33 +195,33 @@ public enum ProjectsCommand {
|
|
|
196
195
|
|
|
197
196
|
private static func showProjectList(
|
|
198
197
|
entries: [UsageLedgerEntry],
|
|
199
|
-
config: Configuration
|
|
200
|
-
|
|
198
|
+
config: Configuration) async
|
|
199
|
+
{
|
|
201
200
|
var summaries = UsageLedgerAggregator.projectSummaries(entries: entries)
|
|
202
201
|
|
|
203
202
|
if config.sortByName {
|
|
204
|
-
summaries.sort {
|
|
203
|
+
summaries.sort { lhs, rhs in
|
|
205
204
|
(lhs.projectID ?? "unknown") < (rhs.projectID ?? "unknown")
|
|
206
205
|
}
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
if config.isJSON {
|
|
210
|
-
outputJSON(summaries, pretty: config.isPretty)
|
|
209
|
+
self.outputJSON(summaries, pretty: config.isPretty)
|
|
211
210
|
} else {
|
|
212
|
-
outputProjectListText(summaries, useColor: config.useColor)
|
|
211
|
+
self.outputProjectListText(summaries, useColor: config.useColor)
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
214
|
|
|
216
215
|
private static func outputProjectListText(
|
|
217
216
|
_ summaries: [UsageLedgerProjectSummary],
|
|
218
|
-
useColor: Bool
|
|
219
|
-
|
|
217
|
+
useColor: Bool)
|
|
218
|
+
{
|
|
220
219
|
if summaries.isEmpty {
|
|
221
220
|
print("No projects found.")
|
|
222
221
|
return
|
|
223
222
|
}
|
|
224
223
|
|
|
225
|
-
printHeader("Projects (\(summaries.count) total)", useColor: useColor)
|
|
224
|
+
self.printHeader("Projects (\(summaries.count) total)", useColor: useColor)
|
|
226
225
|
print("")
|
|
227
226
|
|
|
228
227
|
// Calculate column widths
|
|
@@ -230,10 +229,15 @@ public enum ProjectsCommand {
|
|
|
230
229
|
let projectWidth = min(max(maxProjectWidth, 20), 50)
|
|
231
230
|
|
|
232
231
|
// Print table header
|
|
233
|
-
let header = String(
|
|
234
|
-
|
|
232
|
+
let header = String(
|
|
233
|
+
format: "%-\(projectWidth)s %-12s %-15s %-12s %-10s",
|
|
234
|
+
"PROJECT ID",
|
|
235
|
+
"PROVIDER",
|
|
236
|
+
"TOKENS",
|
|
237
|
+
"REQUESTS",
|
|
238
|
+
"COST")
|
|
235
239
|
if useColor {
|
|
236
|
-
print(ansi("1;37", header))
|
|
240
|
+
print(self.ansi("1;37", header))
|
|
237
241
|
print(String(repeating: "-", count: projectWidth + 55))
|
|
238
242
|
} else {
|
|
239
243
|
print(header)
|
|
@@ -245,17 +249,22 @@ public enum ProjectsCommand {
|
|
|
245
249
|
var totalCost = 0.0
|
|
246
250
|
|
|
247
251
|
for summary in summaries {
|
|
248
|
-
let projectID = truncate(summary.projectID ?? "unknown", maxWidth: projectWidth)
|
|
252
|
+
let projectID = self.truncate(summary.projectID ?? "unknown", maxWidth: projectWidth)
|
|
249
253
|
let provider = summary.provider.rawValue
|
|
250
|
-
let tokens = formatNumber(summary.totals.totalTokens)
|
|
254
|
+
let tokens = self.formatNumber(summary.totals.totalTokens)
|
|
251
255
|
let requests = "\(summary.entryCount)"
|
|
252
|
-
let cost = formatCost(summary.totals.costUSD)
|
|
256
|
+
let cost = self.formatCost(summary.totals.costUSD)
|
|
253
257
|
|
|
254
|
-
let line = String(
|
|
255
|
-
|
|
258
|
+
let line = String(
|
|
259
|
+
format: "%-\(projectWidth)s %-12s %15s %12s %10s",
|
|
260
|
+
projectID,
|
|
261
|
+
provider,
|
|
262
|
+
tokens,
|
|
263
|
+
requests,
|
|
264
|
+
cost)
|
|
256
265
|
|
|
257
266
|
if useColor {
|
|
258
|
-
print(colorizeByUsage(line, tokens: summary.totals.totalTokens))
|
|
267
|
+
print(self.colorizeByUsage(line, tokens: summary.totals.totalTokens))
|
|
259
268
|
} else {
|
|
260
269
|
print(line)
|
|
261
270
|
}
|
|
@@ -269,12 +278,16 @@ public enum ProjectsCommand {
|
|
|
269
278
|
|
|
270
279
|
// Print totals
|
|
271
280
|
print(String(repeating: "-", count: projectWidth + 55))
|
|
272
|
-
let totalLine = String(
|
|
273
|
-
|
|
274
|
-
|
|
281
|
+
let totalLine = String(
|
|
282
|
+
format: "%-\(projectWidth)s %-12s %15s %12s %10s",
|
|
283
|
+
"TOTAL",
|
|
284
|
+
"",
|
|
285
|
+
formatNumber(totalTokens),
|
|
286
|
+
"\(totalRequests)",
|
|
287
|
+
formatCost(totalCost > 0 ? totalCost : nil))
|
|
275
288
|
|
|
276
289
|
if useColor {
|
|
277
|
-
print(ansi("1;36", totalLine))
|
|
290
|
+
print(self.ansi("1;36", totalLine))
|
|
278
291
|
} else {
|
|
279
292
|
print(totalLine)
|
|
280
293
|
}
|
|
@@ -285,12 +298,12 @@ public enum ProjectsCommand {
|
|
|
285
298
|
private static func showProjectStats(
|
|
286
299
|
_ projectID: String,
|
|
287
300
|
entries: [UsageLedgerEntry],
|
|
288
|
-
config: Configuration
|
|
289
|
-
|
|
301
|
+
config: Configuration) async
|
|
302
|
+
{
|
|
290
303
|
let projectEntries = entries.filter { $0.projectID == projectID }
|
|
291
304
|
|
|
292
305
|
guard !projectEntries.isEmpty else {
|
|
293
|
-
exitWithError("No usage data found for project: \(projectID)")
|
|
306
|
+
self.exitWithError("No usage data found for project: \(projectID)")
|
|
294
307
|
}
|
|
295
308
|
|
|
296
309
|
let summaries = UsageLedgerAggregator.projectSummaries(entries: projectEntries)
|
|
@@ -307,9 +320,9 @@ public enum ProjectsCommand {
|
|
|
307
320
|
dailySummaries: dailySummaries)
|
|
308
321
|
|
|
309
322
|
if config.isJSON {
|
|
310
|
-
outputJSON(stats, pretty: config.isPretty)
|
|
323
|
+
self.outputJSON(stats, pretty: config.isPretty)
|
|
311
324
|
} else {
|
|
312
|
-
outputProjectStatsText(stats, useColor: config.useColor)
|
|
325
|
+
self.outputProjectStatsText(stats, useColor: config.useColor)
|
|
313
326
|
}
|
|
314
327
|
}
|
|
315
328
|
|
|
@@ -321,7 +334,7 @@ public enum ProjectsCommand {
|
|
|
321
334
|
}
|
|
322
335
|
|
|
323
336
|
private static func outputProjectStatsText(_ stats: ProjectStats, useColor: Bool) {
|
|
324
|
-
printHeader("Project: \(stats.projectID)", useColor: useColor)
|
|
337
|
+
self.printHeader("Project: \(stats.projectID)", useColor: useColor)
|
|
325
338
|
print("")
|
|
326
339
|
|
|
327
340
|
// Overall stats
|
|
@@ -330,17 +343,17 @@ public enum ProjectsCommand {
|
|
|
330
343
|
let totalCost = stats.summaries.reduce(0.0) { $0 + ($1.totals.costUSD ?? 0) }
|
|
331
344
|
|
|
332
345
|
print("Overall Statistics:")
|
|
333
|
-
print(" Total Tokens: \(formatNumber(totalTokens))")
|
|
346
|
+
print(" Total Tokens: \(self.formatNumber(totalTokens))")
|
|
334
347
|
print(" Total Requests: \(totalRequests)")
|
|
335
|
-
print(" Total Cost: \(formatCost(totalCost > 0 ? totalCost : nil))")
|
|
348
|
+
print(" Total Cost: \(self.formatCost(totalCost > 0 ? totalCost : nil))")
|
|
336
349
|
print("")
|
|
337
350
|
|
|
338
351
|
// Models used
|
|
339
352
|
if !stats.modelSummaries.isEmpty {
|
|
340
353
|
print("Models Used:")
|
|
341
354
|
for model in stats.modelSummaries.prefix(10) {
|
|
342
|
-
let tokens = formatNumber(model.totals.totalTokens)
|
|
343
|
-
let cost = formatCost(model.totals.costUSD)
|
|
355
|
+
let tokens = self.formatNumber(model.totals.totalTokens)
|
|
356
|
+
let cost = self.formatCost(model.totals.costUSD)
|
|
344
357
|
print(" \(model.model): \(tokens) tokens, \(cost)")
|
|
345
358
|
}
|
|
346
359
|
if stats.modelSummaries.count > 10 {
|
|
@@ -353,8 +366,8 @@ public enum ProjectsCommand {
|
|
|
353
366
|
if !stats.dailySummaries.isEmpty {
|
|
354
367
|
print("Recent Activity (last 10 days):")
|
|
355
368
|
for daily in stats.dailySummaries.suffix(10) {
|
|
356
|
-
let tokens = formatNumber(daily.totals.totalTokens)
|
|
357
|
-
let cost = formatCost(daily.totals.costUSD)
|
|
369
|
+
let tokens = self.formatNumber(daily.totals.totalTokens)
|
|
370
|
+
let cost = self.formatCost(daily.totals.costUSD)
|
|
358
371
|
print(" \(daily.dayKey): \(tokens) tokens, \(cost)")
|
|
359
372
|
}
|
|
360
373
|
print("")
|
|
@@ -363,7 +376,7 @@ public enum ProjectsCommand {
|
|
|
363
376
|
|
|
364
377
|
// MARK: - Output Formatting
|
|
365
378
|
|
|
366
|
-
private static func outputJSON
|
|
379
|
+
private static func outputJSON(_ data: some Encodable, pretty: Bool) {
|
|
367
380
|
let encoder = JSONEncoder()
|
|
368
381
|
encoder.dateEncodingStrategy = .iso8601
|
|
369
382
|
|
|
@@ -372,7 +385,8 @@ public enum ProjectsCommand {
|
|
|
372
385
|
}
|
|
373
386
|
|
|
374
387
|
guard let jsonData = try? encoder.encode(data),
|
|
375
|
-
let text = String(data: jsonData, encoding: .utf8)
|
|
388
|
+
let text = String(data: jsonData, encoding: .utf8)
|
|
389
|
+
else {
|
|
376
390
|
print("{}")
|
|
377
391
|
return
|
|
378
392
|
}
|
|
@@ -385,14 +399,14 @@ public enum ProjectsCommand {
|
|
|
385
399
|
private static func formatNumber(_ n: Int) -> String {
|
|
386
400
|
if n >= 1_000_000 {
|
|
387
401
|
return String(format: "%.1fM", Double(n) / 1_000_000)
|
|
388
|
-
} else if n >=
|
|
389
|
-
return String(format: "%.1fK", Double(n) /
|
|
402
|
+
} else if n >= 1000 {
|
|
403
|
+
return String(format: "%.1fK", Double(n) / 1000)
|
|
390
404
|
}
|
|
391
405
|
return "\(n)"
|
|
392
406
|
}
|
|
393
407
|
|
|
394
408
|
private static func formatCost(_ cost: Double?) -> String {
|
|
395
|
-
guard let cost
|
|
409
|
+
guard let cost, cost > 0 else {
|
|
396
410
|
return "n/a"
|
|
397
411
|
}
|
|
398
412
|
return String(format: "$%.4f", cost)
|
|
@@ -408,7 +422,7 @@ public enum ProjectsCommand {
|
|
|
408
422
|
|
|
409
423
|
private static func printHeader(_ text: String, useColor: Bool) {
|
|
410
424
|
if useColor {
|
|
411
|
-
print(ansi("1;36", text))
|
|
425
|
+
print(self.ansi("1;36", text))
|
|
412
426
|
} else {
|
|
413
427
|
print(text)
|
|
414
428
|
}
|
|
@@ -416,12 +430,12 @@ public enum ProjectsCommand {
|
|
|
416
430
|
|
|
417
431
|
private static func colorizeByUsage(_ text: String, tokens: Int) -> String {
|
|
418
432
|
let code = switch tokens {
|
|
419
|
-
case 1_000_000...: "35"
|
|
420
|
-
case 500_000...: "33"
|
|
421
|
-
case 100_000...: "32"
|
|
422
|
-
default: "0"
|
|
433
|
+
case 1_000_000...: "35" // magenta - very high
|
|
434
|
+
case 500_000...: "33" // yellow - high
|
|
435
|
+
case 100_000...: "32" // green - medium
|
|
436
|
+
default: "0" // default
|
|
423
437
|
}
|
|
424
|
-
return ansi(code, text)
|
|
438
|
+
return self.ansi(code, text)
|
|
425
439
|
}
|
|
426
440
|
|
|
427
441
|
private static func ansi(_ code: String, _ text: String) -> String {
|
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
// ResetCommand.swift
|
|
2
|
+
// Runic CLI - Usage Reset Timing Information
|
|
3
|
+
//
|
|
4
|
+
// Displays when usage limits reset for different providers,
|
|
5
|
+
// helping you plan usage and understand billing cycles.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// runic reset [--when <provider>] [--all] [--json] [--pretty] [--no-color]
|
|
9
|
+
//
|
|
10
|
+
// Examples:
|
|
11
|
+
// runic reset # Show reset times for all providers
|
|
12
|
+
// runic reset --when claude # Show Claude reset timings only
|
|
13
|
+
// runic reset --all --json # JSON output for all providers
|
|
14
14
|
|
|
15
|
-
import RunicCore
|
|
16
|
-
import Helix
|
|
17
15
|
import Foundation
|
|
16
|
+
import Helix
|
|
17
|
+
import RunicCore
|
|
18
18
|
|
|
19
19
|
/// Main entry point for the reset command
|
|
20
20
|
public enum ResetCommand {
|
|
21
|
-
|
|
22
21
|
/// Command signature defining available options and flags
|
|
23
22
|
public static var signature: CommandSignature {
|
|
24
23
|
CommandSignature(
|
|
@@ -86,22 +85,22 @@ public enum ResetCommand {
|
|
|
86
85
|
|
|
87
86
|
/// Execute the reset command
|
|
88
87
|
public static func run(_ invocation: CommandInvocation) async {
|
|
89
|
-
let config = parseConfiguration(invocation)
|
|
88
|
+
let config = self.parseConfiguration(invocation)
|
|
90
89
|
|
|
91
90
|
do {
|
|
92
91
|
let resetInfos = try await loadResetInformation(providers: config.providers)
|
|
93
92
|
|
|
94
93
|
if resetInfos.isEmpty {
|
|
95
|
-
exitWithError("No reset information available for the specified providers.")
|
|
94
|
+
self.exitWithError("No reset information available for the specified providers.")
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
if config.isJSON {
|
|
99
|
-
outputJSON(resetInfos, pretty: config.isPretty)
|
|
98
|
+
self.outputJSON(resetInfos, pretty: config.isPretty)
|
|
100
99
|
} else {
|
|
101
|
-
outputText(resetInfos, config: config)
|
|
100
|
+
self.outputText(resetInfos, config: config)
|
|
102
101
|
}
|
|
103
102
|
} catch {
|
|
104
|
-
exitWithError("Failed to load reset information: \(error.localizedDescription)")
|
|
103
|
+
self.exitWithError("Failed to load reset information: \(error.localizedDescription)")
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
|
|
@@ -125,7 +124,7 @@ public enum ResetCommand {
|
|
|
125
124
|
let noColor = invocation.parsedValues.flags.contains("noColor")
|
|
126
125
|
let compact = invocation.parsedValues.flags.contains("compact")
|
|
127
126
|
|
|
128
|
-
let providers = resolveProviders(whenArg)
|
|
127
|
+
let providers = self.resolveProviders(whenArg)
|
|
129
128
|
|
|
130
129
|
return Configuration(
|
|
131
130
|
providers: providers,
|
|
@@ -145,7 +144,7 @@ public enum ResetCommand {
|
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
guard let provider = UsageProvider(rawValue: arg) else {
|
|
148
|
-
exitWithError("Unknown provider: \(arg)")
|
|
147
|
+
self.exitWithError("Unknown provider: \(arg)")
|
|
149
148
|
}
|
|
150
149
|
|
|
151
150
|
return [provider]
|
|
@@ -173,8 +172,8 @@ public enum ResetCommand {
|
|
|
173
172
|
// MARK: - Data Loading
|
|
174
173
|
|
|
175
174
|
private static func loadResetInformation(
|
|
176
|
-
providers: [UsageProvider]
|
|
177
|
-
|
|
175
|
+
providers: [UsageProvider]) async throws -> [ProviderResetInfo]
|
|
176
|
+
{
|
|
178
177
|
let fetcher = UsageFetcher()
|
|
179
178
|
var results: [ProviderResetInfo] = []
|
|
180
179
|
|
|
@@ -194,8 +193,8 @@ public enum ResetCommand {
|
|
|
194
193
|
|
|
195
194
|
let outcome = await descriptor.fetchOutcome(context: context)
|
|
196
195
|
|
|
197
|
-
if case .success(
|
|
198
|
-
let info = buildResetInfo(
|
|
196
|
+
if case let .success(result) = outcome.result {
|
|
197
|
+
let info = self.buildResetInfo(
|
|
199
198
|
provider: provider,
|
|
200
199
|
snapshot: result.usage,
|
|
201
200
|
metadata: descriptor.metadata)
|
|
@@ -209,30 +208,30 @@ public enum ResetCommand {
|
|
|
209
208
|
private static func buildResetInfo(
|
|
210
209
|
provider: UsageProvider,
|
|
211
210
|
snapshot: UsageSnapshot,
|
|
212
|
-
metadata: ProviderMetadata
|
|
213
|
-
|
|
211
|
+
metadata: ProviderMetadata) -> ProviderResetInfo
|
|
212
|
+
{
|
|
214
213
|
var windows: [WindowResetInfo] = []
|
|
215
214
|
|
|
216
215
|
// Primary window
|
|
217
|
-
windows.append(buildWindowInfo(
|
|
216
|
+
windows.append(self.buildWindowInfo(
|
|
218
217
|
windowName: metadata.sessionLabel,
|
|
219
218
|
window: snapshot.primary))
|
|
220
219
|
|
|
221
220
|
// Secondary window
|
|
222
221
|
if let secondary = snapshot.secondary {
|
|
223
|
-
windows.append(buildWindowInfo(
|
|
222
|
+
windows.append(self.buildWindowInfo(
|
|
224
223
|
windowName: metadata.weeklyLabel,
|
|
225
224
|
window: secondary))
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
// Tertiary window (Opus)
|
|
229
228
|
if metadata.supportsOpus, let tertiary = snapshot.tertiary {
|
|
230
|
-
windows.append(buildWindowInfo(
|
|
229
|
+
windows.append(self.buildWindowInfo(
|
|
231
230
|
windowName: metadata.opusLabel ?? "Opus",
|
|
232
231
|
window: tertiary))
|
|
233
232
|
}
|
|
234
233
|
|
|
235
|
-
let status = determineStatus(windows: windows)
|
|
234
|
+
let status = self.determineStatus(windows: windows)
|
|
236
235
|
|
|
237
236
|
return ProviderResetInfo(
|
|
238
237
|
provider: provider,
|
|
@@ -243,9 +242,9 @@ public enum ResetCommand {
|
|
|
243
242
|
|
|
244
243
|
private static func buildWindowInfo(
|
|
245
244
|
windowName: String,
|
|
246
|
-
window: RateWindow
|
|
247
|
-
|
|
248
|
-
let resetAt = extractResetDate(from: window.resetDescription)
|
|
245
|
+
window: RateWindow) -> WindowResetInfo
|
|
246
|
+
{
|
|
247
|
+
let resetAt = self.extractResetDate(from: window.resetDescription)
|
|
249
248
|
let timeUntilReset = resetAt?.timeIntervalSinceNow
|
|
250
249
|
|
|
251
250
|
return WindowResetInfo(
|
|
@@ -261,14 +260,15 @@ public enum ResetCommand {
|
|
|
261
260
|
private static func extractResetDate(from description: String?) -> Date? {
|
|
262
261
|
// This is a simplified implementation
|
|
263
262
|
// Real implementation would parse the reset description
|
|
264
|
-
guard let description
|
|
263
|
+
guard let description else { return nil }
|
|
265
264
|
|
|
266
265
|
// Look for patterns like "in Xh Ym"
|
|
267
266
|
let pattern = #"in (\d+)h(?: (\d+)m)?"#
|
|
268
267
|
guard let regex = try? NSRegularExpression(pattern: pattern),
|
|
269
268
|
let match = regex.firstMatch(
|
|
270
269
|
in: description,
|
|
271
|
-
range: NSRange(description.startIndex..., in: description))
|
|
270
|
+
range: NSRange(description.startIndex..., in: description))
|
|
271
|
+
else {
|
|
272
272
|
return nil
|
|
273
273
|
}
|
|
274
274
|
|
|
@@ -313,7 +313,8 @@ public enum ResetCommand {
|
|
|
313
313
|
}
|
|
314
314
|
|
|
315
315
|
guard let data = try? encoder.encode(infos),
|
|
316
|
-
let text = String(data: data, encoding: .utf8)
|
|
316
|
+
let text = String(data: data, encoding: .utf8)
|
|
317
|
+
else {
|
|
317
318
|
print("[]")
|
|
318
319
|
return
|
|
319
320
|
}
|
|
@@ -323,18 +324,18 @@ public enum ResetCommand {
|
|
|
323
324
|
|
|
324
325
|
private static func outputText(_ infos: [ProviderResetInfo], config: Configuration) {
|
|
325
326
|
if config.compact {
|
|
326
|
-
outputCompactText(infos, useColor: config.useColor)
|
|
327
|
+
self.outputCompactText(infos, useColor: config.useColor)
|
|
327
328
|
} else {
|
|
328
|
-
outputDetailedText(infos, useColor: config.useColor)
|
|
329
|
+
self.outputDetailedText(infos, useColor: config.useColor)
|
|
329
330
|
}
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
private static func outputCompactText(_ infos: [ProviderResetInfo], useColor: Bool) {
|
|
333
|
-
printHeader("Usage Reset Times", useColor: useColor)
|
|
334
|
+
self.printHeader("Usage Reset Times", useColor: useColor)
|
|
334
335
|
print("")
|
|
335
336
|
|
|
336
337
|
for info in infos {
|
|
337
|
-
let status = statusIndicator(info.status, useColor: useColor)
|
|
338
|
+
let status = self.statusIndicator(info.status, useColor: useColor)
|
|
338
339
|
let provider = info.providerName.padding(toLength: 12, withPad: " ", startingAt: 0)
|
|
339
340
|
|
|
340
341
|
var resetTimes: [String] = []
|
|
@@ -350,7 +351,7 @@ public enum ResetCommand {
|
|
|
350
351
|
}
|
|
351
352
|
|
|
352
353
|
private static func outputDetailedText(_ infos: [ProviderResetInfo], useColor: Bool) {
|
|
353
|
-
printHeader("Usage Reset Information", useColor: useColor)
|
|
354
|
+
self.printHeader("Usage Reset Information", useColor: useColor)
|
|
354
355
|
print("")
|
|
355
356
|
|
|
356
357
|
for (index, info) in infos.enumerated() {
|
|
@@ -358,11 +359,11 @@ public enum ResetCommand {
|
|
|
358
359
|
print("")
|
|
359
360
|
}
|
|
360
361
|
|
|
361
|
-
let status = statusIndicator(info.status, useColor: useColor)
|
|
362
|
+
let status = self.statusIndicator(info.status, useColor: useColor)
|
|
362
363
|
let providerLine = "\(status) \(info.providerName) - \(info.status)"
|
|
363
364
|
|
|
364
365
|
if useColor {
|
|
365
|
-
print(ansi("1;37", providerLine))
|
|
366
|
+
print(self.ansi("1;37", providerLine))
|
|
366
367
|
} else {
|
|
367
368
|
print(providerLine)
|
|
368
369
|
}
|
|
@@ -382,7 +383,7 @@ public enum ResetCommand {
|
|
|
382
383
|
if window.isNearLimit {
|
|
383
384
|
let warning = "⚠ High usage - consider monitoring closely"
|
|
384
385
|
if useColor {
|
|
385
|
-
print(" \(ansi("33", warning))")
|
|
386
|
+
print(" \(self.ansi("33", warning))")
|
|
386
387
|
} else {
|
|
387
388
|
print(" \(warning)")
|
|
388
389
|
}
|
|
@@ -403,12 +404,12 @@ public enum ResetCommand {
|
|
|
403
404
|
("✓", "32")
|
|
404
405
|
}
|
|
405
406
|
|
|
406
|
-
return useColor ? ansi(color, icon) : icon
|
|
407
|
+
return useColor ? self.ansi(color, icon) : icon
|
|
407
408
|
}
|
|
408
409
|
|
|
409
410
|
private static func printHeader(_ text: String, useColor: Bool) {
|
|
410
411
|
if useColor {
|
|
411
|
-
print(ansi("1;36", text))
|
|
412
|
+
print(self.ansi("1;36", text))
|
|
412
413
|
} else {
|
|
413
414
|
print(text)
|
|
414
415
|
}
|