cartethyia 2.5.9 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (386) hide show
  1. package/.env.example +15 -42
  2. package/README.md +11 -10
  3. package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
  5. package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
  6. package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
  9. package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
  10. package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
  19. package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
  20. package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
  25. package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
  26. package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
  27. package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
  28. package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
  38. package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
  39. package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
  40. package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
  41. package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
  42. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  43. package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
  44. package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
  45. package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
  46. package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
  47. package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
  48. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  49. package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
  50. package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
  51. package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
  52. package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
  53. package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
  54. package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
  55. package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
  56. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  57. package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
  58. package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
  59. package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
  60. package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
  61. package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
  62. package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
  63. package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
  64. package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
  66. package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
  68. package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
  69. package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
  70. package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
  71. package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
  72. package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
  73. package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
  74. package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
  75. package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
  76. package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
  77. package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
  78. package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
  79. package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
  80. package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
  81. package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
  82. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  83. package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
  84. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  85. package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
  86. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  87. package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
  88. package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
  89. package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
  90. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  91. package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
  92. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  93. package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
  94. package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
  95. package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
  96. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  97. package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
  98. package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
  99. package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
  100. package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
  101. package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
  102. package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
  103. package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
  104. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  105. package/dist/dashboard/index.html +1 -1
  106. package/dist/dashboard/index.html.gz +0 -0
  107. package/dist/dashboard/precompressed-assets.json +51 -51
  108. package/migrations/README.md +52 -52
  109. package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
  110. package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
  111. package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
  112. package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  113. package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
  114. package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
  115. package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
  116. package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  117. package/package.json +96 -97
  118. package/scripts/cli/admin.ts +126 -125
  119. package/scripts/cli/autostart.ts +237 -237
  120. package/scripts/cli/background.ts +120 -110
  121. package/scripts/cli/bin/carte.cjs +8 -8
  122. package/scripts/cli/bin/cartethyia.cjs +167 -167
  123. package/scripts/cli/console-menu.ts +308 -275
  124. package/scripts/cli/controller.ts +145 -0
  125. package/scripts/cli/doctor.ts +1 -1
  126. package/scripts/cli/env.ts +99 -99
  127. package/scripts/cli/gateway.ts +264 -261
  128. package/scripts/{health.ts → cli/health.ts} +4 -4
  129. package/scripts/cli/instance.ts +234 -224
  130. package/scripts/cli/main.ts +188 -164
  131. package/scripts/cli/platform.ts +64 -64
  132. package/scripts/cli/prompt.ts +275 -107
  133. package/scripts/cli/services.ts +88 -88
  134. package/scripts/cli/setup.ts +445 -445
  135. package/scripts/cli/start.ts +85 -82
  136. package/scripts/cli/startup-signal.ts +23 -23
  137. package/scripts/cli/telemetry.ts +70 -0
  138. package/scripts/cli/tray/index.ts +263 -263
  139. package/scripts/cli/tray/windows.ps1 +107 -107
  140. package/scripts/cli/tray.ts +192 -208
  141. package/scripts/cli/update.ts +39 -39
  142. package/scripts/cli/version.ts +67 -67
  143. package/scripts/cli/wordmark.ts +58 -58
  144. package/src/boot/app.ts +16 -1
  145. package/src/boot/dependencies.ts +128 -25
  146. package/src/config/package-paths.ts +24 -0
  147. package/src/config/resources.ts +137 -0
  148. package/src/config/runtime.ts +133 -76
  149. package/src/config/spec.ts +30 -29
  150. package/src/config/telemetry.ts +0 -25
  151. package/src/console/api-keys/bucket-spend.ts +14 -17
  152. package/src/console/api-keys/contracts.ts +410 -410
  153. package/src/console/api-keys/routes.ts +12 -0
  154. package/src/console/audit/store.ts +2 -3
  155. package/src/console/auth/setup-service.ts +8 -1
  156. package/src/console/backup/routes.ts +7 -0
  157. package/src/console/backup/service.ts +463 -463
  158. package/src/console/backup/store.ts +16 -11
  159. package/src/console/backup/validate.ts +1 -4
  160. package/src/console/cli-tools/fs-ops.ts +18 -2
  161. package/src/console/data-management/contracts.ts +1 -8
  162. package/src/console/data-management/operations.ts +61 -48
  163. package/src/console/data-management/routes.ts +4 -2
  164. package/src/console/domain-registration.ts +25 -3
  165. package/src/console/inferhub/client.ts +139 -0
  166. package/src/console/inferhub/key-store.ts +129 -0
  167. package/src/console/inferhub/normalize.ts +347 -0
  168. package/src/console/inferhub/routes.ts +411 -0
  169. package/src/console/inferhub/store.ts +109 -0
  170. package/src/console/inferhub/types.ts +183 -0
  171. package/src/console/observability/contracts.ts +2 -41
  172. package/src/console/observability/store.ts +176 -371
  173. package/src/console/providers/catalog/public-model-store.ts +0 -0
  174. package/src/console/providers/catalog/store.ts +23 -36
  175. package/src/console/quota/account-quota-shared.ts +52 -52
  176. package/src/console/quota/account-quota-tenant.ts +16 -24
  177. package/src/console/quota/refresh.ts +441 -441
  178. package/src/console/remote-support/service.ts +270 -114
  179. package/src/console/routing/pools/import-parse.ts +206 -206
  180. package/src/console/routing/pools/operations.ts +543 -543
  181. package/src/console/routing/pools/probe-verdict.ts +132 -132
  182. package/src/console/routing/pools/routes.ts +181 -181
  183. package/src/console/routing/pools/store.ts +1 -1
  184. package/src/console/settings/contracts.ts +42 -40
  185. package/src/console/settings/store.ts +25 -25
  186. package/src/console/share/share-stats.ts +22 -4
  187. package/src/console/share/share-usage.ts +30 -4
  188. package/src/console/shared/cursor.ts +15 -0
  189. package/src/console/updates/repository-updates.ts +40 -39
  190. package/src/console/updates/routes.ts +12 -0
  191. package/src/main.ts +13 -5
  192. package/src/network/pool/resolver.ts +1104 -1104
  193. package/src/observability/client-name.ts +22 -0
  194. package/src/observability/event-log.ts +5 -29
  195. package/src/observability/event-query.ts +690 -0
  196. package/src/observability/log-ring.ts +26 -2
  197. package/src/observability/log-sanitize.ts +118 -0
  198. package/src/observability/logger.ts +17 -13
  199. package/src/observability/metrics.ts +35 -0
  200. package/src/observability/payload-capture.ts +114 -41
  201. package/src/observability/payload-store.ts +249 -24
  202. package/src/observability/raw-event-writer.ts +118 -0
  203. package/src/observability/runtime-metrics.ts +71 -1
  204. package/src/observability/telemetry-buffer.ts +100 -17
  205. package/src/observability/telemetry-reconciliation.ts +417 -0
  206. package/src/persistence/boot.ts +20 -9
  207. package/src/persistence/db-pg.ts +13 -0
  208. package/src/persistence/db-sqlite.ts +569 -497
  209. package/src/persistence/dialect.ts +3 -2
  210. package/src/persistence/migrate.ts +181 -181
  211. package/src/persistence/schema.ts +1496 -1406
  212. package/src/persistence/shared-cache.ts +5 -1
  213. package/src/persistence/stores/api-key-store.ts +581 -581
  214. package/src/persistence/stores/telemetry-store.ts +124 -63
  215. package/src/protocol/reasoning-text.ts +30 -0
  216. package/src/protocol/registry.ts +2 -0
  217. package/src/protocol/response/chat.ts +33 -7
  218. package/src/protocol/response/codex.ts +14 -2
  219. package/src/protocol/response/messages.ts +5 -2
  220. package/src/providers/authentication/oauth-flow-store.ts +3 -10
  221. package/src/providers/bundled-discovery-providers.ts +122 -122
  222. package/src/providers/default-registry.ts +14 -0
  223. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  224. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  225. package/src/providers/discovery/probe-phases.ts +43 -7
  226. package/src/providers/discovery/probing-service.ts +70 -32
  227. package/src/providers/integrations/antigravity/antigravity.ts +9 -2
  228. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
  229. package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
  230. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
  231. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
  232. package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
  233. package/src/providers/integrations/buddy/capabilities.ts +4 -0
  234. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
  235. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
  236. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
  237. package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
  238. package/src/providers/integrations/claude/claude-quota.ts +2 -2
  239. package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
  240. package/src/providers/integrations/claude/claude.ts +3 -4
  241. package/src/providers/integrations/cline/cline.ts +94 -8
  242. package/src/providers/integrations/codex/codex.ts +61 -37
  243. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
  244. package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
  245. package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
  246. package/src/providers/integrations/cursor/cursor-request.ts +11 -0
  247. package/src/providers/integrations/cursor/cursor.ts +29 -17
  248. package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
  249. package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
  250. package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
  251. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
  252. package/src/providers/integrations/github/github.ts +3 -1
  253. package/src/providers/integrations/grok/grok-quota.ts +279 -279
  254. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
  255. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
  256. package/src/providers/integrations/hermes.ts +7 -1
  257. package/src/providers/integrations/inferhub.ts +201 -73
  258. package/src/providers/integrations/kilo/kilo.ts +152 -152
  259. package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
  260. package/src/providers/integrations/opencode/provider.ts +2 -0
  261. package/src/providers/integrations/openrouter/provider.ts +129 -129
  262. package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
  263. package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
  264. package/src/providers/integrations/trae/trae-headers.ts +17 -48
  265. package/src/providers/integrations/trae/trae-oauth.ts +4 -26
  266. package/src/providers/integrations/trae/trae-quota.ts +389 -384
  267. package/src/providers/integrations/trae/trae-realm.ts +15 -2
  268. package/src/providers/integrations/trae/trae.ts +22 -24
  269. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
  270. package/src/providers/integrations/zcode/oauth.ts +1 -1
  271. package/src/providers/operations/account-health-service.ts +605 -279
  272. package/src/providers/provider-metadata.ts +12 -1
  273. package/src/providers/provider-model-definition.ts +373 -373
  274. package/src/providers/provider-probe.ts +3 -0
  275. package/src/providers/quota/quota-contracts.ts +3 -7
  276. package/src/providers/search/search-provider.ts +2 -6
  277. package/src/security/admission/contracts.ts +10 -0
  278. package/src/security/admission/in-memory-store.ts +89 -14
  279. package/src/security/admission/lua.ts +191 -182
  280. package/src/security/admission/redis-store.ts +30 -0
  281. package/src/security/admission/service.ts +18 -0
  282. package/src/security/api-key-auth-cache.ts +96 -0
  283. package/src/security/api-key-auth.ts +11 -100
  284. package/src/security/client-router-fingerprint.ts +3 -7
  285. package/src/security/ip-boundary.ts +7 -3
  286. package/src/shared/coerce.ts +67 -0
  287. package/src/shared/hash.ts +6 -0
  288. package/src/shared/time.ts +7 -0
  289. package/src/transport/dispatch/attempt-finalize.ts +26 -138
  290. package/src/transport/dispatch/attempt-loop.ts +103 -81
  291. package/src/transport/dispatch/capture-policy.ts +78 -0
  292. package/src/transport/dispatch/proxy-request.ts +4 -2
  293. package/src/transport/dispatch/streaming/encoder.ts +111 -5
  294. package/src/transport/dispatch/streaming/stream.ts +19 -2
  295. package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
  296. package/src/transport/failure-policy.ts +27 -0
  297. package/src/transport/guards/abuse-guard.ts +2 -2
  298. package/src/transport/guards/auth-guard.ts +17 -0
  299. package/src/transport/guards/body-policy.ts +3 -2
  300. package/src/transport/guards/concurrency-guard.ts +89 -13
  301. package/src/transport/guards/early-rejection-capture.ts +43 -3
  302. package/src/transport/guards/error-lifecycle.ts +10 -2
  303. package/src/transport/guards/guard-errors.ts +23 -0
  304. package/src/transport/guards/headroom-guard.ts +41 -7
  305. package/src/transport/guards/pipeline.ts +24 -0
  306. package/src/transport/guards/request-context.ts +2 -1
  307. package/src/transport/routing/admission.ts +51 -9
  308. package/src/transport/routing/bounded-lru.ts +49 -0
  309. package/src/transport/routing/candidate-index.ts +88 -0
  310. package/src/transport/routing/reservations.ts +121 -7
  311. package/src/transport/routing/round-robin.ts +10 -22
  312. package/src/transport/routing/route-catalog.ts +26 -13
  313. package/src/transport/routing/route-model.ts +22 -2
  314. package/src/transport/routing/router.ts +53 -31
  315. package/src/transport/surface/chat/encode.ts +2 -3
  316. package/src/transport/surface/chat/parse.ts +2 -1
  317. package/src/transport/surface/dialects.ts +11 -3
  318. package/src/transport/surface/messages/encode.ts +2 -1
  319. package/src/transport/surface/messages/parse.ts +3 -1
  320. package/src/transport/surface/messages/stream.ts +2 -6
  321. package/src/workers/maintenance-lease.ts +200 -0
  322. package/src/workers/scheduler.ts +101 -1
  323. package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
  324. package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
  325. package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
  326. package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
  327. package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
  328. package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
  329. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
  330. package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
  331. package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
  332. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  333. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
  334. package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
  335. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
  336. package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
  337. package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
  338. package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
  339. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
  340. package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
  341. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
  342. package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
  343. package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
  344. package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
  345. package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
  346. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  347. package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
  348. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  349. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  350. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  351. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  352. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  353. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  354. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  355. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  356. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  357. package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
  358. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  359. package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
  360. package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
  361. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  362. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  363. package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
  364. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  365. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  366. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  367. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  368. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  369. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  370. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  371. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  372. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  373. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  374. package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
  375. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  376. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  377. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  378. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  379. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  380. package/scripts/build/aot.ts +0 -143
  381. package/scripts/build/build.ts +0 -24
  382. package/scripts/build/compile.ts +0 -88
  383. package/scripts/build/restart.ts +0 -120
  384. package/scripts/build/start.ts +0 -25
  385. package/src/observability/event-compaction.ts +0 -317
  386. package/src/observability/event-day-index.ts +0 -440
@@ -1,164 +1,188 @@
1
- import { mkdirSync } from "node:fs";
2
- import { resolveUserConfigDir } from "../../src/config/package-paths";
3
- import { readRequiredBunVersion, assertBunVersion } from "./version";
4
- import { printUpdateInstructions, runDetachedUpdate } from "./update";
5
-
6
- process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
7
-
8
- const HELP = (minimumBunVersion: string) => `Cartethyia ${minimumBunVersion}+ (Bun is required)
9
-
10
- Usage:
11
- carte start, then show an arrow-key menu (no GUI needed)
12
- carte --tray start with a system-tray icon instead
13
- cartethyia setup [--non-interactive]
14
- cartethyia start
15
- cartethyia stop
16
- cartethyia menu
17
- cartethyia doctor
18
- cartethyia admin reset-lockout <username> <clientIp>
19
- cartethyia admin reset-lockout --all
20
- cartethyia update
21
-
22
- carte starts the gateway in the background and leaves only the console menu on
23
- your terminal — Open Console, Reset password, Backup, Update, Hide to tray,
24
- Exit. Arrow keys and Enter; no desktop environment needed, so it works over SSH
25
- and headless. A menu started by this command drains its child gateway on Exit;
26
- leaving a menu attached to an existing gateway only detaches. Use stop for that.
27
- carte --tray uses a system-tray icon instead, which needs a graphical session.
28
- Stop a detached gateway with cartethyia stop (or the install's ./carte stop).
29
- Setup explains Lite (embedded SQLite) and Full (external PostgreSQL), then saves
30
- configuration under the current user's Cartethyia config directory.
31
- `;
32
-
33
- /** Loads the user's `.env` into the environment without overriding it. */
34
- async function loadUserConfig(): Promise<void> {
35
- const configDir = process.env.CARTETHYIA_CONFIG_DIR;
36
- if (!configDir) throw new Error("Cartethyia config directory could not be resolved");
37
- mkdirSync(configDir, { recursive: true });
38
- // Imported here rather than at module scope: `support/env` pulls in the
39
- // package-root resolver and `node:fs/promises`, and `carte --help` (which
40
- // never reads a .env) should not pay for either before it prints.
41
- const { ENV_PATH, readEnvFile } = await import("./env");
42
- // Bun injects PORT=12800 when no explicit port was supplied. It is a
43
- // runner default, not an operator override; without removing it, a saved
44
- // custom port is ignored by npm/carte and the gateway starts on 12800.
45
- // Preserve any non-default/custom shell value exactly as before.
46
- if (process.env.PORT === "12800") delete process.env.PORT;
47
- const values = await readEnvFile(ENV_PATH);
48
- for (const [key, value] of Object.entries(values)) {
49
- if (!process.env[key]) process.env[key] = value;
50
- }
51
- }
52
-
53
- /**
54
- * Reports tray readiness to the launcher waiting on a signal file.
55
- *
56
- * Deferred to a dynamic import for the same reason as `loadUserConfig`: only
57
- * the tray path writes this file, so the other commands should not load
58
- * `node:fs` machinery they never touch.
59
- */
60
- async function reportTrayFailed(message: string): Promise<void> {
61
- const { reportTrayStartup } = await import("./startup-signal");
62
- reportTrayStartup({ status: "failed", message });
63
- }
64
-
65
- async function main(): Promise<void> {
66
- await assertBunVersion();
67
- const [command] = process.argv.slice(2);
68
- if (command === "setup" || command === "start" || command === "stop" || command === "tray" || command === "menu" || command === "doctor" || command === "admin") {
69
- await loadUserConfig();
70
- }
71
-
72
- switch (command) {
73
- case "setup": {
74
- const { setup } = await import("./setup");
75
- await setup();
76
- return;
77
- }
78
- case "start": {
79
- const { startForeground } = await import("./start");
80
- await startForeground();
81
- return;
82
- }
83
- case "stop": {
84
- const { resolvePort } = await import("../../src/config/runtime");
85
- const { ensureDrainToken, attachGateway, stopAttachedGateway } = await import("./instance");
86
- const port = resolvePort();
87
- const token = ensureDrainToken();
88
- const gateway = await attachGateway(port, token);
89
- if (!gateway) {
90
- console.log("No running Cartethyia gateway is owned by this installation.");
91
- return;
92
- }
93
- await stopAttachedGateway(gateway, token);
94
- console.log("Cartethyia stopped.");
95
- return;
96
- }
97
- case "menu": {
98
- const { startMenu } = await import("./console-menu");
99
- await startMenu();
100
- return;
101
- }
102
- case "tray": {
103
- const { startTray } = await import("./tray");
104
- await startTray();
105
- return;
106
- }
107
- case "autostart": {
108
- const { enableAutostart, disableAutostart, autostartStatus } = await import("./autostart");
109
- const arg = Bun.argv[3]?.trim();
110
- if (arg === "off" || arg === "disable") {
111
- const state = await disableAutostart();
112
- console.log(`Autostart disabled (${state.location || state.platform}).`);
113
- return;
114
- }
115
- if (arg === "status") {
116
- const state = autostartStatus();
117
- console.log(
118
- `Autostart: ${state.enabled ? "enabled" : "not enabled"} — ${state.location || state.platform}` +
119
- (state.reason ? ` (${state.reason})` : ""),
120
- );
121
- return;
122
- }
123
- // The tray is what autostart launches: a gateway alone has no session to
124
- // attach to and no icon to show.
125
- const state = await enableAutostart(process.execPath, [Bun.argv[1] ?? "carte", "tray"]);
126
- console.log(
127
- state.enabled
128
- ? `Autostart enabled — ${state.location}`
129
- : `Autostart not enabled: ${state.reason ?? "unknown reason"}`,
130
- );
131
- return;
132
- }
133
- case "doctor": {
134
- const { doctor } = await import("./doctor");
135
- await doctor();
136
- return;
137
- }
138
- case "admin": {
139
- if (process.argv[3] !== "reset-lockout") throw new Error("Usage: cartethyia admin reset-lockout <username> <clientIp> | --all");
140
- const { resetLockout } = await import("./admin");
141
- await resetLockout(process.argv.slice(4));
142
- return;
143
- }
144
- case "update":
145
- runDetachedUpdate();
146
- printUpdateInstructions("this install", null);
147
- return;
148
- case "--help":
149
- case "-h":
150
- case undefined:
151
- console.log(HELP(await readRequiredBunVersion()));
152
- return;
153
- default:
154
- console.error(`Unknown command: ${command}\n\n${HELP(await readRequiredBunVersion())}`);
155
- process.exitCode = 2;
156
- }
157
- }
158
-
159
- main().catch(async (error: unknown) => {
160
- const message = error instanceof Error ? error.message : "unknown error";
161
- if (process.argv[2] === "tray") await reportTrayFailed(message);
162
- console.error(`Cartethyia failed: ${message}`);
163
- process.exitCode = 1;
164
- });
1
+ // dynamic-import-ok: subcommand dispatcher. Every branch lazy-loads only the
2
+ // module its command needs so `cartethyia --help` and `cartethyia stop` do
3
+ // not drag the gateway, dashboard, and database driver into memory.
4
+ import { mkdirSync } from "node:fs";
5
+ import { resolveUserConfigDir } from "../../src/config/package-paths";
6
+ import { readRequiredBunVersion, assertBunVersion } from "./version";
7
+ import { printUpdateInstructions, runDetachedUpdate } from "./update";
8
+
9
+ process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
10
+
11
+ const HELP = (minimumBunVersion: string) => `Cartethyia ${minimumBunVersion}+ (Bun is required)
12
+
13
+ Usage:
14
+ carte start, then show an arrow-key menu (no GUI needed)
15
+ carte --tray start with a system-tray icon instead
16
+ cartethyia setup [--non-interactive]
17
+ cartethyia start
18
+ cartethyia stop
19
+ cartethyia menu
20
+ cartethyia doctor
21
+ cartethyia admin reset-lockout <username> <clientIp>
22
+ cartethyia admin reset-lockout --all
23
+ cartethyia telemetry reconcile --apply|--verify
24
+ cartethyia update
25
+
26
+ carte starts the gateway in the background and leaves only the console menu on
27
+ your terminal — Open Console, Reset password, Backup, Update, Hide to tray,
28
+ Exit. Arrow keys and Enter; no desktop environment needed, so it works over SSH
29
+ and headless. A menu started by this command drains its child gateway on Exit;
30
+ leaving a menu attached to an existing gateway only detaches. Use stop for that.
31
+ carte --tray uses a system-tray icon instead, which needs a graphical session.
32
+ Stop a detached gateway with cartethyia stop (or the install's ./carte stop).
33
+ Setup explains Lite (embedded SQLite) and Full (external PostgreSQL), then saves
34
+ configuration under the current user's Cartethyia config directory.
35
+ `;
36
+
37
+ /** Loads the user's `.env` into the environment without overriding it. */
38
+ async function loadUserConfig(): Promise<void> {
39
+ const configDir = process.env.CARTETHYIA_CONFIG_DIR;
40
+ if (!configDir) throw new Error("Cartethyia config directory could not be resolved");
41
+ mkdirSync(configDir, { recursive: true });
42
+ // Imported here rather than at module scope: `support/env` pulls in the
43
+ // package-root resolver and `node:fs/promises`, and `carte --help` (which
44
+ // never reads a .env) should not pay for either before it prints.
45
+ const { ENV_PATH, readEnvFile } = await import("./env");
46
+ // Bun injects PORT=12800 when no explicit port was supplied. It is a
47
+ // runner default, not an operator override; without removing it, a saved
48
+ // custom port is ignored by npm/carte and the gateway starts on 12800.
49
+ // Preserve any non-default/custom shell value exactly as before.
50
+ if (process.env.PORT === "12800") delete process.env.PORT;
51
+ const values = await readEnvFile(ENV_PATH);
52
+ for (const [key, value] of Object.entries(values)) {
53
+ if (!process.env[key]) process.env[key] = value;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Reports tray readiness to the launcher waiting on a signal file.
59
+ *
60
+ * Deferred to a dynamic import for the same reason as `loadUserConfig`: only
61
+ * the tray path writes this file, so the other commands should not load
62
+ * `node:fs` machinery they never touch.
63
+ */
64
+ async function reportTrayFailed(message: string): Promise<void> {
65
+ const { reportTrayStartup } = await import("./startup-signal");
66
+ reportTrayStartup({ status: "failed", message });
67
+ }
68
+
69
+ async function main(): Promise<void> {
70
+ await assertBunVersion();
71
+ const [command] = process.argv.slice(2);
72
+ if (command === "setup" || command === "start" || command === "stop" || command === "tray" || command === "menu" || command === "doctor" || command === "admin" || command === "telemetry") {
73
+ await loadUserConfig();
74
+ }
75
+
76
+ switch (command) {
77
+ case "setup": {
78
+ const { setup } = await import("./setup");
79
+ await setup();
80
+ return;
81
+ }
82
+ case "start": {
83
+ const { startForeground } = await import("./start");
84
+ await startForeground();
85
+ return;
86
+ }
87
+ case "stop": {
88
+ const { resolvePort } = await import("../../src/config/runtime");
89
+ const { ensureDrainToken, attachGateway, stopAttachedGateway } = await import("./instance");
90
+ const port = resolvePort();
91
+ const token = ensureDrainToken();
92
+ const gateway = await attachGateway(port, token);
93
+ if (!gateway) {
94
+ console.log("No running Cartethyia gateway is owned by this installation.");
95
+ return;
96
+ }
97
+ await stopAttachedGateway(gateway, token);
98
+ console.log("Cartethyia stopped.");
99
+ return;
100
+ }
101
+ case "menu": {
102
+ const { startMenu } = await import("./console-menu");
103
+ await startMenu();
104
+ return;
105
+ }
106
+ case "tray": {
107
+ const { startTray } = await import("./tray");
108
+ await startTray();
109
+ return;
110
+ }
111
+ case "autostart": {
112
+ const { enableAutostart, disableAutostart, autostartStatus } = await import("./autostart");
113
+ const arg = Bun.argv[3]?.trim();
114
+ if (arg === "off" || arg === "disable") {
115
+ const state = await disableAutostart();
116
+ console.log(`Autostart disabled (${state.location || state.platform}).`);
117
+ return;
118
+ }
119
+ if (arg === "status") {
120
+ const state = autostartStatus();
121
+ console.log(
122
+ `Autostart: ${state.enabled ? "enabled" : "not enabled"} — ${state.location || state.platform}` +
123
+ (state.reason ? ` (${state.reason})` : ""),
124
+ );
125
+ return;
126
+ }
127
+ // The tray is what autostart launches: a gateway alone has no session to
128
+ // attach to and no icon to show.
129
+ const state = await enableAutostart(process.execPath, [Bun.argv[1] ?? "carte", "tray"]);
130
+ console.log(
131
+ state.enabled
132
+ ? `Autostart enabled — ${state.location}`
133
+ : `Autostart not enabled: ${state.reason ?? "unknown reason"}`,
134
+ );
135
+ return;
136
+ }
137
+ case "doctor": {
138
+ const { doctor } = await import("./doctor");
139
+ await doctor();
140
+ return;
141
+ }
142
+ case "admin": {
143
+ if (process.argv[3] !== "reset-lockout") throw new Error("Usage: cartethyia admin reset-lockout <username> <clientIp> | --all");
144
+ const { resetLockout } = await import("./admin");
145
+ await resetLockout(process.argv.slice(4));
146
+ return;
147
+ }
148
+ case "telemetry": {
149
+ if (process.argv[3] !== "reconcile") {
150
+ console.error("Usage: cartethyia telemetry reconcile --apply|--verify");
151
+ process.exitCode = 2;
152
+ return;
153
+ }
154
+ const { telemetry } = await import("./telemetry");
155
+ await telemetry(process.argv.slice(4));
156
+ return;
157
+ }
158
+ case "update":
159
+ runDetachedUpdate();
160
+ printUpdateInstructions("this install", null);
161
+ return;
162
+ case "--help":
163
+ case "-h":
164
+ case undefined:
165
+ console.log(HELP(await readRequiredBunVersion()));
166
+ return;
167
+ default:
168
+ console.error(`Unknown command: ${command}\n\n${HELP(await readRequiredBunVersion())}`);
169
+ process.exitCode = 2;
170
+ }
171
+ }
172
+
173
+ main().catch(async (error: unknown) => {
174
+ const message = error instanceof Error ? error.message : "unknown error";
175
+ // Only the tray launched for this exact handoff may answer its parent's
176
+ // signal file. An independent `carte --tray` must never fail an unrelated
177
+ // menu that happens to be transferring ownership at the same time.
178
+ if (process.argv[2] === "tray" && process.env.CARTETHYIA_CONTROLLER_PARENT_PID) {
179
+ await reportTrayFailed(message);
180
+ }
181
+ if (message.includes("not starting another instance")) {
182
+ console.error(`Cartethyia is already running.\n${message}`);
183
+ process.exitCode = 1;
184
+ return;
185
+ }
186
+ console.error(`Cartethyia failed: ${message}`);
187
+ process.exitCode = 1;
188
+ });
@@ -1,64 +1,64 @@
1
- export type LocalPlatform = "windows" | "macos" | "linux" | "unknown";
2
-
3
- /** Detects the host platform once so setup/doctor share identical labels. */
4
- export function getLocalPlatform(): LocalPlatform {
5
- if (process.platform === "win32") return "windows";
6
- if (process.platform === "darwin") return "macos";
7
- if (process.platform === "linux") return "linux";
8
- return "unknown";
9
- }
10
-
11
- /**
12
- * Returns actionable hints for the configured local/external service.
13
- * These are hints only: setup never assumes Docker or silently mutates an
14
- * external service. Windows users commonly run PostgreSQL through Laragon;
15
- * Redis may be native, WSL-backed, or external.
16
- */
17
- export function getOsHints(service: "postgres" | "redis"): string[] {
18
- const platform = getLocalPlatform();
19
- if (platform === "windows") {
20
- return service === "postgres"
21
- ? [
22
- "Windows / Laragon:",
23
- " Start Laragon (Start All) and ensure PostgreSQL is enabled.",
24
- " Verify PostgreSQL is listening on the DATABASE_URL host/port (usually 5432).",
25
- " External PostgreSQL is also supported — keep DATABASE_URL unchanged and reachable.",
26
- ]
27
- : [
28
- "Windows / Redis:",
29
- " Start Redis through WSL, a native Redis-compatible service, or an external host.",
30
- " Verify Redis is listening on the REDIS_URL host/port (usually 6379).",
31
- " Or omit REDIS_URL to use the in-memory backend.",
32
- ];
33
- }
34
- if (platform === "macos") {
35
- return service === "postgres"
36
- ? [
37
- "macOS / Homebrew:",
38
- " brew services start postgresql@16 (or the installed PostgreSQL version).",
39
- " External PostgreSQL is also supported — verify DATABASE_URL reachability.",
40
- ]
41
- : [
42
- "macOS / Redis:",
43
- " brew services start redis, or use an external Redis instance.",
44
- " Or omit REDIS_URL to use the in-memory backend.",
45
- ];
46
- }
47
- if (platform === "linux") {
48
- return service === "postgres"
49
- ? [
50
- "Linux / systemd:",
51
- " sudo systemctl start postgresql (or the installed PostgreSQL service).",
52
- " External PostgreSQL is also supported — verify DATABASE_URL reachability.",
53
- ]
54
- : [
55
- "Linux / Redis:",
56
- " sudo systemctl start redis-server (or the installed Redis service).",
57
- " Or omit REDIS_URL to use the in-memory backend.",
58
- ];
59
- }
60
- return [
61
- `${service} is not reachable on this platform.`,
62
- "Verify the configured service URL or use an external service.",
63
- ];
64
- }
1
+ export type LocalPlatform = "windows" | "macos" | "linux" | "unknown";
2
+
3
+ /** Detects the host platform once so setup/doctor share identical labels. */
4
+ export function getLocalPlatform(): LocalPlatform {
5
+ if (process.platform === "win32") return "windows";
6
+ if (process.platform === "darwin") return "macos";
7
+ if (process.platform === "linux") return "linux";
8
+ return "unknown";
9
+ }
10
+
11
+ /**
12
+ * Returns actionable hints for the configured local/external service.
13
+ * These are hints only: setup never assumes Docker or silently mutates an
14
+ * external service. Windows users commonly run PostgreSQL through Laragon;
15
+ * Redis may be native, WSL-backed, or external.
16
+ */
17
+ export function getOsHints(service: "postgres" | "redis"): string[] {
18
+ const platform = getLocalPlatform();
19
+ if (platform === "windows") {
20
+ return service === "postgres"
21
+ ? [
22
+ "Windows / Laragon:",
23
+ " Start Laragon (Start All) and ensure PostgreSQL is enabled.",
24
+ " Verify PostgreSQL is listening on the DATABASE_URL host/port (usually 5432).",
25
+ " External PostgreSQL is also supported — keep DATABASE_URL unchanged and reachable.",
26
+ ]
27
+ : [
28
+ "Windows / Redis:",
29
+ " Start Redis through WSL, a native Redis-compatible service, or an external host.",
30
+ " Verify Redis is listening on the REDIS_URL host/port (usually 6379).",
31
+ " Or omit REDIS_URL to use the in-memory backend.",
32
+ ];
33
+ }
34
+ if (platform === "macos") {
35
+ return service === "postgres"
36
+ ? [
37
+ "macOS / Homebrew:",
38
+ " brew services start postgresql@16 (or the installed PostgreSQL version).",
39
+ " External PostgreSQL is also supported — verify DATABASE_URL reachability.",
40
+ ]
41
+ : [
42
+ "macOS / Redis:",
43
+ " brew services start redis, or use an external Redis instance.",
44
+ " Or omit REDIS_URL to use the in-memory backend.",
45
+ ];
46
+ }
47
+ if (platform === "linux") {
48
+ return service === "postgres"
49
+ ? [
50
+ "Linux / systemd:",
51
+ " sudo systemctl start postgresql (or the installed PostgreSQL service).",
52
+ " External PostgreSQL is also supported — verify DATABASE_URL reachability.",
53
+ ]
54
+ : [
55
+ "Linux / Redis:",
56
+ " sudo systemctl start redis-server (or the installed Redis service).",
57
+ " Or omit REDIS_URL to use the in-memory backend.",
58
+ ];
59
+ }
60
+ return [
61
+ `${service} is not reachable on this platform.`,
62
+ "Verify the configured service URL or use an external service.",
63
+ ];
64
+ }