@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,7 +1,7 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
|
|
3
3
|
/// Storage for team management, memberships, project ownership, and invitations
|
|
4
|
-
public
|
|
4
|
+
public enum TeamStore {
|
|
5
5
|
// MARK: - Types
|
|
6
6
|
|
|
7
7
|
public enum TeamRole: String, Codable, Sendable {
|
|
@@ -57,8 +57,8 @@ public struct TeamStore {
|
|
|
57
57
|
quota: Int,
|
|
58
58
|
usedQuota: Int = 0,
|
|
59
59
|
createdAt: Date = Date(),
|
|
60
|
-
updatedAt: Date = Date()
|
|
61
|
-
|
|
60
|
+
updatedAt: Date = Date())
|
|
61
|
+
{
|
|
62
62
|
self.id = id
|
|
63
63
|
self.name = name
|
|
64
64
|
self.ownerUserID = ownerUserID
|
|
@@ -87,8 +87,8 @@ public struct TeamStore {
|
|
|
87
87
|
quota: Int? = nil,
|
|
88
88
|
usedQuota: Int = 0,
|
|
89
89
|
joinedAt: Date = Date(),
|
|
90
|
-
updatedAt: Date = Date()
|
|
91
|
-
|
|
90
|
+
updatedAt: Date = Date())
|
|
91
|
+
{
|
|
92
92
|
self.id = id
|
|
93
93
|
self.teamID = teamID
|
|
94
94
|
self.userID = userID
|
|
@@ -101,7 +101,10 @@ public struct TeamStore {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
public struct ProjectOwnership: Codable, Sendable, Identifiable {
|
|
104
|
-
public var id: String {
|
|
104
|
+
public var id: String {
|
|
105
|
+
self.projectID
|
|
106
|
+
}
|
|
107
|
+
|
|
105
108
|
public let projectID: String
|
|
106
109
|
public var ownerUserID: String
|
|
107
110
|
public var teamID: String?
|
|
@@ -117,8 +120,8 @@ public struct TeamStore {
|
|
|
117
120
|
accessLevel: AccessLevel = .private,
|
|
118
121
|
sharedWithUserIDs: [String] = [],
|
|
119
122
|
createdAt: Date = Date(),
|
|
120
|
-
updatedAt: Date = Date()
|
|
121
|
-
|
|
123
|
+
updatedAt: Date = Date())
|
|
124
|
+
{
|
|
122
125
|
self.projectID = projectID
|
|
123
126
|
self.ownerUserID = ownerUserID
|
|
124
127
|
self.teamID = teamID
|
|
@@ -149,8 +152,8 @@ public struct TeamStore {
|
|
|
149
152
|
status: InvitationStatus = .pending,
|
|
150
153
|
createdAt: Date = Date(),
|
|
151
154
|
expiresAt: Date = Date().addingTimeInterval(7 * 24 * 60 * 60), // 7 days
|
|
152
|
-
respondedAt: Date? = nil
|
|
153
|
-
|
|
155
|
+
respondedAt: Date? = nil)
|
|
156
|
+
{
|
|
154
157
|
self.id = id
|
|
155
158
|
self.teamID = teamID
|
|
156
159
|
self.email = email
|
|
@@ -163,7 +166,7 @@ public struct TeamStore {
|
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
public var isExpired: Bool {
|
|
166
|
-
Date() > expiresAt && status == .pending
|
|
169
|
+
Date() > self.expiresAt && self.status == .pending
|
|
167
170
|
}
|
|
168
171
|
}
|
|
169
172
|
|
|
@@ -181,8 +184,8 @@ public struct TeamStore {
|
|
|
181
184
|
teams: [String: Team] = [:],
|
|
182
185
|
memberships: [String: TeamMembership] = [:],
|
|
183
186
|
projectOwnership: [String: ProjectOwnership] = [:],
|
|
184
|
-
invitations: [String: TeamInvitation] = [:]
|
|
185
|
-
|
|
187
|
+
invitations: [String: TeamInvitation] = [:])
|
|
188
|
+
{
|
|
186
189
|
self.version = version
|
|
187
190
|
self.currentUserID = currentUserID
|
|
188
191
|
self.teams = teams
|
|
@@ -212,31 +215,31 @@ public struct TeamStore {
|
|
|
212
215
|
public var errorDescription: String? {
|
|
213
216
|
switch self {
|
|
214
217
|
case .teamNotFound:
|
|
215
|
-
|
|
218
|
+
"Team not found"
|
|
216
219
|
case .membershipNotFound:
|
|
217
|
-
|
|
220
|
+
"Membership not found"
|
|
218
221
|
case .projectNotFound:
|
|
219
|
-
|
|
222
|
+
"Project not found"
|
|
220
223
|
case .invitationNotFound:
|
|
221
|
-
|
|
224
|
+
"Invitation not found"
|
|
222
225
|
case .invalidQuota:
|
|
223
|
-
|
|
226
|
+
"Invalid quota value"
|
|
224
227
|
case .insufficientPermissions:
|
|
225
|
-
|
|
228
|
+
"Insufficient permissions to perform this action"
|
|
226
229
|
case .duplicateMembership:
|
|
227
|
-
|
|
230
|
+
"User is already a member of this team"
|
|
228
231
|
case .duplicateInvitation:
|
|
229
|
-
|
|
232
|
+
"An invitation has already been sent to this email"
|
|
230
233
|
case .invitationExpired:
|
|
231
|
-
|
|
234
|
+
"This invitation has expired"
|
|
232
235
|
case .ownerCannotLeave:
|
|
233
|
-
|
|
236
|
+
"Team owner cannot leave the team. Transfer ownership first."
|
|
234
237
|
case .invalidEmailFormat:
|
|
235
|
-
|
|
238
|
+
"Invalid email format"
|
|
236
239
|
case .teamQuotaExceeded:
|
|
237
|
-
|
|
240
|
+
"Team quota exceeded"
|
|
238
241
|
case .memberQuotaExceeded:
|
|
239
|
-
|
|
242
|
+
"Member quota exceeded"
|
|
240
243
|
}
|
|
241
244
|
}
|
|
242
245
|
}
|
|
@@ -253,7 +256,7 @@ public struct TeamStore {
|
|
|
253
256
|
// MARK: - Storage Operations
|
|
254
257
|
|
|
255
258
|
public static func load() -> TeamsData {
|
|
256
|
-
guard FileManager.default.fileExists(atPath: storageURL.path) else {
|
|
259
|
+
guard FileManager.default.fileExists(atPath: self.storageURL.path) else {
|
|
257
260
|
return TeamsData()
|
|
258
261
|
}
|
|
259
262
|
|
|
@@ -274,7 +277,7 @@ public struct TeamStore {
|
|
|
274
277
|
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
275
278
|
|
|
276
279
|
let data = try encoder.encode(teamsData)
|
|
277
|
-
try data.write(to: storageURL, options: .atomic)
|
|
280
|
+
try data.write(to: self.storageURL, options: .atomic)
|
|
278
281
|
}
|
|
279
282
|
|
|
280
283
|
// MARK: - Team Management
|
|
@@ -284,7 +287,7 @@ public struct TeamStore {
|
|
|
284
287
|
throw TeamStoreError.invalidQuota
|
|
285
288
|
}
|
|
286
289
|
|
|
287
|
-
var data = load()
|
|
290
|
+
var data = self.load()
|
|
288
291
|
let team = Team(name: name, ownerUserID: ownerUserID, quota: quota)
|
|
289
292
|
data.teams[team.id] = team
|
|
290
293
|
|
|
@@ -292,20 +295,19 @@ public struct TeamStore {
|
|
|
292
295
|
let ownerMembership = TeamMembership(
|
|
293
296
|
teamID: team.id,
|
|
294
297
|
userID: ownerUserID,
|
|
295
|
-
role: .owner
|
|
296
|
-
)
|
|
298
|
+
role: .owner)
|
|
297
299
|
data.memberships[ownerMembership.id] = ownerMembership
|
|
298
300
|
|
|
299
|
-
try save(data)
|
|
301
|
+
try self.save(data)
|
|
300
302
|
return team
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
public static func getTeam(id: String) -> Team? {
|
|
304
|
-
load().teams[id]
|
|
306
|
+
self.load().teams[id]
|
|
305
307
|
}
|
|
306
308
|
|
|
307
309
|
public static func updateTeam(_ team: Team) throws {
|
|
308
|
-
var data = load()
|
|
310
|
+
var data = self.load()
|
|
309
311
|
guard data.teams[team.id] != nil else {
|
|
310
312
|
throw TeamStoreError.teamNotFound
|
|
311
313
|
}
|
|
@@ -313,11 +315,11 @@ public struct TeamStore {
|
|
|
313
315
|
var updatedTeam = team
|
|
314
316
|
updatedTeam.updatedAt = Date()
|
|
315
317
|
data.teams[team.id] = updatedTeam
|
|
316
|
-
try save(data)
|
|
318
|
+
try self.save(data)
|
|
317
319
|
}
|
|
318
320
|
|
|
319
321
|
public static func deleteTeam(id: String) throws {
|
|
320
|
-
var data = load()
|
|
322
|
+
var data = self.load()
|
|
321
323
|
guard data.teams[id] != nil else {
|
|
322
324
|
throw TeamStoreError.teamNotFound
|
|
323
325
|
}
|
|
@@ -340,20 +342,25 @@ public struct TeamStore {
|
|
|
340
342
|
data.projectOwnership[projectID] = updatedOwnership
|
|
341
343
|
}
|
|
342
344
|
|
|
343
|
-
try save(data)
|
|
345
|
+
try self.save(data)
|
|
344
346
|
}
|
|
345
347
|
|
|
346
348
|
public static func getUserTeams(userID: String) -> [Team] {
|
|
347
|
-
let data = load()
|
|
349
|
+
let data = self.load()
|
|
348
350
|
let userMemberships = data.memberships.values.filter { $0.userID == userID }
|
|
349
|
-
let teamIDs = Set(userMemberships.map
|
|
351
|
+
let teamIDs = Set(userMemberships.map(\.teamID))
|
|
350
352
|
return teamIDs.compactMap { data.teams[$0] }.sorted { $0.createdAt < $1.createdAt }
|
|
351
353
|
}
|
|
352
354
|
|
|
353
355
|
// MARK: - Membership Management
|
|
354
356
|
|
|
355
|
-
public static func addMember(
|
|
356
|
-
|
|
357
|
+
public static func addMember(
|
|
358
|
+
teamID: String,
|
|
359
|
+
userID: String,
|
|
360
|
+
role: TeamRole,
|
|
361
|
+
quota: Int? = nil) throws -> TeamMembership
|
|
362
|
+
{
|
|
363
|
+
var data = self.load()
|
|
357
364
|
|
|
358
365
|
guard data.teams[teamID] != nil else {
|
|
359
366
|
throw TeamStoreError.teamNotFound
|
|
@@ -366,7 +373,7 @@ public struct TeamStore {
|
|
|
366
373
|
}
|
|
367
374
|
|
|
368
375
|
// Validate quota if provided
|
|
369
|
-
if let quota
|
|
376
|
+
if let quota, quota < 0 {
|
|
370
377
|
throw TeamStoreError.invalidQuota
|
|
371
378
|
}
|
|
372
379
|
|
|
@@ -374,22 +381,21 @@ public struct TeamStore {
|
|
|
374
381
|
teamID: teamID,
|
|
375
382
|
userID: userID,
|
|
376
383
|
role: role,
|
|
377
|
-
quota: quota
|
|
378
|
-
)
|
|
384
|
+
quota: quota)
|
|
379
385
|
data.memberships[membership.id] = membership
|
|
380
386
|
|
|
381
|
-
try save(data)
|
|
387
|
+
try self.save(data)
|
|
382
388
|
return membership
|
|
383
389
|
}
|
|
384
390
|
|
|
385
391
|
public static func updateMemberRole(membershipID: String, newRole: TeamRole) throws {
|
|
386
|
-
var data = load()
|
|
392
|
+
var data = self.load()
|
|
387
393
|
guard var membership = data.memberships[membershipID] else {
|
|
388
394
|
throw TeamStoreError.membershipNotFound
|
|
389
395
|
}
|
|
390
396
|
|
|
391
397
|
// Prevent changing owner role (should use transfer ownership instead)
|
|
392
|
-
if membership.role == .owner
|
|
398
|
+
if membership.role == .owner, newRole != .owner {
|
|
393
399
|
throw TeamStoreError.insufficientPermissions
|
|
394
400
|
}
|
|
395
401
|
|
|
@@ -397,11 +403,11 @@ public struct TeamStore {
|
|
|
397
403
|
membership.updatedAt = Date()
|
|
398
404
|
data.memberships[membershipID] = membership
|
|
399
405
|
|
|
400
|
-
try save(data)
|
|
406
|
+
try self.save(data)
|
|
401
407
|
}
|
|
402
408
|
|
|
403
409
|
public static func updateMemberQuota(membershipID: String, newQuota: Int?) throws {
|
|
404
|
-
var data = load()
|
|
410
|
+
var data = self.load()
|
|
405
411
|
guard var membership = data.memberships[membershipID] else {
|
|
406
412
|
throw TeamStoreError.membershipNotFound
|
|
407
413
|
}
|
|
@@ -414,11 +420,11 @@ public struct TeamStore {
|
|
|
414
420
|
membership.updatedAt = Date()
|
|
415
421
|
data.memberships[membershipID] = membership
|
|
416
422
|
|
|
417
|
-
try save(data)
|
|
423
|
+
try self.save(data)
|
|
418
424
|
}
|
|
419
425
|
|
|
420
426
|
public static func removeMember(membershipID: String) throws {
|
|
421
|
-
var data = load()
|
|
427
|
+
var data = self.load()
|
|
422
428
|
guard let membership = data.memberships[membershipID] else {
|
|
423
429
|
throw TeamStoreError.membershipNotFound
|
|
424
430
|
}
|
|
@@ -429,32 +435,36 @@ public struct TeamStore {
|
|
|
429
435
|
}
|
|
430
436
|
|
|
431
437
|
data.memberships.removeValue(forKey: membershipID)
|
|
432
|
-
try save(data)
|
|
438
|
+
try self.save(data)
|
|
433
439
|
}
|
|
434
440
|
|
|
435
441
|
public static func getTeamMembers(teamID: String) -> [TeamMembership] {
|
|
436
|
-
load().memberships.values
|
|
442
|
+
self.load().memberships.values
|
|
437
443
|
.filter { $0.teamID == teamID }
|
|
438
444
|
.sorted { $0.joinedAt < $1.joinedAt }
|
|
439
445
|
}
|
|
440
446
|
|
|
441
447
|
public static func getUserMembership(teamID: String, userID: String) -> TeamMembership? {
|
|
442
|
-
load().memberships.values.first { $0.teamID == teamID && $0.userID == userID }
|
|
448
|
+
self.load().memberships.values.first { $0.teamID == teamID && $0.userID == userID }
|
|
443
449
|
}
|
|
444
450
|
|
|
445
451
|
public static func transferOwnership(teamID: String, newOwnerUserID: String) throws {
|
|
446
|
-
var data = load()
|
|
452
|
+
var data = self.load()
|
|
447
453
|
guard var team = data.teams[teamID] else {
|
|
448
454
|
throw TeamStoreError.teamNotFound
|
|
449
455
|
}
|
|
450
456
|
|
|
451
457
|
// Get current owner membership
|
|
452
|
-
guard let currentOwnerMembership = data.memberships.values
|
|
458
|
+
guard let currentOwnerMembership = data.memberships.values
|
|
459
|
+
.first(where: { $0.teamID == teamID && $0.userID == team.ownerUserID })
|
|
460
|
+
else {
|
|
453
461
|
throw TeamStoreError.membershipNotFound
|
|
454
462
|
}
|
|
455
463
|
|
|
456
464
|
// Get new owner membership
|
|
457
|
-
guard let newOwnerMembership = data.memberships.values
|
|
465
|
+
guard let newOwnerMembership = data.memberships.values
|
|
466
|
+
.first(where: { $0.teamID == teamID && $0.userID == newOwnerUserID })
|
|
467
|
+
else {
|
|
458
468
|
throw TeamStoreError.membershipNotFound
|
|
459
469
|
}
|
|
460
470
|
|
|
@@ -474,16 +484,21 @@ public struct TeamStore {
|
|
|
474
484
|
updatedNewOwner.updatedAt = Date()
|
|
475
485
|
data.memberships[newOwnerMembership.id] = updatedNewOwner
|
|
476
486
|
|
|
477
|
-
try save(data)
|
|
487
|
+
try self.save(data)
|
|
478
488
|
}
|
|
479
489
|
|
|
480
490
|
// MARK: - Project Ownership Management
|
|
481
491
|
|
|
482
|
-
public static func setProjectOwnership(
|
|
483
|
-
|
|
492
|
+
public static func setProjectOwnership(
|
|
493
|
+
projectID: String,
|
|
494
|
+
ownerUserID: String,
|
|
495
|
+
teamID: String? = nil,
|
|
496
|
+
accessLevel: AccessLevel) throws
|
|
497
|
+
{
|
|
498
|
+
var data = self.load()
|
|
484
499
|
|
|
485
500
|
// Validate team exists if provided
|
|
486
|
-
if let teamID
|
|
501
|
+
if let teamID {
|
|
487
502
|
guard data.teams[teamID] != nil else {
|
|
488
503
|
throw TeamStoreError.teamNotFound
|
|
489
504
|
}
|
|
@@ -501,16 +516,15 @@ public struct TeamStore {
|
|
|
501
516
|
projectID: projectID,
|
|
502
517
|
ownerUserID: ownerUserID,
|
|
503
518
|
teamID: teamID,
|
|
504
|
-
accessLevel: accessLevel
|
|
505
|
-
)
|
|
519
|
+
accessLevel: accessLevel)
|
|
506
520
|
data.projectOwnership[projectID] = ownership
|
|
507
521
|
}
|
|
508
522
|
|
|
509
|
-
try save(data)
|
|
523
|
+
try self.save(data)
|
|
510
524
|
}
|
|
511
525
|
|
|
512
526
|
public static func shareProject(projectID: String, withUserIDs: [String]) throws {
|
|
513
|
-
var data = load()
|
|
527
|
+
var data = self.load()
|
|
514
528
|
guard var ownership = data.projectOwnership[projectID] else {
|
|
515
529
|
throw TeamStoreError.projectNotFound
|
|
516
530
|
}
|
|
@@ -520,11 +534,11 @@ public struct TeamStore {
|
|
|
520
534
|
ownership.updatedAt = Date()
|
|
521
535
|
data.projectOwnership[projectID] = ownership
|
|
522
536
|
|
|
523
|
-
try save(data)
|
|
537
|
+
try self.save(data)
|
|
524
538
|
}
|
|
525
539
|
|
|
526
540
|
public static func unshareProject(projectID: String, withUserIDs: [String]) throws {
|
|
527
|
-
var data = load()
|
|
541
|
+
var data = self.load()
|
|
528
542
|
guard var ownership = data.projectOwnership[projectID] else {
|
|
529
543
|
throw TeamStoreError.projectNotFound
|
|
530
544
|
}
|
|
@@ -533,52 +547,57 @@ public struct TeamStore {
|
|
|
533
547
|
ownership.updatedAt = Date()
|
|
534
548
|
|
|
535
549
|
// If no users are shared with, revert to private
|
|
536
|
-
if ownership.sharedWithUserIDs.isEmpty
|
|
550
|
+
if ownership.sharedWithUserIDs.isEmpty, ownership.accessLevel == .shared {
|
|
537
551
|
ownership.accessLevel = .private
|
|
538
552
|
}
|
|
539
553
|
|
|
540
554
|
data.projectOwnership[projectID] = ownership
|
|
541
|
-
try save(data)
|
|
555
|
+
try self.save(data)
|
|
542
556
|
}
|
|
543
557
|
|
|
544
558
|
public static func getProjectOwnership(projectID: String) -> ProjectOwnership? {
|
|
545
|
-
load().projectOwnership[projectID]
|
|
559
|
+
self.load().projectOwnership[projectID]
|
|
546
560
|
}
|
|
547
561
|
|
|
548
562
|
public static func getUserProjects(userID: String) -> [ProjectOwnership] {
|
|
549
|
-
let data = load()
|
|
563
|
+
let data = self.load()
|
|
550
564
|
return data.projectOwnership.values
|
|
551
565
|
.filter { $0.ownerUserID == userID || $0.sharedWithUserIDs.contains(userID) }
|
|
552
566
|
.sorted { $0.createdAt < $1.createdAt }
|
|
553
567
|
}
|
|
554
568
|
|
|
555
569
|
public static func getTeamProjects(teamID: String) -> [ProjectOwnership] {
|
|
556
|
-
load().projectOwnership.values
|
|
570
|
+
self.load().projectOwnership.values
|
|
557
571
|
.filter { $0.teamID == teamID }
|
|
558
572
|
.sorted { $0.createdAt < $1.createdAt }
|
|
559
573
|
}
|
|
560
574
|
|
|
561
575
|
public static func deleteProjectOwnership(projectID: String) throws {
|
|
562
|
-
var data = load()
|
|
576
|
+
var data = self.load()
|
|
563
577
|
guard data.projectOwnership[projectID] != nil else {
|
|
564
578
|
throw TeamStoreError.projectNotFound
|
|
565
579
|
}
|
|
566
580
|
|
|
567
581
|
data.projectOwnership.removeValue(forKey: projectID)
|
|
568
|
-
try save(data)
|
|
582
|
+
try self.save(data)
|
|
569
583
|
}
|
|
570
584
|
|
|
571
585
|
// MARK: - Invitation Management
|
|
572
586
|
|
|
573
|
-
public static func createInvitation(
|
|
574
|
-
|
|
587
|
+
public static func createInvitation(
|
|
588
|
+
teamID: String,
|
|
589
|
+
email: String,
|
|
590
|
+
role: TeamRole,
|
|
591
|
+
invitedBy: String) throws -> TeamInvitation
|
|
592
|
+
{
|
|
593
|
+
var data = self.load()
|
|
575
594
|
|
|
576
595
|
guard data.teams[teamID] != nil else {
|
|
577
596
|
throw TeamStoreError.teamNotFound
|
|
578
597
|
}
|
|
579
598
|
|
|
580
599
|
// Validate email format (basic validation)
|
|
581
|
-
guard email.contains("@")
|
|
600
|
+
guard email.contains("@"), email.contains(".") else {
|
|
582
601
|
throw TeamStoreError.invalidEmailFormat
|
|
583
602
|
}
|
|
584
603
|
|
|
@@ -594,16 +613,15 @@ public struct TeamStore {
|
|
|
594
613
|
teamID: teamID,
|
|
595
614
|
email: email,
|
|
596
615
|
role: role,
|
|
597
|
-
invitedBy: invitedBy
|
|
598
|
-
)
|
|
616
|
+
invitedBy: invitedBy)
|
|
599
617
|
data.invitations[invitation.id] = invitation
|
|
600
618
|
|
|
601
|
-
try save(data)
|
|
619
|
+
try self.save(data)
|
|
602
620
|
return invitation
|
|
603
621
|
}
|
|
604
622
|
|
|
605
623
|
public static func acceptInvitation(id: String, userID: String) throws {
|
|
606
|
-
var data = load()
|
|
624
|
+
var data = self.load()
|
|
607
625
|
guard var invitation = data.invitations[id] else {
|
|
608
626
|
throw TeamStoreError.invitationNotFound
|
|
609
627
|
}
|
|
@@ -616,7 +634,7 @@ public struct TeamStore {
|
|
|
616
634
|
invitation.status = .expired
|
|
617
635
|
invitation.respondedAt = Date()
|
|
618
636
|
data.invitations[id] = invitation
|
|
619
|
-
try save(data)
|
|
637
|
+
try self.save(data)
|
|
620
638
|
throw TeamStoreError.invitationExpired
|
|
621
639
|
}
|
|
622
640
|
|
|
@@ -624,8 +642,7 @@ public struct TeamStore {
|
|
|
624
642
|
let membership = TeamMembership(
|
|
625
643
|
teamID: invitation.teamID,
|
|
626
644
|
userID: userID,
|
|
627
|
-
role: invitation.role
|
|
628
|
-
)
|
|
645
|
+
role: invitation.role)
|
|
629
646
|
data.memberships[membership.id] = membership
|
|
630
647
|
|
|
631
648
|
// Update invitation status
|
|
@@ -633,11 +650,11 @@ public struct TeamStore {
|
|
|
633
650
|
invitation.respondedAt = Date()
|
|
634
651
|
data.invitations[id] = invitation
|
|
635
652
|
|
|
636
|
-
try save(data)
|
|
653
|
+
try self.save(data)
|
|
637
654
|
}
|
|
638
655
|
|
|
639
656
|
public static func declineInvitation(id: String) throws {
|
|
640
|
-
var data = load()
|
|
657
|
+
var data = self.load()
|
|
641
658
|
guard var invitation = data.invitations[id] else {
|
|
642
659
|
throw TeamStoreError.invitationNotFound
|
|
643
660
|
}
|
|
@@ -646,53 +663,53 @@ public struct TeamStore {
|
|
|
646
663
|
invitation.respondedAt = Date()
|
|
647
664
|
data.invitations[id] = invitation
|
|
648
665
|
|
|
649
|
-
try save(data)
|
|
666
|
+
try self.save(data)
|
|
650
667
|
}
|
|
651
668
|
|
|
652
669
|
public static func cancelInvitation(id: String) throws {
|
|
653
|
-
var data = load()
|
|
670
|
+
var data = self.load()
|
|
654
671
|
guard data.invitations[id] != nil else {
|
|
655
672
|
throw TeamStoreError.invitationNotFound
|
|
656
673
|
}
|
|
657
674
|
|
|
658
675
|
data.invitations.removeValue(forKey: id)
|
|
659
|
-
try save(data)
|
|
676
|
+
try self.save(data)
|
|
660
677
|
}
|
|
661
678
|
|
|
662
679
|
public static func getPendingInvitations(email: String) -> [TeamInvitation] {
|
|
663
|
-
load().invitations.values
|
|
680
|
+
self.load().invitations.values
|
|
664
681
|
.filter { $0.email.lowercased() == email.lowercased() && $0.status == .pending && !$0.isExpired }
|
|
665
682
|
.sorted { $0.createdAt > $1.createdAt }
|
|
666
683
|
}
|
|
667
684
|
|
|
668
685
|
public static func getTeamInvitations(teamID: String) -> [TeamInvitation] {
|
|
669
|
-
load().invitations.values
|
|
686
|
+
self.load().invitations.values
|
|
670
687
|
.filter { $0.teamID == teamID }
|
|
671
688
|
.sorted { $0.createdAt > $1.createdAt }
|
|
672
689
|
}
|
|
673
690
|
|
|
674
691
|
public static func cleanupExpiredInvitations() throws {
|
|
675
|
-
var data = load()
|
|
676
|
-
let expiredIDs = data.invitations.filter
|
|
692
|
+
var data = self.load()
|
|
693
|
+
let expiredIDs = data.invitations.filter(\.value.isExpired).map(\.key)
|
|
677
694
|
|
|
678
695
|
for id in expiredIDs {
|
|
679
696
|
data.invitations[id]?.status = .expired
|
|
680
697
|
data.invitations[id]?.respondedAt = Date()
|
|
681
698
|
}
|
|
682
699
|
|
|
683
|
-
try save(data)
|
|
700
|
+
try self.save(data)
|
|
684
701
|
}
|
|
685
702
|
|
|
686
703
|
// MARK: - Current User Management
|
|
687
704
|
|
|
688
705
|
public static func setCurrentUser(userID: String) throws {
|
|
689
|
-
var data = load()
|
|
706
|
+
var data = self.load()
|
|
690
707
|
data.currentUserID = userID
|
|
691
|
-
try save(data)
|
|
708
|
+
try self.save(data)
|
|
692
709
|
}
|
|
693
710
|
|
|
694
711
|
public static func getCurrentUser() -> String? {
|
|
695
|
-
load().currentUserID
|
|
712
|
+
self.load().currentUserID
|
|
696
713
|
}
|
|
697
714
|
|
|
698
715
|
// MARK: - Usage Tracking
|
|
@@ -702,7 +719,7 @@ public struct TeamStore {
|
|
|
702
719
|
throw TeamStoreError.invalidQuota
|
|
703
720
|
}
|
|
704
721
|
|
|
705
|
-
var data = load()
|
|
722
|
+
var data = self.load()
|
|
706
723
|
guard var team = data.teams[teamID] else {
|
|
707
724
|
throw TeamStoreError.teamNotFound
|
|
708
725
|
}
|
|
@@ -712,12 +729,12 @@ public struct TeamStore {
|
|
|
712
729
|
|
|
713
730
|
if team.usedQuota > team.quota {
|
|
714
731
|
data.teams[teamID] = team
|
|
715
|
-
try save(data)
|
|
732
|
+
try self.save(data)
|
|
716
733
|
throw TeamStoreError.teamQuotaExceeded
|
|
717
734
|
}
|
|
718
735
|
|
|
719
736
|
data.teams[teamID] = team
|
|
720
|
-
try save(data)
|
|
737
|
+
try self.save(data)
|
|
721
738
|
}
|
|
722
739
|
|
|
723
740
|
public static func incrementMemberUsage(membershipID: String, amount: Int) throws {
|
|
@@ -725,7 +742,7 @@ public struct TeamStore {
|
|
|
725
742
|
throw TeamStoreError.invalidQuota
|
|
726
743
|
}
|
|
727
744
|
|
|
728
|
-
var data = load()
|
|
745
|
+
var data = self.load()
|
|
729
746
|
guard var membership = data.memberships[membershipID] else {
|
|
730
747
|
throw TeamStoreError.membershipNotFound
|
|
731
748
|
}
|
|
@@ -736,16 +753,16 @@ public struct TeamStore {
|
|
|
736
753
|
// Check individual quota if set
|
|
737
754
|
if let quota = membership.quota, membership.usedQuota > quota {
|
|
738
755
|
data.memberships[membershipID] = membership
|
|
739
|
-
try save(data)
|
|
756
|
+
try self.save(data)
|
|
740
757
|
throw TeamStoreError.memberQuotaExceeded
|
|
741
758
|
}
|
|
742
759
|
|
|
743
760
|
data.memberships[membershipID] = membership
|
|
744
|
-
try save(data)
|
|
761
|
+
try self.save(data)
|
|
745
762
|
}
|
|
746
763
|
|
|
747
764
|
public static func resetTeamUsage(teamID: String) throws {
|
|
748
|
-
var data = load()
|
|
765
|
+
var data = self.load()
|
|
749
766
|
guard var team = data.teams[teamID] else {
|
|
750
767
|
throw TeamStoreError.teamNotFound
|
|
751
768
|
}
|
|
@@ -754,11 +771,11 @@ public struct TeamStore {
|
|
|
754
771
|
team.updatedAt = Date()
|
|
755
772
|
data.teams[teamID] = team
|
|
756
773
|
|
|
757
|
-
try save(data)
|
|
774
|
+
try self.save(data)
|
|
758
775
|
}
|
|
759
776
|
|
|
760
777
|
public static func resetMemberUsage(membershipID: String) throws {
|
|
761
|
-
var data = load()
|
|
778
|
+
var data = self.load()
|
|
762
779
|
guard var membership = data.memberships[membershipID] else {
|
|
763
780
|
throw TeamStoreError.membershipNotFound
|
|
764
781
|
}
|
|
@@ -767,11 +784,11 @@ public struct TeamStore {
|
|
|
767
784
|
membership.updatedAt = Date()
|
|
768
785
|
data.memberships[membershipID] = membership
|
|
769
786
|
|
|
770
|
-
try save(data)
|
|
787
|
+
try self.save(data)
|
|
771
788
|
}
|
|
772
789
|
|
|
773
790
|
public static func resetAllTeamUsages() throws {
|
|
774
|
-
var data = load()
|
|
791
|
+
var data = self.load()
|
|
775
792
|
|
|
776
793
|
// Reset all teams
|
|
777
794
|
for (id, var team) in data.teams {
|
|
@@ -787,6 +804,6 @@ public struct TeamStore {
|
|
|
787
804
|
data.memberships[id] = membership
|
|
788
805
|
}
|
|
789
806
|
|
|
790
|
-
try save(data)
|
|
807
|
+
try self.save(data)
|
|
791
808
|
}
|
|
792
809
|
}
|
|
@@ -277,7 +277,7 @@ private enum RPCWireError: Error, CustomStringConvertible {
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
/// RPC helper used on background tasks; safe because we confine it to the owning task.
|
|
281
281
|
private final class CodexRPCClient: @unchecked Sendable {
|
|
282
282
|
private let process = Process()
|
|
283
283
|
private let stdinPipe = Pipe()
|
|
@@ -687,7 +687,7 @@ public struct UsageFetcher: Sendable {
|
|
|
687
687
|
}
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
-
|
|
690
|
+
/// Minimal auth.json struct preserved from previous implementation
|
|
691
691
|
private struct AuthFile: Decodable {
|
|
692
692
|
struct Tokens: Decodable { let idToken: String? }
|
|
693
693
|
let tokens: Tokens?
|