cartethyia 2.5.10 → 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 (367) hide show
  1. package/.env.example +8 -0
  2. package/dist/dashboard/assets/{BannedPage-Cd30Z0b9.js → BannedPage-l6XMnfr9.js} +1 -1
  3. package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
  4. package/dist/dashboard/assets/{CliToolDetailPage-wf5u-ww6.js → CliToolDetailPage-DwOzAW45.js} +1 -1
  5. package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
  6. package/dist/dashboard/assets/{CliToolsPage-DykPvuck.js → CliToolsPage-BT3Up3QA.js} +1 -1
  7. package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
  8. package/dist/dashboard/assets/{CombosPage-DwvSD3vs.js → CombosPage-6kYIUpzR.js} +1 -1
  9. package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
  10. package/dist/dashboard/assets/{ConsoleLogPage-CIr7KDex.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
  11. package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
  12. package/dist/dashboard/assets/{CustomizationPage-82X0DDdH.js → CustomizationPage-C7m-H1Rz.js} +1 -1
  13. package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
  14. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
  15. package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
  16. package/dist/dashboard/assets/{LoginPage-CS5mPuVF.js → LoginPage-CbphLvjg.js} +1 -1
  17. package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
  18. package/dist/dashboard/assets/{ModelPicker-C0UCUsVJ.js → ModelPicker-DVny4S5_.js} +1 -1
  19. package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
  20. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
  21. package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
  22. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
  24. package/dist/dashboard/assets/{ProvidersPage-vdja5ZPK.js → ProvidersPage-Dfb2896L.js} +1 -1
  25. package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
  26. package/dist/dashboard/assets/{ProxyPage-BB4CNm4-.js → ProxyPage-BaCIdAUt.js} +1 -1
  27. package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
  28. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
  29. package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
  30. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
  31. package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
  32. package/dist/dashboard/assets/{SetupPage-FadUOl9S.js → SetupPage-D4g6vOGc.js} +1 -1
  33. package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
  34. package/dist/dashboard/assets/{SortableList-Cfy6_Khv.js → SortableList-C93i14BD.js} +1 -1
  35. package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
  36. package/dist/dashboard/assets/{StudioPage-CM92fgJf.js → StudioPage-SgQpymjm.js} +1 -1
  37. package/dist/dashboard/assets/{StudioPage-CM92fgJf.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
  38. package/dist/dashboard/assets/{UsagePage-BT4MyQeT.js → UsagePage-BFME-eba.js} +2 -2
  39. package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
  40. package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
  41. package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
  42. package/dist/dashboard/assets/{app-B7pVRhJ1.js → app-2KhtHCv9.js} +1 -1
  43. package/dist/dashboard/assets/{app-B7pVRhJ1.js.gz → app-2KhtHCv9.js.gz} +0 -0
  44. package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
  45. package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
  46. package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
  47. package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
  48. package/dist/dashboard/assets/{badge-Bvyr7D0u.js → badge-CKsF123d.js} +1 -1
  49. package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
  50. package/dist/dashboard/assets/{banner-DIvKAZih.js → banner-8j7uChA9.js} +1 -1
  51. package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
  52. package/dist/dashboard/assets/{card-D5_gYIS_.js → card-CdsSlWA3.js} +1 -1
  53. package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
  54. package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
  55. package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
  56. package/dist/dashboard/assets/{clipboard-button-B6vDBtbm.js → clipboard-button-uL5aYLfW.js} +1 -1
  57. package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
  58. package/dist/dashboard/assets/{dialog-CpjrgdwJ.js → dialog-C07hr5_G.js} +1 -1
  59. package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
  60. package/dist/dashboard/assets/{drawer-BWmt3b5y.js → drawer-BPO1TcKH.js} +1 -1
  61. package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
  62. package/dist/dashboard/assets/{entry-C_oakV3z.js → entry-Bz_CtDfC.js} +1 -1
  63. package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
  64. package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
  65. package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
  66. package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
  67. package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
  68. package/dist/dashboard/assets/{github-badge-RRHHWGxh.js → github-badge-DmQreSDd.js} +1 -1
  69. package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
  70. package/dist/dashboard/assets/{index-Bhey_rg_.js → index-Bblu6erR.js} +2 -2
  71. package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
  72. package/dist/dashboard/assets/{inline-C2_EQ5Uf.js → inline-C9dCYaBZ.js} +1 -1
  73. package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
  74. package/dist/dashboard/assets/{input-BMRlQ450.js → input-CjCnXweJ.js} +1 -1
  75. package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
  76. package/dist/dashboard/assets/{layout-XR5xP8ew.js → layout-V6y8DAkl.js} +1 -1
  77. package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
  78. package/dist/dashboard/assets/{list-row-Cw07dioc.js → list-row-CM1AaG_y.js} +1 -1
  79. package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
  80. package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
  81. package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
  82. package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
  83. package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
  84. package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
  85. package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
  86. package/dist/dashboard/assets/{numbers-DrdA9oSM.js → numbers-BOevJzdz.js} +1 -1
  87. package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
  88. package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
  89. package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
  90. package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
  91. package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
  92. package/dist/dashboard/assets/{select-CxVVB__N.js → select-C980c4cz.js} +1 -1
  93. package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
  94. package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
  95. package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
  96. package/dist/dashboard/assets/{stack-uPOwM9Hs.js → stack-_gfTdLVB.js} +1 -1
  97. package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
  98. package/dist/dashboard/assets/{state-D1Yz79iL.js → state-2c5V4zo7.js} +1 -1
  99. package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
  100. package/dist/dashboard/assets/{switch-sevsRrRo.js → switch-Dkke7Naf.js} +1 -1
  101. package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
  102. package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
  103. package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
  104. package/dist/dashboard/index.html +1 -1
  105. package/dist/dashboard/index.html.gz +0 -0
  106. package/dist/dashboard/precompressed-assets.json +51 -51
  107. package/migrations/README.md +52 -52
  108. package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
  109. package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
  110. package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
  111. package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  112. package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
  113. package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
  114. package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
  115. package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
  116. package/package.json +96 -95
  117. package/scripts/cli/admin.ts +126 -128
  118. package/scripts/cli/autostart.ts +237 -237
  119. package/scripts/cli/background.ts +120 -110
  120. package/scripts/cli/bin/carte.cjs +8 -8
  121. package/scripts/cli/bin/cartethyia.cjs +167 -167
  122. package/scripts/cli/console-menu.ts +39 -30
  123. package/scripts/cli/controller.ts +145 -0
  124. package/scripts/cli/env.ts +99 -99
  125. package/scripts/cli/gateway.ts +264 -260
  126. package/scripts/cli/instance.ts +234 -224
  127. package/scripts/cli/main.ts +188 -167
  128. package/scripts/cli/platform.ts +64 -64
  129. package/scripts/cli/prompt.ts +217 -79
  130. package/scripts/cli/services.ts +88 -88
  131. package/scripts/cli/setup.ts +445 -445
  132. package/scripts/cli/start.ts +85 -85
  133. package/scripts/cli/startup-signal.ts +23 -23
  134. package/scripts/cli/telemetry.ts +70 -0
  135. package/scripts/cli/tray/index.ts +263 -263
  136. package/scripts/cli/tray/windows.ps1 +107 -107
  137. package/scripts/cli/tray.ts +29 -4
  138. package/scripts/cli/update.ts +39 -39
  139. package/scripts/cli/version.ts +67 -67
  140. package/scripts/cli/wordmark.ts +58 -58
  141. package/src/boot/app.ts +16 -1
  142. package/src/boot/dependencies.ts +128 -25
  143. package/src/config/resources.ts +137 -0
  144. package/src/config/runtime.ts +133 -76
  145. package/src/config/spec.ts +30 -29
  146. package/src/config/telemetry.ts +0 -25
  147. package/src/console/api-keys/bucket-spend.ts +14 -17
  148. package/src/console/api-keys/contracts.ts +410 -410
  149. package/src/console/api-keys/routes.ts +12 -0
  150. package/src/console/audit/store.ts +2 -3
  151. package/src/console/auth/setup-service.ts +8 -1
  152. package/src/console/backup/routes.ts +7 -0
  153. package/src/console/backup/service.ts +463 -463
  154. package/src/console/backup/store.ts +16 -11
  155. package/src/console/backup/validate.ts +1 -4
  156. package/src/console/cli-tools/fs-ops.ts +18 -2
  157. package/src/console/data-management/contracts.ts +1 -8
  158. package/src/console/data-management/operations.ts +61 -48
  159. package/src/console/data-management/routes.ts +4 -2
  160. package/src/console/domain-registration.ts +25 -3
  161. package/src/console/inferhub/client.ts +139 -0
  162. package/src/console/inferhub/key-store.ts +129 -0
  163. package/src/console/inferhub/normalize.ts +347 -0
  164. package/src/console/inferhub/routes.ts +411 -0
  165. package/src/console/inferhub/store.ts +109 -0
  166. package/src/console/inferhub/types.ts +183 -0
  167. package/src/console/observability/contracts.ts +2 -41
  168. package/src/console/observability/store.ts +176 -371
  169. package/src/console/providers/catalog/public-model-store.ts +0 -0
  170. package/src/console/providers/catalog/store.ts +23 -36
  171. package/src/console/quota/account-quota-shared.ts +52 -52
  172. package/src/console/quota/account-quota-tenant.ts +16 -24
  173. package/src/console/quota/refresh.ts +441 -441
  174. package/src/console/routing/pools/import-parse.ts +206 -206
  175. package/src/console/routing/pools/operations.ts +543 -543
  176. package/src/console/routing/pools/probe-verdict.ts +132 -132
  177. package/src/console/routing/pools/routes.ts +181 -181
  178. package/src/console/settings/contracts.ts +42 -40
  179. package/src/console/settings/store.ts +25 -25
  180. package/src/console/share/share-stats.ts +22 -4
  181. package/src/console/share/share-usage.ts +30 -4
  182. package/src/console/shared/cursor.ts +15 -0
  183. package/src/console/updates/routes.ts +12 -0
  184. package/src/main.ts +13 -5
  185. package/src/network/pool/resolver.ts +1104 -1104
  186. package/src/observability/client-name.ts +22 -0
  187. package/src/observability/event-log.ts +5 -29
  188. package/src/observability/event-query.ts +690 -0
  189. package/src/observability/log-ring.ts +26 -2
  190. package/src/observability/log-sanitize.ts +118 -0
  191. package/src/observability/logger.ts +17 -13
  192. package/src/observability/metrics.ts +35 -0
  193. package/src/observability/payload-capture.ts +114 -41
  194. package/src/observability/payload-store.ts +249 -24
  195. package/src/observability/raw-event-writer.ts +118 -0
  196. package/src/observability/runtime-metrics.ts +71 -1
  197. package/src/observability/telemetry-buffer.ts +100 -17
  198. package/src/observability/telemetry-reconciliation.ts +417 -0
  199. package/src/persistence/boot.ts +20 -9
  200. package/src/persistence/db-pg.ts +13 -0
  201. package/src/persistence/db-sqlite.ts +569 -497
  202. package/src/persistence/dialect.ts +3 -2
  203. package/src/persistence/migrate.ts +181 -181
  204. package/src/persistence/schema.ts +1496 -1406
  205. package/src/persistence/shared-cache.ts +5 -1
  206. package/src/persistence/stores/api-key-store.ts +581 -581
  207. package/src/persistence/stores/telemetry-store.ts +124 -63
  208. package/src/protocol/reasoning-text.ts +30 -0
  209. package/src/protocol/response/chat.ts +33 -7
  210. package/src/protocol/response/codex.ts +14 -2
  211. package/src/protocol/response/messages.ts +5 -2
  212. package/src/providers/authentication/oauth-flow-store.ts +3 -10
  213. package/src/providers/bundled-discovery-providers.ts +122 -122
  214. package/src/providers/default-registry.ts +10 -0
  215. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  216. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  217. package/src/providers/discovery/probe-phases.ts +43 -7
  218. package/src/providers/discovery/probing-service.ts +70 -32
  219. package/src/providers/integrations/antigravity/antigravity.ts +9 -2
  220. package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
  221. package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
  222. package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
  223. package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
  224. package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
  225. package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
  226. package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
  227. package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
  228. package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
  229. package/src/providers/integrations/claude/claude-quota.ts +2 -2
  230. package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
  231. package/src/providers/integrations/claude/claude.ts +3 -4
  232. package/src/providers/integrations/cline/cline.ts +94 -8
  233. package/src/providers/integrations/codex/codex.ts +61 -37
  234. package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
  235. package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
  236. package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
  237. package/src/providers/integrations/cursor/cursor-request.ts +11 -0
  238. package/src/providers/integrations/cursor/cursor.ts +29 -17
  239. package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
  240. package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
  241. package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
  242. package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
  243. package/src/providers/integrations/grok/grok-quota.ts +279 -279
  244. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
  245. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
  246. package/src/providers/integrations/hermes.ts +7 -1
  247. package/src/providers/integrations/inferhub.ts +201 -73
  248. package/src/providers/integrations/kilo/kilo.ts +152 -152
  249. package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
  250. package/src/providers/integrations/openrouter/provider.ts +129 -129
  251. package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
  252. package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
  253. package/src/providers/integrations/trae/trae-oauth.ts +4 -26
  254. package/src/providers/integrations/trae/trae-quota.ts +389 -384
  255. package/src/providers/integrations/trae/trae-realm.ts +15 -2
  256. package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
  257. package/src/providers/integrations/zcode/oauth.ts +1 -1
  258. package/src/providers/operations/account-health-service.ts +605 -279
  259. package/src/providers/provider-metadata.ts +12 -1
  260. package/src/providers/provider-model-definition.ts +373 -373
  261. package/src/providers/quota/quota-contracts.ts +3 -7
  262. package/src/providers/search/search-provider.ts +2 -6
  263. package/src/security/admission/contracts.ts +10 -0
  264. package/src/security/admission/in-memory-store.ts +89 -14
  265. package/src/security/admission/lua.ts +191 -182
  266. package/src/security/admission/redis-store.ts +30 -0
  267. package/src/security/admission/service.ts +18 -0
  268. package/src/security/api-key-auth-cache.ts +96 -0
  269. package/src/security/api-key-auth.ts +11 -100
  270. package/src/security/client-router-fingerprint.ts +3 -7
  271. package/src/security/ip-boundary.ts +7 -3
  272. package/src/shared/coerce.ts +67 -0
  273. package/src/shared/hash.ts +6 -0
  274. package/src/shared/time.ts +7 -0
  275. package/src/transport/dispatch/attempt-finalize.ts +26 -138
  276. package/src/transport/dispatch/attempt-loop.ts +103 -81
  277. package/src/transport/dispatch/capture-policy.ts +78 -0
  278. package/src/transport/dispatch/proxy-request.ts +4 -2
  279. package/src/transport/dispatch/streaming/encoder.ts +111 -5
  280. package/src/transport/dispatch/streaming/stream.ts +19 -2
  281. package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
  282. package/src/transport/failure-policy.ts +27 -0
  283. package/src/transport/guards/abuse-guard.ts +2 -2
  284. package/src/transport/guards/auth-guard.ts +17 -0
  285. package/src/transport/guards/body-policy.ts +3 -2
  286. package/src/transport/guards/concurrency-guard.ts +89 -13
  287. package/src/transport/guards/early-rejection-capture.ts +43 -3
  288. package/src/transport/guards/error-lifecycle.ts +10 -2
  289. package/src/transport/guards/guard-errors.ts +23 -0
  290. package/src/transport/guards/headroom-guard.ts +41 -7
  291. package/src/transport/guards/pipeline.ts +24 -0
  292. package/src/transport/guards/request-context.ts +2 -1
  293. package/src/transport/routing/admission.ts +51 -9
  294. package/src/transport/routing/bounded-lru.ts +49 -0
  295. package/src/transport/routing/candidate-index.ts +88 -0
  296. package/src/transport/routing/reservations.ts +121 -7
  297. package/src/transport/routing/round-robin.ts +10 -22
  298. package/src/transport/routing/route-catalog.ts +26 -13
  299. package/src/transport/routing/route-model.ts +22 -2
  300. package/src/transport/routing/router.ts +53 -31
  301. package/src/transport/surface/chat/encode.ts +2 -3
  302. package/src/transport/surface/chat/parse.ts +2 -1
  303. package/src/transport/surface/dialects.ts +11 -3
  304. package/src/transport/surface/messages/encode.ts +2 -1
  305. package/src/transport/surface/messages/parse.ts +3 -1
  306. package/src/transport/surface/messages/stream.ts +2 -6
  307. package/src/workers/maintenance-lease.ts +200 -0
  308. package/src/workers/scheduler.ts +101 -1
  309. package/dist/dashboard/assets/BannedPage-Cd30Z0b9.js.gz +0 -0
  310. package/dist/dashboard/assets/CliToolDetailPage-wf5u-ww6.js.gz +0 -0
  311. package/dist/dashboard/assets/CliToolsPage-DykPvuck.js.gz +0 -0
  312. package/dist/dashboard/assets/CombosPage-DwvSD3vs.js.gz +0 -0
  313. package/dist/dashboard/assets/ConsoleLogPage-CIr7KDex.js.gz +0 -0
  314. package/dist/dashboard/assets/CustomizationPage-82X0DDdH.js.gz +0 -0
  315. package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js +0 -1
  316. package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js.gz +0 -0
  317. package/dist/dashboard/assets/LoginPage-CS5mPuVF.js.gz +0 -0
  318. package/dist/dashboard/assets/ModelPicker-C0UCUsVJ.js.gz +0 -0
  319. package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js +0 -1
  320. package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js.gz +0 -0
  321. package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js +0 -3
  322. package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js.gz +0 -0
  323. package/dist/dashboard/assets/ProvidersPage-vdja5ZPK.js.gz +0 -0
  324. package/dist/dashboard/assets/ProxyPage-BB4CNm4-.js.gz +0 -0
  325. package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js +0 -1
  326. package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js.gz +0 -0
  327. package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js +0 -1
  328. package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js.gz +0 -0
  329. package/dist/dashboard/assets/SetupPage-FadUOl9S.js.gz +0 -0
  330. package/dist/dashboard/assets/SortableList-Cfy6_Khv.js.gz +0 -0
  331. package/dist/dashboard/assets/UsagePage-BT4MyQeT.js.gz +0 -0
  332. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  333. package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
  334. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  335. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  336. package/dist/dashboard/assets/badge-Bvyr7D0u.js.gz +0 -0
  337. package/dist/dashboard/assets/banner-DIvKAZih.js.gz +0 -0
  338. package/dist/dashboard/assets/card-D5_gYIS_.js.gz +0 -0
  339. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  340. package/dist/dashboard/assets/clipboard-button-B6vDBtbm.js.gz +0 -0
  341. package/dist/dashboard/assets/dialog-CpjrgdwJ.js.gz +0 -0
  342. package/dist/dashboard/assets/drawer-BWmt3b5y.js.gz +0 -0
  343. package/dist/dashboard/assets/entry-B-gvimrg.js +0 -1
  344. package/dist/dashboard/assets/entry-B-gvimrg.js.gz +0 -0
  345. package/dist/dashboard/assets/entry-C_oakV3z.js.gz +0 -0
  346. package/dist/dashboard/assets/entry-Dj0cHfEc.js +0 -4
  347. package/dist/dashboard/assets/entry-Dj0cHfEc.js.gz +0 -0
  348. package/dist/dashboard/assets/github-badge-RRHHWGxh.js.gz +0 -0
  349. package/dist/dashboard/assets/index-Bhey_rg_.js.gz +0 -0
  350. package/dist/dashboard/assets/inline-C2_EQ5Uf.js.gz +0 -0
  351. package/dist/dashboard/assets/input-BMRlQ450.js.gz +0 -0
  352. package/dist/dashboard/assets/layout-XR5xP8ew.js.gz +0 -0
  353. package/dist/dashboard/assets/list-row-Cw07dioc.js.gz +0 -0
  354. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  355. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  356. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  357. package/dist/dashboard/assets/numbers-DrdA9oSM.js.gz +0 -0
  358. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  359. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  360. package/dist/dashboard/assets/select-CxVVB__N.js.gz +0 -0
  361. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  362. package/dist/dashboard/assets/stack-uPOwM9Hs.js.gz +0 -0
  363. package/dist/dashboard/assets/state-D1Yz79iL.js.gz +0 -0
  364. package/dist/dashboard/assets/switch-sevsRrRo.js.gz +0 -0
  365. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  366. package/src/observability/event-compaction.ts +0 -317
  367. package/src/observability/event-day-index.ts +0 -439
@@ -1,107 +1,107 @@
1
- # Cartethyia tray icon for Windows using NotifyIcon.
2
- #
3
- # Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
4
- # helper binary and still leave the spawning process attached to a console, so
5
- # Windows Terminal shows an empty tab for the tray's whole lifetime. A
6
- # PowerShell NotifyIcon owns no console of its own when started with
7
- # -WindowStyle Hidden, and the protocol here is plain JSON lines on
8
- # stdin/stdout, so the parent stays in charge.
9
- #
10
- # Protocol: the parent writes one JSON object per line.
11
- # {"action":"add-item","index":0,"title":"...","enabled":true}
12
- # {"action":"update-item","index":0,"title":"...","enabled":true}
13
- # {"action":"set-tooltip","text":"..."}
14
- # {"action":"kill"}
15
- # This script writes one JSON object per line:
16
- # {"type":"started"} {"type":"ready"} {"type":"click","index":0}
17
- param([string]$IconPath, [string]$Tooltip)
18
-
19
- $ErrorActionPreference = "Stop"
20
-
21
- Add-Type -AssemblyName System.Windows.Forms
22
- Add-Type -AssemblyName System.Drawing
23
-
24
- [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
25
- [Console]::InputEncoding = [System.Text.Encoding]::UTF8
26
- $OutputEncoding = [System.Text.Encoding]::UTF8
27
-
28
- [System.Windows.Forms.Application]::EnableVisualStyles()
29
- [System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
30
-
31
- $script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
32
- $script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
33
- $script:notifyIcon.Text = $Tooltip
34
- $script:notifyIcon.Visible = $true
35
-
36
- $script:menu = New-Object System.Windows.Forms.ContextMenuStrip
37
- $script:notifyIcon.ContextMenuStrip = $script:menu
38
- $script:items = @()
39
-
40
- function Write-Event($obj) {
41
- $json = $obj | ConvertTo-Json -Compress
42
- [Console]::Out.WriteLine($json)
43
- [Console]::Out.Flush()
44
- }
45
-
46
- function Add-MenuItem($index, $title, $enabled) {
47
- $item = New-Object System.Windows.Forms.ToolStripMenuItem
48
- $item.Text = $title
49
- $item.Enabled = $enabled
50
- $idx = $index
51
- $item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
52
- $script:menu.Items.Add($item) | Out-Null
53
- $script:items += $item
54
- }
55
-
56
- # A real ToolStripSeparator draws the divider. Sending a "-" title instead
57
- # renders a literal dash row, which is what the menu showed before.
58
- function Add-MenuSeparator($index) {
59
- $item = New-Object System.Windows.Forms.ToolStripSeparator
60
- $script:menu.Items.Add($item) | Out-Null
61
- $script:items += $item
62
- }
63
-
64
- function Update-MenuItem($index, $title, $enabled) {
65
- if ($index -lt $script:items.Count) {
66
- $script:items[$index].Text = $title
67
- $script:items[$index].Enabled = $enabled
68
- }
69
- }
70
-
71
- function Set-Tooltip($text) {
72
- # NotifyIcon.Text is capped at 63 characters.
73
- if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
74
- $script:notifyIcon.Text = $text
75
- }
76
-
77
- # Stdin is polled from the UI thread: a background thread reading Console.In
78
- # races the message loop and deadlocks when the menu is open.
79
- $script:timer = New-Object System.Windows.Forms.Timer
80
- $script:timer.Interval = 100
81
- $script:timer.Add_Tick({
82
- try {
83
- while ([Console]::In.Peek() -ne -1) {
84
- $line = [Console]::In.ReadLine()
85
- if ([string]::IsNullOrWhiteSpace($line)) { continue }
86
- $cmd = $line | ConvertFrom-Json
87
- switch ($cmd.action) {
88
- "add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
89
- "add-separator" { Add-MenuSeparator $cmd.index }
90
- "update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
91
- "set-tooltip" { Set-Tooltip $cmd.text }
92
- "ready" { Write-Event @{ type = "ready" } }
93
- "kill" {
94
- $script:notifyIcon.Visible = $false
95
- $script:notifyIcon.Dispose()
96
- [System.Windows.Forms.Application]::Exit()
97
- }
98
- }
99
- }
100
- } catch {
101
- Write-Event @{ type = "error"; message = $_.Exception.Message }
102
- }
103
- })
104
- $script:timer.Start()
105
-
106
- Write-Event @{ type = "started" }
107
- [System.Windows.Forms.Application]::Run()
1
+ # Cartethyia tray icon for Windows using NotifyIcon.
2
+ #
3
+ # Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
4
+ # helper binary and still leave the spawning process attached to a console, so
5
+ # Windows Terminal shows an empty tab for the tray's whole lifetime. A
6
+ # PowerShell NotifyIcon owns no console of its own when started with
7
+ # -WindowStyle Hidden, and the protocol here is plain JSON lines on
8
+ # stdin/stdout, so the parent stays in charge.
9
+ #
10
+ # Protocol: the parent writes one JSON object per line.
11
+ # {"action":"add-item","index":0,"title":"...","enabled":true}
12
+ # {"action":"update-item","index":0,"title":"...","enabled":true}
13
+ # {"action":"set-tooltip","text":"..."}
14
+ # {"action":"kill"}
15
+ # This script writes one JSON object per line:
16
+ # {"type":"started"} {"type":"ready"} {"type":"click","index":0}
17
+ param([string]$IconPath, [string]$Tooltip)
18
+
19
+ $ErrorActionPreference = "Stop"
20
+
21
+ Add-Type -AssemblyName System.Windows.Forms
22
+ Add-Type -AssemblyName System.Drawing
23
+
24
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
25
+ [Console]::InputEncoding = [System.Text.Encoding]::UTF8
26
+ $OutputEncoding = [System.Text.Encoding]::UTF8
27
+
28
+ [System.Windows.Forms.Application]::EnableVisualStyles()
29
+ [System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
30
+
31
+ $script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
32
+ $script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
33
+ $script:notifyIcon.Text = $Tooltip
34
+ $script:notifyIcon.Visible = $true
35
+
36
+ $script:menu = New-Object System.Windows.Forms.ContextMenuStrip
37
+ $script:notifyIcon.ContextMenuStrip = $script:menu
38
+ $script:items = @()
39
+
40
+ function Write-Event($obj) {
41
+ $json = $obj | ConvertTo-Json -Compress
42
+ [Console]::Out.WriteLine($json)
43
+ [Console]::Out.Flush()
44
+ }
45
+
46
+ function Add-MenuItem($index, $title, $enabled) {
47
+ $item = New-Object System.Windows.Forms.ToolStripMenuItem
48
+ $item.Text = $title
49
+ $item.Enabled = $enabled
50
+ $idx = $index
51
+ $item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
52
+ $script:menu.Items.Add($item) | Out-Null
53
+ $script:items += $item
54
+ }
55
+
56
+ # A real ToolStripSeparator draws the divider. Sending a "-" title instead
57
+ # renders a literal dash row, which is what the menu showed before.
58
+ function Add-MenuSeparator($index) {
59
+ $item = New-Object System.Windows.Forms.ToolStripSeparator
60
+ $script:menu.Items.Add($item) | Out-Null
61
+ $script:items += $item
62
+ }
63
+
64
+ function Update-MenuItem($index, $title, $enabled) {
65
+ if ($index -lt $script:items.Count) {
66
+ $script:items[$index].Text = $title
67
+ $script:items[$index].Enabled = $enabled
68
+ }
69
+ }
70
+
71
+ function Set-Tooltip($text) {
72
+ # NotifyIcon.Text is capped at 63 characters.
73
+ if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
74
+ $script:notifyIcon.Text = $text
75
+ }
76
+
77
+ # Stdin is polled from the UI thread: a background thread reading Console.In
78
+ # races the message loop and deadlocks when the menu is open.
79
+ $script:timer = New-Object System.Windows.Forms.Timer
80
+ $script:timer.Interval = 100
81
+ $script:timer.Add_Tick({
82
+ try {
83
+ while ([Console]::In.Peek() -ne -1) {
84
+ $line = [Console]::In.ReadLine()
85
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
86
+ $cmd = $line | ConvertFrom-Json
87
+ switch ($cmd.action) {
88
+ "add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
89
+ "add-separator" { Add-MenuSeparator $cmd.index }
90
+ "update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
91
+ "set-tooltip" { Set-Tooltip $cmd.text }
92
+ "ready" { Write-Event @{ type = "ready" } }
93
+ "kill" {
94
+ $script:notifyIcon.Visible = $false
95
+ $script:notifyIcon.Dispose()
96
+ [System.Windows.Forms.Application]::Exit()
97
+ }
98
+ }
99
+ }
100
+ } catch {
101
+ Write-Event @{ type = "error"; message = $_.Exception.Message }
102
+ }
103
+ })
104
+ $script:timer.Start()
105
+
106
+ Write-Event @{ type = "started" }
107
+ [System.Windows.Forms.Application]::Run()
@@ -1,6 +1,6 @@
1
1
  import { resolvePackagePath } from "../../src/config/package-paths";
2
2
  import { resolvePort } from "../../src/config/runtime";
3
- import { ensureDrainToken } from "./instance";
3
+ import { ensureDrainToken, attachGateway, stopAttachedGateway, type RunningGateway } from "./instance";
4
4
  import { startGateway } from "./gateway";
5
5
  import { stopGateway } from "./gateway";
6
6
  import { readInstalledVersion } from "./version";
@@ -8,6 +8,8 @@ import { reportTrayStartup } from "./startup-signal";
8
8
  import { createTray } from "./tray/index";
9
9
  import { openUrl } from "./background";
10
10
  import { exportBackup, resetConsoleAccount } from "./admin";
11
+ import { acquireController, inheritedControllerHandoff } from "./controller";
12
+ import { restoreControllerAfterFailedHandoff } from "./controller";
11
13
 
12
14
  /**
13
15
  * Runs the gateway with a tray icon. The tray is platform-specific (`tray/`):
@@ -39,6 +41,24 @@ const TRAY_HEALTH_POLL_MS = 10_000;
39
41
  const TRAY_HEALTH_MISSES_BEFORE_CLOSE = 3;
40
42
 
41
43
  export async function startTray(): Promise<void> {
44
+ const handoff = inheritedControllerHandoff();
45
+ const controller = acquireController("tray", handoff);
46
+ const releaseController = (): void => controller.release();
47
+ const failedHandoff = (): void => {
48
+ if (handoff) restoreControllerAfterFailedHandoff(handoff);
49
+ };
50
+ process.once("exit", failedHandoff);
51
+ process.once("exit", releaseController);
52
+ try {
53
+ await runTray();
54
+ } finally {
55
+ process.removeListener("exit", failedHandoff);
56
+ controller.release();
57
+ process.removeListener("exit", releaseController);
58
+ }
59
+ }
60
+
61
+ async function runTray(): Promise<void> {
42
62
  const port = resolvePort();
43
63
  const drainToken = ensureDrainToken();
44
64
  const dashboardUrl = `http://127.0.0.1:${port}/console`;
@@ -46,6 +66,7 @@ export async function startTray(): Promise<void> {
46
66
  const currentVersion = await readInstalledVersion();
47
67
 
48
68
  let gateway: Bun.Subprocess | null = null;
69
+ let adoptedGateway: RunningGateway | null = null;
49
70
  let closing = false;
50
71
  let resetArmed = false;
51
72
  let closePromise: Promise<void> | undefined;
@@ -83,6 +104,7 @@ export async function startTray(): Promise<void> {
83
104
  const current: Bun.Subprocess | null = gateway;
84
105
  gateway = null;
85
106
  if (current) await stopGateway(current, port, drainToken);
107
+ else if (adoptedGateway !== null) await stopAttachedGateway(adoptedGateway, drainToken);
86
108
  await tray.close();
87
109
  process.exitCode = exitCode;
88
110
  })();
@@ -96,7 +118,9 @@ export async function startTray(): Promise<void> {
96
118
  if (id === "version") return void (await openUrl(releasesUrl));
97
119
  if (id === "backup") {
98
120
  const result = await exportBackup(port, drainToken);
99
- if (result !== null) {
121
+ if ("error" in result) {
122
+ console.error(result.error);
123
+ } else {
100
124
  console.log(
101
125
  `Backup written to ${result.path} (${(result.bytes / 1024).toFixed(1)} KiB). Telemetry is not included.`,
102
126
  );
@@ -129,7 +153,8 @@ export async function startTray(): Promise<void> {
129
153
  // down too and "Hide to tray" left nothing serving. `stopGateway` drains
130
154
  // over HTTP before it ever reaches for `kill()`, so detaching costs the
131
155
  // exit path nothing: the tray can still stop the gateway on Exit.
132
- gateway = await startGateway(port, drainToken, { detached: true });
156
+ adoptedGateway = await attachGateway(port, drainToken);
157
+ if (adoptedGateway === null) gateway = await startGateway(port, drainToken, { detached: true });
133
158
  // The launcher that spawned this tray is blocked on a signal file: it
134
159
  // cannot read our stdio, because the tray is detached. Without this write
135
160
  // it waits out its 60 s deadline and reports a failure for a tray that is
@@ -146,7 +171,7 @@ export async function startTray(): Promise<void> {
146
171
  // is up reads as up regardless of who spawned it.
147
172
  while (!closing) {
148
173
  await Bun.sleep(TRAY_HEALTH_POLL_MS);
149
- if (closing || gateway === null) continue;
174
+ if (closing || gateway === null && adoptedGateway === null) continue;
150
175
  const alive = await fetch(`http://127.0.0.1:${port}/health`, {
151
176
  signal: AbortSignal.timeout(2_000),
152
177
  }).then((response) => response.ok).catch(() => false);
@@ -1,40 +1,40 @@
1
- /**
2
- * The update path.
3
- *
4
- * npm cannot replace this package's directory while a process is executing out
5
- * of it (EBUSY on Windows). So we spawn the installer as a detached process,
6
- * print a short notice, and exit — the installer runs after this process is gone.
7
- * After install, it attempts to restart the gateway (systemd if available, else
8
- * `cartethyia start`), so the new version comes up automatically.
9
- * Cross-OS: uses npm.cmd on Windows.
10
- */
11
- import { spawn } from "node:child_process";
12
-
13
- export function updateCommand(): string {
14
- return "npm install -g cartethyia@latest --prefer-online";
15
- }
16
-
17
- export function runDetachedUpdate(): void {
18
- const isWin = process.platform === "win32";
19
- const shell = isWin ? "cmd.exe" : "/bin/sh";
20
- const install = "npm install -g cartethyia@latest --prefer-online";
21
- const restart = isWin
22
- ? "cartethyia start"
23
- : "systemctl restart cartethyia 2>/dev/null || cartethyia start";
24
- const script = `${install} && ${restart}`;
25
- const args = isWin ? ["/c", script] : ["-c", script];
26
- const child = spawn(shell, args, {
27
- detached: true,
28
- stdio: "ignore",
29
- });
30
- child.unref();
31
- }
32
-
33
- export function printUpdateInstructions(from: string, to: string | null): void {
34
- console.log("");
35
- console.log(`Update available: ${from} \u2192 ${to ?? "latest"}`);
36
- console.log("");
37
- console.log("Starting detached update; this process will exit now.");
38
- console.log("The gateway will restart automatically after the install.");
39
- console.log("");
1
+ /**
2
+ * The update path.
3
+ *
4
+ * npm cannot replace this package's directory while a process is executing out
5
+ * of it (EBUSY on Windows). So we spawn the installer as a detached process,
6
+ * print a short notice, and exit — the installer runs after this process is gone.
7
+ * After install, it attempts to restart the gateway (systemd if available, else
8
+ * `cartethyia start`), so the new version comes up automatically.
9
+ * Cross-OS: uses npm.cmd on Windows.
10
+ */
11
+ import { spawn } from "node:child_process";
12
+
13
+ export function updateCommand(): string {
14
+ return "npm install -g cartethyia@latest --prefer-online";
15
+ }
16
+
17
+ export function runDetachedUpdate(): void {
18
+ const isWin = process.platform === "win32";
19
+ const shell = isWin ? "cmd.exe" : "/bin/sh";
20
+ const install = "npm install -g cartethyia@latest --prefer-online";
21
+ const restart = isWin
22
+ ? "cartethyia start"
23
+ : "systemctl restart cartethyia 2>/dev/null || cartethyia start";
24
+ const script = `${install} && ${restart}`;
25
+ const args = isWin ? ["/c", script] : ["-c", script];
26
+ const child = spawn(shell, args, {
27
+ detached: true,
28
+ stdio: "ignore",
29
+ });
30
+ child.unref();
31
+ }
32
+
33
+ export function printUpdateInstructions(from: string, to: string | null): void {
34
+ console.log("");
35
+ console.log(`Update available: ${from} \u2192 ${to ?? "latest"}`);
36
+ console.log("");
37
+ console.log("Starting detached update; this process will exit now.");
38
+ console.log("The gateway will restart automatically after the install.");
39
+ console.log("");
40
40
  }
@@ -1,67 +1,67 @@
1
- import { resolvePackagePath } from "../../src/config/package-paths";
2
-
3
- export async function readRequiredBunVersion(): Promise<string> {
4
- const metadata: unknown = await Bun.file(resolvePackagePath("package.json")).json();
5
- if (metadata === null || typeof metadata !== "object" || !("engines" in metadata) ||
6
- metadata.engines === null || typeof metadata.engines !== "object" ||
7
- !("bun" in metadata.engines) || typeof metadata.engines.bun !== "string") {
8
- throw new Error("Cartethyia's required Bun version is unavailable in package.json");
9
- }
10
- const minimum = metadata.engines.bun.trim().replace(/^>=\s*/, "");
11
- if (!/^\d+(?:\.\d+){0,2}$/.test(minimum)) throw new Error(`Unsupported Bun engine requirement: ${metadata.engines.bun}`);
12
- return minimum;
13
- }
14
-
15
- export function bunVersionAtLeast(actual: string, minimum: string): boolean {
16
- const left = actual.split(".").map((part) => Number.parseInt(part, 10) || 0);
17
- const right = minimum.split(".").map((part) => Number.parseInt(part, 10) || 0);
18
- const length = Math.max(left.length, right.length);
19
- for (let i = 0; i < length; i += 1) {
20
- const a = left[i] ?? 0;
21
- const b = right[i] ?? 0;
22
- if (a !== b) return a > b;
23
- }
24
- return true;
25
- }
26
-
27
- export async function assertBunVersion(): Promise<void> {
28
- const minimum = await readRequiredBunVersion();
29
- if (bunVersionAtLeast(Bun.version, minimum)) return;
30
- throw new Error(`Bun ${minimum} or newer is required (found ${Bun.version}). Install it from https://bun.sh and run this command again.`);
31
- }
32
-
33
- /** Reads the version from the installed package manifest. */
34
- export async function readInstalledVersion(): Promise<string> {
35
- const metadata: unknown = await Bun.file(resolvePackagePath("package.json")).json();
36
- if (
37
- metadata === null ||
38
- typeof metadata !== "object" ||
39
- !("version" in metadata) ||
40
- typeof metadata.version !== "string"
41
- ) {
42
- throw new Error("Cartethyia package version is unavailable");
43
- }
44
- return metadata.version;
45
- }
46
-
47
- /** Asks the npm registry for the newest published version, or null on failure. */
48
- export async function readLatestPublishedVersion(): Promise<string | null> {
49
- try {
50
- const response = await fetch("https://registry.npmjs.org/cartethyia/latest", {
51
- signal: AbortSignal.timeout(4_000),
52
- });
53
- if (!response.ok) return null;
54
- const metadata: unknown = await response.json();
55
- if (
56
- metadata !== null &&
57
- typeof metadata === "object" &&
58
- "version" in metadata &&
59
- typeof metadata.version === "string"
60
- ) {
61
- return metadata.version;
62
- }
63
- return null;
64
- } catch {
65
- return null;
66
- }
67
- }
1
+ import { resolvePackagePath } from "../../src/config/package-paths";
2
+
3
+ export async function readRequiredBunVersion(): Promise<string> {
4
+ const metadata: unknown = await Bun.file(resolvePackagePath("package.json")).json();
5
+ if (metadata === null || typeof metadata !== "object" || !("engines" in metadata) ||
6
+ metadata.engines === null || typeof metadata.engines !== "object" ||
7
+ !("bun" in metadata.engines) || typeof metadata.engines.bun !== "string") {
8
+ throw new Error("Cartethyia's required Bun version is unavailable in package.json");
9
+ }
10
+ const minimum = metadata.engines.bun.trim().replace(/^>=\s*/, "");
11
+ if (!/^\d+(?:\.\d+){0,2}$/.test(minimum)) throw new Error(`Unsupported Bun engine requirement: ${metadata.engines.bun}`);
12
+ return minimum;
13
+ }
14
+
15
+ export function bunVersionAtLeast(actual: string, minimum: string): boolean {
16
+ const left = actual.split(".").map((part) => Number.parseInt(part, 10) || 0);
17
+ const right = minimum.split(".").map((part) => Number.parseInt(part, 10) || 0);
18
+ const length = Math.max(left.length, right.length);
19
+ for (let i = 0; i < length; i += 1) {
20
+ const a = left[i] ?? 0;
21
+ const b = right[i] ?? 0;
22
+ if (a !== b) return a > b;
23
+ }
24
+ return true;
25
+ }
26
+
27
+ export async function assertBunVersion(): Promise<void> {
28
+ const minimum = await readRequiredBunVersion();
29
+ if (bunVersionAtLeast(Bun.version, minimum)) return;
30
+ throw new Error(`Bun ${minimum} or newer is required (found ${Bun.version}). Install it from https://bun.sh and run this command again.`);
31
+ }
32
+
33
+ /** Reads the version from the installed package manifest. */
34
+ export async function readInstalledVersion(): Promise<string> {
35
+ const metadata: unknown = await Bun.file(resolvePackagePath("package.json")).json();
36
+ if (
37
+ metadata === null ||
38
+ typeof metadata !== "object" ||
39
+ !("version" in metadata) ||
40
+ typeof metadata.version !== "string"
41
+ ) {
42
+ throw new Error("Cartethyia package version is unavailable");
43
+ }
44
+ return metadata.version;
45
+ }
46
+
47
+ /** Asks the npm registry for the newest published version, or null on failure. */
48
+ export async function readLatestPublishedVersion(): Promise<string | null> {
49
+ try {
50
+ const response = await fetch("https://registry.npmjs.org/cartethyia/latest", {
51
+ signal: AbortSignal.timeout(4_000),
52
+ });
53
+ if (!response.ok) return null;
54
+ const metadata: unknown = await response.json();
55
+ if (
56
+ metadata !== null &&
57
+ typeof metadata === "object" &&
58
+ "version" in metadata &&
59
+ typeof metadata.version === "string"
60
+ ) {
61
+ return metadata.version;
62
+ }
63
+ return null;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
@@ -1,58 +1,58 @@
1
- /**
2
- * The console wordmark.
3
- *
4
- * Rendered with half-block characters so each letter is two pixel rows tall in
5
- * one character cell, and coloured with a horizontal gradient across the
6
- * Cartethyia blue-grey range. The caller decides whether to print it: a
7
- * non-colour terminal, or one narrower than the wordmark, should skip it.
8
- */
9
-
10
- const WORDMARK: readonly string[] = [
11
- " ██████╗ █████╗ ██████╗ ████████╗███████╗████████╗██╗ ██╗██╗ ██╗██╗ █████╗ ",
12
- "██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝╚══██╔══╝██║ ██║╚██╗ ██╔╝██║██╔══██╗",
13
- "██║ ███████║██████╔╝ ██║ █████╗ ██║ ███████║ ╚████╔╝ ██║███████║",
14
- "██║ ██╔══██║██╔══██╗ ██║ ██╔══╝ ██║ ██╔══██║ ╚██╔╝ ██║██╔══██║",
15
- "╚██████╗██║ ██║██║ ██║ ██║ ███████╗ ██║ ██║ ██║ ██║ ██║██║ ██║",
16
- " ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝",
17
- ];
18
-
19
- /** Blue-grey gradient: dim at the edges, brighter through the middle. */
20
- const GRADIENT: readonly [number, number, number][] = [
21
- [90, 120, 150],
22
- [120, 160, 200],
23
- [150, 200, 240],
24
- [120, 160, 200],
25
- [90, 120, 150],
26
- ];
27
-
28
- const RESET = "\u001b[0m";
29
-
30
- /**
31
- * Returns the wordmark lines with per-column colour, or an empty array when the
32
- * terminal is too narrow or colour is disabled.
33
- */
34
- export function renderWordmark(columns = process.stdout.columns ?? 80): string[] {
35
- if (process.env.NO_COLOR !== undefined) return [];
36
- const width = WORDMARK[0]!.length;
37
- if (columns < width) return [];
38
-
39
- const coloured: string[] = [];
40
- for (const row of WORDMARK) {
41
- let line = "";
42
- let current = -1;
43
- for (let column = 0; column < row.length; column += 1) {
44
- const band = Math.min(
45
- GRADIENT.length - 1,
46
- Math.floor((column / width) * GRADIENT.length),
47
- );
48
- const [r, g, b] = GRADIENT[band]!;
49
- if (band !== current) {
50
- line += `\u001b[38;2;${r};${g};${b}m`;
51
- current = band;
52
- }
53
- line += row[column];
54
- }
55
- coloured.push(`${line}${RESET}`);
56
- }
57
- return coloured;
58
- }
1
+ /**
2
+ * The console wordmark.
3
+ *
4
+ * Rendered with half-block characters so each letter is two pixel rows tall in
5
+ * one character cell, and coloured with a horizontal gradient across the
6
+ * Cartethyia blue-grey range. The caller decides whether to print it: a
7
+ * non-colour terminal, or one narrower than the wordmark, should skip it.
8
+ */
9
+
10
+ const WORDMARK: readonly string[] = [
11
+ " ██████╗ █████╗ ██████╗ ████████╗███████╗████████╗██╗ ██╗██╗ ██╗██╗ █████╗ ",
12
+ "██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔════╝╚══██╔══╝██║ ██║╚██╗ ██╔╝██║██╔══██╗",
13
+ "██║ ███████║██████╔╝ ██║ █████╗ ██║ ███████║ ╚████╔╝ ██║███████║",
14
+ "██║ ██╔══██║██╔══██╗ ██║ ██╔══╝ ██║ ██╔══██║ ╚██╔╝ ██║██╔══██║",
15
+ "╚██████╗██║ ██║██║ ██║ ██║ ███████╗ ██║ ██║ ██║ ██║ ██║██║ ██║",
16
+ " ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝",
17
+ ];
18
+
19
+ /** Blue-grey gradient: dim at the edges, brighter through the middle. */
20
+ const GRADIENT: readonly [number, number, number][] = [
21
+ [90, 120, 150],
22
+ [120, 160, 200],
23
+ [150, 200, 240],
24
+ [120, 160, 200],
25
+ [90, 120, 150],
26
+ ];
27
+
28
+ const RESET = "\u001b[0m";
29
+
30
+ /**
31
+ * Returns the wordmark lines with per-column colour, or an empty array when the
32
+ * terminal is too narrow or colour is disabled.
33
+ */
34
+ export function renderWordmark(columns = process.stdout.columns ?? 80): string[] {
35
+ if (process.env.NO_COLOR !== undefined) return [];
36
+ const width = WORDMARK[0]!.length;
37
+ if (columns < width) return [];
38
+
39
+ const coloured: string[] = [];
40
+ for (const row of WORDMARK) {
41
+ let line = "";
42
+ let current = -1;
43
+ for (let column = 0; column < row.length; column += 1) {
44
+ const band = Math.min(
45
+ GRADIENT.length - 1,
46
+ Math.floor((column / width) * GRADIENT.length),
47
+ );
48
+ const [r, g, b] = GRADIENT[band]!;
49
+ if (band !== current) {
50
+ line += `\u001b[38;2;${r};${g};${b}m`;
51
+ current = band;
52
+ }
53
+ line += row[column];
54
+ }
55
+ coloured.push(`${line}${RESET}`);
56
+ }
57
+ return coloured;
58
+ }