@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
|
@@ -18,7 +18,7 @@ struct TeamManagementView: View {
|
|
|
18
18
|
@State private var editingMember: TeamMember?
|
|
19
19
|
@State private var editingMemberTeamID: String?
|
|
20
20
|
@State private var memberQuotaHasLimit = false
|
|
21
|
-
@State private var memberQuotaLimit =
|
|
21
|
+
@State private var memberQuotaLimit = 10000
|
|
22
22
|
@State private var changingRoleMember: TeamMember?
|
|
23
23
|
@State private var changingRoleTeamID: String?
|
|
24
24
|
@State private var memberRoleSelection: TeamRole = .member
|
|
@@ -30,7 +30,7 @@ struct TeamManagementView: View {
|
|
|
30
30
|
PreferencesPane {
|
|
31
31
|
SettingsSection(title: "Teams", contentSpacing: RunicSpacing.md) {
|
|
32
32
|
Text("Manage team workspaces, members, and quota allocation.")
|
|
33
|
-
.font(.footnote)
|
|
33
|
+
.font(RunicFont.footnote)
|
|
34
34
|
.foregroundStyle(.tertiary)
|
|
35
35
|
.fixedSize(horizontal: false, vertical: true)
|
|
36
36
|
}
|
|
@@ -41,7 +41,7 @@ struct TeamManagementView: View {
|
|
|
41
41
|
VStack(alignment: .leading, spacing: RunicSpacing.sm) {
|
|
42
42
|
HStack {
|
|
43
43
|
Text("Your Teams")
|
|
44
|
-
.font(.subheadline.weight(.semibold))
|
|
44
|
+
.font(RunicFont.subheadline.weight(.semibold))
|
|
45
45
|
Spacer()
|
|
46
46
|
Button {
|
|
47
47
|
self.showingCreateTeam = true
|
|
@@ -58,10 +58,10 @@ struct TeamManagementView: View {
|
|
|
58
58
|
.font(.system(size: 48))
|
|
59
59
|
.foregroundStyle(.tertiary)
|
|
60
60
|
Text("No teams yet")
|
|
61
|
-
.font(.body.weight(.semibold))
|
|
61
|
+
.font(RunicFont.body.weight(.semibold))
|
|
62
62
|
.foregroundStyle(.secondary)
|
|
63
63
|
Text("Create a team to collaborate with others")
|
|
64
|
-
.font(.footnote)
|
|
64
|
+
.font(RunicFont.footnote)
|
|
65
65
|
.foregroundStyle(.tertiary)
|
|
66
66
|
}
|
|
67
67
|
.frame(maxWidth: .infinity)
|
|
@@ -103,7 +103,7 @@ struct TeamManagementView: View {
|
|
|
103
103
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
104
104
|
HStack {
|
|
105
105
|
Text("Members")
|
|
106
|
-
.font(.footnote.weight(.semibold))
|
|
106
|
+
.font(RunicFont.footnote.weight(.semibold))
|
|
107
107
|
.foregroundStyle(.secondary)
|
|
108
108
|
Spacer()
|
|
109
109
|
Button {
|
|
@@ -127,24 +127,24 @@ struct TeamManagementView: View {
|
|
|
127
127
|
|
|
128
128
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
129
129
|
Text("Team Usage")
|
|
130
|
-
.font(.footnote.weight(.semibold))
|
|
130
|
+
.font(RunicFont.footnote.weight(.semibold))
|
|
131
131
|
.foregroundStyle(.secondary)
|
|
132
132
|
|
|
133
133
|
HStack {
|
|
134
134
|
Text("Total quota")
|
|
135
|
-
.font(.footnote)
|
|
135
|
+
.font(RunicFont.footnote)
|
|
136
136
|
Spacer()
|
|
137
137
|
Text("\(team.totalQuota, format: .number) credits")
|
|
138
|
-
.font(.footnote.weight(.semibold))
|
|
138
|
+
.font(RunicFont.footnote.weight(.semibold))
|
|
139
139
|
.foregroundStyle(.secondary)
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
HStack {
|
|
143
143
|
Text("Used")
|
|
144
|
-
.font(.footnote)
|
|
144
|
+
.font(RunicFont.footnote)
|
|
145
145
|
Spacer()
|
|
146
146
|
Text("\(team.usedQuota, format: .number) credits")
|
|
147
|
-
.font(.footnote.weight(.semibold))
|
|
147
|
+
.font(RunicFont.footnote.weight(.semibold))
|
|
148
148
|
.foregroundStyle(.secondary)
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -170,6 +170,7 @@ struct TeamManagementView: View {
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
+
.runicTypography()
|
|
173
174
|
.sheet(isPresented: self.$showingCreateTeam) {
|
|
174
175
|
CreateTeamSheet(
|
|
175
176
|
teamName: self.$newTeamName,
|
|
@@ -323,7 +324,7 @@ struct TeamManagementView: View {
|
|
|
323
324
|
self.editingMember = member
|
|
324
325
|
self.editingMemberTeamID = team.id
|
|
325
326
|
self.memberQuotaHasLimit = member.quotaLimit != nil
|
|
326
|
-
self.memberQuotaLimit = member.quotaLimit ??
|
|
327
|
+
self.memberQuotaLimit = member.quotaLimit ?? 10000
|
|
327
328
|
self.showingQuotaSheet = true
|
|
328
329
|
}
|
|
329
330
|
|
|
@@ -366,7 +367,7 @@ struct TeamManagementView: View {
|
|
|
366
367
|
var updated = self.teams[teamIndex]
|
|
367
368
|
guard let memberIndex = updated.members.firstIndex(where: { $0.id == memberID }) else { return }
|
|
368
369
|
var member = updated.members[memberIndex]
|
|
369
|
-
member.quotaLimit = hasLimit ? max(
|
|
370
|
+
member.quotaLimit = hasLimit ? max(1000, limit) : nil
|
|
370
371
|
updated.members[memberIndex] = member
|
|
371
372
|
self.teams[teamIndex] = updated
|
|
372
373
|
self.selectedTeam = updated
|
|
@@ -431,14 +432,15 @@ private struct TeamRowView: View {
|
|
|
431
432
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
432
433
|
HStack(spacing: RunicSpacing.xs) {
|
|
433
434
|
Image(systemName: self.team.role.icon)
|
|
434
|
-
.font(.body)
|
|
435
|
+
.font(RunicFont.body)
|
|
435
436
|
.foregroundStyle(self.team.role.color)
|
|
436
437
|
Text(self.team.name)
|
|
437
|
-
.font(.body.weight(.semibold))
|
|
438
|
+
.font(RunicFont.body.weight(.semibold))
|
|
438
439
|
}
|
|
439
440
|
|
|
440
|
-
Text(
|
|
441
|
-
.
|
|
441
|
+
Text(
|
|
442
|
+
"\(self.team.members.count) member\(self.team.members.count == 1 ? "" : "s") · \(self.team.usedQuota, format: .number) / \(self.team.totalQuota, format: .number) credits")
|
|
443
|
+
.font(RunicFont.caption)
|
|
442
444
|
.foregroundStyle(.tertiary)
|
|
443
445
|
}
|
|
444
446
|
|
|
@@ -499,11 +501,11 @@ private struct CreateTeamSheet: View {
|
|
|
499
501
|
var body: some View {
|
|
500
502
|
VStack(alignment: .leading, spacing: RunicSpacing.lg) {
|
|
501
503
|
Text("Create Team")
|
|
502
|
-
.font(.title2.weight(.semibold))
|
|
504
|
+
.font(RunicFont.title2.weight(.semibold))
|
|
503
505
|
|
|
504
506
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
505
507
|
Text("Team Name")
|
|
506
|
-
.font(.subheadline.weight(.medium))
|
|
508
|
+
.font(RunicFont.subheadline.weight(.medium))
|
|
507
509
|
TextField("Enter team name", text: self.$teamName)
|
|
508
510
|
.textFieldStyle(.roundedBorder)
|
|
509
511
|
.focused(self.$isNameFieldFocused)
|
|
@@ -543,11 +545,11 @@ private struct EditTeamSheet: View {
|
|
|
543
545
|
var body: some View {
|
|
544
546
|
VStack(alignment: .leading, spacing: RunicSpacing.lg) {
|
|
545
547
|
Text("Edit Team")
|
|
546
|
-
.font(.title2.weight(.semibold))
|
|
548
|
+
.font(RunicFont.title2.weight(.semibold))
|
|
547
549
|
|
|
548
550
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
549
551
|
Text("Team Name")
|
|
550
|
-
.font(.subheadline.weight(.medium))
|
|
552
|
+
.font(RunicFont.subheadline.weight(.medium))
|
|
551
553
|
TextField("Enter team name", text: self.$teamName)
|
|
552
554
|
.textFieldStyle(.roundedBorder)
|
|
553
555
|
.focused(self.$isNameFieldFocused)
|
|
@@ -588,34 +590,34 @@ private struct MemberQuotaSheet: View {
|
|
|
588
590
|
VStack(alignment: .leading, spacing: RunicSpacing.lg) {
|
|
589
591
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
590
592
|
Text("Edit Quota")
|
|
591
|
-
.font(.title2.weight(.semibold))
|
|
593
|
+
.font(RunicFont.title2.weight(.semibold))
|
|
592
594
|
Text("Set a monthly quota for \(self.memberName)")
|
|
593
|
-
.font(.footnote)
|
|
595
|
+
.font(RunicFont.footnote)
|
|
594
596
|
.foregroundStyle(.tertiary)
|
|
595
597
|
}
|
|
596
598
|
|
|
597
599
|
PreferencesDivider()
|
|
598
600
|
|
|
599
601
|
Toggle("Enable quota limit", isOn: self.$hasLimit)
|
|
600
|
-
.font(.subheadline.weight(.medium))
|
|
602
|
+
.font(RunicFont.subheadline.weight(.medium))
|
|
601
603
|
|
|
602
604
|
if self.hasLimit {
|
|
603
605
|
HStack(spacing: RunicSpacing.sm) {
|
|
604
606
|
Text("Credits")
|
|
605
|
-
.font(.footnote)
|
|
607
|
+
.font(RunicFont.footnote)
|
|
606
608
|
.foregroundStyle(.secondary)
|
|
607
|
-
Stepper(value: self.$quotaLimit, in:
|
|
609
|
+
Stepper(value: self.$quotaLimit, in: 1000...1_000_000, step: 1000) {
|
|
608
610
|
TextField("", value: self.$quotaLimit, format: .number)
|
|
609
611
|
.textFieldStyle(.roundedBorder)
|
|
610
612
|
.frame(width: 140)
|
|
611
613
|
}
|
|
612
614
|
}
|
|
613
615
|
Text("Monthly credit allocation for this member")
|
|
614
|
-
.font(.caption)
|
|
616
|
+
.font(RunicFont.caption)
|
|
615
617
|
.foregroundStyle(.tertiary)
|
|
616
618
|
} else {
|
|
617
619
|
Text("No limit — uses shared team quota")
|
|
618
|
-
.font(.caption)
|
|
620
|
+
.font(RunicFont.caption)
|
|
619
621
|
.foregroundStyle(.tertiary)
|
|
620
622
|
}
|
|
621
623
|
|
|
@@ -646,9 +648,9 @@ private struct MemberRoleSheet: View {
|
|
|
646
648
|
VStack(alignment: .leading, spacing: RunicSpacing.lg) {
|
|
647
649
|
VStack(alignment: .leading, spacing: RunicSpacing.xs) {
|
|
648
650
|
Text("Change Role")
|
|
649
|
-
.font(.title2.weight(.semibold))
|
|
651
|
+
.font(RunicFont.title2.weight(.semibold))
|
|
650
652
|
Text("Update permissions for \(self.memberName)")
|
|
651
|
-
.font(.footnote)
|
|
653
|
+
.font(RunicFont.footnote)
|
|
652
654
|
.foregroundStyle(.tertiary)
|
|
653
655
|
}
|
|
654
656
|
|
|
@@ -702,7 +704,6 @@ struct Team: Identifiable, Hashable, Codable {
|
|
|
702
704
|
if percent >= 75 { return Color(nsColor: .systemOrange) }
|
|
703
705
|
return Color(nsColor: .systemBlue)
|
|
704
706
|
}
|
|
705
|
-
|
|
706
707
|
}
|
|
707
708
|
|
|
708
709
|
enum TeamRole: String, CaseIterable, Codable {
|
|
@@ -713,49 +714,49 @@ enum TeamRole: String, CaseIterable, Codable {
|
|
|
713
714
|
|
|
714
715
|
var displayName: String {
|
|
715
716
|
switch self {
|
|
716
|
-
case .owner:
|
|
717
|
-
case .admin:
|
|
718
|
-
case .member:
|
|
719
|
-
case .viewer:
|
|
717
|
+
case .owner: "Owner"
|
|
718
|
+
case .admin: "Admin"
|
|
719
|
+
case .member: "Member"
|
|
720
|
+
case .viewer: "Viewer"
|
|
720
721
|
}
|
|
721
722
|
}
|
|
722
723
|
|
|
723
724
|
var icon: String {
|
|
724
725
|
switch self {
|
|
725
|
-
case .owner:
|
|
726
|
-
case .admin:
|
|
727
|
-
case .member:
|
|
728
|
-
case .viewer:
|
|
726
|
+
case .owner: "crown.fill"
|
|
727
|
+
case .admin: "star.fill"
|
|
728
|
+
case .member: "person.fill"
|
|
729
|
+
case .viewer: "eye.fill"
|
|
729
730
|
}
|
|
730
731
|
}
|
|
731
732
|
|
|
732
733
|
var color: Color {
|
|
733
734
|
switch self {
|
|
734
|
-
case .owner:
|
|
735
|
-
case .admin:
|
|
736
|
-
case .member:
|
|
737
|
-
case .viewer:
|
|
735
|
+
case .owner: Color(nsColor: .systemYellow)
|
|
736
|
+
case .admin: Color(nsColor: .systemBlue)
|
|
737
|
+
case .member: Color(nsColor: .systemGreen)
|
|
738
|
+
case .viewer: Color(nsColor: .systemGray)
|
|
738
739
|
}
|
|
739
740
|
}
|
|
740
741
|
|
|
741
742
|
var canInvite: Bool {
|
|
742
743
|
switch self {
|
|
743
|
-
case .owner, .admin:
|
|
744
|
-
case .member, .viewer:
|
|
744
|
+
case .owner, .admin: true
|
|
745
|
+
case .member, .viewer: false
|
|
745
746
|
}
|
|
746
747
|
}
|
|
747
748
|
|
|
748
749
|
var canEditQuota: Bool {
|
|
749
750
|
switch self {
|
|
750
|
-
case .owner, .admin:
|
|
751
|
-
case .member, .viewer:
|
|
751
|
+
case .owner, .admin: true
|
|
752
|
+
case .member, .viewer: false
|
|
752
753
|
}
|
|
753
754
|
}
|
|
754
755
|
|
|
755
756
|
var canRemoveMembers: Bool {
|
|
756
757
|
switch self {
|
|
757
|
-
case .owner, .admin:
|
|
758
|
-
case .member, .viewer:
|
|
758
|
+
case .owner, .admin: true
|
|
759
|
+
case .member, .viewer: false
|
|
759
760
|
}
|
|
760
761
|
}
|
|
761
762
|
}
|
|
@@ -17,19 +17,19 @@ struct TeamMemberRow: View {
|
|
|
17
17
|
VStack(alignment: .leading, spacing: RunicSpacing.xxs) {
|
|
18
18
|
HStack(spacing: RunicSpacing.xs) {
|
|
19
19
|
Text(self.member.name)
|
|
20
|
-
.font(.body.weight(.medium))
|
|
20
|
+
.font(RunicFont.body.weight(.medium))
|
|
21
21
|
|
|
22
22
|
self.roleBadge
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
Text(self.member.email)
|
|
26
|
-
.font(.caption)
|
|
26
|
+
.font(RunicFont.caption)
|
|
27
27
|
.foregroundStyle(.tertiary)
|
|
28
28
|
|
|
29
29
|
if let quota = self.member.quotaLimit {
|
|
30
30
|
HStack(spacing: RunicSpacing.xxs) {
|
|
31
31
|
Text("\(self.member.usedQuota, format: .number) / \(quota, format: .number)")
|
|
32
|
-
.font(.caption2)
|
|
32
|
+
.font(RunicFont.caption2)
|
|
33
33
|
.foregroundStyle(.secondary)
|
|
34
34
|
|
|
35
35
|
UsageProgressBar(
|
|
@@ -95,7 +95,7 @@ struct TeamMemberRow: View {
|
|
|
95
95
|
.frame(width: 36, height: 36)
|
|
96
96
|
|
|
97
97
|
Text(self.member.initials)
|
|
98
|
-
.font(.footnote.weight(.semibold))
|
|
98
|
+
.font(RunicFont.footnote.weight(.semibold))
|
|
99
99
|
.foregroundStyle(.white)
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -103,9 +103,9 @@ struct TeamMemberRow: View {
|
|
|
103
103
|
private var roleBadge: some View {
|
|
104
104
|
HStack(spacing: RunicSpacing.xxxs) {
|
|
105
105
|
Image(systemName: self.member.role.icon)
|
|
106
|
-
.font(.caption2)
|
|
106
|
+
.font(RunicFont.caption2)
|
|
107
107
|
Text(self.member.role.displayName)
|
|
108
|
-
.font(.caption2.weight(.medium))
|
|
108
|
+
.font(RunicFont.caption2.weight(.medium))
|
|
109
109
|
}
|
|
110
110
|
.padding(.horizontal, RunicSpacing.xxs + RunicSpacing.xxxs)
|
|
111
111
|
.padding(.vertical, RunicSpacing.xxxs)
|
|
@@ -174,8 +174,8 @@ struct TeamMember: Identifiable, Hashable, Codable {
|
|
|
174
174
|
name: "You",
|
|
175
175
|
email: resolvedEmail,
|
|
176
176
|
role: .owner,
|
|
177
|
-
quotaLimit:
|
|
178
|
-
usedQuota:
|
|
177
|
+
quotaLimit: 50000,
|
|
178
|
+
usedQuota: 12500,
|
|
179
179
|
isCurrentUser: true)
|
|
180
180
|
}
|
|
181
181
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import RunicCore
|
|
2
|
-
import Helix
|
|
3
1
|
import Foundation
|
|
2
|
+
import Helix
|
|
3
|
+
import RunicCore
|
|
4
4
|
|
|
5
5
|
@main
|
|
6
6
|
enum RunicCLI {
|
|
@@ -24,7 +24,10 @@ enum RunicCLI {
|
|
|
24
24
|
OptionDefinition(label: "format", names: [.long("format")], help: "Output format: text | json"),
|
|
25
25
|
],
|
|
26
26
|
flags: [
|
|
27
|
-
FlagDefinition(
|
|
27
|
+
FlagDefinition(
|
|
28
|
+
label: "verbose",
|
|
29
|
+
names: [.short("v"), .long("verbose")],
|
|
30
|
+
help: "Enable verbose logging"),
|
|
28
31
|
FlagDefinition(label: "json", names: [.long("json")], help: "Output JSON format"),
|
|
29
32
|
FlagDefinition(label: "pretty", names: [.long("pretty")], help: "Pretty-print output"),
|
|
30
33
|
FlagDefinition(label: "noColor", names: [.long("no-color")], help: "Disable ANSI colors"),
|
|
@@ -36,7 +39,10 @@ enum RunicCLI {
|
|
|
36
39
|
OptionDefinition(label: "format", names: [.long("format")], help: "Output format: text | json"),
|
|
37
40
|
],
|
|
38
41
|
flags: [
|
|
39
|
-
FlagDefinition(
|
|
42
|
+
FlagDefinition(
|
|
43
|
+
label: "verbose",
|
|
44
|
+
names: [.short("v"), .long("verbose")],
|
|
45
|
+
help: "Enable verbose logging"),
|
|
40
46
|
FlagDefinition(label: "json", names: [.long("json")], help: "Output JSON format"),
|
|
41
47
|
FlagDefinition(label: "pretty", names: [.long("pretty")], help: "Pretty-print output"),
|
|
42
48
|
FlagDefinition(label: "noColor", names: [.long("no-color")], help: "Disable ANSI colors"),
|
|
@@ -45,19 +51,37 @@ enum RunicCLI {
|
|
|
45
51
|
|
|
46
52
|
let insightsSignature = CommandSignature(
|
|
47
53
|
options: [
|
|
48
|
-
OptionDefinition(
|
|
49
|
-
|
|
54
|
+
OptionDefinition(
|
|
55
|
+
label: "provider",
|
|
56
|
+
names: [.long("provider")],
|
|
57
|
+
help: "Provider to analyze (claude, codex, or all)"),
|
|
58
|
+
OptionDefinition(
|
|
59
|
+
label: "view",
|
|
60
|
+
names: [.long("view")],
|
|
61
|
+
help: "View: daily | session | blocks | models | projects | comparative | efficiency"),
|
|
50
62
|
OptionDefinition(label: "project", names: [.long("project")], help: "Filter to a specific project"),
|
|
51
|
-
OptionDefinition(
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
OptionDefinition(
|
|
64
|
+
label: "timezone",
|
|
65
|
+
names: [.long("timezone")],
|
|
66
|
+
help: "Timezone identifier (defaults to local)"),
|
|
67
|
+
OptionDefinition(
|
|
68
|
+
label: "granularity",
|
|
69
|
+
names: [.long("granularity")],
|
|
70
|
+
help: "Time granularity: hourly (for daily view)"),
|
|
71
|
+
OptionDefinition(
|
|
72
|
+
label: "gitDirectory",
|
|
73
|
+
names: [.long("git-directory")],
|
|
74
|
+
help: "Path to .git directory (defaults to current directory)"),
|
|
54
75
|
],
|
|
55
76
|
flags: [
|
|
56
77
|
FlagDefinition(label: "json", names: [.long("json")], help: "Output JSON format"),
|
|
57
78
|
FlagDefinition(label: "pretty", names: [.long("pretty")], help: "Pretty-print output"),
|
|
58
79
|
FlagDefinition(label: "noColor", names: [.long("no-color")], help: "Disable ANSI colors"),
|
|
59
80
|
FlagDefinition(label: "budget", names: [.long("budget")], help: "Include budget tracking information"),
|
|
60
|
-
FlagDefinition(
|
|
81
|
+
FlagDefinition(
|
|
82
|
+
label: "withCommits",
|
|
83
|
+
names: [.long("with-commits")],
|
|
84
|
+
help: "Link usage entries to git commits"),
|
|
61
85
|
])
|
|
62
86
|
|
|
63
87
|
let usageDescriptor = CommandDescriptor(
|
|
@@ -118,7 +142,7 @@ enum RunicCLI {
|
|
|
118
142
|
}
|
|
119
143
|
|
|
120
144
|
let fetcher = UsageFetcher()
|
|
121
|
-
var output
|
|
145
|
+
var output = ""
|
|
122
146
|
|
|
123
147
|
for provider in providers {
|
|
124
148
|
do {
|
|
@@ -155,11 +179,14 @@ enum RunicCLI {
|
|
|
155
179
|
}
|
|
156
180
|
}
|
|
157
181
|
|
|
158
|
-
if isPretty
|
|
182
|
+
if isPretty, isJson {
|
|
159
183
|
if let data = output.data(using: .utf8),
|
|
160
184
|
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
161
|
-
let prettyData = try? JSONSerialization.data(
|
|
162
|
-
|
|
185
|
+
let prettyData = try? JSONSerialization.data(
|
|
186
|
+
withJSONObject: json,
|
|
187
|
+
options: [.prettyPrinted, .sortedKeys]),
|
|
188
|
+
let pretty = String(data: prettyData, encoding: .utf8)
|
|
189
|
+
{
|
|
163
190
|
print(pretty)
|
|
164
191
|
} else {
|
|
165
192
|
print(output)
|
|
@@ -183,7 +210,7 @@ enum RunicCLI {
|
|
|
183
210
|
guard let p = UsageProvider(rawValue: providerName) else {
|
|
184
211
|
Self.exit(code: 1, message: "Unknown provider: \(providerName)")
|
|
185
212
|
}
|
|
186
|
-
if p != .claude
|
|
213
|
+
if p != .claude, p != .codex {
|
|
187
214
|
Self.exit(code: 1, message: "Cost is only supported for claude and codex")
|
|
188
215
|
}
|
|
189
216
|
providers = [p]
|
|
@@ -192,7 +219,7 @@ enum RunicCLI {
|
|
|
192
219
|
}
|
|
193
220
|
|
|
194
221
|
let fetcher = CostUsageFetcher()
|
|
195
|
-
var output
|
|
222
|
+
var output = ""
|
|
196
223
|
|
|
197
224
|
for provider in providers {
|
|
198
225
|
do {
|
|
@@ -207,7 +234,7 @@ enum RunicCLI {
|
|
|
207
234
|
}
|
|
208
235
|
}
|
|
209
236
|
|
|
210
|
-
if isPretty
|
|
237
|
+
if isPretty, isJson {
|
|
211
238
|
print("{\"cost\": \"output available in text format\"}")
|
|
212
239
|
} else {
|
|
213
240
|
print(output)
|
|
@@ -365,7 +392,8 @@ enum RunicCLI {
|
|
|
365
392
|
return summaries.map { summary in
|
|
366
393
|
guard let projectID = summary.projectID,
|
|
367
394
|
let budget = budgetsData.budgets[projectID],
|
|
368
|
-
budget.enabled
|
|
395
|
+
budget.enabled
|
|
396
|
+
else {
|
|
369
397
|
return ProjectBudgetSummary(
|
|
370
398
|
provider: summary.provider,
|
|
371
399
|
projectID: summary.projectID,
|
|
@@ -468,7 +496,11 @@ enum RunicCLI {
|
|
|
468
496
|
.sorted { $0.requestCount > $1.requestCount }
|
|
469
497
|
}
|
|
470
498
|
|
|
471
|
-
private static func renderInsightsWithCommits(
|
|
499
|
+
private static func renderInsightsWithCommits(
|
|
500
|
+
_ entriesWithCommits: [UsageWithCommit],
|
|
501
|
+
isJson: Bool,
|
|
502
|
+
isPretty: Bool)
|
|
503
|
+
{
|
|
472
504
|
if isJson {
|
|
473
505
|
let encoder = JSONEncoder()
|
|
474
506
|
encoder.dateEncodingStrategy = .iso8601
|
|
@@ -504,7 +536,7 @@ enum RunicCLI {
|
|
|
504
536
|
}
|
|
505
537
|
}
|
|
506
538
|
|
|
507
|
-
private static func renderInsightsOutput
|
|
539
|
+
private static func renderInsightsOutput(_ payload: some Encodable, isJson: Bool, isPretty: Bool) {
|
|
508
540
|
if isJson {
|
|
509
541
|
let encoder = JSONEncoder()
|
|
510
542
|
encoder.dateEncodingStrategy = .iso8601
|
|
@@ -525,7 +557,8 @@ enum RunicCLI {
|
|
|
525
557
|
for summary in summaries {
|
|
526
558
|
let project = summary.projectID ?? "all"
|
|
527
559
|
let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
|
|
528
|
-
print(
|
|
560
|
+
print(
|
|
561
|
+
"\(summary.dayKey) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
|
|
529
562
|
}
|
|
530
563
|
return
|
|
531
564
|
}
|
|
@@ -534,7 +567,8 @@ enum RunicCLI {
|
|
|
534
567
|
for summary in summaries {
|
|
535
568
|
let project = summary.projectID ?? "all"
|
|
536
569
|
let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
|
|
537
|
-
print(
|
|
570
|
+
print(
|
|
571
|
+
"\(summary.sessionID) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
|
|
538
572
|
}
|
|
539
573
|
return
|
|
540
574
|
}
|
|
@@ -543,7 +577,8 @@ enum RunicCLI {
|
|
|
543
577
|
for summary in summaries {
|
|
544
578
|
let project = summary.projectID ?? "all"
|
|
545
579
|
let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
|
|
546
|
-
print(
|
|
580
|
+
print(
|
|
581
|
+
"\(summary.start) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
|
|
547
582
|
}
|
|
548
583
|
return
|
|
549
584
|
}
|
|
@@ -552,7 +587,8 @@ enum RunicCLI {
|
|
|
552
587
|
for summary in summaries {
|
|
553
588
|
let project = summary.projectID ?? "all"
|
|
554
589
|
let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
|
|
555
|
-
print(
|
|
590
|
+
print(
|
|
591
|
+
"\(summary.model) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText)")
|
|
556
592
|
}
|
|
557
593
|
return
|
|
558
594
|
}
|
|
@@ -562,7 +598,8 @@ enum RunicCLI {
|
|
|
562
598
|
let project = summary.projectID ?? "unknown"
|
|
563
599
|
let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
|
|
564
600
|
let models = summary.modelsUsed.isEmpty ? "no models" : summary.modelsUsed.joined(separator: ", ")
|
|
565
|
-
print(
|
|
601
|
+
print(
|
|
602
|
+
"\(project) - \(summary.provider.rawValue) - \(summary.totals.totalTokens) tokens - \(costText) - \(models)")
|
|
566
603
|
}
|
|
567
604
|
return
|
|
568
605
|
}
|
|
@@ -571,7 +608,8 @@ enum RunicCLI {
|
|
|
571
608
|
for summary in summaries {
|
|
572
609
|
let project = summary.projectID ?? "all"
|
|
573
610
|
let costText = summary.totals.costUSD.map { String(format: "$%.2f", $0) } ?? "n/a"
|
|
574
|
-
print(
|
|
611
|
+
print(
|
|
612
|
+
"\(summary.hourKey) - \(summary.provider.rawValue) - \(project) - \(summary.totals.totalTokens) tokens - \(costText) - \(summary.requestCount) requests")
|
|
575
613
|
}
|
|
576
614
|
return
|
|
577
615
|
}
|
|
@@ -594,7 +632,8 @@ enum RunicCLI {
|
|
|
594
632
|
for comparison in comparisons {
|
|
595
633
|
let costPerToken = String(format: "$%.6f", comparison.costPerToken)
|
|
596
634
|
let totalCost = String(format: "$%.2f", comparison.totalCost)
|
|
597
|
-
print(
|
|
635
|
+
print(
|
|
636
|
+
"#\(comparison.rank ?? 0) \(comparison.model) - \(costPerToken)/token - Total: \(totalCost) - Tokens: \(comparison.totalTokens) - Requests: \(comparison.requestCount)")
|
|
598
637
|
}
|
|
599
638
|
return
|
|
600
639
|
}
|
|
@@ -605,7 +644,8 @@ enum RunicCLI {
|
|
|
605
644
|
let costPerReq = metric.costPerRequest.map { String(format: "$%.4f", $0) } ?? "n/a"
|
|
606
645
|
let cacheHit = String(format: "%.1f%%", metric.cacheHitRate)
|
|
607
646
|
let totalCost = metric.totalCost.map { String(format: "$%.2f", $0) } ?? "n/a"
|
|
608
|
-
print(
|
|
647
|
+
print(
|
|
648
|
+
"\(metric.model) - \(metric.requestCount) reqs - \(tokensPerReq) tok/req - \(costPerReq)/req - Cache: \(cacheHit) - Total: \(totalCost)")
|
|
609
649
|
}
|
|
610
650
|
return
|
|
611
651
|
}
|
|
@@ -727,8 +767,8 @@ enum RunicCLI {
|
|
|
727
767
|
private static func creditsString(from remaining: Double) -> String {
|
|
728
768
|
if remaining >= 1_000_000 {
|
|
729
769
|
return String(format: "%.1fM", remaining / 1_000_000)
|
|
730
|
-
} else if remaining >=
|
|
731
|
-
return String(format: "%.1fK", remaining /
|
|
770
|
+
} else if remaining >= 1000 {
|
|
771
|
+
return String(format: "%.1fK", remaining / 1000)
|
|
732
772
|
}
|
|
733
773
|
return String(format: "%.0f", remaining)
|
|
734
774
|
}
|
|
@@ -736,8 +776,8 @@ enum RunicCLI {
|
|
|
736
776
|
private static func formatNumber(_ n: Int) -> String {
|
|
737
777
|
if n >= 1_000_000 {
|
|
738
778
|
return String(format: "%.1fM", Double(n) / 1_000_000)
|
|
739
|
-
} else if n >=
|
|
740
|
-
return String(format: "%.1fK", Double(n) /
|
|
779
|
+
} else if n >= 1000 {
|
|
780
|
+
return String(format: "%.1fK", Double(n) / 1000)
|
|
741
781
|
}
|
|
742
782
|
return "\(n)"
|
|
743
783
|
}
|
|
@@ -788,7 +828,8 @@ enum RunicCLI {
|
|
|
788
828
|
print("insights - Analyze local usage logs")
|
|
789
829
|
print(" Options:")
|
|
790
830
|
print(" --provider PROVIDER Provider to analyze (claude, codex, all)")
|
|
791
|
-
print(
|
|
831
|
+
print(
|
|
832
|
+
" --view VIEW daily | session | blocks | models | projects | comparative | efficiency")
|
|
792
833
|
print(" --project PROJECT Filter to a specific project")
|
|
793
834
|
print(" --timezone TZ Timezone identifier (defaults to local)")
|
|
794
835
|
print(" --granularity GRAN Time granularity: hourly (for daily view)")
|
|
@@ -808,7 +849,7 @@ enum RunicCLI {
|
|
|
808
849
|
print(" comparative Model cost-per-token comparison with rankings")
|
|
809
850
|
print(" efficiency Efficiency metrics (tokens/request, cost/request, cache hit rate)")
|
|
810
851
|
}
|
|
811
|
-
if command != nil
|
|
852
|
+
if command != nil, command != "usage", command != "cost", command != "insights" {
|
|
812
853
|
print("Unknown command: \(command ?? "")")
|
|
813
854
|
}
|
|
814
855
|
Foundation.exit(0)
|
|
@@ -820,7 +861,7 @@ enum RunicCLI {
|
|
|
820
861
|
}
|
|
821
862
|
|
|
822
863
|
private static func exit(code: Int32, message: String? = nil) -> Never {
|
|
823
|
-
if let message
|
|
864
|
+
if let message {
|
|
824
865
|
FileHandle.standardError.write((message + "\n").data(using: .utf8)!)
|
|
825
866
|
}
|
|
826
867
|
Foundation.exit(code)
|
|
@@ -834,7 +875,7 @@ struct UsageCommand {
|
|
|
834
875
|
var json: Bool = false
|
|
835
876
|
var pretty: Bool = false
|
|
836
877
|
var noColor: Bool = false
|
|
837
|
-
var provider: String?
|
|
878
|
+
var provider: String?
|
|
838
879
|
var format: String = "text"
|
|
839
880
|
}
|
|
840
881
|
|
|
@@ -843,7 +884,7 @@ struct CostCommand {
|
|
|
843
884
|
var json: Bool = false
|
|
844
885
|
var pretty: Bool = false
|
|
845
886
|
var noColor: Bool = false
|
|
846
|
-
var provider: String?
|
|
887
|
+
var provider: String?
|
|
847
888
|
var format: String = "text"
|
|
848
889
|
var refresh: Bool = false
|
|
849
890
|
}
|
|
@@ -852,12 +893,12 @@ struct InsightsCommand {
|
|
|
852
893
|
var json: Bool = false
|
|
853
894
|
var pretty: Bool = false
|
|
854
895
|
var noColor: Bool = false
|
|
855
|
-
var provider: String?
|
|
896
|
+
var provider: String?
|
|
856
897
|
var view: String = "daily"
|
|
857
|
-
var project: String?
|
|
858
|
-
var timezone: String?
|
|
859
|
-
var granularity: String?
|
|
860
|
-
var gitDirectory: String?
|
|
898
|
+
var project: String?
|
|
899
|
+
var timezone: String?
|
|
900
|
+
var granularity: String?
|
|
901
|
+
var gitDirectory: String?
|
|
861
902
|
var budget: Bool = false
|
|
862
903
|
var withCommits: Bool = false
|
|
863
904
|
}
|