cartethyia 2.4.5 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/.env.example +4 -1
  2. package/README.md +63 -13
  3. package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
  4. package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
  5. package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
  7. package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
  8. package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-D3xqZbN-.js} +1 -1
  9. package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
  10. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
  11. package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
  12. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js +1 -0
  13. package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
  14. package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-CTTpCb25.js} +1 -1
  15. package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js +1 -0
  17. package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
  18. package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-DHoB3Agn.js} +1 -1
  19. package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
  20. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +1 -0
  21. package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
  22. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
  23. package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
  25. package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
  26. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
  27. package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
  28. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
  29. package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
  30. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
  31. package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
  32. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
  33. package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
  34. package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-CvjZxHE4.js} +1 -1
  35. package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
  36. package/dist/dashboard/assets/SetupPage-D3nza7LH.js +1 -0
  37. package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
  38. package/dist/dashboard/assets/{SortableList-Bol1ASmI.js → SortableList-Dtt12WT1.js} +1 -1
  39. package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
  40. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js +31 -0
  41. package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
  42. package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
  43. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
  44. package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
  45. package/dist/dashboard/assets/api-keys-BmDbsior.js +1 -0
  46. package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
  47. package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
  48. package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
  49. package/dist/dashboard/assets/{app-BDJWMThu.js → app-C-Y4oZT0.js} +3 -3
  50. package/dist/dashboard/assets/app-C-Y4oZT0.js.gz +0 -0
  51. package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
  52. package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
  53. package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
  54. package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
  55. package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
  56. package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-DpXT4Ixt.js} +1 -1
  57. package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
  58. package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-CSAAWLJv.js} +1 -1
  59. package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
  60. package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
  61. package/dist/dashboard/assets/{card-BTdbRFFg.js → card-DEhRXXw4.js} +1 -1
  62. package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
  63. package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
  64. package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
  65. package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
  66. package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
  67. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js +1 -0
  68. package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
  69. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +1 -0
  70. package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
  71. package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
  72. package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
  73. package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BvQeBMfP.js} +1 -1
  74. package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
  75. package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
  76. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  77. package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
  78. package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-CMwPXwQB.js} +1 -1
  79. package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
  80. package/dist/dashboard/assets/entry-D4O2229X.js +1 -0
  81. package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
  82. package/dist/dashboard/assets/entry-DKvr5Q9K.js +1 -0
  83. package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
  84. package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
  85. package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
  86. package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
  87. package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
  88. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  89. package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
  90. package/dist/dashboard/assets/github-badge-zliLvBwr.js +1 -0
  91. package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
  92. package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
  93. package/dist/dashboard/assets/{index-C4hXfAMs.js → index-CXm3FIyt.js} +2 -2
  94. package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
  95. package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1IlZZ8H.js} +1 -1
  96. package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
  97. package/dist/dashboard/assets/{input-Bn4_kNav.js → input-BpVZdrbc.js} +1 -1
  98. package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
  99. package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
  100. package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
  101. package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
  102. package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BioLW7gN.js} +1 -1
  103. package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
  104. package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-CwVcGAIc.js} +1 -1
  105. package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
  106. package/dist/dashboard/assets/{live-YlifPYox.js → live-yfEJFF9T.js} +1 -1
  107. package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
  108. package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
  109. package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
  110. package/dist/dashboard/assets/model-bans-CT-TxSNX.js +1 -0
  111. package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
  112. package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
  113. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  114. package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
  115. package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
  116. package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
  117. package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
  118. package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
  119. package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
  120. package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
  121. package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
  122. package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
  123. package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
  124. package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
  125. package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
  126. package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
  127. package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
  128. package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
  129. package/dist/dashboard/assets/providers/cursor.webp +0 -0
  130. package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
  131. package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
  132. package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
  133. package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
  134. package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
  135. package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
  136. package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
  137. package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
  138. package/dist/dashboard/assets/providers/trae.webp +0 -0
  139. package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
  140. package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
  141. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  142. package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
  143. package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
  144. package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
  145. package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
  146. package/dist/dashboard/assets/routing-Cs6jl49L.js +1 -0
  147. package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
  148. package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
  149. package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
  150. package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
  151. package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
  152. package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
  153. package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
  154. package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
  155. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  156. package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-C9_uUjaA.js} +1 -1
  157. package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
  158. package/dist/dashboard/assets/{state-DHKdg-pm.js → state-DA0B95UQ.js} +1 -1
  159. package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
  160. package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
  161. package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-Cittxy-x.js} +1 -1
  162. package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
  163. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
  164. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  165. package/dist/dashboard/assets/toast-CNt_jzO4.js +2 -0
  166. package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
  167. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  168. package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
  169. package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
  170. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  171. package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
  172. package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
  173. package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-BQKMcJ1H.js} +1 -1
  174. package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
  175. package/dist/dashboard/assets/wrench-B6E1oEuY.js.gz +0 -0
  176. package/dist/dashboard/index.html +1 -1
  177. package/dist/dashboard/index.html.gz +0 -0
  178. package/dist/dashboard/precompressed-assets.json +122 -0
  179. package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
  180. package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
  181. package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
  182. package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
  183. package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
  184. package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
  185. package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
  186. package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
  187. package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
  188. package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
  189. package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
  190. package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
  191. package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
  192. package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
  193. package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
  194. package/package.json +26 -30
  195. package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
  196. package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
  197. package/scripts/cli/command/admin.ts +65 -0
  198. package/scripts/cli/command/console-menu.ts +246 -0
  199. package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
  200. package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
  201. package/scripts/cli/command/start.ts +77 -0
  202. package/scripts/cli/command/tray.ts +165 -0
  203. package/scripts/cli/command/update.ts +23 -0
  204. package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
  205. package/scripts/cli/gateway/instance-lock.ts +105 -0
  206. package/scripts/cli/gateway/spawn.ts +173 -0
  207. package/scripts/cli/gateway/stop.ts +51 -0
  208. package/scripts/cli/main.ts +94 -0
  209. package/scripts/cli/support/bun.ts +27 -0
  210. package/scripts/cli/support/env.ts +99 -0
  211. package/scripts/cli/support/platform.ts +64 -0
  212. package/scripts/cli/support/services.ts +88 -0
  213. package/scripts/cli/support/startup-signal.ts +23 -0
  214. package/scripts/cli/{version.ts → support/version.ts} +1 -1
  215. package/scripts/cli/tray/hidden-window.vbs +28 -0
  216. package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
  217. package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
  218. package/scripts/cli/ui/background.ts +110 -0
  219. package/src/boot/app.ts +23 -7
  220. package/src/boot/dependencies.ts +55 -13
  221. package/src/boot/elysia-typebox-bridge.ts +37 -0
  222. package/src/boot/lifecycle.ts +2 -0
  223. package/src/config/cooldowns.ts +138 -138
  224. package/src/config/providers.ts +31 -5
  225. package/src/config/runtime.ts +19 -3
  226. package/src/config/spec.ts +20 -0
  227. package/src/console/api-keys/contracts.ts +414 -391
  228. package/src/console/api-keys/routes.ts +880 -870
  229. package/src/console/auth/account-reset-store.ts +14 -0
  230. package/src/console/auth/account-reset.ts +3 -2
  231. package/src/console/auth/setup-service.ts +6 -3
  232. package/src/console/backup/routes.ts +8 -0
  233. package/src/console/cli-tools/contracts.ts +6 -6
  234. package/src/console/console-router.ts +5 -3
  235. package/src/console/dashboard-assets.ts +81 -11
  236. package/src/console/domain-registration.ts +9 -2
  237. package/src/console/observability/contracts.ts +475 -423
  238. package/src/console/observability/routes.ts +31 -6
  239. package/src/console/observability/store.ts +1015 -912
  240. package/src/console/providers/catalog/contracts.ts +31 -0
  241. package/src/console/providers/catalog/provider-operations.ts +8 -0
  242. package/src/console/providers/catalog/public-model-store.ts +91 -60
  243. package/src/console/providers/catalog/store.ts +1532 -1521
  244. package/src/console/providers/oauth/callback-listener.ts +18 -3
  245. package/src/console/providers/oauth/routes.ts +38 -15
  246. package/src/console/quota/account-quota-tenant.ts +9 -6
  247. package/src/console/quota/refresh.ts +19 -0
  248. package/src/console/response-compression.ts +69 -2
  249. package/src/console/routing/pools/probe-verdict.ts +72 -0
  250. package/src/console/share/share-router.ts +7 -4
  251. package/src/console/share/share-stats.ts +12 -5
  252. package/src/console/shared/read-through-cache.ts +133 -7
  253. package/src/console/updates/repository-updates.ts +39 -34
  254. package/src/console/updates/routes.ts +162 -4
  255. package/src/main.ts +3 -0
  256. package/src/network/http2-bidi.ts +85 -0
  257. package/src/network/outbound-fetch.ts +87 -24
  258. package/src/network/pool/agent.ts +478 -460
  259. package/src/network/pool/resolver.ts +1104 -544
  260. package/src/network/pool-health-machine.ts +12 -5
  261. package/src/observability/event-log.ts +29 -8
  262. package/src/observability/metrics.ts +40 -13
  263. package/src/observability/payload-capture.ts +344 -324
  264. package/src/observability/runtime-metrics.ts +7 -0
  265. package/src/persistence/db-pg.ts +9 -3
  266. package/src/persistence/db-sqlite.ts +497 -495
  267. package/src/persistence/health-event-flush.ts +137 -0
  268. package/src/persistence/migrate.ts +181 -129
  269. package/src/persistence/schema.ts +1360 -1257
  270. package/src/persistence/settings-revision.ts +25 -0
  271. package/src/persistence/shared-cache.ts +217 -0
  272. package/src/persistence/stores/share-store.ts +556 -528
  273. package/src/persistence/usage-flush.ts +61 -1
  274. package/src/protocol/canonical-model.ts +23 -1
  275. package/src/protocol/response/chat.ts +143 -10
  276. package/src/protocol/response/codex.ts +9 -75
  277. package/src/protocol/response/omp-tool-call-markup.ts +221 -0
  278. package/src/protocol/tool-identity.ts +43 -9
  279. package/src/providers/authentication/jwt-validator.ts +130 -187
  280. package/src/providers/authentication/oauth-flow-store.ts +16 -9
  281. package/src/providers/default-registry.ts +177 -1
  282. package/src/providers/discovery/catalog/catalog-format.ts +20 -0
  283. package/src/providers/discovery/models-dev-catalog.ts +39 -22
  284. package/src/providers/discovery/openai-model-discovery.ts +39 -3
  285. package/src/providers/discovery/openrouter-free-roster.ts +98 -0
  286. package/src/providers/discovery/probe-wire.ts +29 -5
  287. package/src/providers/discovery/probing-service.ts +1022 -1001
  288. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +42 -2
  289. package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
  290. package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
  291. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +22 -5
  292. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
  293. package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
  294. package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
  295. package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
  296. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
  297. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
  298. package/src/providers/integrations/buddy/codebuddy/shared.ts +115 -73
  299. package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
  300. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
  301. package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
  302. package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
  303. package/src/providers/integrations/claude/claude.ts +606 -582
  304. package/src/providers/integrations/cline/cline-quota.ts +18 -16
  305. package/src/providers/integrations/cline/cline.ts +539 -468
  306. package/src/providers/integrations/codex/codex.ts +5 -8
  307. package/src/providers/integrations/commandcode/commandcode.ts +675 -541
  308. package/src/providers/integrations/commandcode/discovery.ts +116 -0
  309. package/src/providers/integrations/commandcode/quota.ts +160 -0
  310. package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
  311. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
  312. package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
  313. package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
  314. package/src/providers/integrations/cursor/cursor-request.ts +471 -0
  315. package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
  316. package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
  317. package/src/providers/integrations/cursor/cursor.ts +960 -0
  318. package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
  319. package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
  320. package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
  321. package/src/providers/integrations/factory/factory-oauth.ts +7 -11
  322. package/src/providers/integrations/github/github.ts +4 -4
  323. package/src/providers/integrations/hermes.ts +79 -8
  324. package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
  325. package/src/providers/integrations/kilo/kilo.ts +153 -146
  326. package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
  327. package/src/providers/integrations/opencode/provider.ts +532 -499
  328. package/src/providers/integrations/openrouter/provider.ts +139 -281
  329. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
  330. package/src/providers/integrations/qoder/qoder-oauth.ts +310 -263
  331. package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
  332. package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
  333. package/src/providers/integrations/qoder/qoder.ts +865 -730
  334. package/src/providers/integrations/shared/install-id.ts +29 -2
  335. package/src/providers/integrations/trae/trae-catalog.ts +190 -0
  336. package/src/providers/integrations/trae/trae-headers.ts +149 -0
  337. package/src/providers/integrations/trae/trae-oauth.ts +773 -0
  338. package/src/providers/integrations/trae/trae-protocol.ts +400 -0
  339. package/src/providers/integrations/trae/trae-realm.ts +220 -0
  340. package/src/providers/integrations/trae/trae.ts +458 -0
  341. package/src/providers/operations/account-health-service.ts +107 -10
  342. package/src/providers/operations/client-versions.ts +143 -6
  343. package/src/providers/operations/model-catalog-cache.ts +18 -3
  344. package/src/providers/operations/provider-catalog-seeder.ts +95 -86
  345. package/src/providers/operations/provider-catalog-service.ts +76 -0
  346. package/src/providers/operations/provider-credential-service.ts +46 -7
  347. package/src/providers/provider-contracts.ts +76 -0
  348. package/src/providers/provider-metadata.ts +78 -6
  349. package/src/providers/provider-model-definition.ts +373 -349
  350. package/src/security/api-key-auth.ts +126 -8
  351. package/src/transport/contracts.ts +3 -0
  352. package/src/transport/dispatch/attempt-finalize.ts +39 -2
  353. package/src/transport/dispatch/attempt-loop.ts +530 -513
  354. package/src/transport/dispatch/empty-stream.ts +17 -26
  355. package/src/transport/dispatch/generation.ts +88 -21
  356. package/src/transport/dispatch/leases.ts +99 -2
  357. package/src/transport/dispatch/proxy-request.ts +60 -3
  358. package/src/transport/dispatch/streaming/finalize.ts +16 -1
  359. package/src/transport/dispatch/streaming/stream.ts +22 -2
  360. package/src/transport/dispatch/upstream.ts +19 -1
  361. package/src/transport/failure-policy.ts +20 -0
  362. package/src/transport/guards/auth-guard.ts +26 -1
  363. package/src/transport/guards/body-policy.ts +5 -4
  364. package/src/transport/request/preparer.ts +103 -0
  365. package/src/transport/request/state.ts +14 -0
  366. package/src/transport/routing/plan-gate.ts +45 -0
  367. package/src/transport/routing/route-catalog.ts +799 -738
  368. package/src/transport/routing/route-model.ts +52 -1
  369. package/src/transport/routing/router.ts +51 -0
  370. package/src/transport/surface/chat/encode.ts +18 -31
  371. package/src/transport/surface/messages/stream.ts +3 -2
  372. package/src/transport/translation/tool-repair.ts +37 -16
  373. package/src/workers/account-identity-sweep.ts +14 -12
  374. package/src/workers/daily-checkin.ts +62 -0
  375. package/src/workers/quota-refresh-sweep.ts +27 -2
  376. package/dist/dashboard/assets/BannedPage-DUrXpp-X.js +0 -1
  377. package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
  378. package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
  379. package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
  380. package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
  381. package/dist/dashboard/assets/OverviewPage-C3NIzsDx.js +0 -1
  382. package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
  383. package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
  384. package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
  385. package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
  386. package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
  387. package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
  388. package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
  389. package/dist/dashboard/assets/UsagePage-Djes-Epy.js +0 -56
  390. package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
  391. package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
  392. package/dist/dashboard/assets/assert-Bwh6DJTK.js +0 -1
  393. package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
  394. package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
  395. package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
  396. package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
  397. package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
  398. package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
  399. package/dist/dashboard/assets/model-bans-CVvHZ-qz.js +0 -1
  400. package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
  401. package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
  402. package/dist/dashboard/assets/routing-jntw9-Ui.js +0 -1
  403. package/dist/dashboard/assets/select-CGUuTQmj.js +0 -41
  404. package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
  405. package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
  406. package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
  407. package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
  408. package/scripts/cli/cli.ts +0 -771
  409. package/scripts/operations/probe-egress.ts +0 -335
  410. package/scripts/operations/restart.ts +0 -120
  411. package/scripts/shared/env.ts +0 -269
  412. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
  413. /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
  414. /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
  415. /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
@@ -0,0 +1,94 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { resolveUserConfigDir } from "../../src/config/package-paths";
3
+ import { ENV_PATH, readEnvFile } from "./support/env";
4
+ import { MINIMUM_BUN_VERSION, assertBunVersion } from "./support/bun";
5
+ import { printUpdateInstructions } from "./command/update";
6
+ import { reportTrayStartup } from "./support/startup-signal";
7
+
8
+ process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
9
+
10
+ const HELP = `Cartethyia ${MINIMUM_BUN_VERSION}+ (Bun is required)
11
+
12
+ Usage:
13
+ carte start, then show an arrow-key menu (no GUI needed)
14
+ carte --tray start with a system-tray icon instead
15
+ cartethyia setup [--non-interactive]
16
+ cartethyia start
17
+ cartethyia menu
18
+ cartethyia doctor
19
+ cartethyia update
20
+
21
+ carte starts the gateway in the background and leaves only the console menu on
22
+ your terminal — Open Console, Reset password, Backup, Update, Hide to tray,
23
+ Exit. Arrow keys and Enter; no desktop environment needed, so it works over SSH
24
+ and headless. Exiting the menu drains the gateway.
25
+ carte --tray uses a system-tray icon instead, which needs a graphical session.
26
+ Stop a gateway whose terminal is gone with Ctrl-C in that terminal.
27
+ Setup explains Lite (embedded SQLite) and Full (external PostgreSQL), then saves
28
+ configuration under the current user's Cartethyia config directory.
29
+ `;
30
+
31
+ /** Loads the user's `.env` into the environment without overriding it. */
32
+ async function loadUserConfig(): Promise<void> {
33
+ const configDir = process.env.CARTETHYIA_CONFIG_DIR;
34
+ if (!configDir) throw new Error("Cartethyia config directory could not be resolved");
35
+ mkdirSync(configDir, { recursive: true });
36
+ const values = await readEnvFile(ENV_PATH);
37
+ for (const [key, value] of Object.entries(values)) {
38
+ if (!process.env[key]) process.env[key] = value;
39
+ }
40
+ }
41
+
42
+ async function main(): Promise<void> {
43
+ assertBunVersion();
44
+ const [command] = process.argv.slice(2);
45
+ if (command === "setup" || command === "start" || command === "tray" || command === "menu" || command === "doctor") {
46
+ await loadUserConfig();
47
+ }
48
+
49
+ switch (command) {
50
+ case "setup": {
51
+ const { setup } = await import("./command/setup");
52
+ await setup();
53
+ return;
54
+ }
55
+ case "start": {
56
+ const { startForeground } = await import("./command/start");
57
+ await startForeground();
58
+ return;
59
+ }
60
+ case "menu": {
61
+ const { startMenu } = await import("./command/console-menu");
62
+ await startMenu();
63
+ return;
64
+ }
65
+ case "tray": {
66
+ const { startTray } = await import("./command/tray");
67
+ await startTray();
68
+ return;
69
+ }
70
+ case "doctor": {
71
+ const { doctor } = await import("./command/doctor");
72
+ await doctor();
73
+ return;
74
+ }
75
+ case "update":
76
+ printUpdateInstructions("this install", null);
77
+ return;
78
+ case "--help":
79
+ case "-h":
80
+ case undefined:
81
+ console.log(HELP);
82
+ return;
83
+ default:
84
+ console.error(`Unknown command: ${command}\n\n${HELP}`);
85
+ process.exitCode = 2;
86
+ }
87
+ }
88
+
89
+ main().catch((error: unknown) => {
90
+ const message = error instanceof Error ? error.message : "unknown error";
91
+ if (process.argv[2] === "tray") reportTrayStartup({ status: "failed", message });
92
+ console.error(`Cartethyia failed: ${message}`);
93
+ process.exitCode = 1;
94
+ });
@@ -0,0 +1,27 @@
1
+ /** Minimum Bun the repository declares in `package.json` `engines`. */
2
+ export const MINIMUM_BUN_VERSION = "1.4.0";
3
+
4
+ /**
5
+ * True when `actual` is at least `minimum`, compared as dotted integers.
6
+ * A non-numeric segment reads as 0, so a malformed version fails closed
7
+ * against a real minimum instead of passing.
8
+ */
9
+ export function bunVersionAtLeast(actual: string, minimum: string): boolean {
10
+ const left = actual.split(".").map((part) => Number.parseInt(part, 10) || 0);
11
+ const right = minimum.split(".").map((part) => Number.parseInt(part, 10) || 0);
12
+ const length = Math.max(left.length, right.length);
13
+ for (let i = 0; i < length; i += 1) {
14
+ const a = left[i] ?? 0;
15
+ const b = right[i] ?? 0;
16
+ if (a !== b) return a > b;
17
+ }
18
+ return true;
19
+ }
20
+
21
+ /** Throws unless the running Bun satisfies the declared minimum. */
22
+ export function assertBunVersion(): void {
23
+ if (bunVersionAtLeast(Bun.version, MINIMUM_BUN_VERSION)) return;
24
+ throw new Error(
25
+ `Bun ${MINIMUM_BUN_VERSION} or newer is required (found ${Bun.version}). Install it from https://bun.sh and run this command again.`,
26
+ );
27
+ }
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import { resolvePackagePath } from "../../src/config/package-paths";
1
+ import { resolvePackagePath } from "../../../src/config/package-paths";
2
2
 
3
3
  /** Reads the version from the installed package manifest. */
4
4
  export async function readInstalledVersion(): Promise<string> {
@@ -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()