@sriinnu/runic 1.2.0 → 2.11.1
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 +267 -0
- package/LICENSE +373 -21
- package/README.md +98 -84
- package/THIRD-PARTY-NOTICES.md +651 -0
- package/package.json +14 -4
- package/.swiftformat +0 -50
- package/.swiftlint.yml +0 -160
- package/CONTRIBUTING.md +0 -456
- package/Icon.icns +0 -0
- package/Icon.icon/Assets/runic.png +0 -0
- package/Icon.icon/icon.json +0 -36
- package/Package.resolved +0 -51
- package/Package.swift +0 -136
- package/Packages/Helix/LICENSE +0 -21
- package/Packages/Helix/Package.swift +0 -29
- package/Packages/Helix/README.md +0 -976
- package/Packages/Helix/Sources/Helix/Binding.swift +0 -111
- package/Packages/Helix/Sources/Helix/CommandParser.swift +0 -160
- package/Packages/Helix/Sources/Helix/CommandSignature.swift +0 -117
- package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +0 -72
- package/Packages/Helix/Sources/Helix/HelpGenerator.swift +0 -175
- package/Packages/Helix/Sources/Helix/NameSpecification.swift +0 -98
- package/Packages/Helix/Sources/Helix/ParsableCommand.swift +0 -204
- package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +0 -44
- package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +0 -34
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +0 -91
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +0 -94
- package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +0 -167
- package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +0 -99
- package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +0 -146
- package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +0 -97
- package/Packages/Helix/Sources/Helix/Program.swift +0 -204
- package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +0 -367
- package/Packages/Silo/LICENSE +0 -21
- package/Packages/Silo/Package.swift +0 -34
- package/Packages/Silo/README.md +0 -466
- package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +0 -169
- package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +0 -365
- package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +0 -594
- package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +0 -326
- package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +0 -154
- package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +0 -119
- package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +0 -121
- package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +0 -391
- package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +0 -240
- package/Packages/Silo/Sources/Silo/CryptoSupport.swift +0 -86
- package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +0 -172
- package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +0 -172
- package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +0 -105
- package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +0 -191
- package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +0 -41
- package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +0 -110
- package/RuniciOS/App/RuniciOSApp.swift +0 -105
- package/RuniciOS/Core/iOSUsageStore.swift +0 -192
- package/RuniciOS/Views/ProviderDetailView.swift +0 -312
- package/RuniciOS/Views/ProviderListView.swift +0 -228
- package/RuniciOS/Widgets/LargeWidget.swift +0 -367
- package/RuniciOS/Widgets/LiveActivity.swift +0 -342
- package/RuniciOS/Widgets/LockScreenWidget.swift +0 -273
- package/RuniciOS/Widgets/MediumWidget.swift +0 -370
- package/RuniciOS/Widgets/RunicWidget.swift +0 -274
- package/RuniciOS/Widgets/SmallWidget.swift +0 -281
- package/RuniciOS/Widgets/WidgetModels.swift +0 -285
- package/RuniciOS/Widgets/WidgetTimeline.swift +0 -270
- package/Scripts/CREATION-SUMMARY.md +0 -367
- package/Scripts/DIRECTORY-STRUCTURE.md +0 -358
- package/Scripts/GETTING-STARTED-CHECKLIST.md +0 -364
- package/Scripts/INDEX.md +0 -365
- package/Scripts/QUICK-REFERENCE.md +0 -164
- package/Scripts/README-SCRIPTS.md +0 -926
- package/Scripts/build-android.sh +0 -526
- package/Scripts/build-ios.sh +0 -529
- package/Scripts/build-macos.sh +0 -603
- package/Scripts/build-windows.sh +0 -504
- package/Scripts/build_icon.sh +0 -49
- package/Scripts/changelog-to-html.sh +0 -89
- package/Scripts/check-release-assets.sh +0 -10
- package/Scripts/ci-config.yml +0 -474
- package/Scripts/compile_and_run.sh +0 -219
- package/Scripts/compile_and_run_adhoc.sh +0 -4
- package/Scripts/docs-list.mjs +0 -119
- package/Scripts/make_appcast.sh +0 -58
- package/Scripts/package_app.sh +0 -409
- package/Scripts/release-lib.sh +0 -214
- package/Scripts/release.sh +0 -76
- package/Scripts/setup-all.sh +0 -387
- package/Scripts/setup-ios.sh +0 -404
- package/Scripts/setup-react-native.sh +0 -523
- package/Scripts/sign-and-notarize.sh +0 -122
- package/Scripts/test_live_update.sh +0 -38
- package/Scripts/validate_changelog.sh +0 -23
- package/Scripts/verify_appcast.sh +0 -92
- package/Sources/Runic/About.swift +0 -93
- package/Sources/Runic/AppNotifications.swift +0 -115
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +0 -666
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +0 -518
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +0 -810
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +0 -87
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +0 -1407
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +0 -337
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +0 -395
- package/Sources/Runic/Controllers/StatusItemController.swift +0 -360
- package/Sources/Runic/Core/BudgetNotificationManager.swift +0 -80
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +0 -103
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +0 -154
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +0 -206
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +0 -92
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +0 -1216
- package/Sources/Runic/Core/Rendering/IconView.swift +0 -120
- package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +0 -71
- package/Sources/Runic/Core/RunicAnimation.swift +0 -53
- package/Sources/Runic/Core/RunicColors.swift +0 -73
- package/Sources/Runic/Core/RunicCornerRadius.swift +0 -15
- package/Sources/Runic/Core/RunicSpacing.swift +0 -68
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +0 -158
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +0 -30
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/SettingsStore.swift +0 -1711
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +0 -20
- package/Sources/Runic/Core/Stores/UsageStore+Status.swift +0 -189
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +0 -33
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +0 -67
- package/Sources/Runic/Core/Stores/UsageStore.swift +0 -3605
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +0 -119
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +0 -118
- package/Sources/Runic/Core/UsageExporter.swift +0 -193
- package/Sources/Runic/Date+RelativeDescription.swift +0 -21
- package/Sources/Runic/HiddenWindowView.swift +0 -32
- package/Sources/Runic/InstallOrigin.swift +0 -9
- package/Sources/Runic/KeyboardShortcuts+Names.swift +0 -6
- package/Sources/Runic/MouseLocationReader.swift +0 -65
- package/Sources/Runic/Notifications+Runic.swift +0 -6
- package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +0 -503
- package/Sources/Runic/ProviderRegistry.swift +0 -100
- package/Sources/Runic/ProviderSwitcherButtons.swift +0 -267
- package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +0 -11
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +0 -14
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +0 -52
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +0 -42
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +0 -26
- package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +0 -14
- package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +0 -18
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +0 -193
- package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +0 -110
- package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +0 -28
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +0 -12
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +0 -20
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +0 -15
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +0 -32
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +0 -15
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +0 -48
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +0 -66
- package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +0 -12
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +0 -99
- package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +0 -18
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +0 -24
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +0 -24
- package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +0 -2
- package/Sources/Runic/Resources/ProviderIcon-auggie.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-azure.svg +0 -5
- package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +0 -7
- package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-claude.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-codex.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-cohere.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-copilot.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-cursor.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-factory.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-gemini.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-groq.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-kimi.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-minimax.svg +0 -1
- package/Sources/Runic/Resources/ProviderIcon-mistral.svg +0 -5
- package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +0 -7
- package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-qwen.svg +0 -9
- package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +0 -4
- package/Sources/Runic/Resources/ProviderIcon-together.svg +0 -5
- package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +0 -17
- package/Sources/Runic/Resources/ProviderIcon-xai.svg +0 -3
- package/Sources/Runic/Resources/ProviderIcon-zai.svg +0 -5
- package/Sources/Runic/Resources/RunicMenubarIcon.svg +0 -7
- package/Sources/Runic/Resources/provider-context-windows.json +0 -20
- package/Sources/Runic/RunicApp.swift +0 -357
- package/Sources/Runic/SessionQuotaNotifications.swift +0 -58
- package/Sources/Runic/UsagePaceText.swift +0 -52
- package/Sources/Runic/UsageProgressBar.swift +0 -224
- package/Sources/Runic/Utilities/DisplayLink.swift +0 -96
- package/Sources/Runic/Utilities/LoadingPattern.swift +0 -60
- package/Sources/Runic/Utilities/PerformanceConstants.swift +0 -85
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +0 -256
- package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +0 -22
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +0 -275
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +0 -377
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +0 -312
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +0 -312
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +0 -228
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +0 -228
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +0 -155
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +0 -64
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +0 -268
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +0 -104
- package/Sources/Runic/Views/Menu/MenuCardView.swift +0 -1900
- package/Sources/Runic/Views/Menu/MenuContent.swift +0 -150
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +0 -369
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +0 -42
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +0 -235
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +0 -278
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +0 -209
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +0 -220
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +0 -88
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +0 -201
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +0 -398
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +0 -232
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +0 -364
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +0 -190
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +0 -132
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +0 -618
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +0 -305
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +0 -271
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +0 -268
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +0 -422
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +0 -641
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +0 -344
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +0 -230
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +0 -322
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +0 -469
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +0 -231
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +0 -50
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +0 -567
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +0 -288
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +0 -3435
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +0 -25
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +0 -59
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +0 -206
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +0 -238
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +0 -371
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +0 -175
- package/Sources/Runic/Views/Team/TeamManagementView.swift +0 -761
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +0 -181
- package/Sources/RunicCLI/CLIEntry.swift +0 -942
- package/Sources/RunicCLI/CLIRenderer.swift +0 -87
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +0 -588
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +0 -362
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +0 -439
- package/Sources/RunicCLI/Commands/ResetCommand.swift +0 -429
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +0 -168
- package/Sources/RunicClaudeWatchdog/main.swift +0 -122
- package/Sources/RunicClaudeWebProbe/main.swift +0 -62
- package/Sources/RunicCore/AlertRuleStore.swift +0 -187
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +0 -25
- package/Sources/RunicCore/CopilotUsageModels.swift +0 -84
- package/Sources/RunicCore/CostUsageFetcher.swift +0 -119
- package/Sources/RunicCore/CostUsageModels.swift +0 -479
- package/Sources/RunicCore/CreditsModels.swift +0 -50
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +0 -269
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +0 -633
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +0 -500
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +0 -679
- package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +0 -135
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +0 -173
- package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +0 -79
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +0 -86
- package/Sources/RunicCore/Logging/RunicLog.swift +0 -123
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +0 -371
- package/Sources/RunicCore/OpenAIDashboardModels.swift +0 -210
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +0 -676
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +0 -492
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +0 -51
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +0 -483
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +0 -558
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +0 -204
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +0 -78
- package/Sources/RunicCore/PathEnvironment.swift +0 -482
- package/Sources/RunicCore/Performance/PerformanceModels.swift +0 -371
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +0 -409
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +0 -178
- package/Sources/RunicCore/ProjectBudgetStore.swift +0 -99
- package/Sources/RunicCore/ProviderCostSnapshot.swift +0 -29
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +0 -66
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +0 -685
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +0 -142
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +0 -141
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +0 -188
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +0 -126
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +0 -168
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +0 -332
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +0 -180
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +0 -137
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +0 -207
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +0 -654
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +0 -28
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +0 -480
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +0 -780
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +0 -147
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +0 -280
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +0 -100
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +0 -243
- package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +0 -125
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +0 -24
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +0 -181
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +0 -85
- package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +0 -123
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +0 -114
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +0 -100
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +0 -291
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +0 -386
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +0 -65
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +0 -595
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +0 -85
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +0 -108
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +0 -398
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +0 -65
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +0 -1276
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +0 -65
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +0 -836
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +0 -71
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +0 -77
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +0 -92
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +0 -54
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +0 -168
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +0 -911
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +0 -87
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +0 -203
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/ProviderBranding.swift +0 -25
- package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +0 -17
- package/Sources/RunicCore/Providers/ProviderDescriptor.swift +0 -129
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +0 -263
- package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +0 -98
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +0 -501
- package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +0 -68
- package/Sources/RunicCore/Providers/Providers.swift +0 -184
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +0 -30
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +0 -315
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +0 -84
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +0 -123
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +0 -48
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +0 -144
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +0 -83
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +0 -75
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +0 -72
- package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +0 -41
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +0 -718
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +0 -385
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +0 -310
- package/Sources/RunicCore/Sync/SyncProtocol.swift +0 -288
- package/Sources/RunicCore/Sync/SyncRecord.swift +0 -405
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +0 -294
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +0 -775
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +0 -333
- package/Sources/RunicCore/Team/TeamModels.swift +0 -583
- package/Sources/RunicCore/Team/TeamShowbackExporter.swift +0 -495
- package/Sources/RunicCore/Team/TeamStore.swift +0 -792
- package/Sources/RunicCore/TextParsing.swift +0 -55
- package/Sources/RunicCore/UsageFetcher.swift +0 -694
- package/Sources/RunicCore/UsageFormatter.swift +0 -410
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +0 -427
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +0 -494
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +0 -158
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +0 -550
- package/Sources/RunicCore/UsageLedger/UsageLedger.swift +0 -29
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +0 -660
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +0 -209
- package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +0 -417
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +0 -245
- package/Sources/RunicCore/UsagePace.swift +0 -81
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +0 -73
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +0 -74
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +0 -208
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +0 -455
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +0 -132
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +0 -589
- package/Sources/RunicCore/WidgetSnapshot.swift +0 -183
- package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +0 -14
- package/Sources/RunicMacros/ProviderRegistrationMacros.swift +0 -216
- package/Sources/RunicWidget/RunicWidgetBundle.swift +0 -79
- package/Sources/RunicWidget/RunicWidgetProvider.swift +0 -283
- package/Sources/RunicWidget/RunicWidgetViews.swift +0 -680
- package/Tests/RunicTests/AntigravityStatusProbeTests.swift +0 -50
- package/Tests/RunicTests/AppDelegateTests.swift +0 -43
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +0 -22
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +0 -70
- package/Tests/RunicTests/CLICostTests.swift +0 -65
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +0 -22
- package/Tests/RunicTests/CLISnapshotTests.swift +0 -72
- package/Tests/RunicTests/CLIWebFallbackTests.swift +0 -58
- package/Tests/RunicTests/ClaudeOAuthTests.swift +0 -164
- package/Tests/RunicTests/ClaudeResilienceTests.swift +0 -30
- package/Tests/RunicTests/ClaudeUsageTests.swift +0 -370
- package/Tests/RunicTests/CodexOAuthTests.swift +0 -122
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +0 -82
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +0 -75
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +0 -75
- package/Tests/RunicTests/CostUsageDecodingTests.swift +0 -394
- package/Tests/RunicTests/CostUsagePricingTests.swift +0 -50
- package/Tests/RunicTests/CostUsageScannerTests.swift +0 -700
- package/Tests/RunicTests/CursorStatusProbeTests.swift +0 -323
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +0 -69
- package/Tests/RunicTests/GeminiAPITestHelpers.swift +0 -94
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +0 -28
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +0 -283
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +0 -279
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +0 -233
- package/Tests/RunicTests/GeminiTestEnvironment.swift +0 -87
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +0 -73
- package/Tests/RunicTests/InstallOriginTests.swift +0 -18
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +0 -9
- package/Tests/RunicTests/LiveAccountTests.swift +0 -24
- package/Tests/RunicTests/LoadingPatternTests.swift +0 -35
- package/Tests/RunicTests/MenuCardModelTests.swift +0 -545
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +0 -8
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +0 -8
- package/Tests/RunicTests/MiniMaxParsingTests.swift +0 -68
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +0 -8
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +0 -112
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +0 -17
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +0 -75
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +0 -27
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +0 -134
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +0 -53
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +0 -92
- package/Tests/RunicTests/PathBuilderTests.swift +0 -237
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +0 -160
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +0 -38
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +0 -16
- package/Tests/RunicTests/ProviderRegistryTests.swift +0 -29
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +0 -202
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +0 -51
- package/Tests/RunicTests/RunicTests.swift +0 -120
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +0 -36
- package/Tests/RunicTests/SettingsStoreTests.swift +0 -161
- package/Tests/RunicTests/StatusItemAnimationTests.swift +0 -49
- package/Tests/RunicTests/StatusMenuTests.swift +0 -434
- package/Tests/RunicTests/StatusProbeTests.swift +0 -377
- package/Tests/RunicTests/SubprocessRunnerTests.swift +0 -20
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +0 -65
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +0 -148
- package/Tests/RunicTests/TTYIntegrationTests.swift +0 -41
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +0 -159
- package/Tests/RunicTests/TextParsingTests.swift +0 -12
- package/Tests/RunicTests/UsageFormatterTests.swift +0 -112
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +0 -172
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +0 -213
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +0 -83
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +0 -170
- package/Tests/RunicTests/UsagePaceTests.swift +0 -77
- package/Tests/RunicTests/UsagePaceTextTests.swift +0 -109
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +0 -91
- package/Tests/RunicTests/WidgetSnapshotTests.swift +0 -45
- package/Tests/RunicTests/ZaiProviderTests.swift +0 -61
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +0 -6
- package/TestsLinux/PlatformGatingTests.swift +0 -46
- package/appcast.xml +0 -144
- package/assets/logo-concept-stack.svg +0 -24
- package/assets/logo-minimal-bars.svg +0 -15
- package/assets/logo.svg +0 -20
- package/assets/logo2.svg +0 -43
- package/bin/docs-list +0 -5
- package/build-and-run.sh +0 -369
- package/docs/.nojekyll +0 -1
- package/docs/CNAME +0 -1
- package/docs/antigravity.md +0 -75
- package/docs/architecture.md +0 -32
- package/docs/claude.md +0 -93
- package/docs/cli-commands/integration_guide.md +0 -371
- package/docs/cli-commands/quick_reference.md +0 -362
- package/docs/cli-commands/readme.md +0 -361
- package/docs/cli.md +0 -156
- package/docs/cloudkit_sync_guide.md +0 -495
- package/docs/codebase_review.md +0 -288
- package/docs/codex-oauth.md +0 -635
- package/docs/codex.md +0 -104
- package/docs/codexbar.png +0 -0
- package/docs/copilot.md +0 -44
- package/docs/cursor.md +0 -46
- package/docs/design/designcomparisonchart.md +0 -510
- package/docs/design/implementationguide.md +0 -792
- package/docs/design/menubaricondesignconcepts.md +0 -796
- package/docs/design/mockups/hybridiconconcept.svg +0 -62
- package/docs/design/mockups/infinityiconconcept.svg +0 -41
- package/docs/design/mockups/infinityiconstates.svg +0 -129
- package/docs/design/mockups/wavelogorefined.svg +0 -48
- package/docs/design/quick_summary.md +0 -426
- package/docs/design/readme.md +0 -478
- package/docs/design/sizespecifications.md +0 -575
- package/docs/design-system.md +0 -836
- package/docs/factory.md +0 -103
- package/docs/gemini.md +0 -73
- package/docs/icon.md +0 -37
- package/docs/icon.png +0 -0
- package/docs/index.html +0 -175
- package/docs/keychain_fix.md +0 -274
- package/docs/minimax.md +0 -71
- package/docs/packaging.md +0 -25
- package/docs/progress-bar-improvements.md +0 -240
- package/docs/progress-bar-quick-reference.md +0 -283
- package/docs/progress-bar-technical-details.md +0 -432
- package/docs/progress-bar-visual-test.swift +0 -375
- package/docs/provider.md +0 -202
- package/docs/providers.md +0 -86
- package/docs/refactor/macros.md +0 -35
- package/docs/refresh-loop.md +0 -22
- package/docs/releasing-homebrew.md +0 -35
- package/docs/releasing.md +0 -121
- package/docs/research/concrete_todo_2026-02-22.md +0 -47
- package/docs/research/core-feature-research.md +0 -248
- package/docs/research/feature_uiux_roadmap_2026-02-22.md +0 -202
- package/docs/research/parity_matrix.md +0 -49
- package/docs/research/provider_expansion_candidates_2026-02-22.md +0 -80
- package/docs/research/provider_model_token_project_matrix_2026-02-23.md +0 -46
- package/docs/research/release_gate_checklist.md +0 -57
- package/docs/site.css +0 -505
- package/docs/sparkle.md +0 -28
- package/docs/status.md +0 -28
- package/docs/sync/implementation.md +0 -393
- package/docs/sync/readme.md +0 -317
- package/docs/sync_files_summary.md +0 -391
- package/docs/sync_quick_reference.md +0 -225
- package/docs/todo.md +0 -11
- package/docs/ui.md +0 -29
- package/docs/web-integration.md +0 -9
- package/docs/widgets.md +0 -85
- package/docs/workflows/error-handling-improvements.md +0 -434
- package/docs/workflows/folder_structure.md +0 -299
- package/docs/workflows/loading_states.md +0 -446
- package/docs/workflows/loading_states_examples.md +0 -565
- package/docs/workflows/loading_states_flow.md +0 -430
- package/docs/workflows/performance_optimizations.md +0 -457
- package/docs/workflows/phase1-cli-testing-report.md +0 -313
- package/docs/workflows/quick_reference.md +0 -228
- package/docs/workflows/readme.md +0 -59
- package/docs/workflows/recent_improvements.md +0 -275
- package/docs/workflows/sync_enablement_summary.md +0 -400
- package/docs/zai.md +0 -43
- package/runic.png +0 -0
- package/version.env +0 -2
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import SQLite3
|
|
3
|
-
|
|
4
|
-
/// Storage layer for performance metrics using SQLite
|
|
5
|
-
public final class PerformanceStorageImpl {
|
|
6
|
-
|
|
7
|
-
// MARK: - Properties
|
|
8
|
-
|
|
9
|
-
private var db: OpaquePointer?
|
|
10
|
-
private let dbPath: URL
|
|
11
|
-
|
|
12
|
-
// MARK: - Initialization
|
|
13
|
-
|
|
14
|
-
public init() {
|
|
15
|
-
let appSupport = FileManager.default.urls(
|
|
16
|
-
for: .applicationSupportDirectory,
|
|
17
|
-
in: .userDomainMask
|
|
18
|
-
).first!
|
|
19
|
-
let runicDir = appSupport.appendingPathComponent("Runic")
|
|
20
|
-
|
|
21
|
-
// Ensure directory exists
|
|
22
|
-
try? FileManager.default.createDirectory(at: runicDir, withIntermediateDirectories: true)
|
|
23
|
-
|
|
24
|
-
self.dbPath = runicDir.appendingPathComponent("performance.db")
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
deinit {
|
|
28
|
-
if let db = db {
|
|
29
|
-
sqlite3_close(db)
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// MARK: - Database Connection
|
|
34
|
-
|
|
35
|
-
private func open() throws {
|
|
36
|
-
guard db == nil else { return }
|
|
37
|
-
|
|
38
|
-
let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
|
|
39
|
-
let result = sqlite3_open_v2(dbPath.path, &db, flags, nil)
|
|
40
|
-
|
|
41
|
-
guard result == SQLITE_OK else {
|
|
42
|
-
throw StorageError.openFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
try createTablesIfNeeded()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private func close() {
|
|
49
|
-
if let db = db {
|
|
50
|
-
sqlite3_close(db)
|
|
51
|
-
self.db = nil
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// MARK: - Schema Creation
|
|
56
|
-
|
|
57
|
-
private func createTablesIfNeeded() throws {
|
|
58
|
-
let schemas = [
|
|
59
|
-
"""
|
|
60
|
-
CREATE TABLE IF NOT EXISTS latency_metrics (
|
|
61
|
-
id TEXT PRIMARY KEY,
|
|
62
|
-
request_id TEXT NOT NULL,
|
|
63
|
-
provider TEXT NOT NULL,
|
|
64
|
-
model TEXT,
|
|
65
|
-
start_time REAL NOT NULL,
|
|
66
|
-
end_time REAL NOT NULL,
|
|
67
|
-
duration_ms INTEGER NOT NULL,
|
|
68
|
-
success INTEGER NOT NULL,
|
|
69
|
-
created_at REAL NOT NULL
|
|
70
|
-
)
|
|
71
|
-
""",
|
|
72
|
-
"""
|
|
73
|
-
CREATE TABLE IF NOT EXISTS quality_ratings (
|
|
74
|
-
id TEXT PRIMARY KEY,
|
|
75
|
-
request_id TEXT NOT NULL,
|
|
76
|
-
provider TEXT NOT NULL,
|
|
77
|
-
model TEXT,
|
|
78
|
-
rating INTEGER NOT NULL,
|
|
79
|
-
comment TEXT,
|
|
80
|
-
timestamp REAL NOT NULL
|
|
81
|
-
)
|
|
82
|
-
""",
|
|
83
|
-
"""
|
|
84
|
-
CREATE TABLE IF NOT EXISTS error_events (
|
|
85
|
-
id TEXT PRIMARY KEY,
|
|
86
|
-
provider TEXT NOT NULL,
|
|
87
|
-
error_type TEXT NOT NULL,
|
|
88
|
-
error_message TEXT,
|
|
89
|
-
retry_count INTEGER NOT NULL,
|
|
90
|
-
timestamp REAL NOT NULL
|
|
91
|
-
)
|
|
92
|
-
""",
|
|
93
|
-
"""
|
|
94
|
-
CREATE TABLE IF NOT EXISTS daily_stats (
|
|
95
|
-
provider TEXT NOT NULL,
|
|
96
|
-
model TEXT,
|
|
97
|
-
date TEXT NOT NULL,
|
|
98
|
-
total_requests INTEGER NOT NULL,
|
|
99
|
-
avg_latency_ms INTEGER NOT NULL,
|
|
100
|
-
p50_latency_ms INTEGER NOT NULL,
|
|
101
|
-
p95_latency_ms INTEGER NOT NULL,
|
|
102
|
-
p99_latency_ms INTEGER NOT NULL,
|
|
103
|
-
avg_quality_rating REAL NOT NULL,
|
|
104
|
-
total_ratings INTEGER NOT NULL,
|
|
105
|
-
rating_1_count INTEGER NOT NULL,
|
|
106
|
-
rating_2_count INTEGER NOT NULL,
|
|
107
|
-
rating_3_count INTEGER NOT NULL,
|
|
108
|
-
rating_4_count INTEGER NOT NULL,
|
|
109
|
-
rating_5_count INTEGER NOT NULL,
|
|
110
|
-
error_count INTEGER NOT NULL,
|
|
111
|
-
error_rate REAL NOT NULL,
|
|
112
|
-
timeout_count INTEGER NOT NULL,
|
|
113
|
-
quota_count INTEGER NOT NULL,
|
|
114
|
-
network_count INTEGER NOT NULL,
|
|
115
|
-
api_error_count INTEGER NOT NULL,
|
|
116
|
-
created_at REAL NOT NULL,
|
|
117
|
-
PRIMARY KEY (provider, model, date)
|
|
118
|
-
)
|
|
119
|
-
""",
|
|
120
|
-
"CREATE INDEX IF NOT EXISTS idx_latency_created ON latency_metrics(created_at)",
|
|
121
|
-
"CREATE INDEX IF NOT EXISTS idx_quality_timestamp ON quality_ratings(timestamp)",
|
|
122
|
-
"CREATE INDEX IF NOT EXISTS idx_error_timestamp ON error_events(timestamp)",
|
|
123
|
-
"CREATE INDEX IF NOT EXISTS idx_daily_stats_date ON daily_stats(date)"
|
|
124
|
-
]
|
|
125
|
-
|
|
126
|
-
for schema in schemas {
|
|
127
|
-
try execute(schema)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// MARK: - Execute SQL
|
|
132
|
-
|
|
133
|
-
private func execute(_ sql: String) throws {
|
|
134
|
-
guard let db = db else {
|
|
135
|
-
throw StorageError.notOpen
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
var errorMessage: UnsafeMutablePointer<CChar>?
|
|
139
|
-
let result = sqlite3_exec(db, sql, nil, nil, &errorMessage)
|
|
140
|
-
|
|
141
|
-
if result != SQLITE_OK {
|
|
142
|
-
let message = errorMessage.map { String(cString: $0) } ?? "Unknown error"
|
|
143
|
-
sqlite3_free(errorMessage)
|
|
144
|
-
throw StorageError.executionFailed(message: message)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// MARK: - Save Metrics
|
|
149
|
-
|
|
150
|
-
public func save(latency: LatencyMetric) async throws {
|
|
151
|
-
try open()
|
|
152
|
-
|
|
153
|
-
let sql = """
|
|
154
|
-
INSERT OR REPLACE INTO latency_metrics
|
|
155
|
-
(id, request_id, provider, model, start_time, end_time, duration_ms, success, created_at)
|
|
156
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
157
|
-
"""
|
|
158
|
-
|
|
159
|
-
var statement: OpaquePointer?
|
|
160
|
-
defer { sqlite3_finalize(statement) }
|
|
161
|
-
|
|
162
|
-
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
|
|
163
|
-
throw StorageError.prepareFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
sqlite3_bind_text(statement, 1, latency.id, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
167
|
-
sqlite3_bind_text(statement, 2, latency.requestID, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
168
|
-
sqlite3_bind_text(statement, 3, latency.provider.rawValue, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
169
|
-
|
|
170
|
-
if let model = latency.model {
|
|
171
|
-
sqlite3_bind_text(statement, 4, model, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
172
|
-
} else {
|
|
173
|
-
sqlite3_bind_null(statement, 4)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
sqlite3_bind_double(statement, 5, latency.startTime.timeIntervalSince1970)
|
|
177
|
-
sqlite3_bind_double(statement, 6, latency.endTime.timeIntervalSince1970)
|
|
178
|
-
sqlite3_bind_int(statement, 7, Int32(latency.durationMs))
|
|
179
|
-
sqlite3_bind_int(statement, 8, latency.success ? 1 : 0)
|
|
180
|
-
sqlite3_bind_double(statement, 9, latency.createdAt.timeIntervalSince1970)
|
|
181
|
-
|
|
182
|
-
guard sqlite3_step(statement) == SQLITE_DONE else {
|
|
183
|
-
throw StorageError.insertFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
public func save(rating: QualityRating) async throws {
|
|
188
|
-
try open()
|
|
189
|
-
|
|
190
|
-
let sql = """
|
|
191
|
-
INSERT OR REPLACE INTO quality_ratings
|
|
192
|
-
(id, request_id, provider, model, rating, comment, timestamp)
|
|
193
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
194
|
-
"""
|
|
195
|
-
|
|
196
|
-
var statement: OpaquePointer?
|
|
197
|
-
defer { sqlite3_finalize(statement) }
|
|
198
|
-
|
|
199
|
-
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
|
|
200
|
-
throw StorageError.prepareFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
sqlite3_bind_text(statement, 1, rating.id, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
204
|
-
sqlite3_bind_text(statement, 2, rating.requestID, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
205
|
-
sqlite3_bind_text(statement, 3, rating.provider.rawValue, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
206
|
-
|
|
207
|
-
if let model = rating.model {
|
|
208
|
-
sqlite3_bind_text(statement, 4, model, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
209
|
-
} else {
|
|
210
|
-
sqlite3_bind_null(statement, 4)
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
sqlite3_bind_int(statement, 5, Int32(rating.rating))
|
|
214
|
-
|
|
215
|
-
if let comment = rating.comment {
|
|
216
|
-
sqlite3_bind_text(statement, 6, comment, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
217
|
-
} else {
|
|
218
|
-
sqlite3_bind_null(statement, 6)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
sqlite3_bind_double(statement, 7, rating.timestamp.timeIntervalSince1970)
|
|
222
|
-
|
|
223
|
-
guard sqlite3_step(statement) == SQLITE_DONE else {
|
|
224
|
-
throw StorageError.insertFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
public func save(error: ErrorEvent) async throws {
|
|
229
|
-
try open()
|
|
230
|
-
|
|
231
|
-
let sql = """
|
|
232
|
-
INSERT OR REPLACE INTO error_events
|
|
233
|
-
(id, provider, error_type, error_message, retry_count, timestamp)
|
|
234
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
235
|
-
"""
|
|
236
|
-
|
|
237
|
-
var statement: OpaquePointer?
|
|
238
|
-
defer { sqlite3_finalize(statement) }
|
|
239
|
-
|
|
240
|
-
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
|
|
241
|
-
throw StorageError.prepareFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
sqlite3_bind_text(statement, 1, error.id, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
245
|
-
sqlite3_bind_text(statement, 2, error.provider.rawValue, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
246
|
-
sqlite3_bind_text(statement, 3, error.errorType.rawValue, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
247
|
-
|
|
248
|
-
if let message = error.errorMessage {
|
|
249
|
-
sqlite3_bind_text(statement, 4, message, -1, unsafeBitCast(-1, to: sqlite3_destructor_type.self))
|
|
250
|
-
} else {
|
|
251
|
-
sqlite3_bind_null(statement, 4)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
sqlite3_bind_int(statement, 5, Int32(error.retryCount))
|
|
255
|
-
sqlite3_bind_double(statement, 6, error.timestamp.timeIntervalSince1970)
|
|
256
|
-
|
|
257
|
-
guard sqlite3_step(statement) == SQLITE_DONE else {
|
|
258
|
-
throw StorageError.insertFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// MARK: - Fetch Daily Stats
|
|
263
|
-
|
|
264
|
-
public func fetchDailyStats(
|
|
265
|
-
timeRange: Int,
|
|
266
|
-
provider: UsageProvider? = nil,
|
|
267
|
-
model: String? = nil
|
|
268
|
-
) async throws -> [DailyPerformanceStats] {
|
|
269
|
-
try open()
|
|
270
|
-
|
|
271
|
-
var sql = "SELECT * FROM daily_stats WHERE date >= date('now', '-\(timeRange) days')"
|
|
272
|
-
|
|
273
|
-
if let provider = provider {
|
|
274
|
-
sql += " AND provider = '\(provider.rawValue)'"
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if let model = model {
|
|
278
|
-
sql += " AND model = '\(model)'"
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
sql += " ORDER BY date DESC"
|
|
282
|
-
|
|
283
|
-
var statement: OpaquePointer?
|
|
284
|
-
defer { sqlite3_finalize(statement) }
|
|
285
|
-
|
|
286
|
-
guard sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK else {
|
|
287
|
-
throw StorageError.prepareFailed(message: String(cString: sqlite3_errmsg(db)))
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
var stats: [DailyPerformanceStats] = []
|
|
291
|
-
|
|
292
|
-
while sqlite3_step(statement) == SQLITE_ROW {
|
|
293
|
-
let provider = String(cString: sqlite3_column_text(statement, 0))
|
|
294
|
-
let model = sqlite3_column_type(statement, 1) == SQLITE_NULL
|
|
295
|
-
? nil
|
|
296
|
-
: String(cString: sqlite3_column_text(statement, 1))
|
|
297
|
-
let date = String(cString: sqlite3_column_text(statement, 2))
|
|
298
|
-
let totalRequests = Int(sqlite3_column_int(statement, 3))
|
|
299
|
-
let avgLatencyMs = Int(sqlite3_column_int(statement, 4))
|
|
300
|
-
let p50LatencyMs = Int(sqlite3_column_int(statement, 5))
|
|
301
|
-
let p95LatencyMs = Int(sqlite3_column_int(statement, 6))
|
|
302
|
-
let p99LatencyMs = Int(sqlite3_column_int(statement, 7))
|
|
303
|
-
let avgQualityRating = sqlite3_column_double(statement, 8)
|
|
304
|
-
let totalRatings = Int(sqlite3_column_int(statement, 9))
|
|
305
|
-
let rating1Count = Int(sqlite3_column_int(statement, 10))
|
|
306
|
-
let rating2Count = Int(sqlite3_column_int(statement, 11))
|
|
307
|
-
let rating3Count = Int(sqlite3_column_int(statement, 12))
|
|
308
|
-
let rating4Count = Int(sqlite3_column_int(statement, 13))
|
|
309
|
-
let rating5Count = Int(sqlite3_column_int(statement, 14))
|
|
310
|
-
let errorCount = Int(sqlite3_column_int(statement, 15))
|
|
311
|
-
let errorRate = sqlite3_column_double(statement, 16)
|
|
312
|
-
let timeoutCount = Int(sqlite3_column_int(statement, 17))
|
|
313
|
-
let quotaCount = Int(sqlite3_column_int(statement, 18))
|
|
314
|
-
let networkCount = Int(sqlite3_column_int(statement, 19))
|
|
315
|
-
let apiErrorCount = Int(sqlite3_column_int(statement, 20))
|
|
316
|
-
let createdAt = Date(timeIntervalSince1970: sqlite3_column_double(statement, 21))
|
|
317
|
-
|
|
318
|
-
let stat = DailyPerformanceStats(
|
|
319
|
-
provider: provider,
|
|
320
|
-
model: model,
|
|
321
|
-
date: date,
|
|
322
|
-
totalRequests: totalRequests,
|
|
323
|
-
avgLatencyMs: avgLatencyMs,
|
|
324
|
-
p50LatencyMs: p50LatencyMs,
|
|
325
|
-
p95LatencyMs: p95LatencyMs,
|
|
326
|
-
p99LatencyMs: p99LatencyMs,
|
|
327
|
-
avgQualityRating: avgQualityRating,
|
|
328
|
-
totalRatings: totalRatings,
|
|
329
|
-
rating1Count: rating1Count,
|
|
330
|
-
rating2Count: rating2Count,
|
|
331
|
-
rating3Count: rating3Count,
|
|
332
|
-
rating4Count: rating4Count,
|
|
333
|
-
rating5Count: rating5Count,
|
|
334
|
-
errorCount: errorCount,
|
|
335
|
-
errorRate: errorRate,
|
|
336
|
-
timeoutCount: timeoutCount,
|
|
337
|
-
quotaCount: quotaCount,
|
|
338
|
-
networkCount: networkCount,
|
|
339
|
-
apiErrorCount: apiErrorCount,
|
|
340
|
-
createdAt: createdAt
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
stats.append(stat)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return stats
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// MARK: - Database Maintenance
|
|
350
|
-
|
|
351
|
-
public func vacuum() async throws {
|
|
352
|
-
try open()
|
|
353
|
-
try execute("VACUUM")
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
public func deleteOldData(olderThan days: Int) async throws {
|
|
357
|
-
try open()
|
|
358
|
-
|
|
359
|
-
let cutoffDate = Date().addingTimeInterval(-Double(days) * 24 * 60 * 60)
|
|
360
|
-
let cutoffTimestamp = cutoffDate.timeIntervalSince1970
|
|
361
|
-
|
|
362
|
-
let queries = [
|
|
363
|
-
"DELETE FROM latency_metrics WHERE created_at < \(cutoffTimestamp)",
|
|
364
|
-
"DELETE FROM quality_ratings WHERE timestamp < \(cutoffTimestamp)",
|
|
365
|
-
"DELETE FROM error_events WHERE timestamp < \(cutoffTimestamp)"
|
|
366
|
-
]
|
|
367
|
-
|
|
368
|
-
for query in queries {
|
|
369
|
-
try execute(query)
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// MARK: - Error Types
|
|
374
|
-
|
|
375
|
-
public enum StorageError: Error, LocalizedError {
|
|
376
|
-
case notOpen
|
|
377
|
-
case openFailed(message: String)
|
|
378
|
-
case prepareFailed(message: String)
|
|
379
|
-
case executionFailed(message: String)
|
|
380
|
-
case insertFailed(message: String)
|
|
381
|
-
|
|
382
|
-
public var errorDescription: String? {
|
|
383
|
-
switch self {
|
|
384
|
-
case .notOpen:
|
|
385
|
-
return "Database not open"
|
|
386
|
-
case .openFailed(let message):
|
|
387
|
-
return "Failed to open database: \(message)"
|
|
388
|
-
case .prepareFailed(let message):
|
|
389
|
-
return "Failed to prepare statement: \(message)"
|
|
390
|
-
case .executionFailed(let message):
|
|
391
|
-
return "Failed to execute statement: \(message)"
|
|
392
|
-
case .insertFailed(let message):
|
|
393
|
-
return "Failed to insert data: \(message)"
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// MARK: - Helper Extensions
|
|
400
|
-
|
|
401
|
-
extension String {
|
|
402
|
-
fileprivate init(cString pointer: UnsafePointer<UInt8>?) {
|
|
403
|
-
guard let pointer = pointer else {
|
|
404
|
-
self = ""
|
|
405
|
-
return
|
|
406
|
-
}
|
|
407
|
-
self = String(cString: pointer)
|
|
408
|
-
}
|
|
409
|
-
}
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
public enum UsagePolicyAction: String, Sendable, Codable, Hashable, CaseIterable {
|
|
4
|
-
case none
|
|
5
|
-
case warn
|
|
6
|
-
case softLimit
|
|
7
|
-
case hardStop
|
|
8
|
-
|
|
9
|
-
var severityRank: Int {
|
|
10
|
-
switch self {
|
|
11
|
-
case .none: 0
|
|
12
|
-
case .warn: 1
|
|
13
|
-
case .softLimit: 2
|
|
14
|
-
case .hardStop: 3
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public enum UsagePolicyAnomalySeverity: Int, Sendable, Codable, Hashable {
|
|
20
|
-
case elevated = 1
|
|
21
|
-
case high = 2
|
|
22
|
-
case critical = 3
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public struct UsagePolicyContext: Sendable, Codable, Hashable {
|
|
26
|
-
public let provider: UsageProvider
|
|
27
|
-
public let observedSpendUSD: Double?
|
|
28
|
-
public let projectedSpendUSD: Double?
|
|
29
|
-
public let budgetLimitUSD: Double?
|
|
30
|
-
public let anomalySeverity: UsagePolicyAnomalySeverity?
|
|
31
|
-
|
|
32
|
-
public init(
|
|
33
|
-
provider: UsageProvider,
|
|
34
|
-
observedSpendUSD: Double?,
|
|
35
|
-
projectedSpendUSD: Double?,
|
|
36
|
-
budgetLimitUSD: Double?,
|
|
37
|
-
anomalySeverity: UsagePolicyAnomalySeverity? = nil)
|
|
38
|
-
{
|
|
39
|
-
self.provider = provider
|
|
40
|
-
self.observedSpendUSD = observedSpendUSD
|
|
41
|
-
self.projectedSpendUSD = projectedSpendUSD
|
|
42
|
-
self.budgetLimitUSD = budgetLimitUSD
|
|
43
|
-
self.anomalySeverity = anomalySeverity
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
public enum UsagePolicyCondition: Sendable, Codable, Hashable {
|
|
48
|
-
case projectedBudgetOverrun(minimumPercent: Double)
|
|
49
|
-
case actualBudgetOverrun
|
|
50
|
-
case anomalySeverityAtLeast(UsagePolicyAnomalySeverity)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
public struct UsagePolicyRule: Sendable, Codable, Hashable, Identifiable {
|
|
54
|
-
public let id: String
|
|
55
|
-
public let name: String
|
|
56
|
-
public let enabled: Bool
|
|
57
|
-
public let condition: UsagePolicyCondition
|
|
58
|
-
public let action: UsagePolicyAction
|
|
59
|
-
|
|
60
|
-
public init(
|
|
61
|
-
id: String = UUID().uuidString,
|
|
62
|
-
name: String,
|
|
63
|
-
enabled: Bool = true,
|
|
64
|
-
condition: UsagePolicyCondition,
|
|
65
|
-
action: UsagePolicyAction)
|
|
66
|
-
{
|
|
67
|
-
self.id = id
|
|
68
|
-
self.name = name
|
|
69
|
-
self.enabled = enabled
|
|
70
|
-
self.condition = condition
|
|
71
|
-
self.action = action
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public struct UsagePolicyRuleMatch: Sendable, Codable, Hashable {
|
|
76
|
-
public let ruleID: String
|
|
77
|
-
public let ruleName: String
|
|
78
|
-
public let action: UsagePolicyAction
|
|
79
|
-
public let reason: String
|
|
80
|
-
|
|
81
|
-
public init(ruleID: String, ruleName: String, action: UsagePolicyAction, reason: String) {
|
|
82
|
-
self.ruleID = ruleID
|
|
83
|
-
self.ruleName = ruleName
|
|
84
|
-
self.action = action
|
|
85
|
-
self.reason = reason
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public struct UsagePolicyDecision: Sendable, Codable, Hashable {
|
|
90
|
-
public let action: UsagePolicyAction
|
|
91
|
-
public let matches: [UsagePolicyRuleMatch]
|
|
92
|
-
public let reasons: [String]
|
|
93
|
-
|
|
94
|
-
public init(action: UsagePolicyAction, matches: [UsagePolicyRuleMatch], reasons: [String]) {
|
|
95
|
-
self.action = action
|
|
96
|
-
self.matches = matches
|
|
97
|
-
self.reasons = reasons
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
public var shouldThrottle: Bool {
|
|
101
|
-
self.action.severityRank >= UsagePolicyAction.softLimit.severityRank
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public var shouldBlock: Bool {
|
|
105
|
-
self.action == .hardStop
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
public enum UsagePolicyEngine {
|
|
110
|
-
public static func evaluate(
|
|
111
|
-
context: UsagePolicyContext,
|
|
112
|
-
rules: [UsagePolicyRule]) -> UsagePolicyDecision
|
|
113
|
-
{
|
|
114
|
-
var matches: [UsagePolicyRuleMatch] = []
|
|
115
|
-
matches.reserveCapacity(rules.count)
|
|
116
|
-
|
|
117
|
-
for rule in rules where rule.enabled {
|
|
118
|
-
guard let reason = self.matchReason(for: rule.condition, context: context) else { continue }
|
|
119
|
-
matches.append(UsagePolicyRuleMatch(
|
|
120
|
-
ruleID: rule.id,
|
|
121
|
-
ruleName: rule.name,
|
|
122
|
-
action: rule.action,
|
|
123
|
-
reason: reason))
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
guard !matches.isEmpty else {
|
|
127
|
-
return UsagePolicyDecision(
|
|
128
|
-
action: .none,
|
|
129
|
-
matches: [],
|
|
130
|
-
reasons: ["No policy rules matched."])
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let strongestAction = matches
|
|
134
|
-
.map(\.action)
|
|
135
|
-
.max { $0.severityRank < $1.severityRank } ?? .none
|
|
136
|
-
|
|
137
|
-
return UsagePolicyDecision(
|
|
138
|
-
action: strongestAction,
|
|
139
|
-
matches: matches,
|
|
140
|
-
reasons: matches.map(\.reason))
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private static func matchReason(
|
|
144
|
-
for condition: UsagePolicyCondition,
|
|
145
|
-
context: UsagePolicyContext) -> String?
|
|
146
|
-
{
|
|
147
|
-
switch condition {
|
|
148
|
-
case .projectedBudgetOverrun(let minimumPercent):
|
|
149
|
-
guard let budget = context.budgetLimitUSD, budget > 0 else { return nil }
|
|
150
|
-
guard let projected = context.projectedSpendUSD, projected.isFinite else { return nil }
|
|
151
|
-
let overrunUSD = projected - budget
|
|
152
|
-
guard overrunUSD > 0 else { return nil }
|
|
153
|
-
let overrunPercent = overrunUSD / budget
|
|
154
|
-
guard overrunPercent >= max(0, minimumPercent) else { return nil }
|
|
155
|
-
let percentText = "\(Int((overrunPercent * 100).rounded()))%"
|
|
156
|
-
return "Projected spend \(UsageFormatter.usdString(projected)) is \(percentText) over budget \(UsageFormatter.usdString(budget))."
|
|
157
|
-
|
|
158
|
-
case .actualBudgetOverrun:
|
|
159
|
-
guard let budget = context.budgetLimitUSD, budget > 0 else { return nil }
|
|
160
|
-
guard let observed = context.observedSpendUSD, observed.isFinite else { return nil }
|
|
161
|
-
guard observed >= budget else { return nil }
|
|
162
|
-
return "Observed spend \(UsageFormatter.usdString(observed)) reached budget \(UsageFormatter.usdString(budget))."
|
|
163
|
-
|
|
164
|
-
case .anomalySeverityAtLeast(let required):
|
|
165
|
-
guard let observed = context.anomalySeverity else { return nil }
|
|
166
|
-
guard observed.rawValue >= required.rawValue else { return nil }
|
|
167
|
-
return "Anomaly severity \(self.severityLabel(observed)) meets threshold \(self.severityLabel(required))."
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
private static func severityLabel(_ severity: UsagePolicyAnomalySeverity) -> String {
|
|
172
|
-
switch severity {
|
|
173
|
-
case .elevated: "Elevated"
|
|
174
|
-
case .high: "High"
|
|
175
|
-
case .critical: "Critical"
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import RunicCore
|
|
3
|
-
|
|
4
|
-
/// Storage for project budget limits and alert thresholds
|
|
5
|
-
public struct ProjectBudgetStore {
|
|
6
|
-
// MARK: - Types
|
|
7
|
-
|
|
8
|
-
public struct ProjectBudget: Codable, Sendable, Identifiable {
|
|
9
|
-
public var id: String { projectID }
|
|
10
|
-
public let projectID: String
|
|
11
|
-
public var projectName: String?
|
|
12
|
-
public var monthlyLimit: Double
|
|
13
|
-
public var alertThreshold: Double // 0.0-1.0, default 0.8 (80%)
|
|
14
|
-
public var enabled: Bool
|
|
15
|
-
public let createdAt: Date
|
|
16
|
-
|
|
17
|
-
public init(
|
|
18
|
-
projectID: String,
|
|
19
|
-
projectName: String? = nil,
|
|
20
|
-
monthlyLimit: Double,
|
|
21
|
-
alertThreshold: Double = 0.8,
|
|
22
|
-
enabled: Bool = true,
|
|
23
|
-
createdAt: Date = Date()
|
|
24
|
-
) {
|
|
25
|
-
self.projectID = projectID
|
|
26
|
-
self.projectName = projectName
|
|
27
|
-
self.monthlyLimit = monthlyLimit
|
|
28
|
-
self.alertThreshold = alertThreshold
|
|
29
|
-
self.enabled = enabled
|
|
30
|
-
self.createdAt = createdAt
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public struct BudgetsData: Codable {
|
|
35
|
-
public let version: Int
|
|
36
|
-
public var budgets: [String: ProjectBudget] // projectID -> budget
|
|
37
|
-
|
|
38
|
-
public init(version: Int = 1, budgets: [String: ProjectBudget] = [:]) {
|
|
39
|
-
self.version = version
|
|
40
|
-
self.budgets = budgets
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// MARK: - Storage Location
|
|
45
|
-
|
|
46
|
-
private static var storageURL: URL {
|
|
47
|
-
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
|
48
|
-
let runicDir = appSupport.appendingPathComponent("Runic", isDirectory: true)
|
|
49
|
-
try? FileManager.default.createDirectory(at: runicDir, withIntermediateDirectories: true)
|
|
50
|
-
return runicDir.appendingPathComponent("project-budgets.json")
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// MARK: - Public Methods
|
|
54
|
-
|
|
55
|
-
public static func load() -> BudgetsData {
|
|
56
|
-
guard FileManager.default.fileExists(atPath: storageURL.path) else {
|
|
57
|
-
return BudgetsData()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
do {
|
|
61
|
-
let data = try Data(contentsOf: storageURL)
|
|
62
|
-
let decoder = JSONDecoder()
|
|
63
|
-
decoder.dateDecodingStrategy = .iso8601
|
|
64
|
-
return try decoder.decode(BudgetsData.self, from: data)
|
|
65
|
-
} catch {
|
|
66
|
-
print("[ProjectBudgetStore] Failed to load budgets: \(error)")
|
|
67
|
-
return BudgetsData()
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
public static func save(_ budgetsData: BudgetsData) throws {
|
|
72
|
-
let encoder = JSONEncoder()
|
|
73
|
-
encoder.dateEncodingStrategy = .iso8601
|
|
74
|
-
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
|
|
75
|
-
|
|
76
|
-
let data = try encoder.encode(budgetsData)
|
|
77
|
-
try data.write(to: storageURL, options: .atomic)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
public static func getBudget(projectID: String) -> ProjectBudget? {
|
|
81
|
-
load().budgets[projectID]
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public static func setBudget(_ budget: ProjectBudget) throws {
|
|
85
|
-
var data = load()
|
|
86
|
-
data.budgets[budget.projectID] = budget
|
|
87
|
-
try save(data)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
public static func removeBudget(projectID: String) throws {
|
|
91
|
-
var data = load()
|
|
92
|
-
data.budgets.removeValue(forKey: projectID)
|
|
93
|
-
try save(data)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
public static func getAllBudgets() -> [ProjectBudget] {
|
|
97
|
-
Array(load().budgets.values).sorted { $0.createdAt < $1.createdAt }
|
|
98
|
-
}
|
|
99
|
-
}
|