@sriinnu/runic 1.2.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/README.md +8 -1
- 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/Sources/Runic/About.swift +3 -3
- package/Sources/Runic/AppNotifications.swift +5 -3
- package/Sources/Runic/Controllers/StatusItemController+Actions.swift +7 -3
- 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 +18 -13
- 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 +1 -1
- 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 +1 -3
- package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CohereTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
- package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/GroqTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/KimiTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/MistralTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
- package/Sources/Runic/Core/Stores/QwenTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/SettingsStore.swift +89 -51
- package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +1 -3
- 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 +187 -161
- package/Sources/Runic/Core/Stores/XAITokenStore.swift +1 -3
- package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +1 -2
- 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/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 +12 -10
- 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 +18 -15
- 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 +22 -17
- 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 +44 -39
- 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 +73 -75
- 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/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 +3 -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/CodexOAuthUsageFetcher.swift +1 -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/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 +2 -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 +5 -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 +7 -7
- 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 +23 -10
- 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 +26 -13
- 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 +48 -21
- 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 +27 -13
- package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +11 -3
- package/Sources/RunicCore/UsageLedger/LedgerCache.swift +5 -2
- 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 -76
- package/package.json +1 -1
- package/runic.png +0 -0
- package/runic.svg +15 -0
- package/version.env +2 -2
|
@@ -207,8 +207,8 @@ public enum ZaiModelPricing {
|
|
|
207
207
|
"glm-4.7-thinking": Tier(inputPerMillion: 0.38, outputPerMillion: 1.75, contextWindow: 203_000),
|
|
208
208
|
"glm-4.6": Tier(inputPerMillion: 0.39, outputPerMillion: 1.74, contextWindow: 205_000),
|
|
209
209
|
"glm-4.6-thinking": Tier(inputPerMillion: 0.39, outputPerMillion: 1.74, contextWindow: 205_000),
|
|
210
|
-
"glm-4.5v": Tier(inputPerMillion: 0.60, outputPerMillion: 1.80, contextWindow:
|
|
211
|
-
"glm-4.5v-thinking": Tier(inputPerMillion: 0.60, outputPerMillion: 1.80, contextWindow:
|
|
210
|
+
"glm-4.5v": Tier(inputPerMillion: 0.60, outputPerMillion: 1.80, contextWindow: 66000),
|
|
211
|
+
"glm-4.5v-thinking": Tier(inputPerMillion: 0.60, outputPerMillion: 1.80, contextWindow: 66000),
|
|
212
212
|
"glm-4.5": Tier(inputPerMillion: 0.60, outputPerMillion: 2.20, contextWindow: 131_000),
|
|
213
213
|
"glm-4.5-thinking": Tier(inputPerMillion: 0.60, outputPerMillion: 2.20, contextWindow: 131_000),
|
|
214
214
|
"glm-5": Tier(inputPerMillion: 0.72, outputPerMillion: 2.30, contextWindow: 203_000),
|
|
@@ -321,13 +321,15 @@ private struct ZaiQuotaLimitResponse: Decodable {
|
|
|
321
321
|
let data: ZaiQuotaLimitData?
|
|
322
322
|
let success: Bool?
|
|
323
323
|
|
|
324
|
-
var isSuccess: Bool {
|
|
324
|
+
var isSuccess: Bool {
|
|
325
|
+
(self.success ?? (self.code == 200)) && (self.code ?? 200) == 200
|
|
326
|
+
}
|
|
325
327
|
|
|
326
328
|
var errorMessage: String {
|
|
327
329
|
self.msg ?? self.message ?? "Unknown API error (code: \(self.code ?? -1))"
|
|
328
330
|
}
|
|
329
331
|
|
|
330
|
-
|
|
332
|
+
/// Some responses use "message" instead of "msg".
|
|
331
333
|
private let message: String?
|
|
332
334
|
|
|
333
335
|
private enum CodingKeys: String, CodingKey {
|
|
@@ -407,8 +409,13 @@ private struct ZaiModelUsageResponse: Decodable {
|
|
|
407
409
|
let total_tokens: Int?
|
|
408
410
|
let total_prompts: Int?
|
|
409
411
|
|
|
410
|
-
var resolvedTotalTokens: Int? {
|
|
411
|
-
|
|
412
|
+
var resolvedTotalTokens: Int? {
|
|
413
|
+
self.totalTokens ?? self.total_tokens
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
var resolvedTotalPrompts: Int? {
|
|
417
|
+
self.totalPrompts ?? self.total_prompts
|
|
418
|
+
}
|
|
412
419
|
}
|
|
413
420
|
|
|
414
421
|
struct ZaiModelUsageRaw: Decodable {
|
|
@@ -420,9 +427,17 @@ private struct ZaiModelUsageResponse: Decodable {
|
|
|
420
427
|
let prompts: Int?
|
|
421
428
|
let calls: Int?
|
|
422
429
|
|
|
423
|
-
var resolvedModelCode: String? {
|
|
424
|
-
|
|
425
|
-
|
|
430
|
+
var resolvedModelCode: String? {
|
|
431
|
+
self.modelCode ?? self.model_code ?? self.model
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
var resolvedTokens: Int {
|
|
435
|
+
self.tokens ?? self.usage ?? 0
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
var resolvedPrompts: Int {
|
|
439
|
+
self.prompts ?? self.calls ?? 0
|
|
440
|
+
}
|
|
426
441
|
}
|
|
427
442
|
}
|
|
428
443
|
|
|
@@ -438,7 +453,10 @@ private struct ZaiToolUsageResponse: Decodable {
|
|
|
438
453
|
init(from decoder: Decoder) throws {
|
|
439
454
|
let container = try decoder.container(keyedBy: DynamicKey.self)
|
|
440
455
|
var toolEntries: [ZaiToolUsageRaw] = []
|
|
441
|
-
if let tools = try? container.decodeIfPresent(
|
|
456
|
+
if let tools = try? container.decodeIfPresent(
|
|
457
|
+
[ZaiToolUsageRaw].self,
|
|
458
|
+
forKey: DynamicKey(stringValue: "tools")!)
|
|
459
|
+
{
|
|
442
460
|
toolEntries = tools
|
|
443
461
|
} else {
|
|
444
462
|
// Fallback: try top-level keys as tool names with int values
|
|
@@ -458,15 +476,25 @@ private struct ZaiToolUsageResponse: Decodable {
|
|
|
458
476
|
let count: Int?
|
|
459
477
|
let usage: Int?
|
|
460
478
|
|
|
461
|
-
var resolvedName: String? {
|
|
462
|
-
|
|
479
|
+
var resolvedName: String? {
|
|
480
|
+
self.tool ?? self.name
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
var resolvedCount: Int {
|
|
484
|
+
self.count ?? self.usage ?? 0
|
|
485
|
+
}
|
|
463
486
|
}
|
|
464
487
|
|
|
465
488
|
struct DynamicKey: CodingKey {
|
|
466
489
|
var stringValue: String
|
|
467
490
|
var intValue: Int?
|
|
468
|
-
init?(stringValue: String) {
|
|
469
|
-
|
|
491
|
+
init?(stringValue: String) {
|
|
492
|
+
self.stringValue = stringValue
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
init?(intValue: Int) {
|
|
496
|
+
self.stringValue = "\(intValue)"; self.intValue = intValue
|
|
497
|
+
}
|
|
470
498
|
}
|
|
471
499
|
}
|
|
472
500
|
|
|
@@ -508,7 +536,7 @@ public struct ZaiUsageFetcher: Sendable {
|
|
|
508
536
|
// MARK: - Quota endpoint (required)
|
|
509
537
|
|
|
510
538
|
private static func fetchQuota(apiKey: String) async throws -> (ZaiLimitEntry?, ZaiLimitEntry?, String?) {
|
|
511
|
-
let request = try self.makeRequest(url: quotaAPIURL, apiKey: apiKey)
|
|
539
|
+
let request = try self.makeRequest(url: self.quotaAPIURL, apiKey: apiKey)
|
|
512
540
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
513
541
|
|
|
514
542
|
guard let httpResponse = response as? HTTPURLResponse else {
|
|
@@ -556,7 +584,7 @@ public struct ZaiUsageFetcher: Sendable {
|
|
|
556
584
|
do {
|
|
557
585
|
return try await self.fetchModelUsage(apiKey: apiKey)
|
|
558
586
|
} catch {
|
|
559
|
-
|
|
587
|
+
self.log.info("z.ai model-usage endpoint unavailable: \(error.localizedDescription)")
|
|
560
588
|
return nil
|
|
561
589
|
}
|
|
562
590
|
}
|
|
@@ -616,7 +644,7 @@ public struct ZaiUsageFetcher: Sendable {
|
|
|
616
644
|
do {
|
|
617
645
|
return try await self.fetchToolUsage(apiKey: apiKey)
|
|
618
646
|
} catch {
|
|
619
|
-
|
|
647
|
+
self.log.info("z.ai tool-usage endpoint unavailable: \(error.localizedDescription)")
|
|
620
648
|
return nil
|
|
621
649
|
}
|
|
622
650
|
}
|
|
@@ -659,11 +687,10 @@ public struct ZaiUsageFetcher: Sendable {
|
|
|
659
687
|
var request = URLRequest(url: requestURL)
|
|
660
688
|
request.httpMethod = "GET"
|
|
661
689
|
// z.ai expects "Bearer <token>" — add prefix if the user didn't include it.
|
|
662
|
-
let authValue
|
|
663
|
-
|
|
664
|
-
authValue = "Bearer \(apiKey.dropFirst(7).trimmingCharacters(in: .whitespaces))"
|
|
690
|
+
let authValue = if apiKey.lowercased().hasPrefix("bearer ") {
|
|
691
|
+
"Bearer \(apiKey.dropFirst(7).trimmingCharacters(in: .whitespaces))"
|
|
665
692
|
} else {
|
|
666
|
-
|
|
693
|
+
"Bearer \(apiKey)"
|
|
667
694
|
}
|
|
668
695
|
request.setValue(authValue, forHTTPHeaderField: "Authorization")
|
|
669
696
|
request.setValue("application/json", forHTTPHeaderField: "Accept")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import Foundation
|
|
2
1
|
import Combine
|
|
2
|
+
import Foundation
|
|
3
3
|
|
|
4
4
|
#if canImport(UIKit)
|
|
5
5
|
import UIKit
|
|
@@ -21,7 +21,6 @@ import AppKit
|
|
|
21
21
|
/// - Lifecycle-aware sync (app launch, foreground, background)
|
|
22
22
|
/// - Network reachability monitoring
|
|
23
23
|
public actor BackgroundSyncManager {
|
|
24
|
-
|
|
25
24
|
// MARK: - Properties
|
|
26
25
|
|
|
27
26
|
/// The underlying sync engine
|
|
@@ -49,7 +48,7 @@ public actor BackgroundSyncManager {
|
|
|
49
48
|
/// Observers registered for sync events
|
|
50
49
|
private var observers: [WeakSyncObserver] = []
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
// Background task identifier (iOS only)
|
|
53
52
|
#if canImport(UIKit)
|
|
54
53
|
private var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
|
|
55
54
|
#endif
|
|
@@ -63,14 +62,14 @@ public actor BackgroundSyncManager {
|
|
|
63
62
|
/// - config: Configuration for sync intervals and behavior
|
|
64
63
|
public init(
|
|
65
64
|
syncEngine: any SyncEngine,
|
|
66
|
-
config: SyncConfiguration = .default
|
|
67
|
-
|
|
65
|
+
config: SyncConfiguration = .default)
|
|
66
|
+
{
|
|
68
67
|
self.syncEngine = syncEngine
|
|
69
68
|
self.config = config
|
|
70
69
|
|
|
71
70
|
// Register for app lifecycle notifications
|
|
72
71
|
Task {
|
|
73
|
-
await registerLifecycleObservers()
|
|
72
|
+
await self.registerLifecycleObservers()
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -78,19 +77,19 @@ public actor BackgroundSyncManager {
|
|
|
78
77
|
|
|
79
78
|
/// Starts background synchronization
|
|
80
79
|
public func start() {
|
|
81
|
-
guard !isEnabled else { return }
|
|
80
|
+
guard !self.isEnabled else { return }
|
|
82
81
|
|
|
83
|
-
isEnabled = true
|
|
84
|
-
scheduleNextSync()
|
|
85
|
-
notifyObservers { $0.syncDidStart() }
|
|
82
|
+
self.isEnabled = true
|
|
83
|
+
self.scheduleNextSync()
|
|
84
|
+
self.notifyObservers { $0.syncDidStart() }
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
/// Stops background synchronization
|
|
89
88
|
public func stop() {
|
|
90
|
-
isEnabled = false
|
|
89
|
+
self.isEnabled = false
|
|
91
90
|
Task { @MainActor in
|
|
92
|
-
syncTimer?.invalidate()
|
|
93
|
-
syncTimer = nil
|
|
91
|
+
self.syncTimer?.invalidate()
|
|
92
|
+
self.syncTimer = nil
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
|
|
@@ -100,21 +99,21 @@ public actor BackgroundSyncManager {
|
|
|
100
99
|
/// - Returns: Result of the sync operation
|
|
101
100
|
@discardableResult
|
|
102
101
|
public func syncNow(options: SyncOptions? = nil) async -> Result<SyncResult, SyncError> {
|
|
103
|
-
let syncOptions = options ?? config.defaultSyncOptions
|
|
102
|
+
let syncOptions = options ?? self.config.defaultSyncOptions
|
|
104
103
|
|
|
105
|
-
notifyObservers { $0.syncDidStart() }
|
|
104
|
+
self.notifyObservers { $0.syncDidStart() }
|
|
106
105
|
|
|
107
106
|
let result = await syncEngine.sync(options: syncOptions)
|
|
108
107
|
|
|
109
108
|
switch result {
|
|
110
|
-
case .success(
|
|
111
|
-
lastSyncDate = Date()
|
|
112
|
-
recordSyncHistory(success: true, result: syncResult)
|
|
113
|
-
notifyObservers { $0.syncDidComplete(result: syncResult) }
|
|
114
|
-
|
|
115
|
-
case .failure(
|
|
116
|
-
recordSyncHistory(success: false, error: error)
|
|
117
|
-
notifyObservers { $0.syncDidFail(error: error) }
|
|
109
|
+
case let .success(syncResult):
|
|
110
|
+
self.lastSyncDate = Date()
|
|
111
|
+
self.recordSyncHistory(success: true, result: syncResult)
|
|
112
|
+
self.notifyObservers { $0.syncDidComplete(result: syncResult) }
|
|
113
|
+
|
|
114
|
+
case let .failure(error):
|
|
115
|
+
self.recordSyncHistory(success: false, error: error)
|
|
116
|
+
self.notifyObservers { $0.syncDidFail(error: error) }
|
|
118
117
|
}
|
|
119
118
|
|
|
120
119
|
return result
|
|
@@ -125,26 +124,25 @@ public actor BackgroundSyncManager {
|
|
|
125
124
|
/// - Parameter observer: The observer to add
|
|
126
125
|
public func addObserver(_ observer: SyncObserver) {
|
|
127
126
|
let weakObserver = WeakSyncObserver(observer)
|
|
128
|
-
observers.append(weakObserver)
|
|
127
|
+
self.observers.append(weakObserver)
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/// Removes an observer
|
|
132
131
|
///
|
|
133
132
|
/// - Parameter observer: The observer to remove
|
|
134
133
|
public func removeObserver(_ observer: SyncObserver) {
|
|
135
|
-
observers.removeAll { $0.observer === observer }
|
|
134
|
+
self.observers.removeAll { $0.observer === observer }
|
|
136
135
|
}
|
|
137
136
|
|
|
138
137
|
/// Returns sync statistics
|
|
139
138
|
public var statistics: SyncStatistics {
|
|
140
139
|
SyncStatistics(
|
|
141
|
-
lastSyncDate: lastSyncDate,
|
|
142
|
-
totalSyncs: syncHistory.count,
|
|
143
|
-
successfulSyncs: syncHistory.
|
|
144
|
-
failedSyncs: syncHistory.
|
|
145
|
-
averageDuration: calculateAverageDuration(),
|
|
146
|
-
syncHistory: Array(syncHistory.suffix(10))
|
|
147
|
-
)
|
|
140
|
+
lastSyncDate: self.lastSyncDate,
|
|
141
|
+
totalSyncs: self.syncHistory.count,
|
|
142
|
+
successfulSyncs: self.syncHistory.count(where: { $0.success }),
|
|
143
|
+
failedSyncs: self.syncHistory.count(where: { !$0.success }),
|
|
144
|
+
averageDuration: self.calculateAverageDuration(),
|
|
145
|
+
syncHistory: Array(self.syncHistory.suffix(10)))
|
|
148
146
|
}
|
|
149
147
|
|
|
150
148
|
/// Updates sync configuration
|
|
@@ -153,8 +151,8 @@ public actor BackgroundSyncManager {
|
|
|
153
151
|
public func updateConfiguration(_ config: SyncConfiguration) async {
|
|
154
152
|
// Note: Configuration updates require recreating the manager
|
|
155
153
|
// This is a placeholder - in practice, you'd want to make config mutable
|
|
156
|
-
if isEnabled {
|
|
157
|
-
stop()
|
|
154
|
+
if self.isEnabled {
|
|
155
|
+
self.stop()
|
|
158
156
|
// Caller should create a new BackgroundSyncManager with new config
|
|
159
157
|
}
|
|
160
158
|
}
|
|
@@ -163,17 +161,17 @@ public actor BackgroundSyncManager {
|
|
|
163
161
|
|
|
164
162
|
/// Schedules the next automatic sync
|
|
165
163
|
private func scheduleNextSync() {
|
|
166
|
-
guard isEnabled else { return }
|
|
164
|
+
guard self.isEnabled else { return }
|
|
167
165
|
|
|
168
|
-
let interval = determineNextInterval()
|
|
166
|
+
let interval = self.determineNextInterval()
|
|
169
167
|
|
|
170
168
|
Task { @MainActor [weak self] in
|
|
171
|
-
guard let self
|
|
169
|
+
guard let self else { return }
|
|
172
170
|
self.syncTimer?.invalidate()
|
|
173
171
|
self.syncTimer = Timer.scheduledTimer(
|
|
174
172
|
withTimeInterval: interval,
|
|
175
|
-
repeats: false
|
|
176
|
-
|
|
173
|
+
repeats: false)
|
|
174
|
+
{ [weak self] _ in
|
|
177
175
|
Task {
|
|
178
176
|
await self?.performScheduledSync()
|
|
179
177
|
}
|
|
@@ -185,9 +183,9 @@ public actor BackgroundSyncManager {
|
|
|
185
183
|
private func determineNextInterval() -> TimeInterval {
|
|
186
184
|
#if canImport(UIKit)
|
|
187
185
|
let isActive = UIApplication.shared.applicationState == .active
|
|
188
|
-
return isActive ? config.activeSyncInterval : config.backgroundSyncInterval
|
|
186
|
+
return isActive ? self.config.activeSyncInterval : self.config.backgroundSyncInterval
|
|
189
187
|
#else
|
|
190
|
-
return config.activeSyncInterval
|
|
188
|
+
return self.config.activeSyncInterval
|
|
191
189
|
#endif
|
|
192
190
|
}
|
|
193
191
|
|
|
@@ -195,27 +193,27 @@ public actor BackgroundSyncManager {
|
|
|
195
193
|
private func performScheduledSync() async {
|
|
196
194
|
#if canImport(UIKit)
|
|
197
195
|
// Begin background task
|
|
198
|
-
backgroundTaskID = UIApplication.shared.beginBackgroundTask { [weak self] in
|
|
196
|
+
self.backgroundTaskID = UIApplication.shared.beginBackgroundTask { [weak self] in
|
|
199
197
|
self?.endBackgroundTask()
|
|
200
198
|
}
|
|
201
199
|
#endif
|
|
202
200
|
|
|
203
|
-
await syncNow()
|
|
201
|
+
await self.syncNow()
|
|
204
202
|
|
|
205
203
|
#if canImport(UIKit)
|
|
206
|
-
endBackgroundTask()
|
|
204
|
+
self.endBackgroundTask()
|
|
207
205
|
#endif
|
|
208
206
|
|
|
209
207
|
// Schedule next sync
|
|
210
|
-
scheduleNextSync()
|
|
208
|
+
self.scheduleNextSync()
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
#if canImport(UIKit)
|
|
214
212
|
/// Ends the background task
|
|
215
213
|
private func endBackgroundTask() {
|
|
216
|
-
guard backgroundTaskID != .invalid else { return }
|
|
217
|
-
UIApplication.shared.endBackgroundTask(backgroundTaskID)
|
|
218
|
-
backgroundTaskID = .invalid
|
|
214
|
+
guard self.backgroundTaskID != .invalid else { return }
|
|
215
|
+
UIApplication.shared.endBackgroundTask(self.backgroundTaskID)
|
|
216
|
+
self.backgroundTaskID = .invalid
|
|
219
217
|
}
|
|
220
218
|
#endif
|
|
221
219
|
|
|
@@ -225,16 +223,16 @@ public actor BackgroundSyncManager {
|
|
|
225
223
|
NotificationCenter.default.addObserver(
|
|
226
224
|
forName: UIApplication.didBecomeActiveNotification,
|
|
227
225
|
object: nil,
|
|
228
|
-
queue: .main
|
|
229
|
-
|
|
226
|
+
queue: .main)
|
|
227
|
+
{ [weak self] _ in
|
|
230
228
|
Task { await self?.handleAppBecameActive() }
|
|
231
229
|
}
|
|
232
230
|
|
|
233
231
|
NotificationCenter.default.addObserver(
|
|
234
232
|
forName: UIApplication.didEnterBackgroundNotification,
|
|
235
233
|
object: nil,
|
|
236
|
-
queue: .main
|
|
237
|
-
|
|
234
|
+
queue: .main)
|
|
235
|
+
{ [weak self] _ in
|
|
238
236
|
Task { await self?.handleAppEnteredBackground() }
|
|
239
237
|
}
|
|
240
238
|
#endif
|
|
@@ -243,16 +241,16 @@ public actor BackgroundSyncManager {
|
|
|
243
241
|
NotificationCenter.default.addObserver(
|
|
244
242
|
forName: NSApplication.didBecomeActiveNotification,
|
|
245
243
|
object: nil,
|
|
246
|
-
queue: .main
|
|
247
|
-
|
|
244
|
+
queue: .main)
|
|
245
|
+
{ [weak self] _ in
|
|
248
246
|
Task { await self?.handleAppBecameActive() }
|
|
249
247
|
}
|
|
250
248
|
|
|
251
249
|
NotificationCenter.default.addObserver(
|
|
252
250
|
forName: NSApplication.didResignActiveNotification,
|
|
253
251
|
object: nil,
|
|
254
|
-
queue: .main
|
|
255
|
-
|
|
252
|
+
queue: .main)
|
|
253
|
+
{ [weak self] _ in
|
|
256
254
|
Task { await self?.handleAppEnteredBackground() }
|
|
257
255
|
}
|
|
258
256
|
#endif
|
|
@@ -260,18 +258,18 @@ public actor BackgroundSyncManager {
|
|
|
260
258
|
|
|
261
259
|
/// Handles app becoming active
|
|
262
260
|
private func handleAppBecameActive() async {
|
|
263
|
-
guard config.syncOnForeground else { return }
|
|
261
|
+
guard self.config.syncOnForeground else { return }
|
|
264
262
|
|
|
265
263
|
// Sync if last sync was more than interval ago
|
|
266
|
-
if shouldSyncOnForeground() {
|
|
267
|
-
await syncNow()
|
|
264
|
+
if self.shouldSyncOnForeground() {
|
|
265
|
+
await self.syncNow()
|
|
268
266
|
}
|
|
269
267
|
}
|
|
270
268
|
|
|
271
269
|
/// Handles app entering background
|
|
272
270
|
private func handleAppEnteredBackground() async {
|
|
273
|
-
guard config.syncOnBackground else { return }
|
|
274
|
-
await syncNow()
|
|
271
|
+
guard self.config.syncOnBackground else { return }
|
|
272
|
+
await self.syncNow()
|
|
275
273
|
}
|
|
276
274
|
|
|
277
275
|
/// Determines if sync should occur on foreground
|
|
@@ -279,35 +277,34 @@ public actor BackgroundSyncManager {
|
|
|
279
277
|
guard let lastSync = lastSyncDate else { return true }
|
|
280
278
|
|
|
281
279
|
let elapsed = Date().timeIntervalSince(lastSync)
|
|
282
|
-
return elapsed >= config.activeSyncInterval
|
|
280
|
+
return elapsed >= self.config.activeSyncInterval
|
|
283
281
|
}
|
|
284
282
|
|
|
285
283
|
/// Records sync operation in history
|
|
286
284
|
private func recordSyncHistory(
|
|
287
285
|
success: Bool,
|
|
288
286
|
result: SyncResult? = nil,
|
|
289
|
-
error: SyncError? = nil
|
|
290
|
-
|
|
287
|
+
error: SyncError? = nil)
|
|
288
|
+
{
|
|
291
289
|
let entry = SyncHistoryEntry(
|
|
292
290
|
timestamp: Date(),
|
|
293
291
|
success: success,
|
|
294
292
|
duration: result?.duration ?? 0,
|
|
295
293
|
pushedCount: result?.pushedCount ?? 0,
|
|
296
294
|
fetchedCount: result?.fetchedCount ?? 0,
|
|
297
|
-
error: error?.localizedDescription
|
|
298
|
-
)
|
|
295
|
+
error: error?.localizedDescription)
|
|
299
296
|
|
|
300
|
-
syncHistory.append(entry)
|
|
297
|
+
self.syncHistory.append(entry)
|
|
301
298
|
|
|
302
299
|
// Trim history if needed
|
|
303
|
-
if syncHistory.count > maxHistoryEntries {
|
|
304
|
-
syncHistory.removeFirst(syncHistory.count - maxHistoryEntries)
|
|
300
|
+
if self.syncHistory.count > self.maxHistoryEntries {
|
|
301
|
+
self.syncHistory.removeFirst(self.syncHistory.count - self.maxHistoryEntries)
|
|
305
302
|
}
|
|
306
303
|
}
|
|
307
304
|
|
|
308
305
|
/// Calculates average sync duration
|
|
309
306
|
private func calculateAverageDuration() -> TimeInterval {
|
|
310
|
-
let successfulSyncs = syncHistory.filter { $0.success && $0.duration > 0 }
|
|
307
|
+
let successfulSyncs = self.syncHistory.filter { $0.success && $0.duration > 0 }
|
|
311
308
|
guard !successfulSyncs.isEmpty else { return 0 }
|
|
312
309
|
|
|
313
310
|
let totalDuration = successfulSyncs.reduce(0.0) { $0 + $1.duration }
|
|
@@ -317,10 +314,10 @@ public actor BackgroundSyncManager {
|
|
|
317
314
|
/// Notifies all observers
|
|
318
315
|
private func notifyObservers(_ action: (SyncObserver) -> Void) {
|
|
319
316
|
// Clean up deallocated observers
|
|
320
|
-
observers.removeAll { $0.observer == nil }
|
|
317
|
+
self.observers.removeAll { $0.observer == nil }
|
|
321
318
|
|
|
322
319
|
// Notify active observers
|
|
323
|
-
for weakObserver in observers {
|
|
320
|
+
for weakObserver in self.observers {
|
|
324
321
|
if let observer = weakObserver.observer {
|
|
325
322
|
action(observer)
|
|
326
323
|
}
|
|
@@ -343,8 +340,8 @@ public struct SyncConfiguration: Sendable {
|
|
|
343
340
|
backgroundSyncInterval: TimeInterval = 3600,
|
|
344
341
|
syncOnForeground: Bool = true,
|
|
345
342
|
syncOnBackground: Bool = true,
|
|
346
|
-
defaultSyncOptions: SyncOptions = SyncOptions()
|
|
347
|
-
|
|
343
|
+
defaultSyncOptions: SyncOptions = SyncOptions())
|
|
344
|
+
{
|
|
348
345
|
self.activeSyncInterval = activeSyncInterval
|
|
349
346
|
self.backgroundSyncInterval = backgroundSyncInterval
|
|
350
347
|
self.syncOnForeground = syncOnForeground
|
|
@@ -365,8 +362,8 @@ public struct SyncStatistics: Sendable {
|
|
|
365
362
|
public let syncHistory: [SyncHistoryEntry]
|
|
366
363
|
|
|
367
364
|
public var successRate: Double {
|
|
368
|
-
guard totalSyncs > 0 else { return 0 }
|
|
369
|
-
return Double(successfulSyncs) / Double(totalSyncs) * 100
|
|
365
|
+
guard self.totalSyncs > 0 else { return 0 }
|
|
366
|
+
return Double(self.successfulSyncs) / Double(self.totalSyncs) * 100
|
|
370
367
|
}
|
|
371
368
|
}
|
|
372
369
|
|
|
@@ -381,5 +378,7 @@ public struct SyncHistoryEntry: Sendable, Codable {
|
|
|
381
378
|
|
|
382
379
|
private struct WeakSyncObserver {
|
|
383
380
|
weak var observer: SyncObserver?
|
|
384
|
-
init(_ observer: SyncObserver) {
|
|
381
|
+
init(_ observer: SyncObserver) {
|
|
382
|
+
self.observer = observer
|
|
383
|
+
}
|
|
385
384
|
}
|