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,134 +1,167 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const fs = require("node:fs");
5
- const { randomUUID } = require("node:crypto");
6
- const os = require("node:os");
7
- const path = require("node:path");
8
- const { spawn } = require("node:child_process");
9
-
10
- const packageRoot = path.resolve(__dirname, "..", "..");
11
- const cliPath = path.join(packageRoot, "scripts", "cli", "cli.ts");
12
- const configDir = process.env.CARTETHYIA_CONFIG_DIR || (
13
- process.platform === "win32"
14
- ? path.join(process.env.APPDATA || os.homedir(), "Cartethyia")
15
- : process.platform === "darwin"
16
- ? path.join(os.homedir(), "Library", "Application Support", "Cartethyia")
17
- : path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "Cartethyia")
18
- );
19
- const trayMode = process.argv[2] === "tray";
20
- const startupSignalPath = trayMode
21
- ? path.join(os.tmpdir(), `cartethyia-tray-${process.pid}-${randomUUID()}.json`)
22
- : null;
23
- let spawnError;
24
- // On Windows, `cmd /c start "" /b` gives bun its own console group so the
25
- // launching terminal can close without signalling the gateway. Elsewhere
26
- // `detached: true` is enough. Either way the child is unref'd below so the
27
- // launcher can exit without taking the gateway down with it.
28
- const useWindowsDetach = process.platform === "win32" && trayMode;
29
- const child = useWindowsDetach
30
- ? spawn("cmd", ["/c", "start", "", "/b", "bun", "run", cliPath, ...process.argv.slice(2)], {
31
- cwd: packageRoot,
32
- detached: true,
33
- stdio: "ignore",
34
- windowsHide: true,
35
- env: {
36
- ...process.env,
37
- CARTETHYIA_CONFIG_DIR: configDir,
38
- CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath,
39
- },
40
- })
41
- : spawn("bun", ["run", cliPath, ...process.argv.slice(2)], {
42
- cwd: packageRoot,
43
- detached: trayMode,
44
- stdio: trayMode ? "ignore" : "inherit",
45
- windowsHide: trayMode,
46
- env: {
47
- ...process.env,
48
- CARTETHYIA_CONFIG_DIR: configDir,
49
- ...(trayMode ? { CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath } : {}),
50
- },
51
- });
52
-
53
- child.once("error", (error) => {
54
- spawnError = error;
55
- if (error.code === "ENOENT") {
56
- console.error("Cartethyia requires Bun 1.4.0 or newer. Install Bun from https://bun.sh, then run this command again.");
57
- } else {
58
- console.error(`Could not start Cartethyia's Bun runtime: ${error.message}`);
59
- }
60
- process.exitCode = 1;
61
- });
62
-
63
- if (trayMode) {
64
- child.unref();
65
- console.log("Loading Cartethyia…");
66
- let settled = false;
67
- const deadline = Date.now() + 60_000;
68
- const finish = (error, url) => {
69
- if (settled) return;
70
- settled = true;
71
- try {
72
- fs.unlinkSync(startupSignalPath);
73
- } catch {}
74
- if (error) {
75
- // `error.message` can be an empty string when the failure came from a
76
- // signal rather than a thrown message, which printed as a bare
77
- // "Cartethyia failed to start:" with no cause. Fall back to the code and
78
- // name what usually causes it.
79
- const detail =
80
- error.message && error.message.trim() !== ""
81
- ? error.message
82
- : `the process exited without a message (code ${child.exitCode ?? "unknown"})`;
83
- console.error(`Cartethyia failed to start: ${detail}`);
84
- console.error(
85
- "If another Cartethyia is already running on this port, stop it with `cartethyia stop` first.",
86
- );
87
- process.exitCode = 1;
88
- if (child.exitCode === null) child.kill();
89
- return;
90
- }
91
- console.log(`Cartethyia is ready at ${url}`);
92
- // The gateway child is already detached and unref'd; this Node process is
93
- // the only thing still holding the launching terminal open. Exit once the
94
- // URL has been shown so the shell prompt comes back.
95
- setTimeout(() => process.exit(0), 1_500);
96
- };
97
- const poll = () => {
98
- if (settled) return;
99
- if (fs.existsSync(startupSignalPath)) {
100
- try {
101
- const signal = JSON.parse(fs.readFileSync(startupSignalPath, "utf8"));
102
- if (signal.status === "ready" && typeof signal.url === "string") {
103
- finish(null, signal.url);
104
- return;
105
- }
106
- if (signal.status === "failed" && typeof signal.message === "string") {
107
- finish(new Error(signal.message));
108
- return;
109
- }
110
- } catch {}
111
- }
112
- if (spawnError) {
113
- finish(spawnError);
114
- return;
115
- }
116
- if (Date.now() >= deadline) {
117
- finish(new Error("startup did not complete within 60 seconds"));
118
- return;
119
- }
120
- setTimeout(poll, 150);
121
- };
122
- child.once("exit", (code, signal) => {
123
- if (!settled) setTimeout(() => finish(new Error(`the tray process exited before readiness (code ${code ?? signal ?? "unknown"})`)), 250);
124
- });
125
- poll();
126
- } else {
127
- child.once("exit", (code, signal) => {
128
- if (signal) {
129
- process.kill(process.pid, signal);
130
- return;
131
- }
132
- process.exitCode = code ?? 1;
133
- });
134
- }
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const { randomUUID } = require("node:crypto");
6
+ const os = require("node:os");
7
+ const path = require("node:path");
8
+ const { spawn } = require("node:child_process");
9
+
10
+ const packageRoot = path.resolve(__dirname, "..", "..", "..");
11
+ const cliPath = path.join(packageRoot, "scripts", "cli", "main.ts");
12
+ const configDir = process.env.CARTETHYIA_CONFIG_DIR || (
13
+ process.platform === "win32"
14
+ ? path.join(process.env.APPDATA || os.homedir(), "Cartethyia")
15
+ : process.platform === "darwin"
16
+ ? path.join(os.homedir(), "Library", "Application Support", "Cartethyia")
17
+ : path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "Cartethyia")
18
+ );
19
+ const trayMode = process.argv[2] === "tray";
20
+ // The launcher itself runs under Node, so `process.execPath` is node.exe — not
21
+ // the Bun the child needs. Resolve bun from PATH instead; WScript.Shell does
22
+ // not inherit this process's environment, so the child needs an absolute path.
23
+ function resolveBunPath() {
24
+ if (process.env.CARTETHYIA_BUN) return process.env.CARTETHYIA_BUN;
25
+ const name = process.platform === "win32" ? "bun.exe" : "bun";
26
+ for (const dir of (process.env.PATH || "").split(path.delimiter)) {
27
+ if (!dir) continue;
28
+ const candidate = path.join(dir, name);
29
+ try {
30
+ if (fs.statSync(candidate).isFile()) return candidate;
31
+ } catch {}
32
+ }
33
+ return name;
34
+ }
35
+
36
+ const startupSignalPath = trayMode
37
+ ? path.join(os.tmpdir(), `cartethyia-tray-${process.pid}-${randomUUID()}.json`)
38
+ : null;
39
+ let spawnError;
40
+ // On Windows the tray must start with no console at all, and neither
41
+ // `detached: true` nor `windowsHide: true` achieves that: a console-subsystem
42
+ // child of a window-less parent still allocates a console, and inside Windows
43
+ // Terminal the tab is already on screen before a flag can take effect
44
+ // (measured). `wscript.exe` is GUI-subsystem, so it never allocates one, and a
45
+ // child launched from it with window style 0 inherits that. Elsewhere
46
+ // `detached: true` is enough.
47
+ const useWindowsDetach = process.platform === "win32" && trayMode;
48
+ const hiddenLauncher = path.join(packageRoot, "scripts", "cli", "tray", "hidden-window.vbs");
49
+ const child = useWindowsDetach
50
+ ? spawn(
51
+ "wscript.exe",
52
+ // Absolute path, not "bun": the VBS runs through WScript.Shell, which
53
+ // does not inherit this process's PATH additions and resolves a bare
54
+ // name against the system PATH only.
55
+ ["//nologo", hiddenLauncher, resolveBunPath(), "run", cliPath, ...process.argv.slice(2)],
56
+ {
57
+ cwd: packageRoot,
58
+ detached: true,
59
+ stdio: "ignore",
60
+ windowsHide: true,
61
+ env: {
62
+ ...process.env,
63
+ CARTETHYIA_CONFIG_DIR: configDir,
64
+ CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath,
65
+ },
66
+ },
67
+ )
68
+ : spawn(resolveBunPath(), ["run", cliPath, ...process.argv.slice(2)], {
69
+ cwd: packageRoot,
70
+ detached: trayMode,
71
+ stdio: trayMode ? "ignore" : "inherit",
72
+ windowsHide: trayMode,
73
+ env: {
74
+ ...process.env,
75
+ CARTETHYIA_CONFIG_DIR: configDir,
76
+ ...(trayMode ? { CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath } : {}),
77
+ },
78
+ });
79
+
80
+ child.once("error", (error) => {
81
+ spawnError = error;
82
+ if (error.code === "ENOENT") {
83
+ console.error("Cartethyia requires Bun 1.4.0 or newer. Install Bun from https://bun.sh, then run this command again.");
84
+ } else {
85
+ console.error(`Could not start Cartethyia's Bun runtime: ${error.message}`);
86
+ }
87
+ process.exitCode = 1;
88
+ });
89
+
90
+ if (trayMode) {
91
+ child.unref();
92
+ console.log("Loading Cartethyia…");
93
+ let settled = false;
94
+ const deadline = Date.now() + 60_000;
95
+ const finish = (error, url) => {
96
+ if (settled) return;
97
+ settled = true;
98
+ try {
99
+ fs.unlinkSync(startupSignalPath);
100
+ } catch {}
101
+ if (error) {
102
+ // `error.message` can be an empty string when the failure came from a
103
+ // signal rather than a thrown message, which printed as a bare
104
+ // "Cartethyia failed to start:" with no cause. Fall back to the code and
105
+ // name what usually causes it.
106
+ const detail =
107
+ error.message && error.message.trim() !== ""
108
+ ? error.message
109
+ : `the process exited without a message (code ${child.exitCode ?? "unknown"})`;
110
+ console.error(`Cartethyia failed to start: ${detail}`);
111
+ console.error(
112
+ "If another Cartethyia is already running on this port, stop the terminal that started it first.",
113
+ );
114
+ process.exitCode = 1;
115
+ if (child.exitCode === null) child.kill();
116
+ return;
117
+ }
118
+ console.log(`Cartethyia is ready at ${url}`);
119
+ // The gateway child is already detached and unref'd; this Node process is
120
+ // the only thing still holding the launching terminal open. Exit once the
121
+ // URL has been shown so the shell prompt comes back.
122
+ setTimeout(() => process.exit(0), 1_500);
123
+ };
124
+ const poll = () => {
125
+ if (settled) return;
126
+ if (fs.existsSync(startupSignalPath)) {
127
+ try {
128
+ const signal = JSON.parse(fs.readFileSync(startupSignalPath, "utf8"));
129
+ if (signal.status === "ready" && typeof signal.url === "string") {
130
+ finish(null, signal.url);
131
+ return;
132
+ }
133
+ if (signal.status === "failed" && typeof signal.message === "string") {
134
+ finish(new Error(signal.message));
135
+ return;
136
+ }
137
+ } catch {}
138
+ }
139
+ if (spawnError) {
140
+ finish(spawnError);
141
+ return;
142
+ }
143
+ if (Date.now() >= deadline) {
144
+ finish(new Error("startup did not complete within 60 seconds"));
145
+ return;
146
+ }
147
+ setTimeout(poll, 150);
148
+ };
149
+ // On Windows the spawned process is `wscript.exe`, which hands off to the
150
+ // tray and exits immediately — a successful handoff, not a failed start. Only
151
+ // a direct spawn's exit says anything about the tray, so the deadline covers
152
+ // the handoff case and this guard covers the direct one.
153
+ if (!useWindowsDetach) {
154
+ child.once("exit", (code, signal) => {
155
+ if (!settled) setTimeout(() => finish(new Error(`the tray process exited before readiness (code ${code ?? signal ?? "unknown"})`)), 250);
156
+ });
157
+ }
158
+ poll();
159
+ } else {
160
+ child.once("exit", (code, signal) => {
161
+ if (signal) {
162
+ process.kill(process.pid, signal);
163
+ return;
164
+ }
165
+ process.exitCode = code ?? 1;
166
+ });
167
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The two admin actions the menu and the tray both offer.
3
+ *
4
+ * Both go through the running gateway rather than the database file: the
5
+ * gateway holds the connection and the schema is dialect-dependent, so reading
6
+ * or writing the tables from this process would need a second implementation
7
+ * of both.
8
+ */
9
+
10
+ /**
11
+ * Asks the gateway to write a gzipped config backup into the download
12
+ * directory. Returns the written path and counts, or null when it failed.
13
+ */
14
+ export async function exportBackup(
15
+ port: number,
16
+ token: string,
17
+ ): Promise<{ path: string; bytes: number; counts: Record<string, number> } | null> {
18
+ try {
19
+ const response = await fetch(`http://127.0.0.1:${port}/admin/export-backup`, {
20
+ method: "POST",
21
+ headers: { "content-type": "application/json", "x-drain-token": token },
22
+ body: JSON.stringify({}),
23
+ signal: AbortSignal.timeout(60_000),
24
+ });
25
+ if (!response.ok) {
26
+ const detail = await response.text().catch(() => "");
27
+ console.error(`The gateway refused the backup (HTTP ${response.status}). ${detail}`.trim());
28
+ return null;
29
+ }
30
+ return (await response.json()) as {
31
+ path: string;
32
+ bytes: number;
33
+ counts: Record<string, number>;
34
+ };
35
+ } catch {
36
+ console.error("Could not reach the gateway. Is it still running?");
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Posts the console-account reset to the running gateway.
43
+ *
44
+ * Returns 0 on success and 1 on any failure so the caller can print one line
45
+ * either way.
46
+ */
47
+ export async function resetConsoleAccount(port: number, token: string): Promise<number> {
48
+ try {
49
+ const response = await fetch(`http://127.0.0.1:${port}/admin/reset-console-account`, {
50
+ method: "POST",
51
+ headers: { "content-type": "application/json", "x-drain-token": token },
52
+ body: JSON.stringify({ confirm: "RESET" }),
53
+ signal: AbortSignal.timeout(10_000),
54
+ });
55
+ if (!response.ok) {
56
+ const detail = await response.text().catch(() => "");
57
+ console.error(`The gateway refused the reset (HTTP ${response.status}). ${detail}`.trim());
58
+ return 1;
59
+ }
60
+ return 0;
61
+ } catch {
62
+ console.error("Could not reach the gateway. Is it still running?");
63
+ return 1;
64
+ }
65
+ }
@@ -0,0 +1,227 @@
1
+ import { existsSync } from "node:fs";
2
+ import { isNewerRelease } from "../../../src/console/updates/version-compare";
3
+ import { resolvePackagePath } from "../../../src/config/package-paths";
4
+ import { resolvePort } from "../../../src/config/runtime";
5
+ import { ensureDrainToken } from "../gateway/drain-token";
6
+ import { startGateway } from "../gateway/spawn";
7
+ import { stopGateway } from "../gateway/stop";
8
+ import { ENV_PATH, readEnvFile } from "../support/env";
9
+ import { readInstalledVersion, readLatestPublishedVersion } from "../support/version";
10
+ import { launchBackground, openUrl } from "../ui/background";
11
+ import { chooseFromMenu, confirmByTyping, confirmStep } from "../ui/prompt";
12
+ import { renderWordmark } from "../ui/wordmark";
13
+ import { exportBackup, resetConsoleAccount } from "./admin";
14
+ import { updateCommand } from "./update";
15
+
16
+ /**
17
+ * The default `carte` experience: start the gateway, then offer an arrow-key
18
+ * menu. This needs no desktop environment, so it works over SSH, on a headless
19
+ * box, and on Windows where the tray icon lands in the hidden-icons overflow
20
+ * and is never seen. `carte --tray` opts into the icon instead.
21
+ */
22
+ export async function startMenu(): Promise<void> {
23
+ // A fresh install has no config file yet, and the gateway refuses to boot
24
+ // without CARTETHYIA_DB_MODE — which surfaced as a stack trace about a
25
+ // missing database mode rather than "you have not set this up". Run setup
26
+ // first, the way `carte` is expected to behave on a new install.
27
+ const configured = existsSync(ENV_PATH) && (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE;
28
+ if (!configured) {
29
+ console.log(
30
+ existsSync(ENV_PATH)
31
+ ? "Cartethyia is missing its database mode. Starting setup.\n"
32
+ : "Cartethyia is not set up yet. Starting setup.\n",
33
+ );
34
+ // Dynamic on purpose: `setup` pulls in the whole wizard and the database
35
+ // mode resolver, and it is only reachable on a first run. A static import
36
+ // would pay that cost on every `carte`, which is the one path that has to
37
+ // stay fast.
38
+ const { setup } = await import("./setup");
39
+ await setup();
40
+ const after = existsSync(ENV_PATH) ? (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE : undefined;
41
+ if (!after) {
42
+ console.error(
43
+ `Setup did not record CARTETHYIA_DB_MODE in ${ENV_PATH}; run \`cartethyia setup\` and try again.`,
44
+ );
45
+ process.exitCode = 1;
46
+ return;
47
+ }
48
+ process.env.CARTETHYIA_DB_MODE = after;
49
+ }
50
+
51
+ const port = resolvePort();
52
+ const drainToken = ensureDrainToken();
53
+ const dashboardUrl = `http://127.0.0.1:${port}/console`;
54
+ const currentVersion = await readInstalledVersion();
55
+
56
+ // The gateway starts in the background: the menu is usable before it has
57
+ // finished booting, and awaiting it cost ~670 ms of blank terminal on every
58
+ // start. `ready` resolves when the health probe passes; actions that need a
59
+ // listening gateway await it, so nothing races.
60
+ const ready = startGateway(port, drainToken);
61
+ let gateway: Bun.Subprocess | null = null;
62
+ void ready.then((child) => {
63
+ gateway = child;
64
+ child.unref();
65
+ }).catch((error: unknown) => {
66
+ console.error(
67
+ `The gateway did not start: ${error instanceof Error ? error.message : "unknown error"}`,
68
+ );
69
+ });
70
+
71
+ let stopping = false;
72
+ let handedOff = false;
73
+ const shutDown = async (): Promise<void> => {
74
+ if (stopping) return;
75
+ stopping = true;
76
+ const child = gateway ?? (await ready.catch(() => null));
77
+ if (child) await stopGateway(child, port, drainToken);
78
+ };
79
+ // Ctrl-C has to stop the gateway, and the drain endpoint cannot be called
80
+ // from an exit path: it is async, and enquirer's own Ctrl-C handler calls
81
+ // process.exit(130) while a prompt is live. So the gateway is a child in this
82
+ // process group — a terminal's Ctrl-C signals the whole group, and the
83
+ // gateway drains through the SIGINT handler it already has in src/main.ts —
84
+ // and `process.on("exit")` is the synchronous backstop for every other way
85
+ // this process can end (a closed terminal, a crash, a signal Bun cannot
86
+ // deliver on Windows). That handler can only call synchronous APIs, which is
87
+ // why it signals the child rather than awaiting the drain endpoint.
88
+ // `handedOff` is set by "Hide to tray": the gateway is being given to a tray
89
+ // that will own it, so this process must not kill it on the way out.
90
+ process.on("exit", () => {
91
+ if (stopping || handedOff || gateway === null) return;
92
+ try {
93
+ gateway.kill();
94
+ } catch {
95
+ // Already gone; nothing to stop.
96
+ }
97
+ });
98
+ const interrupt = async (): Promise<void> => {
99
+ process.stdout.write("\u001b[2J\u001b[H");
100
+ console.log("Stopping Cartethyia…");
101
+ await shutDown();
102
+ process.exit(0);
103
+ };
104
+ process.on("SIGTERM", () => void interrupt());
105
+ process.on("SIGINT", () => void interrupt());
106
+
107
+ let latestVersion = await readLatestPublishedVersion();
108
+ let running = true;
109
+
110
+ const header = [...renderWordmark(), `Cartethyia ${currentVersion} \u00b7 ${dashboardUrl}`];
111
+
112
+ while (running) {
113
+ const upToDate = latestVersion === null || !isNewerRelease(currentVersion, latestVersion);
114
+ const updateLabel =
115
+ latestVersion === null
116
+ ? "Check for updates"
117
+ : upToDate
118
+ ? `Up to date (${currentVersion})`
119
+ : `Update to ${latestVersion}`;
120
+
121
+ // Anything that talks to the gateway waits for it to be listening; the menu
122
+ // itself does not.
123
+ const whenReady = async (): Promise<void> => {
124
+ await ready.catch(() => undefined);
125
+ };
126
+
127
+ const actions: readonly (() => Promise<void>)[] = [
128
+ async () => openUrl(dashboardUrl),
129
+ async () => {
130
+ // Deleting the console account is the only real password recovery:
131
+ // /change-password needs the current password, and first-boot setup
132
+ // only runs when no user exists. Removing the user puts the wizard
133
+ // back. Providers, keys, and routing are untouched.
134
+ console.log("");
135
+ console.log("This deletes every console account and its sessions.");
136
+ console.log("The next visit to /console asks you to create a new one.");
137
+ console.log("Providers, API keys, and routing are not touched.");
138
+ console.log("");
139
+ if (!(await confirmStep("Continue with the reset?"))) {
140
+ console.log("Cancelled.");
141
+ return;
142
+ }
143
+ if (!(await confirmByTyping("RESET", "Confirm deleting the console account"))) {
144
+ console.log("Cancelled.");
145
+ return;
146
+ }
147
+ await whenReady();
148
+ const status = await resetConsoleAccount(port, drainToken);
149
+ console.log(
150
+ status === 0
151
+ ? `Console account removed. Open ${dashboardUrl} to create a new one.`
152
+ : "Reset failed; see the gateway log.",
153
+ );
154
+ },
155
+ async () => {
156
+ // Export straight to the download directory. The console page asks for
157
+ // the operator password, which is exactly the thing a locked-out
158
+ // operator does not have; the CLI already runs as the OS user who owns
159
+ // the database, so the loopback token is the whole gate.
160
+ await whenReady();
161
+ const result = await exportBackup(port, drainToken);
162
+ if (result === null) return;
163
+ console.log(
164
+ `Backup written to ${result.path} (${(result.bytes / 1024).toFixed(1)} KiB, ${Object.keys(result.counts).length} tables).`,
165
+ );
166
+ console.log("Telemetry is not included: it is per-request history, not configuration.");
167
+ },
168
+ async () => {
169
+ if (upToDate) return;
170
+ // npm cannot replace this package's directory while this process is
171
+ // running out of it: on Windows the rename fails with EBUSY and npm
172
+ // exits 14. Even with the gateway stopped, the CLI itself is executing
173
+ // from that directory, so an in-process install can never succeed.
174
+ // Print the command and exit, the way 9router does.
175
+ await shutDown();
176
+ console.log("");
177
+ console.log(`Update available: ${currentVersion} \u2192 ${latestVersion}`);
178
+ console.log("");
179
+ console.log("Run this now that Cartethyia has stopped:");
180
+ console.log("");
181
+ console.log(` ${updateCommand()}`);
182
+ console.log("");
183
+ running = false;
184
+ process.exitCode = 0;
185
+ return;
186
+ },
187
+ async () => {
188
+ // Start a detached `tray` process that owns the gateway from scratch,
189
+ // then drain this one so the new process can claim the port. This is
190
+ // the pattern 9router uses and it is simpler than handing a live
191
+ // gateway between processes: the tray is a normal first-class run, not
192
+ // an adopted orphan, so Exit and update behave the same whether the
193
+ // gateway was started here or from `carte --tray`.
194
+ console.log("Starting the tray in the background…");
195
+ launchBackground(["run", resolvePackagePath("scripts", "cli", "main.ts"), "tray"]);
196
+ running = false;
197
+ stopping = true;
198
+ handedOff = true;
199
+ await shutDown();
200
+ console.log(`Cartethyia is running in the background at ${dashboardUrl}.`);
201
+ console.log("The tray icon owns it; use Exit there to stop it.");
202
+ },
203
+ async () => {
204
+ running = false;
205
+ },
206
+ ];
207
+
208
+ const choice = await chooseFromMenu(header, [
209
+ { kind: "action", label: "Open Console", hint: `browser \u00b7 ${dashboardUrl}` },
210
+ { kind: "action", label: "Reset console password", hint: "deletes the account" },
211
+ { kind: "action", label: "Backup", hint: "export config to Downloads" },
212
+ { kind: "action", label: updateLabel, hint: upToDate ? "open the repository" : "installs now" },
213
+ { kind: "action", label: "Hide to tray", hint: "keep the gateway running" },
214
+ { kind: "rule" },
215
+ { kind: "action", label: "Exit", hint: "stop the gateway" },
216
+ ],
217
+ );
218
+ if (choice === -1) break;
219
+ const action = actions[choice];
220
+ if (action) await action();
221
+ }
222
+ // "Hide to tray" has already given the gateway to the tray. Draining here
223
+ // would stop the very process the tray just adopted, which is why the menu
224
+ // appeared to hand over a gateway that was not listening.
225
+ if (handedOff) return;
226
+ await shutDown();
227
+ }