@sriinnu/runic 2.0.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 +256 -0
- package/LICENSE +373 -21
- package/README.md +96 -89
- 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/RunicTypographyiOS.swift +0 -24
- 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 -117
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +0 -670
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +0 -519
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +0 -824
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +0 -90
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +0 -1412
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +0 -339
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +0 -423
- package/Sources/Runic/Controllers/StatusItemController.swift +0 -360
- package/Sources/Runic/Core/BudgetNotificationManager.swift +0 -81
- 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 -1222
- package/Sources/Runic/Core/Rendering/IconView.swift +0 -122
- 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/RunicTypography.swift +0 -206
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +0 -158
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +0 -117
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +0 -117
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +0 -30
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/SettingsStore.swift +0 -1749
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +0 -33
- 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 -3631
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +0 -117
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +0 -117
- package/Sources/Runic/Core/UsageExporter.swift +0 -197
- 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 -51
- 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/Fonts/Caveat-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/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 -367
- package/Sources/Runic/SessionQuotaNotifications.swift +0 -58
- package/Sources/Runic/UsagePaceText.swift +0 -52
- package/Sources/Runic/UsageProgressBar.swift +0 -228
- package/Sources/Runic/Utilities/DisplayLink.swift +0 -96
- package/Sources/Runic/Utilities/LoadingPattern.swift +0 -62
- package/Sources/Runic/Utilities/PerformanceConstants.swift +0 -85
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +0 -254
- 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 -311
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +0 -231
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +0 -228
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +0 -157
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +0 -63
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +0 -248
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +0 -104
- package/Sources/Runic/Views/Menu/MenuCardView.swift +0 -1889
- package/Sources/Runic/Views/Menu/MenuContent.swift +0 -153
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +0 -371
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +0 -35
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +0 -241
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +0 -281
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +0 -214
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +0 -220
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +0 -86
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +0 -214
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +0 -398
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +0 -231
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +0 -369
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +0 -191
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +0 -132
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +0 -621
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +0 -306
- 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 -418
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +0 -637
- 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 -321
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +0 -469
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +0 -250
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +0 -50
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +0 -572
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +0 -291
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +0 -3480
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +0 -29
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +0 -61
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +0 -213
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +0 -238
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +0 -376
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +0 -175
- package/Sources/Runic/Views/Team/TeamManagementView.swift +0 -762
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +0 -181
- package/Sources/RunicCLI/CLIEntry.swift +0 -983
- package/Sources/RunicCLI/CLIRenderer.swift +0 -87
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +0 -588
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +0 -363
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +0 -453
- package/Sources/RunicCLI/Commands/ResetCommand.swift +0 -430
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +0 -167
- 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 -268
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +0 -631
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +0 -444
- 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 -370
- 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 -426
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +0 -178
- package/Sources/RunicCore/ProjectBudgetStore.swift +0 -102
- package/Sources/RunicCore/ProviderCostSnapshot.swift +0 -29
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +0 -68
- 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 -164
- 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 -209
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +0 -654
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +0 -30
- 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 -245
- package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +0 -125
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +0 -26
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +0 -183
- 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 -117
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +0 -100
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +0 -293
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +0 -385
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +0 -67
- 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 -67
- 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 -67
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +0 -839
- 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 -97
- 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 -216
- 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 -86
- package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +0 -30
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +0 -328
- 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 -74
- package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +0 -41
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +0 -745
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +0 -384
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +0 -307
- package/Sources/RunicCore/Sync/SyncProtocol.swift +0 -287
- package/Sources/RunicCore/Sync/SyncRecord.swift +0 -402
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +0 -288
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +0 -768
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +0 -338
- package/Sources/RunicCore/Team/TeamModels.swift +0 -584
- package/Sources/RunicCore/Team/TeamShowbackExporter.swift +0 -495
- package/Sources/RunicCore/Team/TeamStore.swift +0 -809
- 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 -441
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +0 -502
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +0 -161
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +0 -550
- package/Sources/RunicCore/UsageLedger/UsageLedger.swift +0 -29
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +0 -675
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +0 -209
- package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +0 -417
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +0 -284
- 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 -48
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +0 -21
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +0 -69
- package/Tests/RunicTests/CLICostTests.swift +0 -64
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +0 -21
- package/Tests/RunicTests/CLISnapshotTests.swift +0 -71
- package/Tests/RunicTests/CLIWebFallbackTests.swift +0 -57
- package/Tests/RunicTests/ClaudeOAuthTests.swift +0 -163
- package/Tests/RunicTests/ClaudeResilienceTests.swift +0 -29
- package/Tests/RunicTests/ClaudeUsageTests.swift +0 -369
- package/Tests/RunicTests/CodexOAuthTests.swift +0 -121
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +0 -80
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +0 -74
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +0 -74
- package/Tests/RunicTests/CostUsageDecodingTests.swift +0 -392
- package/Tests/RunicTests/CostUsagePricingTests.swift +0 -48
- package/Tests/RunicTests/CostUsageScannerTests.swift +0 -698
- package/Tests/RunicTests/CursorStatusProbeTests.swift +0 -322
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +0 -67
- package/Tests/RunicTests/GeminiAPITestHelpers.swift +0 -94
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +0 -27
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +0 -283
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +0 -279
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +0 -232
- package/Tests/RunicTests/GeminiTestEnvironment.swift +0 -87
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +0 -72
- package/Tests/RunicTests/InstallOriginTests.swift +0 -17
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +0 -9
- package/Tests/RunicTests/LiveAccountTests.swift +0 -24
- package/Tests/RunicTests/LoadingPatternTests.swift +0 -34
- package/Tests/RunicTests/MenuCardModelTests.swift +0 -544
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +0 -11
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +0 -11
- package/Tests/RunicTests/MiniMaxParsingTests.swift +0 -67
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +0 -11
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +0 -110
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +0 -16
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +0 -74
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +0 -26
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +0 -133
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +0 -64
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +0 -131
- package/Tests/RunicTests/PathBuilderTests.swift +0 -236
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +0 -159
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +0 -37
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +0 -15
- package/Tests/RunicTests/ProviderRegistryTests.swift +0 -29
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +0 -236
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +0 -50
- package/Tests/RunicTests/RunicTests.swift +0 -119
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +0 -35
- package/Tests/RunicTests/SettingsStoreTests.swift +0 -265
- package/Tests/RunicTests/StatusItemAnimationTests.swift +0 -54
- package/Tests/RunicTests/StatusMenuTests.swift +0 -475
- package/Tests/RunicTests/StatusProbeTests.swift +0 -376
- package/Tests/RunicTests/SubprocessRunnerTests.swift +0 -18
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +0 -64
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +0 -147
- package/Tests/RunicTests/TTYIntegrationTests.swift +0 -41
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +0 -157
- package/Tests/RunicTests/TextParsingTests.swift +0 -11
- package/Tests/RunicTests/UsageFormatterTests.swift +0 -111
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +0 -170
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +0 -211
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +0 -81
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +0 -168
- package/Tests/RunicTests/UsagePaceTests.swift +0 -76
- package/Tests/RunicTests/UsagePaceTextTests.swift +0 -108
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +0 -89
- package/Tests/RunicTests/WidgetSnapshotTests.swift +0 -43
- package/Tests/RunicTests/ZaiProviderTests.swift +0 -59
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +0 -9
- package/TestsLinux/PlatformGatingTests.swift +0 -46
- package/appcast.xml +0 -104
- 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/runic.svg +0 -15
- package/version.env +0 -2
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
import CloudKit
|
|
2
|
-
import Foundation
|
|
3
|
-
|
|
4
|
-
// MARK: - Sync Protocol Definitions
|
|
5
|
-
|
|
6
|
-
/// Protocol defining the core synchronization engine interface
|
|
7
|
-
///
|
|
8
|
-
/// This protocol abstracts the synchronization logic to allow for different
|
|
9
|
-
/// implementations (CloudKit, custom server, etc.) while maintaining a
|
|
10
|
-
/// consistent API for syncing data across devices.
|
|
11
|
-
public protocol SyncEngine: Sendable {
|
|
12
|
-
/// Synchronizes local data with remote storage
|
|
13
|
-
///
|
|
14
|
-
/// - Parameter options: Configuration options for this sync operation
|
|
15
|
-
/// - Returns: Result indicating success or failure with error details
|
|
16
|
-
func sync(options: SyncOptions) async -> Result<SyncResult, SyncError>
|
|
17
|
-
|
|
18
|
-
/// Pushes local changes to remote storage
|
|
19
|
-
///
|
|
20
|
-
/// - Parameter records: Array of records to push
|
|
21
|
-
/// - Returns: Result containing pushed record identifiers or error
|
|
22
|
-
func push(records: [SyncableRecord]) async -> Result<[String], SyncError>
|
|
23
|
-
|
|
24
|
-
/// Fetches changes from remote storage
|
|
25
|
-
///
|
|
26
|
-
/// - Parameter recordTypes: Array of record type names to fetch
|
|
27
|
-
/// - Returns: Result containing fetched records or error
|
|
28
|
-
func fetch(recordTypes: [String]) async -> Result<[SyncableRecord], SyncError>
|
|
29
|
-
|
|
30
|
-
/// Deletes records from remote storage
|
|
31
|
-
///
|
|
32
|
-
/// - Parameter recordIDs: Array of record identifiers to delete
|
|
33
|
-
/// - Returns: Result containing deleted record identifiers or error
|
|
34
|
-
func delete(recordIDs: [String]) async -> Result<[String], SyncError>
|
|
35
|
-
|
|
36
|
-
/// Resets sync state and clears all remote data
|
|
37
|
-
///
|
|
38
|
-
/// - Warning: This operation is destructive and cannot be undone
|
|
39
|
-
/// - Returns: Result indicating success or failure
|
|
40
|
-
func resetSync() async -> Result<Void, SyncError>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// MARK: - Syncable Record Protocol
|
|
44
|
-
|
|
45
|
-
/// Protocol for objects that can be synchronized across devices
|
|
46
|
-
///
|
|
47
|
-
/// Conform to this protocol to enable CloudKit synchronization for your data types.
|
|
48
|
-
public protocol SyncableRecord: Sendable, Codable {
|
|
49
|
-
/// Unique identifier for the record
|
|
50
|
-
var recordID: String { get }
|
|
51
|
-
|
|
52
|
-
/// Type name used for CloudKit record type
|
|
53
|
-
var recordType: String { get }
|
|
54
|
-
|
|
55
|
-
/// Version number for conflict resolution
|
|
56
|
-
var version: Int { get }
|
|
57
|
-
|
|
58
|
-
/// Last modification timestamp
|
|
59
|
-
var modifiedAt: Date { get }
|
|
60
|
-
|
|
61
|
-
/// Device identifier that last modified this record
|
|
62
|
-
var lastModifiedDeviceID: String? { get }
|
|
63
|
-
|
|
64
|
-
/// Converts the record to a CloudKit CKRecord
|
|
65
|
-
///
|
|
66
|
-
/// - Returns: CloudKit record representation
|
|
67
|
-
func toCKRecord() throws -> CKRecord
|
|
68
|
-
|
|
69
|
-
/// Creates a record from a CloudKit CKRecord
|
|
70
|
-
///
|
|
71
|
-
/// - Parameter ckRecord: CloudKit record to convert
|
|
72
|
-
/// - Returns: Initialized syncable record
|
|
73
|
-
static func fromCKRecord(_ ckRecord: CKRecord) throws -> Self
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// MARK: - Sync Conflict Resolver Protocol
|
|
77
|
-
|
|
78
|
-
/// Protocol for resolving conflicts when multiple devices modify the same record
|
|
79
|
-
///
|
|
80
|
-
/// Implement this protocol to define custom conflict resolution strategies
|
|
81
|
-
/// beyond the default last-write-wins behavior.
|
|
82
|
-
public protocol SyncConflictResolverProtocol: Sendable {
|
|
83
|
-
/// Resolves a conflict between local and remote versions of a record
|
|
84
|
-
///
|
|
85
|
-
/// - Parameters:
|
|
86
|
-
/// - local: The local version of the conflicting record
|
|
87
|
-
/// - remote: The remote version of the conflicting record
|
|
88
|
-
/// - strategy: The conflict resolution strategy to apply
|
|
89
|
-
/// - Returns: The resolved record that should be kept
|
|
90
|
-
func resolve(
|
|
91
|
-
local: SyncableRecord,
|
|
92
|
-
remote: SyncableRecord,
|
|
93
|
-
strategy: ConflictResolutionStrategy) async -> SyncableRecord
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// MARK: - Sync Observer Protocol
|
|
97
|
-
|
|
98
|
-
/// Protocol for observing synchronization events
|
|
99
|
-
///
|
|
100
|
-
/// Implement this protocol to receive notifications about sync state changes,
|
|
101
|
-
/// progress updates, and completion events.
|
|
102
|
-
public protocol SyncObserver: AnyObject, Sendable {
|
|
103
|
-
/// Called when synchronization begins
|
|
104
|
-
func syncDidStart()
|
|
105
|
-
|
|
106
|
-
/// Called when synchronization progress updates
|
|
107
|
-
///
|
|
108
|
-
/// - Parameters:
|
|
109
|
-
/// - current: Number of records processed
|
|
110
|
-
/// - total: Total number of records to process
|
|
111
|
-
func syncDidProgress(current: Int, total: Int)
|
|
112
|
-
|
|
113
|
-
/// Called when synchronization completes successfully
|
|
114
|
-
///
|
|
115
|
-
/// - Parameter result: The sync result containing statistics
|
|
116
|
-
func syncDidComplete(result: SyncResult)
|
|
117
|
-
|
|
118
|
-
/// Called when synchronization fails
|
|
119
|
-
///
|
|
120
|
-
/// - Parameter error: The error that caused the failure
|
|
121
|
-
func syncDidFail(error: SyncError)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// MARK: - Supporting Types
|
|
125
|
-
|
|
126
|
-
/// Configuration options for sync operations
|
|
127
|
-
public struct SyncOptions: Sendable {
|
|
128
|
-
/// Whether to force a full sync, ignoring change tracking
|
|
129
|
-
public let forceFullSync: Bool
|
|
130
|
-
|
|
131
|
-
/// Maximum number of records to sync in a single batch
|
|
132
|
-
public let batchSize: Int
|
|
133
|
-
|
|
134
|
-
/// Conflict resolution strategy to use
|
|
135
|
-
public let conflictStrategy: ConflictResolutionStrategy
|
|
136
|
-
|
|
137
|
-
/// Whether to sync in background mode
|
|
138
|
-
public let backgroundMode: Bool
|
|
139
|
-
|
|
140
|
-
/// Timeout interval for sync operations
|
|
141
|
-
public let timeout: TimeInterval
|
|
142
|
-
|
|
143
|
-
/// Whether to encrypt sensitive data
|
|
144
|
-
public let encryptSensitiveData: Bool
|
|
145
|
-
|
|
146
|
-
public init(
|
|
147
|
-
forceFullSync: Bool = false,
|
|
148
|
-
batchSize: Int = 100,
|
|
149
|
-
conflictStrategy: ConflictResolutionStrategy = .lastWriteWins,
|
|
150
|
-
backgroundMode: Bool = false,
|
|
151
|
-
timeout: TimeInterval = 60.0,
|
|
152
|
-
encryptSensitiveData: Bool = true)
|
|
153
|
-
{
|
|
154
|
-
self.forceFullSync = forceFullSync
|
|
155
|
-
self.batchSize = batchSize
|
|
156
|
-
self.conflictStrategy = conflictStrategy
|
|
157
|
-
self.backgroundMode = backgroundMode
|
|
158
|
-
self.timeout = timeout
|
|
159
|
-
self.encryptSensitiveData = encryptSensitiveData
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/// Result of a sync operation with statistics
|
|
164
|
-
public struct SyncResult: Sendable {
|
|
165
|
-
/// Number of records successfully pushed
|
|
166
|
-
public let pushedCount: Int
|
|
167
|
-
|
|
168
|
-
/// Number of records successfully fetched
|
|
169
|
-
public let fetchedCount: Int
|
|
170
|
-
|
|
171
|
-
/// Number of conflicts resolved
|
|
172
|
-
public let conflictsResolved: Int
|
|
173
|
-
|
|
174
|
-
/// Number of records deleted
|
|
175
|
-
public let deletedCount: Int
|
|
176
|
-
|
|
177
|
-
/// Timestamp when sync completed
|
|
178
|
-
public let completedAt: Date
|
|
179
|
-
|
|
180
|
-
/// Duration of the sync operation
|
|
181
|
-
public let duration: TimeInterval
|
|
182
|
-
|
|
183
|
-
/// Any non-fatal warnings encountered
|
|
184
|
-
public let warnings: [String]
|
|
185
|
-
|
|
186
|
-
public init(
|
|
187
|
-
pushedCount: Int = 0,
|
|
188
|
-
fetchedCount: Int = 0,
|
|
189
|
-
conflictsResolved: Int = 0,
|
|
190
|
-
deletedCount: Int = 0,
|
|
191
|
-
completedAt: Date = Date(),
|
|
192
|
-
duration: TimeInterval = 0,
|
|
193
|
-
warnings: [String] = [])
|
|
194
|
-
{
|
|
195
|
-
self.pushedCount = pushedCount
|
|
196
|
-
self.fetchedCount = fetchedCount
|
|
197
|
-
self.conflictsResolved = conflictsResolved
|
|
198
|
-
self.deletedCount = deletedCount
|
|
199
|
-
self.completedAt = completedAt
|
|
200
|
-
self.duration = duration
|
|
201
|
-
self.warnings = warnings
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/// Conflict resolution strategies
|
|
206
|
-
public enum ConflictResolutionStrategy: String, Codable, Sendable {
|
|
207
|
-
/// Keep the most recently modified record (default)
|
|
208
|
-
case lastWriteWins
|
|
209
|
-
|
|
210
|
-
/// Keep the local version
|
|
211
|
-
case preferLocal
|
|
212
|
-
|
|
213
|
-
/// Keep the remote version
|
|
214
|
-
case preferRemote
|
|
215
|
-
|
|
216
|
-
/// Keep the version with higher version number
|
|
217
|
-
case highestVersion
|
|
218
|
-
|
|
219
|
-
/// Merge both versions (requires custom merge logic)
|
|
220
|
-
case merge
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/// Errors that can occur during synchronization
|
|
224
|
-
public enum SyncError: Error, Sendable {
|
|
225
|
-
/// iCloud account is not available
|
|
226
|
-
case iCloudAccountUnavailable
|
|
227
|
-
|
|
228
|
-
/// Network connection is not available
|
|
229
|
-
case networkUnavailable
|
|
230
|
-
|
|
231
|
-
/// CloudKit quota exceeded
|
|
232
|
-
case quotaExceeded
|
|
233
|
-
|
|
234
|
-
/// Record format is invalid
|
|
235
|
-
case invalidRecordFormat(String)
|
|
236
|
-
|
|
237
|
-
/// Sync operation timed out
|
|
238
|
-
case timeout
|
|
239
|
-
|
|
240
|
-
/// Authentication failed
|
|
241
|
-
case authenticationFailed
|
|
242
|
-
|
|
243
|
-
/// Conflict could not be resolved
|
|
244
|
-
case conflictResolutionFailed(String)
|
|
245
|
-
|
|
246
|
-
/// Generic CloudKit error
|
|
247
|
-
case cloudKitError(Error)
|
|
248
|
-
|
|
249
|
-
/// Encryption/decryption failed
|
|
250
|
-
case encryptionFailed(String)
|
|
251
|
-
|
|
252
|
-
/// Unknown error occurred
|
|
253
|
-
case unknown(Error)
|
|
254
|
-
|
|
255
|
-
public var localizedDescription: String {
|
|
256
|
-
switch self {
|
|
257
|
-
case .iCloudAccountUnavailable:
|
|
258
|
-
"iCloud account is not available. Please sign in to iCloud in Settings."
|
|
259
|
-
case .networkUnavailable:
|
|
260
|
-
"Network connection is not available. Sync will retry when online."
|
|
261
|
-
case .quotaExceeded:
|
|
262
|
-
"iCloud storage quota exceeded. Please free up space or upgrade your plan."
|
|
263
|
-
case let .invalidRecordFormat(details):
|
|
264
|
-
"Invalid record format: \(details)"
|
|
265
|
-
case .timeout:
|
|
266
|
-
"Sync operation timed out. Please try again."
|
|
267
|
-
case .authenticationFailed:
|
|
268
|
-
"Authentication failed. Please sign in again."
|
|
269
|
-
case let .conflictResolutionFailed(details):
|
|
270
|
-
"Failed to resolve conflict: \(details)"
|
|
271
|
-
case let .cloudKitError(error):
|
|
272
|
-
"CloudKit error: \(error.localizedDescription)"
|
|
273
|
-
case let .encryptionFailed(details):
|
|
274
|
-
"Encryption failed: \(details)"
|
|
275
|
-
case let .unknown(error):
|
|
276
|
-
"Unknown error: \(error.localizedDescription)"
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/// Priority levels for sync operations
|
|
282
|
-
public enum SyncPriority: Int, Sendable {
|
|
283
|
-
case low = 0
|
|
284
|
-
case normal = 1
|
|
285
|
-
case high = 2
|
|
286
|
-
case critical = 3
|
|
287
|
-
}
|
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import CloudKit
|
|
2
|
-
import CryptoKit
|
|
3
|
-
import Foundation
|
|
4
|
-
import LocalAuthentication
|
|
5
|
-
|
|
6
|
-
// MARK: - CloudKit Record Type Constants
|
|
7
|
-
|
|
8
|
-
/// CloudKit record type identifiers
|
|
9
|
-
public enum CloudKitRecordType {
|
|
10
|
-
public static let usageSnapshot = "UsageSnapshot"
|
|
11
|
-
public static let userPreferences = "UserPreferences"
|
|
12
|
-
public static let alertConfiguration = "AlertConfiguration"
|
|
13
|
-
public static let syncMetadata = "SyncMetadata"
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// MARK: - Usage Snapshot Sync Record
|
|
17
|
-
|
|
18
|
-
/// Syncable usage snapshot record for cross-device synchronization
|
|
19
|
-
///
|
|
20
|
-
/// This record encapsulates usage data that can be synchronized across
|
|
21
|
-
/// devices using CloudKit, with support for encryption and versioning.
|
|
22
|
-
public struct UsageSnapshotSyncRecord: SyncableRecord {
|
|
23
|
-
public let recordID: String
|
|
24
|
-
public let recordType: String = CloudKitRecordType.usageSnapshot
|
|
25
|
-
public let version: Int
|
|
26
|
-
public let modifiedAt: Date
|
|
27
|
-
public let lastModifiedDeviceID: String?
|
|
28
|
-
|
|
29
|
-
// Usage data
|
|
30
|
-
public let providerID: String
|
|
31
|
-
public let primaryUsed: Int
|
|
32
|
-
public let primaryLimit: Int?
|
|
33
|
-
public let secondaryUsed: Int?
|
|
34
|
-
public let secondaryLimit: Int?
|
|
35
|
-
public let costUSD: Double?
|
|
36
|
-
public let accountEmail: String?
|
|
37
|
-
public let updatedAt: Date
|
|
38
|
-
|
|
39
|
-
// Metadata
|
|
40
|
-
public let deviceName: String
|
|
41
|
-
public let platform: String
|
|
42
|
-
|
|
43
|
-
public init(
|
|
44
|
-
recordID: String = UUID().uuidString,
|
|
45
|
-
version: Int = 1,
|
|
46
|
-
modifiedAt: Date = Date(),
|
|
47
|
-
lastModifiedDeviceID: String? = nil,
|
|
48
|
-
providerID: String,
|
|
49
|
-
primaryUsed: Int,
|
|
50
|
-
primaryLimit: Int?,
|
|
51
|
-
secondaryUsed: Int? = nil,
|
|
52
|
-
secondaryLimit: Int? = nil,
|
|
53
|
-
costUSD: Double? = nil,
|
|
54
|
-
accountEmail: String? = nil,
|
|
55
|
-
updatedAt: Date = Date(),
|
|
56
|
-
deviceName: String,
|
|
57
|
-
platform: String)
|
|
58
|
-
{
|
|
59
|
-
self.recordID = recordID
|
|
60
|
-
self.version = version
|
|
61
|
-
self.modifiedAt = modifiedAt
|
|
62
|
-
self.lastModifiedDeviceID = lastModifiedDeviceID
|
|
63
|
-
self.providerID = providerID
|
|
64
|
-
self.primaryUsed = primaryUsed
|
|
65
|
-
self.primaryLimit = primaryLimit
|
|
66
|
-
self.secondaryUsed = secondaryUsed
|
|
67
|
-
self.secondaryLimit = secondaryLimit
|
|
68
|
-
self.costUSD = costUSD
|
|
69
|
-
self.accountEmail = accountEmail
|
|
70
|
-
self.updatedAt = updatedAt
|
|
71
|
-
self.deviceName = deviceName
|
|
72
|
-
self.platform = platform
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
public func toCKRecord() throws -> CKRecord {
|
|
76
|
-
let record = CKRecord(recordType: recordType, recordID: CKRecord.ID(recordName: self.recordID))
|
|
77
|
-
|
|
78
|
-
record["version"] = self.version as CKRecordValue
|
|
79
|
-
record["modifiedAt"] = self.modifiedAt as CKRecordValue
|
|
80
|
-
record["lastModifiedDeviceID"] = self.lastModifiedDeviceID as? CKRecordValue
|
|
81
|
-
record["providerID"] = self.providerID as CKRecordValue
|
|
82
|
-
record["primaryUsed"] = self.primaryUsed as CKRecordValue
|
|
83
|
-
record["primaryLimit"] = self.primaryLimit as? CKRecordValue
|
|
84
|
-
record["secondaryUsed"] = self.secondaryUsed as? CKRecordValue
|
|
85
|
-
record["secondaryLimit"] = self.secondaryLimit as? CKRecordValue
|
|
86
|
-
record["costUSD"] = self.costUSD as? CKRecordValue
|
|
87
|
-
record["updatedAt"] = self.updatedAt as CKRecordValue
|
|
88
|
-
record["deviceName"] = self.deviceName as CKRecordValue
|
|
89
|
-
record["platform"] = self.platform as CKRecordValue
|
|
90
|
-
|
|
91
|
-
// Encrypt sensitive data
|
|
92
|
-
if let email = accountEmail {
|
|
93
|
-
let encrypted = try encryptString(email)
|
|
94
|
-
record["accountEmail"] = encrypted as CKRecordValue
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return record
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
public static func fromCKRecord(_ ckRecord: CKRecord) throws -> UsageSnapshotSyncRecord {
|
|
101
|
-
guard let version = ckRecord["version"] as? Int,
|
|
102
|
-
let modifiedAt = ckRecord["modifiedAt"] as? Date,
|
|
103
|
-
let providerID = ckRecord["providerID"] as? String,
|
|
104
|
-
let primaryUsed = ckRecord["primaryUsed"] as? Int,
|
|
105
|
-
let updatedAt = ckRecord["updatedAt"] as? Date,
|
|
106
|
-
let deviceName = ckRecord["deviceName"] as? String,
|
|
107
|
-
let platform = ckRecord["platform"] as? String
|
|
108
|
-
else {
|
|
109
|
-
throw SyncError.invalidRecordFormat("Missing required fields in UsageSnapshot")
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
var accountEmail: String?
|
|
113
|
-
if let encryptedEmail = ckRecord["accountEmail"] as? String {
|
|
114
|
-
accountEmail = try? decryptString(encryptedEmail)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return UsageSnapshotSyncRecord(
|
|
118
|
-
recordID: ckRecord.recordID.recordName,
|
|
119
|
-
version: version,
|
|
120
|
-
modifiedAt: modifiedAt,
|
|
121
|
-
lastModifiedDeviceID: ckRecord["lastModifiedDeviceID"] as? String,
|
|
122
|
-
providerID: providerID,
|
|
123
|
-
primaryUsed: primaryUsed,
|
|
124
|
-
primaryLimit: ckRecord["primaryLimit"] as? Int,
|
|
125
|
-
secondaryUsed: ckRecord["secondaryUsed"] as? Int,
|
|
126
|
-
secondaryLimit: ckRecord["secondaryLimit"] as? Int,
|
|
127
|
-
costUSD: ckRecord["costUSD"] as? Double,
|
|
128
|
-
accountEmail: accountEmail,
|
|
129
|
-
updatedAt: updatedAt,
|
|
130
|
-
deviceName: deviceName,
|
|
131
|
-
platform: platform)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// MARK: - User Preferences Sync Record
|
|
136
|
-
|
|
137
|
-
/// Syncable user preferences record
|
|
138
|
-
///
|
|
139
|
-
/// Stores user preferences and settings that should be synchronized
|
|
140
|
-
/// across all devices where the user is signed in.
|
|
141
|
-
public struct UserPreferencesSyncRecord: SyncableRecord {
|
|
142
|
-
public let recordID: String
|
|
143
|
-
public let recordType: String = CloudKitRecordType.userPreferences
|
|
144
|
-
public let version: Int
|
|
145
|
-
public let modifiedAt: Date
|
|
146
|
-
public let lastModifiedDeviceID: String?
|
|
147
|
-
|
|
148
|
-
// Preferences
|
|
149
|
-
public let refreshInterval: TimeInterval
|
|
150
|
-
public let enabledProviders: [String]
|
|
151
|
-
public let notificationsEnabled: Bool
|
|
152
|
-
public let autoRefreshEnabled: Bool
|
|
153
|
-
public let theme: String
|
|
154
|
-
public let displayFormat: String
|
|
155
|
-
|
|
156
|
-
public init(
|
|
157
|
-
recordID: String = "user-preferences",
|
|
158
|
-
version: Int = 1,
|
|
159
|
-
modifiedAt: Date = Date(),
|
|
160
|
-
lastModifiedDeviceID: String? = nil,
|
|
161
|
-
refreshInterval: TimeInterval = 300,
|
|
162
|
-
enabledProviders: [String] = [],
|
|
163
|
-
notificationsEnabled: Bool = true,
|
|
164
|
-
autoRefreshEnabled: Bool = true,
|
|
165
|
-
theme: String = "system",
|
|
166
|
-
displayFormat: String = "compact")
|
|
167
|
-
{
|
|
168
|
-
self.recordID = recordID
|
|
169
|
-
self.version = version
|
|
170
|
-
self.modifiedAt = modifiedAt
|
|
171
|
-
self.lastModifiedDeviceID = lastModifiedDeviceID
|
|
172
|
-
self.refreshInterval = refreshInterval
|
|
173
|
-
self.enabledProviders = enabledProviders
|
|
174
|
-
self.notificationsEnabled = notificationsEnabled
|
|
175
|
-
self.autoRefreshEnabled = autoRefreshEnabled
|
|
176
|
-
self.theme = theme
|
|
177
|
-
self.displayFormat = displayFormat
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
public func toCKRecord() throws -> CKRecord {
|
|
181
|
-
let record = CKRecord(recordType: recordType, recordID: CKRecord.ID(recordName: self.recordID))
|
|
182
|
-
|
|
183
|
-
record["version"] = self.version as CKRecordValue
|
|
184
|
-
record["modifiedAt"] = self.modifiedAt as CKRecordValue
|
|
185
|
-
record["lastModifiedDeviceID"] = self.lastModifiedDeviceID as? CKRecordValue
|
|
186
|
-
record["refreshInterval"] = self.refreshInterval as CKRecordValue
|
|
187
|
-
record["enabledProviders"] = self.enabledProviders as CKRecordValue
|
|
188
|
-
record["notificationsEnabled"] = (self.notificationsEnabled ? 1 : 0) as CKRecordValue
|
|
189
|
-
record["autoRefreshEnabled"] = (self.autoRefreshEnabled ? 1 : 0) as CKRecordValue
|
|
190
|
-
record["theme"] = self.theme as CKRecordValue
|
|
191
|
-
record["displayFormat"] = self.displayFormat as CKRecordValue
|
|
192
|
-
|
|
193
|
-
return record
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
public static func fromCKRecord(_ ckRecord: CKRecord) throws -> UserPreferencesSyncRecord {
|
|
197
|
-
guard let version = ckRecord["version"] as? Int,
|
|
198
|
-
let modifiedAt = ckRecord["modifiedAt"] as? Date,
|
|
199
|
-
let refreshInterval = ckRecord["refreshInterval"] as? Double,
|
|
200
|
-
let enabledProviders = ckRecord["enabledProviders"] as? [String],
|
|
201
|
-
let notificationsEnabled = ckRecord["notificationsEnabled"] as? Int,
|
|
202
|
-
let autoRefreshEnabled = ckRecord["autoRefreshEnabled"] as? Int,
|
|
203
|
-
let theme = ckRecord["theme"] as? String,
|
|
204
|
-
let displayFormat = ckRecord["displayFormat"] as? String
|
|
205
|
-
else {
|
|
206
|
-
throw SyncError.invalidRecordFormat("Missing required fields in UserPreferences")
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return UserPreferencesSyncRecord(
|
|
210
|
-
recordID: ckRecord.recordID.recordName,
|
|
211
|
-
version: version,
|
|
212
|
-
modifiedAt: modifiedAt,
|
|
213
|
-
lastModifiedDeviceID: ckRecord["lastModifiedDeviceID"] as? String,
|
|
214
|
-
refreshInterval: refreshInterval,
|
|
215
|
-
enabledProviders: enabledProviders,
|
|
216
|
-
notificationsEnabled: notificationsEnabled == 1,
|
|
217
|
-
autoRefreshEnabled: autoRefreshEnabled == 1,
|
|
218
|
-
theme: theme,
|
|
219
|
-
displayFormat: displayFormat)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// MARK: - Alert Configuration Sync Record
|
|
224
|
-
|
|
225
|
-
/// Syncable alert configuration record
|
|
226
|
-
///
|
|
227
|
-
/// Stores alert thresholds and notification settings that should be
|
|
228
|
-
/// consistent across all user devices.
|
|
229
|
-
public struct AlertConfigurationSyncRecord: SyncableRecord {
|
|
230
|
-
public let recordID: String
|
|
231
|
-
public let recordType: String = CloudKitRecordType.alertConfiguration
|
|
232
|
-
public let version: Int
|
|
233
|
-
public let modifiedAt: Date
|
|
234
|
-
public let lastModifiedDeviceID: String?
|
|
235
|
-
|
|
236
|
-
// Alert settings
|
|
237
|
-
public let providerID: String
|
|
238
|
-
public let warningThreshold: Double
|
|
239
|
-
public let criticalThreshold: Double
|
|
240
|
-
public let notificationChannels: [String]
|
|
241
|
-
public let enabled: Bool
|
|
242
|
-
|
|
243
|
-
public init(
|
|
244
|
-
recordID: String = UUID().uuidString,
|
|
245
|
-
version: Int = 1,
|
|
246
|
-
modifiedAt: Date = Date(),
|
|
247
|
-
lastModifiedDeviceID: String? = nil,
|
|
248
|
-
providerID: String,
|
|
249
|
-
warningThreshold: Double = 0.75,
|
|
250
|
-
criticalThreshold: Double = 0.90,
|
|
251
|
-
notificationChannels: [String] = ["system"],
|
|
252
|
-
enabled: Bool = true)
|
|
253
|
-
{
|
|
254
|
-
self.recordID = recordID
|
|
255
|
-
self.version = version
|
|
256
|
-
self.modifiedAt = modifiedAt
|
|
257
|
-
self.lastModifiedDeviceID = lastModifiedDeviceID
|
|
258
|
-
self.providerID = providerID
|
|
259
|
-
self.warningThreshold = warningThreshold
|
|
260
|
-
self.criticalThreshold = criticalThreshold
|
|
261
|
-
self.notificationChannels = notificationChannels
|
|
262
|
-
self.enabled = enabled
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
public func toCKRecord() throws -> CKRecord {
|
|
266
|
-
let record = CKRecord(recordType: recordType, recordID: CKRecord.ID(recordName: self.recordID))
|
|
267
|
-
|
|
268
|
-
record["version"] = self.version as CKRecordValue
|
|
269
|
-
record["modifiedAt"] = self.modifiedAt as CKRecordValue
|
|
270
|
-
record["lastModifiedDeviceID"] = self.lastModifiedDeviceID as? CKRecordValue
|
|
271
|
-
record["providerID"] = self.providerID as CKRecordValue
|
|
272
|
-
record["warningThreshold"] = self.warningThreshold as CKRecordValue
|
|
273
|
-
record["criticalThreshold"] = self.criticalThreshold as CKRecordValue
|
|
274
|
-
record["notificationChannels"] = self.notificationChannels as CKRecordValue
|
|
275
|
-
record["enabled"] = (self.enabled ? 1 : 0) as CKRecordValue
|
|
276
|
-
|
|
277
|
-
return record
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
public static func fromCKRecord(_ ckRecord: CKRecord) throws -> AlertConfigurationSyncRecord {
|
|
281
|
-
guard let version = ckRecord["version"] as? Int,
|
|
282
|
-
let modifiedAt = ckRecord["modifiedAt"] as? Date,
|
|
283
|
-
let providerID = ckRecord["providerID"] as? String,
|
|
284
|
-
let warningThreshold = ckRecord["warningThreshold"] as? Double,
|
|
285
|
-
let criticalThreshold = ckRecord["criticalThreshold"] as? Double,
|
|
286
|
-
let notificationChannels = ckRecord["notificationChannels"] as? [String],
|
|
287
|
-
let enabled = ckRecord["enabled"] as? Int
|
|
288
|
-
else {
|
|
289
|
-
throw SyncError.invalidRecordFormat("Missing required fields in AlertConfiguration")
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return AlertConfigurationSyncRecord(
|
|
293
|
-
recordID: ckRecord.recordID.recordName,
|
|
294
|
-
version: version,
|
|
295
|
-
modifiedAt: modifiedAt,
|
|
296
|
-
lastModifiedDeviceID: ckRecord["lastModifiedDeviceID"] as? String,
|
|
297
|
-
providerID: providerID,
|
|
298
|
-
warningThreshold: warningThreshold,
|
|
299
|
-
criticalThreshold: criticalThreshold,
|
|
300
|
-
notificationChannels: notificationChannels,
|
|
301
|
-
enabled: enabled == 1)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// MARK: - Encryption Helpers
|
|
306
|
-
|
|
307
|
-
/// Encrypts a string using AES-GCM encryption
|
|
308
|
-
///
|
|
309
|
-
/// - Parameter plaintext: The string to encrypt
|
|
310
|
-
/// - Returns: Base64-encoded encrypted string with nonce prepended
|
|
311
|
-
/// - Throws: SyncError.encryptionFailed if encryption fails
|
|
312
|
-
private func encryptString(_ plaintext: String) throws -> String {
|
|
313
|
-
guard let data = plaintext.data(using: .utf8) else {
|
|
314
|
-
throw SyncError.encryptionFailed("Failed to convert string to data")
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
let key = getOrCreateEncryptionKey()
|
|
318
|
-
let sealed = try AES.GCM.seal(data, using: key)
|
|
319
|
-
|
|
320
|
-
guard let combined = sealed.combined else {
|
|
321
|
-
throw SyncError.encryptionFailed("Failed to create sealed box")
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return combined.base64EncodedString()
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/// Decrypts a string that was encrypted with encryptString
|
|
328
|
-
///
|
|
329
|
-
/// - Parameter encrypted: Base64-encoded encrypted string
|
|
330
|
-
/// - Returns: Decrypted plaintext string
|
|
331
|
-
/// - Throws: SyncError.encryptionFailed if decryption fails
|
|
332
|
-
private func decryptString(_ encrypted: String) throws -> String {
|
|
333
|
-
guard let combined = Data(base64Encoded: encrypted) else {
|
|
334
|
-
throw SyncError.encryptionFailed("Failed to decode base64")
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
let key = getOrCreateEncryptionKey()
|
|
338
|
-
let sealedBox = try AES.GCM.SealedBox(combined: combined)
|
|
339
|
-
let decrypted = try AES.GCM.open(sealedBox, using: key)
|
|
340
|
-
|
|
341
|
-
guard let plaintext = String(data: decrypted, encoding: .utf8) else {
|
|
342
|
-
throw SyncError.encryptionFailed("Failed to convert decrypted data to string")
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return plaintext
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/// Retrieves or creates an encryption key for sensitive data
|
|
349
|
-
///
|
|
350
|
-
/// The key is stored securely in the Keychain and persists across app launches.
|
|
351
|
-
///
|
|
352
|
-
/// - Returns: SymmetricKey for AES-GCM encryption
|
|
353
|
-
private func getOrCreateEncryptionKey() -> SymmetricKey {
|
|
354
|
-
let keychainKey = "com.runic.sync.encryption.key"
|
|
355
|
-
|
|
356
|
-
// Try to load existing key from Keychain
|
|
357
|
-
if let keyData = loadFromKeychain(key: keychainKey) {
|
|
358
|
-
return SymmetricKey(data: keyData)
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Generate new key
|
|
362
|
-
let key = SymmetricKey(size: .bits256)
|
|
363
|
-
let keyData = key.withUnsafeBytes { Data($0) }
|
|
364
|
-
|
|
365
|
-
// Store in Keychain
|
|
366
|
-
saveToKeychain(key: keychainKey, data: keyData)
|
|
367
|
-
|
|
368
|
-
return key
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private func saveToKeychain(key: String, data: Data) {
|
|
372
|
-
let query: [String: Any] = [
|
|
373
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
374
|
-
kSecAttrService as String: "com.sriinnu.athena.Runic",
|
|
375
|
-
kSecAttrAccount as String: key,
|
|
376
|
-
kSecUseDataProtectionKeychain as String: true,
|
|
377
|
-
kSecValueData as String: data,
|
|
378
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
379
|
-
]
|
|
380
|
-
SecItemDelete(query as CFDictionary)
|
|
381
|
-
SecItemAdd(query as CFDictionary, nil)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
private func loadFromKeychain(key: String) -> Data? {
|
|
385
|
-
var query: [String: Any] = [
|
|
386
|
-
kSecClass as String: kSecClassGenericPassword,
|
|
387
|
-
kSecAttrService as String: "com.sriinnu.athena.Runic",
|
|
388
|
-
kSecAttrAccount as String: key,
|
|
389
|
-
kSecUseDataProtectionKeychain as String: true,
|
|
390
|
-
kSecUseAuthenticationUI as String: "kSecUseAuthenticationUIFail" as CFString,
|
|
391
|
-
kSecReturnData as String: true,
|
|
392
|
-
]
|
|
393
|
-
let authContext = LAContext()
|
|
394
|
-
authContext.interactionNotAllowed = true
|
|
395
|
-
query[kSecUseAuthenticationContext as String] = authContext
|
|
396
|
-
var result: AnyObject?
|
|
397
|
-
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
398
|
-
if status == errSecInteractionNotAllowed {
|
|
399
|
-
return nil
|
|
400
|
-
}
|
|
401
|
-
return status == errSecSuccess ? result as? Data : nil
|
|
402
|
-
}
|