cartethyia 2.5.3 → 2.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
  25. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
  26. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  60. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  61. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  62. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  63. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  64. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
  66. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  68. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
  70. package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
  74. package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
  76. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
  78. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  79. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  80. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
  82. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
  84. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  85. package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
  86. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  87. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
  88. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  89. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  90. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
  94. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
  96. package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
  98. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
  100. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
  102. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  103. package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
  104. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  105. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  106. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  107. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  108. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  109. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  110. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  111. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
  112. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  113. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  114. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  115. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  116. package/dist/dashboard/index.html +1 -1
  117. package/dist/dashboard/index.html.gz +0 -0
  118. package/dist/dashboard/precompressed-assets.json +57 -57
  119. package/migrations/README.md +52 -52
  120. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  121. package/package.json +96 -95
  122. package/scripts/cli/admin.ts +125 -0
  123. package/scripts/cli/autostart.ts +237 -0
  124. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  125. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
  126. package/scripts/cli/doctor.ts +37 -0
  127. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  128. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  129. package/scripts/cli/instance.ts +224 -0
  130. package/scripts/cli/main.ts +88 -18
  131. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  132. package/scripts/cli/port.ts +100 -0
  133. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  134. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  135. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  136. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  137. package/scripts/cli/tray/index.ts +62 -12
  138. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  139. package/scripts/cli/update.ts +40 -0
  140. package/scripts/cli/version.ts +67 -0
  141. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  142. package/src/boot/app.ts +11 -7
  143. package/src/boot/dependencies.ts +50 -19
  144. package/src/boot/timeout.ts +2 -13
  145. package/src/config/cooldowns.ts +186 -138
  146. package/src/config/network.ts +8 -14
  147. package/src/config/providers.ts +7 -10
  148. package/src/config/runtime.ts +76 -76
  149. package/src/config/security.ts +15 -0
  150. package/src/config/spec.ts +52 -7
  151. package/src/config/telemetry.ts +26 -0
  152. package/src/config/timeouts.ts +0 -6
  153. package/src/console/admin/model-abuse/contracts.ts +28 -6
  154. package/src/console/admin/model-abuse/routes.ts +56 -3
  155. package/src/console/api-keys/bucket-spend.ts +15 -4
  156. package/src/console/api-keys/contracts.ts +410 -414
  157. package/src/console/api-keys/routes.ts +881 -880
  158. package/src/console/auth/credentials.ts +3 -3
  159. package/src/console/auth/setup-service.ts +2 -2
  160. package/src/console/backup/contracts.ts +15 -2
  161. package/src/console/backup/export-endpoint.ts +6 -2
  162. package/src/console/backup/nine-router.ts +2 -5
  163. package/src/console/backup/service.ts +4 -0
  164. package/src/console/backup/store.ts +192 -29
  165. package/src/console/backup/validate.ts +3 -45
  166. package/src/console/cli-tools/fs-ops.ts +487 -487
  167. package/src/console/console-router-guards.ts +1 -1
  168. package/src/console/console-router.ts +8 -0
  169. package/src/console/data-management/contracts.ts +100 -0
  170. package/src/console/data-management/operations.ts +234 -0
  171. package/src/console/data-management/routes.ts +103 -0
  172. package/src/console/domain-registration.ts +45 -1
  173. package/src/console/observability/contracts.ts +524 -485
  174. package/src/console/observability/live.ts +22 -0
  175. package/src/console/observability/store.ts +1138 -1018
  176. package/src/console/observability/usage-dimensions.ts +4 -4
  177. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  178. package/src/console/providers/catalog/contracts.ts +6 -0
  179. package/src/console/providers/catalog/model-operations.ts +5 -2
  180. package/src/console/providers/catalog/provider-operations.ts +24 -4
  181. package/src/console/providers/catalog/public-model-store.ts +8 -11
  182. package/src/console/providers/catalog/routes.ts +7 -0
  183. package/src/console/providers/catalog/store.ts +1553 -1532
  184. package/src/console/providers/oauth/routes.ts +8 -12
  185. package/src/console/quota/account-quota-shared.ts +52 -39
  186. package/src/console/quota/account-quota-tenant.ts +6 -6
  187. package/src/console/quota/quota-view.ts +1 -1
  188. package/src/console/quota/refresh.ts +441 -429
  189. package/src/console/remote-support/contracts.ts +61 -0
  190. package/src/console/remote-support/routes.ts +65 -0
  191. package/src/console/remote-support/service.ts +207 -0
  192. package/src/console/response-compression.ts +0 -3
  193. package/src/console/routing/pools/import-parse.ts +206 -0
  194. package/src/console/routing/pools/operations.ts +543 -501
  195. package/src/console/routing/pools/probe-result.ts +3 -2
  196. package/src/console/routing/pools/probe-verdict.ts +132 -132
  197. package/src/console/routing/pools/routes.ts +181 -164
  198. package/src/console/routing/pools/store.ts +8 -8
  199. package/src/console/settings/contracts.ts +95 -2
  200. package/src/console/settings/store.ts +81 -3
  201. package/src/console/shared/cursor.ts +8 -8
  202. package/src/console/shared/errors.ts +1 -3
  203. package/src/console/updates/routes.ts +16 -0
  204. package/src/main.ts +10 -0
  205. package/src/network/http2-bidi.ts +2 -4
  206. package/src/network/pool/agent.ts +478 -478
  207. package/src/network/pool/selector.ts +5 -4
  208. package/src/network/retry.ts +1 -2
  209. package/src/network/ssrf.ts +5 -4
  210. package/src/observability/event-compaction.ts +317 -0
  211. package/src/observability/event-day-index.ts +440 -0
  212. package/src/observability/event-log.ts +155 -6
  213. package/src/observability/logger.ts +6 -6
  214. package/src/observability/payload-capture.ts +158 -33
  215. package/src/observability/payload-store.ts +82 -61
  216. package/src/observability/redaction.ts +5 -6
  217. package/src/observability/request-notify.ts +43 -0
  218. package/src/observability/telemetry-status.ts +8 -9
  219. package/src/observability/token-speed.ts +5 -4
  220. package/src/persistence/db-mode.ts +7 -7
  221. package/src/persistence/health-event-flush.ts +36 -0
  222. package/src/persistence/readiness.ts +1 -1
  223. package/src/persistence/schema.ts +75 -29
  224. package/src/persistence/shared-cache.ts +1 -2
  225. package/src/persistence/stores/api-key-store.ts +18 -6
  226. package/src/persistence/stores/share-store.ts +4 -3
  227. package/src/persistence/stores/telemetry-store.ts +33 -11
  228. package/src/protocol/canonical-model.ts +11 -3
  229. package/src/protocol/codecs.ts +139 -6
  230. package/src/protocol/gateway-error.ts +6 -4
  231. package/src/protocol/image-clamp.ts +19 -1
  232. package/src/protocol/registry.ts +8 -0
  233. package/src/protocol/request/chat.ts +21 -1
  234. package/src/protocol/request/codex.ts +10 -1
  235. package/src/protocol/request/gemini.ts +9 -2
  236. package/src/protocol/request/messages.ts +18 -7
  237. package/src/protocol/request/responses.ts +9 -1
  238. package/src/protocol/response/chat.ts +34 -4
  239. package/src/protocol/response/codex.ts +7 -4
  240. package/src/protocol/response/dsml.ts +140 -28
  241. package/src/protocol/response/responses.ts +11 -5
  242. package/src/protocol/stream-error-frames.ts +4 -5
  243. package/src/providers/adapters/compatible-adapter.ts +3 -0
  244. package/src/providers/authentication/jwt-validator.ts +9 -10
  245. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  246. package/src/providers/bundled-discovery-providers.ts +122 -119
  247. package/src/providers/configured-openai-providers.ts +4 -4
  248. package/src/providers/default-registry.ts +5 -4
  249. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  250. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  251. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  252. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  253. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  254. package/src/providers/discovery/probe-phases.ts +4 -4
  255. package/src/providers/discovery/probe-wire.ts +6 -13
  256. package/src/providers/discovery/probing-service.ts +1117 -1045
  257. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  258. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  259. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  260. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  261. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  262. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  263. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  264. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  265. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  266. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  267. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  268. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  269. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  270. package/src/providers/integrations/claude/claude.ts +608 -608
  271. package/src/providers/integrations/cline/cline-quota.ts +27 -20
  272. package/src/providers/integrations/cline/cline.ts +537 -539
  273. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  274. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  275. package/src/providers/integrations/codex/codex.ts +2 -29
  276. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  277. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  278. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  279. package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
  280. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  281. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  282. package/src/providers/integrations/cursor/cursor.ts +1333 -960
  283. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  284. package/src/providers/integrations/factory/factory-models.ts +3 -5
  285. package/src/providers/integrations/factory/factory.ts +8 -1
  286. package/src/providers/integrations/gemini.ts +6 -1
  287. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  288. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  289. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  290. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  291. package/src/providers/integrations/grok/grok.ts +6 -7
  292. package/src/providers/integrations/hermes.ts +14 -18
  293. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  294. package/src/providers/integrations/kiro/kiro.ts +1 -1
  295. package/src/providers/integrations/mistral.ts +6 -6
  296. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  297. package/src/providers/integrations/opencode/provider.ts +19 -20
  298. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  299. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  300. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  301. package/src/providers/integrations/qoder/qoder.ts +884 -865
  302. package/src/providers/integrations/shared/connect.ts +6 -6
  303. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  304. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  305. package/src/providers/integrations/trae/trae.ts +2 -2
  306. package/src/providers/integrations/zcode/oauth.ts +6 -1
  307. package/src/providers/operations/account-health-service.ts +75 -27
  308. package/src/providers/operations/account-reset-service.ts +1 -1
  309. package/src/providers/operations/client-versions.ts +6 -6
  310. package/src/providers/operations/custom-cli-headers.ts +3 -4
  311. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  312. package/src/providers/operations/provider-catalog-service.ts +3 -4
  313. package/src/providers/provider-contracts.ts +7 -0
  314. package/src/providers/provider-metadata.ts +10 -8
  315. package/src/providers/provider-registry.ts +6 -7
  316. package/src/providers/provider-usage.ts +1 -1
  317. package/src/providers/quota/quota-support.ts +8 -0
  318. package/src/security/abuse.ts +15 -10
  319. package/src/security/access-control.ts +144 -146
  320. package/src/security/admission/contracts.ts +11 -0
  321. package/src/security/admission/in-memory-store.ts +4 -3
  322. package/src/security/admission/lease-sweep.ts +6 -6
  323. package/src/security/admission/reasons.ts +5 -1
  324. package/src/security/admission/redis-store.ts +211 -210
  325. package/src/security/admission/service.ts +4 -0
  326. package/src/security/api-key-auth.ts +27 -17
  327. package/src/security/ip-boundary.ts +155 -15
  328. package/src/security/model-abuse.ts +366 -94
  329. package/src/security/outbound-headers.ts +3 -4
  330. package/src/shared/sleep.ts +4 -0
  331. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  332. package/src/transport/dispatch/attempt-loop.ts +531 -530
  333. package/src/transport/dispatch/generation.ts +6 -0
  334. package/src/transport/dispatch/proxy-request.ts +24 -1
  335. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  336. package/src/transport/dispatch/stream-bridge.ts +1 -3
  337. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  338. package/src/transport/dispatch/streaming/stream.ts +19 -6
  339. package/src/transport/dispatch/upstream.ts +9 -0
  340. package/src/transport/failure-policy.ts +162 -8
  341. package/src/transport/guards/auth-guard.ts +7 -4
  342. package/src/transport/guards/concurrency-guard.ts +0 -5
  343. package/src/transport/guards/error-lifecycle.ts +13 -6
  344. package/src/transport/guards/pipeline.ts +15 -3
  345. package/src/transport/guards/request-context.ts +8 -2
  346. package/src/transport/request/inflight.ts +5 -6
  347. package/src/transport/request/preparer.ts +9 -8
  348. package/src/transport/request/state.ts +21 -8
  349. package/src/transport/request/tenant-preferences.ts +60 -0
  350. package/src/transport/routing/admission.ts +4 -0
  351. package/src/transport/routing/route-catalog.ts +65 -10
  352. package/src/transport/routing/route-model.ts +46 -14
  353. package/src/transport/routing/router.ts +21 -14
  354. package/src/transport/surface/completion.ts +2 -3
  355. package/src/transport/surface/dialects.ts +5 -6
  356. package/src/transport/surface/responses/encode.ts +38 -0
  357. package/src/transport/surface/responses/parse.ts +4 -0
  358. package/src/transport/translation/capabilities.ts +2 -3
  359. package/src/transport/translation/quirks.ts +3 -3
  360. package/src/workers/buddy-growth-tasks.ts +5 -10
  361. package/src/workers/daily-checkin.ts +17 -23
  362. package/src/workers/quota-refresh-sweep.ts +26 -26
  363. package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
  364. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
  365. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
  366. package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
  367. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
  368. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
  369. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
  370. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
  371. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
  372. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
  373. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
  374. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
  375. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  376. package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
  377. package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
  378. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
  379. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
  380. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
  381. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
  382. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
  383. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
  384. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
  385. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
  386. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
  387. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
  388. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
  389. package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
  390. package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
  391. package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
  392. package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
  393. package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
  394. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  395. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  396. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  397. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  398. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  399. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  400. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  401. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  402. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  403. package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
  404. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  405. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  406. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  407. package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
  408. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  409. package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
  410. package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
  411. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  412. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  413. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  414. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  415. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  416. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  417. package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
  418. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  419. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  420. package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
  421. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  422. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  423. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  424. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  425. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  426. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  427. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  428. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  429. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  430. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  431. package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
  432. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  433. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  434. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  435. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  436. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  437. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  438. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  439. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  440. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  441. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  442. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  443. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  444. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  445. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  446. package/scripts/cli/command/admin.ts +0 -65
  447. package/scripts/cli/command/doctor.ts +0 -257
  448. package/scripts/cli/command/update.ts +0 -23
  449. package/scripts/cli/gateway/drain-token.ts +0 -78
  450. package/scripts/cli/gateway/instance-lock.ts +0 -105
  451. package/scripts/cli/gateway/stop.ts +0 -51
  452. package/scripts/cli/support/bun.ts +0 -27
  453. package/scripts/cli/support/version.ts +0 -37
  454. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
  455. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
  456. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -1,29 +1,31 @@
1
1
  import { mkdirSync } from "node:fs";
2
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";
3
+ import { readRequiredBunVersion, assertBunVersion } from "./version";
4
+ import { printUpdateInstructions, runDetachedUpdate } from "./update";
7
5
 
8
6
  process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
9
7
 
10
- const HELP = `Cartethyia ${MINIMUM_BUN_VERSION}+ (Bun is required)
8
+ const HELP = (minimumBunVersion: string) => `Cartethyia ${minimumBunVersion}+ (Bun is required)
11
9
 
12
10
  Usage:
13
11
  carte start, then show an arrow-key menu (no GUI needed)
14
12
  carte --tray start with a system-tray icon instead
15
13
  cartethyia setup [--non-interactive]
16
14
  cartethyia start
15
+ cartethyia stop
17
16
  cartethyia menu
18
17
  cartethyia doctor
18
+ cartethyia admin reset-lockout <username> <clientIp>
19
+ cartethyia admin reset-lockout --all
19
20
  cartethyia update
20
21
 
21
22
  carte starts the gateway in the background and leaves only the console menu on
22
23
  your terminal — Open Console, Reset password, Backup, Update, Hide to tray,
23
24
  Exit. Arrow keys and Enter; no desktop environment needed, so it works over SSH
24
- and headless. Exiting the menu drains the gateway.
25
+ and headless. A menu started by this command drains its child gateway on Exit;
26
+ leaving a menu attached to an existing gateway only detaches. Use stop for that.
25
27
  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.
28
+ Stop a detached gateway with cartethyia stop (or the install's ./carte stop).
27
29
  Setup explains Lite (embedded SQLite) and Full (external PostgreSQL), then saves
28
30
  configuration under the current user's Cartethyia config directory.
29
31
  `;
@@ -33,62 +35,130 @@ async function loadUserConfig(): Promise<void> {
33
35
  const configDir = process.env.CARTETHYIA_CONFIG_DIR;
34
36
  if (!configDir) throw new Error("Cartethyia config directory could not be resolved");
35
37
  mkdirSync(configDir, { recursive: true });
38
+ // Imported here rather than at module scope: `support/env` pulls in the
39
+ // package-root resolver and `node:fs/promises`, and `carte --help` (which
40
+ // never reads a .env) should not pay for either before it prints.
41
+ const { ENV_PATH, readEnvFile } = await import("./env");
42
+ // Bun injects PORT=12800 when no explicit port was supplied. It is a
43
+ // runner default, not an operator override; without removing it, a saved
44
+ // custom port is ignored by npm/carte and the gateway starts on 12800.
45
+ // Preserve any non-default/custom shell value exactly as before.
46
+ if (process.env.PORT === "12800") delete process.env.PORT;
36
47
  const values = await readEnvFile(ENV_PATH);
37
48
  for (const [key, value] of Object.entries(values)) {
38
49
  if (!process.env[key]) process.env[key] = value;
39
50
  }
40
51
  }
41
52
 
53
+ /**
54
+ * Reports tray readiness to the launcher waiting on a signal file.
55
+ *
56
+ * Deferred to a dynamic import for the same reason as `loadUserConfig`: only
57
+ * the tray path writes this file, so the other commands should not load
58
+ * `node:fs` machinery they never touch.
59
+ */
60
+ async function reportTrayFailed(message: string): Promise<void> {
61
+ const { reportTrayStartup } = await import("./startup-signal");
62
+ reportTrayStartup({ status: "failed", message });
63
+ }
64
+
42
65
  async function main(): Promise<void> {
43
- assertBunVersion();
66
+ await assertBunVersion();
44
67
  const [command] = process.argv.slice(2);
45
- if (command === "setup" || command === "start" || command === "tray" || command === "menu" || command === "doctor") {
68
+ if (command === "setup" || command === "start" || command === "stop" || command === "tray" || command === "menu" || command === "doctor" || command === "admin") {
46
69
  await loadUserConfig();
47
70
  }
48
71
 
49
72
  switch (command) {
50
73
  case "setup": {
51
- const { setup } = await import("./command/setup");
74
+ const { setup } = await import("./setup");
52
75
  await setup();
53
76
  return;
54
77
  }
55
78
  case "start": {
56
- const { startForeground } = await import("./command/start");
79
+ const { startForeground } = await import("./start");
57
80
  await startForeground();
58
81
  return;
59
82
  }
83
+ case "stop": {
84
+ const { resolvePort } = await import("../../src/config/runtime");
85
+ const { ensureDrainToken, attachGateway, stopAttachedGateway } = await import("./instance");
86
+ const port = resolvePort();
87
+ const token = ensureDrainToken();
88
+ const gateway = await attachGateway(port, token);
89
+ if (!gateway) {
90
+ console.log("No running Cartethyia gateway is owned by this installation.");
91
+ return;
92
+ }
93
+ await stopAttachedGateway(gateway, token);
94
+ console.log("Cartethyia stopped.");
95
+ return;
96
+ }
60
97
  case "menu": {
61
- const { startMenu } = await import("./command/console-menu");
98
+ const { startMenu } = await import("./console-menu");
62
99
  await startMenu();
63
100
  return;
64
101
  }
65
102
  case "tray": {
66
- const { startTray } = await import("./command/tray");
103
+ const { startTray } = await import("./tray");
67
104
  await startTray();
68
105
  return;
69
106
  }
107
+ case "autostart": {
108
+ const { enableAutostart, disableAutostart, autostartStatus } = await import("./autostart");
109
+ const arg = Bun.argv[3]?.trim();
110
+ if (arg === "off" || arg === "disable") {
111
+ const state = await disableAutostart();
112
+ console.log(`Autostart disabled (${state.location || state.platform}).`);
113
+ return;
114
+ }
115
+ if (arg === "status") {
116
+ const state = autostartStatus();
117
+ console.log(
118
+ `Autostart: ${state.enabled ? "enabled" : "not enabled"} — ${state.location || state.platform}` +
119
+ (state.reason ? ` (${state.reason})` : ""),
120
+ );
121
+ return;
122
+ }
123
+ // The tray is what autostart launches: a gateway alone has no session to
124
+ // attach to and no icon to show.
125
+ const state = await enableAutostart(process.execPath, [Bun.argv[1] ?? "carte", "tray"]);
126
+ console.log(
127
+ state.enabled
128
+ ? `Autostart enabled — ${state.location}`
129
+ : `Autostart not enabled: ${state.reason ?? "unknown reason"}`,
130
+ );
131
+ return;
132
+ }
70
133
  case "doctor": {
71
- const { doctor } = await import("./command/doctor");
134
+ const { doctor } = await import("./doctor");
72
135
  await doctor();
73
136
  return;
74
137
  }
138
+ case "admin": {
139
+ if (process.argv[3] !== "reset-lockout") throw new Error("Usage: cartethyia admin reset-lockout <username> <clientIp> | --all");
140
+ const { resetLockout } = await import("./admin");
141
+ await resetLockout(process.argv.slice(4));
142
+ return;
143
+ }
75
144
  case "update":
145
+ runDetachedUpdate();
76
146
  printUpdateInstructions("this install", null);
77
147
  return;
78
148
  case "--help":
79
149
  case "-h":
80
150
  case undefined:
81
- console.log(HELP);
151
+ console.log(HELP(await readRequiredBunVersion()));
82
152
  return;
83
153
  default:
84
- console.error(`Unknown command: ${command}\n\n${HELP}`);
154
+ console.error(`Unknown command: ${command}\n\n${HELP(await readRequiredBunVersion())}`);
85
155
  process.exitCode = 2;
86
156
  }
87
157
  }
88
158
 
89
- main().catch((error: unknown) => {
159
+ main().catch(async (error: unknown) => {
90
160
  const message = error instanceof Error ? error.message : "unknown error";
91
- if (process.argv[2] === "tray") reportTrayStartup({ status: "failed", message });
161
+ if (process.argv[2] === "tray") await reportTrayFailed(message);
92
162
  console.error(`Cartethyia failed: ${message}`);
93
163
  process.exitCode = 1;
94
164
  });
@@ -1,64 +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
- }
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,100 @@
1
+ import { closeSync, openSync, readSync, statSync } from "node:fs";
2
+ import { createServer } from "node:net";
3
+ import { join } from "node:path";
4
+ import { resolveEnvPath } from "./env";
5
+
6
+ export interface ListenPortProbe {
7
+ readonly status: "free" | "blocked";
8
+ readonly code?: string;
9
+ }
10
+
11
+ /**
12
+ * Probes the actual listener boundary rather than inferring availability from
13
+ * an HTTP response. A non-HTTP service still owns the port when /health is
14
+ * unreachable. The probe socket closes before the real gateway starts.
15
+ */
16
+ export async function probeListenPort(port: number): Promise<ListenPortProbe> {
17
+ return await new Promise((resolve) => {
18
+ const server = createServer();
19
+ server.unref();
20
+ server.once("error", (error: NodeJS.ErrnoException) => {
21
+ resolve({ status: "blocked", ...(error.code ? { code: error.code } : {}) });
22
+ });
23
+ server.listen({ port, host: "0.0.0.0", exclusive: true }, () => {
24
+ server.close(() => resolve({ status: "free" }));
25
+ });
26
+ });
27
+ }
28
+
29
+ export function changePortInstructions(envPath = resolveEnvPath()): string {
30
+ return `Stop the process using this port, or change PORT in ${envPath}. ` +
31
+ "If CARTETHYIA_PUBLIC_ORIGIN contains the old port, update it too, then start Cartethyia again.";
32
+ }
33
+
34
+ export function blockedPortMessage(port: number, envPath = resolveEnvPath()): string {
35
+ return `Cannot start Cartethyia: port ${port} is already in use by another process. ` +
36
+ `No process was stopped. ${changePortInstructions(envPath)}`;
37
+ }
38
+
39
+ export function listenPortErrorMessage(port: number, probe: ListenPortProbe): string {
40
+ if (probe.code === "EADDRINUSE") return blockedPortMessage(port);
41
+ const reason = probe.code ?? "unknown listener error";
42
+ return `Cannot start Cartethyia: the operating system refused to listen on port ${port} (${reason}). ` +
43
+ `Check port permissions and firewall policy, or choose another port. ${changePortInstructions()}`;
44
+ }
45
+
46
+ function readNewLogTail(path: string, offset: number): string {
47
+ try {
48
+ const size = statSync(path).size;
49
+ const length = Math.min(Math.max(size - offset, 0), 8_000);
50
+ if (length === 0) return "";
51
+ const buffer = Buffer.alloc(length);
52
+ const fd = openSync(path, "r");
53
+ try {
54
+ readSync(fd, buffer, 0, length, Math.max(offset, size - length));
55
+ } finally {
56
+ closeSync(fd);
57
+ }
58
+ const lines = buffer.toString("utf8")
59
+ .replace(/\u001b\[[0-9;]*m/g, "")
60
+ .split(/\r?\n/)
61
+ .map((line) => line.trim())
62
+ .filter((line) => line.length > 0)
63
+ .filter((line) => !/^at\s/.test(line) && !/^\d+\s*\|/.test(line) && !/^Bun v\d/.test(line))
64
+ .map((line) => {
65
+ if (line.length <= 300) return line;
66
+ const marker = /(?:Error:|ENOENT|EADDRINUSE|address already in use)/.exec(line);
67
+ return marker ? line.slice(marker.index, marker.index + 300) : "";
68
+ })
69
+ .filter((line) => line.length > 0);
70
+ return lines.slice(-8).map((line) => line.slice(0, 300)).join("\n").slice(-1_200);
71
+ } catch {
72
+ return "";
73
+ }
74
+ }
75
+
76
+ export function startupFailureMessage(
77
+ port: number,
78
+ exitCode: number,
79
+ logPath: string | null,
80
+ logOffset = 0,
81
+ ): string {
82
+ const detail = logPath ? readNewLogTail(logPath, logOffset) : "";
83
+ const log = logPath ?? "unavailable (the config directory is not writable)";
84
+ const lines = [
85
+ `Cartethyia exited with code ${exitCode} before becoming ready on port ${port}.`,
86
+ ];
87
+ if (detail) lines.push(`Gateway error: ${detail}`);
88
+ else lines.push("No startup error detail was captured from the gateway.");
89
+ if (/EADDRINUSE|address already in use/i.test(detail)) {
90
+ lines.push(blockedPortMessage(port));
91
+ } else {
92
+ lines.push("Correct the gateway error above, then run `carte` again. Use `cartethyia doctor` to check configuration.");
93
+ }
94
+ lines.push(`Startup log: ${log}`);
95
+ return lines.join("\n");
96
+ }
97
+
98
+ export function gatewayLogPath(configDir = process.env.CARTETHYIA_CONFIG_DIR): string | null {
99
+ return configDir ? join(configDir, "gateway.log") : null;
100
+ }
@@ -1,88 +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
- }
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
+ }