cartethyia 2.4.5 → 2.4.6

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 (303) hide show
  1. package/.env.example +4 -1
  2. package/README.md +18 -0
  3. package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
  4. package/dist/dashboard/assets/{BannedPage-DUrXpp-X.js → BannedPage-QhvbJB9o.js} +1 -1
  5. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-Bcy150Jo.js} +1 -1
  7. package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-DyNXefph.js} +1 -1
  9. package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
  10. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +1 -0
  11. package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js +1 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
  14. package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-D6jEU7nL.js} +1 -1
  15. package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
  16. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js +1 -0
  17. package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-BJlMVN4U.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
  20. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js +1 -0
  21. package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
  22. package/dist/dashboard/assets/{OverviewPage-C3NIzsDx.js → OverviewPage-CQxXbo2g.js} +1 -1
  23. package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +3 -0
  25. package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
  26. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
  27. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +1 -0
  28. package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
  29. package/dist/dashboard/assets/ProxyPage-CXiumA53.js +7 -0
  30. package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
  31. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +1 -0
  32. package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
  33. package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-BHW4Zt06.js} +1 -1
  34. package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
  35. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js +1 -0
  36. package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
  37. package/dist/dashboard/assets/SortableList-DpOTnGuH.js +1 -0
  38. package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
  39. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js +31 -0
  40. package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
  41. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  42. package/dist/dashboard/assets/{UsagePage-Djes-Epy.js → UsagePage-BAFaSDQV.js} +14 -14
  43. package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
  44. package/dist/dashboard/assets/api-keys-BETfaMYZ.js +1 -0
  45. package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
  46. package/dist/dashboard/assets/app-BLCvGdzb.css +2 -0
  47. package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
  48. package/dist/dashboard/assets/{app-BDJWMThu.js → app-Cjq7nrwu.js} +3 -3
  49. package/dist/dashboard/assets/app-Cjq7nrwu.js.gz +0 -0
  50. package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
  51. package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
  52. package/dist/dashboard/assets/{assert-Bwh6DJTK.js → assert-7J6_LVD3.js} +1 -1
  53. package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
  54. package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
  55. package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-BTyGk_DL.js} +1 -1
  56. package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
  57. package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-bujTWYM4.js} +1 -1
  58. package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
  59. package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
  60. package/dist/dashboard/assets/{card-BTdbRFFg.js → card-B0Bnx5CA.js} +1 -1
  61. package/dist/dashboard/assets/card-B0Bnx5CA.js.gz +0 -0
  62. package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
  63. package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
  64. package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
  66. package/dist/dashboard/assets/cli-tools-DE2ug812.js +1 -0
  67. package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
  68. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js +1 -0
  69. package/dist/dashboard/assets/clipboard-button-R_rArH8S.js.gz +0 -0
  70. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  71. package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
  72. package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BWIP_EvW.js} +1 -1
  73. package/dist/dashboard/assets/dialog-BWIP_EvW.js.gz +0 -0
  74. package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
  75. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  76. package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
  77. package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-BKcgVugF.js} +1 -1
  78. package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
  79. package/dist/dashboard/assets/entry-7dpgvpJ3.js +4 -0
  80. package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
  81. package/dist/dashboard/assets/entry-CcWkGR4a.js +1 -0
  82. package/dist/dashboard/assets/entry-CcWkGR4a.js.gz +0 -0
  83. package/dist/dashboard/assets/entry-kTiTFrvh.js +1 -0
  84. package/dist/dashboard/assets/entry-kTiTFrvh.js.gz +0 -0
  85. package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
  86. package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
  87. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  88. package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
  89. package/dist/dashboard/assets/github-badge-Booc5mNe.js +1 -0
  90. package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
  91. package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
  92. package/dist/dashboard/assets/{index-C4hXfAMs.js → index-D70pChfn.js} +2 -2
  93. package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
  94. package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1TCqM2D.js} +1 -1
  95. package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
  96. package/dist/dashboard/assets/{input-Bn4_kNav.js → input-B9oyU93y.js} +1 -1
  97. package/dist/dashboard/assets/input-B9oyU93y.js.gz +0 -0
  98. package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
  99. package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
  100. package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
  101. package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BArzXirQ.js} +1 -1
  102. package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
  103. package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-BVZzaoHF.js} +1 -1
  104. package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
  105. package/dist/dashboard/assets/{live-YlifPYox.js → live-EmuIayqp.js} +1 -1
  106. package/dist/dashboard/assets/live-EmuIayqp.js.gz +0 -0
  107. package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
  108. package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
  109. package/dist/dashboard/assets/{model-bans-CVvHZ-qz.js → model-bans-CSPQ9xB0.js} +1 -1
  110. package/dist/dashboard/assets/model-bans-CSPQ9xB0.js.gz +0 -0
  111. package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
  112. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  113. package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
  114. package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
  115. package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
  116. package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
  117. package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
  118. package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
  119. package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
  120. package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
  121. package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
  122. package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
  123. package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
  124. package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
  125. package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
  126. package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
  127. package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
  128. package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
  129. package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
  130. package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
  131. package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
  132. package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
  133. package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
  134. package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
  135. package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
  136. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  137. package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
  138. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  139. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  140. package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
  141. package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
  142. package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
  143. package/dist/dashboard/assets/{routing-jntw9-Ui.js → routing-BExgxxBU.js} +1 -1
  144. package/dist/dashboard/assets/routing-BExgxxBU.js.gz +0 -0
  145. package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
  146. package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
  147. package/dist/dashboard/assets/{select-CGUuTQmj.js → select-C9mMoEpJ.js} +1 -1
  148. package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
  149. package/dist/dashboard/assets/settings-C4_Gn_wq.js +1 -0
  150. package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
  151. package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
  152. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
  153. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  154. package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-Ca5HxI5i.js} +1 -1
  155. package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
  156. package/dist/dashboard/assets/{state-DHKdg-pm.js → state-CzUPfLXo.js} +1 -1
  157. package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
  158. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  159. package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-DOUIky_i.js} +1 -1
  160. package/dist/dashboard/assets/switch-DOUIky_i.js.gz +0 -0
  161. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
  162. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  163. package/dist/dashboard/assets/toast-C76WrMhc.js +2 -0
  164. package/dist/dashboard/assets/toast-C76WrMhc.js.gz +0 -0
  165. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  166. package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
  167. package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
  168. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  169. package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
  170. package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
  171. package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-D87FZwH0.js} +1 -1
  172. package/dist/dashboard/assets/useMutation-D87FZwH0.js.gz +0 -0
  173. package/dist/dashboard/assets/wrench-B6E1oEuY.js.gz +0 -0
  174. package/dist/dashboard/index.html +1 -1
  175. package/dist/dashboard/index.html.gz +0 -0
  176. package/dist/dashboard/precompressed-assets.json +123 -0
  177. package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
  178. package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
  179. package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
  180. package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
  181. package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
  182. package/package.json +25 -30
  183. package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
  184. package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
  185. package/scripts/cli/command/admin.ts +65 -0
  186. package/scripts/cli/command/console-menu.ts +227 -0
  187. package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
  188. package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
  189. package/scripts/cli/command/start.ts +13 -0
  190. package/scripts/cli/command/tray.ts +165 -0
  191. package/scripts/cli/command/update.ts +23 -0
  192. package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
  193. package/scripts/cli/gateway/spawn.ts +101 -0
  194. package/scripts/cli/gateway/stop.ts +51 -0
  195. package/scripts/cli/main.ts +94 -0
  196. package/scripts/cli/support/bun.ts +27 -0
  197. package/scripts/cli/support/env.ts +99 -0
  198. package/scripts/cli/support/platform.ts +64 -0
  199. package/scripts/cli/support/services.ts +88 -0
  200. package/scripts/cli/support/startup-signal.ts +23 -0
  201. package/scripts/cli/{version.ts → support/version.ts} +1 -1
  202. package/scripts/cli/tray/hidden-window.vbs +28 -0
  203. package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
  204. package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
  205. package/scripts/cli/ui/background.ts +49 -0
  206. package/src/boot/app.ts +3 -1
  207. package/src/boot/dependencies.ts +36 -13
  208. package/src/config/providers.ts +27 -1
  209. package/src/config/spec.ts +11 -0
  210. package/src/console/backup/routes.ts +8 -0
  211. package/src/console/cli-tools/contracts.ts +6 -6
  212. package/src/console/console-router.ts +5 -3
  213. package/src/console/dashboard-assets.ts +81 -11
  214. package/src/console/observability/contracts.ts +54 -22
  215. package/src/console/observability/routes.ts +31 -6
  216. package/src/console/observability/store.ts +68 -28
  217. package/src/console/providers/catalog/public-model-store.ts +91 -60
  218. package/src/console/providers/catalog/store.ts +5 -1
  219. package/src/console/quota/account-quota-tenant.ts +9 -6
  220. package/src/console/response-compression.ts +69 -2
  221. package/src/console/routing/pools/probe-verdict.ts +72 -0
  222. package/src/console/share/share-router.ts +7 -4
  223. package/src/console/share/share-stats.ts +12 -5
  224. package/src/console/shared/read-through-cache.ts +133 -7
  225. package/src/console/updates/repository-updates.ts +39 -34
  226. package/src/network/outbound-fetch.ts +113 -25
  227. package/src/network/tls/bun-client-hello.ts +112 -0
  228. package/src/observability/event-log.ts +29 -8
  229. package/src/observability/metrics.ts +40 -13
  230. package/src/observability/runtime-metrics.ts +7 -0
  231. package/src/persistence/schema.ts +40 -2
  232. package/src/protocol/response/codex.ts +9 -75
  233. package/src/protocol/tool-identity.ts +43 -9
  234. package/src/providers/authentication/jwt-validator.ts +130 -187
  235. package/src/providers/authentication/oauth-flow-store.ts +16 -9
  236. package/src/providers/default-registry.ts +4 -0
  237. package/src/providers/discovery/catalog/catalog-format.ts +20 -0
  238. package/src/providers/discovery/models-dev-catalog.ts +39 -22
  239. package/src/providers/discovery/probe-wire.ts +29 -5
  240. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +3 -2
  241. package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
  242. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +4 -0
  243. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
  244. package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
  245. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
  246. package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
  247. package/src/providers/integrations/buddy/codebuddy/provider.ts +2 -2
  248. package/src/providers/integrations/buddy/codebuddy/shared.ts +106 -73
  249. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
  250. package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
  251. package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
  252. package/src/providers/integrations/codex/codex.ts +5 -8
  253. package/src/providers/integrations/factory/factory-oauth.ts +7 -11
  254. package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
  255. package/src/providers/integrations/opencode/provider.ts +42 -3
  256. package/src/providers/integrations/qoder/qoder-oauth.ts +3 -9
  257. package/src/providers/integrations/shared/install-id.ts +29 -2
  258. package/src/providers/operations/client-versions.ts +11 -1
  259. package/src/providers/operations/model-catalog-cache.ts +18 -3
  260. package/src/providers/operations/provider-catalog-service.ts +76 -0
  261. package/src/transport/dispatch/attempt-finalize.ts +7 -1
  262. package/src/transport/dispatch/attempt-loop.ts +9 -7
  263. package/src/transport/dispatch/empty-stream.ts +17 -26
  264. package/src/transport/failure-policy.ts +20 -0
  265. package/src/transport/routing/route-catalog.ts +42 -3
  266. package/src/transport/surface/chat/encode.ts +18 -31
  267. package/src/transport/surface/messages/stream.ts +3 -2
  268. package/src/transport/translation/tool-repair.ts +37 -16
  269. package/src/workers/account-identity-sweep.ts +14 -12
  270. package/src/workers/daily-checkin.ts +62 -0
  271. package/src/workers/quota-refresh-sweep.ts +27 -2
  272. package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
  273. package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
  274. package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
  275. package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
  276. package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
  277. package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
  278. package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
  279. package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
  280. package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
  281. package/dist/dashboard/assets/SortableList-Bol1ASmI.js +0 -1
  282. package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
  283. package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
  284. package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
  285. package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
  286. package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
  287. package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
  288. package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
  289. package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
  290. package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
  291. package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
  292. package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
  293. package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
  294. package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
  295. package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
  296. package/scripts/cli/cli.ts +0 -771
  297. package/scripts/operations/probe-egress.ts +0 -335
  298. package/scripts/operations/restart.ts +0 -120
  299. package/scripts/shared/env.ts +0 -269
  300. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
  301. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
  302. /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
  303. /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
@@ -1,258 +1,257 @@
1
- import { accessSync, constants, existsSync, mkdirSync } from "node:fs";
2
- import {
3
- ENV_PATH,
4
- getLocalPlatform,
5
- getOsHints,
6
- hasPlaceholderSecrets,
7
- parseServiceUrl,
8
- probeTcpService,
9
- readEnvFile,
10
- } from "../shared/env";
11
- import type { ProbeResult } from "../shared/env";
12
- import { resolveDataDir, resolveDbMode, type DbMode } from "../../src/persistence/db-mode";
13
-
14
- interface DoctorConfig {
15
- envPath: string;
16
- probeTimeoutMs: number;
17
- }
18
-
19
- function loadConfig(): DoctorConfig {
20
- return {
21
- envPath: ENV_PATH,
22
- probeTimeoutMs: 3000,
23
- };
24
- }
25
-
26
- /**
27
- * Operator escape hatch: clears the console login lockout bucket.
28
- *
29
- * The lockout is keyed by `<username>:<clientIp>` and lasts an hour, and there
30
- * is no console surface to clear it — a locked-out operator has no way back in
31
- * short of editing the database by hand. This is the documented reset.
32
- *
33
- * Usage:
34
- * bun run doctor --reset-lockout <username> <clientIp>
35
- * bun run doctor --reset-lockout --all
36
- */
37
- async function resetLockout(args: readonly string[]): Promise<void> {
38
- const all = args.includes("--all");
39
- const positional = args.filter((arg) => !arg.startsWith("--"));
40
- if (!all && positional.length < 2) {
41
- console.error("Usage: bun run doctor --reset-lockout <username> <clientIp>");
42
- console.error(" bun run doctor --reset-lockout --all");
43
- process.exit(1);
44
- }
45
-
46
- const env = await readEnvFile(ENV_PATH);
47
- const databaseUrl = env.DATABASE_URL ?? process.env.DATABASE_URL;
48
- if (!databaseUrl) {
49
- console.error("✗ DATABASE_URL is not set; cannot reach the lockout table.");
50
- process.exit(1);
51
- }
52
- process.env.DATABASE_URL = databaseUrl;
53
-
54
- const { getDb, closeDb } = await import("../../src/persistence/boot");
55
- const { consoleLockouts } = await import("../../src/persistence/schema");
56
- const { eq, or } = await import("drizzle-orm");
57
-
58
- const db = getDb();
59
- try {
60
- if (all) {
61
- const cleared = await db.delete(consoleLockouts).returning({ ip: consoleLockouts.ip });
62
- console.log(`✓ Cleared ${cleared.length} lockout row(s).`);
63
- return;
64
- }
65
- const username = positional[0]!.trim().toLowerCase();
66
- const clientIp = positional[1]!;
67
- // The bucket key is `<identifier>:<ip>`; match either exact spelling so
68
- // an operator does not have to guess how the identifier was normalized.
69
- const cleared = await db
70
- .delete(consoleLockouts)
71
- .where(or(eq(consoleLockouts.ip, `${username}:${clientIp}`), eq(consoleLockouts.ip, clientIp)))
72
- .returning({ ip: consoleLockouts.ip });
73
- if (cleared.length === 0) {
74
- console.log(`• No lockout row for ${username}:${clientIp} (already clear).`);
75
- } else {
76
- console.log(`✓ Cleared lockout for ${cleared.map((row) => row.ip).join(", ")}.`);
77
- }
78
- } finally {
79
- await closeDb();
80
- }
81
- }
82
-
83
-
84
- async function probeReadiness(port: number, timeoutMs: number): Promise<ProbeResult> {
85
- const controller = new AbortController();
86
- const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
87
-
88
- try {
89
- const response = await fetch(`http://localhost:${port}/health/ready`, {
90
- signal: controller.signal,
91
- });
92
-
93
- clearTimeout(timeoutHandle);
94
-
95
- if (response.ok) {
96
- return { success: true };
97
- }
98
- return { success: false, error: `HTTP ${response.status}` };
99
- } catch (err) {
100
- clearTimeout(timeoutHandle);
101
- return { success: false, error: String(err) };
102
- }
103
- }
104
-
105
- async function doctor(): Promise<void> {
106
- const config = loadConfig();
107
-
108
- const resetIndex = process.argv.indexOf("--reset-lockout");
109
- if (resetIndex !== -1) {
110
- await resetLockout(process.argv.slice(resetIndex + 1));
111
- return;
112
- }
113
-
114
- console.log("🩺 Cartethyia Health Check\n");
115
- console.log(`🖥️ Host platform: ${getLocalPlatform()}`);
116
-
117
- // Step 1: Check .env exists
118
- console.log("📋 Environment Configuration");
119
- if (!existsSync(config.envPath)) {
120
- console.log(" ✗ .env file not found");
121
- console.log(" Run: bun setup\n");
122
- process.exit(1);
123
- }
124
- console.log(" ✓ .env file exists");
125
-
126
- // Step 2: Check required environment variables. DATABASE_URL is required in
127
- // full mode only; lite keeps its database in the data dir. Redis is never
128
- // required — unset means the in-memory backend.
129
- const envVars = await readEnvFile(config.envPath);
130
- // `CARTETHYIA_DB_MODE` has no default and throws when absent, which is the
131
- // right answer at boot but the wrong one here: this command exists to report
132
- // a broken environment readably, so the missing key joins the checklist
133
- // instead of crashing before it prints.
134
- const missing: string[] = [];
135
- let dbMode: DbMode | null = null;
136
- try {
137
- dbMode = resolveDbMode({ ...process.env, ...envVars });
138
- } catch {
139
- console.log(" ✗ CARTETHYIA_DB_MODE is not set — run: bun setup");
140
- // Counted as missing so this command exits non-zero. A boot would throw on
141
- // the same key, so a green doctor here would be a false all-clear.
142
- missing.push("CARTETHYIA_DB_MODE");
143
- }
144
- const required =
145
- dbMode === "full"
146
- ? ["DATABASE_URL", "CARTETHYIA_ENCRYPTION_KEY"]
147
- : ["CARTETHYIA_ENCRYPTION_KEY"];
148
-
149
- for (const key of required) {
150
- const value = envVars[key] ?? process.env[key];
151
- if (!value) {
152
- missing.push(key);
153
- console.log(` ✗ ${key} is not set`);
154
- } else if (hasPlaceholderSecrets(value)) {
155
- console.log(` ⚠️ ${key} has placeholder value — update before production`);
156
- } else {
157
- console.log(` ✓ ${key} is set`);
158
- }
159
- }
160
-
161
- if (missing.length > 0) {
162
- console.log(
163
- `\nFailed: ${missing.length} required environment variable(s) missing or using placeholders\n`,
164
- );
165
- process.exit(1);
166
- }
167
-
168
- console.log();
169
-
170
- // Step 3: Check database. Full mode probes the TCP service; lite verifies
171
- // the data dir is writable (the SQLite backend creates the file at boot too).
172
- if (dbMode === "lite") {
173
- const dataDir = resolveDataDir({ ...process.env, ...envVars });
174
- try {
175
- mkdirSync(dataDir, { recursive: true });
176
- accessSync(dataDir, constants.W_OK);
177
- console.log(` ✓ Lite data dir is writable (${dataDir})`);
178
- } catch {
179
- console.log(` ✗ Lite data dir is not writable (${dataDir})`);
180
- process.exit(1);
181
- }
182
- } else {
183
- const dbUrl = envVars.DATABASE_URL ?? process.env.DATABASE_URL;
184
- const dbParsed = parseServiceUrl(dbUrl ?? "");
185
-
186
- if (!dbParsed) {
187
- console.log(" ✗ Invalid DATABASE_URL format");
188
- process.exit(1);
189
- }
190
-
191
- const dbResult = await probeTcpService(dbParsed.host, dbParsed.port, config.probeTimeoutMs);
192
- if (dbResult.success) {
193
- console.log(` ✓ Reachable at ${dbParsed.host}:${dbParsed.port}`);
194
- } else {
195
- console.log(` ✗ Not reachable at ${dbParsed.host}:${dbParsed.port}`);
196
- console.log(` Error: ${dbResult.error || "connection refused"}`);
197
- for (const hint of getOsHints("postgres")) console.log(` ${hint}`);
198
- process.exit(1);
199
- }
200
- }
201
-
202
- console.log();
203
-
204
- // Step 4: Check Redis connectivity — informational only. A configured URL
205
- // is probed; unset means the in-memory backend, which is always up.
206
- console.log("🔴 Redis");
207
- const redisUrl = envVars.REDIS_URL ?? process.env.REDIS_URL;
208
- if (!redisUrl) {
209
- console.log(" ✓ in-memory backend (REDIS_URL not set)");
210
- } else {
211
- const redisParsed = parseServiceUrl(redisUrl);
212
- if (!redisParsed) {
213
- console.log(" ✗ REDIS_URL is invalid");
214
- process.exit(1);
215
- }
216
- const redisResult = await probeTcpService(
217
- redisParsed.host,
218
- redisParsed.port,
219
- config.probeTimeoutMs,
220
- );
221
- if (redisResult.success) {
222
- console.log(` ✓ Reachable at ${redisParsed.host}:${redisParsed.port}`);
223
- } else {
224
- console.log(` ✗ Not reachable at ${redisParsed.host}:${redisParsed.port}`);
225
- console.log(` Error: ${redisResult.error || "connection refused"}`);
226
- for (const hint of getOsHints("redis")) console.log(` ${hint}`);
227
- process.exit(1);
228
- }
229
- }
230
-
231
- console.log();
232
-
233
- // Step 5: Check application readiness
234
- console.log("🏥 Application Readiness");
235
- // Use the same explicit PORT resolution as the application.
236
- const port = Number(envVars.PORT ?? process.env.PORT ?? "12800");
237
- console.log(` Checking http://localhost:${port}/health/ready...`);
238
-
239
- const readinessResult = await probeReadiness(port, config.probeTimeoutMs);
240
- if (readinessResult.success) {
241
- console.log(` ✓ Application is ready`);
242
- } else {
243
- console.log(` ✗ Application is not ready`);
244
- console.log(` Error: ${readinessResult.error || "connection refused"}`);
245
- console.log("");
246
- console.log("Make sure the application is running:");
247
- console.log(` bun run dev`);
248
- process.exit(1);
249
- }
250
-
251
- console.log();
252
- console.log("✅ All systems operational!");
253
- }
254
-
255
- doctor().catch((err) => {
256
- console.error("❌ Health check failed:", err);
257
- process.exit(1);
258
- });
1
+ import { accessSync, constants, existsSync, mkdirSync } from "node:fs";
2
+ import { ENV_PATH, hasPlaceholderSecrets, readEnvFile } from "../support/env";
3
+ import { getLocalPlatform, getOsHints } from "../support/platform";
4
+ import { parseServiceUrl, probeTcpService } from "../support/services";
5
+ import type { ProbeResult } from "../support/services";
6
+ import { resolveDataDir, resolveDbMode, type DbMode } from "../../../src/persistence/db-mode";
7
+
8
+ interface DoctorConfig {
9
+ envPath: string;
10
+ probeTimeoutMs: number;
11
+ }
12
+
13
+ function loadConfig(): DoctorConfig {
14
+ return {
15
+ envPath: ENV_PATH,
16
+ probeTimeoutMs: 3000,
17
+ };
18
+ }
19
+
20
+ /**
21
+ * Operator escape hatch: clears the console login lockout bucket.
22
+ *
23
+ * The lockout is keyed by `<username>:<clientIp>` and lasts an hour, and there
24
+ * is no console surface to clear it — a locked-out operator has no way back in
25
+ * short of editing the database by hand. This is the documented reset.
26
+ *
27
+ * Usage:
28
+ * bun run doctor --reset-lockout <username> <clientIp>
29
+ * bun run doctor --reset-lockout --all
30
+ */
31
+ async function resetLockout(args: readonly string[]): Promise<void> {
32
+ const all = args.includes("--all");
33
+ const positional = args.filter((arg) => !arg.startsWith("--"));
34
+ if (!all && positional.length < 2) {
35
+ console.error("Usage: bun run doctor --reset-lockout <username> <clientIp>");
36
+ console.error(" bun run doctor --reset-lockout --all");
37
+ process.exit(1);
38
+ }
39
+
40
+ const env = await readEnvFile(ENV_PATH);
41
+ const databaseUrl = env.DATABASE_URL ?? process.env.DATABASE_URL;
42
+ if (!databaseUrl) {
43
+ console.error("✗ DATABASE_URL is not set; cannot reach the lockout table.");
44
+ process.exit(1);
45
+ }
46
+ process.env.DATABASE_URL = databaseUrl;
47
+
48
+ const { getDb, closeDb } = await import("../../../src/persistence/boot");
49
+ const { consoleLockouts } = await import("../../../src/persistence/schema");
50
+ const { eq, or } = await import("drizzle-orm");
51
+
52
+ const db = getDb();
53
+ try {
54
+ if (all) {
55
+ const cleared = await db.delete(consoleLockouts).returning({ ip: consoleLockouts.ip });
56
+ console.log(`✓ Cleared ${cleared.length} lockout row(s).`);
57
+ return;
58
+ }
59
+ const username = positional[0]!.trim().toLowerCase();
60
+ const clientIp = positional[1]!;
61
+ // The bucket key is `<identifier>:<ip>`; match either exact spelling so
62
+ // an operator does not have to guess how the identifier was normalized.
63
+ const cleared = await db
64
+ .delete(consoleLockouts)
65
+ .where(or(eq(consoleLockouts.ip, `${username}:${clientIp}`), eq(consoleLockouts.ip, clientIp)))
66
+ .returning({ ip: consoleLockouts.ip });
67
+ if (cleared.length === 0) {
68
+ console.log(`• No lockout row for ${username}:${clientIp} (already clear).`);
69
+ } else {
70
+ console.log(`✓ Cleared lockout for ${cleared.map((row) => row.ip).join(", ")}.`);
71
+ }
72
+ } finally {
73
+ await closeDb();
74
+ }
75
+ }
76
+
77
+
78
+ async function probeReadiness(port: number, timeoutMs: number): Promise<ProbeResult> {
79
+ const controller = new AbortController();
80
+ const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
81
+
82
+ try {
83
+ const response = await fetch(`http://localhost:${port}/health/ready`, {
84
+ signal: controller.signal,
85
+ });
86
+
87
+ clearTimeout(timeoutHandle);
88
+
89
+ if (response.ok) {
90
+ return { success: true };
91
+ }
92
+ return { success: false, error: `HTTP ${response.status}` };
93
+ } catch (err) {
94
+ clearTimeout(timeoutHandle);
95
+ return { success: false, error: String(err) };
96
+ }
97
+ }
98
+
99
+ /** Runs the health check and the lockout reset; the CLI and `bun run doctor` both call this. */
100
+ export async function doctor(): Promise<void> {
101
+ const config = loadConfig();
102
+
103
+ const resetIndex = process.argv.indexOf("--reset-lockout");
104
+ if (resetIndex !== -1) {
105
+ await resetLockout(process.argv.slice(resetIndex + 1));
106
+ return;
107
+ }
108
+
109
+ console.log("🩺 Cartethyia Health Check\n");
110
+ console.log(`🖥️ Host platform: ${getLocalPlatform()}`);
111
+
112
+ // Step 1: Check .env exists
113
+ console.log("📋 Environment Configuration");
114
+ if (!existsSync(config.envPath)) {
115
+ console.log(" ✗ .env file not found");
116
+ console.log(" Run: bun setup\n");
117
+ process.exit(1);
118
+ }
119
+ console.log(" ✓ .env file exists");
120
+
121
+ // Step 2: Check required environment variables. DATABASE_URL is required in
122
+ // full mode only; lite keeps its database in the data dir. Redis is never
123
+ // required — unset means the in-memory backend.
124
+ const envVars = await readEnvFile(config.envPath);
125
+ // `CARTETHYIA_DB_MODE` has no default and throws when absent, which is the
126
+ // right answer at boot but the wrong one here: this command exists to report
127
+ // a broken environment readably, so the missing key joins the checklist
128
+ // instead of crashing before it prints.
129
+ const missing: string[] = [];
130
+ let dbMode: DbMode | null = null;
131
+ try {
132
+ dbMode = resolveDbMode({ ...process.env, ...envVars });
133
+ } catch {
134
+ console.log(" ✗ CARTETHYIA_DB_MODE is not set — run: bun setup");
135
+ // Counted as missing so this command exits non-zero. A boot would throw on
136
+ // the same key, so a green doctor here would be a false all-clear.
137
+ missing.push("CARTETHYIA_DB_MODE");
138
+ }
139
+ const required =
140
+ dbMode === "full"
141
+ ? ["DATABASE_URL", "CARTETHYIA_ENCRYPTION_KEY"]
142
+ : ["CARTETHYIA_ENCRYPTION_KEY"];
143
+
144
+ for (const key of required) {
145
+ const value = envVars[key] ?? process.env[key];
146
+ if (!value) {
147
+ missing.push(key);
148
+ console.log(` ✗ ${key} is not set`);
149
+ } else if (hasPlaceholderSecrets(value)) {
150
+ console.log(` ⚠️ ${key} has placeholder value — update before production`);
151
+ } else {
152
+ console.log(` ✓ ${key} is set`);
153
+ }
154
+ }
155
+
156
+ if (missing.length > 0) {
157
+ console.log(
158
+ `\nFailed: ${missing.length} required environment variable(s) missing or using placeholders\n`,
159
+ );
160
+ process.exit(1);
161
+ }
162
+
163
+ console.log();
164
+
165
+ // Step 3: Check database. Full mode probes the TCP service; lite verifies
166
+ // the data dir is writable (the SQLite backend creates the file at boot too).
167
+ if (dbMode === "lite") {
168
+ const dataDir = resolveDataDir({ ...process.env, ...envVars });
169
+ try {
170
+ mkdirSync(dataDir, { recursive: true });
171
+ accessSync(dataDir, constants.W_OK);
172
+ console.log(` ✓ Lite data dir is writable (${dataDir})`);
173
+ } catch {
174
+ console.log(` ✗ Lite data dir is not writable (${dataDir})`);
175
+ process.exit(1);
176
+ }
177
+ } else {
178
+ const dbUrl = envVars.DATABASE_URL ?? process.env.DATABASE_URL;
179
+ const dbParsed = parseServiceUrl(dbUrl ?? "");
180
+
181
+ if (!dbParsed) {
182
+ console.log(" ✗ Invalid DATABASE_URL format");
183
+ process.exit(1);
184
+ }
185
+
186
+ const dbResult = await probeTcpService(dbParsed.host, dbParsed.port, config.probeTimeoutMs);
187
+ if (dbResult.success) {
188
+ console.log(` ✓ Reachable at ${dbParsed.host}:${dbParsed.port}`);
189
+ } else {
190
+ console.log(` ✗ Not reachable at ${dbParsed.host}:${dbParsed.port}`);
191
+ console.log(` Error: ${dbResult.error || "connection refused"}`);
192
+ for (const hint of getOsHints("postgres")) console.log(` ${hint}`);
193
+ process.exit(1);
194
+ }
195
+ }
196
+
197
+ console.log();
198
+
199
+ // Step 4: Check Redis connectivity — informational only. A configured URL
200
+ // is probed; unset means the in-memory backend, which is always up.
201
+ console.log("🔴 Redis");
202
+ const redisUrl = envVars.REDIS_URL ?? process.env.REDIS_URL;
203
+ if (!redisUrl) {
204
+ console.log(" ✓ in-memory backend (REDIS_URL not set)");
205
+ } else {
206
+ const redisParsed = parseServiceUrl(redisUrl);
207
+ if (!redisParsed) {
208
+ console.log(" ✗ REDIS_URL is invalid");
209
+ process.exit(1);
210
+ }
211
+ const redisResult = await probeTcpService(
212
+ redisParsed.host,
213
+ redisParsed.port,
214
+ config.probeTimeoutMs,
215
+ );
216
+ if (redisResult.success) {
217
+ console.log(` ✓ Reachable at ${redisParsed.host}:${redisParsed.port}`);
218
+ } else {
219
+ console.log(` ✗ Not reachable at ${redisParsed.host}:${redisParsed.port}`);
220
+ console.log(` Error: ${redisResult.error || "connection refused"}`);
221
+ for (const hint of getOsHints("redis")) console.log(` ${hint}`);
222
+ process.exit(1);
223
+ }
224
+ }
225
+
226
+ console.log();
227
+
228
+ // Step 5: Check application readiness
229
+ console.log("🏥 Application Readiness");
230
+ // Use the same explicit PORT resolution as the application.
231
+ const port = Number(envVars.PORT ?? process.env.PORT ?? "12800");
232
+ console.log(` Checking http://localhost:${port}/health/ready...`);
233
+
234
+ const readinessResult = await probeReadiness(port, config.probeTimeoutMs);
235
+ if (readinessResult.success) {
236
+ console.log(` ✓ Application is ready`);
237
+ } else {
238
+ console.log(` ✗ Application is not ready`);
239
+ console.log(` Error: ${readinessResult.error || "connection refused"}`);
240
+ console.log("");
241
+ console.log("Make sure the application is running:");
242
+ console.log(` bun run dev`);
243
+ process.exit(1);
244
+ }
245
+
246
+ console.log();
247
+ console.log("✅ All systems operational!");
248
+ }
249
+
250
+ // `bun run doctor` still works: the module is a script when run directly and a
251
+ // command when imported by the CLI.
252
+ if (import.meta.main) {
253
+ doctor().catch((err) => {
254
+ console.error("❌ Health check failed:", err);
255
+ process.exit(1);
256
+ });
257
+ }
@@ -4,23 +4,19 @@ import { existsSync, mkdirSync } from "node:fs";
4
4
  import { readFile, writeFile } from "node:fs/promises";
5
5
  import { dirname, resolve } from "node:path";
6
6
  import { createServer } from "node:net";
7
+ import { MINIMUM_BUN_VERSION, bunVersionAtLeast } from "../support/bun";
7
8
  import {
8
- MINIMUM_BUN_VERSION,
9
- bunVersionAtLeast,
10
- detectLocalPostgres,
9
+ ENV_PATH,
10
+ PROJECT_ROOT,
11
11
  generateCartethyiaEncryptionKey,
12
- getLocalPlatform,
13
- getOsHints,
14
12
  hasPlaceholderSecrets,
15
13
  mandatoryEnvBody,
16
- parseServiceUrl,
17
- probeTcpService,
18
14
  readEnvFile,
19
- ENV_PATH,
20
- PROJECT_ROOT,
21
- } from "../shared/env";
22
- import type { ProbeResult } from "../shared/env";
23
- import { resolveDataDir, resolveDbMode } from "../../src/persistence/db-mode";
15
+ } from "../support/env";
16
+ import { getLocalPlatform, getOsHints } from "../support/platform";
17
+ import { detectLocalPostgres, parseServiceUrl, probeTcpService } from "../support/services";
18
+ import type { ProbeResult } from "../support/services";
19
+ import { resolveDataDir, resolveDbMode } from "../../../src/persistence/db-mode";
24
20
 
25
21
  async function ask(question: string, defaultValue = ""): Promise<string> {
26
22
  const rl = createInterface({ input, output });
@@ -0,0 +1,13 @@
1
+ import { ensureDrainToken } from "../gateway/drain-token";
2
+
3
+ /**
4
+ * Runs the gateway in the foreground, in this process.
5
+ *
6
+ * A foreground run still needs a drain token on disk: the gateway registers
7
+ * `/admin/drain` only when one is set, and "Hide to tray" hands this run to a
8
+ * tray that drains it through that route.
9
+ */
10
+ export async function startForeground(): Promise<void> {
11
+ process.env.CARTETHYIA_DRAIN_TOKEN ??= ensureDrainToken();
12
+ await import("../../../src/main");
13
+ }