cartethyia 2.5.9 → 2.6.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 (386) hide show
  1. package/.env.example +15 -42
  2. package/README.md +11 -10
  3. package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
  27. package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
  28. package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
  38. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
  39. package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
  40. package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
  41. package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
  42. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  43. package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
  44. package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
  45. package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
  46. package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
  48. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
  50. package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
  52. package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
  54. package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
  60. package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
  62. package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
  66. package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
  68. package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
  72. package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
  74. package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
  76. package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
  77. package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
  78. package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
  80. package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
  82. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
  86. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
  92. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
  94. package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
  96. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
  98. package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
  100. package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
  102. package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  105. package/dist/dashboard/index.html +1 -1
  106. package/dist/dashboard/index.html.gz +0 -0
  107. package/dist/dashboard/precompressed-assets.json +51 -51
  108. package/migrations/README.md +52 -52
  109. package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
  110. package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
  111. package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
  112. package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  113. package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
  114. package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
  115. package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
  116. package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  117. package/package.json +96 -97
  118. package/scripts/cli/admin.ts +126 -125
  119. package/scripts/cli/autostart.ts +237 -237
  120. package/scripts/cli/background.ts +120 -110
  121. package/scripts/cli/bin/carte.cjs +8 -8
  122. package/scripts/cli/bin/cartethyia.cjs +167 -167
  123. package/scripts/cli/console-menu.ts +308 -275
  124. package/scripts/cli/controller.ts +145 -0
  125. package/scripts/cli/doctor.ts +1 -1
  126. package/scripts/cli/env.ts +99 -99
  127. package/scripts/cli/gateway.ts +264 -261
  128. package/scripts/{health.ts → cli/health.ts} +4 -4
  129. package/scripts/cli/instance.ts +234 -224
  130. package/scripts/cli/main.ts +188 -164
  131. package/scripts/cli/platform.ts +64 -64
  132. package/scripts/cli/prompt.ts +275 -107
  133. package/scripts/cli/services.ts +88 -88
  134. package/scripts/cli/setup.ts +445 -445
  135. package/scripts/cli/start.ts +85 -82
  136. package/scripts/cli/startup-signal.ts +23 -23
  137. package/scripts/cli/telemetry.ts +70 -0
  138. package/scripts/cli/tray/index.ts +263 -263
  139. package/scripts/cli/tray/windows.ps1 +107 -107
  140. package/scripts/cli/tray.ts +192 -208
  141. package/scripts/cli/update.ts +39 -39
  142. package/scripts/cli/version.ts +67 -67
  143. package/scripts/cli/wordmark.ts +58 -58
  144. package/src/boot/app.ts +16 -1
  145. package/src/boot/dependencies.ts +128 -25
  146. package/src/config/package-paths.ts +24 -0
  147. package/src/config/resources.ts +137 -0
  148. package/src/config/runtime.ts +133 -76
  149. package/src/config/spec.ts +30 -29
  150. package/src/config/telemetry.ts +0 -25
  151. package/src/console/api-keys/bucket-spend.ts +14 -17
  152. package/src/console/api-keys/contracts.ts +410 -410
  153. package/src/console/api-keys/routes.ts +12 -0
  154. package/src/console/audit/store.ts +2 -3
  155. package/src/console/auth/setup-service.ts +8 -1
  156. package/src/console/backup/routes.ts +7 -0
  157. package/src/console/backup/service.ts +463 -463
  158. package/src/console/backup/store.ts +16 -11
  159. package/src/console/backup/validate.ts +1 -4
  160. package/src/console/cli-tools/fs-ops.ts +18 -2
  161. package/src/console/data-management/contracts.ts +1 -8
  162. package/src/console/data-management/operations.ts +61 -48
  163. package/src/console/data-management/routes.ts +4 -2
  164. package/src/console/domain-registration.ts +25 -3
  165. package/src/console/inferhub/client.ts +139 -0
  166. package/src/console/inferhub/key-store.ts +129 -0
  167. package/src/console/inferhub/normalize.ts +347 -0
  168. package/src/console/inferhub/routes.ts +411 -0
  169. package/src/console/inferhub/store.ts +109 -0
  170. package/src/console/inferhub/types.ts +183 -0
  171. package/src/console/observability/contracts.ts +2 -41
  172. package/src/console/observability/store.ts +176 -371
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/store.ts +23 -36
  175. package/src/console/quota/account-quota-shared.ts +52 -52
  176. package/src/console/quota/account-quota-tenant.ts +16 -24
  177. package/src/console/quota/refresh.ts +441 -441
  178. package/src/console/remote-support/service.ts +270 -114
  179. package/src/console/routing/pools/import-parse.ts +206 -206
  180. package/src/console/routing/pools/operations.ts +543 -543
  181. package/src/console/routing/pools/probe-verdict.ts +132 -132
  182. package/src/console/routing/pools/routes.ts +181 -181
  183. package/src/console/routing/pools/store.ts +1 -1
  184. package/src/console/settings/contracts.ts +42 -40
  185. package/src/console/settings/store.ts +25 -25
  186. package/src/console/share/share-stats.ts +22 -4
  187. package/src/console/share/share-usage.ts +30 -4
  188. package/src/console/shared/cursor.ts +15 -0
  189. package/src/console/updates/repository-updates.ts +40 -39
  190. package/src/console/updates/routes.ts +12 -0
  191. package/src/main.ts +13 -5
  192. package/src/network/pool/resolver.ts +1104 -1104
  193. package/src/observability/client-name.ts +22 -0
  194. package/src/observability/event-log.ts +5 -29
  195. package/src/observability/event-query.ts +690 -0
  196. package/src/observability/log-ring.ts +26 -2
  197. package/src/observability/log-sanitize.ts +118 -0
  198. package/src/observability/logger.ts +17 -13
  199. package/src/observability/metrics.ts +35 -0
  200. package/src/observability/payload-capture.ts +114 -41
  201. package/src/observability/payload-store.ts +249 -24
  202. package/src/observability/raw-event-writer.ts +118 -0
  203. package/src/observability/runtime-metrics.ts +71 -1
  204. package/src/observability/telemetry-buffer.ts +100 -17
  205. package/src/observability/telemetry-reconciliation.ts +417 -0
  206. package/src/persistence/boot.ts +20 -9
  207. package/src/persistence/db-pg.ts +13 -0
  208. package/src/persistence/db-sqlite.ts +569 -497
  209. package/src/persistence/dialect.ts +3 -2
  210. package/src/persistence/migrate.ts +181 -181
  211. package/src/persistence/schema.ts +1496 -1406
  212. package/src/persistence/shared-cache.ts +5 -1
  213. package/src/persistence/stores/api-key-store.ts +581 -581
  214. package/src/persistence/stores/telemetry-store.ts +124 -63
  215. package/src/protocol/reasoning-text.ts +30 -0
  216. package/src/protocol/registry.ts +2 -0
  217. package/src/protocol/response/chat.ts +33 -7
  218. package/src/protocol/response/codex.ts +14 -2
  219. package/src/protocol/response/messages.ts +5 -2
  220. package/src/providers/authentication/oauth-flow-store.ts +3 -10
  221. package/src/providers/bundled-discovery-providers.ts +122 -122
  222. package/src/providers/default-registry.ts +14 -0
  223. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  224. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  225. package/src/providers/discovery/probe-phases.ts +43 -7
  226. package/src/providers/discovery/probing-service.ts +70 -32
  227. package/src/providers/integrations/antigravity/antigravity.ts +9 -2
  228. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
  231. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
  232. package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
  233. package/src/providers/integrations/buddy/capabilities.ts +4 -0
  234. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
  235. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
  236. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
  237. package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
  238. package/src/providers/integrations/claude/claude-quota.ts +2 -2
  239. package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
  240. package/src/providers/integrations/claude/claude.ts +3 -4
  241. package/src/providers/integrations/cline/cline.ts +94 -8
  242. package/src/providers/integrations/codex/codex.ts +61 -37
  243. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
  244. package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
  245. package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
  246. package/src/providers/integrations/cursor/cursor-request.ts +11 -0
  247. package/src/providers/integrations/cursor/cursor.ts +29 -17
  248. package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
  249. package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
  250. package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
  251. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
  252. package/src/providers/integrations/github/github.ts +3 -1
  253. package/src/providers/integrations/grok/grok-quota.ts +279 -279
  254. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
  255. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
  256. package/src/providers/integrations/hermes.ts +7 -1
  257. package/src/providers/integrations/inferhub.ts +201 -73
  258. package/src/providers/integrations/kilo/kilo.ts +152 -152
  259. package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
  260. package/src/providers/integrations/opencode/provider.ts +2 -0
  261. package/src/providers/integrations/openrouter/provider.ts +129 -129
  262. package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
  263. package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
  264. package/src/providers/integrations/trae/trae-headers.ts +17 -48
  265. package/src/providers/integrations/trae/trae-oauth.ts +4 -26
  266. package/src/providers/integrations/trae/trae-quota.ts +389 -384
  267. package/src/providers/integrations/trae/trae-realm.ts +15 -2
  268. package/src/providers/integrations/trae/trae.ts +22 -24
  269. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
  270. package/src/providers/integrations/zcode/oauth.ts +1 -1
  271. package/src/providers/operations/account-health-service.ts +605 -279
  272. package/src/providers/provider-metadata.ts +12 -1
  273. package/src/providers/provider-model-definition.ts +373 -373
  274. package/src/providers/provider-probe.ts +3 -0
  275. package/src/providers/quota/quota-contracts.ts +3 -7
  276. package/src/providers/search/search-provider.ts +2 -6
  277. package/src/security/admission/contracts.ts +10 -0
  278. package/src/security/admission/in-memory-store.ts +89 -14
  279. package/src/security/admission/lua.ts +191 -182
  280. package/src/security/admission/redis-store.ts +30 -0
  281. package/src/security/admission/service.ts +18 -0
  282. package/src/security/api-key-auth-cache.ts +96 -0
  283. package/src/security/api-key-auth.ts +11 -100
  284. package/src/security/client-router-fingerprint.ts +3 -7
  285. package/src/security/ip-boundary.ts +7 -3
  286. package/src/shared/coerce.ts +67 -0
  287. package/src/shared/hash.ts +6 -0
  288. package/src/shared/time.ts +7 -0
  289. package/src/transport/dispatch/attempt-finalize.ts +26 -138
  290. package/src/transport/dispatch/attempt-loop.ts +103 -81
  291. package/src/transport/dispatch/capture-policy.ts +78 -0
  292. package/src/transport/dispatch/proxy-request.ts +4 -2
  293. package/src/transport/dispatch/streaming/encoder.ts +111 -5
  294. package/src/transport/dispatch/streaming/stream.ts +19 -2
  295. package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
  296. package/src/transport/failure-policy.ts +27 -0
  297. package/src/transport/guards/abuse-guard.ts +2 -2
  298. package/src/transport/guards/auth-guard.ts +17 -0
  299. package/src/transport/guards/body-policy.ts +3 -2
  300. package/src/transport/guards/concurrency-guard.ts +89 -13
  301. package/src/transport/guards/early-rejection-capture.ts +43 -3
  302. package/src/transport/guards/error-lifecycle.ts +10 -2
  303. package/src/transport/guards/guard-errors.ts +23 -0
  304. package/src/transport/guards/headroom-guard.ts +41 -7
  305. package/src/transport/guards/pipeline.ts +24 -0
  306. package/src/transport/guards/request-context.ts +2 -1
  307. package/src/transport/routing/admission.ts +51 -9
  308. package/src/transport/routing/bounded-lru.ts +49 -0
  309. package/src/transport/routing/candidate-index.ts +88 -0
  310. package/src/transport/routing/reservations.ts +121 -7
  311. package/src/transport/routing/round-robin.ts +10 -22
  312. package/src/transport/routing/route-catalog.ts +26 -13
  313. package/src/transport/routing/route-model.ts +22 -2
  314. package/src/transport/routing/router.ts +53 -31
  315. package/src/transport/surface/chat/encode.ts +2 -3
  316. package/src/transport/surface/chat/parse.ts +2 -1
  317. package/src/transport/surface/dialects.ts +11 -3
  318. package/src/transport/surface/messages/encode.ts +2 -1
  319. package/src/transport/surface/messages/parse.ts +3 -1
  320. package/src/transport/surface/messages/stream.ts +2 -6
  321. package/src/workers/maintenance-lease.ts +200 -0
  322. package/src/workers/scheduler.ts +101 -1
  323. package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
  324. package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
  325. package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
  326. package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
  327. package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
  328. package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
  329. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
  330. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
  331. package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
  332. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  333. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
  334. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
  335. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
  336. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
  337. package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
  338. package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
  339. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
  340. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
  341. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
  342. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
  343. package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
  344. package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
  345. package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
  346. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  347. package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
  348. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  349. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  350. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  351. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  352. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  353. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  354. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  355. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  356. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  357. package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
  358. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
  360. package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
  361. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  363. package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  365. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  367. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  368. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  369. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  370. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  371. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  372. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  373. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  374. package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
  375. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  376. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  377. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  378. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  379. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  380. package/scripts/build/aot.ts +0 -143
  381. package/scripts/build/build.ts +0 -24
  382. package/scripts/build/compile.ts +0 -88
  383. package/scripts/build/restart.ts +0 -120
  384. package/scripts/build/start.ts +0 -25
  385. package/src/observability/event-compaction.ts +0 -317
  386. package/src/observability/event-day-index.ts +0 -440
@@ -1,110 +1,120 @@
1
- import { spawn } from "node:child_process";
2
- import { readFileSync, unlinkSync } from "node:fs";
3
- import { PACKAGE_ROOT, resolvePackagePath } from "../../src/config/package-paths";
4
-
5
- /** Opens a URL in the operator's default browser, ignoring a missing handler. */
6
- export function openUrl(url: string): Promise<void> {
7
- const command = process.platform === "win32"
8
- ? "explorer.exe"
9
- : process.platform === "darwin"
10
- ? "open"
11
- : "xdg-open";
12
- const child = Bun.spawn([command, url], {
13
- stdin: "ignore",
14
- stdout: "ignore",
15
- stderr: "ignore",
16
- });
17
- return child.exited.then(() => undefined);
18
- }
19
-
20
- /**
21
- * Starts a detached process and resolves once it reports readiness.
22
- *
23
- * The child owns no stdio, so a signal file is the only channel that survives
24
- * the parent. Without it the caller prints "running in the background" for a
25
- * child that may already have died — which is exactly what a port clash looks
26
- * like, because the child's own startup probe fails while the parent's gateway
27
- * still holds the port.
28
- *
29
- * Resolves with the URL the child reported. Rejects with the child's message
30
- * when it reports failure, or with a timeout if it never reports at all.
31
- */
32
- export function launchBackgroundWithStatus(
33
- args: readonly string[],
34
- options: { readonly signalPath: string; readonly timeoutMs?: number } = { signalPath: "" },
35
- ): Promise<string> {
36
- return new Promise<string>((resolve, reject) => {
37
- const signalPath = options.signalPath;
38
- const deadline = Date.now() + (options.timeoutMs ?? 60_000);
39
- let settled = false;
40
- const finish = (error: Error | null, url?: string): void => {
41
- if (settled) return;
42
- settled = true;
43
- clearInterval(timer);
44
- try {
45
- unlinkSync(signalPath);
46
- } catch {}
47
- if (error) reject(error);
48
- else resolve(url ?? "");
49
- };
50
-
51
- const env = { ...process.env, CARTETHYIA_TRAY_STARTUP_SIGNAL: signalPath };
52
- spawnBackground(args, env);
53
-
54
- const timer = setInterval(() => {
55
- if (settled) return;
56
- let raw: string;
57
- try {
58
- raw = readFileSync(signalPath, "utf8");
59
- } catch {
60
- if (Date.now() >= deadline) finish(new Error("the background process did not report startup within the deadline"));
61
- return;
62
- }
63
- try {
64
- const signal = JSON.parse(raw) as { status?: string; url?: string; message?: string };
65
- if (signal.status === "ready" && typeof signal.url === "string") finish(null, signal.url);
66
- else if (signal.status === "failed") finish(new Error(signal.message ?? "the background process failed to start"));
67
- } catch {
68
- // A partially written file is read again on the next tick.
69
- }
70
- }, 150);
71
- timer.unref?.();
72
- });
73
- }
74
-
75
- /**
76
- * Starts a Bun process that owns no console window.
77
- *
78
- * On Windows, spawning the child directly is not enough: a console-subsystem
79
- * child of a window-less parent allocates a new console, and when the parent is
80
- * inside Windows Terminal the tab is already on screen before any flag can take
81
- * effect — measured: `detached: true` plus `windowsHide: true` still produced a
82
- * Windows Terminal window titled with the bun path. `wscript.exe` is a
83
- * GUI-subsystem host, so it never allocates a console, and a child launched
84
- * from it with window style 0 inherits that. The VBS is that launcher.
85
- *
86
- * Elsewhere `detached` is enough and the helper is not used.
87
- */
88
- export function launchBackground(args: readonly string[]): void {
89
- spawnBackground(args, process.env);
90
- }
91
-
92
- function spawnBackground(args: readonly string[], env: NodeJS.ProcessEnv): void {
93
- if (process.platform !== "win32") {
94
- const child = spawn(process.execPath, [...args], {
95
- cwd: PACKAGE_ROOT,
96
- detached: true,
97
- stdio: "ignore",
98
- env,
99
- });
100
- child.unref();
101
- return;
102
- }
103
- const launcher = resolvePackagePath("scripts", "cli", "tray", "hidden-window.vbs");
104
- const child = spawn(
105
- "wscript.exe",
106
- ["//nologo", launcher, process.execPath, ...args],
107
- { cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", windowsHide: true, env },
108
- );
109
- child.unref();
110
- }
1
+ import { spawn } from "node:child_process";
2
+ import { readFileSync, unlinkSync } from "node:fs";
3
+ import { PACKAGE_ROOT, resolvePackagePath } from "../../src/config/package-paths";
4
+
5
+ /** Opens a URL in the operator's default browser, ignoring a missing handler. */
6
+ export function openUrl(url: string): Promise<void> {
7
+ const command = process.platform === "win32"
8
+ ? "explorer.exe"
9
+ : process.platform === "darwin"
10
+ ? "open"
11
+ : "xdg-open";
12
+ const child = Bun.spawn([command, url], {
13
+ stdin: "ignore",
14
+ stdout: "ignore",
15
+ stderr: "ignore",
16
+ });
17
+ return child.exited.then(() => undefined);
18
+ }
19
+
20
+ /**
21
+ * Starts a detached process and resolves once it reports readiness.
22
+ *
23
+ * The child owns no stdio, so a signal file is the only channel that survives
24
+ * the parent. Without it the caller prints "running in the background" for a
25
+ * child that may already have died — which is exactly what a port clash looks
26
+ * like, because the child's own startup probe fails while the parent's gateway
27
+ * still holds the port.
28
+ *
29
+ * Resolves with the URL the child reported. Rejects with the child's message
30
+ * when it reports failure, or with a timeout if it never reports at all.
31
+ */
32
+ export function launchBackgroundWithStatus(
33
+ args: readonly string[],
34
+ options: {
35
+ readonly signalPath: string;
36
+ readonly timeoutMs?: number;
37
+ readonly controllerHandoff?: { readonly token: string; readonly parentPid: number };
38
+ } = { signalPath: "" },
39
+ ): Promise<string> {
40
+ return new Promise<string>((resolve, reject) => {
41
+ const signalPath = options.signalPath;
42
+ const deadline = Date.now() + (options.timeoutMs ?? 60_000);
43
+ let settled = false;
44
+ const finish = (error: Error | null, url?: string): void => {
45
+ if (settled) return;
46
+ settled = true;
47
+ clearInterval(timer);
48
+ try {
49
+ unlinkSync(signalPath);
50
+ } catch {}
51
+ if (error) reject(error);
52
+ else resolve(url ?? "");
53
+ };
54
+
55
+ const env = {
56
+ ...process.env,
57
+ CARTETHYIA_TRAY_STARTUP_SIGNAL: signalPath,
58
+ ...(options.controllerHandoff ? {
59
+ CARTETHYIA_CONTROLLER_TOKEN: options.controllerHandoff.token,
60
+ CARTETHYIA_CONTROLLER_PARENT_PID: String(options.controllerHandoff.parentPid),
61
+ } : {}),
62
+ };
63
+ spawnBackground(args, env);
64
+
65
+ const timer = setInterval(() => {
66
+ if (settled) return;
67
+ let raw: string;
68
+ try {
69
+ raw = readFileSync(signalPath, "utf8");
70
+ } catch {
71
+ if (Date.now() >= deadline) finish(new Error("the background process did not report startup within the deadline"));
72
+ return;
73
+ }
74
+ try {
75
+ const signal = JSON.parse(raw) as { status?: string; url?: string; message?: string };
76
+ if (signal.status === "ready" && typeof signal.url === "string") finish(null, signal.url);
77
+ else if (signal.status === "failed") finish(new Error(signal.message ?? "the background process failed to start"));
78
+ } catch {
79
+ // A partially written file is read again on the next tick.
80
+ }
81
+ }, 150);
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Starts a Bun process that owns no console window.
87
+ *
88
+ * On Windows, spawning the child directly is not enough: a console-subsystem
89
+ * child of a window-less parent allocates a new console, and when the parent is
90
+ * inside Windows Terminal the tab is already on screen before any flag can take
91
+ * effect — measured: `detached: true` plus `windowsHide: true` still produced a
92
+ * Windows Terminal window titled with the bun path. `wscript.exe` is a
93
+ * GUI-subsystem host, so it never allocates a console, and a child launched
94
+ * from it with window style 0 inherits that. The VBS is that launcher.
95
+ *
96
+ * Elsewhere `detached` is enough and the helper is not used.
97
+ */
98
+ export function launchBackground(args: readonly string[]): void {
99
+ spawnBackground(args, process.env);
100
+ }
101
+
102
+ function spawnBackground(args: readonly string[], env: NodeJS.ProcessEnv): void {
103
+ if (process.platform !== "win32") {
104
+ const child = spawn(process.execPath, [...args], {
105
+ cwd: PACKAGE_ROOT,
106
+ detached: true,
107
+ stdio: "ignore",
108
+ env,
109
+ });
110
+ child.unref();
111
+ return;
112
+ }
113
+ const launcher = resolvePackagePath("scripts", "cli", "tray", "hidden-window.vbs");
114
+ const child = spawn(
115
+ "wscript.exe",
116
+ ["//nologo", launcher, process.execPath, ...args],
117
+ { cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", windowsHide: true, env },
118
+ );
119
+ child.unref();
120
+ }
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env node
2
-
3
- // Default to the arrow-key menu so `carte` works without a GUI; on Windows
4
- // especially, the tray icon can land in the hidden-icons overflow and never
5
- // be seen. `carte --tray` still opts into the system-tray icon.
6
- if (process.argv.length <= 2) process.argv.push("menu");
7
- else if (process.argv[2] === "--tray") process.argv[2] = "tray";
8
- require("./cartethyia.cjs");
1
+ #!/usr/bin/env node
2
+
3
+ // Default to the arrow-key menu so `carte` works without a GUI; on Windows
4
+ // especially, the tray icon can land in the hidden-icons overflow and never
5
+ // be seen. `carte --tray` still opts into the system-tray icon.
6
+ if (process.argv.length <= 2) process.argv.push("menu");
7
+ else if (process.argv[2] === "--tray") process.argv[2] = "tray";
8
+ require("./cartethyia.cjs");
@@ -1,167 +1,167 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const fs = require("node:fs");
5
- const { randomUUID } = require("node:crypto");
6
- const os = require("node:os");
7
- const path = require("node:path");
8
- const { spawn } = require("node:child_process");
9
-
10
- const packageRoot = path.resolve(__dirname, "..", "..", "..");
11
- const cliPath = path.join(packageRoot, "scripts", "cli", "main.ts");
12
- const configDir = process.env.CARTETHYIA_CONFIG_DIR || (
13
- process.platform === "win32"
14
- ? path.join(process.env.APPDATA || os.homedir(), "Cartethyia")
15
- : process.platform === "darwin"
16
- ? path.join(os.homedir(), "Library", "Application Support", "Cartethyia")
17
- : path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "Cartethyia")
18
- );
19
- const trayMode = process.argv[2] === "tray";
20
- // The launcher itself runs under Node, so `process.execPath` is node.exe — not
21
- // the Bun the child needs. Resolve bun from PATH instead; WScript.Shell does
22
- // not inherit this process's environment, so the child needs an absolute path.
23
- function resolveBunPath() {
24
- if (process.env.CARTETHYIA_BUN) return process.env.CARTETHYIA_BUN;
25
- const name = process.platform === "win32" ? "bun.exe" : "bun";
26
- for (const dir of (process.env.PATH || "").split(path.delimiter)) {
27
- if (!dir) continue;
28
- const candidate = path.join(dir, name);
29
- try {
30
- if (fs.statSync(candidate).isFile()) return candidate;
31
- } catch {}
32
- }
33
- return name;
34
- }
35
-
36
- const startupSignalPath = trayMode
37
- ? path.join(os.tmpdir(), `cartethyia-tray-${process.pid}-${randomUUID()}.json`)
38
- : null;
39
- let spawnError;
40
- // On Windows the tray must start with no console at all, and neither
41
- // `detached: true` nor `windowsHide: true` achieves that: a console-subsystem
42
- // child of a window-less parent still allocates a console, and inside Windows
43
- // Terminal the tab is already on screen before a flag can take effect
44
- // (measured). `wscript.exe` is GUI-subsystem, so it never allocates one, and a
45
- // child launched from it with window style 0 inherits that. Elsewhere
46
- // `detached: true` is enough.
47
- const useWindowsDetach = process.platform === "win32" && trayMode;
48
- const hiddenLauncher = path.join(packageRoot, "scripts", "cli", "tray", "hidden-window.vbs");
49
- const child = useWindowsDetach
50
- ? spawn(
51
- "wscript.exe",
52
- // Absolute path, not "bun": the VBS runs through WScript.Shell, which
53
- // does not inherit this process's PATH additions and resolves a bare
54
- // name against the system PATH only.
55
- ["//nologo", hiddenLauncher, resolveBunPath(), "run", cliPath, ...process.argv.slice(2)],
56
- {
57
- cwd: packageRoot,
58
- detached: true,
59
- stdio: "ignore",
60
- windowsHide: true,
61
- env: {
62
- ...process.env,
63
- CARTETHYIA_CONFIG_DIR: configDir,
64
- CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath,
65
- },
66
- },
67
- )
68
- : spawn(resolveBunPath(), ["run", cliPath, ...process.argv.slice(2)], {
69
- cwd: packageRoot,
70
- detached: trayMode,
71
- stdio: trayMode ? "ignore" : "inherit",
72
- windowsHide: trayMode,
73
- env: {
74
- ...process.env,
75
- CARTETHYIA_CONFIG_DIR: configDir,
76
- ...(trayMode ? { CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath } : {}),
77
- },
78
- });
79
-
80
- child.once("error", (error) => {
81
- spawnError = error;
82
- if (error.code === "ENOENT") {
83
- console.error("Cartethyia requires Bun 1.4.0 or newer. Install Bun from https://bun.sh, then run this command again.");
84
- } else {
85
- console.error(`Could not start Cartethyia's Bun runtime: ${error.message}`);
86
- }
87
- process.exitCode = 1;
88
- });
89
-
90
- if (trayMode) {
91
- child.unref();
92
- console.log("Loading Cartethyia…");
93
- let settled = false;
94
- const deadline = Date.now() + 60_000;
95
- const finish = (error, url) => {
96
- if (settled) return;
97
- settled = true;
98
- try {
99
- fs.unlinkSync(startupSignalPath);
100
- } catch {}
101
- if (error) {
102
- // `error.message` can be an empty string when the failure came from a
103
- // signal rather than a thrown message, which printed as a bare
104
- // "Cartethyia failed to start:" with no cause. Fall back to the code and
105
- // name what usually causes it.
106
- const detail =
107
- error.message && error.message.trim() !== ""
108
- ? error.message
109
- : `the process exited without a message (code ${child.exitCode ?? "unknown"})`;
110
- console.error(`Cartethyia failed to start: ${detail}`);
111
- console.error(
112
- "If another Cartethyia is already running on this port, stop the terminal that started it first.",
113
- );
114
- process.exitCode = 1;
115
- if (child.exitCode === null) child.kill();
116
- return;
117
- }
118
- console.log(`Cartethyia is ready at ${url}`);
119
- // The gateway child is already detached and unref'd; this Node process is
120
- // the only thing still holding the launching terminal open. Exit once the
121
- // URL has been shown so the shell prompt comes back.
122
- setTimeout(() => process.exit(0), 1_500);
123
- };
124
- const poll = () => {
125
- if (settled) return;
126
- if (fs.existsSync(startupSignalPath)) {
127
- try {
128
- const signal = JSON.parse(fs.readFileSync(startupSignalPath, "utf8"));
129
- if (signal.status === "ready" && typeof signal.url === "string") {
130
- finish(null, signal.url);
131
- return;
132
- }
133
- if (signal.status === "failed" && typeof signal.message === "string") {
134
- finish(new Error(signal.message));
135
- return;
136
- }
137
- } catch {}
138
- }
139
- if (spawnError) {
140
- finish(spawnError);
141
- return;
142
- }
143
- if (Date.now() >= deadline) {
144
- finish(new Error("startup did not complete within 60 seconds"));
145
- return;
146
- }
147
- setTimeout(poll, 150);
148
- };
149
- // On Windows the spawned process is `wscript.exe`, which hands off to the
150
- // tray and exits immediately — a successful handoff, not a failed start. Only
151
- // a direct spawn's exit says anything about the tray, so the deadline covers
152
- // the handoff case and this guard covers the direct one.
153
- if (!useWindowsDetach) {
154
- child.once("exit", (code, signal) => {
155
- if (!settled) setTimeout(() => finish(new Error(`the tray process exited before readiness (code ${code ?? signal ?? "unknown"})`)), 250);
156
- });
157
- }
158
- poll();
159
- } else {
160
- child.once("exit", (code, signal) => {
161
- if (signal) {
162
- process.kill(process.pid, signal);
163
- return;
164
- }
165
- process.exitCode = code ?? 1;
166
- });
167
- }
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const { randomUUID } = require("node:crypto");
6
+ const os = require("node:os");
7
+ const path = require("node:path");
8
+ const { spawn } = require("node:child_process");
9
+
10
+ const packageRoot = path.resolve(__dirname, "..", "..", "..");
11
+ const cliPath = path.join(packageRoot, "scripts", "cli", "main.ts");
12
+ const configDir = process.env.CARTETHYIA_CONFIG_DIR || (
13
+ process.platform === "win32"
14
+ ? path.join(process.env.APPDATA || os.homedir(), "Cartethyia")
15
+ : process.platform === "darwin"
16
+ ? path.join(os.homedir(), "Library", "Application Support", "Cartethyia")
17
+ : path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "Cartethyia")
18
+ );
19
+ const trayMode = process.argv[2] === "tray";
20
+ // The launcher itself runs under Node, so `process.execPath` is node.exe — not
21
+ // the Bun the child needs. Resolve bun from PATH instead; WScript.Shell does
22
+ // not inherit this process's environment, so the child needs an absolute path.
23
+ function resolveBunPath() {
24
+ if (process.env.CARTETHYIA_BUN) return process.env.CARTETHYIA_BUN;
25
+ const name = process.platform === "win32" ? "bun.exe" : "bun";
26
+ for (const dir of (process.env.PATH || "").split(path.delimiter)) {
27
+ if (!dir) continue;
28
+ const candidate = path.join(dir, name);
29
+ try {
30
+ if (fs.statSync(candidate).isFile()) return candidate;
31
+ } catch {}
32
+ }
33
+ return name;
34
+ }
35
+
36
+ const startupSignalPath = trayMode
37
+ ? path.join(os.tmpdir(), `cartethyia-tray-${process.pid}-${randomUUID()}.json`)
38
+ : null;
39
+ let spawnError;
40
+ // On Windows the tray must start with no console at all, and neither
41
+ // `detached: true` nor `windowsHide: true` achieves that: a console-subsystem
42
+ // child of a window-less parent still allocates a console, and inside Windows
43
+ // Terminal the tab is already on screen before a flag can take effect
44
+ // (measured). `wscript.exe` is GUI-subsystem, so it never allocates one, and a
45
+ // child launched from it with window style 0 inherits that. Elsewhere
46
+ // `detached: true` is enough.
47
+ const useWindowsDetach = process.platform === "win32" && trayMode;
48
+ const hiddenLauncher = path.join(packageRoot, "scripts", "cli", "tray", "hidden-window.vbs");
49
+ const child = useWindowsDetach
50
+ ? spawn(
51
+ "wscript.exe",
52
+ // Absolute path, not "bun": the VBS runs through WScript.Shell, which
53
+ // does not inherit this process's PATH additions and resolves a bare
54
+ // name against the system PATH only.
55
+ ["//nologo", hiddenLauncher, resolveBunPath(), "run", cliPath, ...process.argv.slice(2)],
56
+ {
57
+ cwd: packageRoot,
58
+ detached: true,
59
+ stdio: "ignore",
60
+ windowsHide: true,
61
+ env: {
62
+ ...process.env,
63
+ CARTETHYIA_CONFIG_DIR: configDir,
64
+ CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath,
65
+ },
66
+ },
67
+ )
68
+ : spawn(resolveBunPath(), ["run", cliPath, ...process.argv.slice(2)], {
69
+ cwd: packageRoot,
70
+ detached: trayMode,
71
+ stdio: trayMode ? "ignore" : "inherit",
72
+ windowsHide: trayMode,
73
+ env: {
74
+ ...process.env,
75
+ CARTETHYIA_CONFIG_DIR: configDir,
76
+ ...(trayMode ? { CARTETHYIA_TRAY_STARTUP_SIGNAL: startupSignalPath } : {}),
77
+ },
78
+ });
79
+
80
+ child.once("error", (error) => {
81
+ spawnError = error;
82
+ if (error.code === "ENOENT") {
83
+ console.error("Cartethyia requires Bun 1.4.0 or newer. Install Bun from https://bun.sh, then run this command again.");
84
+ } else {
85
+ console.error(`Could not start Cartethyia's Bun runtime: ${error.message}`);
86
+ }
87
+ process.exitCode = 1;
88
+ });
89
+
90
+ if (trayMode) {
91
+ child.unref();
92
+ console.log("Loading Cartethyia…");
93
+ let settled = false;
94
+ const deadline = Date.now() + 60_000;
95
+ const finish = (error, url) => {
96
+ if (settled) return;
97
+ settled = true;
98
+ try {
99
+ fs.unlinkSync(startupSignalPath);
100
+ } catch {}
101
+ if (error) {
102
+ // `error.message` can be an empty string when the failure came from a
103
+ // signal rather than a thrown message, which printed as a bare
104
+ // "Cartethyia failed to start:" with no cause. Fall back to the code and
105
+ // name what usually causes it.
106
+ const detail =
107
+ error.message && error.message.trim() !== ""
108
+ ? error.message
109
+ : `the process exited without a message (code ${child.exitCode ?? "unknown"})`;
110
+ console.error(`Cartethyia failed to start: ${detail}`);
111
+ console.error(
112
+ "If another Cartethyia is already running on this port, stop the terminal that started it first.",
113
+ );
114
+ process.exitCode = 1;
115
+ if (child.exitCode === null) child.kill();
116
+ return;
117
+ }
118
+ console.log(`Cartethyia is ready at ${url}`);
119
+ // The gateway child is already detached and unref'd; this Node process is
120
+ // the only thing still holding the launching terminal open. Exit once the
121
+ // URL has been shown so the shell prompt comes back.
122
+ setTimeout(() => process.exit(0), 1_500);
123
+ };
124
+ const poll = () => {
125
+ if (settled) return;
126
+ if (fs.existsSync(startupSignalPath)) {
127
+ try {
128
+ const signal = JSON.parse(fs.readFileSync(startupSignalPath, "utf8"));
129
+ if (signal.status === "ready" && typeof signal.url === "string") {
130
+ finish(null, signal.url);
131
+ return;
132
+ }
133
+ if (signal.status === "failed" && typeof signal.message === "string") {
134
+ finish(new Error(signal.message));
135
+ return;
136
+ }
137
+ } catch {}
138
+ }
139
+ if (spawnError) {
140
+ finish(spawnError);
141
+ return;
142
+ }
143
+ if (Date.now() >= deadline) {
144
+ finish(new Error("startup did not complete within 60 seconds"));
145
+ return;
146
+ }
147
+ setTimeout(poll, 150);
148
+ };
149
+ // On Windows the spawned process is `wscript.exe`, which hands off to the
150
+ // tray and exits immediately — a successful handoff, not a failed start. Only
151
+ // a direct spawn's exit says anything about the tray, so the deadline covers
152
+ // the handoff case and this guard covers the direct one.
153
+ if (!useWindowsDetach) {
154
+ child.once("exit", (code, signal) => {
155
+ if (!settled) setTimeout(() => finish(new Error(`the tray process exited before readiness (code ${code ?? signal ?? "unknown"})`)), 250);
156
+ });
157
+ }
158
+ poll();
159
+ } else {
160
+ child.once("exit", (code, signal) => {
161
+ if (signal) {
162
+ process.kill(process.pid, signal);
163
+ return;
164
+ }
165
+ process.exitCode = code ?? 1;
166
+ });
167
+ }