@sriinnu/runic 1.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/.swiftformat +50 -0
- package/.swiftlint.yml +160 -0
- package/CHANGELOG.md +307 -0
- package/CONTRIBUTING.md +456 -0
- package/Icon.icns +0 -0
- package/Icon.icon/Assets/runic.png +0 -0
- package/Icon.icon/icon.json +36 -0
- package/LICENSE +21 -0
- package/Package.resolved +51 -0
- package/Package.swift +136 -0
- package/Packages/Helix/LICENSE +21 -0
- package/Packages/Helix/Package.swift +29 -0
- package/Packages/Helix/README.md +976 -0
- package/Packages/Helix/Sources/Helix/Binding.swift +111 -0
- package/Packages/Helix/Sources/Helix/CommandParser.swift +160 -0
- package/Packages/Helix/Sources/Helix/CommandSignature.swift +117 -0
- package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +72 -0
- package/Packages/Helix/Sources/Helix/HelpGenerator.swift +175 -0
- package/Packages/Helix/Sources/Helix/NameSpecification.swift +98 -0
- package/Packages/Helix/Sources/Helix/ParsableCommand.swift +204 -0
- package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +44 -0
- package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +34 -0
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +91 -0
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +94 -0
- package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +167 -0
- package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +99 -0
- package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +146 -0
- package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +97 -0
- package/Packages/Helix/Sources/Helix/Program.swift +204 -0
- package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +367 -0
- package/Packages/Silo/LICENSE +21 -0
- package/Packages/Silo/Package.swift +34 -0
- package/Packages/Silo/README.md +466 -0
- package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +169 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +365 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +594 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +326 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +154 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +119 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +121 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +391 -0
- package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +240 -0
- package/Packages/Silo/Sources/Silo/CryptoSupport.swift +86 -0
- package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +172 -0
- package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +172 -0
- package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +105 -0
- package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +191 -0
- package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +41 -0
- package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +110 -0
- package/README.md +109 -0
- package/RuniciOS/App/RuniciOSApp.swift +105 -0
- package/RuniciOS/Core/iOSUsageStore.swift +192 -0
- package/RuniciOS/Views/ProviderDetailView.swift +312 -0
- package/RuniciOS/Views/ProviderListView.swift +228 -0
- package/RuniciOS/Widgets/LargeWidget.swift +367 -0
- package/RuniciOS/Widgets/LiveActivity.swift +342 -0
- package/RuniciOS/Widgets/LockScreenWidget.swift +273 -0
- package/RuniciOS/Widgets/MediumWidget.swift +370 -0
- package/RuniciOS/Widgets/RunicWidget.swift +274 -0
- package/RuniciOS/Widgets/SmallWidget.swift +281 -0
- package/RuniciOS/Widgets/WidgetModels.swift +285 -0
- package/RuniciOS/Widgets/WidgetTimeline.swift +270 -0
- package/Scripts/CREATION-SUMMARY.md +367 -0
- package/Scripts/DIRECTORY-STRUCTURE.md +358 -0
- package/Scripts/GETTING-STARTED-CHECKLIST.md +364 -0
- package/Scripts/INDEX.md +365 -0
- package/Scripts/QUICK-REFERENCE.md +164 -0
- package/Scripts/README-SCRIPTS.md +926 -0
- package/Scripts/build-android.sh +526 -0
- package/Scripts/build-ios.sh +529 -0
- package/Scripts/build-macos.sh +603 -0
- package/Scripts/build-windows.sh +504 -0
- package/Scripts/build_icon.sh +49 -0
- package/Scripts/changelog-to-html.sh +89 -0
- package/Scripts/check-release-assets.sh +10 -0
- package/Scripts/ci-config.yml +474 -0
- package/Scripts/compile_and_run.sh +219 -0
- package/Scripts/compile_and_run_adhoc.sh +4 -0
- package/Scripts/docs-list.mjs +119 -0
- package/Scripts/make_appcast.sh +58 -0
- package/Scripts/package_app.sh +409 -0
- package/Scripts/release-lib.sh +214 -0
- package/Scripts/release.sh +76 -0
- package/Scripts/setup-all.sh +387 -0
- package/Scripts/setup-ios.sh +404 -0
- package/Scripts/setup-react-native.sh +523 -0
- package/Scripts/sign-and-notarize.sh +122 -0
- package/Scripts/test_live_update.sh +38 -0
- package/Scripts/validate_changelog.sh +23 -0
- package/Scripts/verify_appcast.sh +92 -0
- package/Sources/Runic/About.swift +93 -0
- package/Sources/Runic/AppNotifications.swift +115 -0
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +657 -0
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +518 -0
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +810 -0
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +87 -0
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +1379 -0
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +337 -0
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +395 -0
- package/Sources/Runic/Controllers/StatusItemController.swift +360 -0
- package/Sources/Runic/Core/BudgetNotificationManager.swift +80 -0
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +103 -0
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +154 -0
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +206 -0
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +95 -0
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +1216 -0
- package/Sources/Runic/Core/Rendering/IconView.swift +120 -0
- package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +71 -0
- package/Sources/Runic/Core/RunicAnimation.swift +53 -0
- package/Sources/Runic/Core/RunicColors.swift +73 -0
- package/Sources/Runic/Core/RunicCornerRadius.swift +15 -0
- package/Sources/Runic/Core/RunicSpacing.swift +68 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +158 -0
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +30 -0
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/SettingsStore.swift +1702 -0
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +20 -0
- package/Sources/Runic/Core/Stores/UsageStore+Status.swift +189 -0
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +33 -0
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +67 -0
- package/Sources/Runic/Core/Stores/UsageStore.swift +3564 -0
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +143 -0
- package/Sources/Runic/Core/UsageExporter.swift +193 -0
- package/Sources/Runic/Date+RelativeDescription.swift +21 -0
- package/Sources/Runic/HiddenWindowView.swift +32 -0
- package/Sources/Runic/InstallOrigin.swift +9 -0
- package/Sources/Runic/KeyboardShortcuts+Names.swift +6 -0
- package/Sources/Runic/MouseLocationReader.swift +65 -0
- package/Sources/Runic/Notifications+Runic.swift +6 -0
- package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +503 -0
- package/Sources/Runic/ProviderRegistry.swift +100 -0
- package/Sources/Runic/ProviderSwitcherButtons.swift +267 -0
- package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +11 -0
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +14 -0
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +52 -0
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +42 -0
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +26 -0
- package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +14 -0
- package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +18 -0
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +193 -0
- package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +110 -0
- package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +28 -0
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +12 -0
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +20 -0
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +32 -0
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +15 -0
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +48 -0
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +66 -0
- package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +12 -0
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +99 -0
- package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +18 -0
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +24 -0
- package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +2 -0
- package/Sources/Runic/Resources/ProviderIcon-auggie.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-azure.svg +5 -0
- package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +7 -0
- package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-claude.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-codex.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-cohere.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-copilot.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-cursor.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-factory.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-gemini.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-groq.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-kimi.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-minimax.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-mistral.svg +5 -0
- package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +7 -0
- package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-qwen.svg +9 -0
- package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-together.svg +5 -0
- package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +17 -0
- package/Sources/Runic/Resources/ProviderIcon-xai.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-zai.svg +5 -0
- package/Sources/Runic/Resources/RunicMenubarIcon.svg +7 -0
- package/Sources/Runic/RunicApp.swift +357 -0
- package/Sources/Runic/SessionQuotaNotifications.swift +58 -0
- package/Sources/Runic/UsagePaceText.swift +52 -0
- package/Sources/Runic/UsageProgressBar.swift +224 -0
- package/Sources/Runic/Utilities/DisplayLink.swift +96 -0
- package/Sources/Runic/Utilities/LoadingPattern.swift +60 -0
- package/Sources/Runic/Utilities/PerformanceConstants.swift +85 -0
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +256 -0
- package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +22 -0
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +275 -0
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +377 -0
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +312 -0
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +312 -0
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +228 -0
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +228 -0
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +162 -0
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +64 -0
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +268 -0
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +104 -0
- package/Sources/Runic/Views/Menu/MenuCardView.swift +1900 -0
- package/Sources/Runic/Views/Menu/MenuContent.swift +150 -0
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +369 -0
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +42 -0
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +235 -0
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +141 -0
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +209 -0
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +220 -0
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +88 -0
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +201 -0
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +398 -0
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +232 -0
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +417 -0
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +190 -0
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +132 -0
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +618 -0
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +305 -0
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +271 -0
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +268 -0
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +422 -0
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +641 -0
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +344 -0
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +230 -0
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +322 -0
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +469 -0
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +231 -0
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +50 -0
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +497 -0
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +288 -0
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +3435 -0
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +25 -0
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +59 -0
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +206 -0
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +238 -0
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +371 -0
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +175 -0
- package/Sources/Runic/Views/Team/TeamManagementView.swift +761 -0
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +181 -0
- package/Sources/RunicCLI/CLIEntry.swift +942 -0
- package/Sources/RunicCLI/CLIRenderer.swift +87 -0
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +588 -0
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +362 -0
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +439 -0
- package/Sources/RunicCLI/Commands/ResetCommand.swift +429 -0
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +168 -0
- package/Sources/RunicClaudeWatchdog/main.swift +122 -0
- package/Sources/RunicClaudeWebProbe/main.swift +62 -0
- package/Sources/RunicCore/AlertRuleStore.swift +187 -0
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +25 -0
- package/Sources/RunicCore/CopilotUsageModels.swift +84 -0
- package/Sources/RunicCore/CostUsageFetcher.swift +119 -0
- package/Sources/RunicCore/CostUsageModels.swift +479 -0
- package/Sources/RunicCore/CreditsModels.swift +50 -0
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +269 -0
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +622 -0
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +500 -0
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +679 -0
- package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +135 -0
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +173 -0
- package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +79 -0
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +86 -0
- package/Sources/RunicCore/Logging/RunicLog.swift +123 -0
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +371 -0
- package/Sources/RunicCore/OpenAIDashboardModels.swift +210 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +676 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +492 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +51 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +483 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +558 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +204 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +78 -0
- package/Sources/RunicCore/PathEnvironment.swift +482 -0
- package/Sources/RunicCore/Performance/PerformanceModels.swift +371 -0
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +409 -0
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +178 -0
- package/Sources/RunicCore/ProjectBudgetStore.swift +99 -0
- package/Sources/RunicCore/ProviderCostSnapshot.swift +29 -0
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +66 -0
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +685 -0
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +142 -0
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +141 -0
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +186 -0
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +126 -0
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +168 -0
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +332 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +180 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +137 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +207 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +654 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +28 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +480 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +780 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +146 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +269 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +100 -0
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +243 -0
- package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +125 -0
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +24 -0
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +181 -0
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +85 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +123 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +114 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +100 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +291 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +386 -0
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +65 -0
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +594 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +85 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +108 -0
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +398 -0
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +65 -0
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +1275 -0
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +65 -0
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +835 -0
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +71 -0
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +77 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +92 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +54 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +171 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +911 -0
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +87 -0
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +207 -0
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/ProviderBranding.swift +25 -0
- package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +17 -0
- package/Sources/RunicCore/Providers/ProviderDescriptor.swift +129 -0
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +263 -0
- package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +98 -0
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +501 -0
- package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +68 -0
- package/Sources/RunicCore/Providers/Providers.swift +184 -0
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +30 -0
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +317 -0
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +123 -0
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +48 -0
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +144 -0
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +72 -0
- package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +41 -0
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +724 -0
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +385 -0
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +310 -0
- package/Sources/RunicCore/Sync/SyncProtocol.swift +288 -0
- package/Sources/RunicCore/Sync/SyncRecord.swift +405 -0
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +294 -0
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +775 -0
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +333 -0
- package/Sources/RunicCore/Team/TeamModels.swift +583 -0
- package/Sources/RunicCore/Team/TeamShowbackExporter.swift +495 -0
- package/Sources/RunicCore/Team/TeamStore.swift +792 -0
- package/Sources/RunicCore/TextParsing.swift +55 -0
- package/Sources/RunicCore/UsageFetcher.swift +694 -0
- package/Sources/RunicCore/UsageFormatter.swift +410 -0
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +329 -0
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +448 -0
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +550 -0
- package/Sources/RunicCore/UsageLedger/UsageLedger.swift +29 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +660 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +209 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +417 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +245 -0
- package/Sources/RunicCore/UsagePace.swift +81 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +73 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +74 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +208 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +455 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +132 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +589 -0
- package/Sources/RunicCore/WidgetSnapshot.swift +183 -0
- package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +14 -0
- package/Sources/RunicMacros/ProviderRegistrationMacros.swift +216 -0
- package/Sources/RunicWidget/RunicWidgetBundle.swift +79 -0
- package/Sources/RunicWidget/RunicWidgetProvider.swift +283 -0
- package/Sources/RunicWidget/RunicWidgetViews.swift +680 -0
- package/Tests/RunicTests/AntigravityStatusProbeTests.swift +50 -0
- package/Tests/RunicTests/AppDelegateTests.swift +43 -0
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +22 -0
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +70 -0
- package/Tests/RunicTests/CLICostTests.swift +65 -0
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +22 -0
- package/Tests/RunicTests/CLISnapshotTests.swift +72 -0
- package/Tests/RunicTests/CLIWebFallbackTests.swift +58 -0
- package/Tests/RunicTests/ClaudeOAuthTests.swift +164 -0
- package/Tests/RunicTests/ClaudeResilienceTests.swift +30 -0
- package/Tests/RunicTests/ClaudeUsageTests.swift +370 -0
- package/Tests/RunicTests/CodexOAuthTests.swift +122 -0
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +82 -0
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +75 -0
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +75 -0
- package/Tests/RunicTests/CostUsageDecodingTests.swift +394 -0
- package/Tests/RunicTests/CostUsagePricingTests.swift +50 -0
- package/Tests/RunicTests/CostUsageScannerTests.swift +700 -0
- package/Tests/RunicTests/CursorStatusProbeTests.swift +323 -0
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +69 -0
- package/Tests/RunicTests/GeminiAPITestHelpers.swift +94 -0
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +28 -0
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +283 -0
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +279 -0
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +233 -0
- package/Tests/RunicTests/GeminiTestEnvironment.swift +87 -0
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +73 -0
- package/Tests/RunicTests/InstallOriginTests.swift +18 -0
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +9 -0
- package/Tests/RunicTests/LiveAccountTests.swift +24 -0
- package/Tests/RunicTests/LoadingPatternTests.swift +35 -0
- package/Tests/RunicTests/MenuCardModelTests.swift +545 -0
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +8 -0
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +8 -0
- package/Tests/RunicTests/MiniMaxParsingTests.swift +68 -0
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +8 -0
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +112 -0
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +17 -0
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +75 -0
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +27 -0
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +134 -0
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +53 -0
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +92 -0
- package/Tests/RunicTests/PathBuilderTests.swift +237 -0
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +160 -0
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +38 -0
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +16 -0
- package/Tests/RunicTests/ProviderRegistryTests.swift +29 -0
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +202 -0
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +51 -0
- package/Tests/RunicTests/RunicTests.swift +120 -0
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +36 -0
- package/Tests/RunicTests/SettingsStoreTests.swift +161 -0
- package/Tests/RunicTests/StatusItemAnimationTests.swift +49 -0
- package/Tests/RunicTests/StatusMenuTests.swift +434 -0
- package/Tests/RunicTests/StatusProbeTests.swift +377 -0
- package/Tests/RunicTests/SubprocessRunnerTests.swift +20 -0
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +65 -0
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +148 -0
- package/Tests/RunicTests/TTYIntegrationTests.swift +41 -0
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +159 -0
- package/Tests/RunicTests/TextParsingTests.swift +12 -0
- package/Tests/RunicTests/UsageFormatterTests.swift +112 -0
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +172 -0
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +213 -0
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +83 -0
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +170 -0
- package/Tests/RunicTests/UsagePaceTests.swift +77 -0
- package/Tests/RunicTests/UsagePaceTextTests.swift +109 -0
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +91 -0
- package/Tests/RunicTests/WidgetSnapshotTests.swift +45 -0
- package/Tests/RunicTests/ZaiProviderTests.swift +61 -0
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +6 -0
- package/TestsLinux/PlatformGatingTests.swift +46 -0
- package/appcast.xml +105 -0
- package/assets/logo-concept-stack.svg +24 -0
- package/assets/logo-minimal-bars.svg +15 -0
- package/assets/logo.svg +20 -0
- package/assets/logo2.svg +43 -0
- package/bin/docs-list +5 -0
- package/bin/install-runic-cli.sh +32 -0
- package/build-and-run.sh +369 -0
- package/docs/.nojekyll +1 -0
- package/docs/CNAME +1 -0
- package/docs/antigravity.md +75 -0
- package/docs/architecture.md +32 -0
- package/docs/claude.md +93 -0
- package/docs/cli-commands/integration_guide.md +371 -0
- package/docs/cli-commands/quick_reference.md +362 -0
- package/docs/cli-commands/readme.md +361 -0
- package/docs/cli.md +156 -0
- package/docs/cloudkit_sync_guide.md +495 -0
- package/docs/codebase_review.md +288 -0
- package/docs/codex-oauth.md +635 -0
- package/docs/codex.md +104 -0
- package/docs/codexbar.png +0 -0
- package/docs/copilot.md +44 -0
- package/docs/cursor.md +46 -0
- package/docs/design/designcomparisonchart.md +510 -0
- package/docs/design/implementationguide.md +792 -0
- package/docs/design/menubaricondesignconcepts.md +796 -0
- package/docs/design/mockups/hybridiconconcept.svg +62 -0
- package/docs/design/mockups/infinityiconconcept.svg +41 -0
- package/docs/design/mockups/infinityiconstates.svg +129 -0
- package/docs/design/mockups/wavelogorefined.svg +48 -0
- package/docs/design/quick_summary.md +426 -0
- package/docs/design/readme.md +478 -0
- package/docs/design/sizespecifications.md +575 -0
- package/docs/design-system.md +836 -0
- package/docs/factory.md +103 -0
- package/docs/gemini.md +73 -0
- package/docs/icon.md +37 -0
- package/docs/icon.png +0 -0
- package/docs/index.html +175 -0
- package/docs/keychain_fix.md +274 -0
- package/docs/minimax.md +71 -0
- package/docs/packaging.md +25 -0
- package/docs/progress-bar-improvements.md +240 -0
- package/docs/progress-bar-quick-reference.md +283 -0
- package/docs/progress-bar-technical-details.md +432 -0
- package/docs/progress-bar-visual-test.swift +375 -0
- package/docs/provider.md +202 -0
- package/docs/providers.md +86 -0
- package/docs/refactor/macros.md +35 -0
- package/docs/refresh-loop.md +22 -0
- package/docs/releasing-homebrew.md +35 -0
- package/docs/releasing.md +121 -0
- package/docs/research/concrete_todo_2026-02-22.md +47 -0
- package/docs/research/core-feature-research.md +248 -0
- package/docs/research/feature_uiux_roadmap_2026-02-22.md +202 -0
- package/docs/research/parity_matrix.md +49 -0
- package/docs/research/provider_expansion_candidates_2026-02-22.md +80 -0
- package/docs/research/provider_model_token_project_matrix_2026-02-23.md +46 -0
- package/docs/research/release_gate_checklist.md +57 -0
- package/docs/site.css +505 -0
- package/docs/sparkle.md +28 -0
- package/docs/status.md +28 -0
- package/docs/sync/implementation.md +393 -0
- package/docs/sync/readme.md +317 -0
- package/docs/sync_files_summary.md +391 -0
- package/docs/sync_quick_reference.md +225 -0
- package/docs/todo.md +11 -0
- package/docs/ui.md +29 -0
- package/docs/web-integration.md +9 -0
- package/docs/widgets.md +85 -0
- package/docs/workflows/error-handling-improvements.md +434 -0
- package/docs/workflows/folder_structure.md +299 -0
- package/docs/workflows/loading_states.md +446 -0
- package/docs/workflows/loading_states_examples.md +565 -0
- package/docs/workflows/loading_states_flow.md +430 -0
- package/docs/workflows/performance_optimizations.md +457 -0
- package/docs/workflows/phase1-cli-testing-report.md +313 -0
- package/docs/workflows/quick_reference.md +228 -0
- package/docs/workflows/readme.md +59 -0
- package/docs/workflows/recent_improvements.md +275 -0
- package/docs/workflows/sync_enablement_summary.md +400 -0
- package/docs/zai.md +43 -0
- package/package.json +36 -0
- package/runic.png +0 -0
- package/version.env +2 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
//
|
|
2
|
+
// TeamModels.swift
|
|
3
|
+
// RunicCore
|
|
4
|
+
//
|
|
5
|
+
// Created on 2026-01-31.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
// MARK: - Team
|
|
11
|
+
|
|
12
|
+
/// Core team entity representing a collaborative workspace
|
|
13
|
+
public struct Team: Codable, Sendable, Identifiable {
|
|
14
|
+
public let id: String // UUID
|
|
15
|
+
public var name: String
|
|
16
|
+
public let ownerUserID: String
|
|
17
|
+
public var totalQuota: Int // tokens/month
|
|
18
|
+
public var members: [TeamMembership]
|
|
19
|
+
public let createdAt: Date
|
|
20
|
+
public var updatedAt: Date
|
|
21
|
+
|
|
22
|
+
public init(
|
|
23
|
+
id: String = UUID().uuidString,
|
|
24
|
+
name: String,
|
|
25
|
+
ownerUserID: String,
|
|
26
|
+
totalQuota: Int,
|
|
27
|
+
members: [TeamMembership] = [],
|
|
28
|
+
createdAt: Date = Date(),
|
|
29
|
+
updatedAt: Date = Date()
|
|
30
|
+
) {
|
|
31
|
+
self.id = id
|
|
32
|
+
self.name = name
|
|
33
|
+
self.ownerUserID = ownerUserID
|
|
34
|
+
self.totalQuota = totalQuota
|
|
35
|
+
self.members = members
|
|
36
|
+
self.createdAt = createdAt
|
|
37
|
+
self.updatedAt = updatedAt
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
extension Team {
|
|
42
|
+
/// Total number of team members including owner
|
|
43
|
+
public var memberCount: Int {
|
|
44
|
+
return members.count
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Get all admin users (owner + admins)
|
|
48
|
+
public var adminUserIDs: [String] {
|
|
49
|
+
return members.filter { $0.role == .owner || $0.role == .admin }.map(\.userID)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Check if a user is a member of this team
|
|
53
|
+
public func hasMember(userID: String) -> Bool {
|
|
54
|
+
return members.contains { $0.userID == userID }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Get membership for a specific user
|
|
58
|
+
public func membership(for userID: String) -> TeamMembership? {
|
|
59
|
+
return members.first { $0.userID == userID }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Check if user has admin privileges
|
|
63
|
+
public func isAdmin(userID: String) -> Bool {
|
|
64
|
+
guard let membership = membership(for: userID) else { return false }
|
|
65
|
+
return membership.role == .owner || membership.role == .admin
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Calculate total quota allocated to members with individual limits
|
|
69
|
+
public var allocatedQuota: Int {
|
|
70
|
+
return members.compactMap(\.quotaLimit).reduce(0, +)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Remaining unallocated quota
|
|
74
|
+
public var unallocatedQuota: Int {
|
|
75
|
+
return max(0, totalQuota - allocatedQuota)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// MARK: - TeamMembership
|
|
80
|
+
|
|
81
|
+
/// Represents a user's role and permissions within a team
|
|
82
|
+
public struct TeamMembership: Codable, Sendable, Identifiable {
|
|
83
|
+
public let id: String
|
|
84
|
+
public let userID: String
|
|
85
|
+
public let teamID: String
|
|
86
|
+
public var role: TeamRole
|
|
87
|
+
public var quotaLimit: Int? // Individual member limit (nil = no limit)
|
|
88
|
+
public let joinedAt: Date
|
|
89
|
+
|
|
90
|
+
public init(
|
|
91
|
+
id: String = UUID().uuidString,
|
|
92
|
+
userID: String,
|
|
93
|
+
teamID: String,
|
|
94
|
+
role: TeamRole,
|
|
95
|
+
quotaLimit: Int? = nil,
|
|
96
|
+
joinedAt: Date = Date()
|
|
97
|
+
) {
|
|
98
|
+
self.id = id
|
|
99
|
+
self.userID = userID
|
|
100
|
+
self.teamID = teamID
|
|
101
|
+
self.role = role
|
|
102
|
+
self.quotaLimit = quotaLimit
|
|
103
|
+
self.joinedAt = joinedAt
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
extension TeamMembership {
|
|
108
|
+
/// Check if this member can manage other members
|
|
109
|
+
public var canManageMembers: Bool {
|
|
110
|
+
return role == .owner || role == .admin
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Check if this member can modify team settings
|
|
114
|
+
public var canModifyTeamSettings: Bool {
|
|
115
|
+
return role == .owner || role == .admin
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Check if this member can invite others
|
|
119
|
+
public var canInviteMembers: Bool {
|
|
120
|
+
return role == .owner || role == .admin
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Check if this member can view usage
|
|
124
|
+
public var canViewUsage: Bool {
|
|
125
|
+
return true // All members can view usage
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Check if this member can create projects
|
|
129
|
+
public var canCreateProjects: Bool {
|
|
130
|
+
return role != .viewer
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// MARK: - TeamRole
|
|
135
|
+
|
|
136
|
+
/// Defines permission levels within a team
|
|
137
|
+
public enum TeamRole: String, Codable, Sendable, CaseIterable {
|
|
138
|
+
case owner = "owner"
|
|
139
|
+
case admin = "admin"
|
|
140
|
+
case member = "member"
|
|
141
|
+
case viewer = "viewer"
|
|
142
|
+
|
|
143
|
+
/// Display name for the role
|
|
144
|
+
public var displayName: String {
|
|
145
|
+
switch self {
|
|
146
|
+
case .owner: return "Owner"
|
|
147
|
+
case .admin: return "Admin"
|
|
148
|
+
case .member: return "Member"
|
|
149
|
+
case .viewer: return "Viewer"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// Description of role permissions
|
|
154
|
+
public var description: String {
|
|
155
|
+
switch self {
|
|
156
|
+
case .owner:
|
|
157
|
+
return "Full control over team, billing, and members"
|
|
158
|
+
case .admin:
|
|
159
|
+
return "Can manage members and team settings"
|
|
160
|
+
case .member:
|
|
161
|
+
return "Can create and share projects within team"
|
|
162
|
+
case .viewer:
|
|
163
|
+
return "Read-only access to team projects"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// Permission level (higher = more permissions)
|
|
168
|
+
public var level: Int {
|
|
169
|
+
switch self {
|
|
170
|
+
case .owner: return 4
|
|
171
|
+
case .admin: return 3
|
|
172
|
+
case .member: return 2
|
|
173
|
+
case .viewer: return 1
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Check if this role can modify another role
|
|
178
|
+
public func canModify(_ other: TeamRole) -> Bool {
|
|
179
|
+
return self.level > other.level
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// MARK: - ProjectOwnership
|
|
184
|
+
|
|
185
|
+
/// Defines ownership and sharing settings for a project
|
|
186
|
+
public struct ProjectOwnership: Codable, Sendable, Identifiable {
|
|
187
|
+
public let id: String
|
|
188
|
+
public let projectID: String
|
|
189
|
+
public let teamID: String?
|
|
190
|
+
public let ownerUserID: String
|
|
191
|
+
public var sharedWith: [String] // User IDs
|
|
192
|
+
public var accessLevel: AccessLevel
|
|
193
|
+
public let createdAt: Date
|
|
194
|
+
|
|
195
|
+
public init(
|
|
196
|
+
id: String = UUID().uuidString,
|
|
197
|
+
projectID: String,
|
|
198
|
+
teamID: String? = nil,
|
|
199
|
+
ownerUserID: String,
|
|
200
|
+
sharedWith: [String] = [],
|
|
201
|
+
accessLevel: AccessLevel = .privateAccess,
|
|
202
|
+
createdAt: Date = Date()
|
|
203
|
+
) {
|
|
204
|
+
self.id = id
|
|
205
|
+
self.projectID = projectID
|
|
206
|
+
self.teamID = teamID
|
|
207
|
+
self.ownerUserID = ownerUserID
|
|
208
|
+
self.sharedWith = sharedWith
|
|
209
|
+
self.accessLevel = accessLevel
|
|
210
|
+
self.createdAt = createdAt
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
extension ProjectOwnership {
|
|
215
|
+
/// Check if a user has access to this project
|
|
216
|
+
public func hasAccess(userID: String) -> Bool {
|
|
217
|
+
// Owner always has access
|
|
218
|
+
if userID == ownerUserID {
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check access level
|
|
223
|
+
switch accessLevel {
|
|
224
|
+
case .privateAccess:
|
|
225
|
+
return sharedWith.contains(userID)
|
|
226
|
+
case .team:
|
|
227
|
+
// User must be in the team
|
|
228
|
+
return teamID != nil
|
|
229
|
+
case .public:
|
|
230
|
+
return true
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// Check if a user can edit this project
|
|
235
|
+
public func canEdit(userID: String) -> Bool {
|
|
236
|
+
return userID == ownerUserID || sharedWith.contains(userID)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Add a user to the shared list
|
|
240
|
+
public mutating func share(with userID: String) {
|
|
241
|
+
if !sharedWith.contains(userID) {
|
|
242
|
+
sharedWith.append(userID)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Remove a user from the shared list
|
|
247
|
+
public mutating func unshare(with userID: String) {
|
|
248
|
+
sharedWith.removeAll { $0 == userID }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// Check if project is owned by a team
|
|
252
|
+
public var isTeamOwned: Bool {
|
|
253
|
+
return teamID != nil
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// MARK: - AccessLevel
|
|
258
|
+
|
|
259
|
+
/// Defines who can access a project
|
|
260
|
+
public enum AccessLevel: String, Codable, Sendable, CaseIterable {
|
|
261
|
+
case privateAccess = "private" // Only owner and explicitly shared users
|
|
262
|
+
case team = "team" // All team members
|
|
263
|
+
case `public` = "public" // Anyone with link
|
|
264
|
+
|
|
265
|
+
/// Display name for the access level
|
|
266
|
+
public var displayName: String {
|
|
267
|
+
switch self {
|
|
268
|
+
case .privateAccess: return "Private"
|
|
269
|
+
case .team: return "Team"
|
|
270
|
+
case .public: return "Public"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// Icon representing the access level
|
|
275
|
+
public var icon: String {
|
|
276
|
+
switch self {
|
|
277
|
+
case .privateAccess: return "lock.fill"
|
|
278
|
+
case .team: return "person.2.fill"
|
|
279
|
+
case .public: return "globe"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Description of access level
|
|
284
|
+
public var description: String {
|
|
285
|
+
switch self {
|
|
286
|
+
case .privateAccess:
|
|
287
|
+
return "Only you and people you share with"
|
|
288
|
+
case .team:
|
|
289
|
+
return "All team members can access"
|
|
290
|
+
case .public:
|
|
291
|
+
return "Anyone with the link can view"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// MARK: - TeamUsageSummary
|
|
297
|
+
|
|
298
|
+
/// Aggregated usage statistics for a team
|
|
299
|
+
public struct TeamUsageSummary: Codable, Sendable {
|
|
300
|
+
public let teamID: String
|
|
301
|
+
public let period: DateInterval
|
|
302
|
+
public var totalTokens: Int
|
|
303
|
+
public var totalCost: Double
|
|
304
|
+
public var memberUsage: [String: MemberUsage] // UserID -> usage
|
|
305
|
+
public var quotaUsedPercent: Double
|
|
306
|
+
|
|
307
|
+
public init(
|
|
308
|
+
teamID: String,
|
|
309
|
+
period: DateInterval,
|
|
310
|
+
totalTokens: Int = 0,
|
|
311
|
+
totalCost: Double = 0.0,
|
|
312
|
+
memberUsage: [String: MemberUsage] = [:],
|
|
313
|
+
quotaUsedPercent: Double = 0.0
|
|
314
|
+
) {
|
|
315
|
+
self.teamID = teamID
|
|
316
|
+
self.period = period
|
|
317
|
+
self.totalTokens = totalTokens
|
|
318
|
+
self.totalCost = totalCost
|
|
319
|
+
self.memberUsage = memberUsage
|
|
320
|
+
self.quotaUsedPercent = quotaUsedPercent
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
extension TeamUsageSummary {
|
|
325
|
+
/// Get usage for a specific member
|
|
326
|
+
public func usage(for userID: String) -> MemberUsage? {
|
|
327
|
+
return memberUsage[userID]
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/// Top users by token consumption
|
|
331
|
+
public func topUsers(limit: Int = 5) -> [(userID: String, usage: MemberUsage)] {
|
|
332
|
+
return memberUsage
|
|
333
|
+
.sorted { $0.value.tokens > $1.value.tokens }
|
|
334
|
+
.prefix(limit)
|
|
335
|
+
.map { ($0.key, $0.value) }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// Average tokens per member
|
|
339
|
+
public var averageTokensPerMember: Int {
|
|
340
|
+
guard !memberUsage.isEmpty else { return 0 }
|
|
341
|
+
return totalTokens / memberUsage.count
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// Average cost per member
|
|
345
|
+
public var averageCostPerMember: Double {
|
|
346
|
+
guard !memberUsage.isEmpty else { return 0.0 }
|
|
347
|
+
return totalCost / Double(memberUsage.count)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/// Check if team is approaching quota limit
|
|
351
|
+
public var isApproachingQuota: Bool {
|
|
352
|
+
return quotaUsedPercent >= 80.0
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/// Check if team has exceeded quota
|
|
356
|
+
public var hasExceededQuota: Bool {
|
|
357
|
+
return quotaUsedPercent >= 100.0
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// Add or update member usage
|
|
361
|
+
public mutating func updateUsage(for userID: String, usage: MemberUsage) {
|
|
362
|
+
memberUsage[userID] = usage
|
|
363
|
+
recalculateTotals()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/// Recalculate total tokens and cost from member usage
|
|
367
|
+
private mutating func recalculateTotals() {
|
|
368
|
+
totalTokens = memberUsage.values.reduce(0) { $0 + $1.tokens }
|
|
369
|
+
totalCost = memberUsage.values.reduce(0.0) { $0 + $1.cost }
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// MARK: - MemberUsage
|
|
374
|
+
|
|
375
|
+
/// Usage statistics for an individual team member
|
|
376
|
+
public struct MemberUsage: Codable, Sendable {
|
|
377
|
+
public let userID: String
|
|
378
|
+
public var tokens: Int
|
|
379
|
+
public var cost: Double
|
|
380
|
+
public var requests: Int
|
|
381
|
+
|
|
382
|
+
public init(
|
|
383
|
+
userID: String,
|
|
384
|
+
tokens: Int = 0,
|
|
385
|
+
cost: Double = 0.0,
|
|
386
|
+
requests: Int = 0
|
|
387
|
+
) {
|
|
388
|
+
self.userID = userID
|
|
389
|
+
self.tokens = tokens
|
|
390
|
+
self.cost = cost
|
|
391
|
+
self.requests = requests
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
extension MemberUsage {
|
|
396
|
+
/// Average cost per request
|
|
397
|
+
public var averageCostPerRequest: Double {
|
|
398
|
+
guard requests > 0 else { return 0.0 }
|
|
399
|
+
return cost / Double(requests)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/// Average tokens per request
|
|
403
|
+
public var averageTokensPerRequest: Int {
|
|
404
|
+
guard requests > 0 else { return 0 }
|
|
405
|
+
return tokens / requests
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/// Check if member has quota limit and percentage used
|
|
409
|
+
public func quotaUsedPercent(limit: Int?) -> Double? {
|
|
410
|
+
guard let limit = limit, limit > 0 else { return nil }
|
|
411
|
+
return (Double(tokens) / Double(limit)) * 100.0
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/// Combine with another usage record
|
|
415
|
+
public mutating func merge(with other: MemberUsage) {
|
|
416
|
+
self.tokens += other.tokens
|
|
417
|
+
self.cost += other.cost
|
|
418
|
+
self.requests += other.requests
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// MARK: - TeamInvitation
|
|
423
|
+
|
|
424
|
+
/// Represents an invitation to join a team
|
|
425
|
+
public struct TeamInvitation: Codable, Sendable, Identifiable {
|
|
426
|
+
public let id: String
|
|
427
|
+
public let teamID: String
|
|
428
|
+
public let invitedEmail: String
|
|
429
|
+
public let invitedByUserID: String
|
|
430
|
+
public var role: TeamRole
|
|
431
|
+
public let expiresAt: Date
|
|
432
|
+
public var status: InvitationStatus
|
|
433
|
+
public let createdAt: Date
|
|
434
|
+
|
|
435
|
+
public init(
|
|
436
|
+
id: String = UUID().uuidString,
|
|
437
|
+
teamID: String,
|
|
438
|
+
invitedEmail: String,
|
|
439
|
+
invitedByUserID: String,
|
|
440
|
+
role: TeamRole = .member,
|
|
441
|
+
expiresAt: Date = Date().addingTimeInterval(7 * 24 * 60 * 60), // 7 days
|
|
442
|
+
status: InvitationStatus = .pending,
|
|
443
|
+
createdAt: Date = Date()
|
|
444
|
+
) {
|
|
445
|
+
self.id = id
|
|
446
|
+
self.teamID = teamID
|
|
447
|
+
self.invitedEmail = invitedEmail
|
|
448
|
+
self.invitedByUserID = invitedByUserID
|
|
449
|
+
self.role = role
|
|
450
|
+
self.expiresAt = expiresAt
|
|
451
|
+
self.status = status
|
|
452
|
+
self.createdAt = createdAt
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
extension TeamInvitation {
|
|
457
|
+
/// Check if invitation is still valid
|
|
458
|
+
public var isValid: Bool {
|
|
459
|
+
return status == .pending && !isExpired
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/// Check if invitation has expired
|
|
463
|
+
public var isExpired: Bool {
|
|
464
|
+
return Date() > expiresAt
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/// Days until expiration
|
|
468
|
+
public var daysUntilExpiration: Int {
|
|
469
|
+
let timeInterval = expiresAt.timeIntervalSince(Date())
|
|
470
|
+
return max(0, Int(timeInterval / (24 * 60 * 60)))
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/// Accept the invitation
|
|
474
|
+
public mutating func accept() {
|
|
475
|
+
guard isValid else { return }
|
|
476
|
+
status = .accepted
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/// Decline the invitation
|
|
480
|
+
public mutating func decline() {
|
|
481
|
+
guard isValid else { return }
|
|
482
|
+
status = .declined
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/// Mark as expired
|
|
486
|
+
public mutating func expire() {
|
|
487
|
+
if status == .pending {
|
|
488
|
+
status = .expired
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// MARK: - InvitationStatus
|
|
494
|
+
|
|
495
|
+
/// Status of a team invitation
|
|
496
|
+
public enum InvitationStatus: String, Codable, Sendable, CaseIterable {
|
|
497
|
+
case pending = "pending"
|
|
498
|
+
case accepted = "accepted"
|
|
499
|
+
case declined = "declined"
|
|
500
|
+
case expired = "expired"
|
|
501
|
+
|
|
502
|
+
/// Display name for the status
|
|
503
|
+
public var displayName: String {
|
|
504
|
+
switch self {
|
|
505
|
+
case .pending: return "Pending"
|
|
506
|
+
case .accepted: return "Accepted"
|
|
507
|
+
case .declined: return "Declined"
|
|
508
|
+
case .expired: return "Expired"
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/// Icon representing the status
|
|
513
|
+
public var icon: String {
|
|
514
|
+
switch self {
|
|
515
|
+
case .pending: return "clock.fill"
|
|
516
|
+
case .accepted: return "checkmark.circle.fill"
|
|
517
|
+
case .declined: return "xmark.circle.fill"
|
|
518
|
+
case .expired: return "hourglass.bottomhalf.filled"
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/// Color associated with status
|
|
523
|
+
public var colorName: String {
|
|
524
|
+
switch self {
|
|
525
|
+
case .pending: return "orange"
|
|
526
|
+
case .accepted: return "green"
|
|
527
|
+
case .declined: return "red"
|
|
528
|
+
case .expired: return "gray"
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// MARK: - TeamQuotaAllocation
|
|
534
|
+
|
|
535
|
+
/// Helper for managing quota allocation across team members
|
|
536
|
+
public struct TeamQuotaAllocation: Sendable {
|
|
537
|
+
public let team: Team
|
|
538
|
+
public let usage: TeamUsageSummary
|
|
539
|
+
|
|
540
|
+
public init(team: Team, usage: TeamUsageSummary) {
|
|
541
|
+
self.team = team
|
|
542
|
+
self.usage = usage
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/// Get remaining quota for a specific member
|
|
546
|
+
public func remainingQuota(for userID: String) -> Int? {
|
|
547
|
+
guard let membership = team.membership(for: userID),
|
|
548
|
+
let limit = membership.quotaLimit else {
|
|
549
|
+
return nil
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let used = usage.usage(for: userID)?.tokens ?? 0
|
|
553
|
+
return max(0, limit - used)
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/// Check if a member can use more tokens
|
|
557
|
+
public func canUseTokens(_ tokens: Int, userID: String) -> Bool {
|
|
558
|
+
guard let remaining = remainingQuota(for: userID) else {
|
|
559
|
+
// No individual limit, check team limit
|
|
560
|
+
return usage.totalTokens + tokens <= team.totalQuota
|
|
561
|
+
}
|
|
562
|
+
return remaining >= tokens
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/// Get members who have exceeded their quota
|
|
566
|
+
public func membersExceedingQuota() -> [TeamMembership] {
|
|
567
|
+
return team.members.filter { membership in
|
|
568
|
+
guard let limit = membership.quotaLimit else { return false }
|
|
569
|
+
let used = usage.usage(for: membership.userID)?.tokens ?? 0
|
|
570
|
+
return used > limit
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/// Get members approaching their quota (>80%)
|
|
575
|
+
public func membersApproachingQuota() -> [TeamMembership] {
|
|
576
|
+
return team.members.filter { membership in
|
|
577
|
+
guard let limit = membership.quotaLimit else { return false }
|
|
578
|
+
let used = usage.usage(for: membership.userID)?.tokens ?? 0
|
|
579
|
+
let percent = (Double(used) / Double(limit)) * 100.0
|
|
580
|
+
return percent >= 80.0 && percent < 100.0
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|