@sriinnu/runic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.swiftformat +50 -0
- package/.swiftlint.yml +160 -0
- package/CHANGELOG.md +307 -0
- package/CONTRIBUTING.md +456 -0
- package/Icon.icns +0 -0
- package/Icon.icon/Assets/runic.png +0 -0
- package/Icon.icon/icon.json +36 -0
- package/LICENSE +21 -0
- package/Package.resolved +51 -0
- package/Package.swift +136 -0
- package/Packages/Helix/LICENSE +21 -0
- package/Packages/Helix/Package.swift +29 -0
- package/Packages/Helix/README.md +976 -0
- package/Packages/Helix/Sources/Helix/Binding.swift +111 -0
- package/Packages/Helix/Sources/Helix/CommandParser.swift +160 -0
- package/Packages/Helix/Sources/Helix/CommandSignature.swift +117 -0
- package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +72 -0
- package/Packages/Helix/Sources/Helix/HelpGenerator.swift +175 -0
- package/Packages/Helix/Sources/Helix/NameSpecification.swift +98 -0
- package/Packages/Helix/Sources/Helix/ParsableCommand.swift +204 -0
- package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +44 -0
- package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +34 -0
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +91 -0
- package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +94 -0
- package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +167 -0
- package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +99 -0
- package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +146 -0
- package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +97 -0
- package/Packages/Helix/Sources/Helix/Program.swift +204 -0
- package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +367 -0
- package/Packages/Silo/LICENSE +21 -0
- package/Packages/Silo/Package.swift +34 -0
- package/Packages/Silo/README.md +466 -0
- package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +169 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +365 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +594 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +326 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +154 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +119 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +121 -0
- package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +391 -0
- package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +240 -0
- package/Packages/Silo/Sources/Silo/CryptoSupport.swift +86 -0
- package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +172 -0
- package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +172 -0
- package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +105 -0
- package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +191 -0
- package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +41 -0
- package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +110 -0
- package/README.md +109 -0
- package/RuniciOS/App/RuniciOSApp.swift +105 -0
- package/RuniciOS/Core/iOSUsageStore.swift +192 -0
- package/RuniciOS/Views/ProviderDetailView.swift +312 -0
- package/RuniciOS/Views/ProviderListView.swift +228 -0
- package/RuniciOS/Widgets/LargeWidget.swift +367 -0
- package/RuniciOS/Widgets/LiveActivity.swift +342 -0
- package/RuniciOS/Widgets/LockScreenWidget.swift +273 -0
- package/RuniciOS/Widgets/MediumWidget.swift +370 -0
- package/RuniciOS/Widgets/RunicWidget.swift +274 -0
- package/RuniciOS/Widgets/SmallWidget.swift +281 -0
- package/RuniciOS/Widgets/WidgetModels.swift +285 -0
- package/RuniciOS/Widgets/WidgetTimeline.swift +270 -0
- package/Scripts/CREATION-SUMMARY.md +367 -0
- package/Scripts/DIRECTORY-STRUCTURE.md +358 -0
- package/Scripts/GETTING-STARTED-CHECKLIST.md +364 -0
- package/Scripts/INDEX.md +365 -0
- package/Scripts/QUICK-REFERENCE.md +164 -0
- package/Scripts/README-SCRIPTS.md +926 -0
- package/Scripts/build-android.sh +526 -0
- package/Scripts/build-ios.sh +529 -0
- package/Scripts/build-macos.sh +603 -0
- package/Scripts/build-windows.sh +504 -0
- package/Scripts/build_icon.sh +49 -0
- package/Scripts/changelog-to-html.sh +89 -0
- package/Scripts/check-release-assets.sh +10 -0
- package/Scripts/ci-config.yml +474 -0
- package/Scripts/compile_and_run.sh +219 -0
- package/Scripts/compile_and_run_adhoc.sh +4 -0
- package/Scripts/docs-list.mjs +119 -0
- package/Scripts/make_appcast.sh +58 -0
- package/Scripts/package_app.sh +409 -0
- package/Scripts/release-lib.sh +214 -0
- package/Scripts/release.sh +76 -0
- package/Scripts/setup-all.sh +387 -0
- package/Scripts/setup-ios.sh +404 -0
- package/Scripts/setup-react-native.sh +523 -0
- package/Scripts/sign-and-notarize.sh +122 -0
- package/Scripts/test_live_update.sh +38 -0
- package/Scripts/validate_changelog.sh +23 -0
- package/Scripts/verify_appcast.sh +92 -0
- package/Sources/Runic/About.swift +93 -0
- package/Sources/Runic/AppNotifications.swift +115 -0
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +657 -0
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +518 -0
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +810 -0
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +87 -0
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +1379 -0
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +337 -0
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +395 -0
- package/Sources/Runic/Controllers/StatusItemController.swift +360 -0
- package/Sources/Runic/Core/BudgetNotificationManager.swift +80 -0
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +103 -0
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +154 -0
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +206 -0
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +95 -0
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +1216 -0
- package/Sources/Runic/Core/Rendering/IconView.swift +120 -0
- package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +71 -0
- package/Sources/Runic/Core/RunicAnimation.swift +53 -0
- package/Sources/Runic/Core/RunicColors.swift +73 -0
- package/Sources/Runic/Core/RunicCornerRadius.swift +15 -0
- package/Sources/Runic/Core/RunicSpacing.swift +68 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +158 -0
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +30 -0
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/SettingsStore.swift +1702 -0
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +20 -0
- package/Sources/Runic/Core/Stores/UsageStore+Status.swift +189 -0
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +33 -0
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +67 -0
- package/Sources/Runic/Core/Stores/UsageStore.swift +3564 -0
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +143 -0
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +143 -0
- package/Sources/Runic/Core/UsageExporter.swift +193 -0
- package/Sources/Runic/Date+RelativeDescription.swift +21 -0
- package/Sources/Runic/HiddenWindowView.swift +32 -0
- package/Sources/Runic/InstallOrigin.swift +9 -0
- package/Sources/Runic/KeyboardShortcuts+Names.swift +6 -0
- package/Sources/Runic/MouseLocationReader.swift +65 -0
- package/Sources/Runic/Notifications+Runic.swift +6 -0
- package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +503 -0
- package/Sources/Runic/ProviderRegistry.swift +100 -0
- package/Sources/Runic/ProviderSwitcherButtons.swift +267 -0
- package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +11 -0
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +14 -0
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +52 -0
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +42 -0
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +26 -0
- package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +14 -0
- package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +18 -0
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +193 -0
- package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +110 -0
- package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +28 -0
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +12 -0
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +20 -0
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +15 -0
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +32 -0
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +15 -0
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +48 -0
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +66 -0
- package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +12 -0
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +99 -0
- package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +18 -0
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +24 -0
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +24 -0
- package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +2 -0
- package/Sources/Runic/Resources/ProviderIcon-auggie.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-azure.svg +5 -0
- package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +7 -0
- package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-claude.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-codex.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-cohere.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-copilot.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-cursor.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-factory.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-gemini.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-groq.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-kimi.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-minimax.svg +1 -0
- package/Sources/Runic/Resources/ProviderIcon-mistral.svg +5 -0
- package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +7 -0
- package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-qwen.svg +9 -0
- package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +4 -0
- package/Sources/Runic/Resources/ProviderIcon-together.svg +5 -0
- package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +17 -0
- package/Sources/Runic/Resources/ProviderIcon-xai.svg +3 -0
- package/Sources/Runic/Resources/ProviderIcon-zai.svg +5 -0
- package/Sources/Runic/Resources/RunicMenubarIcon.svg +7 -0
- package/Sources/Runic/RunicApp.swift +357 -0
- package/Sources/Runic/SessionQuotaNotifications.swift +58 -0
- package/Sources/Runic/UsagePaceText.swift +52 -0
- package/Sources/Runic/UsageProgressBar.swift +224 -0
- package/Sources/Runic/Utilities/DisplayLink.swift +96 -0
- package/Sources/Runic/Utilities/LoadingPattern.swift +60 -0
- package/Sources/Runic/Utilities/PerformanceConstants.swift +85 -0
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +256 -0
- package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +22 -0
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +275 -0
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +377 -0
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +312 -0
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +312 -0
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +228 -0
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +228 -0
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +162 -0
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +64 -0
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +268 -0
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +104 -0
- package/Sources/Runic/Views/Menu/MenuCardView.swift +1900 -0
- package/Sources/Runic/Views/Menu/MenuContent.swift +150 -0
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +369 -0
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +42 -0
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +235 -0
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +141 -0
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +209 -0
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +220 -0
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +88 -0
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +201 -0
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +398 -0
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +232 -0
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +417 -0
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +190 -0
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +132 -0
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +618 -0
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +305 -0
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +271 -0
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +268 -0
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +422 -0
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +641 -0
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +344 -0
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +230 -0
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +322 -0
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +469 -0
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +231 -0
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +50 -0
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +497 -0
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +288 -0
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +3435 -0
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +25 -0
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +59 -0
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +206 -0
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +238 -0
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +371 -0
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +175 -0
- package/Sources/Runic/Views/Team/TeamManagementView.swift +761 -0
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +181 -0
- package/Sources/RunicCLI/CLIEntry.swift +942 -0
- package/Sources/RunicCLI/CLIRenderer.swift +87 -0
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +588 -0
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +362 -0
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +439 -0
- package/Sources/RunicCLI/Commands/ResetCommand.swift +429 -0
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +168 -0
- package/Sources/RunicClaudeWatchdog/main.swift +122 -0
- package/Sources/RunicClaudeWebProbe/main.swift +62 -0
- package/Sources/RunicCore/AlertRuleStore.swift +187 -0
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +25 -0
- package/Sources/RunicCore/CopilotUsageModels.swift +84 -0
- package/Sources/RunicCore/CostUsageFetcher.swift +119 -0
- package/Sources/RunicCore/CostUsageModels.swift +479 -0
- package/Sources/RunicCore/CreditsModels.swift +50 -0
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +269 -0
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +622 -0
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +500 -0
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +679 -0
- package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +135 -0
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +173 -0
- package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +79 -0
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +86 -0
- package/Sources/RunicCore/Logging/RunicLog.swift +123 -0
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +371 -0
- package/Sources/RunicCore/OpenAIDashboardModels.swift +210 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +676 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +492 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +51 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +483 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +558 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +204 -0
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +78 -0
- package/Sources/RunicCore/PathEnvironment.swift +482 -0
- package/Sources/RunicCore/Performance/PerformanceModels.swift +371 -0
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +409 -0
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +178 -0
- package/Sources/RunicCore/ProjectBudgetStore.swift +99 -0
- package/Sources/RunicCore/ProviderCostSnapshot.swift +29 -0
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +66 -0
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +685 -0
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +142 -0
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +141 -0
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +186 -0
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +126 -0
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +168 -0
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +332 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +180 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +137 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +207 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +654 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +28 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +480 -0
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +780 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +146 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +269 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +100 -0
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +243 -0
- package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +125 -0
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +24 -0
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +181 -0
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +85 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +123 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +114 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +100 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +291 -0
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +386 -0
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +65 -0
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +594 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +85 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +108 -0
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +398 -0
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +65 -0
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +1275 -0
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +65 -0
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +835 -0
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +71 -0
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +77 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +92 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +54 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +171 -0
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +911 -0
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +87 -0
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +207 -0
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/ProviderBranding.swift +25 -0
- package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +17 -0
- package/Sources/RunicCore/Providers/ProviderDescriptor.swift +129 -0
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +263 -0
- package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +98 -0
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +501 -0
- package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +68 -0
- package/Sources/RunicCore/Providers/Providers.swift +184 -0
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +30 -0
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +317 -0
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +84 -0
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +123 -0
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +48 -0
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +144 -0
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +83 -0
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +75 -0
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +72 -0
- package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +41 -0
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +724 -0
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +385 -0
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +310 -0
- package/Sources/RunicCore/Sync/SyncProtocol.swift +288 -0
- package/Sources/RunicCore/Sync/SyncRecord.swift +405 -0
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +294 -0
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +775 -0
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +333 -0
- package/Sources/RunicCore/Team/TeamModels.swift +583 -0
- package/Sources/RunicCore/Team/TeamShowbackExporter.swift +495 -0
- package/Sources/RunicCore/Team/TeamStore.swift +792 -0
- package/Sources/RunicCore/TextParsing.swift +55 -0
- package/Sources/RunicCore/UsageFetcher.swift +694 -0
- package/Sources/RunicCore/UsageFormatter.swift +410 -0
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +329 -0
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +448 -0
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +550 -0
- package/Sources/RunicCore/UsageLedger/UsageLedger.swift +29 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +660 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +209 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +417 -0
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +245 -0
- package/Sources/RunicCore/UsagePace.swift +81 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +73 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +74 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +208 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +455 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +132 -0
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +589 -0
- package/Sources/RunicCore/WidgetSnapshot.swift +183 -0
- package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +14 -0
- package/Sources/RunicMacros/ProviderRegistrationMacros.swift +216 -0
- package/Sources/RunicWidget/RunicWidgetBundle.swift +79 -0
- package/Sources/RunicWidget/RunicWidgetProvider.swift +283 -0
- package/Sources/RunicWidget/RunicWidgetViews.swift +680 -0
- package/Tests/RunicTests/AntigravityStatusProbeTests.swift +50 -0
- package/Tests/RunicTests/AppDelegateTests.swift +43 -0
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +22 -0
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +70 -0
- package/Tests/RunicTests/CLICostTests.swift +65 -0
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +22 -0
- package/Tests/RunicTests/CLISnapshotTests.swift +72 -0
- package/Tests/RunicTests/CLIWebFallbackTests.swift +58 -0
- package/Tests/RunicTests/ClaudeOAuthTests.swift +164 -0
- package/Tests/RunicTests/ClaudeResilienceTests.swift +30 -0
- package/Tests/RunicTests/ClaudeUsageTests.swift +370 -0
- package/Tests/RunicTests/CodexOAuthTests.swift +122 -0
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +82 -0
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +75 -0
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +75 -0
- package/Tests/RunicTests/CostUsageDecodingTests.swift +394 -0
- package/Tests/RunicTests/CostUsagePricingTests.swift +50 -0
- package/Tests/RunicTests/CostUsageScannerTests.swift +700 -0
- package/Tests/RunicTests/CursorStatusProbeTests.swift +323 -0
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +69 -0
- package/Tests/RunicTests/GeminiAPITestHelpers.swift +94 -0
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +28 -0
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +283 -0
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +279 -0
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +233 -0
- package/Tests/RunicTests/GeminiTestEnvironment.swift +87 -0
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +73 -0
- package/Tests/RunicTests/InstallOriginTests.swift +18 -0
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +9 -0
- package/Tests/RunicTests/LiveAccountTests.swift +24 -0
- package/Tests/RunicTests/LoadingPatternTests.swift +35 -0
- package/Tests/RunicTests/MenuCardModelTests.swift +545 -0
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +8 -0
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +8 -0
- package/Tests/RunicTests/MiniMaxParsingTests.swift +68 -0
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +8 -0
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +112 -0
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +17 -0
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +75 -0
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +27 -0
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +134 -0
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +53 -0
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +92 -0
- package/Tests/RunicTests/PathBuilderTests.swift +237 -0
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +160 -0
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +38 -0
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +16 -0
- package/Tests/RunicTests/ProviderRegistryTests.swift +29 -0
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +202 -0
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +51 -0
- package/Tests/RunicTests/RunicTests.swift +120 -0
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +36 -0
- package/Tests/RunicTests/SettingsStoreTests.swift +161 -0
- package/Tests/RunicTests/StatusItemAnimationTests.swift +49 -0
- package/Tests/RunicTests/StatusMenuTests.swift +434 -0
- package/Tests/RunicTests/StatusProbeTests.swift +377 -0
- package/Tests/RunicTests/SubprocessRunnerTests.swift +20 -0
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +65 -0
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +148 -0
- package/Tests/RunicTests/TTYIntegrationTests.swift +41 -0
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +159 -0
- package/Tests/RunicTests/TextParsingTests.swift +12 -0
- package/Tests/RunicTests/UsageFormatterTests.swift +112 -0
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +172 -0
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +213 -0
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +83 -0
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +170 -0
- package/Tests/RunicTests/UsagePaceTests.swift +77 -0
- package/Tests/RunicTests/UsagePaceTextTests.swift +109 -0
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +91 -0
- package/Tests/RunicTests/WidgetSnapshotTests.swift +45 -0
- package/Tests/RunicTests/ZaiProviderTests.swift +61 -0
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +6 -0
- package/TestsLinux/PlatformGatingTests.swift +46 -0
- package/appcast.xml +105 -0
- package/assets/logo-concept-stack.svg +24 -0
- package/assets/logo-minimal-bars.svg +15 -0
- package/assets/logo.svg +20 -0
- package/assets/logo2.svg +43 -0
- package/bin/docs-list +5 -0
- package/bin/install-runic-cli.sh +32 -0
- package/build-and-run.sh +369 -0
- package/docs/.nojekyll +1 -0
- package/docs/CNAME +1 -0
- package/docs/antigravity.md +75 -0
- package/docs/architecture.md +32 -0
- package/docs/claude.md +93 -0
- package/docs/cli-commands/integration_guide.md +371 -0
- package/docs/cli-commands/quick_reference.md +362 -0
- package/docs/cli-commands/readme.md +361 -0
- package/docs/cli.md +156 -0
- package/docs/cloudkit_sync_guide.md +495 -0
- package/docs/codebase_review.md +288 -0
- package/docs/codex-oauth.md +635 -0
- package/docs/codex.md +104 -0
- package/docs/codexbar.png +0 -0
- package/docs/copilot.md +44 -0
- package/docs/cursor.md +46 -0
- package/docs/design/designcomparisonchart.md +510 -0
- package/docs/design/implementationguide.md +792 -0
- package/docs/design/menubaricondesignconcepts.md +796 -0
- package/docs/design/mockups/hybridiconconcept.svg +62 -0
- package/docs/design/mockups/infinityiconconcept.svg +41 -0
- package/docs/design/mockups/infinityiconstates.svg +129 -0
- package/docs/design/mockups/wavelogorefined.svg +48 -0
- package/docs/design/quick_summary.md +426 -0
- package/docs/design/readme.md +478 -0
- package/docs/design/sizespecifications.md +575 -0
- package/docs/design-system.md +836 -0
- package/docs/factory.md +103 -0
- package/docs/gemini.md +73 -0
- package/docs/icon.md +37 -0
- package/docs/icon.png +0 -0
- package/docs/index.html +175 -0
- package/docs/keychain_fix.md +274 -0
- package/docs/minimax.md +71 -0
- package/docs/packaging.md +25 -0
- package/docs/progress-bar-improvements.md +240 -0
- package/docs/progress-bar-quick-reference.md +283 -0
- package/docs/progress-bar-technical-details.md +432 -0
- package/docs/progress-bar-visual-test.swift +375 -0
- package/docs/provider.md +202 -0
- package/docs/providers.md +86 -0
- package/docs/refactor/macros.md +35 -0
- package/docs/refresh-loop.md +22 -0
- package/docs/releasing-homebrew.md +35 -0
- package/docs/releasing.md +121 -0
- package/docs/research/concrete_todo_2026-02-22.md +47 -0
- package/docs/research/core-feature-research.md +248 -0
- package/docs/research/feature_uiux_roadmap_2026-02-22.md +202 -0
- package/docs/research/parity_matrix.md +49 -0
- package/docs/research/provider_expansion_candidates_2026-02-22.md +80 -0
- package/docs/research/provider_model_token_project_matrix_2026-02-23.md +46 -0
- package/docs/research/release_gate_checklist.md +57 -0
- package/docs/site.css +505 -0
- package/docs/sparkle.md +28 -0
- package/docs/status.md +28 -0
- package/docs/sync/implementation.md +393 -0
- package/docs/sync/readme.md +317 -0
- package/docs/sync_files_summary.md +391 -0
- package/docs/sync_quick_reference.md +225 -0
- package/docs/todo.md +11 -0
- package/docs/ui.md +29 -0
- package/docs/web-integration.md +9 -0
- package/docs/widgets.md +85 -0
- package/docs/workflows/error-handling-improvements.md +434 -0
- package/docs/workflows/folder_structure.md +299 -0
- package/docs/workflows/loading_states.md +446 -0
- package/docs/workflows/loading_states_examples.md +565 -0
- package/docs/workflows/loading_states_flow.md +430 -0
- package/docs/workflows/performance_optimizations.md +457 -0
- package/docs/workflows/phase1-cli-testing-report.md +313 -0
- package/docs/workflows/quick_reference.md +228 -0
- package/docs/workflows/readme.md +59 -0
- package/docs/workflows/recent_improvements.md +275 -0
- package/docs/workflows/sync_enablement_summary.md +400 -0
- package/docs/zai.md +43 -0
- package/package.json +36 -0
- package/runic.png +0 -0
- package/version.env +2 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Runic Sync Implementation Checklist
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This document provides step-by-step instructions for integrating the Runic iCloud sync infrastructure into your application.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- [ ] Xcode 15.0 or later
|
|
10
|
+
- [ ] macOS 14.0+ or iOS 17.0+ deployment target
|
|
11
|
+
- [ ] Active Apple Developer Program membership
|
|
12
|
+
- [ ] iCloud entitlements configured
|
|
13
|
+
|
|
14
|
+
## Setup Steps
|
|
15
|
+
|
|
16
|
+
### 1. Enable CloudKit Capability
|
|
17
|
+
|
|
18
|
+
1. Open your Xcode project
|
|
19
|
+
2. Select your app target
|
|
20
|
+
3. Go to "Signing & Capabilities" tab
|
|
21
|
+
4. Click "+ Capability" button
|
|
22
|
+
5. Add "iCloud" capability
|
|
23
|
+
6. Enable "CloudKit" checkbox
|
|
24
|
+
7. Select or create a CloudKit container
|
|
25
|
+
|
|
26
|
+
**Container ID Format**: `iCloud.com.yourcompany.runic`
|
|
27
|
+
|
|
28
|
+
### 2. Configure CloudKit Schema
|
|
29
|
+
|
|
30
|
+
Log into [CloudKit Dashboard](https://icloud.developer.apple.com/dashboard)
|
|
31
|
+
|
|
32
|
+
#### Create Record Types
|
|
33
|
+
|
|
34
|
+
**UsageSnapshot**
|
|
35
|
+
```
|
|
36
|
+
Fields:
|
|
37
|
+
- version (Int64)
|
|
38
|
+
- modifiedAt (Date/Time)
|
|
39
|
+
- lastModifiedDeviceID (String, optional)
|
|
40
|
+
- providerID (String)
|
|
41
|
+
- primaryUsed (Int64)
|
|
42
|
+
- primaryLimit (Int64, optional)
|
|
43
|
+
- secondaryUsed (Int64, optional)
|
|
44
|
+
- secondaryLimit (Int64, optional)
|
|
45
|
+
- costUSD (Double, optional)
|
|
46
|
+
- accountEmail (String, optional) [encrypted]
|
|
47
|
+
- updatedAt (Date/Time)
|
|
48
|
+
- deviceName (String)
|
|
49
|
+
- platform (String)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**UserPreferences**
|
|
53
|
+
```
|
|
54
|
+
Fields:
|
|
55
|
+
- version (Int64)
|
|
56
|
+
- modifiedAt (Date/Time)
|
|
57
|
+
- lastModifiedDeviceID (String, optional)
|
|
58
|
+
- refreshInterval (Double)
|
|
59
|
+
- enabledProviders (List<String>)
|
|
60
|
+
- notificationsEnabled (Int64) [0 or 1]
|
|
61
|
+
- autoRefreshEnabled (Int64) [0 or 1]
|
|
62
|
+
- theme (String)
|
|
63
|
+
- displayFormat (String)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**AlertConfiguration**
|
|
67
|
+
```
|
|
68
|
+
Fields:
|
|
69
|
+
- version (Int64)
|
|
70
|
+
- modifiedAt (Date/Time)
|
|
71
|
+
- lastModifiedDeviceID (String, optional)
|
|
72
|
+
- providerID (String)
|
|
73
|
+
- warningThreshold (Double)
|
|
74
|
+
- criticalThreshold (Double)
|
|
75
|
+
- notificationChannels (List<String>)
|
|
76
|
+
- enabled (Int64) [0 or 1]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Set Permissions
|
|
80
|
+
|
|
81
|
+
For all record types:
|
|
82
|
+
- Database: **Private**
|
|
83
|
+
- Read/Write: **Owner**
|
|
84
|
+
|
|
85
|
+
### 3. Update Entitlements File
|
|
86
|
+
|
|
87
|
+
Ensure your `*.entitlements` file contains:
|
|
88
|
+
|
|
89
|
+
```xml
|
|
90
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
91
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
92
|
+
<plist version="1.0">
|
|
93
|
+
<dict>
|
|
94
|
+
<key>com.apple.developer.icloud-container-identifiers</key>
|
|
95
|
+
<array>
|
|
96
|
+
<string>iCloud.com.yourcompany.runic</string>
|
|
97
|
+
</array>
|
|
98
|
+
<key>com.apple.developer.icloud-services</key>
|
|
99
|
+
<array>
|
|
100
|
+
<string>CloudKit</string>
|
|
101
|
+
</array>
|
|
102
|
+
</dict>
|
|
103
|
+
</plist>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. Initialize Sync in Your App
|
|
107
|
+
|
|
108
|
+
#### AppDelegate (UIKit) or App (SwiftUI)
|
|
109
|
+
|
|
110
|
+
```swift
|
|
111
|
+
import RunicCore
|
|
112
|
+
|
|
113
|
+
class AppDelegate: NSObject, UIApplicationDelegate {
|
|
114
|
+
var syncManager: BackgroundSyncManager?
|
|
115
|
+
|
|
116
|
+
func application(
|
|
117
|
+
_ application: UIApplication,
|
|
118
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
|
119
|
+
) -> Bool {
|
|
120
|
+
Task {
|
|
121
|
+
await setupSync()
|
|
122
|
+
}
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private func setupSync() async {
|
|
127
|
+
let syncEngine = iCloudSyncEngine(
|
|
128
|
+
containerIdentifier: "iCloud.com.yourcompany.runic"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
let config = SyncConfiguration(
|
|
132
|
+
activeSyncInterval: 300,
|
|
133
|
+
backgroundSyncInterval: 3600,
|
|
134
|
+
syncOnForeground: true,
|
|
135
|
+
syncOnBackground: true
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
syncManager = BackgroundSyncManager(
|
|
139
|
+
syncEngine: syncEngine,
|
|
140
|
+
config: config
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
await syncManager?.start()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### SwiftUI App
|
|
149
|
+
|
|
150
|
+
```swift
|
|
151
|
+
import SwiftUI
|
|
152
|
+
import RunicCore
|
|
153
|
+
|
|
154
|
+
@main
|
|
155
|
+
struct RunicApp: App {
|
|
156
|
+
@StateObject private var syncCoordinator = SyncCoordinator()
|
|
157
|
+
|
|
158
|
+
var body: some Scene {
|
|
159
|
+
WindowGroup {
|
|
160
|
+
ContentView()
|
|
161
|
+
.environmentObject(syncCoordinator)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@MainActor
|
|
167
|
+
class SyncCoordinator: ObservableObject {
|
|
168
|
+
private var syncManager: BackgroundSyncManager?
|
|
169
|
+
|
|
170
|
+
init() {
|
|
171
|
+
Task {
|
|
172
|
+
await setupSync()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private func setupSync() async {
|
|
177
|
+
let syncEngine = iCloudSyncEngine()
|
|
178
|
+
let config = SyncConfiguration.default
|
|
179
|
+
|
|
180
|
+
syncManager = BackgroundSyncManager(
|
|
181
|
+
syncEngine: syncEngine,
|
|
182
|
+
config: config
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
await syncManager?.start()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func manualSync() async {
|
|
189
|
+
await syncManager?.syncNow()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 5. Integrate with Usage Tracking
|
|
195
|
+
|
|
196
|
+
#### Sync Usage Snapshots
|
|
197
|
+
|
|
198
|
+
```swift
|
|
199
|
+
// After fetching usage data
|
|
200
|
+
func syncUsageData(usage: UsageSnapshot, provider: UsageProvider) async {
|
|
201
|
+
let syncRecord = UsageSnapshotSyncRecord(
|
|
202
|
+
providerID: provider.rawValue,
|
|
203
|
+
primaryUsed: usage.primary.used,
|
|
204
|
+
primaryLimit: usage.primary.limit,
|
|
205
|
+
secondaryUsed: usage.secondary?.used,
|
|
206
|
+
secondaryLimit: usage.secondary?.limit,
|
|
207
|
+
costUSD: usage.providerCost?.totalCostUSD,
|
|
208
|
+
accountEmail: usage.identity?.accountEmail,
|
|
209
|
+
updatedAt: usage.updatedAt,
|
|
210
|
+
deviceName: getDeviceName(),
|
|
211
|
+
platform: getPlatform()
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
await syncEngine.enqueue(syncRecord, priority: .normal)
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### Sync Preferences
|
|
219
|
+
|
|
220
|
+
```swift
|
|
221
|
+
func syncPreferences(settings: AppSettings) async {
|
|
222
|
+
let prefsRecord = UserPreferencesSyncRecord(
|
|
223
|
+
refreshInterval: settings.refreshInterval,
|
|
224
|
+
enabledProviders: settings.enabledProviders,
|
|
225
|
+
notificationsEnabled: settings.notificationsEnabled,
|
|
226
|
+
autoRefreshEnabled: settings.autoRefreshEnabled,
|
|
227
|
+
theme: settings.theme,
|
|
228
|
+
displayFormat: settings.displayFormat
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
let result = await syncEngine.push(records: [prefsRecord])
|
|
232
|
+
// Handle result
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 6. Add Sync Status UI
|
|
237
|
+
|
|
238
|
+
```swift
|
|
239
|
+
import SwiftUI
|
|
240
|
+
|
|
241
|
+
struct SyncStatusView: View {
|
|
242
|
+
@State private var stats: SyncStatistics?
|
|
243
|
+
|
|
244
|
+
var body: some View {
|
|
245
|
+
VStack(alignment: .leading, spacing: 8) {
|
|
246
|
+
if let stats = stats {
|
|
247
|
+
Text("Last Sync: \(formattedDate(stats.lastSyncDate))")
|
|
248
|
+
Text("Success Rate: \(String(format: "%.1f%%", stats.successRate))")
|
|
249
|
+
Text("Total Syncs: \(stats.totalSyncs)")
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Button("Sync Now") {
|
|
253
|
+
Task {
|
|
254
|
+
await performManualSync()
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
.task {
|
|
259
|
+
await loadStats()
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private func performManualSync() async {
|
|
264
|
+
// Trigger manual sync
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private func loadStats() async {
|
|
268
|
+
// Load sync statistics
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private func formattedDate(_ date: Date?) -> String {
|
|
272
|
+
guard let date = date else { return "Never" }
|
|
273
|
+
let formatter = RelativeDateTimeFormatter()
|
|
274
|
+
return formatter.localizedString(for: date, relativeTo: Date())
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Testing
|
|
280
|
+
|
|
281
|
+
### Development Testing
|
|
282
|
+
|
|
283
|
+
1. Use CloudKit Development environment
|
|
284
|
+
2. Test with multiple devices/simulators
|
|
285
|
+
3. Verify conflict resolution
|
|
286
|
+
4. Test offline scenarios
|
|
287
|
+
|
|
288
|
+
### Pre-Production Checklist
|
|
289
|
+
|
|
290
|
+
- [ ] Tested on physical devices (not just simulator)
|
|
291
|
+
- [ ] Tested with poor network conditions
|
|
292
|
+
- [ ] Tested conflict resolution with concurrent edits
|
|
293
|
+
- [ ] Verified encryption of sensitive data
|
|
294
|
+
- [ ] Monitored CloudKit quota usage
|
|
295
|
+
- [ ] Tested background sync behavior
|
|
296
|
+
- [ ] Verified sync recovery after network interruption
|
|
297
|
+
|
|
298
|
+
### Production Deployment
|
|
299
|
+
|
|
300
|
+
1. Deploy CloudKit schema to Production
|
|
301
|
+
2. Test with TestFlight builds
|
|
302
|
+
3. Monitor CloudKit Dashboard for errors
|
|
303
|
+
4. Set up CloudKit usage alerts
|
|
304
|
+
|
|
305
|
+
## Monitoring
|
|
306
|
+
|
|
307
|
+
### CloudKit Dashboard Metrics
|
|
308
|
+
|
|
309
|
+
Monitor:
|
|
310
|
+
- Request rate
|
|
311
|
+
- Error rate
|
|
312
|
+
- Storage usage
|
|
313
|
+
- Transfer bandwidth
|
|
314
|
+
|
|
315
|
+
### App Analytics
|
|
316
|
+
|
|
317
|
+
Track:
|
|
318
|
+
- Sync success rate
|
|
319
|
+
- Average sync duration
|
|
320
|
+
- Conflict frequency
|
|
321
|
+
- User engagement with sync features
|
|
322
|
+
|
|
323
|
+
## Troubleshooting
|
|
324
|
+
|
|
325
|
+
### Common Issues
|
|
326
|
+
|
|
327
|
+
**Issue**: "iCloud account unavailable"
|
|
328
|
+
- **Solution**: User needs to sign in to iCloud in Settings
|
|
329
|
+
|
|
330
|
+
**Issue**: Sync taking too long
|
|
331
|
+
- **Solution**: Reduce batch size or sync frequency
|
|
332
|
+
|
|
333
|
+
**Issue**: Conflicts occurring frequently
|
|
334
|
+
- **Solution**: Review conflict resolution strategy
|
|
335
|
+
|
|
336
|
+
**Issue**: Storage quota exceeded
|
|
337
|
+
- **Solution**: Implement data pruning or notify user
|
|
338
|
+
|
|
339
|
+
### Debug Mode
|
|
340
|
+
|
|
341
|
+
Enable verbose logging:
|
|
342
|
+
|
|
343
|
+
```swift
|
|
344
|
+
let options = SyncOptions(
|
|
345
|
+
conflictStrategy: .lastWriteWins
|
|
346
|
+
// Add logging here if needed
|
|
347
|
+
)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Performance Optimization
|
|
351
|
+
|
|
352
|
+
### Best Practices
|
|
353
|
+
|
|
354
|
+
1. **Batch Operations**: Group multiple records into single sync
|
|
355
|
+
2. **Incremental Sync**: Use change tokens for efficient syncs
|
|
356
|
+
3. **Selective Sync**: Only sync changed records
|
|
357
|
+
4. **Compression**: Consider compressing large payloads
|
|
358
|
+
5. **Caching**: Cache frequently accessed data locally
|
|
359
|
+
|
|
360
|
+
### Monitoring Performance
|
|
361
|
+
|
|
362
|
+
```swift
|
|
363
|
+
let stats = await syncManager.statistics
|
|
364
|
+
print("Average sync duration: \(stats.averageDuration)s")
|
|
365
|
+
|
|
366
|
+
if stats.averageDuration > 5.0 {
|
|
367
|
+
print("⚠️ Sync performance degraded")
|
|
368
|
+
// Investigate or adjust configuration
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Security Considerations
|
|
373
|
+
|
|
374
|
+
1. **Encryption**: Sensitive fields are automatically encrypted
|
|
375
|
+
2. **Keychain**: Encryption keys stored in iOS/macOS Keychain
|
|
376
|
+
3. **Private Database**: All data in user's private CloudKit database
|
|
377
|
+
4. **No Shared Data**: Each user's data is isolated
|
|
378
|
+
|
|
379
|
+
## Support
|
|
380
|
+
|
|
381
|
+
For issues or questions:
|
|
382
|
+
- Check CloudKit Dashboard for errors
|
|
383
|
+
- Review sync statistics
|
|
384
|
+
- Enable debug logging
|
|
385
|
+
- Contact support with device logs
|
|
386
|
+
|
|
387
|
+
## Version History
|
|
388
|
+
|
|
389
|
+
- **v1.0.0**: Initial release with CloudKit sync
|
|
390
|
+
- Support for macOS and iOS
|
|
391
|
+
- Three record types (Usage, Preferences, Alerts)
|
|
392
|
+
- Conflict resolution with multiple strategies
|
|
393
|
+
- Background sync with configurable intervals
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
# Runic iCloud Sync Infrastructure
|
|
2
|
+
|
|
3
|
+
Production-ready CloudKit synchronization system for macOS/iOS usage tracking.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Runic Sync infrastructure provides seamless cross-device synchronization of usage data, user preferences, and alert configurations using Apple's CloudKit framework.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
### Core Components
|
|
12
|
+
|
|
13
|
+
1. **SyncProtocol.swift** - Protocol definitions and interfaces
|
|
14
|
+
2. **iCloudSyncEngine.swift** - CloudKit integration implementation
|
|
15
|
+
3. **SyncConflictResolver.swift** - Conflict resolution logic
|
|
16
|
+
4. **SyncRecord.swift** - Data models for CloudKit records
|
|
17
|
+
5. **BackgroundSyncManager.swift** - Background sync coordination
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- ✅ **Privacy-First**: AES-GCM encryption for sensitive data
|
|
22
|
+
- ✅ **Conflict Resolution**: Last-write-wins with version tracking
|
|
23
|
+
- ✅ **Offline Support**: Queue-based pending sync operations
|
|
24
|
+
- ✅ **Background Sync**: Automatic sync at configurable intervals
|
|
25
|
+
- ✅ **Error Recovery**: Retry logic with exponential backoff
|
|
26
|
+
- ✅ **Multi-Platform**: Supports macOS and iOS
|
|
27
|
+
|
|
28
|
+
## CloudKit Schema
|
|
29
|
+
|
|
30
|
+
### Record Types
|
|
31
|
+
|
|
32
|
+
#### UsageSnapshot
|
|
33
|
+
- `recordID`: String (unique identifier)
|
|
34
|
+
- `version`: Int (for conflict resolution)
|
|
35
|
+
- `modifiedAt`: Date (last modification timestamp)
|
|
36
|
+
- `providerID`: String (AI provider identifier)
|
|
37
|
+
- `primaryUsed`: Int (tokens/requests used)
|
|
38
|
+
- `primaryLimit`: Int? (limit if applicable)
|
|
39
|
+
- `secondaryUsed`: Int? (secondary metric)
|
|
40
|
+
- `costUSD`: Double? (estimated cost)
|
|
41
|
+
- `accountEmail`: String (encrypted)
|
|
42
|
+
- `deviceName`: String
|
|
43
|
+
- `platform`: String
|
|
44
|
+
|
|
45
|
+
#### UserPreferences
|
|
46
|
+
- `recordID`: String (singleton: "user-preferences")
|
|
47
|
+
- `version`: Int
|
|
48
|
+
- `refreshInterval`: TimeInterval
|
|
49
|
+
- `enabledProviders`: [String]
|
|
50
|
+
- `notificationsEnabled`: Bool
|
|
51
|
+
- `autoRefreshEnabled`: Bool
|
|
52
|
+
- `theme`: String
|
|
53
|
+
- `displayFormat`: String
|
|
54
|
+
|
|
55
|
+
#### AlertConfiguration
|
|
56
|
+
- `recordID`: String
|
|
57
|
+
- `providerID`: String
|
|
58
|
+
- `warningThreshold`: Double (0.0-1.0)
|
|
59
|
+
- `criticalThreshold`: Double (0.0-1.0)
|
|
60
|
+
- `notificationChannels`: [String]
|
|
61
|
+
- `enabled`: Bool
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
### Initialize Sync Engine
|
|
66
|
+
|
|
67
|
+
```swift
|
|
68
|
+
import RunicCore
|
|
69
|
+
|
|
70
|
+
// Initialize the sync engine
|
|
71
|
+
let syncEngine = iCloudSyncEngine()
|
|
72
|
+
|
|
73
|
+
// Create background sync manager
|
|
74
|
+
let syncManager = BackgroundSyncManager(
|
|
75
|
+
syncEngine: syncEngine,
|
|
76
|
+
config: .default
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
// Start automatic sync
|
|
80
|
+
await syncManager.start()
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Manual Sync
|
|
84
|
+
|
|
85
|
+
```swift
|
|
86
|
+
// Perform manual sync
|
|
87
|
+
let result = await syncManager.syncNow()
|
|
88
|
+
|
|
89
|
+
switch result {
|
|
90
|
+
case .success(let syncResult):
|
|
91
|
+
print("Synced: \(syncResult.fetchedCount) fetched, \(syncResult.pushedCount) pushed")
|
|
92
|
+
case .failure(let error):
|
|
93
|
+
print("Sync failed: \(error.localizedDescription)")
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Sync Usage Data
|
|
98
|
+
|
|
99
|
+
```swift
|
|
100
|
+
let usageRecord = UsageSnapshotSyncRecord(
|
|
101
|
+
providerID: "claude",
|
|
102
|
+
primaryUsed: 50000,
|
|
103
|
+
primaryLimit: 100000,
|
|
104
|
+
costUSD: 2.50,
|
|
105
|
+
deviceName: "MacBook Pro",
|
|
106
|
+
platform: "macOS"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// Queue for sync
|
|
110
|
+
await syncEngine.enqueue(usageRecord, priority: .high)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Configure Sync Intervals
|
|
114
|
+
|
|
115
|
+
```swift
|
|
116
|
+
let config = SyncConfiguration(
|
|
117
|
+
activeSyncInterval: 300, // 5 minutes when active
|
|
118
|
+
backgroundSyncInterval: 3600, // 1 hour in background
|
|
119
|
+
syncOnForeground: true,
|
|
120
|
+
syncOnBackground: true
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
let syncManager = BackgroundSyncManager(
|
|
124
|
+
syncEngine: syncEngine,
|
|
125
|
+
config: config
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Observe Sync Events
|
|
130
|
+
|
|
131
|
+
```swift
|
|
132
|
+
class MyObserver: SyncObserver {
|
|
133
|
+
func syncDidStart() {
|
|
134
|
+
print("Sync started")
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
func syncDidProgress(current: Int, total: Int) {
|
|
138
|
+
print("Progress: \(current)/\(total)")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
func syncDidComplete(result: SyncResult) {
|
|
142
|
+
print("Sync completed: \(result.duration)s")
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func syncDidFail(error: SyncError) {
|
|
146
|
+
print("Sync failed: \(error)")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let observer = MyObserver()
|
|
151
|
+
await syncManager.addObserver(observer)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Conflict Resolution
|
|
155
|
+
|
|
156
|
+
The system supports multiple conflict resolution strategies:
|
|
157
|
+
|
|
158
|
+
### Last-Write-Wins (Default)
|
|
159
|
+
```swift
|
|
160
|
+
let options = SyncOptions(conflictStrategy: .lastWriteWins)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Prefer Local
|
|
164
|
+
```swift
|
|
165
|
+
let options = SyncOptions(conflictStrategy: .preferLocal)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Custom Merge
|
|
169
|
+
```swift
|
|
170
|
+
let resolver = SyncConflictResolver()
|
|
171
|
+
await resolver.registerMergeHandler(for: "UsageSnapshot") { local, remote in
|
|
172
|
+
// Custom merge logic
|
|
173
|
+
return local // or merged result
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Security
|
|
178
|
+
|
|
179
|
+
### Encryption
|
|
180
|
+
Sensitive fields (email addresses, tokens) are encrypted using AES-GCM before syncing:
|
|
181
|
+
|
|
182
|
+
```swift
|
|
183
|
+
// Automatically encrypted
|
|
184
|
+
let record = UsageSnapshotSyncRecord(
|
|
185
|
+
accountEmail: "user@example.com", // Encrypted in CloudKit
|
|
186
|
+
// ...
|
|
187
|
+
)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Keychain Storage
|
|
191
|
+
Encryption keys are stored securely in the macOS/iOS Keychain.
|
|
192
|
+
|
|
193
|
+
## Error Handling
|
|
194
|
+
|
|
195
|
+
### Common Errors
|
|
196
|
+
|
|
197
|
+
- `iCloudAccountUnavailable`: User not signed into iCloud
|
|
198
|
+
- `networkUnavailable`: No network connection (queued for retry)
|
|
199
|
+
- `quotaExceeded`: iCloud storage quota exceeded
|
|
200
|
+
- `conflictResolutionFailed`: Unable to resolve conflict
|
|
201
|
+
|
|
202
|
+
### Retry Logic
|
|
203
|
+
|
|
204
|
+
Failed syncs are automatically retried with exponential backoff:
|
|
205
|
+
- Initial retry: 2 seconds
|
|
206
|
+
- Second retry: 4 seconds
|
|
207
|
+
- Third retry: 8 seconds
|
|
208
|
+
|
|
209
|
+
## Performance
|
|
210
|
+
|
|
211
|
+
### Sync Statistics
|
|
212
|
+
|
|
213
|
+
```swift
|
|
214
|
+
let stats = await syncManager.statistics
|
|
215
|
+
|
|
216
|
+
print(stats.summary)
|
|
217
|
+
// Output:
|
|
218
|
+
// Sync Statistics:
|
|
219
|
+
// Total syncs: 42
|
|
220
|
+
// Successful: 40 (95.2%)
|
|
221
|
+
// Failed: 2
|
|
222
|
+
// Average duration: 1.23s
|
|
223
|
+
// Last sync: 2 minutes ago
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Optimization Tips
|
|
227
|
+
|
|
228
|
+
1. Use appropriate sync intervals (default: 5min active, 1hr background)
|
|
229
|
+
2. Batch updates before syncing
|
|
230
|
+
3. Enable background sync for seamless experience
|
|
231
|
+
4. Monitor sync statistics for issues
|
|
232
|
+
|
|
233
|
+
## CloudKit Setup
|
|
234
|
+
|
|
235
|
+
### 1. Enable CloudKit in Xcode
|
|
236
|
+
|
|
237
|
+
1. Open project settings
|
|
238
|
+
2. Select target → Signing & Capabilities
|
|
239
|
+
3. Add "iCloud" capability
|
|
240
|
+
4. Enable "CloudKit"
|
|
241
|
+
|
|
242
|
+
### 2. Configure Container
|
|
243
|
+
|
|
244
|
+
1. Create CloudKit container or use default
|
|
245
|
+
2. Add record types via CloudKit Dashboard
|
|
246
|
+
3. Set permissions to "Private" database
|
|
247
|
+
|
|
248
|
+
### 3. Entitlements
|
|
249
|
+
|
|
250
|
+
Ensure `entitlements` file contains:
|
|
251
|
+
|
|
252
|
+
```xml
|
|
253
|
+
<key>com.apple.developer.icloud-container-identifiers</key>
|
|
254
|
+
<array>
|
|
255
|
+
<string>iCloud.com.yourcompany.runic</string>
|
|
256
|
+
</array>
|
|
257
|
+
<key>com.apple.developer.icloud-services</key>
|
|
258
|
+
<array>
|
|
259
|
+
<string>CloudKit</string>
|
|
260
|
+
</array>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Testing
|
|
264
|
+
|
|
265
|
+
### Unit Tests
|
|
266
|
+
|
|
267
|
+
```swift
|
|
268
|
+
import XCTest
|
|
269
|
+
@testable import RunicCore
|
|
270
|
+
|
|
271
|
+
class SyncTests: XCTestCase {
|
|
272
|
+
func testConflictResolution() async {
|
|
273
|
+
let resolver = SyncConflictResolver()
|
|
274
|
+
|
|
275
|
+
let local = UsageSnapshotSyncRecord(/* ... */)
|
|
276
|
+
let remote = UsageSnapshotSyncRecord(/* ... */)
|
|
277
|
+
|
|
278
|
+
let resolved = resolver.resolve(
|
|
279
|
+
local: local,
|
|
280
|
+
remote: remote,
|
|
281
|
+
strategy: .lastWriteWins
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
XCTAssertNotNil(resolved)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Integration Tests
|
|
290
|
+
|
|
291
|
+
Test with CloudKit Development environment before production.
|
|
292
|
+
|
|
293
|
+
## Troubleshooting
|
|
294
|
+
|
|
295
|
+
### Sync Not Working
|
|
296
|
+
|
|
297
|
+
1. Check iCloud account status
|
|
298
|
+
2. Verify network connectivity
|
|
299
|
+
3. Check CloudKit Dashboard for errors
|
|
300
|
+
4. Review sync statistics
|
|
301
|
+
|
|
302
|
+
### Data Not Appearing
|
|
303
|
+
|
|
304
|
+
1. Ensure sync completed successfully
|
|
305
|
+
2. Check conflict resolution logs
|
|
306
|
+
3. Verify record types match schema
|
|
307
|
+
|
|
308
|
+
### Performance Issues
|
|
309
|
+
|
|
310
|
+
1. Reduce sync frequency
|
|
311
|
+
2. Batch operations
|
|
312
|
+
3. Check network quality
|
|
313
|
+
4. Monitor CloudKit quota
|
|
314
|
+
|
|
315
|
+
## License
|
|
316
|
+
|
|
317
|
+
Copyright © 2026 Runic. All rights reserved.
|