cartethyia 2.5.4 → 2.5.7

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 (395) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-C2orBsI2.js → BannedPage-DE2607Tb.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-DE2607Tb.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-Duv6mfrl.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-BV6pxVQo.js → CliToolsPage-C2PNYWzk.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-C2PNYWzk.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-TXP9OfAM.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-TXP9OfAM.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-CW-wPmJ6.js → ConsoleLogPage-B-9xnAQN.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-B-9xnAQN.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-0p8uX7vB.js → CustomizationPage-xeax9nJt.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-xeax9nJt.js.gz +0 -0
  15. package/dist/dashboard/assets/{HealthEventsModal-BjqSMfko.js → HealthEventsModal-Df4jjaHJ.js} +1 -1
  16. package/dist/dashboard/assets/HealthEventsModal-Df4jjaHJ.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-BvzCx8Kr.js → LoginPage-Dyojsm84.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-Dyojsm84.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-BJfg4ow6.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BJfg4ow6.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-ChCX1gmT.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-Js9jUPSb.js → ProvidersPage-DeWSOEUO.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-DeWSOEUO.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-CkpWln2S.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-CkpWln2S.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-B3VOgQmt.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-C_klIZom.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-C_klIZom.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-DDbMQsoo.js → SetupPage-BYph-InK.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-BYph-InK.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-CB3Z7VTf.js → SortableList-CvyQCYWJ.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-CvyQCYWJ.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-D95UCPHI.js → StudioPage-Cm1JYilI.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-Cm1JYilI.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-DlFnc3_S.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-DlFnc3_S.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-Chi7pCnt.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-D4pQapKj.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-DH6cg-_B.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-BKqlUOe4.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-fTgljTGA.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-D5Ys_sM0.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/dialog-k37exjNt.js +1 -0
  60. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  61. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  62. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CQBr6U9c.js → entry-B9Xvtnfj.js} +1 -1
  64. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-Bog0p925.js +4 -0
  66. package/dist/dashboard/assets/entry-Bog0p925.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/{github-badge-BlptL8Uf.js → github-badge-sltk1qUi.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-js6TJvdr.js → index-CZQIrdoX.js} +2 -2
  72. package/dist/dashboard/assets/index-CZQIrdoX.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-DUWDSmDV.js → inline-BhY2sSnO.js} +1 -1
  74. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-LVjgZq4f.js → input-V1umFiBk.js} +1 -1
  76. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  77. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  78. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-DupvLfDx.js → list-row-1zzZYM0B.js} +1 -1
  80. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-C4pEJUqI.js → live-BivGfuUs.js} +1 -1
  82. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-BRiuufkB.js → model-bans-CmffjLuG.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-B4ROwk8L.js → mutation-46P0oWMe.js} +1 -1
  86. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  87. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  88. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-gmjzC3RS.js → query-keys-BFhGpwR1.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-C7tBT5o-.js → routing-CAb4F37R.js} +1 -1
  92. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-WUCXorXw.js → select-BPpSsBgL.js} +1 -1
  94. package/dist/dashboard/assets/select-BPpSsBgL.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-C_zKl1GS.js → settings-DNX9sydi.js} +1 -1
  96. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-_3O0F8Xn.js → stack-BHTfXdz_.js} +1 -1
  98. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-BgTxHcJS.js → state-mPDRlQkl.js} +1 -1
  100. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DEjTtOWf.js → switch-DOmJxg7H.js} +1 -1
  102. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  103. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  104. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  105. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  106. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  107. package/dist/dashboard/assets/{useMutation-BWSC0UAS.js → useMutation-Dem0lXQj.js} +1 -1
  108. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  109. package/dist/dashboard/index.html +1 -1
  110. package/dist/dashboard/index.html.gz +0 -0
  111. package/dist/dashboard/precompressed-assets.json +54 -54
  112. package/migrations/README.md +52 -52
  113. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  114. package/package.json +95 -95
  115. package/scripts/cli/admin.ts +125 -0
  116. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  117. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -244
  118. package/scripts/cli/doctor.ts +37 -0
  119. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  120. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  121. package/scripts/cli/instance.ts +224 -0
  122. package/scripts/cli/main.ts +61 -18
  123. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  124. package/scripts/cli/port.ts +100 -0
  125. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  126. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  127. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  128. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  129. package/scripts/cli/tray/index.ts +62 -12
  130. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  131. package/scripts/cli/{command/update.ts → update.ts} +39 -39
  132. package/scripts/cli/version.ts +67 -0
  133. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  134. package/src/boot/app.ts +697 -693
  135. package/src/boot/dependencies.ts +647 -646
  136. package/src/boot/timeout.ts +2 -13
  137. package/src/config/cooldowns.ts +186 -138
  138. package/src/config/network.ts +8 -14
  139. package/src/config/providers.ts +7 -10
  140. package/src/config/runtime.ts +76 -76
  141. package/src/config/security.ts +15 -0
  142. package/src/config/spec.ts +20 -30
  143. package/src/config/telemetry.ts +0 -20
  144. package/src/config/timeouts.ts +0 -6
  145. package/src/console/admin/model-abuse/contracts.ts +28 -6
  146. package/src/console/admin/model-abuse/routes.ts +56 -3
  147. package/src/console/api-keys/contracts.ts +410 -414
  148. package/src/console/api-keys/routes.ts +881 -880
  149. package/src/console/auth/credentials.ts +3 -3
  150. package/src/console/auth/setup-service.ts +2 -2
  151. package/src/console/backup/export-endpoint.ts +6 -2
  152. package/src/console/backup/nine-router.ts +2 -5
  153. package/src/console/backup/service.ts +4 -0
  154. package/src/console/backup/store.ts +192 -29
  155. package/src/console/backup/validate.ts +3 -45
  156. package/src/console/cli-tools/fs-ops.ts +487 -487
  157. package/src/console/console-router-guards.ts +1 -1
  158. package/src/console/data-management/contracts.ts +13 -6
  159. package/src/console/data-management/operations.ts +23 -18
  160. package/src/console/data-management/routes.ts +0 -1
  161. package/src/console/domain-registration.ts +10 -5
  162. package/src/console/observability/live.ts +22 -0
  163. package/src/console/observability/store.ts +39 -16
  164. package/src/console/observability/usage-dimensions.ts +4 -4
  165. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  166. package/src/console/providers/catalog/model-operations.ts +5 -2
  167. package/src/console/providers/catalog/provider-operations.ts +4 -4
  168. package/src/console/providers/catalog/public-model-store.ts +8 -11
  169. package/src/console/providers/catalog/store.ts +1553 -1553
  170. package/src/console/providers/oauth/routes.ts +8 -12
  171. package/src/console/quota/account-quota-tenant.ts +6 -6
  172. package/src/console/quota/quota-view.ts +1 -1
  173. package/src/console/remote-support/contracts.ts +61 -0
  174. package/src/console/remote-support/routes.ts +65 -0
  175. package/src/console/remote-support/service.ts +207 -0
  176. package/src/console/response-compression.ts +0 -3
  177. package/src/console/routing/pools/probe-result.ts +3 -2
  178. package/src/console/routing/pools/probe-verdict.ts +132 -132
  179. package/src/console/routing/pools/store.ts +8 -8
  180. package/src/console/settings/contracts.ts +58 -2
  181. package/src/console/settings/store.ts +30 -3
  182. package/src/console/shared/cursor.ts +8 -8
  183. package/src/console/shared/errors.ts +1 -3
  184. package/src/network/http2-bidi.ts +2 -4
  185. package/src/network/pool/agent.ts +478 -478
  186. package/src/network/pool/selector.ts +5 -4
  187. package/src/network/retry.ts +1 -2
  188. package/src/network/ssrf.ts +5 -4
  189. package/src/observability/event-compaction.ts +80 -78
  190. package/src/observability/event-day-index.ts +13 -15
  191. package/src/observability/event-log.ts +4 -4
  192. package/src/observability/logger.ts +6 -6
  193. package/src/observability/payload-capture.ts +151 -33
  194. package/src/observability/payload-store.ts +397 -440
  195. package/src/observability/redaction.ts +5 -6
  196. package/src/observability/request-notify.ts +43 -0
  197. package/src/observability/telemetry-status.ts +8 -9
  198. package/src/observability/token-speed.ts +5 -4
  199. package/src/persistence/db-mode.ts +7 -7
  200. package/src/persistence/readiness.ts +1 -1
  201. package/src/persistence/schema.ts +1406 -1402
  202. package/src/persistence/shared-cache.ts +1 -2
  203. package/src/persistence/stores/api-key-store.ts +18 -6
  204. package/src/persistence/stores/share-store.ts +4 -3
  205. package/src/persistence/stores/telemetry-store.ts +236 -237
  206. package/src/protocol/canonical-model.ts +11 -3
  207. package/src/protocol/codecs.ts +138 -4
  208. package/src/protocol/gateway-error.ts +6 -4
  209. package/src/protocol/request/chat.ts +21 -1
  210. package/src/protocol/request/codex.ts +10 -1
  211. package/src/protocol/request/messages.ts +4 -5
  212. package/src/protocol/request/responses.ts +9 -1
  213. package/src/protocol/response/chat.ts +34 -4
  214. package/src/protocol/response/codex.ts +7 -4
  215. package/src/protocol/response/dsml.ts +140 -28
  216. package/src/protocol/response/responses.ts +11 -5
  217. package/src/protocol/stream-error-frames.ts +4 -5
  218. package/src/providers/authentication/jwt-validator.ts +9 -10
  219. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  220. package/src/providers/configured-openai-providers.ts +134 -136
  221. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  222. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  223. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  224. package/src/providers/discovery/probe-phases.ts +4 -4
  225. package/src/providers/discovery/probe-wire.ts +6 -13
  226. package/src/providers/discovery/probing-service.ts +1117 -1045
  227. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  228. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  231. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  232. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  233. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  234. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  235. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  236. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  237. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  238. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  239. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  240. package/src/providers/integrations/claude/claude.ts +608 -608
  241. package/src/providers/integrations/cline/cline-quota.ts +132 -132
  242. package/src/providers/integrations/cline/cline.ts +537 -539
  243. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  244. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  245. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  246. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  247. package/src/providers/integrations/cursor/cursor-quota.ts +309 -310
  248. package/src/providers/integrations/cursor/cursor.ts +11 -163
  249. package/src/providers/integrations/factory/factory-models.ts +3 -5
  250. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  251. package/src/providers/integrations/grok/grok.ts +6 -7
  252. package/src/providers/integrations/hermes.ts +14 -18
  253. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  254. package/src/providers/integrations/kiro/kiro.ts +1 -1
  255. package/src/providers/integrations/mistral.ts +6 -6
  256. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  257. package/src/providers/integrations/opencode/provider.ts +8 -8
  258. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  259. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  260. package/src/providers/integrations/qoder/qoder.ts +884 -865
  261. package/src/providers/integrations/shared/connect.ts +6 -6
  262. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  263. package/src/providers/integrations/trae/trae.ts +2 -2
  264. package/src/providers/integrations/zcode/oauth.ts +6 -1
  265. package/src/providers/operations/account-health-service.ts +1528 -1523
  266. package/src/providers/operations/account-reset-service.ts +1 -1
  267. package/src/providers/operations/client-versions.ts +6 -6
  268. package/src/providers/operations/custom-cli-headers.ts +3 -4
  269. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  270. package/src/providers/operations/provider-catalog-service.ts +3 -4
  271. package/src/providers/provider-metadata.ts +4 -8
  272. package/src/providers/provider-registry.ts +6 -7
  273. package/src/providers/provider-usage.ts +1 -1
  274. package/src/security/abuse.ts +15 -10
  275. package/src/security/access-control.ts +144 -146
  276. package/src/security/admission/contracts.ts +11 -0
  277. package/src/security/admission/in-memory-store.ts +4 -3
  278. package/src/security/admission/lease-sweep.ts +6 -6
  279. package/src/security/admission/reasons.ts +5 -1
  280. package/src/security/admission/redis-store.ts +211 -210
  281. package/src/security/admission/service.ts +4 -0
  282. package/src/security/api-key-auth.ts +504 -504
  283. package/src/security/ip-boundary.ts +155 -15
  284. package/src/security/model-abuse.ts +366 -94
  285. package/src/security/outbound-headers.ts +3 -4
  286. package/src/shared/sleep.ts +4 -0
  287. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  288. package/src/transport/dispatch/attempt-loop.ts +531 -530
  289. package/src/transport/dispatch/stream-bridge.ts +1 -3
  290. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  291. package/src/transport/dispatch/streaming/stream.ts +5 -6
  292. package/src/transport/failure-policy.ts +162 -8
  293. package/src/transport/guards/auth-guard.ts +7 -4
  294. package/src/transport/guards/concurrency-guard.ts +0 -5
  295. package/src/transport/guards/error-lifecycle.ts +13 -6
  296. package/src/transport/guards/pipeline.ts +15 -3
  297. package/src/transport/guards/request-context.ts +8 -2
  298. package/src/transport/request/inflight.ts +5 -6
  299. package/src/transport/request/preparer.ts +2 -2
  300. package/src/transport/request/state.ts +12 -8
  301. package/src/transport/routing/admission.ts +4 -0
  302. package/src/transport/routing/route-catalog.ts +8 -1
  303. package/src/transport/routing/route-model.ts +46 -14
  304. package/src/transport/routing/router.ts +21 -14
  305. package/src/transport/surface/completion.ts +2 -3
  306. package/src/transport/surface/dialects.ts +5 -6
  307. package/src/transport/surface/responses/encode.ts +38 -0
  308. package/src/transport/surface/responses/parse.ts +4 -0
  309. package/src/transport/translation/capabilities.ts +2 -3
  310. package/src/transport/translation/quirks.ts +3 -3
  311. package/src/workers/buddy-growth-tasks.ts +5 -10
  312. package/src/workers/daily-checkin.ts +17 -23
  313. package/src/workers/quota-refresh-sweep.ts +26 -26
  314. package/dist/dashboard/assets/BannedPage-C2orBsI2.js.gz +0 -0
  315. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js +0 -1
  316. package/dist/dashboard/assets/CliToolDetailPage-DUcZ6pMB.js.gz +0 -0
  317. package/dist/dashboard/assets/CliToolsPage-BV6pxVQo.js.gz +0 -0
  318. package/dist/dashboard/assets/CombosPage-Bsan4679.js +0 -1
  319. package/dist/dashboard/assets/CombosPage-Bsan4679.js.gz +0 -0
  320. package/dist/dashboard/assets/ConsoleLogPage-CW-wPmJ6.js.gz +0 -0
  321. package/dist/dashboard/assets/CustomizationPage-0p8uX7vB.js.gz +0 -0
  322. package/dist/dashboard/assets/HealthEventsModal-BjqSMfko.js.gz +0 -0
  323. package/dist/dashboard/assets/LoginPage-BvzCx8Kr.js.gz +0 -0
  324. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js +0 -1
  325. package/dist/dashboard/assets/ModelPicker-BY1aKEkP.js.gz +0 -0
  326. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js +0 -1
  327. package/dist/dashboard/assets/OverviewPage-DOQpy1y0.js.gz +0 -0
  328. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js +0 -3
  329. package/dist/dashboard/assets/ProviderDetailPage-ecxyUMzt.js.gz +0 -0
  330. package/dist/dashboard/assets/ProvidersPage-Js9jUPSb.js.gz +0 -0
  331. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js +0 -7
  332. package/dist/dashboard/assets/ProxyPage-J3TBi3Kx.js.gz +0 -0
  333. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js +0 -1
  334. package/dist/dashboard/assets/QuotaPage-C1gZS6xr.js.gz +0 -0
  335. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js +0 -1
  336. package/dist/dashboard/assets/SettingsPage-CS86ho5X.js.gz +0 -0
  337. package/dist/dashboard/assets/SetupPage-DDbMQsoo.js.gz +0 -0
  338. package/dist/dashboard/assets/SortableList-CB3Z7VTf.js.gz +0 -0
  339. package/dist/dashboard/assets/StudioPage-D95UCPHI.js.gz +0 -0
  340. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js +0 -56
  341. package/dist/dashboard/assets/UsagePage-Cnae9SWA.js.gz +0 -0
  342. package/dist/dashboard/assets/api-keys-VY68Mhd1.js +0 -1
  343. package/dist/dashboard/assets/api-keys-VY68Mhd1.js.gz +0 -0
  344. package/dist/dashboard/assets/app-COlK95oi.css +0 -2
  345. package/dist/dashboard/assets/app-COlK95oi.css.gz +0 -0
  346. package/dist/dashboard/assets/assert-Chi7pCnt.js.gz +0 -0
  347. package/dist/dashboard/assets/badge-D4pQapKj.js.gz +0 -0
  348. package/dist/dashboard/assets/banner-DH6cg-_B.js.gz +0 -0
  349. package/dist/dashboard/assets/card-BKqlUOe4.js.gz +0 -0
  350. package/dist/dashboard/assets/cli-tools-fTgljTGA.js.gz +0 -0
  351. package/dist/dashboard/assets/clipboard-button-D5Ys_sM0.js.gz +0 -0
  352. package/dist/dashboard/assets/dialog-BYj-8WuK.js +0 -1
  353. package/dist/dashboard/assets/dialog-BYj-8WuK.js.gz +0 -0
  354. package/dist/dashboard/assets/drawer-CmXIZ6H1.js +0 -1
  355. package/dist/dashboard/assets/drawer-CmXIZ6H1.js.gz +0 -0
  356. package/dist/dashboard/assets/entry-CBg5zeu8.js +0 -1
  357. package/dist/dashboard/assets/entry-CBg5zeu8.js.gz +0 -0
  358. package/dist/dashboard/assets/entry-CQBr6U9c.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-CgdSAzEV.js +0 -4
  360. package/dist/dashboard/assets/entry-CgdSAzEV.js.gz +0 -0
  361. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-BlptL8Uf.js.gz +0 -0
  363. package/dist/dashboard/assets/index-js6TJvdr.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-DUWDSmDV.js.gz +0 -0
  365. package/dist/dashboard/assets/input-LVjgZq4f.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-CPfxpsfy.js +0 -1
  367. package/dist/dashboard/assets/layout-CPfxpsfy.js.gz +0 -0
  368. package/dist/dashboard/assets/list-row-DupvLfDx.js.gz +0 -0
  369. package/dist/dashboard/assets/live-C4pEJUqI.js.gz +0 -0
  370. package/dist/dashboard/assets/model-bans-BRiuufkB.js.gz +0 -0
  371. package/dist/dashboard/assets/mutation-B4ROwk8L.js.gz +0 -0
  372. package/dist/dashboard/assets/numbers-YNcHvRY7.js +0 -2
  373. package/dist/dashboard/assets/numbers-YNcHvRY7.js.gz +0 -0
  374. package/dist/dashboard/assets/query-keys-gmjzC3RS.js.gz +0 -0
  375. package/dist/dashboard/assets/routing-C7tBT5o-.js.gz +0 -0
  376. package/dist/dashboard/assets/select-WUCXorXw.js.gz +0 -0
  377. package/dist/dashboard/assets/settings-C_zKl1GS.js.gz +0 -0
  378. package/dist/dashboard/assets/stack-_3O0F8Xn.js.gz +0 -0
  379. package/dist/dashboard/assets/state-BgTxHcJS.js.gz +0 -0
  380. package/dist/dashboard/assets/switch-DEjTtOWf.js.gz +0 -0
  381. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  382. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  383. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  384. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  385. package/dist/dashboard/assets/useMutation-BWSC0UAS.js.gz +0 -0
  386. package/scripts/cli/command/admin.ts +0 -65
  387. package/scripts/cli/command/doctor.ts +0 -257
  388. package/scripts/cli/gateway/drain-token.ts +0 -78
  389. package/scripts/cli/gateway/instance-lock.ts +0 -105
  390. package/scripts/cli/gateway/stop.ts +0 -51
  391. package/scripts/cli/support/bun.ts +0 -27
  392. package/scripts/cli/support/version.ts +0 -37
  393. /package/dist/dashboard/assets/{app-DCQZRmHY.js → app-BIt446yE.js} +0 -0
  394. /package/dist/dashboard/assets/{app-DCQZRmHY.js.gz → app-BIt446yE.js.gz} +0 -0
  395. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -1,65 +0,0 @@
1
- /**
2
- * The two admin actions the menu and the tray both offer.
3
- *
4
- * Both go through the running gateway rather than the database file: the
5
- * gateway holds the connection and the schema is dialect-dependent, so reading
6
- * or writing the tables from this process would need a second implementation
7
- * of both.
8
- */
9
-
10
- /**
11
- * Asks the gateway to write a gzipped config backup into the download
12
- * directory. Returns the written path and counts, or null when it failed.
13
- */
14
- export async function exportBackup(
15
- port: number,
16
- token: string,
17
- ): Promise<{ path: string; bytes: number; counts: Record<string, number> } | null> {
18
- try {
19
- const response = await fetch(`http://127.0.0.1:${port}/admin/export-backup`, {
20
- method: "POST",
21
- headers: { "content-type": "application/json", "x-drain-token": token },
22
- body: JSON.stringify({}),
23
- signal: AbortSignal.timeout(60_000),
24
- });
25
- if (!response.ok) {
26
- const detail = await response.text().catch(() => "");
27
- console.error(`The gateway refused the backup (HTTP ${response.status}). ${detail}`.trim());
28
- return null;
29
- }
30
- return (await response.json()) as {
31
- path: string;
32
- bytes: number;
33
- counts: Record<string, number>;
34
- };
35
- } catch {
36
- console.error("Could not reach the gateway. Is it still running?");
37
- return null;
38
- }
39
- }
40
-
41
- /**
42
- * Posts the console-account reset to the running gateway.
43
- *
44
- * Returns 0 on success and 1 on any failure so the caller can print one line
45
- * either way.
46
- */
47
- export async function resetConsoleAccount(port: number, token: string): Promise<number> {
48
- try {
49
- const response = await fetch(`http://127.0.0.1:${port}/admin/reset-console-account`, {
50
- method: "POST",
51
- headers: { "content-type": "application/json", "x-drain-token": token },
52
- body: JSON.stringify({ confirm: "RESET" }),
53
- signal: AbortSignal.timeout(10_000),
54
- });
55
- if (!response.ok) {
56
- const detail = await response.text().catch(() => "");
57
- console.error(`The gateway refused the reset (HTTP ${response.status}). ${detail}`.trim());
58
- return 1;
59
- }
60
- return 0;
61
- } catch {
62
- console.error("Could not reach the gateway. Is it still running?");
63
- return 1;
64
- }
65
- }
@@ -1,257 +0,0 @@
1
- import { accessSync, constants, existsSync, mkdirSync } from "node:fs";
2
- import { ENV_PATH, hasPlaceholderSecrets, readEnvFile } from "../support/env";
3
- import { getLocalPlatform, getOsHints } from "../support/platform";
4
- import { parseServiceUrl, probeTcpService } from "../support/services";
5
- import type { ProbeResult } from "../support/services";
6
- import { resolveDataDir, resolveDbMode, type DbMode } from "../../../src/persistence/db-mode";
7
-
8
- interface DoctorConfig {
9
- envPath: string;
10
- probeTimeoutMs: number;
11
- }
12
-
13
- function loadConfig(): DoctorConfig {
14
- return {
15
- envPath: ENV_PATH,
16
- probeTimeoutMs: 3000,
17
- };
18
- }
19
-
20
- /**
21
- * Operator escape hatch: clears the console login lockout bucket.
22
- *
23
- * The lockout is keyed by `<username>:<clientIp>` and lasts an hour, and there
24
- * is no console surface to clear it — a locked-out operator has no way back in
25
- * short of editing the database by hand. This is the documented reset.
26
- *
27
- * Usage:
28
- * bun run doctor --reset-lockout <username> <clientIp>
29
- * bun run doctor --reset-lockout --all
30
- */
31
- async function resetLockout(args: readonly string[]): Promise<void> {
32
- const all = args.includes("--all");
33
- const positional = args.filter((arg) => !arg.startsWith("--"));
34
- if (!all && positional.length < 2) {
35
- console.error("Usage: bun run doctor --reset-lockout <username> <clientIp>");
36
- console.error(" bun run doctor --reset-lockout --all");
37
- process.exit(1);
38
- }
39
-
40
- const env = await readEnvFile(ENV_PATH);
41
- const databaseUrl = env.DATABASE_URL ?? process.env.DATABASE_URL;
42
- if (!databaseUrl) {
43
- console.error("✗ DATABASE_URL is not set; cannot reach the lockout table.");
44
- process.exit(1);
45
- }
46
- process.env.DATABASE_URL = databaseUrl;
47
-
48
- const { getDb, closeDb } = await import("../../../src/persistence/boot");
49
- const { consoleLockouts } = await import("../../../src/persistence/schema");
50
- const { eq, or } = await import("drizzle-orm");
51
-
52
- const db = getDb();
53
- try {
54
- if (all) {
55
- const cleared = await db.delete(consoleLockouts).returning({ ip: consoleLockouts.ip });
56
- console.log(`✓ Cleared ${cleared.length} lockout row(s).`);
57
- return;
58
- }
59
- const username = positional[0]!.trim().toLowerCase();
60
- const clientIp = positional[1]!;
61
- // The bucket key is `<identifier>:<ip>`; match either exact spelling so
62
- // an operator does not have to guess how the identifier was normalized.
63
- const cleared = await db
64
- .delete(consoleLockouts)
65
- .where(or(eq(consoleLockouts.ip, `${username}:${clientIp}`), eq(consoleLockouts.ip, clientIp)))
66
- .returning({ ip: consoleLockouts.ip });
67
- if (cleared.length === 0) {
68
- console.log(`• No lockout row for ${username}:${clientIp} (already clear).`);
69
- } else {
70
- console.log(`✓ Cleared lockout for ${cleared.map((row) => row.ip).join(", ")}.`);
71
- }
72
- } finally {
73
- await closeDb();
74
- }
75
- }
76
-
77
-
78
- async function probeReadiness(port: number, timeoutMs: number): Promise<ProbeResult> {
79
- const controller = new AbortController();
80
- const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
81
-
82
- try {
83
- const response = await fetch(`http://localhost:${port}/health/ready`, {
84
- signal: controller.signal,
85
- });
86
-
87
- clearTimeout(timeoutHandle);
88
-
89
- if (response.ok) {
90
- return { success: true };
91
- }
92
- return { success: false, error: `HTTP ${response.status}` };
93
- } catch (err) {
94
- clearTimeout(timeoutHandle);
95
- return { success: false, error: String(err) };
96
- }
97
- }
98
-
99
- /** Runs the health check and the lockout reset; the CLI and `bun run doctor` both call this. */
100
- export async function doctor(): Promise<void> {
101
- const config = loadConfig();
102
-
103
- const resetIndex = process.argv.indexOf("--reset-lockout");
104
- if (resetIndex !== -1) {
105
- await resetLockout(process.argv.slice(resetIndex + 1));
106
- return;
107
- }
108
-
109
- console.log("🩺 Cartethyia Health Check\n");
110
- console.log(`🖥️ Host platform: ${getLocalPlatform()}`);
111
-
112
- // Step 1: Check .env exists
113
- console.log("📋 Environment Configuration");
114
- if (!existsSync(config.envPath)) {
115
- console.log(" ✗ .env file not found");
116
- console.log(" Run: bun setup\n");
117
- process.exit(1);
118
- }
119
- console.log(" ✓ .env file exists");
120
-
121
- // Step 2: Check required environment variables. DATABASE_URL is required in
122
- // full mode only; lite keeps its database in the data dir. Redis is never
123
- // required — unset means the in-memory backend.
124
- const envVars = await readEnvFile(config.envPath);
125
- // `CARTETHYIA_DB_MODE` has no default and throws when absent, which is the
126
- // right answer at boot but the wrong one here: this command exists to report
127
- // a broken environment readably, so the missing key joins the checklist
128
- // instead of crashing before it prints.
129
- const missing: string[] = [];
130
- let dbMode: DbMode | null = null;
131
- try {
132
- dbMode = resolveDbMode({ ...process.env, ...envVars });
133
- } catch {
134
- console.log(" ✗ CARTETHYIA_DB_MODE is not set — run: bun setup");
135
- // Counted as missing so this command exits non-zero. A boot would throw on
136
- // the same key, so a green doctor here would be a false all-clear.
137
- missing.push("CARTETHYIA_DB_MODE");
138
- }
139
- const required =
140
- dbMode === "full"
141
- ? ["DATABASE_URL", "CARTETHYIA_ENCRYPTION_KEY"]
142
- : ["CARTETHYIA_ENCRYPTION_KEY"];
143
-
144
- for (const key of required) {
145
- const value = envVars[key] ?? process.env[key];
146
- if (!value) {
147
- missing.push(key);
148
- console.log(` ✗ ${key} is not set`);
149
- } else if (hasPlaceholderSecrets(value)) {
150
- console.log(` ⚠️ ${key} has placeholder value — update before production`);
151
- } else {
152
- console.log(` ✓ ${key} is set`);
153
- }
154
- }
155
-
156
- if (missing.length > 0) {
157
- console.log(
158
- `\nFailed: ${missing.length} required environment variable(s) missing or using placeholders\n`,
159
- );
160
- process.exit(1);
161
- }
162
-
163
- console.log();
164
-
165
- // Step 3: Check database. Full mode probes the TCP service; lite verifies
166
- // the data dir is writable (the SQLite backend creates the file at boot too).
167
- if (dbMode === "lite") {
168
- const dataDir = resolveDataDir({ ...process.env, ...envVars });
169
- try {
170
- mkdirSync(dataDir, { recursive: true });
171
- accessSync(dataDir, constants.W_OK);
172
- console.log(` ✓ Lite data dir is writable (${dataDir})`);
173
- } catch {
174
- console.log(` ✗ Lite data dir is not writable (${dataDir})`);
175
- process.exit(1);
176
- }
177
- } else {
178
- const dbUrl = envVars.DATABASE_URL ?? process.env.DATABASE_URL;
179
- const dbParsed = parseServiceUrl(dbUrl ?? "");
180
-
181
- if (!dbParsed) {
182
- console.log(" ✗ Invalid DATABASE_URL format");
183
- process.exit(1);
184
- }
185
-
186
- const dbResult = await probeTcpService(dbParsed.host, dbParsed.port, config.probeTimeoutMs);
187
- if (dbResult.success) {
188
- console.log(` ✓ Reachable at ${dbParsed.host}:${dbParsed.port}`);
189
- } else {
190
- console.log(` ✗ Not reachable at ${dbParsed.host}:${dbParsed.port}`);
191
- console.log(` Error: ${dbResult.error || "connection refused"}`);
192
- for (const hint of getOsHints("postgres")) console.log(` ${hint}`);
193
- process.exit(1);
194
- }
195
- }
196
-
197
- console.log();
198
-
199
- // Step 4: Check Redis connectivity — informational only. A configured URL
200
- // is probed; unset means the in-memory backend, which is always up.
201
- console.log("🔴 Redis");
202
- const redisUrl = envVars.REDIS_URL ?? process.env.REDIS_URL;
203
- if (!redisUrl) {
204
- console.log(" ✓ in-memory backend (REDIS_URL not set)");
205
- } else {
206
- const redisParsed = parseServiceUrl(redisUrl);
207
- if (!redisParsed) {
208
- console.log(" ✗ REDIS_URL is invalid");
209
- process.exit(1);
210
- }
211
- const redisResult = await probeTcpService(
212
- redisParsed.host,
213
- redisParsed.port,
214
- config.probeTimeoutMs,
215
- );
216
- if (redisResult.success) {
217
- console.log(` ✓ Reachable at ${redisParsed.host}:${redisParsed.port}`);
218
- } else {
219
- console.log(` ✗ Not reachable at ${redisParsed.host}:${redisParsed.port}`);
220
- console.log(` Error: ${redisResult.error || "connection refused"}`);
221
- for (const hint of getOsHints("redis")) console.log(` ${hint}`);
222
- process.exit(1);
223
- }
224
- }
225
-
226
- console.log();
227
-
228
- // Step 5: Check application readiness
229
- console.log("🏥 Application Readiness");
230
- // Use the same explicit PORT resolution as the application.
231
- const port = Number(envVars.PORT ?? process.env.PORT ?? "12800");
232
- console.log(` Checking http://localhost:${port}/health/ready...`);
233
-
234
- const readinessResult = await probeReadiness(port, config.probeTimeoutMs);
235
- if (readinessResult.success) {
236
- console.log(` ✓ Application is ready`);
237
- } else {
238
- console.log(` ✗ Application is not ready`);
239
- console.log(` Error: ${readinessResult.error || "connection refused"}`);
240
- console.log("");
241
- console.log("Make sure the application is running:");
242
- console.log(` bun run dev`);
243
- process.exit(1);
244
- }
245
-
246
- console.log();
247
- console.log("✅ All systems operational!");
248
- }
249
-
250
- // `bun run doctor` still works: the module is a script when run directly and a
251
- // command when imported by the CLI.
252
- if (import.meta.main) {
253
- doctor().catch((err) => {
254
- console.error("❌ Health check failed:", err);
255
- process.exit(1);
256
- });
257
- }
@@ -1,78 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- /**
5
- * The install's drain token, persisted in the config directory.
6
- *
7
- * Why it persists instead of being generated per run: the tray that "Hide to
8
- * tray" starts runs in a different process and drains the gateway through the
9
- * loopback endpoint, so the token has to match across processes. A token
10
- * regenerated on each start could never authenticate against a gateway another
11
- * process had started.
12
- *
13
- * It is kept out of `.env` on purpose: it authorises a shutdown, so it belongs
14
- * in a machine-written file the operator never hand-edits and never commits.
15
- * Mode 0600 where the platform honours it.
16
- */
17
- const RUNTIME_STATE_FILE = "runtime.json";
18
-
19
- interface RuntimeState {
20
- readonly drainToken: string;
21
- }
22
-
23
- function statePath(): string | null {
24
- const configDir = process.env.CARTETHYIA_CONFIG_DIR;
25
- return configDir ? join(configDir, RUNTIME_STATE_FILE) : null;
26
- }
27
-
28
- function readState(): RuntimeState | null {
29
- const path = statePath();
30
- if (path === null || !existsSync(path)) return null;
31
- try {
32
- const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
33
- if (
34
- parsed !== null &&
35
- typeof parsed === "object" &&
36
- "drainToken" in parsed &&
37
- typeof parsed.drainToken === "string" &&
38
- parsed.drainToken.length > 0
39
- ) {
40
- return { drainToken: parsed.drainToken };
41
- }
42
- } catch {
43
- // A corrupt or stale state file reads as "no token".
44
- }
45
- return null;
46
- }
47
-
48
- /**
49
- * Returns the install's drain token, creating and persisting one on first use.
50
- *
51
- * Every start calls this, so a gateway started from the menu, from `tray`, or
52
- * from `cartethyia start` all authenticate with the same token and `stop`
53
- * always has the one that works.
54
- */
55
- export function ensureDrainToken(): string {
56
- const existing = readState();
57
- if (existing !== null) return existing.drainToken;
58
-
59
- const token = crypto.randomUUID().replace(/-/g, "") + crypto.randomUUID().replace(/-/g, "");
60
- const path = statePath();
61
- if (path !== null) {
62
- try {
63
- writeFileSync(path, JSON.stringify({ drainToken: token }), {
64
- encoding: "utf8",
65
- mode: 0o600,
66
- });
67
- } catch {
68
- // A read-only config directory still yields a usable in-process token;
69
- // only `stop` from another process is lost.
70
- }
71
- }
72
- return token;
73
- }
74
-
75
- /** The persisted token, or null when this install has never started. */
76
- export function readPersistedDrainToken(): string | null {
77
- return readState()?.drainToken ?? null;
78
- }
@@ -1,105 +0,0 @@
1
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- /**
5
- * A marker that one gateway owns this install's port.
6
- *
7
- * The port probe alone cannot tell a healthy gateway from a stranger's process:
8
- * something answering on 12800 is not necessarily ours, so a start that found
9
- * the port busy either failed outright or — worse — would have to guess before
10
- * stopping anything. The lock names the PID and the token it was written with,
11
- * which is what makes "is this our gateway?" answerable without guessing.
12
- *
13
- * It is advisory, not an OS lock: a stale file from a crashed process is the
14
- * normal case this exists to recover from, so presence alone never blocks a
15
- * start. Liveness is decided by probing the port, and identity by the token.
16
- */
17
- export interface InstanceLock {
18
- readonly pid: number;
19
- readonly port: number;
20
- /** Fingerprint of the drain token, never the token itself. */
21
- readonly tokenFingerprint: string;
22
- readonly startedAt: string;
23
- }
24
-
25
- const LOCK_FILE = "instance.json";
26
-
27
- function lockPath(): string | null {
28
- const configDir = process.env.CARTETHYIA_CONFIG_DIR;
29
- return configDir ? join(configDir, LOCK_FILE) : null;
30
- }
31
-
32
- /** A short, non-reversible fingerprint so the token itself is never written. */
33
- export function fingerprint(token: string): string {
34
- let hash = 0;
35
- for (let i = 0; i < token.length; i += 1) {
36
- hash = (hash * 31 + token.charCodeAt(i)) | 0;
37
- }
38
- return (hash >>> 0).toString(16).padStart(8, "0");
39
- }
40
-
41
- export function writeInstanceLock(port: number, pid: number, token: string): void {
42
- const path = lockPath();
43
- if (path === null) return;
44
- const lock: InstanceLock = {
45
- pid,
46
- port,
47
- tokenFingerprint: fingerprint(token),
48
- startedAt: new Date().toISOString(),
49
- };
50
- try {
51
- writeFileSync(path, JSON.stringify(lock), { encoding: "utf8", mode: 0o600 });
52
- } catch {
53
- // A read-only config directory costs the marker, not the run.
54
- }
55
- }
56
-
57
- export function readInstanceLock(): InstanceLock | null {
58
- const path = lockPath();
59
- if (path === null || !existsSync(path)) return null;
60
- try {
61
- const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
62
- if (
63
- parsed !== null &&
64
- typeof parsed === "object" &&
65
- "pid" in parsed &&
66
- typeof parsed.pid === "number" &&
67
- "port" in parsed &&
68
- typeof parsed.port === "number" &&
69
- "tokenFingerprint" in parsed &&
70
- typeof parsed.tokenFingerprint === "string"
71
- ) {
72
- const startedAt = "startedAt" in parsed && typeof parsed.startedAt === "string" ? parsed.startedAt : "";
73
- return {
74
- pid: parsed.pid,
75
- port: parsed.port,
76
- tokenFingerprint: parsed.tokenFingerprint,
77
- startedAt,
78
- };
79
- }
80
- } catch {
81
- // A corrupt marker reads as none.
82
- }
83
- return null;
84
- }
85
-
86
- export function clearInstanceLock(): void {
87
- const path = lockPath();
88
- if (path === null) return;
89
- try {
90
- unlinkSync(path);
91
- } catch {
92
- // Already gone, or the directory is read-only.
93
- }
94
- }
95
-
96
- /** Whether a process id is alive. `0` is never a real pid. */
97
- export function pidAlive(pid: number): boolean {
98
- if (!Number.isInteger(pid) || pid <= 0) return false;
99
- try {
100
- process.kill(pid, 0);
101
- return true;
102
- } catch {
103
- return false;
104
- }
105
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * Stops a gateway this process started, gracefully where possible.
3
- *
4
- * The drain endpoint is the only graceful path: it lets in-flight responses
5
- * finish. The OS-level kill is the fallback, and it is reached whenever the
6
- * endpoint is unreachable — which is also what happens when the gateway is
7
- * already on its way out, so a failure here is not necessarily an error the
8
- * operator needs to act on.
9
- */
10
- export async function stopGateway(
11
- current: Bun.Subprocess,
12
- port: number,
13
- drainToken: string,
14
- ): Promise<void> {
15
- let drainAccepted = false;
16
- try {
17
- const response = await fetch(`http://127.0.0.1:${port}/admin/drain`, {
18
- method: "POST",
19
- headers: { "x-drain-token": drainToken },
20
- signal: AbortSignal.timeout(2_000),
21
- });
22
- drainAccepted = response.status === 202;
23
- } catch {
24
- // The process may already be exiting; fall back to its OS-level termination.
25
- }
26
- if (!drainAccepted) {
27
- console.error("Could not request a graceful gateway drain; terminating the gateway process.");
28
- current.kill();
29
- await current.exited;
30
- return;
31
- }
32
-
33
- // The drain is bounded: a gateway that accepted the request but never exits
34
- // would otherwise hang the CLI. 30 s is the drain budget `src/main.ts` uses.
35
- // The timer is cleared unconditionally afterwards — leaving it pending keeps
36
- // the event loop alive for its full 30 s after the gateway is already gone,
37
- // which made every `carte` exit take half a minute.
38
- let timer: Timer | undefined;
39
- const drained = await Promise.race([
40
- current.exited,
41
- new Promise<null>((resolveExit) => {
42
- timer = setTimeout(() => resolveExit(null), 30_000);
43
- }),
44
- ]);
45
- clearTimeout(timer);
46
- if (drained === null) {
47
- console.error("Graceful drain timed out; terminating the gateway process.");
48
- current.kill();
49
- await current.exited;
50
- }
51
- }
@@ -1,27 +0,0 @@
1
- /** Minimum Bun the repository declares in `package.json` `engines`. */
2
- export const MINIMUM_BUN_VERSION = "1.4.0";
3
-
4
- /**
5
- * True when `actual` is at least `minimum`, compared as dotted integers.
6
- * A non-numeric segment reads as 0, so a malformed version fails closed
7
- * against a real minimum instead of passing.
8
- */
9
- export function bunVersionAtLeast(actual: string, minimum: string): boolean {
10
- const left = actual.split(".").map((part) => Number.parseInt(part, 10) || 0);
11
- const right = minimum.split(".").map((part) => Number.parseInt(part, 10) || 0);
12
- const length = Math.max(left.length, right.length);
13
- for (let i = 0; i < length; i += 1) {
14
- const a = left[i] ?? 0;
15
- const b = right[i] ?? 0;
16
- if (a !== b) return a > b;
17
- }
18
- return true;
19
- }
20
-
21
- /** Throws unless the running Bun satisfies the declared minimum. */
22
- export function assertBunVersion(): void {
23
- if (bunVersionAtLeast(Bun.version, MINIMUM_BUN_VERSION)) return;
24
- throw new Error(
25
- `Bun ${MINIMUM_BUN_VERSION} or newer is required (found ${Bun.version}). Install it from https://bun.sh and run this command again.`,
26
- );
27
- }
@@ -1,37 +0,0 @@
1
- import { resolvePackagePath } from "../../../src/config/package-paths";
2
-
3
- /** Reads the version from the installed package manifest. */
4
- export async function readInstalledVersion(): Promise<string> {
5
- const metadata: unknown = await Bun.file(resolvePackagePath("package.json")).json();
6
- if (
7
- metadata === null ||
8
- typeof metadata !== "object" ||
9
- !("version" in metadata) ||
10
- typeof metadata.version !== "string"
11
- ) {
12
- throw new Error("Cartethyia package version is unavailable");
13
- }
14
- return metadata.version;
15
- }
16
-
17
- /** Asks the npm registry for the newest published version, or null on failure. */
18
- export async function readLatestPublishedVersion(): Promise<string | null> {
19
- try {
20
- const response = await fetch("https://registry.npmjs.org/cartethyia/latest", {
21
- signal: AbortSignal.timeout(4_000),
22
- });
23
- if (!response.ok) return null;
24
- const metadata: unknown = await response.json();
25
- if (
26
- metadata !== null &&
27
- typeof metadata === "object" &&
28
- "version" in metadata &&
29
- typeof metadata.version === "string"
30
- ) {
31
- return metadata.version;
32
- }
33
- return null;
34
- } catch {
35
- return null;
36
- }
37
- }
File without changes