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