@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,501 @@
1
+ import Foundation
2
+ #if canImport(Security)
3
+ import Security
4
+ #endif
5
+ #if canImport(LocalAuthentication)
6
+ import LocalAuthentication
7
+ #endif
8
+
9
+ public enum ProviderTokenSource: String, Sendable {
10
+ case keychain
11
+ case environment
12
+ case vscode
13
+ }
14
+
15
+ public struct ProviderTokenResolution: Sendable {
16
+ public let token: String
17
+ public let source: ProviderTokenSource
18
+ public let sourceKey: String?
19
+
20
+ public init(token: String, source: ProviderTokenSource, sourceKey: String? = nil) {
21
+ self.token = token
22
+ self.source = source
23
+ self.sourceKey = sourceKey
24
+ }
25
+ }
26
+
27
+ public enum ProviderTokenResolver {
28
+ private static let log = RunicLog.logger("provider-token")
29
+
30
+ private static let keychainService = "com.sriinnu.athena.Runic"
31
+ private static let zaiAccount = "zai-api-token"
32
+ private static let copilotAccount = "copilot-api-token"
33
+ private static let minimaxAccount = "minimax-api-token"
34
+ private static let minimaxCookieAccount = "minimax-cookie-header"
35
+ private static let minimaxGroupAccount = "minimax-group-id"
36
+ private static let openRouterAccount = "openrouter-api-token"
37
+ private static let groqAccount = "groq-api-token"
38
+ private static let deepSeekAccount = "deepseek-api-token"
39
+ private static let fireworksAccount = "fireworks-api-token"
40
+ private static let mistralAccount = "mistral-api-token"
41
+ private static let perplexityAccount = "perplexity-api-token"
42
+ private static let kimiAccount = "kimi-api-token"
43
+ private static let auggieAccount = "auggie-api-token"
44
+ private static let togetherAccount = "together-api-token"
45
+ private static let cohereAccount = "cohere-api-token"
46
+ private static let xaiAccount = "xai-api-token"
47
+ private static let cerebrasAccount = "cerebras-api-token"
48
+ private static let sambanovaAccount = "sambanova-api-token"
49
+ private static let azureOpenAIAccount = "azure-openai-api-token"
50
+ private static let qwenAccount = "qwen-api-token"
51
+
52
+ public static func zaiToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
53
+ self.zaiResolution(environment: environment)?.token
54
+ }
55
+
56
+ public static func copilotToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
57
+ self.copilotResolution(environment: environment)?.token
58
+ }
59
+
60
+ public static func copilotCLIToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
61
+ CopilotGitHubCLITokenReader.token(environment: environment)
62
+ }
63
+
64
+ public static func minimaxToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
65
+ self.minimaxResolution(environment: environment)?.token
66
+ }
67
+
68
+ public static func minimaxCookieHeader(
69
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
70
+ {
71
+ self.minimaxCookieHeaderResolution(environment: environment)?.token
72
+ }
73
+
74
+ public static func minimaxGroupID(
75
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
76
+ {
77
+ self.minimaxGroupResolution(environment: environment)?.token
78
+ }
79
+
80
+ public static func openRouterToken(
81
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
82
+ {
83
+ self.openRouterResolution(environment: environment)?.token
84
+ }
85
+
86
+ public static func groqToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
87
+ self.groqResolution(environment: environment)?.token
88
+ }
89
+
90
+ public static func deepSeekToken(
91
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
92
+ {
93
+ self.deepSeekResolution(environment: environment)?.token
94
+ }
95
+
96
+ public static func fireworksToken(
97
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
98
+ {
99
+ self.fireworksResolution(environment: environment)?.token
100
+ }
101
+
102
+ public static func mistralToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
103
+ self.mistralResolution(environment: environment)?.token
104
+ }
105
+
106
+ public static func perplexityToken(
107
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
108
+ {
109
+ self.perplexityResolution(environment: environment)?.token
110
+ }
111
+
112
+ public static func kimiToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
113
+ self.kimiResolution(environment: environment)?.token
114
+ }
115
+
116
+ public static func auggieToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
117
+ self.auggieResolution(environment: environment)?.token
118
+ }
119
+
120
+ public static func togetherToken(
121
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
122
+ {
123
+ self.togetherResolution(environment: environment)?.token
124
+ }
125
+
126
+ public static func cohereToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
127
+ self.cohereResolution(environment: environment)?.token
128
+ }
129
+
130
+ public static func xaiToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
131
+ self.xaiResolution(environment: environment)?.token
132
+ }
133
+
134
+ public static func cerebrasToken(
135
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
136
+ {
137
+ self.cerebrasResolution(environment: environment)?.token
138
+ }
139
+
140
+ public static func sambaNovaToken(
141
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
142
+ {
143
+ self.sambaNovaResolution(environment: environment)?.token
144
+ }
145
+
146
+ public static func azureOpenAIToken(
147
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> String?
148
+ {
149
+ self.azureOpenAIResolution(environment: environment)?.token
150
+ }
151
+
152
+ public static func qwenToken(environment: [String: String] = ProcessInfo.processInfo.environment) -> String? {
153
+ self.qwenResolution(environment: environment)?.token
154
+ }
155
+
156
+ public static func zaiResolution(
157
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
158
+ {
159
+ if let token = self.keychainToken(service: self.keychainService, account: self.zaiAccount) {
160
+ return ProviderTokenResolution(token: token, source: .keychain)
161
+ }
162
+ if let token = ZaiSettingsReader.apiToken(environment: environment) {
163
+ return ProviderTokenResolution(token: token, source: .environment)
164
+ }
165
+ return nil
166
+ }
167
+
168
+ public static func copilotResolution(
169
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
170
+ {
171
+ if let token = self.keychainToken(service: self.keychainService, account: self.copilotAccount) {
172
+ return ProviderTokenResolution(token: token, source: .keychain, sourceKey: "copilot-api-token")
173
+ }
174
+ if let token = self.cleaned(environment["COPILOT_API_TOKEN"]) {
175
+ return ProviderTokenResolution(token: token, source: .environment, sourceKey: "COPILOT_API_TOKEN")
176
+ }
177
+ if let token = self.cleaned(environment["GITHUB_TOKEN"]) {
178
+ return ProviderTokenResolution(token: token, source: .environment, sourceKey: "GITHUB_TOKEN")
179
+ }
180
+ if let token = self.cleaned(environment["GH_TOKEN"]) {
181
+ return ProviderTokenResolution(token: token, source: .environment, sourceKey: "GH_TOKEN")
182
+ }
183
+ if let token = self.copilotCLIToken(environment: environment) {
184
+ return ProviderTokenResolution(token: token, source: .environment, sourceKey: "gh-cli")
185
+ }
186
+ if let token = CopilotVSCodeTokenReader.token(environment: environment) {
187
+ return ProviderTokenResolution(token: token, source: .vscode, sourceKey: "vscode")
188
+ }
189
+ return nil
190
+ }
191
+
192
+ public static func minimaxResolution(
193
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
194
+ {
195
+ if let api = self.minimaxApiKeyResolution(environment: environment) {
196
+ return api
197
+ }
198
+ return self.minimaxCookieHeaderResolution(environment: environment)
199
+ }
200
+
201
+ public static func minimaxApiKeyResolution(
202
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
203
+ {
204
+ if let token = self.keychainToken(service: self.keychainService, account: self.minimaxAccount) {
205
+ return ProviderTokenResolution(token: token, source: .keychain)
206
+ }
207
+ if let token = self.cleaned(environment["MINIMAX_API_KEY"]) {
208
+ return ProviderTokenResolution(token: token, source: .environment)
209
+ }
210
+ return nil
211
+ }
212
+
213
+ public static func minimaxCookieHeaderResolution(
214
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
215
+ {
216
+ if let token = self.keychainToken(service: self.keychainService, account: self.minimaxCookieAccount) {
217
+ return ProviderTokenResolution(token: token, source: .keychain)
218
+ }
219
+ if let token = MiniMaxSettingsReader.cookieHeader(environment: environment) {
220
+ return ProviderTokenResolution(token: token, source: .environment)
221
+ }
222
+ return nil
223
+ }
224
+
225
+ public static func minimaxGroupResolution(
226
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
227
+ {
228
+ if let token = self.keychainToken(service: self.keychainService, account: self.minimaxGroupAccount) {
229
+ return ProviderTokenResolution(token: token, source: .keychain)
230
+ }
231
+ if let token = MiniMaxSettingsReader.groupID(environment: environment) {
232
+ return ProviderTokenResolution(token: token, source: .environment)
233
+ }
234
+ return nil
235
+ }
236
+
237
+ public static func openRouterResolution(
238
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
239
+ {
240
+ if let token = self.keychainToken(service: self.keychainService, account: self.openRouterAccount) {
241
+ return ProviderTokenResolution(token: token, source: .keychain)
242
+ }
243
+ if let token = self.cleaned(environment["OPENROUTER_API_KEY"]) {
244
+ return ProviderTokenResolution(token: token, source: .environment)
245
+ }
246
+ return nil
247
+ }
248
+
249
+ public static func groqResolution(
250
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
251
+ {
252
+ if let token = self.keychainToken(service: self.keychainService, account: self.groqAccount) {
253
+ return ProviderTokenResolution(token: token, source: .keychain)
254
+ }
255
+ if let token = self.cleaned(environment["GROQ_API_KEY"]) {
256
+ return ProviderTokenResolution(token: token, source: .environment)
257
+ }
258
+ return nil
259
+ }
260
+
261
+ public static func deepSeekResolution(
262
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
263
+ {
264
+ if let token = self.keychainToken(service: self.keychainService, account: self.deepSeekAccount) {
265
+ return ProviderTokenResolution(token: token, source: .keychain)
266
+ }
267
+ if let token = self.cleaned(environment["DEEPSEEK_API_KEY"]) {
268
+ return ProviderTokenResolution(token: token, source: .environment)
269
+ }
270
+ return nil
271
+ }
272
+
273
+ public static func fireworksResolution(
274
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
275
+ {
276
+ if let token = self.keychainToken(service: self.keychainService, account: self.fireworksAccount) {
277
+ return ProviderTokenResolution(token: token, source: .keychain)
278
+ }
279
+ if let token = self.cleaned(environment["FIREWORKS_API_KEY"]) {
280
+ return ProviderTokenResolution(token: token, source: .environment)
281
+ }
282
+ return nil
283
+ }
284
+
285
+ public static func mistralResolution(
286
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
287
+ {
288
+ if let token = self.keychainToken(service: self.keychainService, account: self.mistralAccount) {
289
+ return ProviderTokenResolution(token: token, source: .keychain)
290
+ }
291
+ if let token = self.cleaned(environment["MISTRAL_API_KEY"]) {
292
+ return ProviderTokenResolution(token: token, source: .environment)
293
+ }
294
+ return nil
295
+ }
296
+
297
+ public static func perplexityResolution(
298
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
299
+ {
300
+ if let token = self.keychainToken(service: self.keychainService, account: self.perplexityAccount) {
301
+ return ProviderTokenResolution(token: token, source: .keychain)
302
+ }
303
+ if let token = self.cleaned(environment["PPLX_API_KEY"]) {
304
+ return ProviderTokenResolution(token: token, source: .environment)
305
+ }
306
+ if let token = self.cleaned(environment["PERPLEXITY_API_KEY"]) {
307
+ return ProviderTokenResolution(token: token, source: .environment)
308
+ }
309
+ return nil
310
+ }
311
+
312
+ public static func kimiResolution(
313
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
314
+ {
315
+ if let token = self.keychainToken(service: self.keychainService, account: self.kimiAccount) {
316
+ return ProviderTokenResolution(token: token, source: .keychain)
317
+ }
318
+ if let token = self.cleaned(environment["KIMI_API_KEY"]) {
319
+ return ProviderTokenResolution(token: token, source: .environment)
320
+ }
321
+ if let token = self.cleaned(environment["MOONSHOT_API_KEY"]) {
322
+ return ProviderTokenResolution(token: token, source: .environment)
323
+ }
324
+ return nil
325
+ }
326
+
327
+ public static func auggieResolution(
328
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
329
+ {
330
+ if let token = self.keychainToken(service: self.keychainService, account: self.auggieAccount) {
331
+ return ProviderTokenResolution(token: token, source: .keychain)
332
+ }
333
+ if let token = self.cleaned(environment["AUGMENT_API_TOKEN"]) {
334
+ return ProviderTokenResolution(token: token, source: .environment)
335
+ }
336
+ if let token = self.cleaned(environment["AUGGIE_API_TOKEN"]) {
337
+ return ProviderTokenResolution(token: token, source: .environment)
338
+ }
339
+ return nil
340
+ }
341
+
342
+ public static func togetherResolution(
343
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
344
+ {
345
+ if let token = self.keychainToken(service: self.keychainService, account: self.togetherAccount) {
346
+ return ProviderTokenResolution(token: token, source: .keychain)
347
+ }
348
+ if let token = self.cleaned(environment["TOGETHER_API_KEY"]) {
349
+ return ProviderTokenResolution(token: token, source: .environment)
350
+ }
351
+ return nil
352
+ }
353
+
354
+ public static func cohereResolution(
355
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
356
+ {
357
+ if let token = self.keychainToken(service: self.keychainService, account: self.cohereAccount) {
358
+ return ProviderTokenResolution(token: token, source: .keychain)
359
+ }
360
+ if let token = self.cleaned(environment["COHERE_API_KEY"]) {
361
+ return ProviderTokenResolution(token: token, source: .environment)
362
+ }
363
+ return nil
364
+ }
365
+
366
+ public static func xaiResolution(
367
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
368
+ {
369
+ if let token = self.keychainToken(service: self.keychainService, account: self.xaiAccount) {
370
+ return ProviderTokenResolution(token: token, source: .keychain)
371
+ }
372
+ if let token = self.cleaned(environment["XAI_API_KEY"]) {
373
+ return ProviderTokenResolution(token: token, source: .environment)
374
+ }
375
+ return nil
376
+ }
377
+
378
+ public static func cerebrasResolution(
379
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
380
+ {
381
+ if let token = self.keychainToken(service: self.keychainService, account: self.cerebrasAccount) {
382
+ return ProviderTokenResolution(token: token, source: .keychain)
383
+ }
384
+ if let token = self.cleaned(environment["CEREBRAS_API_KEY"]) {
385
+ return ProviderTokenResolution(token: token, source: .environment)
386
+ }
387
+ return nil
388
+ }
389
+
390
+ public static func sambaNovaResolution(
391
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
392
+ {
393
+ if let token = self.keychainToken(service: self.keychainService, account: self.sambanovaAccount) {
394
+ return ProviderTokenResolution(token: token, source: .keychain)
395
+ }
396
+ if let token = self.cleaned(environment["SAMBANOVA_API_KEY"]) {
397
+ return ProviderTokenResolution(token: token, source: .environment)
398
+ }
399
+ return nil
400
+ }
401
+
402
+ public static func azureOpenAIResolution(
403
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
404
+ {
405
+ if let token = self.keychainToken(service: self.keychainService, account: self.azureOpenAIAccount) {
406
+ return ProviderTokenResolution(token: token, source: .keychain)
407
+ }
408
+ if let token = self.cleaned(environment["AZURE_OPENAI_API_KEY"]) {
409
+ return ProviderTokenResolution(token: token, source: .environment)
410
+ }
411
+ if let token = self.cleaned(environment["AZURE_AI_API_KEY"]) {
412
+ return ProviderTokenResolution(token: token, source: .environment)
413
+ }
414
+ return nil
415
+ }
416
+
417
+ public static func qwenResolution(
418
+ environment: [String: String] = ProcessInfo.processInfo.environment) -> ProviderTokenResolution?
419
+ {
420
+ if let token = self.keychainToken(service: self.keychainService, account: self.qwenAccount) {
421
+ return ProviderTokenResolution(token: token, source: .keychain)
422
+ }
423
+ if let token = QwenSettingsReader.apiToken(environment: environment) {
424
+ return ProviderTokenResolution(token: token, source: .environment)
425
+ }
426
+ return nil
427
+ }
428
+
429
+ private static func cleaned(_ raw: String?) -> String? {
430
+ guard var value = raw?.trimmingCharacters(in: .whitespacesAndNewlines), !value.isEmpty else {
431
+ return nil
432
+ }
433
+
434
+ if (value.hasPrefix("\"") && value.hasSuffix("\"")) ||
435
+ (value.hasPrefix("'") && value.hasSuffix("'"))
436
+ {
437
+ value.removeFirst()
438
+ value.removeLast()
439
+ }
440
+
441
+ value = value.trimmingCharacters(in: .whitespacesAndNewlines)
442
+ return value.isEmpty ? nil : value
443
+ }
444
+
445
+ private static func keychainToken(service: String, account: String) -> String? {
446
+ #if canImport(Security)
447
+ // Token stores write to the standard keychain (dataProtection: false),
448
+ // so try it first. Fall back to the Data Protection keychain for
449
+ // pre-migration items that haven't been moved yet.
450
+ if let token = self.keychainRead(service: service, account: account, dataProtection: false) {
451
+ return token
452
+ }
453
+ return self.keychainRead(service: service, account: account, dataProtection: true)
454
+ #else
455
+ _ = service
456
+ _ = account
457
+ return nil
458
+ #endif
459
+ }
460
+
461
+ #if canImport(Security)
462
+ private static func keychainRead(service: String, account: String, dataProtection: Bool) -> String? {
463
+ var result: CFTypeRef?
464
+ var query: [String: Any] = [
465
+ kSecClass as String: kSecClassGenericPassword,
466
+ kSecAttrService as String: service,
467
+ kSecAttrAccount as String: account,
468
+ kSecUseAuthenticationUI as String: "kSecUseAuthenticationUIFail" as CFString,
469
+ kSecMatchLimit as String: kSecMatchLimitOne,
470
+ kSecReturnData as String: true,
471
+ ]
472
+ if dataProtection {
473
+ query[kSecUseDataProtectionKeychain as String] = true
474
+ }
475
+ #if canImport(LocalAuthentication)
476
+ let authContext = LAContext()
477
+ authContext.interactionNotAllowed = true
478
+ query[kSecUseAuthenticationContext as String] = authContext
479
+ #endif
480
+
481
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
482
+ if status == errSecItemNotFound || status == errSecInteractionNotAllowed {
483
+ return nil
484
+ }
485
+ guard status == errSecSuccess else {
486
+ self.log.error("Keychain read failed: \(status)")
487
+ return nil
488
+ }
489
+
490
+ guard let data = result as? Data,
491
+ let token = String(data: data, encoding: .utf8),
492
+ let cleaned = self.cleaned(token),
493
+ !cleaned.isEmpty
494
+ else {
495
+ return nil
496
+ }
497
+
498
+ return cleaned
499
+ }
500
+ #endif
501
+ }
@@ -0,0 +1,68 @@
1
+ import Foundation
2
+
3
+ public enum ProviderVersionDetector {
4
+ public static func codexVersion() -> String? {
5
+ guard let path = TTYCommandRunner.which("codex") else { return nil }
6
+ let candidates = [
7
+ ["--version"],
8
+ ["version"],
9
+ ["-v"],
10
+ ]
11
+ for args in candidates {
12
+ if let version = Self.run(path: path, args: args) { return version }
13
+ }
14
+ return nil
15
+ }
16
+
17
+ public static func geminiVersion() -> String? {
18
+ let env = ProcessInfo.processInfo.environment
19
+ guard let path = BinaryLocator.resolveGeminiBinary(env: env, loginPATH: nil)
20
+ ?? TTYCommandRunner.which("gemini") else { return nil }
21
+ let candidates = [
22
+ ["--version"],
23
+ ["-v"],
24
+ ]
25
+ for args in candidates {
26
+ if let version = Self.run(path: path, args: args) { return version }
27
+ }
28
+ return nil
29
+ }
30
+
31
+ private static func run(path: String, args: [String]) -> String? {
32
+ let proc = Process()
33
+ proc.executableURL = URL(fileURLWithPath: path)
34
+ proc.arguments = args
35
+ let out = Pipe()
36
+ proc.standardOutput = out
37
+ proc.standardError = Pipe()
38
+
39
+ do {
40
+ try proc.run()
41
+ } catch {
42
+ return nil
43
+ }
44
+
45
+ let deadline = Date().addingTimeInterval(2.0)
46
+ while proc.isRunning, Date() < deadline {
47
+ usleep(50000)
48
+ }
49
+ if proc.isRunning {
50
+ proc.terminate()
51
+ let killDeadline = Date().addingTimeInterval(0.5)
52
+ while proc.isRunning, Date() < killDeadline {
53
+ usleep(20000)
54
+ }
55
+ if proc.isRunning {
56
+ kill(proc.processIdentifier, SIGKILL)
57
+ }
58
+ }
59
+
60
+ let data = out.fileHandleForReading.readDataToEndOfFile()
61
+ guard proc.terminationStatus == 0,
62
+ let text = String(data: data, encoding: .utf8)?
63
+ .split(whereSeparator: \.isNewline).first
64
+ else { return nil }
65
+ let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
66
+ return trimmed.isEmpty ? nil : trimmed
67
+ }
68
+ }