@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.
Files changed (348) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +1 -1
  3. package/README.md +47 -29
  4. package/RuniciOS/Core/RunicTypographyiOS.swift +24 -0
  5. package/RuniciOS/Views/ProviderDetailView.swift +9 -9
  6. package/RuniciOS/Views/ProviderListView.swift +10 -10
  7. package/RuniciOS/Widgets/LargeWidget.swift +14 -14
  8. package/RuniciOS/Widgets/LiveActivity.swift +19 -19
  9. package/RuniciOS/Widgets/LockScreenWidget.swift +15 -15
  10. package/RuniciOS/Widgets/MediumWidget.swift +10 -10
  11. package/RuniciOS/Widgets/RunicWidget.swift +5 -5
  12. package/RuniciOS/Widgets/SmallWidget.swift +9 -9
  13. package/Scripts/package_app.sh +2 -2
  14. package/Sources/Runic/About.swift +3 -3
  15. package/Sources/Runic/AppNotifications.swift +5 -3
  16. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +17 -4
  17. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +5 -4
  18. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +30 -16
  19. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +6 -3
  20. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +47 -14
  21. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +6 -4
  22. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +40 -12
  23. package/Sources/Runic/Controllers/StatusItemController.swift +43 -43
  24. package/Sources/Runic/Core/BudgetNotificationManager.swift +2 -1
  25. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +2 -2
  26. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +1 -1
  27. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +4 -4
  28. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +3 -6
  29. package/Sources/Runic/Core/Rendering/IconRenderer.swift +91 -85
  30. package/Sources/Runic/Core/Rendering/IconView.swift +3 -1
  31. package/Sources/Runic/Core/RunicColors.swift +6 -6
  32. package/Sources/Runic/Core/RunicSpacing.swift +7 -7
  33. package/Sources/Runic/Core/RunicTypography.swift +206 -0
  34. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +10 -36
  35. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +10 -36
  36. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +10 -36
  37. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +10 -36
  38. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +10 -36
  39. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +10 -10
  40. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +10 -36
  41. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +10 -36
  42. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +10 -36
  43. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +10 -36
  44. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +10 -36
  45. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +10 -36
  46. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +10 -36
  47. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +10 -36
  48. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +10 -36
  49. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +10 -36
  50. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +1 -1
  51. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +10 -36
  52. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +10 -36
  53. package/Sources/Runic/Core/Stores/SettingsStore.swift +99 -52
  54. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +10 -36
  55. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +19 -6
  56. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +1 -1
  57. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +1 -1
  58. package/Sources/Runic/Core/Stores/UsageStore.swift +234 -167
  59. package/Sources/Runic/Core/Stores/XAITokenStore.swift +10 -36
  60. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +10 -36
  61. package/Sources/Runic/Core/UsageExporter.swift +8 -4
  62. package/Sources/Runic/ProviderRegistry.swift +1 -1
  63. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +1 -1
  64. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +1 -1
  65. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +1 -1
  66. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +1 -1
  67. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +1 -1
  68. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +1 -1
  69. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +1 -1
  70. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +1 -1
  71. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +1 -1
  72. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +1 -1
  73. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +1 -1
  74. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +1 -1
  75. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +1 -1
  76. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +1 -1
  77. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +1 -1
  78. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +1 -1
  79. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +1 -1
  80. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +1 -1
  81. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +1 -1
  82. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +5 -2
  83. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +1 -1
  84. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +1 -1
  85. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +1 -1
  86. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +1 -1
  87. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +1 -1
  88. package/Sources/Runic/Resources/Fonts/Caveat-Bold.ttf +0 -0
  89. package/Sources/Runic/Resources/Fonts/Caveat-Regular.ttf +0 -0
  90. package/Sources/Runic/Resources/Fonts/FiraCode-Bold.ttf +0 -0
  91. package/Sources/Runic/Resources/Fonts/FiraCode-Light.ttf +0 -0
  92. package/Sources/Runic/Resources/Fonts/FiraCode-Medium.ttf +0 -0
  93. package/Sources/Runic/Resources/Fonts/FiraCode-Regular.ttf +0 -0
  94. package/Sources/Runic/Resources/Fonts/FiraCode-SemiBold.ttf +0 -0
  95. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Bold.ttf +0 -0
  96. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Medium.ttf +0 -0
  97. package/Sources/Runic/Resources/Fonts/IBMPlexMono-Regular.ttf +0 -0
  98. package/Sources/Runic/Resources/Fonts/IBMPlexMono-SemiBold.ttf +0 -0
  99. package/Sources/Runic/Resources/Fonts/Inconsolata-Bold.ttf +0 -0
  100. package/Sources/Runic/Resources/Fonts/Inconsolata-Medium.ttf +0 -0
  101. package/Sources/Runic/Resources/Fonts/Inconsolata-Regular.ttf +0 -0
  102. package/Sources/Runic/Resources/Fonts/Inconsolata-SemiBold.ttf +0 -0
  103. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Bold.ttf +0 -0
  104. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Medium.ttf +0 -0
  105. package/Sources/Runic/Resources/Fonts/JetBrainsMono-Regular.ttf +0 -0
  106. package/Sources/Runic/Resources/Fonts/JetBrainsMono-SemiBold.ttf +0 -0
  107. package/Sources/Runic/Resources/Fonts/SpaceMono-Bold.ttf +0 -0
  108. package/Sources/Runic/Resources/Fonts/SpaceMono-Regular.ttf +0 -0
  109. package/Sources/Runic/Resources/provider-context-windows.json +20 -0
  110. package/Sources/Runic/RunicApp.swift +46 -36
  111. package/Sources/Runic/SessionQuotaNotifications.swift +2 -2
  112. package/Sources/Runic/UsagePaceText.swift +1 -1
  113. package/Sources/Runic/UsageProgressBar.swift +13 -9
  114. package/Sources/Runic/Utilities/LoadingPattern.swift +4 -2
  115. package/Sources/Runic/Utilities/PerformanceConstants.swift +28 -28
  116. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +19 -21
  117. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +19 -19
  118. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +7 -7
  119. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +5 -5
  120. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +29 -30
  121. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +20 -17
  122. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +14 -14
  123. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +15 -20
  124. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +3 -4
  125. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +5 -25
  126. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +1 -1
  127. package/Sources/Runic/Views/Menu/MenuCardView.swift +120 -131
  128. package/Sources/Runic/Views/Menu/MenuContent.swift +11 -8
  129. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +4 -2
  130. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +1 -8
  131. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +20 -14
  132. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +198 -58
  133. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +16 -11
  134. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +27 -27
  135. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +4 -6
  136. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +24 -11
  137. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +5 -5
  138. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +7 -8
  139. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +28 -76
  140. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +8 -7
  141. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +7 -7
  142. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +50 -47
  143. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +15 -14
  144. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +7 -7
  145. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +27 -27
  146. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +38 -42
  147. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +39 -43
  148. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +21 -21
  149. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +7 -7
  150. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +49 -50
  151. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +10 -10
  152. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +40 -21
  153. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +7 -7
  154. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +108 -33
  155. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +26 -23
  156. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +271 -226
  157. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +6 -2
  158. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +7 -5
  159. package/Sources/Runic/Views/Preferences/PreferencesView.swift +38 -31
  160. package/Sources/Runic/Views/Rating/QualityRatingView.swift +11 -11
  161. package/Sources/Runic/Views/Team/TeamDashboardView.swift +29 -24
  162. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +16 -16
  163. package/Sources/Runic/Views/Team/TeamManagementView.swift +50 -49
  164. package/Sources/Runic/Views/Team/TeamMemberRow.swift +8 -8
  165. package/Sources/RunicCLI/CLIEntry.swift +83 -42
  166. package/Sources/RunicCLI/CLIRenderer.swift +1 -1
  167. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +60 -60
  168. package/Sources/RunicCLI/Commands/ModelsCommand.swift +45 -44
  169. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +83 -69
  170. package/Sources/RunicCLI/Commands/ResetCommand.swift +50 -49
  171. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +31 -32
  172. package/Sources/RunicClaudeWebProbe/main.swift +1 -1
  173. package/Sources/RunicCore/AlertRuleStore.swift +20 -20
  174. package/Sources/RunicCore/BrowserCookieImportOrder.swift +3 -3
  175. package/Sources/RunicCore/CostUsageModels.swift +1 -1
  176. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +29 -30
  177. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +80 -71
  178. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +53 -109
  179. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +3 -3
  180. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +3 -3
  181. package/Sources/RunicCore/Logging/OSLogLogHandler.swift +6 -6
  182. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +47 -48
  183. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +1 -1
  184. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +1 -1
  185. package/Sources/RunicCore/Performance/PerformanceModels.swift +4 -4
  186. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +81 -64
  187. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +2 -2
  188. package/Sources/RunicCore/ProjectBudgetStore.swift +15 -12
  189. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +4 -2
  190. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +2 -2
  191. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +2 -2
  192. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +3 -3
  193. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +8 -8
  194. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +5 -3
  195. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +6 -6
  196. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +3 -7
  197. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +2 -2
  198. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +2 -2
  199. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +3 -3
  200. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +4 -2
  201. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +4 -4
  202. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +3 -1
  203. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +1 -1
  204. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +1 -0
  205. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +12 -1
  206. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +4 -2
  207. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +3 -1
  208. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +5 -3
  209. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +2 -2
  210. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +3 -3
  211. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +7 -4
  212. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +4 -4
  213. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +5 -3
  214. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +9 -10
  215. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +4 -2
  216. package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +1 -0
  217. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +2 -2
  218. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +2 -2
  219. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +4 -4
  220. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +4 -2
  221. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +3 -2
  222. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +2 -2
  223. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +2 -2
  224. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +4 -2
  225. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +6 -2
  226. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +2 -2
  227. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +2 -2
  228. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +2 -2
  229. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +2 -2
  230. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +10 -5
  231. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +8 -11
  232. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +10 -10
  233. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +2 -2
  234. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +2 -2
  235. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +2 -2
  236. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +25 -16
  237. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +2 -2
  238. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +2 -2
  239. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +2 -2
  240. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +2 -2
  241. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +5 -3
  242. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +27 -16
  243. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +2 -2
  244. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +2 -2
  245. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +2 -2
  246. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +2 -2
  247. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +5 -5
  248. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +1 -1
  249. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +3 -3
  250. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +2 -2
  251. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +2 -2
  252. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +4 -2
  253. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +51 -30
  254. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +76 -77
  255. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +55 -58
  256. package/Sources/RunicCore/Sync/SyncProtocol.swift +21 -22
  257. package/Sources/RunicCore/Sync/SyncRecord.swift +46 -49
  258. package/Sources/RunicCore/Sync/SyncUsageExample.swift +44 -50
  259. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +94 -101
  260. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +51 -46
  261. package/Sources/RunicCore/Team/TeamModels.swift +111 -110
  262. package/Sources/RunicCore/Team/TeamStore.swift +126 -109
  263. package/Sources/RunicCore/UsageFetcher.swift +2 -2
  264. package/Sources/RunicCore/UsageFormatter.swift +25 -25
  265. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +141 -29
  266. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +59 -5
  267. package/Sources/RunicCore/UsageLedger/LedgerCache.swift +161 -0
  268. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +1 -1
  269. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +40 -25
  270. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +1 -1
  271. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +55 -16
  272. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +6 -6
  273. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +1 -1
  274. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +2 -2
  275. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +5 -5
  276. package/Tests/RunicTests/AppDelegateTests.swift +8 -3
  277. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +2 -3
  278. package/Tests/RunicTests/CLIArgumentParsingTests.swift +3 -4
  279. package/Tests/RunicTests/CLICostTests.swift +6 -7
  280. package/Tests/RunicTests/CLIProviderSelectionTests.swift +2 -3
  281. package/Tests/RunicTests/CLISnapshotTests.swift +3 -4
  282. package/Tests/RunicTests/CLIWebFallbackTests.swift +3 -4
  283. package/Tests/RunicTests/ClaudeOAuthTests.swift +11 -12
  284. package/Tests/RunicTests/ClaudeResilienceTests.swift +3 -4
  285. package/Tests/RunicTests/ClaudeUsageTests.swift +20 -21
  286. package/Tests/RunicTests/CodexOAuthTests.swift +7 -8
  287. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +1 -3
  288. package/Tests/RunicTests/CopilotTokenResolverTests.swift +5 -6
  289. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +4 -5
  290. package/Tests/RunicTests/CostUsageDecodingTests.swift +15 -17
  291. package/Tests/RunicTests/CostUsagePricingTests.swift +5 -7
  292. package/Tests/RunicTests/CostUsageScannerTests.swift +10 -12
  293. package/Tests/RunicTests/CursorStatusProbeTests.swift +15 -16
  294. package/Tests/RunicTests/FactoryStatusProbeTests.swift +3 -5
  295. package/Tests/RunicTests/GeminiLoginAlertTests.swift +3 -4
  296. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +11 -11
  297. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +8 -8
  298. package/Tests/RunicTests/GeminiStatusProbeTests.swift +20 -21
  299. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +2 -3
  300. package/Tests/RunicTests/InstallOriginTests.swift +1 -2
  301. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +2 -2
  302. package/Tests/RunicTests/LiveAccountTests.swift +3 -3
  303. package/Tests/RunicTests/LoadingPatternTests.swift +3 -4
  304. package/Tests/RunicTests/MenuCardModelTests.swift +33 -34
  305. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +4 -1
  306. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +4 -1
  307. package/Tests/RunicTests/MiniMaxParsingTests.swift +4 -5
  308. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +4 -1
  309. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +4 -6
  310. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +1 -2
  311. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +5 -6
  312. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +2 -3
  313. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +12 -13
  314. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +17 -6
  315. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +52 -13
  316. package/Tests/RunicTests/PathBuilderTests.swift +14 -15
  317. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +3 -4
  318. package/Tests/RunicTests/ProviderIconResourcesTests.swift +1 -2
  319. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +1 -2
  320. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +53 -19
  321. package/Tests/RunicTests/ProviderToggleStoreTests.swift +10 -11
  322. package/Tests/RunicTests/RunicTests.swift +6 -7
  323. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +5 -6
  324. package/Tests/RunicTests/SettingsStoreTests.swift +149 -45
  325. package/Tests/RunicTests/StatusItemAnimationTests.swift +8 -3
  326. package/Tests/RunicTests/StatusMenuTests.swift +63 -22
  327. package/Tests/RunicTests/StatusProbeTests.swift +28 -29
  328. package/Tests/RunicTests/SubprocessRunnerTests.swift +1 -3
  329. package/Tests/RunicTests/SubscriptionDetectionTests.swift +8 -9
  330. package/Tests/RunicTests/TTYCommandRunnerTests.swift +8 -9
  331. package/Tests/RunicTests/TeamShowbackExporterTests.swift +2 -4
  332. package/Tests/RunicTests/TextParsingTests.swift +1 -2
  333. package/Tests/RunicTests/UsageFormatterTests.swift +15 -16
  334. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +12 -14
  335. package/Tests/RunicTests/UsageLedgerForecastTests.swift +4 -6
  336. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +7 -9
  337. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +6 -8
  338. package/Tests/RunicTests/UsagePaceTests.swift +5 -6
  339. package/Tests/RunicTests/UsagePaceTextTests.swift +8 -9
  340. package/Tests/RunicTests/UsagePolicyEngineTests.swift +3 -5
  341. package/Tests/RunicTests/WidgetSnapshotTests.swift +1 -3
  342. package/Tests/RunicTests/ZaiProviderTests.swift +3 -5
  343. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +4 -1
  344. package/appcast.xml +36 -37
  345. package/package.json +1 -1
  346. package/runic.png +0 -0
  347. package/runic.svg +15 -0
  348. package/version.env +2 -2
@@ -1,5 +1,5 @@
1
- import RunicMacroSupport
2
1
  import Foundation
2
+ import RunicMacroSupport
3
3
 
4
4
  @ProviderDescriptorRegistration
5
5
  @ProviderDescriptorDefinition
@@ -71,7 +71,7 @@ struct XAIApiFetchStrategy: ProviderFetchStrategy {
71
71
  }
72
72
  }
73
73
 
74
- enum XAISettingsError: LocalizedError, Sendable {
74
+ enum XAISettingsError: LocalizedError {
75
75
  case missingToken
76
76
 
77
77
  var errorDescription: String? {
@@ -8,7 +8,7 @@ struct XAIModelsResponse: Decodable {
8
8
  }
9
9
  }
10
10
 
11
- struct XAIUsageFetcher {
11
+ enum XAIUsageFetcher {
12
12
  static let apiURL = URL(string: "https://api.x.ai/v1/models")!
13
13
 
14
14
  static func fetchModels(apiKey: String) async throws -> XAIModelsResponse {
@@ -57,7 +57,7 @@ extension XAIModelsResponse {
57
57
  }
58
58
  }
59
59
 
60
- enum XAIApiError: LocalizedError, Sendable {
60
+ enum XAIApiError: LocalizedError {
61
61
  case invalidResponse
62
62
  case httpError(statusCode: Int, body: String?)
63
63
 
@@ -1,5 +1,5 @@
1
- import RunicMacroSupport
2
1
  import Foundation
2
+ import RunicMacroSupport
3
3
 
4
4
  @ProviderDescriptorRegistration
5
5
  @ProviderDescriptorDefinition
@@ -33,7 +33,9 @@ public enum ZaiProviderDescriptor {
33
33
  color: ProviderColor(red: 232 / 255, green: 90 / 255, blue: 106 / 255)),
34
34
  tokenCost: ProviderTokenCostConfig(
35
35
  supportsTokenCost: false,
36
- noDataMessage: { "z.ai cost estimates are shown per-model in the details submenu (based on public API pricing)." }),
36
+ noDataMessage: {
37
+ "z.ai cost estimates are shown per-model in the details submenu (based on public API pricing)."
38
+ }),
37
39
  fetchPlan: ProviderFetchPlan(
38
40
  sourceModes: [.auto, .cli],
39
41
  pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [ZaiAPIFetchStrategy()] })),
@@ -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: 66_000),
211
- "glm-4.5v-thinking": Tier(inputPerMillion: 0.60, outputPerMillion: 1.80, contextWindow: 66_000),
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 { (self.success ?? (self.code == 200)) && (self.code ?? 200) == 200 }
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
- // Some responses use "message" instead of "msg".
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? { self.totalTokens ?? self.total_tokens }
411
- var resolvedTotalPrompts: Int? { self.totalPrompts ?? self.total_prompts }
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? { self.modelCode ?? self.model_code ?? self.model }
424
- var resolvedTokens: Int { self.tokens ?? self.usage ?? 0 }
425
- var resolvedPrompts: Int { self.prompts ?? self.calls ?? 0 }
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([ZaiToolUsageRaw].self, forKey: DynamicKey(stringValue: "tools")!) {
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? { self.tool ?? self.name }
462
- var resolvedCount: Int { self.count ?? self.usage ?? 0 }
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) { self.stringValue = stringValue }
469
- init?(intValue: Int) { self.stringValue = "\(intValue)"; self.intValue = intValue }
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 {
@@ -520,9 +548,7 @@ public struct ZaiUsageFetcher: Sendable {
520
548
  throw ZaiUsageError.apiError("HTTP \(httpResponse.statusCode): \(errorMessage)")
521
549
  }
522
550
 
523
- if let jsonString = String(data: data, encoding: .utf8) {
524
- Self.log.debug("z.ai quota response: \(jsonString)")
525
- }
551
+ Self.log.debug("z.ai quota response: HTTP \(httpResponse.statusCode), \(data.count) bytes")
526
552
 
527
553
  let apiResponse: ZaiQuotaLimitResponse
528
554
  do {
@@ -558,7 +584,7 @@ public struct ZaiUsageFetcher: Sendable {
558
584
  do {
559
585
  return try await self.fetchModelUsage(apiKey: apiKey)
560
586
  } catch {
561
- Self.log.info("z.ai model-usage endpoint unavailable: \(error.localizedDescription)")
587
+ self.log.info("z.ai model-usage endpoint unavailable: \(error.localizedDescription)")
562
588
  return nil
563
589
  }
564
590
  }
@@ -573,9 +599,7 @@ public struct ZaiUsageFetcher: Sendable {
573
599
  throw ZaiUsageError.apiError("model-usage endpoint returned non-200")
574
600
  }
575
601
 
576
- if let jsonString = String(data: data, encoding: .utf8) {
577
- Self.log.debug("z.ai model-usage response: \(jsonString)")
578
- }
602
+ Self.log.debug("z.ai model-usage response: HTTP \(httpResponse.statusCode), \(data.count) bytes")
579
603
 
580
604
  let raw = try JSONDecoder().decode(ZaiModelUsageResponse.self, from: data)
581
605
  let models = raw.data?.models ?? []
@@ -620,7 +644,7 @@ public struct ZaiUsageFetcher: Sendable {
620
644
  do {
621
645
  return try await self.fetchToolUsage(apiKey: apiKey)
622
646
  } catch {
623
- Self.log.info("z.ai tool-usage endpoint unavailable: \(error.localizedDescription)")
647
+ self.log.info("z.ai tool-usage endpoint unavailable: \(error.localizedDescription)")
624
648
  return nil
625
649
  }
626
650
  }
@@ -635,9 +659,7 @@ public struct ZaiUsageFetcher: Sendable {
635
659
  throw ZaiUsageError.apiError("tool-usage endpoint returned non-200")
636
660
  }
637
661
 
638
- if let jsonString = String(data: data, encoding: .utf8) {
639
- Self.log.debug("z.ai tool-usage response: \(jsonString)")
640
- }
662
+ Self.log.debug("z.ai tool-usage response: HTTP \(httpResponse.statusCode), \(data.count) bytes")
641
663
 
642
664
  let raw = try JSONDecoder().decode(ZaiToolUsageResponse.self, from: data)
643
665
  let tools = raw.data?.tools ?? []
@@ -665,11 +687,10 @@ public struct ZaiUsageFetcher: Sendable {
665
687
  var request = URLRequest(url: requestURL)
666
688
  request.httpMethod = "GET"
667
689
  // z.ai expects "Bearer <token>" — add prefix if the user didn't include it.
668
- let authValue: String
669
- if apiKey.lowercased().hasPrefix("bearer ") {
670
- authValue = "Bearer \(apiKey.dropFirst(7).trimmingCharacters(in: .whitespaces))"
690
+ let authValue = if apiKey.lowercased().hasPrefix("bearer ") {
691
+ "Bearer \(apiKey.dropFirst(7).trimmingCharacters(in: .whitespaces))"
671
692
  } else {
672
- authValue = "Bearer \(apiKey)"
693
+ "Bearer \(apiKey)"
673
694
  }
674
695
  request.setValue(authValue, forHTTPHeaderField: "Authorization")
675
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
- /// Background task identifier (iOS only)
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(let syncResult):
111
- lastSyncDate = Date()
112
- recordSyncHistory(success: true, result: syncResult)
113
- notifyObservers { $0.syncDidComplete(result: syncResult) }
114
-
115
- case .failure(let error):
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.filter { $0.success }.count,
144
- failedSyncs: syncHistory.filter { !$0.success }.count,
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 = self else { return }
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
- ) { [weak self] _ in
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
- ) { [weak self] _ in
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
- ) { [weak self] _ in
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
- ) { [weak self] _ in
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
- ) { [weak self] _ in
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) { self.observer = observer }
381
+ init(_ observer: SyncObserver) {
382
+ self.observer = observer
383
+ }
385
384
  }