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,248 +1,275 @@
1
- import { existsSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { randomUUID } from "node:crypto";
5
- import { isNewerRelease } from "../../../src/console/updates/version-compare";
6
- import { resolvePackagePath } from "../../../src/config/package-paths";
7
- import { resolvePort } from "../../../src/config/runtime";
8
- import { ensureDrainToken } from "../gateway/drain-token";
9
- import { startGateway } from "../gateway/spawn";
10
- import { stopGateway } from "../gateway/stop";
11
- import { ENV_PATH, readEnvFile } from "../support/env";
12
- import { readInstalledVersion, readLatestPublishedVersion } from "../support/version";
13
- import { launchBackgroundWithStatus, openUrl } from "../ui/background";
14
- import { chooseFromMenu, confirmByTyping, confirmStep } from "../ui/prompt";
15
- import { renderWordmark } from "../ui/wordmark";
16
- import { exportBackup, resetConsoleAccount } from "./admin";
17
- import { updateCommand } from "./update";
18
-
19
- /**
20
- * The default `carte` experience: start the gateway, then offer an arrow-key
21
- * menu. This needs no desktop environment, so it works over SSH, on a headless
22
- * box, and on Windows where the tray icon lands in the hidden-icons overflow
23
- * and is never seen. `carte --tray` opts into the icon instead.
24
- */
25
- export async function startMenu(): Promise<void> {
26
- // A fresh install has no config file yet, and the gateway refuses to boot
27
- // without CARTETHYIA_DB_MODE — which surfaced as a stack trace about a
28
- // missing database mode rather than "you have not set this up". Run setup
29
- // first, the way `carte` is expected to behave on a new install.
30
- const configured = existsSync(ENV_PATH) && (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE;
31
- if (!configured) {
32
- console.log(
33
- existsSync(ENV_PATH)
34
- ? "Cartethyia is missing its database mode. Starting setup.\n"
35
- : "Cartethyia is not set up yet. Starting setup.\n",
36
- );
37
- // Dynamic on purpose: `setup` pulls in the whole wizard and the database
38
- // mode resolver, and it is only reachable on a first run. A static import
39
- // would pay that cost on every `carte`, which is the one path that has to
40
- // stay fast.
41
- const { setup } = await import("./setup");
42
- await setup();
43
- const after = existsSync(ENV_PATH) ? (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE : undefined;
44
- if (!after) {
45
- console.error(
46
- `Setup did not record CARTETHYIA_DB_MODE in ${ENV_PATH}; run \`cartethyia setup\` and try again.`,
47
- );
48
- process.exitCode = 1;
49
- return;
50
- }
51
- process.env.CARTETHYIA_DB_MODE = after;
52
- }
53
-
54
- const port = resolvePort();
55
- const drainToken = ensureDrainToken();
56
- const dashboardUrl = `http://127.0.0.1:${port}/console`;
57
- const currentVersion = await readInstalledVersion();
58
-
59
- // The gateway starts in the background: the menu is usable before it has
60
- // finished booting, and awaiting it cost ~670 ms of blank terminal on every
61
- // start. `ready` resolves when the health probe passes; actions that need a
62
- // listening gateway await it, so nothing races.
63
- const ready = startGateway(port, drainToken);
64
- let gateway: Bun.Subprocess | null = null;
65
- void ready.then((child) => {
66
- gateway = child;
67
- child.unref();
68
- }).catch((error: unknown) => {
69
- console.error(
70
- `The gateway did not start: ${error instanceof Error ? error.message : "unknown error"}`,
71
- );
72
- });
73
-
74
- let stopping = false;
75
- let handedOff = false;
76
- const shutDown = async (): Promise<void> => {
77
- if (stopping) return;
78
- stopping = true;
79
- const child = gateway ?? (await ready.catch(() => null));
80
- if (child) await stopGateway(child, port, drainToken);
81
- };
82
- // Ctrl-C has to stop the gateway, and the drain endpoint cannot be called
83
- // from an exit path: it is async, and enquirer's own Ctrl-C handler calls
84
- // process.exit(130) while a prompt is live. So the gateway is a child in this
85
- // process group — a terminal's Ctrl-C signals the whole group, and the
86
- // gateway drains through the SIGINT handler it already has in src/main.ts —
87
- // and `process.on("exit")` is the synchronous backstop for every other way
88
- // this process can end (a closed terminal, a crash, a signal Bun cannot
89
- // deliver on Windows). That handler can only call synchronous APIs, which is
90
- // why it signals the child rather than awaiting the drain endpoint.
91
- // `handedOff` is set by "Hide to tray": the gateway is being given to a tray
92
- // that will own it, so this process must not kill it on the way out.
93
- process.on("exit", () => {
94
- if (stopping || handedOff || gateway === null) return;
95
- try {
96
- gateway.kill();
97
- } catch {
98
- // Already gone; nothing to stop.
99
- }
100
- });
101
- const interrupt = async (): Promise<void> => {
102
- process.stdout.write("\u001b[2J\u001b[H");
103
- console.log("Stopping Cartethyia…");
104
- await shutDown();
105
- process.exit(0);
106
- };
107
- process.on("SIGTERM", () => void interrupt());
108
- process.on("SIGINT", () => void interrupt());
109
-
110
- let latestVersion = await readLatestPublishedVersion();
111
- let running = true;
112
-
113
- const header = [...renderWordmark(), `Cartethyia ${currentVersion} \u00b7 ${dashboardUrl}`];
114
-
115
- while (running) {
116
- const upToDate = latestVersion === null || !isNewerRelease(currentVersion, latestVersion);
117
- const updateLabel =
118
- latestVersion === null
119
- ? "Check for updates"
120
- : upToDate
121
- ? `Up to date (${currentVersion})`
122
- : `Update to ${latestVersion}`;
123
-
124
- // Anything that talks to the gateway waits for it to be listening; the menu
125
- // itself does not.
126
- const whenReady = async (): Promise<void> => {
127
- await ready.catch(() => undefined);
128
- };
129
-
130
- const actions: readonly (() => Promise<void>)[] = [
131
- async () => openUrl(dashboardUrl),
132
- async () => {
133
- // Deleting the console account is the only real password recovery:
134
- // /change-password needs the current password, and first-boot setup
135
- // only runs when no user exists. Removing the user puts the wizard
136
- // back. Providers, keys, and routing are untouched.
137
- console.log("");
138
- console.log("This deletes every console account and its sessions.");
139
- console.log("The next visit to /console asks you to create a new one.");
140
- console.log("Providers, API keys, and routing are not touched.");
141
- console.log("");
142
- if (!(await confirmStep("Continue with the reset?"))) {
143
- console.log("Cancelled.");
144
- return;
145
- }
146
- if (!(await confirmByTyping("RESET", "Confirm deleting the console account"))) {
147
- console.log("Cancelled.");
148
- return;
149
- }
150
- await whenReady();
151
- const status = await resetConsoleAccount(port, drainToken);
152
- console.log(
153
- status === 0
154
- ? `Console account removed. Open ${dashboardUrl} to create a new one.`
155
- : "Reset failed; see the gateway log.",
156
- );
157
- },
158
- async () => {
159
- // Export straight to the download directory. The console page asks for
160
- // the operator password, which is exactly the thing a locked-out
161
- // operator does not have; the CLI already runs as the OS user who owns
162
- // the database, so the loopback token is the whole gate.
163
- await whenReady();
164
- const result = await exportBackup(port, drainToken);
165
- if (result === null) return;
166
- console.log(
167
- `Backup written to ${result.path} (${(result.bytes / 1024).toFixed(1)} KiB, ${Object.keys(result.counts).length} tables).`,
168
- );
169
- console.log("Telemetry is not included: it is per-request history, not configuration.");
170
- },
171
- async () => {
172
- if (upToDate) return;
173
- // npm cannot replace this package's directory while this process is
174
- // running out of it: on Windows the rename fails with EBUSY and npm
175
- // exits 14. Even with the gateway stopped, the CLI itself is executing
176
- // from that directory, so an in-process install can never succeed.
177
- // Print the command and exit, the way 9router does.
178
- await shutDown();
179
- console.log("");
180
- console.log(`Update available: ${currentVersion} \u2192 ${latestVersion}`);
181
- console.log("");
182
- console.log("Run this now that Cartethyia has stopped:");
183
- console.log("");
184
- console.log(` ${updateCommand()}`);
185
- console.log("");
186
- console.log("This terminal will stay open for 180s so you can copy the command, then exit.");
187
- running = false;
188
- await new Promise<void>((resolve) => setTimeout(resolve, 180_000));
189
- console.log("Session ended. Run the command above to update.");
190
- process.exit(0);
191
- },
192
- async () => {
193
- // Drain first, then start the tray.
194
- //
195
- // The tray's own startup probe refuses to boot when the port is busy,
196
- // so starting it before this process has let go made it fail with
197
- // "already running on port" every time — while the menu still printed
198
- // that everything was running, because a detached child with no stdio
199
- // cannot report anything back. Releasing the port first is the fix, and
200
- // waiting on the child's signal is what stops the menu from claiming
201
- // success for a child that died.
202
- console.log("Stopping the gateway to free the port…");
203
- running = false;
204
- stopping = true;
205
- handedOff = true;
206
- await shutDown();
207
- console.log("Starting the tray in the background…");
208
- const signalPath = join(tmpdir(), `cartethyia-tray-menu-${process.pid}-${randomUUID()}.json`);
209
- try {
210
- const url = await launchBackgroundWithStatus(
211
- ["run", resolvePackagePath("scripts", "cli", "main.ts"), "tray"],
212
- { signalPath },
213
- );
214
- console.log(`Cartethyia is running in the background at ${url || dashboardUrl}.`);
215
- console.log("The tray icon owns it; use Exit there to stop it.");
216
- } catch (error) {
217
- handedOff = false;
218
- console.error(
219
- `Could not hand off to the tray: ${error instanceof Error ? error.message : "unknown error"}`,
220
- );
221
- console.error("Cartethyia is stopped. Start it again from this menu.");
222
- }
223
- },
224
- async () => {
225
- running = false;
226
- },
227
- ];
228
-
229
- const choice = await chooseFromMenu(header, [
230
- { kind: "action", label: "Open Console", hint: `browser \u00b7 ${dashboardUrl}` },
231
- { kind: "action", label: "Reset console password", hint: "deletes the account" },
232
- { kind: "action", label: "Backup", hint: "export config to Downloads" },
233
- { kind: "action", label: updateLabel, hint: upToDate ? "open the repository" : "installs now" },
234
- { kind: "action", label: "Hide to tray", hint: "keep the gateway running" },
235
- { kind: "rule" },
236
- { kind: "action", label: "Exit", hint: "stop the gateway" },
237
- ],
238
- );
239
- if (choice === -1) break;
240
- const action = actions[choice];
241
- if (action) await action();
242
- }
243
- // "Hide to tray" has already given the gateway to the tray. Draining here
244
- // would stop the very process the tray just adopted, which is why the menu
245
- // appeared to hand over a gateway that was not listening.
246
- if (handedOff) return;
247
- await shutDown();
248
- }
1
+ import { existsSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { randomUUID } from "node:crypto";
5
+ import { isNewerRelease } from "../../src/console/updates/version-compare";
6
+ import { resolvePackagePath } from "../../src/config/package-paths";
7
+ import { resolvePort } from "../../src/config/runtime";
8
+ import { ensureDrainToken } from "./instance";
9
+ import { attachGateway, stopAttachedGateway, type RunningGateway } from "./instance";
10
+ import { startGateway } from "./gateway";
11
+ import { stopGateway } from "./gateway";
12
+ import { ENV_PATH, readEnvFile } from "./env";
13
+ import { readInstalledVersion, readLatestPublishedVersion } from "./version";
14
+ import { launchBackgroundWithStatus, openUrl } from "./background";
15
+ import { chooseFromMenu, confirmByTyping, confirmStep } from "./prompt";
16
+ import { renderWordmark } from "./wordmark";
17
+ import { exportBackup, resetConsoleAccount } from "./admin";
18
+ import { runDetachedUpdate } from "./update";
19
+
20
+
21
+ /**
22
+ * The default `carte` experience: start the gateway, then offer an arrow-key
23
+ * menu. This needs no desktop environment, so it works over SSH, on a headless
24
+ * box, and on Windows where the tray icon lands in the hidden-icons overflow
25
+ * and is never seen. `carte --tray` opts into the icon instead.
26
+ */
27
+ export async function startMenu(): Promise<void> {
28
+ // A fresh install has no config file yet, and the gateway refuses to boot
29
+ // without CARTETHYIA_DB_MODE — which surfaced as a stack trace about a
30
+ // missing database mode rather than "you have not set this up". Run setup
31
+ // first, the way `carte` is expected to behave on a new install.
32
+ const configured = existsSync(ENV_PATH) && (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE;
33
+ if (!configured) {
34
+ console.log(
35
+ existsSync(ENV_PATH)
36
+ ? "Cartethyia is missing its database mode. Starting setup.\n"
37
+ : "Cartethyia is not set up yet. Starting setup.\n",
38
+ );
39
+ // Dynamic on purpose: `setup` pulls in the whole wizard and the database
40
+ // mode resolver, and it is only reachable on a first run. A static import
41
+ // would pay that cost on every `carte`, which is the one path that has to
42
+ // stay fast.
43
+ const { setup } = await import("./setup");
44
+ await setup();
45
+ const after = existsSync(ENV_PATH) ? (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE : undefined;
46
+ if (!after) {
47
+ console.error(
48
+ `Setup did not record CARTETHYIA_DB_MODE in ${ENV_PATH}; run \`cartethyia setup\` and try again.`,
49
+ );
50
+ process.exitCode = 1;
51
+ return;
52
+ }
53
+ process.env.CARTETHYIA_DB_MODE = after;
54
+ }
55
+
56
+ const port = resolvePort();
57
+ const drainToken = ensureDrainToken();
58
+ const dashboardUrl = `http://127.0.0.1:${port}/console`;
59
+ const currentVersion = await readInstalledVersion();
60
+
61
+ // Reattach without draining a healthy instance. A new child must either be
62
+ // ready or produce its actionable failure before the menu is shown; otherwise
63
+ // the UI offers gateway actions for a process that already exited.
64
+ const attached = await attachGateway(port, drainToken);
65
+ let startupError: unknown = null;
66
+ const ready: Promise<Bun.Subprocess | null> = attached
67
+ ? Promise.resolve(null)
68
+ : startGateway(port, drainToken).catch((error: unknown) => {
69
+ startupError = error;
70
+ return null;
71
+ });
72
+ let gateway: Bun.Subprocess | null = null;
73
+ const runningAttached: RunningGateway | null = attached;
74
+ void ready.then((child) => {
75
+ if (child === null) return;
76
+ gateway = child;
77
+ child.unref();
78
+ }).catch((error: unknown) => {
79
+ console.error(`The gateway did not start: ${error instanceof Error ? error.message : "unknown error"}`);
80
+ });
81
+
82
+ if (!attached) {
83
+ await ready;
84
+ if (startupError !== null) {
85
+ const detail = startupError instanceof Error ? startupError.message : "unknown startup error";
86
+ console.error(`The gateway did not start on port ${port}.\n${detail}`);
87
+ console.error("Fix the reported issue, then run `carte` again.");
88
+ process.exitCode = 1;
89
+ return;
90
+ }
91
+ }
92
+
93
+ let stopping = false;
94
+ let handedOff = false;
95
+ const shutDown = async (): Promise<void> => {
96
+ if (stopping) return;
97
+ stopping = true;
98
+ const child = gateway ?? (await ready.catch(() => null));
99
+ if (child) await stopGateway(child, port, drainToken);
100
+ else if (runningAttached) await stopAttachedGateway(runningAttached, drainToken);
101
+ };
102
+ const detachAttached = (): void => {
103
+ if (runningAttached !== null) stopping = true;
104
+ };
105
+ // Ctrl-C has to stop the gateway, and the drain endpoint cannot be called
106
+ // from an exit path: it is async, and enquirer's own Ctrl-C handler calls
107
+ // process.exit(130) while a prompt is live. So the gateway is a child in this
108
+ // process group — a terminal's Ctrl-C signals the whole group, and the
109
+ // gateway drains through the SIGINT handler it already has in src/main.ts —
110
+ // and `process.on("exit")` is the synchronous backstop for every other way
111
+ // this process can end (a closed terminal, a crash, a signal Bun cannot
112
+ // deliver on Windows). That handler can only call synchronous APIs, which is
113
+ // why it signals the child rather than awaiting the drain endpoint.
114
+ // `handedOff` is set by "Hide to tray": the gateway is being given to a tray
115
+ // that will own it, so this process must not kill it on the way out.
116
+ process.on("exit", () => {
117
+ if (stopping || handedOff || gateway === null || runningAttached !== null) return;
118
+ try {
119
+ gateway.kill();
120
+ } catch {
121
+ // Already gone; nothing to stop.
122
+ }
123
+ });
124
+ const interrupt = async (): Promise<void> => {
125
+ if (runningAttached !== null) {
126
+ detachAttached();
127
+ process.exit(0);
128
+ }
129
+ process.stdout.write("\u001b[2J\u001b[H");
130
+ console.log("Stopping Cartethyia…");
131
+ await shutDown();
132
+ process.exit(0);
133
+ };
134
+ process.on("SIGTERM", () => void interrupt());
135
+ process.on("SIGINT", () => void interrupt());
136
+
137
+ let latestVersion = await readLatestPublishedVersion();
138
+ let running = true;
139
+
140
+ const header = [...renderWordmark(), `Cartethyia ${currentVersion} \u00b7 ${dashboardUrl}`];
141
+
142
+ while (running) {
143
+ const upToDate = latestVersion === null || !isNewerRelease(currentVersion, latestVersion);
144
+ const updateLabel =
145
+ latestVersion === null
146
+ ? "Check for updates"
147
+ : upToDate
148
+ ? `Up to date (${currentVersion})`
149
+ : `Update to ${latestVersion}`;
150
+
151
+ // Anything that talks to the gateway waits for it to be listening; the menu
152
+ // itself does not.
153
+ const whenReady = async (): Promise<void> => {
154
+ await ready.catch(() => undefined);
155
+ };
156
+
157
+ const actions: readonly (() => Promise<void>)[] = [
158
+ async () => openUrl(dashboardUrl),
159
+ async () => {
160
+ // Deleting the console account is the only real password recovery:
161
+ // /change-password needs the current password, and first-boot setup
162
+ // only runs when no user exists. Removing the user puts the wizard
163
+ // back. Providers, keys, and routing are untouched.
164
+ console.log("");
165
+ console.log("This deletes every console account and its sessions.");
166
+ console.log("The next visit to /console asks you to create a new one.");
167
+ console.log("Providers, API keys, and routing are not touched.");
168
+ console.log("");
169
+ if (!(await confirmStep("Continue with the reset?"))) {
170
+ console.log("Cancelled.");
171
+ return;
172
+ }
173
+ if (!(await confirmByTyping("RESET", "Confirm deleting the console account"))) {
174
+ console.log("Cancelled.");
175
+ return;
176
+ }
177
+ await whenReady();
178
+ const status = await resetConsoleAccount(port, drainToken);
179
+ console.log(
180
+ status === 0
181
+ ? `Console account removed. Open ${dashboardUrl} to create a new one.`
182
+ : "Reset failed; see the gateway log.",
183
+ );
184
+ },
185
+ async () => {
186
+ // Export straight to the download directory. The console page asks for
187
+ // the operator password, which is exactly the thing a locked-out
188
+ // operator does not have; the CLI already runs as the OS user who owns
189
+ // the database, so the loopback token is the whole gate.
190
+ await whenReady();
191
+ const result = await exportBackup(port, drainToken);
192
+ if (result === null) return;
193
+ console.log(
194
+ `Backup written to ${result.path} (${(result.bytes / 1024).toFixed(1)} KiB, ${Object.keys(result.counts).length} tables).`,
195
+ );
196
+ console.log("Telemetry is not included: it is per-request history, not configuration.");
197
+ },
198
+ async () => {
199
+ if (upToDate) return;
200
+ // npm cannot replace this package's directory while this process is
201
+ // running out of it: on Windows the rename fails with EBUSY and npm
202
+ // exits 14. Even with the gateway stopped, the CLI itself is executing
203
+ // from that directory, so an in-process install can never succeed.
204
+ // Print the command and exit, the way 9router does.
205
+ await shutDown();
206
+ runDetachedUpdate();
207
+ console.log("");
208
+ console.log(`Update available: ${currentVersion} \u2192 ${latestVersion}`);
209
+ console.log("Detached update started; this process will exit now.");
210
+ console.log("Re-run `carte` after it finishes.");
211
+ console.log("");
212
+ running = false;
213
+ process.exit(0);
214
+ },
215
+ async () => {
216
+ // Drain first, then start the tray.
217
+ //
218
+ // The tray's own startup probe refuses to boot when the port is busy,
219
+ // so starting it before this process has let go made it fail with
220
+ // "already running on port" every time — while the menu still printed
221
+ // that everything was running, because a detached child with no stdio
222
+ // cannot report anything back. Releasing the port first is the fix, and
223
+ // waiting on the child's signal is what stops the menu from claiming
224
+ // success for a child that died.
225
+ console.log("Stopping the gateway to free the port…");
226
+ running = false;
227
+ stopping = true;
228
+ handedOff = true;
229
+ await shutDown();
230
+ console.log("Starting the tray in the background…");
231
+ const signalPath = join(tmpdir(), `cartethyia-tray-menu-${process.pid}-${randomUUID()}.json`);
232
+ try {
233
+ const url = await launchBackgroundWithStatus(
234
+ ["run", resolvePackagePath("scripts", "cli", "main.ts"), "tray"],
235
+ { signalPath },
236
+ );
237
+ console.log(`Cartethyia is running in the background at ${url || dashboardUrl}.`);
238
+ console.log("The tray icon owns it; use Exit there to stop it.");
239
+ } catch (error) {
240
+ handedOff = false;
241
+ console.error(
242
+ `Could not hand off to the tray: ${error instanceof Error ? error.message : "unknown error"}`,
243
+ );
244
+ console.error("Cartethyia is stopped. Start it again from this menu.");
245
+ }
246
+ },
247
+ async () => {
248
+ running = false;
249
+ },
250
+ ];
251
+
252
+ const choice = await chooseFromMenu(header, [
253
+ { kind: "action", label: "Open Console", hint: `browser \u00b7 ${dashboardUrl}` },
254
+ { kind: "action", label: "Reset console password", hint: "deletes the account" },
255
+ { kind: "action", label: "Backup", hint: "export config to Downloads" },
256
+ { kind: "action", label: updateLabel, hint: upToDate ? "open the repository" : "installs now" },
257
+ { kind: "action", label: "Hide to tray", hint: "keep the gateway running" },
258
+ { kind: "rule" },
259
+ { kind: "action", label: "Exit", hint: "stop the gateway" },
260
+ ],
261
+ );
262
+ if (choice === -1) break;
263
+ const action = actions[choice];
264
+ if (action) await action();
265
+ }
266
+ // "Hide to tray" has already given the gateway to the tray. Draining here
267
+ // would stop the very process the tray just adopted, which is why the menu
268
+ // appeared to hand over a gateway that was not listening.
269
+ if (handedOff) return;
270
+ if (runningAttached !== null) {
271
+ detachAttached();
272
+ return;
273
+ }
274
+ await shutDown();
275
+ }
@@ -0,0 +1,37 @@
1
+ import { ENV_PATH } from "./env";
2
+ import { getLocalPlatform } from "./platform";
3
+ import { hasRequiredFailure, runHealthChecks, type CheckPhase } from "../health";
4
+
5
+ const phases: readonly CheckPhase[] = ["preflight", "configuration", "runtime"];
6
+
7
+ export async function doctor(): Promise<void> {
8
+ const args = process.argv.slice(2).filter((arg) => arg !== "doctor");
9
+ const phaseIndex = args.indexOf("--phase");
10
+ const unknown = args.filter((arg, index) => arg !== "--json" && arg !== "--phase" && args[index - 1] !== "--phase");
11
+ const phase = phaseIndex >= 0 ? args[phaseIndex + 1] : undefined;
12
+ if (unknown.length > 0 || (phase !== undefined && !phases.includes(phase as CheckPhase)) || (phaseIndex >= 0 && phase === undefined)) {
13
+ throw new Error("Usage: cartethyia doctor [--phase preflight|configuration|runtime] [--json]");
14
+ }
15
+ const selected = phase ? [phase as CheckPhase] : (["configuration", "runtime"] as const);
16
+ const envPath = ENV_PATH;
17
+ const port = Number(process.env.PORT ?? "12800");
18
+ console.log(`Cartethyia doctor · ${getLocalPlatform()}`);
19
+ const env = Object.fromEntries(Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined));
20
+ const results = await runHealthChecks({ target: "local", env, envPath, port, phases: selected });
21
+ if (args.includes("--json")) console.log(JSON.stringify(results, null, 2));
22
+ else {
23
+ for (const item of results) {
24
+ const marker = item.status === "pass" ? "PASS" : item.status.toUpperCase();
25
+ console.log(`${marker} [${item.phase}] ${item.summary}`);
26
+ if (item.remediation) console.log(` ${item.remediation}`);
27
+ }
28
+ }
29
+ if (hasRequiredFailure(results)) process.exitCode = 1;
30
+ }
31
+
32
+ if (import.meta.main) {
33
+ doctor().catch((error: unknown) => {
34
+ console.error(`Doctor failed: ${error instanceof Error ? error.message : String(error)}`);
35
+ process.exitCode = 1;
36
+ });
37
+ }