cartethyia 2.4.4 → 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 (304) 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-TDue2X0p.js → BannedPage-QhvbJB9o.js} +1 -1
  5. package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolDetailPage-qaHXuiI5.js → CliToolDetailPage-Bcy150Jo.js} +1 -1
  7. package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BFuQBKez.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-BhglcqyR.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-CeqROJrT.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-CRXSAITY.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-B6LA4ewF.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-BA_ni9Ix.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/support/version.ts +37 -0
  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-vkZBxMVo.js +0 -1
  274. package/dist/dashboard/assets/HealthEventsModal-DDkHfwpV.js +0 -1
  275. package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
  276. package/dist/dashboard/assets/ProviderDetailPage-cNAD_6uN.js +0 -3
  277. package/dist/dashboard/assets/ProvidersPage-Cv5tdxlx.js +0 -1
  278. package/dist/dashboard/assets/ProxyPage-CWvMfzjj.js +0 -7
  279. package/dist/dashboard/assets/QuotaPage-IYQOOHR4.js +0 -1
  280. package/dist/dashboard/assets/SetupPage-BPDNo3Ek.js +0 -1
  281. package/dist/dashboard/assets/SortableList-Bol1ASmI.js +0 -1
  282. package/dist/dashboard/assets/StudioPage-BE5DJONZ.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-DTiWhIY-.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 -766
  297. package/scripts/cli/version.ts +0 -66
  298. package/scripts/operations/probe-egress.ts +0 -335
  299. package/scripts/operations/restart.ts +0 -120
  300. package/scripts/shared/env.ts +0 -269
  301. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
  302. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
  303. /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
  304. /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
@@ -0,0 +1,99 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { PACKAGE_ROOT } from "../../../src/config/package-paths";
5
+
6
+ /**
7
+ * Absolute path of the repository root.
8
+ *
9
+ * Operational scripts and the npm runtime share one package-root resolver so
10
+ * assets and repository config do not depend on the caller's working directory.
11
+ */
12
+ export const PROJECT_ROOT = PACKAGE_ROOT;
13
+
14
+ /** Resolve the active `.env`; npm CLI sets a user-owned config directory. */
15
+ export function resolveEnvPath(env: NodeJS.ProcessEnv = process.env): string {
16
+ const configDir = env.CARTETHYIA_CONFIG_DIR?.trim();
17
+ return configDir ? resolve(configDir, ".env") : resolve(PROJECT_ROOT, ".env");
18
+ }
19
+
20
+ /** Absolute path of the active `.env`. */
21
+ export const ENV_PATH = resolveEnvPath();
22
+
23
+ /**
24
+ * Reads a dotenv-style file without overriding already-exported variables.
25
+ */
26
+ export async function readEnvFile(path: string): Promise<Record<string, string>> {
27
+ if (!existsSync(path)) return {};
28
+ const content = await readFile(path, "utf8");
29
+ const values: Record<string, string> = {};
30
+ for (const line of content.split(/\r?\n/)) {
31
+ if (line.startsWith("#")) continue;
32
+ const separator = line.indexOf("=");
33
+ if (separator <= 0) continue;
34
+ const key = line.slice(0, separator).trim();
35
+ const value = line.slice(separator + 1).trim();
36
+ if (key) values[key] = value;
37
+ }
38
+ return values;
39
+ }
40
+
41
+ /**
42
+ * Checks if a string contains a configured placeholder value.
43
+ */
44
+ export function hasPlaceholderSecrets(value: string): boolean {
45
+ const normalized = value.toLowerCase();
46
+ return (
47
+ normalized.includes("replace-with") ||
48
+ normalized.includes("replace_me") ||
49
+ normalized.includes("your_")
50
+ );
51
+ }
52
+
53
+ /**
54
+ * Generates a 256-bit hex cartethyia encryption key.
55
+ *
56
+ * Hex is preferred over base64 because it never carries padding or `+`/`/`
57
+ * characters that confuse some dotenv shells, and it round-trips through
58
+ * every adapter that accepts either encoding. The install/setup helpers call
59
+ * this when the key is absent or still a placeholder.
60
+ */
61
+ export function generateCartethyiaEncryptionKey(): string {
62
+ const bytes = new Uint8Array(32);
63
+ crypto.getRandomValues(bytes);
64
+ let hex = "";
65
+ for (let i = 0; i < bytes.length; i += 1) hex += bytes[i]!.toString(16).padStart(2, "0");
66
+ return hex;
67
+ }
68
+
69
+ /**
70
+ * Returns the mandatory-only `.env` body derived from `.env.example`.
71
+ *
72
+ * Mandatory means the key is assigned without a leading `#`. Optional entries
73
+ * (leading `#`) carry built-in defaults and must not be copied into a fresh
74
+ * `.env` — they would override the default with a literal `example` value.
75
+ * The generated body therefore contains only mandatory rows, plus a single
76
+ * `CARTETHYIA_ENCRYPTION_KEY` replacement when the caller supplies one.
77
+ */
78
+ export async function mandatoryEnvBody(
79
+ examplePath: string,
80
+ overrides: Readonly<Record<string, string>> = {},
81
+ ): Promise<string> {
82
+ const raw = await readFile(examplePath, "utf8");
83
+ const lines: string[] = [];
84
+ for (const line of raw.split(/\r?\n/)) {
85
+ const trimmed = line.trim();
86
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
87
+ const sep = line.indexOf("=");
88
+ if (sep <= 0) continue;
89
+ const key = line.slice(0, sep).trim();
90
+ if (!key) continue;
91
+ const value = (overrides as Record<string, string>)[key] ?? line.slice(sep + 1).trim();
92
+ lines.push(`${key}=${value}`);
93
+ }
94
+ // Preserve overrides even when the example row was commented out (legacy case).
95
+ for (const [key, value] of Object.entries(overrides)) {
96
+ if (!lines.some((l) => l.startsWith(`${key}=`))) lines.push(`${key}=${value}`);
97
+ }
98
+ return `${lines.join("\n")}\n`;
99
+ }
@@ -0,0 +1,64 @@
1
+ export type LocalPlatform = "windows" | "macos" | "linux" | "unknown";
2
+
3
+ /** Detects the host platform once so setup/doctor share identical labels. */
4
+ export function getLocalPlatform(): LocalPlatform {
5
+ if (process.platform === "win32") return "windows";
6
+ if (process.platform === "darwin") return "macos";
7
+ if (process.platform === "linux") return "linux";
8
+ return "unknown";
9
+ }
10
+
11
+ /**
12
+ * Returns actionable hints for the configured local/external service.
13
+ * These are hints only: setup never assumes Docker or silently mutates an
14
+ * external service. Windows users commonly run PostgreSQL through Laragon;
15
+ * Redis may be native, WSL-backed, or external.
16
+ */
17
+ export function getOsHints(service: "postgres" | "redis"): string[] {
18
+ const platform = getLocalPlatform();
19
+ if (platform === "windows") {
20
+ return service === "postgres"
21
+ ? [
22
+ "Windows / Laragon:",
23
+ " Start Laragon (Start All) and ensure PostgreSQL is enabled.",
24
+ " Verify PostgreSQL is listening on the DATABASE_URL host/port (usually 5432).",
25
+ " External PostgreSQL is also supported — keep DATABASE_URL unchanged and reachable.",
26
+ ]
27
+ : [
28
+ "Windows / Redis:",
29
+ " Start Redis through WSL, a native Redis-compatible service, or an external host.",
30
+ " Verify Redis is listening on the REDIS_URL host/port (usually 6379).",
31
+ " Or omit REDIS_URL to use the in-memory backend.",
32
+ ];
33
+ }
34
+ if (platform === "macos") {
35
+ return service === "postgres"
36
+ ? [
37
+ "macOS / Homebrew:",
38
+ " brew services start postgresql@16 (or the installed PostgreSQL version).",
39
+ " External PostgreSQL is also supported — verify DATABASE_URL reachability.",
40
+ ]
41
+ : [
42
+ "macOS / Redis:",
43
+ " brew services start redis, or use an external Redis instance.",
44
+ " Or omit REDIS_URL to use the in-memory backend.",
45
+ ];
46
+ }
47
+ if (platform === "linux") {
48
+ return service === "postgres"
49
+ ? [
50
+ "Linux / systemd:",
51
+ " sudo systemctl start postgresql (or the installed PostgreSQL service).",
52
+ " External PostgreSQL is also supported — verify DATABASE_URL reachability.",
53
+ ]
54
+ : [
55
+ "Linux / Redis:",
56
+ " sudo systemctl start redis-server (or the installed Redis service).",
57
+ " Or omit REDIS_URL to use the in-memory backend.",
58
+ ];
59
+ }
60
+ return [
61
+ `${service} is not reachable on this platform.`,
62
+ "Verify the configured service URL or use an external service.",
63
+ ];
64
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * TCP reachability checks for the services setup and doctor ask about.
3
+ *
4
+ * These are extracted from the env helpers so the dotenv reader and the probe
5
+ * stay independent: doctor can be imported without pulling in `.env` parsing.
6
+ */
7
+
8
+ export interface ProbeResult {
9
+ success: boolean;
10
+ error?: string;
11
+ }
12
+
13
+ /**
14
+ * Opens a TCP connection to `host:port` and resolves once it connects, errors,
15
+ * or the timeout elapses. The socket is always closed so a probe never leaves a
16
+ * half-open connection behind.
17
+ */
18
+ export async function probeTcpService(
19
+ host: string,
20
+ port: number,
21
+ timeoutMs: number,
22
+ ): Promise<ProbeResult> {
23
+ const { promise, resolve } = Promise.withResolvers<ProbeResult>();
24
+
25
+ const timeoutHandle = setTimeout(() => {
26
+ resolve({ success: false, error: "timeout" });
27
+ }, timeoutMs);
28
+
29
+ Bun.connect({
30
+ hostname: host,
31
+ port: port,
32
+ socket: {
33
+ open: () => {
34
+ clearTimeout(timeoutHandle);
35
+ resolve({ success: true });
36
+ },
37
+ data: () => {},
38
+ error: (err) => {
39
+ clearTimeout(timeoutHandle);
40
+ resolve({ success: false, error: String(err) });
41
+ },
42
+ },
43
+ })
44
+ .then((socket) => {
45
+ socket.end();
46
+ })
47
+ .catch((err) => {
48
+ clearTimeout(timeoutHandle);
49
+ resolve({ success: false, error: String(err) });
50
+ });
51
+
52
+ return promise;
53
+ }
54
+
55
+ /**
56
+ * Local PostgreSQL ports worth probing before asking for a URL. 5432 is the
57
+ * server default (Laragon, Homebrew, systemd); 5433 is the usual second
58
+ * instance when 5432 is already taken.
59
+ */
60
+ export const LOCAL_POSTGRES_CANDIDATES = [
61
+ { host: "127.0.0.1", port: 5432 },
62
+ { host: "127.0.0.1", port: 5433 },
63
+ ] as const;
64
+
65
+ /** First local PostgreSQL that accepts a TCP connection, or null. */
66
+ export async function detectLocalPostgres(
67
+ timeoutMs: number,
68
+ ): Promise<{ host: string; port: number } | null> {
69
+ for (const candidate of LOCAL_POSTGRES_CANDIDATES) {
70
+ const probe = await probeTcpService(candidate.host, candidate.port, timeoutMs);
71
+ if (probe.success) return candidate;
72
+ }
73
+ return null;
74
+ }
75
+
76
+ /**
77
+ * Parses a service URL and requires an explicit host and port.
78
+ * Returns null if the URL is invalid or incomplete.
79
+ */
80
+ export function parseServiceUrl(url: string): { host: string; port: number } | null {
81
+ try {
82
+ const urlObj = new URL(url);
83
+ if (!urlObj.hostname || !urlObj.port) return null;
84
+ return { host: urlObj.hostname, port: Number(urlObj.port) };
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
@@ -0,0 +1,23 @@
1
+ import { writeFileSync } from "node:fs";
2
+
3
+ /**
4
+ * Reports tray readiness to the launcher that is waiting on a signal file.
5
+ *
6
+ * The tray is spawned detached with no stdio, so the launcher cannot read its
7
+ * output; a one-shot file is the only channel that survives the parent exiting.
8
+ * `flag: "wx"` makes the write fail rather than overwrite, which keeps a late
9
+ * report from replacing the failure the launcher already acted on.
10
+ */
11
+ export type TrayStartupSignal =
12
+ | { readonly status: "ready"; readonly url: string }
13
+ | { readonly status: "failed"; readonly message: string };
14
+
15
+ export function reportTrayStartup(signal: TrayStartupSignal): void {
16
+ const signalPath = process.env.CARTETHYIA_TRAY_STARTUP_SIGNAL;
17
+ if (!signalPath) return;
18
+ try {
19
+ writeFileSync(signalPath, JSON.stringify(signal), { encoding: "utf8", flag: "wx" });
20
+ } catch {
21
+ // The launcher may have timed out and removed the one-shot signal file.
22
+ }
23
+ }
@@ -0,0 +1,37 @@
1
+ import { resolvePackagePath } from "../../../src/config/package-paths";
2
+
3
+ /** Reads the version from the installed package manifest. */
4
+ export async function readInstalledVersion(): Promise<string> {
5
+ const metadata: unknown = await Bun.file(resolvePackagePath("package.json")).json();
6
+ if (
7
+ metadata === null ||
8
+ typeof metadata !== "object" ||
9
+ !("version" in metadata) ||
10
+ typeof metadata.version !== "string"
11
+ ) {
12
+ throw new Error("Cartethyia package version is unavailable");
13
+ }
14
+ return metadata.version;
15
+ }
16
+
17
+ /** Asks the npm registry for the newest published version, or null on failure. */
18
+ export async function readLatestPublishedVersion(): Promise<string | null> {
19
+ try {
20
+ const response = await fetch("https://registry.npmjs.org/cartethyia/latest", {
21
+ signal: AbortSignal.timeout(4_000),
22
+ });
23
+ if (!response.ok) return null;
24
+ const metadata: unknown = await response.json();
25
+ if (
26
+ metadata !== null &&
27
+ typeof metadata === "object" &&
28
+ "version" in metadata &&
29
+ typeof metadata.version === "string"
30
+ ) {
31
+ return metadata.version;
32
+ }
33
+ return null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
@@ -0,0 +1,28 @@
1
+ ' Launches a command with no console window at all.
2
+ '
3
+ ' Why a VBS: `wscript.exe` is a GUI-subsystem host, so it never allocates a
4
+ ' console for itself. Everything it starts with window style 0 inherits that —
5
+ ' no console window is shown, and none is left behind. That is different from
6
+ ' spawning the child directly: a console-subsystem child spawned from a
7
+ ' window-less parent gets a new console unless CREATE_NO_WINDOW is passed, and
8
+ ' when the parent lives inside Windows Terminal the tab is already open before
9
+ ' any flag can take effect.
10
+ '
11
+ ' Usage: wscript.exe //nologo launch-hidden.vbs <exe> [args...]
12
+ ' The third argument (True) would make wscript wait for the child; it must stay
13
+ ' False so the launcher returns immediately and the child outlives it.
14
+
15
+ Set shell = CreateObject("WScript.Shell")
16
+
17
+ command = ""
18
+ For Each arg In WScript.Arguments
19
+ If command <> "" Then command = command & " "
20
+ command = command & """" & arg & """"
21
+ Next
22
+
23
+ If command = "" Then
24
+ WScript.Quit 2
25
+ End If
26
+
27
+ ' 0 = hidden window, False = do not wait.
28
+ shell.Run command, 0, False
@@ -1,7 +1,7 @@
1
1
  import { spawn, type ChildProcess } from "node:child_process";
2
2
  import { createInterface } from "node:readline";
3
3
  import SysTray from "systray2";
4
- import { resolvePackagePath } from "../../src/config/package-paths";
4
+ import { resolvePackagePath } from "../../../src/config/package-paths";
5
5
 
6
6
  /**
7
7
  * One tray row. `action` rows report their id on click; separators are drawn
@@ -66,7 +66,7 @@ class WindowsTray implements Tray {
66
66
  "-OutputFormat",
67
67
  "Text",
68
68
  "-File",
69
- resolvePackagePath("scripts", "cli", "tray.ps1"),
69
+ resolvePackagePath("scripts", "cli", "tray", "windows.ps1"),
70
70
  "-IconPath",
71
71
  options.iconPath,
72
72
  "-Tooltip",
@@ -112,7 +112,7 @@ class WindowsTray implements Tray {
112
112
  let index = 0;
113
113
  for (const item of options.items) {
114
114
  if (item.kind === "separator") {
115
- this.#send({ action: "add-item", index, title: "-", enabled: false });
115
+ this.#send({ action: "add-separator", index });
116
116
  index += 1;
117
117
  continue;
118
118
  }
@@ -1,98 +1,107 @@
1
- # Cartethyia tray icon for Windows using NotifyIcon.
2
- #
3
- # Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
4
- # helper binary and still leave the spawning process attached to a console, so
5
- # Windows Terminal shows an empty tab for the tray's whole lifetime. A
6
- # PowerShell NotifyIcon owns no console of its own when started with
7
- # -WindowStyle Hidden, and the protocol here is plain JSON lines on
8
- # stdin/stdout, so the parent stays in charge.
9
- #
10
- # Protocol: the parent writes one JSON object per line.
11
- # {"action":"add-item","index":0,"title":"...","enabled":true}
12
- # {"action":"update-item","index":0,"title":"...","enabled":true}
13
- # {"action":"set-tooltip","text":"..."}
14
- # {"action":"kill"}
15
- # This script writes one JSON object per line:
16
- # {"type":"started"} {"type":"ready"} {"type":"click","index":0}
17
- param([string]$IconPath, [string]$Tooltip)
18
-
19
- $ErrorActionPreference = "Stop"
20
-
21
- Add-Type -AssemblyName System.Windows.Forms
22
- Add-Type -AssemblyName System.Drawing
23
-
24
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
25
- [Console]::InputEncoding = [System.Text.Encoding]::UTF8
26
- $OutputEncoding = [System.Text.Encoding]::UTF8
27
-
28
- [System.Windows.Forms.Application]::EnableVisualStyles()
29
- [System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
30
-
31
- $script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
32
- $script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
33
- $script:notifyIcon.Text = $Tooltip
34
- $script:notifyIcon.Visible = $true
35
-
36
- $script:menu = New-Object System.Windows.Forms.ContextMenuStrip
37
- $script:notifyIcon.ContextMenuStrip = $script:menu
38
- $script:items = @()
39
-
40
- function Write-Event($obj) {
41
- $json = $obj | ConvertTo-Json -Compress
42
- [Console]::Out.WriteLine($json)
43
- [Console]::Out.Flush()
44
- }
45
-
46
- function Add-MenuItem($index, $title, $enabled) {
47
- $item = New-Object System.Windows.Forms.ToolStripMenuItem
48
- $item.Text = $title
49
- $item.Enabled = $enabled
50
- $idx = $index
51
- $item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
52
- $script:menu.Items.Add($item) | Out-Null
53
- $script:items += $item
54
- }
55
-
56
- function Update-MenuItem($index, $title, $enabled) {
57
- if ($index -lt $script:items.Count) {
58
- $script:items[$index].Text = $title
59
- $script:items[$index].Enabled = $enabled
60
- }
61
- }
62
-
63
- function Set-Tooltip($text) {
64
- # NotifyIcon.Text is capped at 63 characters.
65
- if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
66
- $script:notifyIcon.Text = $text
67
- }
68
-
69
- # Stdin is polled from the UI thread: a background thread reading Console.In
70
- # races the message loop and deadlocks when the menu is open.
71
- $script:timer = New-Object System.Windows.Forms.Timer
72
- $script:timer.Interval = 100
73
- $script:timer.Add_Tick({
74
- try {
75
- while ([Console]::In.Peek() -ne -1) {
76
- $line = [Console]::In.ReadLine()
77
- if ([string]::IsNullOrWhiteSpace($line)) { continue }
78
- $cmd = $line | ConvertFrom-Json
79
- switch ($cmd.action) {
80
- "add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
81
- "update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
82
- "set-tooltip" { Set-Tooltip $cmd.text }
83
- "ready" { Write-Event @{ type = "ready" } }
84
- "kill" {
85
- $script:notifyIcon.Visible = $false
86
- $script:notifyIcon.Dispose()
87
- [System.Windows.Forms.Application]::Exit()
88
- }
89
- }
90
- }
91
- } catch {
92
- Write-Event @{ type = "error"; message = $_.Exception.Message }
93
- }
94
- })
95
- $script:timer.Start()
96
-
97
- Write-Event @{ type = "started" }
98
- [System.Windows.Forms.Application]::Run()
1
+ # Cartethyia tray icon for Windows using NotifyIcon.
2
+ #
3
+ # Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
4
+ # helper binary and still leave the spawning process attached to a console, so
5
+ # Windows Terminal shows an empty tab for the tray's whole lifetime. A
6
+ # PowerShell NotifyIcon owns no console of its own when started with
7
+ # -WindowStyle Hidden, and the protocol here is plain JSON lines on
8
+ # stdin/stdout, so the parent stays in charge.
9
+ #
10
+ # Protocol: the parent writes one JSON object per line.
11
+ # {"action":"add-item","index":0,"title":"...","enabled":true}
12
+ # {"action":"update-item","index":0,"title":"...","enabled":true}
13
+ # {"action":"set-tooltip","text":"..."}
14
+ # {"action":"kill"}
15
+ # This script writes one JSON object per line:
16
+ # {"type":"started"} {"type":"ready"} {"type":"click","index":0}
17
+ param([string]$IconPath, [string]$Tooltip)
18
+
19
+ $ErrorActionPreference = "Stop"
20
+
21
+ Add-Type -AssemblyName System.Windows.Forms
22
+ Add-Type -AssemblyName System.Drawing
23
+
24
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
25
+ [Console]::InputEncoding = [System.Text.Encoding]::UTF8
26
+ $OutputEncoding = [System.Text.Encoding]::UTF8
27
+
28
+ [System.Windows.Forms.Application]::EnableVisualStyles()
29
+ [System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
30
+
31
+ $script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
32
+ $script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
33
+ $script:notifyIcon.Text = $Tooltip
34
+ $script:notifyIcon.Visible = $true
35
+
36
+ $script:menu = New-Object System.Windows.Forms.ContextMenuStrip
37
+ $script:notifyIcon.ContextMenuStrip = $script:menu
38
+ $script:items = @()
39
+
40
+ function Write-Event($obj) {
41
+ $json = $obj | ConvertTo-Json -Compress
42
+ [Console]::Out.WriteLine($json)
43
+ [Console]::Out.Flush()
44
+ }
45
+
46
+ function Add-MenuItem($index, $title, $enabled) {
47
+ $item = New-Object System.Windows.Forms.ToolStripMenuItem
48
+ $item.Text = $title
49
+ $item.Enabled = $enabled
50
+ $idx = $index
51
+ $item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
52
+ $script:menu.Items.Add($item) | Out-Null
53
+ $script:items += $item
54
+ }
55
+
56
+ # A real ToolStripSeparator draws the divider. Sending a "-" title instead
57
+ # renders a literal dash row, which is what the menu showed before.
58
+ function Add-MenuSeparator($index) {
59
+ $item = New-Object System.Windows.Forms.ToolStripSeparator
60
+ $script:menu.Items.Add($item) | Out-Null
61
+ $script:items += $item
62
+ }
63
+
64
+ function Update-MenuItem($index, $title, $enabled) {
65
+ if ($index -lt $script:items.Count) {
66
+ $script:items[$index].Text = $title
67
+ $script:items[$index].Enabled = $enabled
68
+ }
69
+ }
70
+
71
+ function Set-Tooltip($text) {
72
+ # NotifyIcon.Text is capped at 63 characters.
73
+ if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
74
+ $script:notifyIcon.Text = $text
75
+ }
76
+
77
+ # Stdin is polled from the UI thread: a background thread reading Console.In
78
+ # races the message loop and deadlocks when the menu is open.
79
+ $script:timer = New-Object System.Windows.Forms.Timer
80
+ $script:timer.Interval = 100
81
+ $script:timer.Add_Tick({
82
+ try {
83
+ while ([Console]::In.Peek() -ne -1) {
84
+ $line = [Console]::In.ReadLine()
85
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
86
+ $cmd = $line | ConvertFrom-Json
87
+ switch ($cmd.action) {
88
+ "add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
89
+ "add-separator" { Add-MenuSeparator $cmd.index }
90
+ "update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
91
+ "set-tooltip" { Set-Tooltip $cmd.text }
92
+ "ready" { Write-Event @{ type = "ready" } }
93
+ "kill" {
94
+ $script:notifyIcon.Visible = $false
95
+ $script:notifyIcon.Dispose()
96
+ [System.Windows.Forms.Application]::Exit()
97
+ }
98
+ }
99
+ }
100
+ } catch {
101
+ Write-Event @{ type = "error"; message = $_.Exception.Message }
102
+ }
103
+ })
104
+ $script:timer.Start()
105
+
106
+ Write-Event @{ type = "started" }
107
+ [System.Windows.Forms.Application]::Run()
@@ -0,0 +1,49 @@
1
+ import { spawn } from "node:child_process";
2
+ import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
3
+
4
+ /** Opens a URL in the operator's default browser, ignoring a missing handler. */
5
+ export function openUrl(url: string): Promise<void> {
6
+ const command = process.platform === "win32"
7
+ ? "explorer.exe"
8
+ : process.platform === "darwin"
9
+ ? "open"
10
+ : "xdg-open";
11
+ const child = Bun.spawn([command, url], {
12
+ stdin: "ignore",
13
+ stdout: "ignore",
14
+ stderr: "ignore",
15
+ });
16
+ return child.exited.then(() => undefined);
17
+ }
18
+
19
+ /**
20
+ * Starts a Bun process that owns no console window.
21
+ *
22
+ * On Windows, spawning the child directly is not enough: a console-subsystem
23
+ * child of a window-less parent allocates a new console, and when the parent is
24
+ * inside Windows Terminal the tab is already on screen before any flag can take
25
+ * effect — measured: `detached: true` plus `windowsHide: true` still produced a
26
+ * Windows Terminal window titled with the bun path. `wscript.exe` is a
27
+ * GUI-subsystem host, so it never allocates a console, and a child launched
28
+ * from it with window style 0 inherits that. The VBS is that launcher.
29
+ *
30
+ * Elsewhere `detached` is enough and the helper is not used.
31
+ */
32
+ export function launchBackground(args: readonly string[]): void {
33
+ if (process.platform !== "win32") {
34
+ const child = spawn(process.execPath, [...args], {
35
+ cwd: PACKAGE_ROOT,
36
+ detached: true,
37
+ stdio: "ignore",
38
+ });
39
+ child.unref();
40
+ return;
41
+ }
42
+ const launcher = resolvePackagePath("scripts", "cli", "tray", "hidden-window.vbs");
43
+ const child = spawn(
44
+ "wscript.exe",
45
+ ["//nologo", launcher, process.execPath, ...args],
46
+ { cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", windowsHide: true },
47
+ );
48
+ child.unref();
49
+ }
package/src/boot/app.ts CHANGED
@@ -196,7 +196,9 @@ export function createGatewayApp(deps: GatewayAppDeps) {
196
196
  const peerAddresses = new WeakMap<Request, string>();
197
197
  const serveDashboard = async ({ request }: { request: Request }): Promise<Response> => {
198
198
  const pathname = fastPathname(request.url);
199
- const result = await staticHandler(pathname);
199
+ // The client's `accept-encoding` is what decides whether a precompressed
200
+ // `.gz` sibling can be served instead of the plain asset.
201
+ const result = await staticHandler(pathname, request.headers.get("accept-encoding"));
200
202
  return new Response(result.body ? (result.body as unknown as BodyInit) : null, {
201
203
  status: result.status,
202
204
  headers: result.headers,