@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
@@ -2,8 +2,8 @@ import Foundation
2
2
 
3
3
  // MARK: - Example Usage of Runic Sync Infrastructure
4
4
 
5
- /// This file demonstrates how to integrate and use the Runic sync infrastructure
6
- /// in your macOS/iOS application. These examples are for documentation purposes.
5
+ // This file demonstrates how to integrate and use the Runic sync infrastructure
6
+ // in your macOS/iOS application. These examples are for documentation purposes.
7
7
 
8
8
  #if DEBUG
9
9
 
@@ -16,8 +16,7 @@ func setupSyncExample() async {
16
16
  // Create background sync manager with default configuration
17
17
  let syncManager = BackgroundSyncManager(
18
18
  syncEngine: syncEngine,
19
- config: .default
20
- )
19
+ config: .default)
21
20
 
22
21
  // Start automatic background sync
23
22
  await syncManager.start()
@@ -30,19 +29,17 @@ func setupSyncExample() async {
30
29
  func customConfigurationExample() async {
31
30
  // Create custom sync configuration
32
31
  let config = SyncConfiguration(
33
- activeSyncInterval: 180, // 3 minutes when app is active
32
+ activeSyncInterval: 180, // 3 minutes when app is active
34
33
  backgroundSyncInterval: 1800, // 30 minutes in background
35
- syncOnForeground: true, // Sync when app comes to foreground
36
- syncOnBackground: true, // Sync when app goes to background
34
+ syncOnForeground: true, // Sync when app comes to foreground
35
+ syncOnBackground: true, // Sync when app goes to background
37
36
  defaultSyncOptions: SyncOptions(
38
37
  forceFullSync: false,
39
38
  batchSize: 50,
40
39
  conflictStrategy: .lastWriteWins,
41
40
  backgroundMode: true,
42
41
  timeout: 30.0,
43
- encryptSensitiveData: true
44
- )
45
- )
42
+ encryptSensitiveData: true))
46
43
 
47
44
  let syncEngine = iCloudSyncEngine()
48
45
  let syncManager = BackgroundSyncManager(syncEngine: syncEngine, config: config)
@@ -59,15 +56,14 @@ func syncUsageDataExample() async {
59
56
  let usageRecord = UsageSnapshotSyncRecord(
60
57
  providerID: "claude",
61
58
  primaryUsed: 75000,
62
- primaryLimit: 100000,
59
+ primaryLimit: 100_000,
63
60
  secondaryUsed: 50,
64
61
  secondaryLimit: 100,
65
62
  costUSD: 3.75,
66
63
  accountEmail: "user@example.com",
67
64
  updatedAt: Date(),
68
65
  deviceName: "MacBook Pro",
69
- platform: "macOS"
70
- )
66
+ platform: "macOS")
71
67
 
72
68
  // Queue for high-priority sync
73
69
  await syncEngine.enqueue(usageRecord, priority: .high)
@@ -76,9 +72,9 @@ func syncUsageDataExample() async {
76
72
  let result = await syncEngine.push(records: [usageRecord])
77
73
 
78
74
  switch result {
79
- case .success(let recordIDs):
75
+ case let .success(recordIDs):
80
76
  print("Successfully synced \(recordIDs.count) records")
81
- case .failure(let error):
77
+ case let .failure(error):
82
78
  print("Sync failed: \(error.localizedDescription)")
83
79
  }
84
80
  }
@@ -95,8 +91,7 @@ func syncPreferencesExample() async {
95
91
  notificationsEnabled: true,
96
92
  autoRefreshEnabled: true,
97
93
  theme: "dark",
98
- displayFormat: "detailed"
99
- )
94
+ displayFormat: "detailed")
100
95
 
101
96
  // Push to CloudKit
102
97
  let result = await syncEngine.push(records: [preferences])
@@ -104,7 +99,7 @@ func syncPreferencesExample() async {
104
99
  switch result {
105
100
  case .success:
106
101
  print("Preferences synced successfully")
107
- case .failure(let error):
102
+ case let .failure(error):
108
103
  print("Failed to sync preferences: \(error)")
109
104
  }
110
105
  }
@@ -117,11 +112,10 @@ func syncAlertConfigExample() async {
117
112
  // Create alert configuration for Claude
118
113
  let alertConfig = AlertConfigurationSyncRecord(
119
114
  providerID: "claude",
120
- warningThreshold: 0.75, // Warn at 75%
121
- criticalThreshold: 0.90, // Critical at 90%
115
+ warningThreshold: 0.75, // Warn at 75%
116
+ criticalThreshold: 0.90, // Critical at 90%
122
117
  notificationChannels: ["system", "email"],
123
- enabled: true
124
- )
118
+ enabled: true)
125
119
 
126
120
  await syncEngine.enqueue(alertConfig, priority: .normal)
127
121
  }
@@ -135,21 +129,20 @@ func manualSyncExample() async {
135
129
  // Perform manual sync with custom options
136
130
  let options = SyncOptions(
137
131
  forceFullSync: true,
138
- conflictStrategy: .lastWriteWins
139
- )
132
+ conflictStrategy: .lastWriteWins)
140
133
 
141
134
  let result = await syncManager.syncNow(options: options)
142
135
 
143
136
  switch result {
144
- case .success(let syncResult):
137
+ case let .success(syncResult):
145
138
  print("""
146
- Sync completed:
147
- - Pushed: \(syncResult.pushedCount)
148
- - Fetched: \(syncResult.fetchedCount)
149
- - Conflicts resolved: \(syncResult.conflictsResolved)
150
- - Duration: \(syncResult.duration)s
151
- """)
152
- case .failure(let error):
139
+ Sync completed:
140
+ - Pushed: \(syncResult.pushedCount)
141
+ - Fetched: \(syncResult.fetchedCount)
142
+ - Conflicts resolved: \(syncResult.conflictsResolved)
143
+ - Duration: \(syncResult.duration)s
144
+ """)
145
+ case let .failure(error):
153
146
  print("Sync failed: \(error)")
154
147
  }
155
148
  }
@@ -165,7 +158,8 @@ func conflictResolutionExample() async {
165
158
  // Register custom merge handler for usage snapshots
166
159
  await resolver.registerMergeHandler(for: CloudKitRecordType.usageSnapshot) { local, remote in
167
160
  guard let localUsage = local as? UsageSnapshotSyncRecord,
168
- let remoteUsage = remote as? UsageSnapshotSyncRecord else {
161
+ let remoteUsage = remote as? UsageSnapshotSyncRecord
162
+ else {
169
163
  return local
170
164
  }
171
165
 
@@ -196,10 +190,10 @@ final class SyncProgressObserver: SyncObserver {
196
190
 
197
191
  func syncDidComplete(result: SyncResult) {
198
192
  print("""
199
- ✅ Sync completed successfully:
200
- - Duration: \(String(format: "%.2f", result.duration))s
201
- - Records synced: \(result.pushedCount + result.fetchedCount)
202
- """)
193
+ ✅ Sync completed successfully:
194
+ - Duration: \(String(format: "%.2f", result.duration))s
195
+ - Records synced: \(result.pushedCount + result.fetchedCount)
196
+ """)
203
197
  }
204
198
 
205
199
  func syncDidFail(error: SyncError) {
@@ -232,15 +226,15 @@ func statisticsExample() async {
232
226
  let stats = await syncManager.statistics
233
227
 
234
228
  print("""
235
- Sync Statistics:
236
- ================
237
- Total syncs: \(stats.totalSyncs)
238
- Successful: \(stats.successfulSyncs)
239
- Failed: \(stats.failedSyncs)
240
- Success rate: \(String(format: "%.1f%%", stats.successRate))
241
- Average duration: \(String(format: "%.2f", stats.averageDuration))s
242
- Last sync: \(stats.lastSyncDate?.description ?? "Never")
243
- """)
229
+ Sync Statistics:
230
+ ================
231
+ Total syncs: \(stats.totalSyncs)
232
+ Successful: \(stats.successfulSyncs)
233
+ Failed: \(stats.failedSyncs)
234
+ Success rate: \(String(format: "%.1f%%", stats.successRate))
235
+ Average duration: \(String(format: "%.2f", stats.averageDuration))s
236
+ Last sync: \(stats.lastSyncDate?.description ?? "Never")
237
+ """)
244
238
 
245
239
  // Check pending operations
246
240
  let pendingCount = await syncEngine.pendingOperationCount
@@ -255,7 +249,7 @@ func errorHandlingExample() async {
255
249
  let result = await syncEngine.sync(options: SyncOptions())
256
250
 
257
251
  switch result {
258
- case .success(let syncResult):
252
+ case let .success(syncResult):
259
253
  // Check for warnings
260
254
  if !syncResult.warnings.isEmpty {
261
255
  print("⚠️ Sync completed with warnings:")
@@ -264,7 +258,7 @@ func errorHandlingExample() async {
264
258
  }
265
259
  }
266
260
 
267
- case .failure(let error):
261
+ case let .failure(error):
268
262
  // Handle specific errors
269
263
  switch error {
270
264
  case .iCloudAccountUnavailable:
@@ -279,10 +273,10 @@ func errorHandlingExample() async {
279
273
  case .timeout:
280
274
  print("Sync timed out. Please try again.")
281
275
 
282
- case .conflictResolutionFailed(let details):
276
+ case let .conflictResolutionFailed(details):
283
277
  print("Conflict resolution failed: \(details)")
284
278
 
285
- case .cloudKitError(let ckError):
279
+ case let .cloudKitError(ckError):
286
280
  print("CloudKit error: \(ckError.localizedDescription)")
287
281
 
288
282
  default:
@@ -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
@@ -18,7 +18,7 @@ extension CloudKitRecordType {
18
18
  ///
19
19
  /// Using a custom zone allows for atomic batch operations and better
20
20
  /// change tracking for team-related records.
21
- public struct TeamSyncZone {
21
+ public enum TeamSyncZone {
22
22
  public static let zoneName = "TeamCollaborationZone"
23
23
  public static let ownerName = CKCurrentUserDefaultName
24
24
 
@@ -27,7 +27,7 @@ public struct TeamSyncZone {
27
27
  }
28
28
 
29
29
  public static func createZone() -> CKRecordZone {
30
- CKRecordZone(zoneID: zoneID)
30
+ CKRecordZone(zoneID: self.zoneID)
31
31
  }
32
32
  }
33
33
 
@@ -72,8 +72,8 @@ public struct TeamRecord: SyncableRecord {
72
72
  updatedAt: Date = Date(),
73
73
  description: String? = nil,
74
74
  avatarURL: String? = nil,
75
- isActive: Bool = true
76
- ) {
75
+ isActive: Bool = true)
76
+ {
77
77
  self.recordID = recordID
78
78
  self.version = version
79
79
  self.modifiedAt = modifiedAt
@@ -93,26 +93,25 @@ public struct TeamRecord: SyncableRecord {
93
93
  let zoneID = TeamSyncZone.zoneID
94
94
  let record = CKRecord(
95
95
  recordType: recordType,
96
- recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
97
- )
96
+ recordID: CKRecord.ID(recordName: self.recordID, zoneID: zoneID))
98
97
 
99
98
  // Sync metadata
100
- record["version"] = version as CKRecordValue
101
- record["modifiedAt"] = modifiedAt as CKRecordValue
102
- record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
99
+ record["version"] = self.version as CKRecordValue
100
+ record["modifiedAt"] = self.modifiedAt as CKRecordValue
101
+ record["lastModifiedDeviceID"] = self.lastModifiedDeviceID as? CKRecordValue
103
102
 
104
103
  // Team data
105
- record["teamID"] = teamID as CKRecordValue
106
- record["name"] = name as CKRecordValue
107
- record["ownerUserID"] = ownerUserID as CKRecordValue
108
- record["totalQuota"] = totalQuota as CKRecordValue
109
- record["createdAt"] = createdAt as CKRecordValue
110
- record["updatedAt"] = updatedAt as CKRecordValue
111
- record["isActive"] = (isActive ? 1 : 0) as CKRecordValue
104
+ record["teamID"] = self.teamID as CKRecordValue
105
+ record["name"] = self.name as CKRecordValue
106
+ record["ownerUserID"] = self.ownerUserID as CKRecordValue
107
+ record["totalQuota"] = self.totalQuota as CKRecordValue
108
+ record["createdAt"] = self.createdAt as CKRecordValue
109
+ record["updatedAt"] = self.updatedAt as CKRecordValue
110
+ record["isActive"] = (self.isActive ? 1 : 0) as CKRecordValue
112
111
 
113
112
  // Optional fields
114
- record["description"] = description as? CKRecordValue
115
- record["avatarURL"] = avatarURL as? CKRecordValue
113
+ record["description"] = self.description as? CKRecordValue
114
+ record["avatarURL"] = self.avatarURL as? CKRecordValue
116
115
 
117
116
  return record
118
117
  }
@@ -144,8 +143,7 @@ public struct TeamRecord: SyncableRecord {
144
143
  updatedAt: updatedAt,
145
144
  description: ckRecord["description"] as? String,
146
145
  avatarURL: ckRecord["avatarURL"] as? String,
147
- isActive: isActiveInt == 1
148
- )
146
+ isActive: isActiveInt == 1)
149
147
  }
150
148
  }
151
149
 
@@ -180,14 +178,14 @@ public struct TeamMembershipRecord: SyncableRecord {
180
178
  public let acceptedAt: Date?
181
179
  public let isActive: Bool
182
180
 
183
- // Reference to parent team
181
+ /// Reference to parent team
184
182
  public let teamRecordName: String
185
183
 
186
184
  public enum TeamRole: String, Codable, Sendable {
187
- case owner = "owner"
188
- case admin = "admin"
189
- case member = "member"
190
- case viewer = "viewer"
185
+ case owner
186
+ case admin
187
+ case member
188
+ case viewer
191
189
  }
192
190
 
193
191
  public init(
@@ -206,8 +204,8 @@ public struct TeamMembershipRecord: SyncableRecord {
206
204
  invitedBy: String? = nil,
207
205
  acceptedAt: Date? = nil,
208
206
  isActive: Bool = true,
209
- teamRecordName: String
210
- ) {
207
+ teamRecordName: String)
208
+ {
211
209
  self.recordID = recordID
212
210
  self.version = version
213
211
  self.modifiedAt = modifiedAt
@@ -230,37 +228,35 @@ public struct TeamMembershipRecord: SyncableRecord {
230
228
  let zoneID = TeamSyncZone.zoneID
231
229
  let record = CKRecord(
232
230
  recordType: recordType,
233
- recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
234
- )
231
+ recordID: CKRecord.ID(recordName: self.recordID, zoneID: zoneID))
235
232
 
236
233
  // Sync metadata
237
- record["version"] = version as CKRecordValue
238
- record["modifiedAt"] = modifiedAt as CKRecordValue
239
- record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
234
+ record["version"] = self.version as CKRecordValue
235
+ record["modifiedAt"] = self.modifiedAt as CKRecordValue
236
+ record["lastModifiedDeviceID"] = self.lastModifiedDeviceID as? CKRecordValue
240
237
 
241
238
  // Membership data
242
- record["membershipID"] = membershipID as CKRecordValue
243
- record["teamID"] = teamID as CKRecordValue
244
- record["userID"] = userID as CKRecordValue
245
- record["role"] = role.rawValue as CKRecordValue
246
- record["quotaLimit"] = quotaLimit as? CKRecordValue
247
- record["joinedAt"] = joinedAt as CKRecordValue
248
- record["isActive"] = (isActive ? 1 : 0) as CKRecordValue
239
+ record["membershipID"] = self.membershipID as CKRecordValue
240
+ record["teamID"] = self.teamID as CKRecordValue
241
+ record["userID"] = self.userID as CKRecordValue
242
+ record["role"] = self.role.rawValue as CKRecordValue
243
+ record["quotaLimit"] = self.quotaLimit as? CKRecordValue
244
+ record["joinedAt"] = self.joinedAt as CKRecordValue
245
+ record["isActive"] = (self.isActive ? 1 : 0) as CKRecordValue
249
246
 
250
247
  // Optional encrypted fields
251
- if let email = email {
248
+ if let email {
252
249
  let encrypted = try encryptString(email)
253
250
  record["email"] = encrypted as CKRecordValue
254
251
  }
255
- record["displayName"] = displayName as? CKRecordValue
256
- record["invitedBy"] = invitedBy as? CKRecordValue
257
- record["acceptedAt"] = acceptedAt as? CKRecordValue
252
+ record["displayName"] = self.displayName as? CKRecordValue
253
+ record["invitedBy"] = self.invitedBy as? CKRecordValue
254
+ record["acceptedAt"] = self.acceptedAt as? CKRecordValue
258
255
 
259
256
  // Reference to parent team
260
257
  let teamReference = CKRecord.Reference(
261
- recordID: CKRecord.ID(recordName: teamRecordName, zoneID: zoneID),
262
- action: .deleteSelf
263
- )
258
+ recordID: CKRecord.ID(recordName: self.teamRecordName, zoneID: zoneID),
259
+ action: .deleteSelf)
264
260
  record["teamReference"] = teamReference as CKRecordValue
265
261
 
266
262
  return record
@@ -302,8 +298,7 @@ public struct TeamMembershipRecord: SyncableRecord {
302
298
  invitedBy: ckRecord["invitedBy"] as? String,
303
299
  acceptedAt: ckRecord["acceptedAt"] as? Date,
304
300
  isActive: isActiveInt == 1,
305
- teamRecordName: teamReference.recordID.recordName
306
- )
301
+ teamRecordName: teamReference.recordID.recordName)
307
302
  }
308
303
  }
309
304
 
@@ -337,14 +332,14 @@ public struct ProjectOwnershipRecord: SyncableRecord {
337
332
  public let tags: [String]
338
333
  public let isArchived: Bool
339
334
 
340
- // Reference to team (if team-owned)
335
+ /// Reference to team (if team-owned)
341
336
  public let teamRecordName: String?
342
337
 
343
338
  public enum AccessLevel: String, Codable, Sendable {
344
- case `private` = "private"
339
+ case `private`
345
340
  case teamReadOnly = "team_read_only"
346
341
  case teamReadWrite = "team_read_write"
347
- case `public` = "public"
342
+ case `public`
348
343
  }
349
344
 
350
345
  public init(
@@ -362,8 +357,8 @@ public struct ProjectOwnershipRecord: SyncableRecord {
362
357
  projectDescription: String? = nil,
363
358
  tags: [String] = [],
364
359
  isArchived: Bool = false,
365
- teamRecordName: String? = nil
366
- ) {
360
+ teamRecordName: String? = nil)
361
+ {
367
362
  self.recordID = recordID
368
363
  self.version = version
369
364
  self.modifiedAt = modifiedAt
@@ -385,34 +380,32 @@ public struct ProjectOwnershipRecord: SyncableRecord {
385
380
  let zoneID = TeamSyncZone.zoneID
386
381
  let record = CKRecord(
387
382
  recordType: recordType,
388
- recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
389
- )
383
+ recordID: CKRecord.ID(recordName: self.recordID, zoneID: zoneID))
390
384
 
391
385
  // Sync metadata
392
- record["version"] = version as CKRecordValue
393
- record["modifiedAt"] = modifiedAt as CKRecordValue
394
- record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
386
+ record["version"] = self.version as CKRecordValue
387
+ record["modifiedAt"] = self.modifiedAt as CKRecordValue
388
+ record["lastModifiedDeviceID"] = self.lastModifiedDeviceID as? CKRecordValue
395
389
 
396
390
  // Project ownership data
397
- record["projectID"] = projectID as CKRecordValue
398
- record["teamID"] = teamID as? CKRecordValue
399
- record["ownerUserID"] = ownerUserID as CKRecordValue
400
- record["sharedWith"] = sharedWith as CKRecordValue
401
- record["accessLevel"] = accessLevel.rawValue as CKRecordValue
402
- record["createdAt"] = createdAt as CKRecordValue
403
- record["isArchived"] = (isArchived ? 1 : 0) as CKRecordValue
391
+ record["projectID"] = self.projectID as CKRecordValue
392
+ record["teamID"] = self.teamID as? CKRecordValue
393
+ record["ownerUserID"] = self.ownerUserID as CKRecordValue
394
+ record["sharedWith"] = self.sharedWith as CKRecordValue
395
+ record["accessLevel"] = self.accessLevel.rawValue as CKRecordValue
396
+ record["createdAt"] = self.createdAt as CKRecordValue
397
+ record["isArchived"] = (self.isArchived ? 1 : 0) as CKRecordValue
404
398
 
405
399
  // Optional metadata
406
- record["projectName"] = projectName as? CKRecordValue
407
- record["projectDescription"] = projectDescription as? CKRecordValue
408
- record["tags"] = tags as CKRecordValue
400
+ record["projectName"] = self.projectName as? CKRecordValue
401
+ record["projectDescription"] = self.projectDescription as? CKRecordValue
402
+ record["tags"] = self.tags as CKRecordValue
409
403
 
410
404
  // Reference to team (if team-owned)
411
- if let teamRecordName = teamRecordName {
405
+ if let teamRecordName {
412
406
  let teamReference = CKRecord.Reference(
413
407
  recordID: CKRecord.ID(recordName: teamRecordName, zoneID: zoneID),
414
- action: .none
415
- )
408
+ action: .none)
416
409
  record["teamReference"] = teamReference as CKRecordValue
417
410
  }
418
411
 
@@ -450,8 +443,7 @@ public struct ProjectOwnershipRecord: SyncableRecord {
450
443
  projectDescription: ckRecord["projectDescription"] as? String,
451
444
  tags: ckRecord["tags"] as? [String] ?? [],
452
445
  isArchived: isArchivedInt == 1,
453
- teamRecordName: teamReference?.recordID.recordName
454
- )
446
+ teamRecordName: teamReference?.recordID.recordName)
455
447
  }
456
448
  }
457
449
 
@@ -484,7 +476,7 @@ public struct TeamUsageSnapshotRecord: SyncableRecord {
484
476
  public let mostActiveUser: String?
485
477
  public let averageTokensPerMember: Double
486
478
 
487
- // Reference to parent team
479
+ /// Reference to parent team
488
480
  public let teamRecordName: String
489
481
 
490
482
  public struct MemberUsageData: Codable, Sendable, Hashable {
@@ -515,8 +507,8 @@ public struct TeamUsageSnapshotRecord: SyncableRecord {
515
507
  primaryProvider: String? = nil,
516
508
  mostActiveUser: String? = nil,
517
509
  averageTokensPerMember: Double = 0.0,
518
- teamRecordName: String
519
- ) {
510
+ teamRecordName: String)
511
+ {
520
512
  self.recordID = recordID
521
513
  self.version = version
522
514
  self.modifiedAt = modifiedAt
@@ -537,36 +529,34 @@ public struct TeamUsageSnapshotRecord: SyncableRecord {
537
529
  let zoneID = TeamSyncZone.zoneID
538
530
  let record = CKRecord(
539
531
  recordType: recordType,
540
- recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
541
- )
532
+ recordID: CKRecord.ID(recordName: self.recordID, zoneID: zoneID))
542
533
 
543
534
  // Sync metadata
544
- record["version"] = version as CKRecordValue
545
- record["modifiedAt"] = modifiedAt as CKRecordValue
546
- record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
535
+ record["version"] = self.version as CKRecordValue
536
+ record["modifiedAt"] = self.modifiedAt as CKRecordValue
537
+ record["lastModifiedDeviceID"] = self.lastModifiedDeviceID as? CKRecordValue
547
538
 
548
539
  // Team usage data
549
- record["teamID"] = teamID as CKRecordValue
550
- record["date"] = date as CKRecordValue
551
- record["totalTokens"] = totalTokens as CKRecordValue
552
- record["totalCost"] = totalCost as CKRecordValue
553
- record["quotaUsedPercent"] = quotaUsedPercent as CKRecordValue
540
+ record["teamID"] = self.teamID as CKRecordValue
541
+ record["date"] = self.date as CKRecordValue
542
+ record["totalTokens"] = self.totalTokens as CKRecordValue
543
+ record["totalCost"] = self.totalCost as CKRecordValue
544
+ record["quotaUsedPercent"] = self.quotaUsedPercent as CKRecordValue
554
545
 
555
546
  // Encode member usage as JSON
556
547
  let encoder = JSONEncoder()
557
- let memberUsageData = try encoder.encode(memberUsage)
548
+ let memberUsageData = try encoder.encode(self.memberUsage)
558
549
  record["memberUsage"] = String(data: memberUsageData, encoding: .utf8) as? CKRecordValue
559
550
 
560
551
  // Optional metrics
561
- record["primaryProvider"] = primaryProvider as? CKRecordValue
562
- record["mostActiveUser"] = mostActiveUser as? CKRecordValue
563
- record["averageTokensPerMember"] = averageTokensPerMember as CKRecordValue
552
+ record["primaryProvider"] = self.primaryProvider as? CKRecordValue
553
+ record["mostActiveUser"] = self.mostActiveUser as? CKRecordValue
554
+ record["averageTokensPerMember"] = self.averageTokensPerMember as CKRecordValue
564
555
 
565
556
  // Reference to parent team
566
557
  let teamReference = CKRecord.Reference(
567
- recordID: CKRecord.ID(recordName: teamRecordName, zoneID: zoneID),
568
- action: .deleteSelf
569
- )
558
+ recordID: CKRecord.ID(recordName: self.teamRecordName, zoneID: zoneID),
559
+ action: .deleteSelf)
570
560
  record["teamReference"] = teamReference as CKRecordValue
571
561
 
572
562
  return record
@@ -588,7 +578,8 @@ public struct TeamUsageSnapshotRecord: SyncableRecord {
588
578
  // Decode member usage from JSON
589
579
  var memberUsage: [String: MemberUsageData] = [:]
590
580
  if let memberUsageJSON = ckRecord["memberUsage"] as? String,
591
- let data = memberUsageJSON.data(using: .utf8) {
581
+ let data = memberUsageJSON.data(using: .utf8)
582
+ {
592
583
  let decoder = JSONDecoder()
593
584
  memberUsage = (try? decoder.decode([String: MemberUsageData].self, from: data)) ?? [:]
594
585
  }
@@ -607,8 +598,7 @@ public struct TeamUsageSnapshotRecord: SyncableRecord {
607
598
  primaryProvider: ckRecord["primaryProvider"] as? String,
608
599
  mostActiveUser: ckRecord["mostActiveUser"] as? String,
609
600
  averageTokensPerMember: ckRecord["averageTokensPerMember"] as? Double ?? 0.0,
610
- teamRecordName: teamReference.recordID.recordName
611
- )
601
+ teamRecordName: teamReference.recordID.recordName)
612
602
  }
613
603
  }
614
604
 
@@ -616,7 +606,6 @@ public struct TeamUsageSnapshotRecord: SyncableRecord {
616
606
 
617
607
  /// Helper methods for querying team-related records
618
608
  public enum TeamRecordQuery {
619
-
620
609
  /// Creates a predicate to query teams by owner
621
610
  public static func teamsByOwner(_ ownerUserID: String) -> NSPredicate {
622
611
  NSPredicate(format: "ownerUserID == %@", ownerUserID)
@@ -644,7 +633,11 @@ public enum TeamRecordQuery {
644
633
 
645
634
  /// Creates a predicate to query team usage snapshots by date range
646
635
  public static func teamUsageByDateRange(teamID: String, startDate: Date, endDate: Date) -> NSPredicate {
647
- NSPredicate(format: "teamID == %@ AND date >= %@ AND date <= %@", teamID, startDate as NSDate, endDate as NSDate)
636
+ NSPredicate(
637
+ format: "teamID == %@ AND date >= %@ AND date <= %@",
638
+ teamID,
639
+ startDate as NSDate,
640
+ endDate as NSDate)
648
641
  }
649
642
 
650
643
  /// Creates a predicate to query latest team usage
@@ -748,7 +741,7 @@ private func saveToKeychain(key: String, data: Data) {
748
741
  kSecAttrAccount as String: key,
749
742
  kSecUseDataProtectionKeychain as String: true,
750
743
  kSecValueData as String: data,
751
- kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
744
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock,
752
745
  ]
753
746
  SecItemDelete(query as CFDictionary)
754
747
  SecItemAdd(query as CFDictionary, nil)
@@ -761,7 +754,7 @@ private func loadFromKeychain(key: String) -> Data? {
761
754
  kSecAttrAccount as String: key,
762
755
  kSecUseDataProtectionKeychain as String: true,
763
756
  kSecUseAuthenticationUI as String: "kSecUseAuthenticationUIFail" as CFString,
764
- kSecReturnData as String: true
757
+ kSecReturnData as String: true,
765
758
  ]
766
759
  let authContext = LAContext()
767
760
  authContext.interactionNotAllowed = true