@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,168 @@
1
+ import Foundation
2
+
3
+ struct BedrockModelsResponse: Decodable, Sendable {
4
+ struct ModelSummary: Decodable, Sendable {
5
+ let modelID: String?
6
+ let modelName: String?
7
+ let providerName: String?
8
+
9
+ enum CodingKeys: String, CodingKey {
10
+ case modelID = "modelId"
11
+ case modelName
12
+ case providerName
13
+ }
14
+ }
15
+
16
+ let modelSummaries: [ModelSummary]?
17
+
18
+ init(modelSummaries: [ModelSummary]?) {
19
+ self.modelSummaries = modelSummaries
20
+ }
21
+ }
22
+
23
+ struct BedrockUsageFetcher {
24
+ private static let commandTimeout: TimeInterval = 20
25
+
26
+ static func fetchModels(
27
+ region: String,
28
+ profile: String?,
29
+ modelFilter: String?) async throws -> BedrockModelsResponse
30
+ {
31
+ let output = try await self.runAWSCLI(
32
+ arguments: ["bedrock", "list-foundation-models", "--region", region, "--output", "json"],
33
+ profile: profile)
34
+
35
+ let decoder = JSONDecoder()
36
+ let decoded: BedrockModelsResponse
37
+ do {
38
+ decoded = try decoder.decode(BedrockModelsResponse.self, from: output)
39
+ } catch {
40
+ throw BedrockCLIError.decodeFailed(error.localizedDescription)
41
+ }
42
+
43
+ guard let modelFilter = self.cleaned(modelFilter) else {
44
+ return decoded
45
+ }
46
+ let loweredFilter = modelFilter.lowercased()
47
+ let filtered = (decoded.modelSummaries ?? []).filter { model in
48
+ let id = model.modelID?.lowercased() ?? ""
49
+ let name = model.modelName?.lowercased() ?? ""
50
+ let provider = model.providerName?.lowercased() ?? ""
51
+ return id.contains(loweredFilter) || name.contains(loweredFilter) || provider.contains(loweredFilter)
52
+ }
53
+ return BedrockModelsResponse(modelSummaries: filtered)
54
+ }
55
+
56
+ private static func runAWSCLI(arguments: [String], profile: String?) async throws -> Data {
57
+ var environment = ProcessInfo.processInfo.environment
58
+ environment["PATH"] = self.effectivePATH(existing: environment["PATH"])
59
+ if let profile = self.cleaned(profile) {
60
+ environment["AWS_PROFILE"] = profile
61
+ }
62
+
63
+ do {
64
+ let result = try await SubprocessRunner.run(
65
+ binary: "/usr/bin/env",
66
+ arguments: ["aws"] + arguments,
67
+ environment: environment,
68
+ timeout: Self.commandTimeout,
69
+ label: "bedrock.cli")
70
+ let output = Data(result.stdout.utf8)
71
+ guard !output.isEmpty else {
72
+ throw BedrockCLIError.emptyResponse
73
+ }
74
+ return output
75
+ } catch let error as SubprocessRunnerError {
76
+ switch error {
77
+ case .binaryNotFound:
78
+ throw BedrockCLIError.awsCLINotFound
79
+ case let .launchFailed(details):
80
+ throw BedrockCLIError.commandFailed(statusCode: -1, detail: details)
81
+ case .timedOut:
82
+ throw BedrockCLIError.commandTimedOut
83
+ case let .nonZeroExit(code, stderr):
84
+ throw BedrockCLIError.commandFailed(statusCode: Int(code), detail: stderr)
85
+ }
86
+ }
87
+ }
88
+
89
+ private static func effectivePATH(existing: String?) -> String {
90
+ let defaults = ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"]
91
+ var parts = (existing ?? "")
92
+ .split(separator: ":")
93
+ .map(String.init)
94
+ .filter { !$0.isEmpty }
95
+ for path in defaults where !parts.contains(path) {
96
+ parts.append(path)
97
+ }
98
+ return parts.joined(separator: ":")
99
+ }
100
+
101
+ private static func cleaned(_ raw: String?) -> String? {
102
+ let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines)
103
+ guard let trimmed, !trimmed.isEmpty else { return nil }
104
+ return trimmed
105
+ }
106
+ }
107
+
108
+ extension BedrockModelsResponse {
109
+ func toUsageSnapshot(region: String, profile: String?, modelFilter: String?) -> UsageSnapshot {
110
+ let models = self.modelSummaries ?? []
111
+ let count = models.count
112
+ let preview = models
113
+ .compactMap(\.modelID)
114
+ .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
115
+ .filter { !$0.isEmpty }
116
+ .prefix(3)
117
+ .joined(separator: ", ")
118
+
119
+ var summaryParts: [String] = ["Models available: \(count)", "region \(region)"]
120
+ if let profile = profile?.trimmingCharacters(in: .whitespacesAndNewlines), !profile.isEmpty {
121
+ summaryParts.append("profile \(profile)")
122
+ }
123
+ if let modelFilter = modelFilter?.trimmingCharacters(in: .whitespacesAndNewlines), !modelFilter.isEmpty {
124
+ summaryParts.append("filter \(modelFilter)")
125
+ }
126
+ if !preview.isEmpty {
127
+ summaryParts.append(preview)
128
+ }
129
+
130
+ return UsageSnapshot(
131
+ primary: RateWindow(
132
+ usedPercent: 0,
133
+ windowMinutes: nil,
134
+ resetsAt: nil,
135
+ resetDescription: summaryParts.joined(separator: " • "),
136
+ label: "Foundation models"),
137
+ secondary: nil,
138
+ tertiary: nil,
139
+ updatedAt: Date(),
140
+ identity: nil)
141
+ }
142
+ }
143
+
144
+ enum BedrockCLIError: LocalizedError, Sendable {
145
+ case awsCLINotFound
146
+ case commandFailed(statusCode: Int, detail: String?)
147
+ case commandTimedOut
148
+ case decodeFailed(String)
149
+ case emptyResponse
150
+
151
+ var errorDescription: String? {
152
+ switch self {
153
+ case .awsCLINotFound:
154
+ return "AWS CLI is not available. Install awscli and ensure `aws` is on PATH."
155
+ case let .commandFailed(statusCode, detail):
156
+ if let detail, !detail.isEmpty {
157
+ return "AWS Bedrock CLI request failed (\(statusCode)): \(detail)"
158
+ }
159
+ return "AWS Bedrock CLI request failed with exit code \(statusCode)."
160
+ case .commandTimedOut:
161
+ return "AWS Bedrock CLI request timed out."
162
+ case let .decodeFailed(detail):
163
+ return "Could not decode Bedrock response: \(detail)"
164
+ case .emptyResponse:
165
+ return "AWS Bedrock returned an empty response."
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,84 @@
1
+ import RunicMacroSupport
2
+ import Foundation
3
+
4
+ @ProviderDescriptorRegistration
5
+ @ProviderDescriptorDefinition
6
+ public enum CerebrasProviderDescriptor {
7
+ static func makeDescriptor() -> ProviderDescriptor {
8
+ ProviderDescriptor(
9
+ id: .cerebras,
10
+ metadata: ProviderMetadata(
11
+ id: .cerebras,
12
+ displayName: "Cerebras",
13
+ sessionLabel: "Models",
14
+ weeklyLabel: "",
15
+ opusLabel: nil,
16
+ supportsOpus: false,
17
+ supportsCredits: false,
18
+ creditsHint: "Shows model availability from Cerebras API.",
19
+ toggleTitle: "Show Cerebras usage",
20
+ cliName: "cerebras",
21
+ defaultEnabled: false,
22
+ isPrimaryProvider: false,
23
+ usesAccountFallback: false,
24
+ dashboardURL: "https://cloud.cerebras.ai",
25
+ statusPageURL: nil,
26
+ usageCoverage: ProviderUsageCoverage(
27
+ supportsModelBreakdown: true,
28
+ supportsTokenMetrics: false,
29
+ supportsProjectAttribution: false)),
30
+ branding: ProviderBranding(
31
+ iconStyle: .cerebras,
32
+ iconResourceName: "ProviderIcon-cerebras",
33
+ color: ProviderColor(red: 14 / 255, green: 122 / 255, blue: 161 / 255)),
34
+ tokenCost: ProviderTokenCostConfig(
35
+ supportsTokenCost: false,
36
+ noDataMessage: { "Cerebras cost summary is not supported." }),
37
+ fetchPlan: ProviderFetchPlan(
38
+ sourceModes: [.auto, .cli],
39
+ pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [CerebrasAPIFetchStrategy()] })),
40
+ cli: ProviderCLIConfig(
41
+ name: "cerebras",
42
+ aliases: [],
43
+ versionDetector: nil))
44
+ }
45
+ }
46
+
47
+ struct CerebrasAPIFetchStrategy: ProviderFetchStrategy {
48
+ let id: String = "cerebras.api"
49
+ let kind: ProviderFetchKind = .apiToken
50
+
51
+ func isAvailable(_ context: ProviderFetchContext) async -> Bool {
52
+ Self.resolveTokenResolution(environment: context.env) != nil
53
+ }
54
+
55
+ func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
56
+ guard let tokenRes = Self.resolveTokenResolution(environment: context.env) else {
57
+ throw CerebrasSettingsError.missingToken
58
+ }
59
+ let usage = try await CerebrasUsageFetcher.fetchModels(apiKey: tokenRes.token)
60
+ return self.makeResult(
61
+ usage: usage.toUsageSnapshot(),
62
+ sourceLabel: tokenRes.source.rawValue)
63
+ }
64
+
65
+ func shouldFallback(on _: Error, context _: ProviderFetchContext) -> Bool {
66
+ false
67
+ }
68
+
69
+ private static func resolveTokenResolution(environment: [String: String]) -> ProviderTokenResolution? {
70
+ ProviderTokenResolver.cerebrasResolution(environment: environment)
71
+ }
72
+ }
73
+
74
+ enum CerebrasSettingsError: LocalizedError, Sendable {
75
+ case missingToken
76
+
77
+ var errorDescription: String? {
78
+ switch self {
79
+ case .missingToken:
80
+ "Cerebras API key not found. Set it in Preferences → Providers → Cerebras or " +
81
+ "export CEREBRAS_API_KEY."
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,75 @@
1
+ import Foundation
2
+
3
+ struct CerebrasModelsResponse: Decodable {
4
+ let data: [Model]?
5
+
6
+ struct Model: Decodable {
7
+ let id: String?
8
+ }
9
+ }
10
+
11
+ struct CerebrasUsageFetcher {
12
+ static let apiURL = URL(string: "https://api.cerebras.ai/v1/models")!
13
+
14
+ static func fetchModels(apiKey: String) async throws -> CerebrasModelsResponse {
15
+ var request = URLRequest(url: apiURL)
16
+ request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
17
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
18
+
19
+ let (data, response) = try await URLSession.shared.data(for: request)
20
+
21
+ guard let httpResponse = response as? HTTPURLResponse else {
22
+ throw CerebrasAPIError.invalidResponse
23
+ }
24
+
25
+ guard httpResponse.statusCode == 200 else {
26
+ let body = String(data: data, encoding: .utf8)?
27
+ .trimmingCharacters(in: .whitespacesAndNewlines)
28
+ throw CerebrasAPIError.httpError(
29
+ statusCode: httpResponse.statusCode,
30
+ body: body?.isEmpty == false ? body : nil)
31
+ }
32
+
33
+ let decoder = JSONDecoder()
34
+ return try decoder.decode(CerebrasModelsResponse.self, from: data)
35
+ }
36
+ }
37
+
38
+ extension CerebrasModelsResponse {
39
+ func toUsageSnapshot() -> UsageSnapshot {
40
+ let models = (self.data ?? []).compactMap(\.id)
41
+ var summary = "Models available: \(models.count)"
42
+ let preview = models.prefix(3).joined(separator: ", ")
43
+ if !preview.isEmpty {
44
+ summary += " (\(preview))"
45
+ }
46
+
47
+ return UsageSnapshot(
48
+ primary: RateWindow(
49
+ usedPercent: 0,
50
+ windowMinutes: nil,
51
+ resetsAt: nil,
52
+ resetDescription: summary),
53
+ secondary: nil,
54
+ tertiary: nil,
55
+ updatedAt: Date(),
56
+ identity: nil)
57
+ }
58
+ }
59
+
60
+ enum CerebrasAPIError: LocalizedError, Sendable {
61
+ case invalidResponse
62
+ case httpError(statusCode: Int, body: String?)
63
+
64
+ var errorDescription: String? {
65
+ switch self {
66
+ case .invalidResponse:
67
+ return "Invalid response from Cerebras API"
68
+ case let .httpError(statusCode, body):
69
+ if let body, !body.isEmpty {
70
+ return "Cerebras API returned status code \(statusCode): \(body)"
71
+ }
72
+ return "Cerebras API returned status code \(statusCode)"
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,332 @@
1
+ #if canImport(Darwin)
2
+ import Darwin
3
+ #else
4
+ import Glibc
5
+ #endif
6
+ import Foundation
7
+
8
+ actor ClaudeCLISession {
9
+ static let shared = ClaudeCLISession()
10
+
11
+ enum SessionError: LocalizedError {
12
+ case launchFailed(String)
13
+ case timedOut
14
+ case processExited
15
+
16
+ var errorDescription: String? {
17
+ switch self {
18
+ case let .launchFailed(msg): "Failed to launch Claude CLI session: \(msg)"
19
+ case .timedOut: "Claude CLI session timed out."
20
+ case .processExited: "Claude CLI session exited."
21
+ }
22
+ }
23
+ }
24
+
25
+ private var process: Process?
26
+ private var primaryFD: Int32 = -1
27
+ private var primaryHandle: FileHandle?
28
+ private var secondaryHandle: FileHandle?
29
+ private var processGroup: pid_t?
30
+ private var binaryPath: String?
31
+ private var startedAt: Date?
32
+ private var lastActivityAt: Date?
33
+ private var idleShutdownTask: Task<Void, Never>?
34
+ private var isCapturing = false
35
+ private let idleShutdownDelay: TimeInterval = 90
36
+
37
+ private let sendOnSubstrings: [String: String] = [
38
+ "Do you trust the files in this folder?": "y\r",
39
+ "Ready to code here?": "\r",
40
+ "Press Enter to continue": "\r",
41
+ ]
42
+
43
+ private struct RollingBuffer {
44
+ private let maxNeedle: Int
45
+ private var tail = Data()
46
+
47
+ init(maxNeedle: Int) {
48
+ self.maxNeedle = max(0, maxNeedle)
49
+ }
50
+
51
+ mutating func append(_ data: Data) -> Data {
52
+ guard !data.isEmpty else { return Data() }
53
+ var combined = Data()
54
+ combined.reserveCapacity(self.tail.count + data.count)
55
+ combined.append(self.tail)
56
+ combined.append(data)
57
+ if self.maxNeedle > 1 {
58
+ if combined.count >= self.maxNeedle - 1 {
59
+ self.tail = combined.suffix(self.maxNeedle - 1)
60
+ } else {
61
+ self.tail = combined
62
+ }
63
+ } else {
64
+ self.tail.removeAll(keepingCapacity: true)
65
+ }
66
+ return combined
67
+ }
68
+ }
69
+
70
+ func capture(
71
+ subcommand: String,
72
+ binary: String,
73
+ timeout: TimeInterval,
74
+ idleTimeout: TimeInterval? = 3.0,
75
+ stopOnSubstrings: [String] = [],
76
+ settleAfterStop: TimeInterval = 0.25,
77
+ sendEnterEvery: TimeInterval? = nil) async throws -> String
78
+ {
79
+ try self.ensureStarted(binary: binary)
80
+ self.isCapturing = true
81
+ self.lastActivityAt = Date()
82
+ defer {
83
+ self.isCapturing = false
84
+ self.lastActivityAt = Date()
85
+ self.scheduleIdleShutdown()
86
+ }
87
+ if let startedAt {
88
+ let sinceStart = Date().timeIntervalSince(startedAt)
89
+ if sinceStart < 0.4 {
90
+ let delay = UInt64((0.4 - sinceStart) * 1_000_000_000)
91
+ try await Task.sleep(nanoseconds: delay)
92
+ }
93
+ }
94
+ self.drainOutput()
95
+
96
+ let trimmed = subcommand.trimmingCharacters(in: .whitespacesAndNewlines)
97
+ if !trimmed.isEmpty {
98
+ try self.send(trimmed)
99
+ try self.send("\r")
100
+ }
101
+
102
+ let stopNeedles = stopOnSubstrings.map { Data($0.utf8) }
103
+ let sendNeedles = self.sendOnSubstrings.map { (needle: Data($0.key.utf8), keys: Data($0.value.utf8)) }
104
+ let cursorQuery = Data([0x1B, 0x5B, 0x36, 0x6E])
105
+ let needleLengths =
106
+ stopNeedles.map(\.count) +
107
+ sendNeedles.map(\.needle.count) +
108
+ [cursorQuery.count]
109
+ let maxNeedle = needleLengths.max() ?? cursorQuery.count
110
+ var scanBuffer = RollingBuffer(maxNeedle: maxNeedle)
111
+ var triggeredSends = Set<Data>()
112
+
113
+ var buffer = Data()
114
+ let deadline = Date().addingTimeInterval(timeout)
115
+ var lastOutputAt = Date()
116
+ var lastEnterAt = Date()
117
+ var nextCursorCheckAt = Date(timeIntervalSince1970: 0)
118
+ var stoppedEarly = false
119
+
120
+ while Date() < deadline {
121
+ let newData = self.readChunk()
122
+ if !newData.isEmpty {
123
+ buffer.append(newData)
124
+ lastOutputAt = Date()
125
+ }
126
+
127
+ let scanData = scanBuffer.append(newData)
128
+ if Date() >= nextCursorCheckAt,
129
+ !scanData.isEmpty,
130
+ scanData.range(of: cursorQuery) != nil
131
+ {
132
+ try? self.send("\u{1b}[1;1R")
133
+ nextCursorCheckAt = Date().addingTimeInterval(1.0)
134
+ }
135
+
136
+ if !sendNeedles.isEmpty {
137
+ for item in sendNeedles where !triggeredSends.contains(item.needle) {
138
+ if scanData.range(of: item.needle) != nil {
139
+ try? self.primaryHandle?.write(contentsOf: item.keys)
140
+ triggeredSends.insert(item.needle)
141
+ }
142
+ }
143
+ }
144
+
145
+ if !stopNeedles.isEmpty, stopNeedles.contains(where: { scanData.range(of: $0) != nil }) {
146
+ stoppedEarly = true
147
+ break
148
+ }
149
+
150
+ if let idleTimeout,
151
+ !buffer.isEmpty,
152
+ Date().timeIntervalSince(lastOutputAt) >= idleTimeout
153
+ {
154
+ stoppedEarly = true
155
+ break
156
+ }
157
+
158
+ if let every = sendEnterEvery, Date().timeIntervalSince(lastEnterAt) >= every {
159
+ try? self.send("\r")
160
+ lastEnterAt = Date()
161
+ }
162
+
163
+ if let proc = self.process, !proc.isRunning {
164
+ throw SessionError.processExited
165
+ }
166
+
167
+ try await Task.sleep(nanoseconds: 60_000_000)
168
+ }
169
+
170
+ if stoppedEarly {
171
+ let settle = max(0, min(settleAfterStop, deadline.timeIntervalSinceNow))
172
+ if settle > 0 {
173
+ let settleDeadline = Date().addingTimeInterval(settle)
174
+ while Date() < settleDeadline {
175
+ let newData = self.readChunk()
176
+ if !newData.isEmpty { buffer.append(newData) }
177
+ try await Task.sleep(nanoseconds: 50_000_000)
178
+ }
179
+ }
180
+ }
181
+
182
+ guard !buffer.isEmpty, let text = String(data: buffer, encoding: .utf8) else {
183
+ throw SessionError.timedOut
184
+ }
185
+ return text
186
+ }
187
+
188
+ func reset() {
189
+ self.idleShutdownTask?.cancel()
190
+ self.idleShutdownTask = nil
191
+ self.cleanup()
192
+ }
193
+
194
+ private func ensureStarted(binary: String) throws {
195
+ if let proc = self.process, proc.isRunning, self.binaryPath == binary { return }
196
+ self.cleanup()
197
+
198
+ var primaryFD: Int32 = -1
199
+ var secondaryFD: Int32 = -1
200
+ var win = winsize(ws_row: 50, ws_col: 160, ws_xpixel: 0, ws_ypixel: 0)
201
+ guard openpty(&primaryFD, &secondaryFD, nil, nil, &win) == 0 else {
202
+ throw SessionError.launchFailed("openpty failed")
203
+ }
204
+ _ = fcntl(primaryFD, F_SETFL, O_NONBLOCK)
205
+
206
+ let primaryHandle = FileHandle(fileDescriptor: primaryFD, closeOnDealloc: true)
207
+ let secondaryHandle = FileHandle(fileDescriptor: secondaryFD, closeOnDealloc: true)
208
+
209
+ let proc = Process()
210
+ let resolvedURL = URL(fileURLWithPath: binary)
211
+ if resolvedURL.lastPathComponent == "claude",
212
+ let watchdog = TTYCommandRunner.locateBundledHelper("RunicClaudeWatchdog")
213
+ {
214
+ proc.executableURL = URL(fileURLWithPath: watchdog)
215
+ proc.arguments = ["--", binary, "--allowed-tools", ""]
216
+ } else {
217
+ proc.executableURL = resolvedURL
218
+ proc.arguments = ["--allowed-tools", ""]
219
+ }
220
+ proc.standardInput = secondaryHandle
221
+ proc.standardOutput = secondaryHandle
222
+ proc.standardError = secondaryHandle
223
+
224
+ let workingDirectory = ClaudeStatusProbe.probeWorkingDirectoryURL()
225
+ proc.currentDirectoryURL = workingDirectory
226
+ var env = TTYCommandRunner.enrichedEnvironment()
227
+ env["PWD"] = workingDirectory.path
228
+ proc.environment = env
229
+
230
+ do {
231
+ try proc.run()
232
+ } catch {
233
+ try? primaryHandle.close()
234
+ try? secondaryHandle.close()
235
+ throw SessionError.launchFailed(error.localizedDescription)
236
+ }
237
+
238
+ let pid = proc.processIdentifier
239
+ var processGroup: pid_t?
240
+ if setpgid(pid, pid) == 0 {
241
+ processGroup = pid
242
+ }
243
+
244
+ self.process = proc
245
+ self.primaryFD = primaryFD
246
+ self.primaryHandle = primaryHandle
247
+ self.secondaryHandle = secondaryHandle
248
+ self.processGroup = processGroup
249
+ self.binaryPath = binary
250
+ self.startedAt = Date()
251
+ self.lastActivityAt = self.startedAt
252
+ }
253
+
254
+ private func cleanup() {
255
+ if let proc = self.process, proc.isRunning, let handle = self.primaryHandle {
256
+ try? handle.write(contentsOf: Data("/exit\n".utf8))
257
+ }
258
+ try? self.primaryHandle?.close()
259
+ try? self.secondaryHandle?.close()
260
+
261
+ if let proc = self.process, proc.isRunning {
262
+ proc.terminate()
263
+ }
264
+ if let pgid = self.processGroup {
265
+ kill(-pgid, SIGTERM)
266
+ }
267
+ let waitDeadline = Date().addingTimeInterval(1.0)
268
+ if let proc = self.process {
269
+ while proc.isRunning, Date() < waitDeadline {
270
+ usleep(100_000)
271
+ }
272
+ if proc.isRunning {
273
+ if let pgid = self.processGroup {
274
+ kill(-pgid, SIGKILL)
275
+ }
276
+ kill(proc.processIdentifier, SIGKILL)
277
+ }
278
+ }
279
+
280
+ self.process = nil
281
+ self.primaryHandle = nil
282
+ self.secondaryHandle = nil
283
+ self.primaryFD = -1
284
+ self.processGroup = nil
285
+ self.startedAt = nil
286
+ self.lastActivityAt = nil
287
+ self.isCapturing = false
288
+ }
289
+
290
+ private func readChunk() -> Data {
291
+ guard self.primaryFD >= 0 else { return Data() }
292
+ var appended = Data()
293
+ while true {
294
+ var tmp = [UInt8](repeating: 0, count: 8192)
295
+ let n = read(self.primaryFD, &tmp, tmp.count)
296
+ if n > 0 {
297
+ appended.append(contentsOf: tmp.prefix(n))
298
+ continue
299
+ }
300
+ break
301
+ }
302
+ return appended
303
+ }
304
+
305
+ private func drainOutput() {
306
+ _ = self.readChunk()
307
+ }
308
+
309
+ private func send(_ text: String) throws {
310
+ guard let data = text.data(using: .utf8) else { return }
311
+ guard let handle = self.primaryHandle else { throw SessionError.processExited }
312
+ try handle.write(contentsOf: data)
313
+ }
314
+
315
+ private func scheduleIdleShutdown() {
316
+ self.idleShutdownTask?.cancel()
317
+ let deadline = Date().addingTimeInterval(self.idleShutdownDelay)
318
+ self.idleShutdownTask = Task { [weak self] in
319
+ let delay = self?.idleShutdownDelay ?? 0
320
+ if delay > 0 {
321
+ try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
322
+ }
323
+ await self?.shutdownIfIdle(since: deadline)
324
+ }
325
+ }
326
+
327
+ private func shutdownIfIdle(since deadline: Date) {
328
+ guard !self.isCapturing else { return }
329
+ guard let lastActivityAt, lastActivityAt <= deadline else { return }
330
+ self.cleanup()
331
+ }
332
+ }