@sriinnu/runic 1.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 (584) hide show
  1. package/.swiftformat +50 -0
  2. package/.swiftlint.yml +160 -0
  3. package/CHANGELOG.md +307 -0
  4. package/CONTRIBUTING.md +456 -0
  5. package/Icon.icns +0 -0
  6. package/Icon.icon/Assets/runic.png +0 -0
  7. package/Icon.icon/icon.json +36 -0
  8. package/LICENSE +21 -0
  9. package/Package.resolved +51 -0
  10. package/Package.swift +136 -0
  11. package/Packages/Helix/LICENSE +21 -0
  12. package/Packages/Helix/Package.swift +29 -0
  13. package/Packages/Helix/README.md +976 -0
  14. package/Packages/Helix/Sources/Helix/Binding.swift +111 -0
  15. package/Packages/Helix/Sources/Helix/CommandParser.swift +160 -0
  16. package/Packages/Helix/Sources/Helix/CommandSignature.swift +117 -0
  17. package/Packages/Helix/Sources/Helix/ExpressibleFromArgument.swift +72 -0
  18. package/Packages/Helix/Sources/Helix/HelpGenerator.swift +175 -0
  19. package/Packages/Helix/Sources/Helix/NameSpecification.swift +98 -0
  20. package/Packages/Helix/Sources/Helix/ParsableCommand.swift +204 -0
  21. package/Packages/Helix/Sources/Helix/Platform/DarwinPlatformContext.swift +44 -0
  22. package/Packages/Helix/Sources/Helix/Platform/LinuxPlatformContext.swift +34 -0
  23. package/Packages/Helix/Sources/Helix/Platform/PlatformContext+Default.swift +91 -0
  24. package/Packages/Helix/Sources/Helix/Platform/PlatformContext.swift +94 -0
  25. package/Packages/Helix/Sources/Helix/Platform/PlatformPath.swift +167 -0
  26. package/Packages/Helix/Sources/Helix/Platform/Stdio.swift +99 -0
  27. package/Packages/Helix/Sources/Helix/Platform/WebPlatformContext.swift +146 -0
  28. package/Packages/Helix/Sources/Helix/Platform/WindowsPlatformContext.swift +97 -0
  29. package/Packages/Helix/Sources/Helix/Program.swift +204 -0
  30. package/Packages/Helix/Sources/Helix/PropertyWrappers.swift +367 -0
  31. package/Packages/Silo/LICENSE +21 -0
  32. package/Packages/Silo/Package.swift +34 -0
  33. package/Packages/Silo/README.md +466 -0
  34. package/Packages/Silo/Sources/Silo/BinaryCookiesReader.swift +169 -0
  35. package/Packages/Silo/Sources/Silo/BrowserCookieAnalytics.swift +365 -0
  36. package/Packages/Silo/Sources/Silo/BrowserCookieBackends.swift +594 -0
  37. package/Packages/Silo/Sources/Silo/BrowserCookieClient.swift +326 -0
  38. package/Packages/Silo/Sources/Silo/BrowserCookieExport.swift +154 -0
  39. package/Packages/Silo/Sources/Silo/BrowserCookieImport.swift +119 -0
  40. package/Packages/Silo/Sources/Silo/BrowserCookieMockStore.swift +121 -0
  41. package/Packages/Silo/Sources/Silo/BrowserCookieModels.swift +391 -0
  42. package/Packages/Silo/Sources/Silo/ChromiumCookieReader.swift +240 -0
  43. package/Packages/Silo/Sources/Silo/CryptoSupport.swift +86 -0
  44. package/Packages/Silo/Sources/Silo/FirefoxCookieReader.swift +172 -0
  45. package/Packages/Silo/Sources/Silo/LinuxChromiumCookieReader.swift +172 -0
  46. package/Packages/Silo/Sources/Silo/LinuxSecretService.swift +105 -0
  47. package/Packages/Silo/Sources/Silo/MacOSChromiumCookieReader.swift +191 -0
  48. package/Packages/Silo/Sources/Silo/SQLiteSnapshot.swift +41 -0
  49. package/Packages/Silo/Sources/Silo/WindowsChromiumCookieReader.swift +110 -0
  50. package/README.md +109 -0
  51. package/RuniciOS/App/RuniciOSApp.swift +105 -0
  52. package/RuniciOS/Core/iOSUsageStore.swift +192 -0
  53. package/RuniciOS/Views/ProviderDetailView.swift +312 -0
  54. package/RuniciOS/Views/ProviderListView.swift +228 -0
  55. package/RuniciOS/Widgets/LargeWidget.swift +367 -0
  56. package/RuniciOS/Widgets/LiveActivity.swift +342 -0
  57. package/RuniciOS/Widgets/LockScreenWidget.swift +273 -0
  58. package/RuniciOS/Widgets/MediumWidget.swift +370 -0
  59. package/RuniciOS/Widgets/RunicWidget.swift +274 -0
  60. package/RuniciOS/Widgets/SmallWidget.swift +281 -0
  61. package/RuniciOS/Widgets/WidgetModels.swift +285 -0
  62. package/RuniciOS/Widgets/WidgetTimeline.swift +270 -0
  63. package/Scripts/CREATION-SUMMARY.md +367 -0
  64. package/Scripts/DIRECTORY-STRUCTURE.md +358 -0
  65. package/Scripts/GETTING-STARTED-CHECKLIST.md +364 -0
  66. package/Scripts/INDEX.md +365 -0
  67. package/Scripts/QUICK-REFERENCE.md +164 -0
  68. package/Scripts/README-SCRIPTS.md +926 -0
  69. package/Scripts/build-android.sh +526 -0
  70. package/Scripts/build-ios.sh +529 -0
  71. package/Scripts/build-macos.sh +603 -0
  72. package/Scripts/build-windows.sh +504 -0
  73. package/Scripts/build_icon.sh +49 -0
  74. package/Scripts/changelog-to-html.sh +89 -0
  75. package/Scripts/check-release-assets.sh +10 -0
  76. package/Scripts/ci-config.yml +474 -0
  77. package/Scripts/compile_and_run.sh +219 -0
  78. package/Scripts/compile_and_run_adhoc.sh +4 -0
  79. package/Scripts/docs-list.mjs +119 -0
  80. package/Scripts/make_appcast.sh +58 -0
  81. package/Scripts/package_app.sh +409 -0
  82. package/Scripts/release-lib.sh +214 -0
  83. package/Scripts/release.sh +76 -0
  84. package/Scripts/setup-all.sh +387 -0
  85. package/Scripts/setup-ios.sh +404 -0
  86. package/Scripts/setup-react-native.sh +523 -0
  87. package/Scripts/sign-and-notarize.sh +122 -0
  88. package/Scripts/test_live_update.sh +38 -0
  89. package/Scripts/validate_changelog.sh +23 -0
  90. package/Scripts/verify_appcast.sh +92 -0
  91. package/Sources/Runic/About.swift +93 -0
  92. package/Sources/Runic/AppNotifications.swift +115 -0
  93. package/Sources/Runic/Controllers/StatusItemController+Actions.swift +657 -0
  94. package/Sources/Runic/Controllers/StatusItemController+Animation.swift +518 -0
  95. package/Sources/Runic/Controllers/StatusItemController+ChartSubmenus.swift +810 -0
  96. package/Sources/Runic/Controllers/StatusItemController+CustomProviders.swift +87 -0
  97. package/Sources/Runic/Controllers/StatusItemController+Menu.swift +1379 -0
  98. package/Sources/Runic/Controllers/StatusItemController+MenuCards.swift +337 -0
  99. package/Sources/Runic/Controllers/StatusItemController+MenuHelpers.swift +395 -0
  100. package/Sources/Runic/Controllers/StatusItemController.swift +360 -0
  101. package/Sources/Runic/Core/BudgetNotificationManager.swift +80 -0
  102. package/Sources/Runic/Core/Login/ClaudeLoginRunner.swift +103 -0
  103. package/Sources/Runic/Core/Login/CodexLoginRunner.swift +154 -0
  104. package/Sources/Runic/Core/Login/CursorLoginRunner.swift +206 -0
  105. package/Sources/Runic/Core/Login/GeminiLoginRunner.swift +95 -0
  106. package/Sources/Runic/Core/Rendering/IconRenderer.swift +1216 -0
  107. package/Sources/Runic/Core/Rendering/IconView.swift +120 -0
  108. package/Sources/Runic/Core/Rendering/ProviderBrandIcon.swift +71 -0
  109. package/Sources/Runic/Core/RunicAnimation.swift +53 -0
  110. package/Sources/Runic/Core/RunicColors.swift +73 -0
  111. package/Sources/Runic/Core/RunicCornerRadius.swift +15 -0
  112. package/Sources/Runic/Core/RunicSpacing.swift +68 -0
  113. package/Sources/Runic/Core/Stores/AuggieTokenStore.swift +143 -0
  114. package/Sources/Runic/Core/Stores/AzureOpenAITokenStore.swift +143 -0
  115. package/Sources/Runic/Core/Stores/CerebrasTokenStore.swift +143 -0
  116. package/Sources/Runic/Core/Stores/CohereTokenStore.swift +143 -0
  117. package/Sources/Runic/Core/Stores/CopilotTokenStore.swift +143 -0
  118. package/Sources/Runic/Core/Stores/CustomProviderStore.swift +158 -0
  119. package/Sources/Runic/Core/Stores/DeepSeekTokenStore.swift +143 -0
  120. package/Sources/Runic/Core/Stores/FireworksTokenStore.swift +143 -0
  121. package/Sources/Runic/Core/Stores/GroqTokenStore.swift +143 -0
  122. package/Sources/Runic/Core/Stores/KimiTokenStore.swift +143 -0
  123. package/Sources/Runic/Core/Stores/MiniMaxCookieHeaderStore.swift +143 -0
  124. package/Sources/Runic/Core/Stores/MiniMaxGroupIDStore.swift +143 -0
  125. package/Sources/Runic/Core/Stores/MiniMaxTokenStore.swift +143 -0
  126. package/Sources/Runic/Core/Stores/MistralTokenStore.swift +143 -0
  127. package/Sources/Runic/Core/Stores/OpenRouterTokenStore.swift +143 -0
  128. package/Sources/Runic/Core/Stores/PerplexityTokenStore.swift +143 -0
  129. package/Sources/Runic/Core/Stores/ProviderToggleStore.swift +30 -0
  130. package/Sources/Runic/Core/Stores/QwenTokenStore.swift +143 -0
  131. package/Sources/Runic/Core/Stores/SambaNovaTokenStore.swift +143 -0
  132. package/Sources/Runic/Core/Stores/SettingsStore.swift +1702 -0
  133. package/Sources/Runic/Core/Stores/TogetherTokenStore.swift +143 -0
  134. package/Sources/Runic/Core/Stores/UsageStore+Accessors.swift +20 -0
  135. package/Sources/Runic/Core/Stores/UsageStore+Status.swift +189 -0
  136. package/Sources/Runic/Core/Stores/UsageStore+TokenCost.swift +33 -0
  137. package/Sources/Runic/Core/Stores/UsageStore+WidgetSnapshot.swift +67 -0
  138. package/Sources/Runic/Core/Stores/UsageStore.swift +3564 -0
  139. package/Sources/Runic/Core/Stores/XAITokenStore.swift +143 -0
  140. package/Sources/Runic/Core/Stores/ZaiTokenStore.swift +143 -0
  141. package/Sources/Runic/Core/UsageExporter.swift +193 -0
  142. package/Sources/Runic/Date+RelativeDescription.swift +21 -0
  143. package/Sources/Runic/HiddenWindowView.swift +32 -0
  144. package/Sources/Runic/InstallOrigin.swift +9 -0
  145. package/Sources/Runic/KeyboardShortcuts+Names.swift +6 -0
  146. package/Sources/Runic/MouseLocationReader.swift +65 -0
  147. package/Sources/Runic/Notifications+Runic.swift +6 -0
  148. package/Sources/Runic/OpenAICreditsPurchaseWindowController.swift +503 -0
  149. package/Sources/Runic/ProviderRegistry.swift +100 -0
  150. package/Sources/Runic/ProviderSwitcherButtons.swift +267 -0
  151. package/Sources/Runic/Providers/Antigravity/AntigravityLoginFlow.swift +11 -0
  152. package/Sources/Runic/Providers/Antigravity/AntigravityProviderImplementation.swift +14 -0
  153. package/Sources/Runic/Providers/Auggie/AuggieProviderImplementation.swift +24 -0
  154. package/Sources/Runic/Providers/Azure/AzureProviderImplementation.swift +52 -0
  155. package/Sources/Runic/Providers/Bedrock/BedrockProviderImplementation.swift +42 -0
  156. package/Sources/Runic/Providers/Cerebras/CerebrasProviderImplementation.swift +24 -0
  157. package/Sources/Runic/Providers/Claude/ClaudeLoginFlow.swift +26 -0
  158. package/Sources/Runic/Providers/Claude/ClaudeProviderImplementation.swift +14 -0
  159. package/Sources/Runic/Providers/Codex/CodexLoginFlow.swift +18 -0
  160. package/Sources/Runic/Providers/Codex/CodexProviderImplementation.swift +15 -0
  161. package/Sources/Runic/Providers/Cohere/CohereProviderImplementation.swift +24 -0
  162. package/Sources/Runic/Providers/Copilot/CopilotLoginFlow.swift +193 -0
  163. package/Sources/Runic/Providers/Copilot/CopilotProviderImplementation.swift +110 -0
  164. package/Sources/Runic/Providers/Cursor/CursorLoginFlow.swift +28 -0
  165. package/Sources/Runic/Providers/Cursor/CursorProviderImplementation.swift +15 -0
  166. package/Sources/Runic/Providers/DeepSeek/DeepSeekProviderImplementation.swift +24 -0
  167. package/Sources/Runic/Providers/Factory/FactoryLoginFlow.swift +12 -0
  168. package/Sources/Runic/Providers/Factory/FactoryProviderImplementation.swift +15 -0
  169. package/Sources/Runic/Providers/Fireworks/FireworksProviderImplementation.swift +24 -0
  170. package/Sources/Runic/Providers/Gemini/GeminiLoginFlow.swift +20 -0
  171. package/Sources/Runic/Providers/Gemini/GeminiProviderImplementation.swift +15 -0
  172. package/Sources/Runic/Providers/Groq/GroqProviderImplementation.swift +24 -0
  173. package/Sources/Runic/Providers/Kimi/KimiProviderImplementation.swift +24 -0
  174. package/Sources/Runic/Providers/MiniMax/MiniMaxProviderImplementation.swift +32 -0
  175. package/Sources/Runic/Providers/Mistral/MistralProviderImplementation.swift +24 -0
  176. package/Sources/Runic/Providers/OpenRouter/OpenRouterProviderImplementation.swift +24 -0
  177. package/Sources/Runic/Providers/Perplexity/PerplexityProviderImplementation.swift +24 -0
  178. package/Sources/Runic/Providers/SambaNova/SambaNovaProviderImplementation.swift +24 -0
  179. package/Sources/Runic/Providers/Shared/ProviderCatalog.swift +15 -0
  180. package/Sources/Runic/Providers/Shared/ProviderImplementation.swift +48 -0
  181. package/Sources/Runic/Providers/Shared/ProviderImplementationRegistry.swift +66 -0
  182. package/Sources/Runic/Providers/Shared/ProviderLoginFlow.swift +12 -0
  183. package/Sources/Runic/Providers/Shared/ProviderSettingsDescriptors.swift +99 -0
  184. package/Sources/Runic/Providers/Shared/SystemSettingsLinks.swift +18 -0
  185. package/Sources/Runic/Providers/Together/TogetherProviderImplementation.swift +24 -0
  186. package/Sources/Runic/Providers/XAI/XAIProviderImplementation.swift +24 -0
  187. package/Sources/Runic/Providers/Zai/ZaiProviderImplementation.swift +24 -0
  188. package/Sources/Runic/Resources/ProviderIcon-antigravity.svg +2 -0
  189. package/Sources/Runic/Resources/ProviderIcon-auggie.svg +3 -0
  190. package/Sources/Runic/Resources/ProviderIcon-azure.svg +5 -0
  191. package/Sources/Runic/Resources/ProviderIcon-bedrock.svg +7 -0
  192. package/Sources/Runic/Resources/ProviderIcon-cerebras.svg +3 -0
  193. package/Sources/Runic/Resources/ProviderIcon-claude.svg +1 -0
  194. package/Sources/Runic/Resources/ProviderIcon-codex.svg +1 -0
  195. package/Sources/Runic/Resources/ProviderIcon-cohere.svg +3 -0
  196. package/Sources/Runic/Resources/ProviderIcon-copilot.svg +1 -0
  197. package/Sources/Runic/Resources/ProviderIcon-cursor.svg +3 -0
  198. package/Sources/Runic/Resources/ProviderIcon-deepseek.svg +4 -0
  199. package/Sources/Runic/Resources/ProviderIcon-factory.svg +3 -0
  200. package/Sources/Runic/Resources/ProviderIcon-fireworks.svg +3 -0
  201. package/Sources/Runic/Resources/ProviderIcon-gemini.svg +1 -0
  202. package/Sources/Runic/Resources/ProviderIcon-groq.svg +1 -0
  203. package/Sources/Runic/Resources/ProviderIcon-kimi.svg +4 -0
  204. package/Sources/Runic/Resources/ProviderIcon-minimax.svg +1 -0
  205. package/Sources/Runic/Resources/ProviderIcon-mistral.svg +5 -0
  206. package/Sources/Runic/Resources/ProviderIcon-openrouter.svg +7 -0
  207. package/Sources/Runic/Resources/ProviderIcon-perplexity.svg +4 -0
  208. package/Sources/Runic/Resources/ProviderIcon-qwen.svg +9 -0
  209. package/Sources/Runic/Resources/ProviderIcon-sambanova.svg +4 -0
  210. package/Sources/Runic/Resources/ProviderIcon-together.svg +5 -0
  211. package/Sources/Runic/Resources/ProviderIcon-vertexai.svg +17 -0
  212. package/Sources/Runic/Resources/ProviderIcon-xai.svg +3 -0
  213. package/Sources/Runic/Resources/ProviderIcon-zai.svg +5 -0
  214. package/Sources/Runic/Resources/RunicMenubarIcon.svg +7 -0
  215. package/Sources/Runic/RunicApp.swift +357 -0
  216. package/Sources/Runic/SessionQuotaNotifications.swift +58 -0
  217. package/Sources/Runic/UsagePaceText.swift +52 -0
  218. package/Sources/Runic/UsageProgressBar.swift +224 -0
  219. package/Sources/Runic/Utilities/DisplayLink.swift +96 -0
  220. package/Sources/Runic/Utilities/LoadingPattern.swift +60 -0
  221. package/Sources/Runic/Utilities/PerformanceConstants.swift +85 -0
  222. package/Sources/Runic/Views/Menu/AlertsMenuView.swift +256 -0
  223. package/Sources/Runic/Views/Menu/ChartPanelStyle.swift +22 -0
  224. package/Sources/Runic/Views/Menu/CostComparativeChartMenuView.swift +275 -0
  225. package/Sources/Runic/Views/Menu/CostHistoryChartMenuView.swift +377 -0
  226. package/Sources/Runic/Views/Menu/CreditsHistoryChartMenuView.swift +312 -0
  227. package/Sources/Runic/Views/Menu/EfficiencyMetricsMenuView.swift +312 -0
  228. package/Sources/Runic/Views/Menu/HeroStatCardsMenuView.swift +228 -0
  229. package/Sources/Runic/Views/Menu/HourlyActivityChartMenuView.swift +228 -0
  230. package/Sources/Runic/Views/Menu/InlineUsageChartView.swift +162 -0
  231. package/Sources/Runic/Views/Menu/MenuCardEntranceModifier.swift +64 -0
  232. package/Sources/Runic/Views/Menu/MenuCardSidebarView.swift +268 -0
  233. package/Sources/Runic/Views/Menu/MenuCardSkeletonView.swift +104 -0
  234. package/Sources/Runic/Views/Menu/MenuCardView.swift +1900 -0
  235. package/Sources/Runic/Views/Menu/MenuContent.swift +150 -0
  236. package/Sources/Runic/Views/Menu/MenuDescriptor.swift +369 -0
  237. package/Sources/Runic/Views/Menu/MenuHighlightStyle.swift +42 -0
  238. package/Sources/Runic/Views/Menu/ModelBreakdownMenuView.swift +235 -0
  239. package/Sources/Runic/Views/Menu/OverviewMenuView.swift +141 -0
  240. package/Sources/Runic/Views/Menu/ProjectBreakdownMenuView.swift +209 -0
  241. package/Sources/Runic/Views/Menu/ProjectBudgetMenuView.swift +220 -0
  242. package/Sources/Runic/Views/Menu/ProviderTabBarView.swift +88 -0
  243. package/Sources/Runic/Views/Menu/SubscriptionUtilizationChartMenuView.swift +201 -0
  244. package/Sources/Runic/Views/Menu/UsageBreakdownChartMenuView.swift +398 -0
  245. package/Sources/Runic/Views/Menu/UsageHeatmapMenuView.swift +232 -0
  246. package/Sources/Runic/Views/Menu/UsageTimelineChartMenuView.swift +417 -0
  247. package/Sources/Runic/Views/Menu/UsageWindowComparisonChartMenuView.swift +190 -0
  248. package/Sources/Runic/Views/Menu/WeeklyActivityChartMenuView.swift +132 -0
  249. package/Sources/Runic/Views/Performance/PerformanceDashboardView.swift +618 -0
  250. package/Sources/Runic/Views/Preferences/LiquidComponents.swift +305 -0
  251. package/Sources/Runic/Views/Preferences/PreferencesAboutPane.swift +271 -0
  252. package/Sources/Runic/Views/Preferences/PreferencesAdvancedPane.swift +268 -0
  253. package/Sources/Runic/Views/Preferences/PreferencesAlertsPane.swift +422 -0
  254. package/Sources/Runic/Views/Preferences/PreferencesAnalyticsPane.swift +641 -0
  255. package/Sources/Runic/Views/Preferences/PreferencesBudgetsPane.swift +344 -0
  256. package/Sources/Runic/Views/Preferences/PreferencesComponents.swift +230 -0
  257. package/Sources/Runic/Views/Preferences/PreferencesCustomProvidersPane.swift +322 -0
  258. package/Sources/Runic/Views/Preferences/PreferencesDebugPane.swift +469 -0
  259. package/Sources/Runic/Views/Preferences/PreferencesGeneralPane.swift +231 -0
  260. package/Sources/Runic/Views/Preferences/PreferencesHelpPane.swift +50 -0
  261. package/Sources/Runic/Views/Preferences/PreferencesIntegrationsPane.swift +497 -0
  262. package/Sources/Runic/Views/Preferences/PreferencesPerformancePane.swift +288 -0
  263. package/Sources/Runic/Views/Preferences/PreferencesProvidersPane.swift +3435 -0
  264. package/Sources/Runic/Views/Preferences/PreferencesSelection.swift +25 -0
  265. package/Sources/Runic/Views/Preferences/PreferencesSyncPane.swift +59 -0
  266. package/Sources/Runic/Views/Preferences/PreferencesView.swift +206 -0
  267. package/Sources/Runic/Views/Rating/QualityRatingView.swift +238 -0
  268. package/Sources/Runic/Views/Team/TeamDashboardView.swift +371 -0
  269. package/Sources/Runic/Views/Team/TeamInviteSheet.swift +175 -0
  270. package/Sources/Runic/Views/Team/TeamManagementView.swift +761 -0
  271. package/Sources/Runic/Views/Team/TeamMemberRow.swift +181 -0
  272. package/Sources/RunicCLI/CLIEntry.swift +942 -0
  273. package/Sources/RunicCLI/CLIRenderer.swift +87 -0
  274. package/Sources/RunicCLI/Commands/EnhancedUsageCommand.swift +588 -0
  275. package/Sources/RunicCLI/Commands/ModelsCommand.swift +362 -0
  276. package/Sources/RunicCLI/Commands/ProjectsCommand.swift +439 -0
  277. package/Sources/RunicCLI/Commands/ResetCommand.swift +429 -0
  278. package/Sources/RunicCLI/Commands/UsageLedgerSourceFactory.swift +168 -0
  279. package/Sources/RunicClaudeWatchdog/main.swift +122 -0
  280. package/Sources/RunicClaudeWebProbe/main.swift +62 -0
  281. package/Sources/RunicCore/AlertRuleStore.swift +187 -0
  282. package/Sources/RunicCore/BrowserCookieImportOrder.swift +25 -0
  283. package/Sources/RunicCore/CopilotUsageModels.swift +84 -0
  284. package/Sources/RunicCore/CostUsageFetcher.swift +119 -0
  285. package/Sources/RunicCore/CostUsageModels.swift +479 -0
  286. package/Sources/RunicCore/CreditsModels.swift +50 -0
  287. package/Sources/RunicCore/CustomProviders/CustomProviderModels.swift +269 -0
  288. package/Sources/RunicCore/CustomProviders/GenericProviderFetcher.swift +622 -0
  289. package/Sources/RunicCore/CustomProviders/ProviderTemplates.swift +500 -0
  290. package/Sources/RunicCore/Host/PTY/TTYCommandRunner.swift +679 -0
  291. package/Sources/RunicCore/Host/Process/SubprocessRunner.swift +135 -0
  292. package/Sources/RunicCore/Integrations/GitHubIntegration.swift +173 -0
  293. package/Sources/RunicCore/Logging/JSONStderrLogHandler.swift +79 -0
  294. package/Sources/RunicCore/Logging/OSLogLogHandler.swift +86 -0
  295. package/Sources/RunicCore/Logging/RunicLog.swift +123 -0
  296. package/Sources/RunicCore/Models/EnhancedUsageModels.swift +371 -0
  297. package/Sources/RunicCore/OpenAIDashboardModels.swift +210 -0
  298. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardBrowserCookieImporter.swift +676 -0
  299. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardFetcher.swift +492 -0
  300. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardNavigationDelegate.swift +51 -0
  301. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardParser.swift +483 -0
  302. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardScrapeScript.swift +558 -0
  303. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebViewCache.swift +204 -0
  304. package/Sources/RunicCore/OpenAIWeb/OpenAIDashboardWebsiteDataStore.swift +78 -0
  305. package/Sources/RunicCore/PathEnvironment.swift +482 -0
  306. package/Sources/RunicCore/Performance/PerformanceModels.swift +371 -0
  307. package/Sources/RunicCore/Performance/PerformanceStorageImpl.swift +409 -0
  308. package/Sources/RunicCore/Policy/UsagePolicyEngine.swift +178 -0
  309. package/Sources/RunicCore/ProjectBudgetStore.swift +99 -0
  310. package/Sources/RunicCore/ProviderCostSnapshot.swift +29 -0
  311. package/Sources/RunicCore/Providers/Antigravity/AntigravityProviderDescriptor.swift +66 -0
  312. package/Sources/RunicCore/Providers/Antigravity/AntigravityStatusProbe.swift +685 -0
  313. package/Sources/RunicCore/Providers/Auggie/AuggieProviderDescriptor.swift +83 -0
  314. package/Sources/RunicCore/Providers/Auggie/AuggieUsageFetcher.swift +142 -0
  315. package/Sources/RunicCore/Providers/Azure/AzureProviderDescriptor.swift +141 -0
  316. package/Sources/RunicCore/Providers/Azure/AzureUsageFetcher.swift +186 -0
  317. package/Sources/RunicCore/Providers/Bedrock/BedrockProviderDescriptor.swift +126 -0
  318. package/Sources/RunicCore/Providers/Bedrock/BedrockUsageFetcher.swift +168 -0
  319. package/Sources/RunicCore/Providers/Cerebras/CerebrasProviderDescriptor.swift +84 -0
  320. package/Sources/RunicCore/Providers/Cerebras/CerebrasUsageFetcher.swift +75 -0
  321. package/Sources/RunicCore/Providers/Claude/ClaudeCLISession.swift +332 -0
  322. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthCredentials.swift +180 -0
  323. package/Sources/RunicCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift +137 -0
  324. package/Sources/RunicCore/Providers/Claude/ClaudeProviderDescriptor.swift +207 -0
  325. package/Sources/RunicCore/Providers/Claude/ClaudeStatusProbe.swift +654 -0
  326. package/Sources/RunicCore/Providers/Claude/ClaudeUsageDataSource.swift +28 -0
  327. package/Sources/RunicCore/Providers/Claude/ClaudeUsageFetcher.swift +480 -0
  328. package/Sources/RunicCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift +780 -0
  329. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthCredentials.swift +146 -0
  330. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexOAuthUsageFetcher.swift +269 -0
  331. package/Sources/RunicCore/Providers/Codex/CodexOAuth/CodexTokenRefresher.swift +100 -0
  332. package/Sources/RunicCore/Providers/Codex/CodexProviderDescriptor.swift +243 -0
  333. package/Sources/RunicCore/Providers/Codex/CodexStatusProbe.swift +125 -0
  334. package/Sources/RunicCore/Providers/Codex/CodexUsageDataSource.swift +24 -0
  335. package/Sources/RunicCore/Providers/Codex/CodexWebDashboardStrategy.swift +181 -0
  336. package/Sources/RunicCore/Providers/Cohere/CohereProviderDescriptor.swift +83 -0
  337. package/Sources/RunicCore/Providers/Cohere/CohereUsageFetcher.swift +85 -0
  338. package/Sources/RunicCore/Providers/Copilot/CopilotDeviceFlow.swift +123 -0
  339. package/Sources/RunicCore/Providers/Copilot/CopilotGitHubCLITokenReader.swift +114 -0
  340. package/Sources/RunicCore/Providers/Copilot/CopilotProviderDescriptor.swift +100 -0
  341. package/Sources/RunicCore/Providers/Copilot/CopilotUsageFetcher.swift +291 -0
  342. package/Sources/RunicCore/Providers/Copilot/CopilotVSCodeTokenReader.swift +386 -0
  343. package/Sources/RunicCore/Providers/Cursor/CursorProviderDescriptor.swift +65 -0
  344. package/Sources/RunicCore/Providers/Cursor/CursorStatusProbe.swift +594 -0
  345. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift +85 -0
  346. package/Sources/RunicCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift +108 -0
  347. package/Sources/RunicCore/Providers/Factory/FactoryLocalStorageImporter.swift +398 -0
  348. package/Sources/RunicCore/Providers/Factory/FactoryProviderDescriptor.swift +65 -0
  349. package/Sources/RunicCore/Providers/Factory/FactoryStatusProbe.swift +1275 -0
  350. package/Sources/RunicCore/Providers/Fireworks/FireworksProviderDescriptor.swift +84 -0
  351. package/Sources/RunicCore/Providers/Fireworks/FireworksUsageFetcher.swift +75 -0
  352. package/Sources/RunicCore/Providers/Gemini/GeminiProviderDescriptor.swift +65 -0
  353. package/Sources/RunicCore/Providers/Gemini/GeminiStatusProbe.swift +835 -0
  354. package/Sources/RunicCore/Providers/Groq/GroqProviderDescriptor.swift +83 -0
  355. package/Sources/RunicCore/Providers/Groq/GroqUsageFetcher.swift +71 -0
  356. package/Sources/RunicCore/Providers/Kimi/KimiProviderDescriptor.swift +83 -0
  357. package/Sources/RunicCore/Providers/Kimi/KimiUsageFetcher.swift +77 -0
  358. package/Sources/RunicCore/Providers/MiniMax/MiniMaxProviderDescriptor.swift +92 -0
  359. package/Sources/RunicCore/Providers/MiniMax/MiniMaxSettingsReader.swift +54 -0
  360. package/Sources/RunicCore/Providers/MiniMax/MiniMaxUsageFetcher.swift +171 -0
  361. package/Sources/RunicCore/Providers/MiniMax/MiniMaxWebUsageFetcher.swift +911 -0
  362. package/Sources/RunicCore/Providers/Mistral/MistralProviderDescriptor.swift +84 -0
  363. package/Sources/RunicCore/Providers/Mistral/MistralUsageFetcher.swift +75 -0
  364. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterProviderDescriptor.swift +87 -0
  365. package/Sources/RunicCore/Providers/OpenRouter/OpenRouterUsageFetcher.swift +207 -0
  366. package/Sources/RunicCore/Providers/Perplexity/PerplexityProviderDescriptor.swift +84 -0
  367. package/Sources/RunicCore/Providers/Perplexity/PerplexityUsageFetcher.swift +75 -0
  368. package/Sources/RunicCore/Providers/ProviderBranding.swift +25 -0
  369. package/Sources/RunicCore/Providers/ProviderCLIConfig.swift +17 -0
  370. package/Sources/RunicCore/Providers/ProviderDescriptor.swift +129 -0
  371. package/Sources/RunicCore/Providers/ProviderFetchPlan.swift +263 -0
  372. package/Sources/RunicCore/Providers/ProviderSettingsSnapshot.swift +98 -0
  373. package/Sources/RunicCore/Providers/ProviderTokenResolver.swift +501 -0
  374. package/Sources/RunicCore/Providers/ProviderVersionDetector.swift +68 -0
  375. package/Sources/RunicCore/Providers/Providers.swift +184 -0
  376. package/Sources/RunicCore/Providers/Qwen/QwenProviderDescriptor.swift +84 -0
  377. package/Sources/RunicCore/Providers/Qwen/QwenSettingsReader.swift +30 -0
  378. package/Sources/RunicCore/Providers/Qwen/QwenUsageStats.swift +317 -0
  379. package/Sources/RunicCore/Providers/SambaNova/SambaNovaProviderDescriptor.swift +84 -0
  380. package/Sources/RunicCore/Providers/SambaNova/SambaNovaUsageFetcher.swift +75 -0
  381. package/Sources/RunicCore/Providers/Together/TogetherProviderDescriptor.swift +83 -0
  382. package/Sources/RunicCore/Providers/Together/TogetherUsageFetcher.swift +75 -0
  383. package/Sources/RunicCore/Providers/VertexAI/VertexAIProviderDescriptor.swift +123 -0
  384. package/Sources/RunicCore/Providers/VertexAI/VertexAISettingsReader.swift +48 -0
  385. package/Sources/RunicCore/Providers/VertexAI/VertexAIUsageFetcher.swift +144 -0
  386. package/Sources/RunicCore/Providers/XAI/XAIProviderDescriptor.swift +83 -0
  387. package/Sources/RunicCore/Providers/XAI/XAIUsageFetcher.swift +75 -0
  388. package/Sources/RunicCore/Providers/Zai/ZaiProviderDescriptor.swift +72 -0
  389. package/Sources/RunicCore/Providers/Zai/ZaiSettingsReader.swift +41 -0
  390. package/Sources/RunicCore/Providers/Zai/ZaiUsageStats.swift +724 -0
  391. package/Sources/RunicCore/Sync/BackgroundSyncManager.swift +385 -0
  392. package/Sources/RunicCore/Sync/SyncConflictResolver.swift +310 -0
  393. package/Sources/RunicCore/Sync/SyncProtocol.swift +288 -0
  394. package/Sources/RunicCore/Sync/SyncRecord.swift +405 -0
  395. package/Sources/RunicCore/Sync/SyncUsageExample.swift +294 -0
  396. package/Sources/RunicCore/Sync/TeamSyncRecords.swift +775 -0
  397. package/Sources/RunicCore/Sync/iCloudSyncEngine.swift +333 -0
  398. package/Sources/RunicCore/Team/TeamModels.swift +583 -0
  399. package/Sources/RunicCore/Team/TeamShowbackExporter.swift +495 -0
  400. package/Sources/RunicCore/Team/TeamStore.swift +792 -0
  401. package/Sources/RunicCore/TextParsing.swift +55 -0
  402. package/Sources/RunicCore/UsageFetcher.swift +694 -0
  403. package/Sources/RunicCore/UsageFormatter.swift +410 -0
  404. package/Sources/RunicCore/UsageLedger/ClaudeUsageLogSource.swift +329 -0
  405. package/Sources/RunicCore/UsageLedger/CodexUsageLogSource.swift +448 -0
  406. package/Sources/RunicCore/UsageLedger/OTelGenAILedgerAdapter.swift +550 -0
  407. package/Sources/RunicCore/UsageLedger/UsageLedger.swift +29 -0
  408. package/Sources/RunicCore/UsageLedger/UsageLedgerAggregator.swift +660 -0
  409. package/Sources/RunicCore/UsageLedger/UsageLedgerInsightsAdvisor.swift +209 -0
  410. package/Sources/RunicCore/UsageLedger/UsageLedgerModels.swift +417 -0
  411. package/Sources/RunicCore/UsageLedger/UsageLedgerProjectIdentity.swift +245 -0
  412. package/Sources/RunicCore/UsagePace.swift +81 -0
  413. package/Sources/RunicCore/Vendored/CostUsage/CostUsageCache.swift +73 -0
  414. package/Sources/RunicCore/Vendored/CostUsage/CostUsageJsonl.swift +74 -0
  415. package/Sources/RunicCore/Vendored/CostUsage/CostUsagePricing.swift +208 -0
  416. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Claude.swift +455 -0
  417. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner+Timestamp.swift +132 -0
  418. package/Sources/RunicCore/Vendored/CostUsage/CostUsageScanner.swift +589 -0
  419. package/Sources/RunicCore/WidgetSnapshot.swift +183 -0
  420. package/Sources/RunicMacroSupport/ProviderRegistrationMacros.swift +14 -0
  421. package/Sources/RunicMacros/ProviderRegistrationMacros.swift +216 -0
  422. package/Sources/RunicWidget/RunicWidgetBundle.swift +79 -0
  423. package/Sources/RunicWidget/RunicWidgetProvider.swift +283 -0
  424. package/Sources/RunicWidget/RunicWidgetViews.swift +680 -0
  425. package/Tests/RunicTests/AntigravityStatusProbeTests.swift +50 -0
  426. package/Tests/RunicTests/AppDelegateTests.swift +43 -0
  427. package/Tests/RunicTests/BrowserCookieOrderLabelTests.swift +22 -0
  428. package/Tests/RunicTests/CLIArgumentParsingTests.swift +70 -0
  429. package/Tests/RunicTests/CLICostTests.swift +65 -0
  430. package/Tests/RunicTests/CLIProviderSelectionTests.swift +22 -0
  431. package/Tests/RunicTests/CLISnapshotTests.swift +72 -0
  432. package/Tests/RunicTests/CLIWebFallbackTests.swift +58 -0
  433. package/Tests/RunicTests/ClaudeOAuthTests.swift +164 -0
  434. package/Tests/RunicTests/ClaudeResilienceTests.swift +30 -0
  435. package/Tests/RunicTests/ClaudeUsageTests.swift +370 -0
  436. package/Tests/RunicTests/CodexOAuthTests.swift +122 -0
  437. package/Tests/RunicTests/CodexUsageLogSourceTests.swift +82 -0
  438. package/Tests/RunicTests/CopilotTokenResolverTests.swift +75 -0
  439. package/Tests/RunicTests/CopilotVSCodeTokenReaderTests.swift +75 -0
  440. package/Tests/RunicTests/CostUsageDecodingTests.swift +394 -0
  441. package/Tests/RunicTests/CostUsagePricingTests.swift +50 -0
  442. package/Tests/RunicTests/CostUsageScannerTests.swift +700 -0
  443. package/Tests/RunicTests/CursorStatusProbeTests.swift +323 -0
  444. package/Tests/RunicTests/FactoryStatusProbeTests.swift +69 -0
  445. package/Tests/RunicTests/GeminiAPITestHelpers.swift +94 -0
  446. package/Tests/RunicTests/GeminiLoginAlertTests.swift +28 -0
  447. package/Tests/RunicTests/GeminiStatusProbeAPITests.swift +283 -0
  448. package/Tests/RunicTests/GeminiStatusProbePlanTests.swift +279 -0
  449. package/Tests/RunicTests/GeminiStatusProbeTests.swift +233 -0
  450. package/Tests/RunicTests/GeminiTestEnvironment.swift +87 -0
  451. package/Tests/RunicTests/GoogleWorkspaceStatusTests.swift +73 -0
  452. package/Tests/RunicTests/InstallOriginTests.swift +18 -0
  453. package/Tests/RunicTests/KeyboardShortcutsBundleTests.swift +9 -0
  454. package/Tests/RunicTests/LiveAccountTests.swift +24 -0
  455. package/Tests/RunicTests/LoadingPatternTests.swift +35 -0
  456. package/Tests/RunicTests/MenuCardModelTests.swift +545 -0
  457. package/Tests/RunicTests/MiniMaxCookieHeaderStoreTestSupport.swift +8 -0
  458. package/Tests/RunicTests/MiniMaxGroupIDStoreTestSupport.swift +8 -0
  459. package/Tests/RunicTests/MiniMaxParsingTests.swift +68 -0
  460. package/Tests/RunicTests/MiniMaxTokenStoreTestSupport.swift +8 -0
  461. package/Tests/RunicTests/OTelGenAILedgerAdapterTests.swift +112 -0
  462. package/Tests/RunicTests/OpenAIDashboardBrowserCookieImporterTests.swift +17 -0
  463. package/Tests/RunicTests/OpenAIDashboardFetcherCreditsWaitTests.swift +75 -0
  464. package/Tests/RunicTests/OpenAIDashboardOffscreenHostTests.swift +27 -0
  465. package/Tests/RunicTests/OpenAIDashboardParserTests.swift +134 -0
  466. package/Tests/RunicTests/OpenAIWebAccountSwitchTests.swift +53 -0
  467. package/Tests/RunicTests/OpenRouterTokenStoreTestSupport.swift +92 -0
  468. package/Tests/RunicTests/PathBuilderTests.swift +237 -0
  469. package/Tests/RunicTests/ProviderFetchPipelineTests.swift +160 -0
  470. package/Tests/RunicTests/ProviderIconResourcesTests.swift +38 -0
  471. package/Tests/RunicTests/ProviderMetadataStatusLinkTests.swift +16 -0
  472. package/Tests/RunicTests/ProviderRegistryTests.swift +29 -0
  473. package/Tests/RunicTests/ProviderSettingsDescriptorTests.swift +202 -0
  474. package/Tests/RunicTests/ProviderToggleStoreTests.swift +51 -0
  475. package/Tests/RunicTests/RunicTests.swift +120 -0
  476. package/Tests/RunicTests/SessionQuotaNotificationLogicTests.swift +36 -0
  477. package/Tests/RunicTests/SettingsStoreTests.swift +161 -0
  478. package/Tests/RunicTests/StatusItemAnimationTests.swift +49 -0
  479. package/Tests/RunicTests/StatusMenuTests.swift +434 -0
  480. package/Tests/RunicTests/StatusProbeTests.swift +377 -0
  481. package/Tests/RunicTests/SubprocessRunnerTests.swift +20 -0
  482. package/Tests/RunicTests/SubscriptionDetectionTests.swift +65 -0
  483. package/Tests/RunicTests/TTYCommandRunnerTests.swift +148 -0
  484. package/Tests/RunicTests/TTYIntegrationTests.swift +41 -0
  485. package/Tests/RunicTests/TeamShowbackExporterTests.swift +159 -0
  486. package/Tests/RunicTests/TextParsingTests.swift +12 -0
  487. package/Tests/RunicTests/UsageFormatterTests.swift +112 -0
  488. package/Tests/RunicTests/UsageLedgerAnomalyDetectionTests.swift +172 -0
  489. package/Tests/RunicTests/UsageLedgerForecastTests.swift +213 -0
  490. package/Tests/RunicTests/UsageLedgerInsightsAdvisorTests.swift +83 -0
  491. package/Tests/RunicTests/UsageLedgerProjectNameTests.swift +170 -0
  492. package/Tests/RunicTests/UsagePaceTests.swift +77 -0
  493. package/Tests/RunicTests/UsagePaceTextTests.swift +109 -0
  494. package/Tests/RunicTests/UsagePolicyEngineTests.swift +91 -0
  495. package/Tests/RunicTests/WidgetSnapshotTests.swift +45 -0
  496. package/Tests/RunicTests/ZaiProviderTests.swift +61 -0
  497. package/Tests/RunicTests/ZaiTokenStoreTestSupport.swift +6 -0
  498. package/TestsLinux/PlatformGatingTests.swift +46 -0
  499. package/appcast.xml +105 -0
  500. package/assets/logo-concept-stack.svg +24 -0
  501. package/assets/logo-minimal-bars.svg +15 -0
  502. package/assets/logo.svg +20 -0
  503. package/assets/logo2.svg +43 -0
  504. package/bin/docs-list +5 -0
  505. package/bin/install-runic-cli.sh +32 -0
  506. package/build-and-run.sh +369 -0
  507. package/docs/.nojekyll +1 -0
  508. package/docs/CNAME +1 -0
  509. package/docs/antigravity.md +75 -0
  510. package/docs/architecture.md +32 -0
  511. package/docs/claude.md +93 -0
  512. package/docs/cli-commands/integration_guide.md +371 -0
  513. package/docs/cli-commands/quick_reference.md +362 -0
  514. package/docs/cli-commands/readme.md +361 -0
  515. package/docs/cli.md +156 -0
  516. package/docs/cloudkit_sync_guide.md +495 -0
  517. package/docs/codebase_review.md +288 -0
  518. package/docs/codex-oauth.md +635 -0
  519. package/docs/codex.md +104 -0
  520. package/docs/codexbar.png +0 -0
  521. package/docs/copilot.md +44 -0
  522. package/docs/cursor.md +46 -0
  523. package/docs/design/designcomparisonchart.md +510 -0
  524. package/docs/design/implementationguide.md +792 -0
  525. package/docs/design/menubaricondesignconcepts.md +796 -0
  526. package/docs/design/mockups/hybridiconconcept.svg +62 -0
  527. package/docs/design/mockups/infinityiconconcept.svg +41 -0
  528. package/docs/design/mockups/infinityiconstates.svg +129 -0
  529. package/docs/design/mockups/wavelogorefined.svg +48 -0
  530. package/docs/design/quick_summary.md +426 -0
  531. package/docs/design/readme.md +478 -0
  532. package/docs/design/sizespecifications.md +575 -0
  533. package/docs/design-system.md +836 -0
  534. package/docs/factory.md +103 -0
  535. package/docs/gemini.md +73 -0
  536. package/docs/icon.md +37 -0
  537. package/docs/icon.png +0 -0
  538. package/docs/index.html +175 -0
  539. package/docs/keychain_fix.md +274 -0
  540. package/docs/minimax.md +71 -0
  541. package/docs/packaging.md +25 -0
  542. package/docs/progress-bar-improvements.md +240 -0
  543. package/docs/progress-bar-quick-reference.md +283 -0
  544. package/docs/progress-bar-technical-details.md +432 -0
  545. package/docs/progress-bar-visual-test.swift +375 -0
  546. package/docs/provider.md +202 -0
  547. package/docs/providers.md +86 -0
  548. package/docs/refactor/macros.md +35 -0
  549. package/docs/refresh-loop.md +22 -0
  550. package/docs/releasing-homebrew.md +35 -0
  551. package/docs/releasing.md +121 -0
  552. package/docs/research/concrete_todo_2026-02-22.md +47 -0
  553. package/docs/research/core-feature-research.md +248 -0
  554. package/docs/research/feature_uiux_roadmap_2026-02-22.md +202 -0
  555. package/docs/research/parity_matrix.md +49 -0
  556. package/docs/research/provider_expansion_candidates_2026-02-22.md +80 -0
  557. package/docs/research/provider_model_token_project_matrix_2026-02-23.md +46 -0
  558. package/docs/research/release_gate_checklist.md +57 -0
  559. package/docs/site.css +505 -0
  560. package/docs/sparkle.md +28 -0
  561. package/docs/status.md +28 -0
  562. package/docs/sync/implementation.md +393 -0
  563. package/docs/sync/readme.md +317 -0
  564. package/docs/sync_files_summary.md +391 -0
  565. package/docs/sync_quick_reference.md +225 -0
  566. package/docs/todo.md +11 -0
  567. package/docs/ui.md +29 -0
  568. package/docs/web-integration.md +9 -0
  569. package/docs/widgets.md +85 -0
  570. package/docs/workflows/error-handling-improvements.md +434 -0
  571. package/docs/workflows/folder_structure.md +299 -0
  572. package/docs/workflows/loading_states.md +446 -0
  573. package/docs/workflows/loading_states_examples.md +565 -0
  574. package/docs/workflows/loading_states_flow.md +430 -0
  575. package/docs/workflows/performance_optimizations.md +457 -0
  576. package/docs/workflows/phase1-cli-testing-report.md +313 -0
  577. package/docs/workflows/quick_reference.md +228 -0
  578. package/docs/workflows/readme.md +59 -0
  579. package/docs/workflows/recent_improvements.md +275 -0
  580. package/docs/workflows/sync_enablement_summary.md +400 -0
  581. package/docs/zai.md +43 -0
  582. package/package.json +36 -0
  583. package/runic.png +0 -0
  584. package/version.env +2 -0
@@ -0,0 +1,775 @@
1
+ import Foundation
2
+ import CloudKit
3
+ import CryptoKit
4
+ import LocalAuthentication
5
+
6
+ // MARK: - CloudKit Record Type Constants
7
+
8
+ extension CloudKitRecordType {
9
+ public static let team = "Team"
10
+ public static let teamMembership = "TeamMembership"
11
+ public static let projectOwnership = "ProjectOwnership"
12
+ public static let teamUsageSnapshot = "TeamUsageSnapshot"
13
+ }
14
+
15
+ // MARK: - CloudKit Custom Zone Configuration
16
+
17
+ /// Custom zone for team collaboration data
18
+ ///
19
+ /// Using a custom zone allows for atomic batch operations and better
20
+ /// change tracking for team-related records.
21
+ public struct TeamSyncZone {
22
+ public static let zoneName = "TeamCollaborationZone"
23
+ public static let ownerName = CKCurrentUserDefaultName
24
+
25
+ public static var zoneID: CKRecordZone.ID {
26
+ CKRecordZone.ID(zoneName: zoneName, ownerName: ownerName)
27
+ }
28
+
29
+ public static func createZone() -> CKRecordZone {
30
+ CKRecordZone(zoneID: zoneID)
31
+ }
32
+ }
33
+
34
+ // MARK: - Team Record
35
+
36
+ /// Team record for CloudKit synchronization
37
+ ///
38
+ /// Represents a collaborative team that can share usage quotas and projects.
39
+ /// - Record Type: "Team"
40
+ /// - Queryable by: ownerUserID
41
+ /// - Zone: TeamCollaborationZone (custom zone)
42
+ public struct TeamRecord: SyncableRecord {
43
+ public let recordID: String
44
+ public let recordType: String = CloudKitRecordType.team
45
+ public let version: Int
46
+ public let modifiedAt: Date
47
+ public let lastModifiedDeviceID: String?
48
+
49
+ // Team data
50
+ public let teamID: String
51
+ public let name: String
52
+ public let ownerUserID: String
53
+ public let totalQuota: Int64
54
+ public let createdAt: Date
55
+ public let updatedAt: Date
56
+
57
+ // Optional metadata
58
+ public let description: String?
59
+ public let avatarURL: String?
60
+ public let isActive: Bool
61
+
62
+ public init(
63
+ recordID: String = UUID().uuidString,
64
+ version: Int = 1,
65
+ modifiedAt: Date = Date(),
66
+ lastModifiedDeviceID: String? = nil,
67
+ teamID: String = UUID().uuidString,
68
+ name: String,
69
+ ownerUserID: String,
70
+ totalQuota: Int64,
71
+ createdAt: Date = Date(),
72
+ updatedAt: Date = Date(),
73
+ description: String? = nil,
74
+ avatarURL: String? = nil,
75
+ isActive: Bool = true
76
+ ) {
77
+ self.recordID = recordID
78
+ self.version = version
79
+ self.modifiedAt = modifiedAt
80
+ self.lastModifiedDeviceID = lastModifiedDeviceID
81
+ self.teamID = teamID
82
+ self.name = name
83
+ self.ownerUserID = ownerUserID
84
+ self.totalQuota = totalQuota
85
+ self.createdAt = createdAt
86
+ self.updatedAt = updatedAt
87
+ self.description = description
88
+ self.avatarURL = avatarURL
89
+ self.isActive = isActive
90
+ }
91
+
92
+ public func toCKRecord() throws -> CKRecord {
93
+ let zoneID = TeamSyncZone.zoneID
94
+ let record = CKRecord(
95
+ recordType: recordType,
96
+ recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
97
+ )
98
+
99
+ // Sync metadata
100
+ record["version"] = version as CKRecordValue
101
+ record["modifiedAt"] = modifiedAt as CKRecordValue
102
+ record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
103
+
104
+ // 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
112
+
113
+ // Optional fields
114
+ record["description"] = description as? CKRecordValue
115
+ record["avatarURL"] = avatarURL as? CKRecordValue
116
+
117
+ return record
118
+ }
119
+
120
+ public static func fromCKRecord(_ ckRecord: CKRecord) throws -> TeamRecord {
121
+ guard let version = ckRecord["version"] as? Int,
122
+ let modifiedAt = ckRecord["modifiedAt"] as? Date,
123
+ let teamID = ckRecord["teamID"] as? String,
124
+ let name = ckRecord["name"] as? String,
125
+ let ownerUserID = ckRecord["ownerUserID"] as? String,
126
+ let totalQuota = ckRecord["totalQuota"] as? Int64,
127
+ let createdAt = ckRecord["createdAt"] as? Date,
128
+ let updatedAt = ckRecord["updatedAt"] as? Date,
129
+ let isActiveInt = ckRecord["isActive"] as? Int
130
+ else {
131
+ throw SyncError.invalidRecordFormat("Missing required fields in Team record")
132
+ }
133
+
134
+ return TeamRecord(
135
+ recordID: ckRecord.recordID.recordName,
136
+ version: version,
137
+ modifiedAt: modifiedAt,
138
+ lastModifiedDeviceID: ckRecord["lastModifiedDeviceID"] as? String,
139
+ teamID: teamID,
140
+ name: name,
141
+ ownerUserID: ownerUserID,
142
+ totalQuota: totalQuota,
143
+ createdAt: createdAt,
144
+ updatedAt: updatedAt,
145
+ description: ckRecord["description"] as? String,
146
+ avatarURL: ckRecord["avatarURL"] as? String,
147
+ isActive: isActiveInt == 1
148
+ )
149
+ }
150
+ }
151
+
152
+ // MARK: - Team Membership Record
153
+
154
+ /// Team membership record for CloudKit synchronization
155
+ ///
156
+ /// Represents a user's membership in a team with role and quota information.
157
+ /// - Record Type: "TeamMembership"
158
+ /// - Queryable by: teamID, userID
159
+ /// - References: Team record (parent)
160
+ /// - Zone: TeamCollaborationZone (custom zone)
161
+ public struct TeamMembershipRecord: SyncableRecord {
162
+ public let recordID: String
163
+ public let recordType: String = CloudKitRecordType.teamMembership
164
+ public let version: Int
165
+ public let modifiedAt: Date
166
+ public let lastModifiedDeviceID: String?
167
+
168
+ // Membership data
169
+ public let membershipID: String
170
+ public let teamID: String
171
+ public let userID: String
172
+ public let role: TeamRole
173
+ public let quotaLimit: Int64?
174
+ public let joinedAt: Date
175
+
176
+ // Optional metadata
177
+ public let email: String?
178
+ public let displayName: String?
179
+ public let invitedBy: String?
180
+ public let acceptedAt: Date?
181
+ public let isActive: Bool
182
+
183
+ // Reference to parent team
184
+ public let teamRecordName: String
185
+
186
+ public enum TeamRole: String, Codable, Sendable {
187
+ case owner = "owner"
188
+ case admin = "admin"
189
+ case member = "member"
190
+ case viewer = "viewer"
191
+ }
192
+
193
+ public init(
194
+ recordID: String = UUID().uuidString,
195
+ version: Int = 1,
196
+ modifiedAt: Date = Date(),
197
+ lastModifiedDeviceID: String? = nil,
198
+ membershipID: String = UUID().uuidString,
199
+ teamID: String,
200
+ userID: String,
201
+ role: TeamRole,
202
+ quotaLimit: Int64? = nil,
203
+ joinedAt: Date = Date(),
204
+ email: String? = nil,
205
+ displayName: String? = nil,
206
+ invitedBy: String? = nil,
207
+ acceptedAt: Date? = nil,
208
+ isActive: Bool = true,
209
+ teamRecordName: String
210
+ ) {
211
+ self.recordID = recordID
212
+ self.version = version
213
+ self.modifiedAt = modifiedAt
214
+ self.lastModifiedDeviceID = lastModifiedDeviceID
215
+ self.membershipID = membershipID
216
+ self.teamID = teamID
217
+ self.userID = userID
218
+ self.role = role
219
+ self.quotaLimit = quotaLimit
220
+ self.joinedAt = joinedAt
221
+ self.email = email
222
+ self.displayName = displayName
223
+ self.invitedBy = invitedBy
224
+ self.acceptedAt = acceptedAt
225
+ self.isActive = isActive
226
+ self.teamRecordName = teamRecordName
227
+ }
228
+
229
+ public func toCKRecord() throws -> CKRecord {
230
+ let zoneID = TeamSyncZone.zoneID
231
+ let record = CKRecord(
232
+ recordType: recordType,
233
+ recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
234
+ )
235
+
236
+ // Sync metadata
237
+ record["version"] = version as CKRecordValue
238
+ record["modifiedAt"] = modifiedAt as CKRecordValue
239
+ record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
240
+
241
+ // 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
249
+
250
+ // Optional encrypted fields
251
+ if let email = email {
252
+ let encrypted = try encryptString(email)
253
+ record["email"] = encrypted as CKRecordValue
254
+ }
255
+ record["displayName"] = displayName as? CKRecordValue
256
+ record["invitedBy"] = invitedBy as? CKRecordValue
257
+ record["acceptedAt"] = acceptedAt as? CKRecordValue
258
+
259
+ // Reference to parent team
260
+ let teamReference = CKRecord.Reference(
261
+ recordID: CKRecord.ID(recordName: teamRecordName, zoneID: zoneID),
262
+ action: .deleteSelf
263
+ )
264
+ record["teamReference"] = teamReference as CKRecordValue
265
+
266
+ return record
267
+ }
268
+
269
+ public static func fromCKRecord(_ ckRecord: CKRecord) throws -> TeamMembershipRecord {
270
+ guard let version = ckRecord["version"] as? Int,
271
+ let modifiedAt = ckRecord["modifiedAt"] as? Date,
272
+ let membershipID = ckRecord["membershipID"] as? String,
273
+ let teamID = ckRecord["teamID"] as? String,
274
+ let userID = ckRecord["userID"] as? String,
275
+ let roleString = ckRecord["role"] as? String,
276
+ let role = TeamRole(rawValue: roleString),
277
+ let joinedAt = ckRecord["joinedAt"] as? Date,
278
+ let isActiveInt = ckRecord["isActive"] as? Int,
279
+ let teamReference = ckRecord["teamReference"] as? CKRecord.Reference
280
+ else {
281
+ throw SyncError.invalidRecordFormat("Missing required fields in TeamMembership record")
282
+ }
283
+
284
+ var email: String?
285
+ if let encryptedEmail = ckRecord["email"] as? String {
286
+ email = try? decryptString(encryptedEmail)
287
+ }
288
+
289
+ return TeamMembershipRecord(
290
+ recordID: ckRecord.recordID.recordName,
291
+ version: version,
292
+ modifiedAt: modifiedAt,
293
+ lastModifiedDeviceID: ckRecord["lastModifiedDeviceID"] as? String,
294
+ membershipID: membershipID,
295
+ teamID: teamID,
296
+ userID: userID,
297
+ role: role,
298
+ quotaLimit: ckRecord["quotaLimit"] as? Int64,
299
+ joinedAt: joinedAt,
300
+ email: email,
301
+ displayName: ckRecord["displayName"] as? String,
302
+ invitedBy: ckRecord["invitedBy"] as? String,
303
+ acceptedAt: ckRecord["acceptedAt"] as? Date,
304
+ isActive: isActiveInt == 1,
305
+ teamRecordName: teamReference.recordID.recordName
306
+ )
307
+ }
308
+ }
309
+
310
+ // MARK: - Project Ownership Record
311
+
312
+ /// Project ownership record for CloudKit synchronization
313
+ ///
314
+ /// Represents ownership and sharing permissions for a project.
315
+ /// - Record Type: "ProjectOwnership"
316
+ /// - Queryable by: ownerUserID, teamID, projectID
317
+ /// - References: Team record (optional - if team-owned)
318
+ /// - Zone: TeamCollaborationZone (custom zone)
319
+ public struct ProjectOwnershipRecord: SyncableRecord {
320
+ public let recordID: String
321
+ public let recordType: String = CloudKitRecordType.projectOwnership
322
+ public let version: Int
323
+ public let modifiedAt: Date
324
+ public let lastModifiedDeviceID: String?
325
+
326
+ // Project ownership data
327
+ public let projectID: String
328
+ public let teamID: String?
329
+ public let ownerUserID: String
330
+ public let sharedWith: [String]
331
+ public let accessLevel: AccessLevel
332
+ public let createdAt: Date
333
+
334
+ // Optional metadata
335
+ public let projectName: String?
336
+ public let projectDescription: String?
337
+ public let tags: [String]
338
+ public let isArchived: Bool
339
+
340
+ // Reference to team (if team-owned)
341
+ public let teamRecordName: String?
342
+
343
+ public enum AccessLevel: String, Codable, Sendable {
344
+ case `private` = "private"
345
+ case teamReadOnly = "team_read_only"
346
+ case teamReadWrite = "team_read_write"
347
+ case `public` = "public"
348
+ }
349
+
350
+ public init(
351
+ recordID: String = UUID().uuidString,
352
+ version: Int = 1,
353
+ modifiedAt: Date = Date(),
354
+ lastModifiedDeviceID: String? = nil,
355
+ projectID: String = UUID().uuidString,
356
+ teamID: String? = nil,
357
+ ownerUserID: String,
358
+ sharedWith: [String] = [],
359
+ accessLevel: AccessLevel = .private,
360
+ createdAt: Date = Date(),
361
+ projectName: String? = nil,
362
+ projectDescription: String? = nil,
363
+ tags: [String] = [],
364
+ isArchived: Bool = false,
365
+ teamRecordName: String? = nil
366
+ ) {
367
+ self.recordID = recordID
368
+ self.version = version
369
+ self.modifiedAt = modifiedAt
370
+ self.lastModifiedDeviceID = lastModifiedDeviceID
371
+ self.projectID = projectID
372
+ self.teamID = teamID
373
+ self.ownerUserID = ownerUserID
374
+ self.sharedWith = sharedWith
375
+ self.accessLevel = accessLevel
376
+ self.createdAt = createdAt
377
+ self.projectName = projectName
378
+ self.projectDescription = projectDescription
379
+ self.tags = tags
380
+ self.isArchived = isArchived
381
+ self.teamRecordName = teamRecordName
382
+ }
383
+
384
+ public func toCKRecord() throws -> CKRecord {
385
+ let zoneID = TeamSyncZone.zoneID
386
+ let record = CKRecord(
387
+ recordType: recordType,
388
+ recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
389
+ )
390
+
391
+ // Sync metadata
392
+ record["version"] = version as CKRecordValue
393
+ record["modifiedAt"] = modifiedAt as CKRecordValue
394
+ record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
395
+
396
+ // 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
404
+
405
+ // Optional metadata
406
+ record["projectName"] = projectName as? CKRecordValue
407
+ record["projectDescription"] = projectDescription as? CKRecordValue
408
+ record["tags"] = tags as CKRecordValue
409
+
410
+ // Reference to team (if team-owned)
411
+ if let teamRecordName = teamRecordName {
412
+ let teamReference = CKRecord.Reference(
413
+ recordID: CKRecord.ID(recordName: teamRecordName, zoneID: zoneID),
414
+ action: .none
415
+ )
416
+ record["teamReference"] = teamReference as CKRecordValue
417
+ }
418
+
419
+ return record
420
+ }
421
+
422
+ public static func fromCKRecord(_ ckRecord: CKRecord) throws -> ProjectOwnershipRecord {
423
+ guard let version = ckRecord["version"] as? Int,
424
+ let modifiedAt = ckRecord["modifiedAt"] as? Date,
425
+ let projectID = ckRecord["projectID"] as? String,
426
+ let ownerUserID = ckRecord["ownerUserID"] as? String,
427
+ let sharedWith = ckRecord["sharedWith"] as? [String],
428
+ let accessLevelString = ckRecord["accessLevel"] as? String,
429
+ let accessLevel = AccessLevel(rawValue: accessLevelString),
430
+ let createdAt = ckRecord["createdAt"] as? Date,
431
+ let isArchivedInt = ckRecord["isArchived"] as? Int
432
+ else {
433
+ throw SyncError.invalidRecordFormat("Missing required fields in ProjectOwnership record")
434
+ }
435
+
436
+ let teamReference = ckRecord["teamReference"] as? CKRecord.Reference
437
+
438
+ return ProjectOwnershipRecord(
439
+ recordID: ckRecord.recordID.recordName,
440
+ version: version,
441
+ modifiedAt: modifiedAt,
442
+ lastModifiedDeviceID: ckRecord["lastModifiedDeviceID"] as? String,
443
+ projectID: projectID,
444
+ teamID: ckRecord["teamID"] as? String,
445
+ ownerUserID: ownerUserID,
446
+ sharedWith: sharedWith,
447
+ accessLevel: accessLevel,
448
+ createdAt: createdAt,
449
+ projectName: ckRecord["projectName"] as? String,
450
+ projectDescription: ckRecord["projectDescription"] as? String,
451
+ tags: ckRecord["tags"] as? [String] ?? [],
452
+ isArchived: isArchivedInt == 1,
453
+ teamRecordName: teamReference?.recordID.recordName
454
+ )
455
+ }
456
+ }
457
+
458
+ // MARK: - Team Usage Snapshot Record
459
+
460
+ /// Team usage snapshot record for CloudKit synchronization
461
+ ///
462
+ /// Aggregates usage data across team members for tracking and quota management.
463
+ /// - Record Type: "TeamUsageSnapshot"
464
+ /// - Queryable by: teamID, date
465
+ /// - References: Team record (parent)
466
+ /// - Zone: TeamCollaborationZone (custom zone)
467
+ public struct TeamUsageSnapshotRecord: SyncableRecord {
468
+ public let recordID: String
469
+ public let recordType: String = CloudKitRecordType.teamUsageSnapshot
470
+ public let version: Int
471
+ public let modifiedAt: Date
472
+ public let lastModifiedDeviceID: String?
473
+
474
+ // Team usage data
475
+ public let teamID: String
476
+ public let date: Date
477
+ public let totalTokens: Int64
478
+ public let totalCost: Double
479
+ public let memberUsage: [String: MemberUsageData]
480
+ public let quotaUsedPercent: Double
481
+
482
+ // Additional metrics
483
+ public let primaryProvider: String?
484
+ public let mostActiveUser: String?
485
+ public let averageTokensPerMember: Double
486
+
487
+ // Reference to parent team
488
+ public let teamRecordName: String
489
+
490
+ public struct MemberUsageData: Codable, Sendable, Hashable {
491
+ public let userID: String
492
+ public let tokens: Int64
493
+ public let cost: Double
494
+ public let requestCount: Int
495
+
496
+ public init(userID: String, tokens: Int64, cost: Double, requestCount: Int) {
497
+ self.userID = userID
498
+ self.tokens = tokens
499
+ self.cost = cost
500
+ self.requestCount = requestCount
501
+ }
502
+ }
503
+
504
+ public init(
505
+ recordID: String = UUID().uuidString,
506
+ version: Int = 1,
507
+ modifiedAt: Date = Date(),
508
+ lastModifiedDeviceID: String? = nil,
509
+ teamID: String,
510
+ date: Date = Date(),
511
+ totalTokens: Int64,
512
+ totalCost: Double,
513
+ memberUsage: [String: MemberUsageData] = [:],
514
+ quotaUsedPercent: Double,
515
+ primaryProvider: String? = nil,
516
+ mostActiveUser: String? = nil,
517
+ averageTokensPerMember: Double = 0.0,
518
+ teamRecordName: String
519
+ ) {
520
+ self.recordID = recordID
521
+ self.version = version
522
+ self.modifiedAt = modifiedAt
523
+ self.lastModifiedDeviceID = lastModifiedDeviceID
524
+ self.teamID = teamID
525
+ self.date = date
526
+ self.totalTokens = totalTokens
527
+ self.totalCost = totalCost
528
+ self.memberUsage = memberUsage
529
+ self.quotaUsedPercent = quotaUsedPercent
530
+ self.primaryProvider = primaryProvider
531
+ self.mostActiveUser = mostActiveUser
532
+ self.averageTokensPerMember = averageTokensPerMember
533
+ self.teamRecordName = teamRecordName
534
+ }
535
+
536
+ public func toCKRecord() throws -> CKRecord {
537
+ let zoneID = TeamSyncZone.zoneID
538
+ let record = CKRecord(
539
+ recordType: recordType,
540
+ recordID: CKRecord.ID(recordName: recordID, zoneID: zoneID)
541
+ )
542
+
543
+ // Sync metadata
544
+ record["version"] = version as CKRecordValue
545
+ record["modifiedAt"] = modifiedAt as CKRecordValue
546
+ record["lastModifiedDeviceID"] = lastModifiedDeviceID as? CKRecordValue
547
+
548
+ // 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
554
+
555
+ // Encode member usage as JSON
556
+ let encoder = JSONEncoder()
557
+ let memberUsageData = try encoder.encode(memberUsage)
558
+ record["memberUsage"] = String(data: memberUsageData, encoding: .utf8) as? CKRecordValue
559
+
560
+ // Optional metrics
561
+ record["primaryProvider"] = primaryProvider as? CKRecordValue
562
+ record["mostActiveUser"] = mostActiveUser as? CKRecordValue
563
+ record["averageTokensPerMember"] = averageTokensPerMember as CKRecordValue
564
+
565
+ // Reference to parent team
566
+ let teamReference = CKRecord.Reference(
567
+ recordID: CKRecord.ID(recordName: teamRecordName, zoneID: zoneID),
568
+ action: .deleteSelf
569
+ )
570
+ record["teamReference"] = teamReference as CKRecordValue
571
+
572
+ return record
573
+ }
574
+
575
+ public static func fromCKRecord(_ ckRecord: CKRecord) throws -> TeamUsageSnapshotRecord {
576
+ guard let version = ckRecord["version"] as? Int,
577
+ let modifiedAt = ckRecord["modifiedAt"] as? Date,
578
+ let teamID = ckRecord["teamID"] as? String,
579
+ let date = ckRecord["date"] as? Date,
580
+ let totalTokens = ckRecord["totalTokens"] as? Int64,
581
+ let totalCost = ckRecord["totalCost"] as? Double,
582
+ let quotaUsedPercent = ckRecord["quotaUsedPercent"] as? Double,
583
+ let teamReference = ckRecord["teamReference"] as? CKRecord.Reference
584
+ else {
585
+ throw SyncError.invalidRecordFormat("Missing required fields in TeamUsageSnapshot record")
586
+ }
587
+
588
+ // Decode member usage from JSON
589
+ var memberUsage: [String: MemberUsageData] = [:]
590
+ if let memberUsageJSON = ckRecord["memberUsage"] as? String,
591
+ let data = memberUsageJSON.data(using: .utf8) {
592
+ let decoder = JSONDecoder()
593
+ memberUsage = (try? decoder.decode([String: MemberUsageData].self, from: data)) ?? [:]
594
+ }
595
+
596
+ return TeamUsageSnapshotRecord(
597
+ recordID: ckRecord.recordID.recordName,
598
+ version: version,
599
+ modifiedAt: modifiedAt,
600
+ lastModifiedDeviceID: ckRecord["lastModifiedDeviceID"] as? String,
601
+ teamID: teamID,
602
+ date: date,
603
+ totalTokens: totalTokens,
604
+ totalCost: totalCost,
605
+ memberUsage: memberUsage,
606
+ quotaUsedPercent: quotaUsedPercent,
607
+ primaryProvider: ckRecord["primaryProvider"] as? String,
608
+ mostActiveUser: ckRecord["mostActiveUser"] as? String,
609
+ averageTokensPerMember: ckRecord["averageTokensPerMember"] as? Double ?? 0.0,
610
+ teamRecordName: teamReference.recordID.recordName
611
+ )
612
+ }
613
+ }
614
+
615
+ // MARK: - CloudKit Query Helpers
616
+
617
+ /// Helper methods for querying team-related records
618
+ public enum TeamRecordQuery {
619
+
620
+ /// Creates a predicate to query teams by owner
621
+ public static func teamsByOwner(_ ownerUserID: String) -> NSPredicate {
622
+ NSPredicate(format: "ownerUserID == %@", ownerUserID)
623
+ }
624
+
625
+ /// Creates a predicate to query memberships by team
626
+ public static func membershipsByTeam(_ teamID: String) -> NSPredicate {
627
+ NSPredicate(format: "teamID == %@ AND isActive == 1", teamID)
628
+ }
629
+
630
+ /// Creates a predicate to query memberships by user
631
+ public static func membershipsByUser(_ userID: String) -> NSPredicate {
632
+ NSPredicate(format: "userID == %@ AND isActive == 1", userID)
633
+ }
634
+
635
+ /// Creates a predicate to query projects by owner
636
+ public static func projectsByOwner(_ ownerUserID: String) -> NSPredicate {
637
+ NSPredicate(format: "ownerUserID == %@ AND isArchived == 0", ownerUserID)
638
+ }
639
+
640
+ /// Creates a predicate to query projects by team
641
+ public static func projectsByTeam(_ teamID: String) -> NSPredicate {
642
+ NSPredicate(format: "teamID == %@ AND isArchived == 0", teamID)
643
+ }
644
+
645
+ /// Creates a predicate to query team usage snapshots by date range
646
+ 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)
648
+ }
649
+
650
+ /// Creates a predicate to query latest team usage
651
+ public static func latestTeamUsage(_ teamID: String) -> NSPredicate {
652
+ NSPredicate(format: "teamID == %@", teamID)
653
+ }
654
+ }
655
+
656
+ // MARK: - CloudKit Index Configuration
657
+
658
+ /// Recommended CloudKit indexes for efficient querying
659
+ ///
660
+ /// Configure these indexes in CloudKit Dashboard for optimal performance:
661
+ ///
662
+ /// Team Record:
663
+ /// - ownerUserID (Queryable, Sortable)
664
+ /// - isActive (Queryable)
665
+ /// - createdAt (Sortable)
666
+ ///
667
+ /// TeamMembership Record:
668
+ /// - teamID (Queryable, Sortable)
669
+ /// - userID (Queryable, Sortable)
670
+ /// - isActive (Queryable)
671
+ /// - role (Queryable)
672
+ /// - joinedAt (Sortable)
673
+ ///
674
+ /// ProjectOwnership Record:
675
+ /// - ownerUserID (Queryable, Sortable)
676
+ /// - teamID (Queryable, Sortable)
677
+ /// - projectID (Queryable)
678
+ /// - isArchived (Queryable)
679
+ /// - accessLevel (Queryable)
680
+ /// - createdAt (Sortable)
681
+ ///
682
+ /// TeamUsageSnapshot Record:
683
+ /// - teamID (Queryable, Sortable)
684
+ /// - date (Queryable, Sortable)
685
+ /// - quotaUsedPercent (Sortable)
686
+ public enum TeamRecordIndexes {
687
+ // This is a documentation enum - configure actual indexes in CloudKit Dashboard
688
+ }
689
+
690
+ // MARK: - Encryption Helpers (Team-specific)
691
+
692
+ /// Encrypts a string using AES-GCM encryption (team-specific wrapper)
693
+ private func encryptString(_ plaintext: String) throws -> String {
694
+ guard let data = plaintext.data(using: .utf8) else {
695
+ throw SyncError.encryptionFailed("Failed to convert string to data")
696
+ }
697
+
698
+ let key = getOrCreateEncryptionKey()
699
+ let sealed = try AES.GCM.seal(data, using: key)
700
+
701
+ guard let combined = sealed.combined else {
702
+ throw SyncError.encryptionFailed("Failed to create sealed box")
703
+ }
704
+
705
+ return combined.base64EncodedString()
706
+ }
707
+
708
+ /// Decrypts a string that was encrypted with encryptString
709
+ private func decryptString(_ encrypted: String) throws -> String {
710
+ guard let combined = Data(base64Encoded: encrypted) else {
711
+ throw SyncError.encryptionFailed("Failed to decode base64")
712
+ }
713
+
714
+ let key = getOrCreateEncryptionKey()
715
+ let sealedBox = try AES.GCM.SealedBox(combined: combined)
716
+ let decrypted = try AES.GCM.open(sealedBox, using: key)
717
+
718
+ guard let plaintext = String(data: decrypted, encoding: .utf8) else {
719
+ throw SyncError.encryptionFailed("Failed to convert decrypted data to string")
720
+ }
721
+
722
+ return plaintext
723
+ }
724
+
725
+ /// Retrieves or creates an encryption key for sensitive data
726
+ private func getOrCreateEncryptionKey() -> SymmetricKey {
727
+ let keychainKey = "com.runic.sync.team.encryption.key"
728
+
729
+ // Try to load existing key from Keychain
730
+ if let keyData = loadFromKeychain(key: keychainKey) {
731
+ return SymmetricKey(data: keyData)
732
+ }
733
+
734
+ // Generate new key
735
+ let key = SymmetricKey(size: .bits256)
736
+ let keyData = key.withUnsafeBytes { Data($0) }
737
+
738
+ // Store in Keychain
739
+ saveToKeychain(key: keychainKey, data: keyData)
740
+
741
+ return key
742
+ }
743
+
744
+ private func saveToKeychain(key: String, data: Data) {
745
+ let query: [String: Any] = [
746
+ kSecClass as String: kSecClassGenericPassword,
747
+ kSecAttrService as String: "com.sriinnu.athena.Runic",
748
+ kSecAttrAccount as String: key,
749
+ kSecUseDataProtectionKeychain as String: true,
750
+ kSecValueData as String: data,
751
+ kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
752
+ ]
753
+ SecItemDelete(query as CFDictionary)
754
+ SecItemAdd(query as CFDictionary, nil)
755
+ }
756
+
757
+ private func loadFromKeychain(key: String) -> Data? {
758
+ var query: [String: Any] = [
759
+ kSecClass as String: kSecClassGenericPassword,
760
+ kSecAttrService as String: "com.sriinnu.athena.Runic",
761
+ kSecAttrAccount as String: key,
762
+ kSecUseDataProtectionKeychain as String: true,
763
+ kSecUseAuthenticationUI as String: "kSecUseAuthenticationUIFail" as CFString,
764
+ kSecReturnData as String: true
765
+ ]
766
+ let authContext = LAContext()
767
+ authContext.interactionNotAllowed = true
768
+ query[kSecUseAuthenticationContext as String] = authContext
769
+ var result: AnyObject?
770
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
771
+ if status == errSecInteractionNotAllowed {
772
+ return nil
773
+ }
774
+ return status == errSecSuccess ? result as? Data : nil
775
+ }