@sriinnu/runic 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +1 -1
- package/README.md +47 -29
- package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
- package/RuniciOS/Views/ProviderDetailView.swift +9 -9
- package/RuniciOS/Views/ProviderListView.swift +10 -10
- package/RuniciOS/Widgets/LargeWidget.swift +14 -14
- package/RuniciOS/Widgets/LiveActivity.swift +19 -19
- package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
- package/RuniciOS/Widgets/MediumWidget.swift +10 -10
- package/RuniciOS/Widgets/RunicWidget.swift +5 -5
- package/RuniciOS/Widgets/SmallWidget.swift +9 -9
- package/Scripts/package_app.sh +2 -2
- package/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +17 -4
- package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
- package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
- package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
- package/Sources/Runic/Controllers/StatusItemController+Menu.swift +47 -14
- package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
- package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
- package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
- package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
- package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
- package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
- package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
- package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +3 -6
- package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
- package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
- package/Sources/Runic/Core/RunicColors.swift +6 -6
- package/Sources/Runic/Core/RunicSpacing.swift +7 -7
- package/Sources/Runic/Core/RunicTypography.swift +206 -0
- package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/SettingsStore.swift +99 -52
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
- package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
- package/Sources/Runic/Core/Stores/UsageStore.swift +234 -167
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +10 -36
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +10 -36
- package/Sources/Runic/Core/UsageExporter.swift +8 -4
- package/Sources/Runic/ProviderRegistry.swift +1 -1
- package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
- package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
- package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
- package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
- package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
- package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
- package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
- package/Sources/Runic/Resources/provider-context-windows.json +20 -0
- package/Sources/Runic/RunicApp.swift +46 -36
- package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
- package/Sources/Runic/UsagePaceText.swift +1 -1
- package/Sources/Runic/UsageProgressBar.swift +13 -9
- package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
- package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
- package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
- package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
- package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
- package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
- package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
- package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +15 -20
- package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
- package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
- package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
- package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
- package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
- package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
- package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
- package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
- package/Sources/Runic/Views/Menu/OverviewMenuView.swift +198 -58
- package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
- package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
- package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
- package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
- package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
- package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
- package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +28 -76
- package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
- package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
- package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
- package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
- package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
- package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
- package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
- package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
- package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
- package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
- package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
- package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
- package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +108 -33
- package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
- package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
- package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
- package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
- package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
- package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
- package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
- package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
- package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
- package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
- package/Sources/RunicCLI/CLIEntry.swift +83 -42
- package/Sources/RunicCLI/CLIRenderer.swift +1 -1
- package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
- package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
- package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
- package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
- package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
- package/Sources/RunicClaudeWebProbe/main.swift +1 -1
- package/Sources/RunicCore/AlertRuleStore.swift +20 -20
- package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
- package/Sources/RunicCore/CostUsageModels.swift +1 -1
- package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
- package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +80 -71
- package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
- package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
- package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
- package/Sources/RunicCore/Logging/OSLogLogHandler.swift +6 -6
- package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
- package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
- package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
- package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
- package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
- package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
- package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
- package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
- package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
- package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
- package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +1 -0
- package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +12 -1
- package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
- package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
- package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
- package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
- package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
- package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +1 -0
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
- package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +3 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +6 -2
- package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +8 -11
- package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
- package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +25 -16
- package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
- package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
- package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
- package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +27 -16
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
- package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
- package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
- package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
- package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
- package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
- package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +51 -30
- package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
- package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
- package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
- package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
- package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
- package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
- package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
- package/Sources/RunicCore/Team/TeamModels.swift +111 -110
- package/Sources/RunicCore/Team/TeamStore.swift +126 -109
- package/Sources/RunicCore/UsageFetcher.swift +2 -2
- package/Sources/RunicCore/UsageFormatter.swift +25 -25
- package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +141 -29
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +59 -5
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +161 -0
- package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
- package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
- package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
- package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
- package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
- package/Tests/RunicTests/AppDelegateTests.swift +8 -3
- package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
- package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
- package/Tests/RunicTests/CLICostTests.swift +6 -7
- package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
- package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
- package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
- package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
- package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
- package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
- package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
- package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
- package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
- package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
- package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
- package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
- package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
- package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
- package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
- package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
- package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
- package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
- package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
- package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
- package/Tests/RunicTests/InstallOriginTests.swift +1 -2
- package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
- package/Tests/RunicTests/LiveAccountTests.swift +3 -3
- package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
- package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
- package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
- package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
- package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
- package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
- package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
- package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
- package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
- package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
- package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
- package/Tests/RunicTests/PathBuilderTests.swift +14 -15
- package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
- package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
- package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
- package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
- package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
- package/Tests/RunicTests/RunicTests.swift +6 -7
- package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
- package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
- package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
- package/Tests/RunicTests/StatusMenuTests.swift +63 -22
- package/Tests/RunicTests/StatusProbeTests.swift +28 -29
- package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
- package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
- package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
- package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
- package/Tests/RunicTests/TextParsingTests.swift +1 -2
- package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
- package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
- package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
- package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
- package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
- package/Tests/RunicTests/UsagePaceTests.swift +5 -6
- package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
- package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
- package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
- package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
- package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
- package/appcast.xml +36 -37
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -11,7 +11,6 @@ import Foundation
|
|
|
11
11
|
/// - Highest version: Keep record with highest version number
|
|
12
12
|
/// - Merge: Attempt to merge both versions (custom logic required)
|
|
13
13
|
public actor SyncConflictResolver: SyncConflictResolverProtocol {
|
|
14
|
-
|
|
15
14
|
// MARK: - Properties
|
|
16
15
|
|
|
17
16
|
/// Statistics about resolved conflicts
|
|
@@ -38,38 +37,35 @@ public actor SyncConflictResolver: SyncConflictResolverProtocol {
|
|
|
38
37
|
public func resolve(
|
|
39
38
|
local: SyncableRecord,
|
|
40
39
|
remote: SyncableRecord,
|
|
41
|
-
strategy: ConflictResolutionStrategy
|
|
42
|
-
|
|
40
|
+
strategy: ConflictResolutionStrategy) async -> SyncableRecord
|
|
41
|
+
{
|
|
43
42
|
// Validate that records are the same type
|
|
44
43
|
guard local.recordID == remote.recordID else {
|
|
45
44
|
return local // Fallback to local if IDs don't match
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
let resolved: SyncableRecord
|
|
49
|
-
|
|
50
|
-
switch strategy {
|
|
47
|
+
let resolved: SyncableRecord = switch strategy {
|
|
51
48
|
case .lastWriteWins:
|
|
52
|
-
|
|
49
|
+
self.resolveLastWriteWins(local: local, remote: remote)
|
|
53
50
|
|
|
54
51
|
case .preferLocal:
|
|
55
|
-
|
|
52
|
+
local
|
|
56
53
|
|
|
57
54
|
case .preferRemote:
|
|
58
|
-
|
|
55
|
+
remote
|
|
59
56
|
|
|
60
57
|
case .highestVersion:
|
|
61
|
-
|
|
58
|
+
self.resolveHighestVersion(local: local, remote: remote)
|
|
62
59
|
|
|
63
60
|
case .merge:
|
|
64
|
-
|
|
61
|
+
self.resolveMerge(local: local, remote: remote)
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
// Update statistics
|
|
68
|
-
conflictStats.recordConflict(
|
|
65
|
+
self.conflictStats.recordConflict(
|
|
69
66
|
recordType: local.recordType,
|
|
70
67
|
strategy: strategy,
|
|
71
|
-
winner: resolved.recordID == local.recordID ? .local : .remote
|
|
72
|
-
)
|
|
68
|
+
winner: resolved.recordID == local.recordID ? .local : .remote)
|
|
73
69
|
|
|
74
70
|
return resolved
|
|
75
71
|
}
|
|
@@ -81,14 +77,14 @@ public actor SyncConflictResolver: SyncConflictResolverProtocol {
|
|
|
81
77
|
/// - handler: Closure that merges two records of this type
|
|
82
78
|
public func registerMergeHandler(
|
|
83
79
|
for recordType: String,
|
|
84
|
-
handler: @escaping MergeHandler
|
|
85
|
-
|
|
86
|
-
mergeHandlers[recordType] = handler
|
|
80
|
+
handler: @escaping MergeHandler)
|
|
81
|
+
{
|
|
82
|
+
self.mergeHandlers[recordType] = handler
|
|
87
83
|
}
|
|
88
84
|
|
|
89
85
|
/// Resets conflict statistics
|
|
90
86
|
public func resetStatistics() {
|
|
91
|
-
conflictStats = ConflictStatistics()
|
|
87
|
+
self.conflictStats = ConflictStatistics()
|
|
92
88
|
}
|
|
93
89
|
|
|
94
90
|
// MARK: - Private Resolution Methods
|
|
@@ -96,45 +92,45 @@ public actor SyncConflictResolver: SyncConflictResolverProtocol {
|
|
|
96
92
|
/// Resolves conflict by keeping the most recently modified record
|
|
97
93
|
private func resolveLastWriteWins(
|
|
98
94
|
local: SyncableRecord,
|
|
99
|
-
remote: SyncableRecord
|
|
100
|
-
|
|
95
|
+
remote: SyncableRecord) -> SyncableRecord
|
|
96
|
+
{
|
|
101
97
|
if local.modifiedAt > remote.modifiedAt {
|
|
102
|
-
|
|
98
|
+
local
|
|
103
99
|
} else if remote.modifiedAt > local.modifiedAt {
|
|
104
|
-
|
|
100
|
+
remote
|
|
105
101
|
} else {
|
|
106
102
|
// Same timestamp, use version as tiebreaker
|
|
107
|
-
|
|
103
|
+
local.version >= remote.version ? local : remote
|
|
108
104
|
}
|
|
109
105
|
}
|
|
110
106
|
|
|
111
107
|
/// Resolves conflict by keeping the record with highest version number
|
|
112
108
|
private func resolveHighestVersion(
|
|
113
109
|
local: SyncableRecord,
|
|
114
|
-
remote: SyncableRecord
|
|
115
|
-
|
|
110
|
+
remote: SyncableRecord) -> SyncableRecord
|
|
111
|
+
{
|
|
116
112
|
if local.version > remote.version {
|
|
117
|
-
|
|
113
|
+
local
|
|
118
114
|
} else if remote.version > local.version {
|
|
119
|
-
|
|
115
|
+
remote
|
|
120
116
|
} else {
|
|
121
117
|
// Same version, use timestamp as tiebreaker
|
|
122
|
-
|
|
118
|
+
local.modifiedAt >= remote.modifiedAt ? local : remote
|
|
123
119
|
}
|
|
124
120
|
}
|
|
125
121
|
|
|
126
122
|
/// Attempts to merge both versions using custom merge logic
|
|
127
123
|
private func resolveMerge(
|
|
128
124
|
local: SyncableRecord,
|
|
129
|
-
remote: SyncableRecord
|
|
130
|
-
|
|
125
|
+
remote: SyncableRecord) -> SyncableRecord
|
|
126
|
+
{
|
|
131
127
|
// Check for registered merge handler
|
|
132
128
|
if let handler = mergeHandlers[local.recordType] {
|
|
133
129
|
return handler(local, remote)
|
|
134
130
|
}
|
|
135
131
|
|
|
136
132
|
// No custom merge handler, fall back to last-write-wins
|
|
137
|
-
return resolveLastWriteWins(local: local, remote: remote)
|
|
133
|
+
return self.resolveLastWriteWins(local: local, remote: remote)
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
136
|
|
|
@@ -173,32 +169,32 @@ public struct ConflictStatistics: Sendable {
|
|
|
173
169
|
mutating func recordConflict(
|
|
174
170
|
recordType: String,
|
|
175
171
|
strategy: ConflictResolutionStrategy,
|
|
176
|
-
winner: ConflictWinner
|
|
177
|
-
|
|
178
|
-
totalConflicts += 1
|
|
179
|
-
conflictsByType[recordType, default: 0] += 1
|
|
180
|
-
conflictsByStrategy[strategy.rawValue, default: 0] += 1
|
|
172
|
+
winner: ConflictWinner)
|
|
173
|
+
{
|
|
174
|
+
self.totalConflicts += 1
|
|
175
|
+
self.conflictsByType[recordType, default: 0] += 1
|
|
176
|
+
self.conflictsByStrategy[strategy.rawValue, default: 0] += 1
|
|
181
177
|
|
|
182
178
|
switch winner {
|
|
183
179
|
case .local:
|
|
184
|
-
localWins += 1
|
|
180
|
+
self.localWins += 1
|
|
185
181
|
case .remote:
|
|
186
|
-
remoteWins += 1
|
|
182
|
+
self.remoteWins += 1
|
|
187
183
|
}
|
|
188
184
|
|
|
189
|
-
lastConflictAt = Date()
|
|
185
|
+
self.lastConflictAt = Date()
|
|
190
186
|
}
|
|
191
187
|
|
|
192
188
|
/// Percentage of conflicts won by local version
|
|
193
189
|
public var localWinRate: Double {
|
|
194
|
-
guard totalConflicts > 0 else { return 0 }
|
|
195
|
-
return Double(localWins) / Double(totalConflicts)
|
|
190
|
+
guard self.totalConflicts > 0 else { return 0 }
|
|
191
|
+
return Double(self.localWins) / Double(self.totalConflicts)
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
/// Percentage of conflicts won by remote version
|
|
199
195
|
public var remoteWinRate: Double {
|
|
200
|
-
guard totalConflicts > 0 else { return 0 }
|
|
201
|
-
return Double(remoteWins) / Double(totalConflicts)
|
|
196
|
+
guard self.totalConflicts > 0 else { return 0 }
|
|
197
|
+
return Double(self.remoteWins) / Double(self.totalConflicts)
|
|
202
198
|
}
|
|
203
199
|
|
|
204
200
|
/// Human-readable summary of conflict statistics
|
|
@@ -207,19 +203,19 @@ public struct ConflictStatistics: Sendable {
|
|
|
207
203
|
"Conflict Resolution Statistics:",
|
|
208
204
|
" Total conflicts: \(totalConflicts)",
|
|
209
205
|
" Local wins: \(localWins) (\(String(format: "%.1f%%", localWinRate * 100)))",
|
|
210
|
-
" Remote wins: \(remoteWins) (\(String(format: "%.1f%%", remoteWinRate * 100)))"
|
|
206
|
+
" Remote wins: \(remoteWins) (\(String(format: "%.1f%%", remoteWinRate * 100)))",
|
|
211
207
|
]
|
|
212
208
|
|
|
213
|
-
if !conflictsByType.isEmpty {
|
|
209
|
+
if !self.conflictsByType.isEmpty {
|
|
214
210
|
lines.append(" By record type:")
|
|
215
|
-
for (type, count) in conflictsByType.sorted(by: { $0.value > $1.value }) {
|
|
211
|
+
for (type, count) in self.conflictsByType.sorted(by: { $0.value > $1.value }) {
|
|
216
212
|
lines.append(" \(type): \(count)")
|
|
217
213
|
}
|
|
218
214
|
}
|
|
219
215
|
|
|
220
|
-
if !conflictsByStrategy.isEmpty {
|
|
216
|
+
if !self.conflictsByStrategy.isEmpty {
|
|
221
217
|
lines.append(" By strategy:")
|
|
222
|
-
for (strategy, count) in conflictsByStrategy.sorted(by: { $0.value > $1.value }) {
|
|
218
|
+
for (strategy, count) in self.conflictsByStrategy.sorted(by: { $0.value > $1.value }) {
|
|
223
219
|
lines.append(" \(strategy): \(count)")
|
|
224
220
|
}
|
|
225
221
|
}
|
|
@@ -240,9 +236,10 @@ extension SyncConflictResolver {
|
|
|
240
236
|
/// Registers default merge handlers for Runic record types
|
|
241
237
|
public func registerDefaultMergeHandlers() {
|
|
242
238
|
// Usage Snapshot merge: prefer record with most recent data
|
|
243
|
-
registerMergeHandler(for: CloudKitRecordType.usageSnapshot) { local, remote in
|
|
239
|
+
self.registerMergeHandler(for: CloudKitRecordType.usageSnapshot) { local, remote in
|
|
244
240
|
guard let localUsage = local as? UsageSnapshotSyncRecord,
|
|
245
|
-
let remoteUsage = remote as? UsageSnapshotSyncRecord
|
|
241
|
+
let remoteUsage = remote as? UsageSnapshotSyncRecord
|
|
242
|
+
else {
|
|
246
243
|
return local
|
|
247
244
|
}
|
|
248
245
|
|
|
@@ -251,9 +248,10 @@ extension SyncConflictResolver {
|
|
|
251
248
|
}
|
|
252
249
|
|
|
253
250
|
// User Preferences merge: combine enabled providers
|
|
254
|
-
registerMergeHandler(for: CloudKitRecordType.userPreferences) { local, remote in
|
|
251
|
+
self.registerMergeHandler(for: CloudKitRecordType.userPreferences) { local, remote in
|
|
255
252
|
guard let localPrefs = local as? UserPreferencesSyncRecord,
|
|
256
|
-
let remotePrefs = remote as? UserPreferencesSyncRecord
|
|
253
|
+
let remotePrefs = remote as? UserPreferencesSyncRecord
|
|
254
|
+
else {
|
|
257
255
|
return local
|
|
258
256
|
}
|
|
259
257
|
|
|
@@ -273,14 +271,14 @@ extension SyncConflictResolver {
|
|
|
273
271
|
notificationsEnabled: source.notificationsEnabled,
|
|
274
272
|
autoRefreshEnabled: source.autoRefreshEnabled,
|
|
275
273
|
theme: source.theme,
|
|
276
|
-
displayFormat: source.displayFormat
|
|
277
|
-
)
|
|
274
|
+
displayFormat: source.displayFormat)
|
|
278
275
|
}
|
|
279
276
|
|
|
280
277
|
// Alert Configuration merge: prefer most restrictive thresholds
|
|
281
|
-
registerMergeHandler(for: CloudKitRecordType.alertConfiguration) { local, remote in
|
|
278
|
+
self.registerMergeHandler(for: CloudKitRecordType.alertConfiguration) { local, remote in
|
|
282
279
|
guard let localAlert = local as? AlertConfigurationSyncRecord,
|
|
283
|
-
let remoteAlert = remote as? AlertConfigurationSyncRecord
|
|
280
|
+
let remoteAlert = remote as? AlertConfigurationSyncRecord
|
|
281
|
+
else {
|
|
284
282
|
return local
|
|
285
283
|
}
|
|
286
284
|
|
|
@@ -303,8 +301,7 @@ extension SyncConflictResolver {
|
|
|
303
301
|
warningThreshold: warningThreshold,
|
|
304
302
|
criticalThreshold: criticalThreshold,
|
|
305
303
|
notificationChannels: mergedChannels,
|
|
306
|
-
enabled: enabled
|
|
307
|
-
)
|
|
304
|
+
enabled: enabled)
|
|
308
305
|
}
|
|
309
306
|
}
|
|
310
307
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Foundation
|
|
2
1
|
import CloudKit
|
|
2
|
+
import Foundation
|
|
3
3
|
|
|
4
4
|
// MARK: - Sync Protocol Definitions
|
|
5
5
|
|
|
@@ -90,8 +90,7 @@ public protocol SyncConflictResolverProtocol: Sendable {
|
|
|
90
90
|
func resolve(
|
|
91
91
|
local: SyncableRecord,
|
|
92
92
|
remote: SyncableRecord,
|
|
93
|
-
strategy: ConflictResolutionStrategy
|
|
94
|
-
) async -> SyncableRecord
|
|
93
|
+
strategy: ConflictResolutionStrategy) async -> SyncableRecord
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
// MARK: - Sync Observer Protocol
|
|
@@ -150,8 +149,8 @@ public struct SyncOptions: Sendable {
|
|
|
150
149
|
conflictStrategy: ConflictResolutionStrategy = .lastWriteWins,
|
|
151
150
|
backgroundMode: Bool = false,
|
|
152
151
|
timeout: TimeInterval = 60.0,
|
|
153
|
-
encryptSensitiveData: Bool = true
|
|
154
|
-
|
|
152
|
+
encryptSensitiveData: Bool = true)
|
|
153
|
+
{
|
|
155
154
|
self.forceFullSync = forceFullSync
|
|
156
155
|
self.batchSize = batchSize
|
|
157
156
|
self.conflictStrategy = conflictStrategy
|
|
@@ -191,8 +190,8 @@ public struct SyncResult: Sendable {
|
|
|
191
190
|
deletedCount: Int = 0,
|
|
192
191
|
completedAt: Date = Date(),
|
|
193
192
|
duration: TimeInterval = 0,
|
|
194
|
-
warnings: [String] = []
|
|
195
|
-
|
|
193
|
+
warnings: [String] = [])
|
|
194
|
+
{
|
|
196
195
|
self.pushedCount = pushedCount
|
|
197
196
|
self.fetchedCount = fetchedCount
|
|
198
197
|
self.conflictsResolved = conflictsResolved
|
|
@@ -256,25 +255,25 @@ public enum SyncError: Error, Sendable {
|
|
|
256
255
|
public var localizedDescription: String {
|
|
257
256
|
switch self {
|
|
258
257
|
case .iCloudAccountUnavailable:
|
|
259
|
-
|
|
258
|
+
"iCloud account is not available. Please sign in to iCloud in Settings."
|
|
260
259
|
case .networkUnavailable:
|
|
261
|
-
|
|
260
|
+
"Network connection is not available. Sync will retry when online."
|
|
262
261
|
case .quotaExceeded:
|
|
263
|
-
|
|
264
|
-
case .invalidRecordFormat(
|
|
265
|
-
|
|
262
|
+
"iCloud storage quota exceeded. Please free up space or upgrade your plan."
|
|
263
|
+
case let .invalidRecordFormat(details):
|
|
264
|
+
"Invalid record format: \(details)"
|
|
266
265
|
case .timeout:
|
|
267
|
-
|
|
266
|
+
"Sync operation timed out. Please try again."
|
|
268
267
|
case .authenticationFailed:
|
|
269
|
-
|
|
270
|
-
case .conflictResolutionFailed(
|
|
271
|
-
|
|
272
|
-
case .cloudKitError(
|
|
273
|
-
|
|
274
|
-
case .encryptionFailed(
|
|
275
|
-
|
|
276
|
-
case .unknown(
|
|
277
|
-
|
|
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)"
|
|
278
277
|
}
|
|
279
278
|
}
|
|
280
279
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import Foundation
|
|
2
1
|
import CloudKit
|
|
3
2
|
import CryptoKit
|
|
3
|
+
import Foundation
|
|
4
4
|
import LocalAuthentication
|
|
5
5
|
|
|
6
6
|
// MARK: - CloudKit Record Type Constants
|
|
@@ -54,8 +54,8 @@ public struct UsageSnapshotSyncRecord: SyncableRecord {
|
|
|
54
54
|
accountEmail: String? = nil,
|
|
55
55
|
updatedAt: Date = Date(),
|
|
56
56
|
deviceName: String,
|
|
57
|
-
platform: String
|
|
58
|
-
|
|
57
|
+
platform: String)
|
|
58
|
+
{
|
|
59
59
|
self.recordID = recordID
|
|
60
60
|
self.version = version
|
|
61
61
|
self.modifiedAt = modifiedAt
|
|
@@ -73,20 +73,20 @@ public struct UsageSnapshotSyncRecord: SyncableRecord {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
public func toCKRecord() throws -> CKRecord {
|
|
76
|
-
let record = CKRecord(recordType: recordType, recordID: CKRecord.ID(recordName: recordID))
|
|
77
|
-
|
|
78
|
-
record["version"] = version as CKRecordValue
|
|
79
|
-
record["modifiedAt"] = modifiedAt as CKRecordValue
|
|
80
|
-
record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
|
|
81
|
-
record["providerID"] = providerID as CKRecordValue
|
|
82
|
-
record["primaryUsed"] = primaryUsed as CKRecordValue
|
|
83
|
-
record["primaryLimit"] = primaryLimit as? CKRecordValue
|
|
84
|
-
record["secondaryUsed"] = secondaryUsed as? CKRecordValue
|
|
85
|
-
record["secondaryLimit"] = secondaryLimit as? CKRecordValue
|
|
86
|
-
record["costUSD"] = costUSD as? CKRecordValue
|
|
87
|
-
record["updatedAt"] = updatedAt as CKRecordValue
|
|
88
|
-
record["deviceName"] = deviceName as CKRecordValue
|
|
89
|
-
record["platform"] = platform as CKRecordValue
|
|
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
90
|
|
|
91
91
|
// Encrypt sensitive data
|
|
92
92
|
if let email = accountEmail {
|
|
@@ -128,8 +128,7 @@ public struct UsageSnapshotSyncRecord: SyncableRecord {
|
|
|
128
128
|
accountEmail: accountEmail,
|
|
129
129
|
updatedAt: updatedAt,
|
|
130
130
|
deviceName: deviceName,
|
|
131
|
-
platform: platform
|
|
132
|
-
)
|
|
131
|
+
platform: platform)
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
134
|
|
|
@@ -164,8 +163,8 @@ public struct UserPreferencesSyncRecord: SyncableRecord {
|
|
|
164
163
|
notificationsEnabled: Bool = true,
|
|
165
164
|
autoRefreshEnabled: Bool = true,
|
|
166
165
|
theme: String = "system",
|
|
167
|
-
displayFormat: String = "compact"
|
|
168
|
-
|
|
166
|
+
displayFormat: String = "compact")
|
|
167
|
+
{
|
|
169
168
|
self.recordID = recordID
|
|
170
169
|
self.version = version
|
|
171
170
|
self.modifiedAt = modifiedAt
|
|
@@ -179,17 +178,17 @@ public struct UserPreferencesSyncRecord: SyncableRecord {
|
|
|
179
178
|
}
|
|
180
179
|
|
|
181
180
|
public func toCKRecord() throws -> CKRecord {
|
|
182
|
-
let record = CKRecord(recordType: recordType, recordID: CKRecord.ID(recordName: recordID))
|
|
183
|
-
|
|
184
|
-
record["version"] = version as CKRecordValue
|
|
185
|
-
record["modifiedAt"] = modifiedAt as CKRecordValue
|
|
186
|
-
record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
|
|
187
|
-
record["refreshInterval"] = refreshInterval as CKRecordValue
|
|
188
|
-
record["enabledProviders"] = enabledProviders as CKRecordValue
|
|
189
|
-
record["notificationsEnabled"] = (notificationsEnabled ? 1 : 0) as CKRecordValue
|
|
190
|
-
record["autoRefreshEnabled"] = (autoRefreshEnabled ? 1 : 0) as CKRecordValue
|
|
191
|
-
record["theme"] = theme as CKRecordValue
|
|
192
|
-
record["displayFormat"] = displayFormat as CKRecordValue
|
|
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
|
|
193
192
|
|
|
194
193
|
return record
|
|
195
194
|
}
|
|
@@ -217,8 +216,7 @@ public struct UserPreferencesSyncRecord: SyncableRecord {
|
|
|
217
216
|
notificationsEnabled: notificationsEnabled == 1,
|
|
218
217
|
autoRefreshEnabled: autoRefreshEnabled == 1,
|
|
219
218
|
theme: theme,
|
|
220
|
-
displayFormat: displayFormat
|
|
221
|
-
)
|
|
219
|
+
displayFormat: displayFormat)
|
|
222
220
|
}
|
|
223
221
|
}
|
|
224
222
|
|
|
@@ -251,8 +249,8 @@ public struct AlertConfigurationSyncRecord: SyncableRecord {
|
|
|
251
249
|
warningThreshold: Double = 0.75,
|
|
252
250
|
criticalThreshold: Double = 0.90,
|
|
253
251
|
notificationChannels: [String] = ["system"],
|
|
254
|
-
enabled: Bool = true
|
|
255
|
-
|
|
252
|
+
enabled: Bool = true)
|
|
253
|
+
{
|
|
256
254
|
self.recordID = recordID
|
|
257
255
|
self.version = version
|
|
258
256
|
self.modifiedAt = modifiedAt
|
|
@@ -265,16 +263,16 @@ public struct AlertConfigurationSyncRecord: SyncableRecord {
|
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
public func toCKRecord() throws -> CKRecord {
|
|
268
|
-
let record = CKRecord(recordType: recordType, recordID: CKRecord.ID(recordName: recordID))
|
|
266
|
+
let record = CKRecord(recordType: recordType, recordID: CKRecord.ID(recordName: self.recordID))
|
|
269
267
|
|
|
270
|
-
record["version"] = version as CKRecordValue
|
|
271
|
-
record["modifiedAt"] = modifiedAt as CKRecordValue
|
|
272
|
-
record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
|
|
273
|
-
record["providerID"] = providerID as CKRecordValue
|
|
274
|
-
record["warningThreshold"] = warningThreshold as CKRecordValue
|
|
275
|
-
record["criticalThreshold"] = criticalThreshold as CKRecordValue
|
|
276
|
-
record["notificationChannels"] = notificationChannels as CKRecordValue
|
|
277
|
-
record["enabled"] = (enabled ? 1 : 0) as CKRecordValue
|
|
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
|
|
278
276
|
|
|
279
277
|
return record
|
|
280
278
|
}
|
|
@@ -300,8 +298,7 @@ public struct AlertConfigurationSyncRecord: SyncableRecord {
|
|
|
300
298
|
warningThreshold: warningThreshold,
|
|
301
299
|
criticalThreshold: criticalThreshold,
|
|
302
300
|
notificationChannels: notificationChannels,
|
|
303
|
-
enabled: enabled == 1
|
|
304
|
-
)
|
|
301
|
+
enabled: enabled == 1)
|
|
305
302
|
}
|
|
306
303
|
}
|
|
307
304
|
|
|
@@ -378,7 +375,7 @@ private func saveToKeychain(key: String, data: Data) {
|
|
|
378
375
|
kSecAttrAccount as String: key,
|
|
379
376
|
kSecUseDataProtectionKeychain as String: true,
|
|
380
377
|
kSecValueData as String: data,
|
|
381
|
-
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
|
|
378
|
+
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
|
|
382
379
|
]
|
|
383
380
|
SecItemDelete(query as CFDictionary)
|
|
384
381
|
SecItemAdd(query as CFDictionary, nil)
|
|
@@ -391,7 +388,7 @@ private func loadFromKeychain(key: String) -> Data? {
|
|
|
391
388
|
kSecAttrAccount as String: key,
|
|
392
389
|
kSecUseDataProtectionKeychain as String: true,
|
|
393
390
|
kSecUseAuthenticationUI as String: "kSecUseAuthenticationUIFail" as CFString,
|
|
394
|
-
kSecReturnData as String: true
|
|
391
|
+
kSecReturnData as String: true,
|
|
395
392
|
]
|
|
396
393
|
let authContext = LAContext()
|
|
397
394
|
authContext.interactionNotAllowed = true
|