cartethyia 2.5.3 → 2.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/.env.example +13 -4
  2. package/README.md +2 -2
  3. package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
  4. package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
  5. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
  6. package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
  7. package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
  8. package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
  9. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
  10. package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
  11. package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
  12. package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
  13. package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
  14. package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
  15. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
  16. package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
  17. package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
  18. package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
  19. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
  20. package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
  21. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
  22. package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
  23. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
  24. package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
  25. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
  26. package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
  27. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
  28. package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
  29. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
  30. package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
  31. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
  32. package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
  33. package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
  34. package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
  35. package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
  36. package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
  37. package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
  38. package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
  39. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
  40. package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
  41. package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
  42. package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
  43. package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
  44. package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
  45. package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
  46. package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
  47. package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
  48. package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
  49. package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
  50. package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
  51. package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
  52. package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
  53. package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
  54. package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
  55. package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
  56. package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
  57. package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
  58. package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
  59. package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
  60. package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
  61. package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
  62. package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
  63. package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
  64. package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
  65. package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
  66. package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
  67. package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
  68. package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
  69. package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
  70. package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
  71. package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
  72. package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
  73. package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
  74. package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
  75. package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
  76. package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
  77. package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
  78. package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
  79. package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
  80. package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
  81. package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
  82. package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
  83. package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
  84. package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
  85. package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
  86. package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
  87. package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
  88. package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
  89. package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
  90. package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
  91. package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
  92. package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
  93. package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
  94. package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
  95. package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
  96. package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
  97. package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
  98. package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
  99. package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
  100. package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
  101. package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
  102. package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
  103. package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
  104. package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
  105. package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
  106. package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
  107. package/dist/dashboard/assets/tokens-COzario9.js +1 -0
  108. package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
  109. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
  110. package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
  111. package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
  112. package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
  113. package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
  114. package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
  115. package/dist/dashboard/donation/qris-risun-marke.png +0 -0
  116. package/dist/dashboard/index.html +1 -1
  117. package/dist/dashboard/index.html.gz +0 -0
  118. package/dist/dashboard/precompressed-assets.json +57 -57
  119. package/migrations/README.md +52 -52
  120. package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
  121. package/package.json +96 -95
  122. package/scripts/cli/admin.ts +125 -0
  123. package/scripts/cli/autostart.ts +237 -0
  124. package/scripts/cli/{ui/background.ts → background.ts} +1 -1
  125. package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
  126. package/scripts/cli/doctor.ts +37 -0
  127. package/scripts/cli/{support/env.ts → env.ts} +99 -99
  128. package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
  129. package/scripts/cli/instance.ts +224 -0
  130. package/scripts/cli/main.ts +88 -18
  131. package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
  132. package/scripts/cli/port.ts +100 -0
  133. package/scripts/cli/{support/services.ts → services.ts} +88 -88
  134. package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
  135. package/scripts/cli/{command/start.ts → start.ts} +10 -5
  136. package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
  137. package/scripts/cli/tray/index.ts +62 -12
  138. package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
  139. package/scripts/cli/update.ts +40 -0
  140. package/scripts/cli/version.ts +67 -0
  141. package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
  142. package/src/boot/app.ts +11 -7
  143. package/src/boot/dependencies.ts +50 -19
  144. package/src/boot/timeout.ts +2 -13
  145. package/src/config/cooldowns.ts +186 -138
  146. package/src/config/network.ts +8 -14
  147. package/src/config/providers.ts +7 -10
  148. package/src/config/runtime.ts +76 -76
  149. package/src/config/security.ts +15 -0
  150. package/src/config/spec.ts +52 -7
  151. package/src/config/telemetry.ts +26 -0
  152. package/src/config/timeouts.ts +0 -6
  153. package/src/console/admin/model-abuse/contracts.ts +28 -6
  154. package/src/console/admin/model-abuse/routes.ts +56 -3
  155. package/src/console/api-keys/bucket-spend.ts +15 -4
  156. package/src/console/api-keys/contracts.ts +410 -414
  157. package/src/console/api-keys/routes.ts +881 -880
  158. package/src/console/auth/credentials.ts +3 -3
  159. package/src/console/auth/setup-service.ts +2 -2
  160. package/src/console/backup/contracts.ts +15 -2
  161. package/src/console/backup/export-endpoint.ts +6 -2
  162. package/src/console/backup/nine-router.ts +2 -5
  163. package/src/console/backup/service.ts +4 -0
  164. package/src/console/backup/store.ts +192 -29
  165. package/src/console/backup/validate.ts +3 -45
  166. package/src/console/cli-tools/fs-ops.ts +487 -487
  167. package/src/console/console-router-guards.ts +1 -1
  168. package/src/console/console-router.ts +8 -0
  169. package/src/console/data-management/contracts.ts +100 -0
  170. package/src/console/data-management/operations.ts +234 -0
  171. package/src/console/data-management/routes.ts +103 -0
  172. package/src/console/domain-registration.ts +45 -1
  173. package/src/console/observability/contracts.ts +524 -485
  174. package/src/console/observability/live.ts +22 -0
  175. package/src/console/observability/store.ts +1138 -1018
  176. package/src/console/observability/usage-dimensions.ts +4 -4
  177. package/src/console/providers/catalog/catalog-projections.ts +6 -7
  178. package/src/console/providers/catalog/contracts.ts +6 -0
  179. package/src/console/providers/catalog/model-operations.ts +5 -2
  180. package/src/console/providers/catalog/provider-operations.ts +24 -4
  181. package/src/console/providers/catalog/public-model-store.ts +8 -11
  182. package/src/console/providers/catalog/routes.ts +7 -0
  183. package/src/console/providers/catalog/store.ts +1553 -1532
  184. package/src/console/providers/oauth/routes.ts +8 -12
  185. package/src/console/quota/account-quota-shared.ts +52 -39
  186. package/src/console/quota/account-quota-tenant.ts +6 -6
  187. package/src/console/quota/quota-view.ts +1 -1
  188. package/src/console/quota/refresh.ts +441 -429
  189. package/src/console/remote-support/contracts.ts +61 -0
  190. package/src/console/remote-support/routes.ts +65 -0
  191. package/src/console/remote-support/service.ts +207 -0
  192. package/src/console/response-compression.ts +0 -3
  193. package/src/console/routing/pools/import-parse.ts +206 -0
  194. package/src/console/routing/pools/operations.ts +543 -501
  195. package/src/console/routing/pools/probe-result.ts +3 -2
  196. package/src/console/routing/pools/probe-verdict.ts +132 -132
  197. package/src/console/routing/pools/routes.ts +181 -164
  198. package/src/console/routing/pools/store.ts +8 -8
  199. package/src/console/settings/contracts.ts +95 -2
  200. package/src/console/settings/store.ts +81 -3
  201. package/src/console/shared/cursor.ts +8 -8
  202. package/src/console/shared/errors.ts +1 -3
  203. package/src/console/updates/routes.ts +16 -0
  204. package/src/main.ts +10 -0
  205. package/src/network/http2-bidi.ts +2 -4
  206. package/src/network/pool/agent.ts +478 -478
  207. package/src/network/pool/selector.ts +5 -4
  208. package/src/network/retry.ts +1 -2
  209. package/src/network/ssrf.ts +5 -4
  210. package/src/observability/event-compaction.ts +317 -0
  211. package/src/observability/event-day-index.ts +440 -0
  212. package/src/observability/event-log.ts +155 -6
  213. package/src/observability/logger.ts +6 -6
  214. package/src/observability/payload-capture.ts +158 -33
  215. package/src/observability/payload-store.ts +82 -61
  216. package/src/observability/redaction.ts +5 -6
  217. package/src/observability/request-notify.ts +43 -0
  218. package/src/observability/telemetry-status.ts +8 -9
  219. package/src/observability/token-speed.ts +5 -4
  220. package/src/persistence/db-mode.ts +7 -7
  221. package/src/persistence/health-event-flush.ts +36 -0
  222. package/src/persistence/readiness.ts +1 -1
  223. package/src/persistence/schema.ts +75 -29
  224. package/src/persistence/shared-cache.ts +1 -2
  225. package/src/persistence/stores/api-key-store.ts +18 -6
  226. package/src/persistence/stores/share-store.ts +4 -3
  227. package/src/persistence/stores/telemetry-store.ts +33 -11
  228. package/src/protocol/canonical-model.ts +11 -3
  229. package/src/protocol/codecs.ts +139 -6
  230. package/src/protocol/gateway-error.ts +6 -4
  231. package/src/protocol/image-clamp.ts +19 -1
  232. package/src/protocol/registry.ts +8 -0
  233. package/src/protocol/request/chat.ts +21 -1
  234. package/src/protocol/request/codex.ts +10 -1
  235. package/src/protocol/request/gemini.ts +9 -2
  236. package/src/protocol/request/messages.ts +18 -7
  237. package/src/protocol/request/responses.ts +9 -1
  238. package/src/protocol/response/chat.ts +34 -4
  239. package/src/protocol/response/codex.ts +7 -4
  240. package/src/protocol/response/dsml.ts +140 -28
  241. package/src/protocol/response/responses.ts +11 -5
  242. package/src/protocol/stream-error-frames.ts +4 -5
  243. package/src/providers/adapters/compatible-adapter.ts +3 -0
  244. package/src/providers/authentication/jwt-validator.ts +9 -10
  245. package/src/providers/authentication/oauth-refresh-service.ts +9 -11
  246. package/src/providers/bundled-discovery-providers.ts +122 -119
  247. package/src/providers/configured-openai-providers.ts +4 -4
  248. package/src/providers/default-registry.ts +5 -4
  249. package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
  250. package/src/providers/discovery/catalog/catalog-format.ts +3 -6
  251. package/src/providers/discovery/models-dev-catalog.ts +354 -354
  252. package/src/providers/discovery/openai-model-discovery.ts +5 -0
  253. package/src/providers/discovery/openrouter-free-roster.ts +15 -17
  254. package/src/providers/discovery/probe-phases.ts +4 -4
  255. package/src/providers/discovery/probe-wire.ts +6 -13
  256. package/src/providers/discovery/probing-service.ts +1117 -1045
  257. package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
  258. package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
  259. package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
  260. package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
  261. package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
  262. package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
  263. package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
  264. package/src/providers/integrations/buddy/buddy-media.ts +2 -2
  265. package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
  266. package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
  267. package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
  268. package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
  269. package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
  270. package/src/providers/integrations/claude/claude.ts +608 -608
  271. package/src/providers/integrations/cline/cline-quota.ts +27 -20
  272. package/src/providers/integrations/cline/cline.ts +537 -539
  273. package/src/providers/integrations/codex/codex-headers.ts +25 -39
  274. package/src/providers/integrations/codex/codex-oauth.ts +1 -1
  275. package/src/providers/integrations/codex/codex.ts +2 -29
  276. package/src/providers/integrations/commandcode/commandcode.ts +674 -675
  277. package/src/providers/integrations/commandcode/discovery.ts +117 -116
  278. package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
  279. package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
  280. package/src/providers/integrations/cursor/cursor-request.ts +616 -471
  281. package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
  282. package/src/providers/integrations/cursor/cursor.ts +1333 -960
  283. package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
  284. package/src/providers/integrations/factory/factory-models.ts +3 -5
  285. package/src/providers/integrations/factory/factory.ts +8 -1
  286. package/src/providers/integrations/gemini.ts +6 -1
  287. package/src/providers/integrations/grok/grok-install-id.ts +2 -3
  288. package/src/providers/integrations/grok/grok-quota.ts +279 -177
  289. package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
  290. package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
  291. package/src/providers/integrations/grok/grok.ts +6 -7
  292. package/src/providers/integrations/hermes.ts +14 -18
  293. package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
  294. package/src/providers/integrations/kiro/kiro.ts +1 -1
  295. package/src/providers/integrations/mistral.ts +6 -6
  296. package/src/providers/integrations/opencode/fingerprint.ts +3 -3
  297. package/src/providers/integrations/opencode/provider.ts +19 -20
  298. package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
  299. package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
  300. package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
  301. package/src/providers/integrations/qoder/qoder.ts +884 -865
  302. package/src/providers/integrations/shared/connect.ts +6 -6
  303. package/src/providers/integrations/trae/trae-catalog.ts +1 -2
  304. package/src/providers/integrations/trae/trae-quota.ts +384 -0
  305. package/src/providers/integrations/trae/trae.ts +2 -2
  306. package/src/providers/integrations/zcode/oauth.ts +6 -1
  307. package/src/providers/operations/account-health-service.ts +75 -27
  308. package/src/providers/operations/account-reset-service.ts +1 -1
  309. package/src/providers/operations/client-versions.ts +6 -6
  310. package/src/providers/operations/custom-cli-headers.ts +3 -4
  311. package/src/providers/operations/provider-catalog-seeder.ts +95 -95
  312. package/src/providers/operations/provider-catalog-service.ts +3 -4
  313. package/src/providers/provider-contracts.ts +7 -0
  314. package/src/providers/provider-metadata.ts +10 -8
  315. package/src/providers/provider-registry.ts +6 -7
  316. package/src/providers/provider-usage.ts +1 -1
  317. package/src/providers/quota/quota-support.ts +8 -0
  318. package/src/security/abuse.ts +15 -10
  319. package/src/security/access-control.ts +144 -146
  320. package/src/security/admission/contracts.ts +11 -0
  321. package/src/security/admission/in-memory-store.ts +4 -3
  322. package/src/security/admission/lease-sweep.ts +6 -6
  323. package/src/security/admission/reasons.ts +5 -1
  324. package/src/security/admission/redis-store.ts +211 -210
  325. package/src/security/admission/service.ts +4 -0
  326. package/src/security/api-key-auth.ts +27 -17
  327. package/src/security/ip-boundary.ts +155 -15
  328. package/src/security/model-abuse.ts +366 -94
  329. package/src/security/outbound-headers.ts +3 -4
  330. package/src/shared/sleep.ts +4 -0
  331. package/src/transport/dispatch/attempt-finalize.ts +47 -5
  332. package/src/transport/dispatch/attempt-loop.ts +531 -530
  333. package/src/transport/dispatch/generation.ts +6 -0
  334. package/src/transport/dispatch/proxy-request.ts +24 -1
  335. package/src/transport/dispatch/ratelimit-capture.ts +37 -0
  336. package/src/transport/dispatch/stream-bridge.ts +1 -3
  337. package/src/transport/dispatch/streaming/finalize.ts +1 -1
  338. package/src/transport/dispatch/streaming/stream.ts +19 -6
  339. package/src/transport/dispatch/upstream.ts +9 -0
  340. package/src/transport/failure-policy.ts +162 -8
  341. package/src/transport/guards/auth-guard.ts +7 -4
  342. package/src/transport/guards/concurrency-guard.ts +0 -5
  343. package/src/transport/guards/error-lifecycle.ts +13 -6
  344. package/src/transport/guards/pipeline.ts +15 -3
  345. package/src/transport/guards/request-context.ts +8 -2
  346. package/src/transport/request/inflight.ts +5 -6
  347. package/src/transport/request/preparer.ts +9 -8
  348. package/src/transport/request/state.ts +21 -8
  349. package/src/transport/request/tenant-preferences.ts +60 -0
  350. package/src/transport/routing/admission.ts +4 -0
  351. package/src/transport/routing/route-catalog.ts +65 -10
  352. package/src/transport/routing/route-model.ts +46 -14
  353. package/src/transport/routing/router.ts +21 -14
  354. package/src/transport/surface/completion.ts +2 -3
  355. package/src/transport/surface/dialects.ts +5 -6
  356. package/src/transport/surface/responses/encode.ts +38 -0
  357. package/src/transport/surface/responses/parse.ts +4 -0
  358. package/src/transport/translation/capabilities.ts +2 -3
  359. package/src/transport/translation/quirks.ts +3 -3
  360. package/src/workers/buddy-growth-tasks.ts +5 -10
  361. package/src/workers/daily-checkin.ts +17 -23
  362. package/src/workers/quota-refresh-sweep.ts +26 -26
  363. package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
  364. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
  365. package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
  366. package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
  367. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
  368. package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
  369. package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
  370. package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
  371. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
  372. package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
  373. package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
  374. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
  375. package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
  376. package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
  377. package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
  378. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
  379. package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
  380. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
  381. package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
  382. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
  383. package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
  384. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
  385. package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
  386. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
  387. package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
  388. package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
  389. package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
  390. package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
  391. package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
  392. package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
  393. package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
  394. package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
  395. package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
  396. package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
  397. package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
  398. package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
  399. package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
  400. package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
  401. package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
  402. package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
  403. package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
  404. package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
  405. package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
  406. package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
  407. package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
  408. package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
  409. package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
  410. package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
  411. package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
  412. package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
  413. package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
  414. package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
  415. package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
  416. package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
  417. package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
  418. package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
  419. package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
  420. package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
  421. package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
  422. package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
  423. package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
  424. package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
  425. package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
  426. package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
  427. package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
  428. package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
  429. package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
  430. package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
  431. package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
  432. package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
  433. package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
  434. package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
  435. package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
  436. package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
  437. package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
  438. package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
  439. package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
  440. package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
  441. package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
  442. package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
  443. package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
  444. package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
  445. package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
  446. package/scripts/cli/command/admin.ts +0 -65
  447. package/scripts/cli/command/doctor.ts +0 -257
  448. package/scripts/cli/command/update.ts +0 -23
  449. package/scripts/cli/gateway/drain-token.ts +0 -78
  450. package/scripts/cli/gateway/instance-lock.ts +0 -105
  451. package/scripts/cli/gateway/stop.ts +0 -51
  452. package/scripts/cli/support/bun.ts +0 -27
  453. package/scripts/cli/support/version.ts +0 -37
  454. /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
  455. /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
  456. /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Remote-support tunnel contracts.
3
+ *
4
+ * A tunnel publishes the loopback gateway to a public or tailnet address. This
5
+ * module owns the vocabulary (which tool, which states, what a status reports)
6
+ * and is deliberately free of Elysia and Node imports so the dashboard can
7
+ * import the same shape without dragging a server runtime into the browser
8
+ * bundle.
9
+ */
10
+
11
+ /** The two supported publishers. Both must already be installed. */
12
+ export const REMOTE_SUPPORT_TOOLS = ["cloudflared", "tailscale"] as const;
13
+
14
+ export type RemoteSupportTool = (typeof REMOTE_SUPPORT_TOOLS)[number];
15
+
16
+ /**
17
+ * Why a tunnel cannot start. `not_installed` is the common one: this product
18
+ * never installs third-party binaries, so a missing `cloudflared` or
19
+ * `tailscale` is reported for the operator to install, never downloaded.
20
+ */
21
+ export type RemoteSupportBlocker =
22
+ | "not_installed"
23
+ | "unsupported_platform"
24
+ | "launch_failed";
25
+
26
+ export interface RemoteSupportStatus {
27
+ /** Which publisher is up, or `null` when neither is. */
28
+ readonly active: RemoteSupportTool | null;
29
+ /** The published base URL, present once the publisher reported one. */
30
+ readonly url: string | null;
31
+ /** Set when a start was refused, with the operator-facing reason. */
32
+ readonly blocker: RemoteSupportBlocker | null;
33
+ /** Operator-facing detail for `blocker`, e.g. the install hint. */
34
+ readonly message: string | null;
35
+ /** Local port the tunnel forwards to. */
36
+ readonly port: number;
37
+ /** Which publishers are installed on this host. Cached so the UI doesn't poll. */
38
+ readonly installed: Readonly<Record<RemoteSupportTool, boolean>>;
39
+ }
40
+
41
+ export interface RemoteSupportStartRequest {
42
+ readonly tool: RemoteSupportTool;
43
+ /** Defaults to the gateway's own port when omitted. */
44
+ readonly port?: number;
45
+ }
46
+
47
+ export interface RemoteSupportStartResponse extends RemoteSupportStatus {
48
+ readonly started: boolean;
49
+ }
50
+
51
+ /** Human-readable install guidance, keyed by tool. Never an installer. */
52
+ export const REMOTE_SUPPORT_INSTALL_HINT: Readonly<Record<RemoteSupportTool, string>> = {
53
+ cloudflared:
54
+ "cloudflared is not installed. Install it yourself (winget install Cloudflare.cloudflared) and try again.",
55
+ tailscale:
56
+ "tailscale is not installed. Install it yourself (winget install Tailscale.Tailscale), run `tailscale up` once, then try again.",
57
+ };
58
+
59
+ export function isRemoteSupportTool(value: unknown): value is RemoteSupportTool {
60
+ return typeof value === "string" && (REMOTE_SUPPORT_TOOLS as readonly string[]).includes(value);
61
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Remote-support HTTP surface.
3
+ *
4
+ * Three routes over one external launcher script: read the current tunnel,
5
+ * start one, stop one. Every mutating route needs `dashboard:write` because it
6
+ * runs a process on the host running the gateway.
7
+ *
8
+ * The start response never contains a secret and never performs an install; it
9
+ * reports `not_installed` with guidance when the publisher is missing.
10
+ */
11
+ import { Elysia, t } from "elysia";
12
+ import type { ConsoleAccessResolver } from "../auth/access";
13
+ import { consoleErrorHandler, requireTenantScope } from "../shared/errors";
14
+ import { REMOTE_SUPPORT_TOOLS } from "./contracts";
15
+ import type { RemoteSupportService } from "./service";
16
+
17
+ export interface RemoteSupportRoutesConfig {
18
+ readonly accessResolver: ConsoleAccessResolver;
19
+ readonly service: RemoteSupportService;
20
+ }
21
+
22
+ const toolSchema = t.Union([
23
+ t.Literal(REMOTE_SUPPORT_TOOLS[0]),
24
+ t.Literal(REMOTE_SUPPORT_TOOLS[1]),
25
+ ]);
26
+
27
+ const startBody = t.Object({
28
+ tool: toolSchema,
29
+ port: t.Optional(t.Number()),
30
+ });
31
+
32
+ const stopBody = t.Object({
33
+ tool: t.Optional(toolSchema),
34
+ });
35
+
36
+ /**
37
+ * The return type is inferred rather than annotated as `Elysia`: `.error()`
38
+ * widens the instance type with its handler's response envelope, so the
39
+ * annotated base type no longer matches the chained result.
40
+ */
41
+ export function createRemoteSupportRoutes(config: RemoteSupportRoutesConfig) {
42
+ return new Elysia()
43
+ .error(consoleErrorHandler("Remote support failed"))
44
+ .get("/remote-support/status", async ({ request }) => {
45
+ requireTenantScope(config.accessResolver(request), "dashboard:read");
46
+ return await config.service.status();
47
+ })
48
+ .post(
49
+ "/remote-support/start",
50
+ { body: startBody },
51
+ async ({ request, body }) => {
52
+ requireTenantScope(config.accessResolver(request), "dashboard:write");
53
+ const status = await config.service.start(body.tool, body.port);
54
+ return { started: status.active !== null, ...status };
55
+ },
56
+ )
57
+ .post(
58
+ "/remote-support/stop",
59
+ { body: stopBody },
60
+ async ({ request, body }) => {
61
+ requireTenantScope(config.accessResolver(request), "dashboard:write");
62
+ return await config.service.stop(body.tool);
63
+ },
64
+ );
65
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Remote-support tunnel service.
3
+ *
4
+ * The tunnel is launched by an external script rather than by spawning
5
+ * `cloudflared`/`tailscale` directly, for one reason that matters: the script
6
+ * hands the publisher to the OS session, so the tunnel is not a child of this
7
+ * gateway. A gateway restart, a hot reload, or a Ctrl-C in the owning terminal
8
+ * therefore cannot kill an operator's live remote-support session.
9
+ *
10
+ * This service never installs anything. A missing binary is reported as a
11
+ * blocker with install guidance; downloading a third-party agent on the
12
+ * operator's behalf is out of scope.
13
+ */
14
+ import { spawn } from "node:child_process";
15
+ import { existsSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { PACKAGE_ROOT } from "../../config/package-paths";
18
+ import {
19
+ REMOTE_SUPPORT_INSTALL_HINT,
20
+ type RemoteSupportBlocker,
21
+ type RemoteSupportStatus,
22
+ type RemoteSupportTool,
23
+ } from "./contracts";
24
+
25
+ /** How long the launcher may take to report a URL before we stop waiting. */
26
+ const LAUNCH_TIMEOUT_MS = 40_000;
27
+
28
+ /** Cached install detection so we don't spawn `where` on every status poll. */
29
+ const installedCache = new Map<RemoteSupportTool, boolean>();
30
+
31
+ export interface RemoteSupportServiceConfig {
32
+ /** Defaults to the gateway's configured port. */
33
+ readonly port: number;
34
+ /** Overridable for tests; defaults to the package root. */
35
+ readonly scriptDir?: string;
36
+ }
37
+
38
+ interface CommandResult {
39
+ readonly status: number;
40
+ readonly stdout: string;
41
+ }
42
+
43
+ function scriptPath(config: RemoteSupportServiceConfig, name: string): string {
44
+ const dir = config.scriptDir ?? join(PACKAGE_ROOT, "scripts");
45
+ return join(dir, name);
46
+ }
47
+
48
+ function runScript(
49
+ config: RemoteSupportServiceConfig,
50
+ args: readonly string[],
51
+ ): Promise<CommandResult> {
52
+ const { promise, resolve, reject } = Promise.withResolvers<CommandResult>();
53
+ const isWindows = process.platform === "win32";
54
+ // Windows cannot spawn a `.cmd` directly — `spawn` rejects it with EINVAL
55
+ // because a batch file is not an executable image. It has to be handed to
56
+ // `cmd.exe /c`, which is also what lets the batch call `exit /b 3` and have
57
+ // that code reach us as the child's status.
58
+ const command = isWindows ? "cmd.exe" : "powershell";
59
+ const fileName = isWindows ? "remote-support.cmd" : "remote-support.ps1";
60
+ const file = scriptPath(config, fileName);
61
+ const spawnArgs = isWindows
62
+ ? ["/d", "/s", "/c", file, ...args]
63
+ : ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", file, ...args];
64
+ const child = spawn(command, spawnArgs, {
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ windowsHide: true,
67
+ });
68
+ let stdout = "";
69
+ let stderr = "";
70
+ child.stdout.setEncoding("utf8");
71
+ child.stderr.setEncoding("utf8");
72
+ child.stdout.on("data", (chunk: string) => {
73
+ stdout += chunk;
74
+ });
75
+ child.stderr.on("data", (chunk: string) => {
76
+ stderr += chunk;
77
+ });
78
+ const timer = setTimeout(() => child.kill(), LAUNCH_TIMEOUT_MS);
79
+ child.on("error", reject);
80
+ child.on("close", (status) => {
81
+ clearTimeout(timer);
82
+ resolve({ status: status ?? 1, stdout: `${stdout}${stderr}`.trim() });
83
+ });
84
+ return promise;
85
+ }
86
+
87
+ /**
88
+ * `where`/`which` lookup, mirroring what the script does itself so the route
89
+ * can answer `not_installed` with a clear message instead of a generic failure.
90
+ */
91
+ function toolAvailable(tool: RemoteSupportTool): Promise<boolean> {
92
+ const cached = installedCache.get(tool);
93
+ if (cached !== undefined) return Promise.resolve(cached);
94
+ const { promise, resolve } = Promise.withResolvers<boolean>();
95
+ const command = process.platform === "win32" ? "where" : "which";
96
+ const child = spawn(command, [tool], { stdio: "ignore", windowsHide: true });
97
+ child.on("error", () => {
98
+ installedCache.set(tool, false);
99
+ resolve(false);
100
+ });
101
+ child.on("close", (status) => {
102
+ const ok = status === 0;
103
+ installedCache.set(tool, ok);
104
+ resolve(ok);
105
+ });
106
+ return promise;
107
+ }
108
+
109
+ export interface RemoteSupportService {
110
+ status(): Promise<RemoteSupportStatus>;
111
+ start(tool: RemoteSupportTool, port?: number): Promise<RemoteSupportStatus>;
112
+ stop(tool?: RemoteSupportTool): Promise<RemoteSupportStatus>;
113
+ }
114
+
115
+ export function createRemoteSupportService(config: RemoteSupportServiceConfig): RemoteSupportService {
116
+ const port = config.port;
117
+
118
+ /**
119
+ * Install state for both publishers, cached per process.
120
+ *
121
+ * Every status carries it so the card can show "not installed" without the
122
+ * browser probing on its own — but the probe is `where`/`which`, which is a
123
+ * process spawn, so it is memoised here rather than run on each poll.
124
+ */
125
+ async function detectInstalled(): Promise<Readonly<Record<RemoteSupportTool, boolean>>> {
126
+ return {
127
+ cloudflared: await toolAvailable("cloudflared"),
128
+ tailscale: await toolAvailable("tailscale"),
129
+ } as const;
130
+ }
131
+
132
+ async function readStatus(blocker: RemoteSupportBlocker | null = null, message: string | null = null): Promise<RemoteSupportStatus> {
133
+ const result = await runScript(config, ["status"]);
134
+ const text = result.stdout;
135
+ const active: RemoteSupportTool | null = text.includes("cloudflared running")
136
+ ? "cloudflared"
137
+ : text.includes("tailscale running")
138
+ ? "tailscale"
139
+ : null;
140
+ let url: string | null = null;
141
+ if (active !== null) {
142
+ const urlResult = await runScript(config, ["url"]);
143
+ const candidate = urlResult.stdout.trim().split(/\s+/).find((line) => line.startsWith("https://"));
144
+ url = candidate ?? null;
145
+ }
146
+ const installed = await detectInstalled();
147
+ return { active, url, blocker, message, port, installed };
148
+ }
149
+
150
+ return {
151
+ async status() {
152
+ return await readStatus();
153
+ },
154
+
155
+ async start(tool, requestedPort) {
156
+ if (process.platform !== "win32") {
157
+ return {
158
+ active: null,
159
+ url: null,
160
+ blocker: "unsupported_platform",
161
+ message: "Remote support is currently scripted for Windows hosts only.",
162
+ port,
163
+ installed: { cloudflared: false, tailscale: false },
164
+ };
165
+ }
166
+ const launcher = scriptPath(config, "remote-support.cmd");
167
+ if (!existsSync(launcher)) {
168
+ return {
169
+ active: null,
170
+ url: null,
171
+ blocker: "launch_failed",
172
+ message: `Launcher not found: ${launcher}`,
173
+ port,
174
+ installed: await detectInstalled(),
175
+ };
176
+ }
177
+ if (!(await toolAvailable(tool))) {
178
+ return {
179
+ active: null,
180
+ url: null,
181
+ blocker: "not_installed",
182
+ message: REMOTE_SUPPORT_INSTALL_HINT[tool],
183
+ port,
184
+ installed: await detectInstalled(),
185
+ };
186
+ }
187
+ const target = requestedPort ?? port;
188
+ const result = await runScript(config, ["start", tool, String(target)]);
189
+ if (result.status !== 0 && !result.stdout.includes("started")) {
190
+ return {
191
+ active: null,
192
+ url: null,
193
+ blocker: "launch_failed",
194
+ message: result.stdout || `Failed to start ${tool}.`,
195
+ port,
196
+ installed: await detectInstalled(),
197
+ };
198
+ }
199
+ return await readStatus();
200
+ },
201
+
202
+ async stop(tool) {
203
+ await runScript(config, tool === undefined ? ["stop"] : ["stop", tool]);
204
+ return await readStatus();
205
+ },
206
+ };
207
+ }
@@ -18,9 +18,6 @@
18
18
  * only". A path test can, and it lives inside `gzipResponse` — the one place
19
19
  * that runs on every request.
20
20
  *
21
- * Consequence: `/v1/models` was previously compressed as a side effect of
22
- * that `hoc` propagation. It deliberately is not now.
23
- *
24
21
  * ## Why this is scoped to the console, not the proxy
25
22
  *
26
23
  * `/v1/*` is deliberately excluded, for two independent reasons:
@@ -0,0 +1,206 @@
1
+ // Parsing a proxy list pasted from a file.
2
+ //
3
+ // Every provider hands out its proxy list in a different shape, and the shape
4
+ // is not something an operator should have to convert by hand before this
5
+ // gateway will accept it. Three forms cover what actually arrives:
6
+ //
7
+ // - a JSON array of pool objects (the shape `POST /network/pools` takes);
8
+ // - one bare URI per line — `http://user:pass@1.2.3.4:8080`;
9
+ // - one `host:port:user:password` row per line, which is what resellers send.
10
+ //
11
+ // A line that parses to nothing is reported rather than silently dropped: a
12
+ // list of 200 where 40 were skipped looks like a successful import and leaves
13
+ // the operator short 40 nodes with no reason.
14
+ //
15
+ // Secrets are handled as little as possible — they are parsed into a
16
+ // credential and handed straight to the store, never logged.
17
+
18
+ import { POOL_KINDS, POOL_STATUSES } from "./contracts";
19
+
20
+ /** One row that parsed, ready to become a pool. */
21
+ export interface ParsedProxyRow {
22
+ readonly kind: (typeof POOL_KINDS)[number];
23
+ readonly endpoint: string;
24
+ readonly credential: string | null;
25
+ readonly label: string | null;
26
+ }
27
+
28
+ /** One row that did not parse, with the reason. */
29
+ export interface SkippedProxyRow {
30
+ readonly line: number;
31
+ readonly reason: string;
32
+ }
33
+
34
+ export interface ParsedProxyFile {
35
+ readonly rows: readonly ParsedProxyRow[];
36
+ readonly skipped: readonly SkippedProxyRow[];
37
+ }
38
+
39
+ /** Schemes this gateway can dispatch through, mapped to a pool kind. */
40
+ const SCHEME_KINDS: Readonly<Record<string, (typeof POOL_KINDS)[number]>> = {
41
+ http: "http",
42
+ https: "http",
43
+ socks5: "socks5",
44
+ socks5h: "socks5",
45
+ socks4: "socks5",
46
+ };
47
+
48
+ /** Kept as a name rather than a literal, because two callers compare against it. */
49
+ const DEFAULT_KIND: (typeof POOL_KINDS)[number] = "http";
50
+
51
+ function isPoolKind(value: unknown): value is (typeof POOL_KINDS)[number] {
52
+ return typeof value === "string" && (POOL_KINDS as readonly string[]).includes(value);
53
+ }
54
+
55
+ function isPoolStatus(value: unknown): value is (typeof POOL_STATUSES)[number] {
56
+ return typeof value === "string" && (POOL_STATUSES as readonly string[]).includes(value);
57
+ }
58
+
59
+ /**
60
+ * The endpoint scheme, lowercased, or `null` when the line carries none.
61
+ *
62
+ * A line without a scheme is still valid — `host:port` is the most common
63
+ * reseller form — so "no scheme" means "assume http", not "unparseable".
64
+ */
65
+ function schemeOf(text: string): string | null {
66
+ const match = /^([a-z][a-z0-9+.-]*):\/\//i.exec(text.trim());
67
+ const scheme = match?.[1];
68
+ return scheme ? scheme.toLowerCase() : null;
69
+ }
70
+
71
+ /** Splits `host:port:user:password`, where the password may itself contain `:`. */
72
+ function splitColonRow(text: string): {
73
+ readonly host: string;
74
+ readonly port: string;
75
+ readonly user: string | null;
76
+ readonly password: string | null;
77
+ } | null {
78
+ // The password is last and may contain colons, so it is everything after the
79
+ // third separator rather than a fourth field.
80
+ const parts: readonly string[] = text.trim().split(":");
81
+ if (parts.length < 2) return null;
82
+ const host = (parts[0] ?? "").trim();
83
+ const port = (parts[1] ?? "").trim();
84
+ if (!host || !port || !/^\d+$/.test(port)) return null;
85
+ const user = parts.length > 2 ? (parts[2] ?? "").trim() : "";
86
+ const password = parts.length > 3 ? parts.slice(3).join(":").trim() : "";
87
+ return {
88
+ host,
89
+ port,
90
+ user: user !== "" ? user : null,
91
+ password: password !== "" ? password : null,
92
+ };
93
+ }
94
+
95
+ /** One bare-URI line, e.g. `http://user:pass@1.2.3.4:8080`. */
96
+ function parseUriLine(text: string): ParsedProxyRow | null {
97
+ const scheme = schemeOf(text);
98
+ if (scheme === null) return null;
99
+ const kind = SCHEME_KINDS[scheme] ?? DEFAULT_KIND;
100
+ const withoutScheme = text.trim().slice(scheme.length + 3);
101
+ const at = withoutScheme.lastIndexOf("@");
102
+ const authority = at >= 0 ? withoutScheme.slice(at + 1) : withoutScheme;
103
+ const userinfo = at >= 0 ? withoutScheme.slice(0, at) : null;
104
+ if (!authority) return null;
105
+ const credential = userinfo
106
+ ? decodeURIComponent(userinfo)
107
+ : null;
108
+ const hasPort = /:\d+$/.test(authority);
109
+ const endpoint = hasPort ? authority : `${authority}`;
110
+ if (!endpoint) return null;
111
+ return { kind, endpoint: `${scheme}://${endpoint}`, credential, label: null };
112
+ }
113
+
114
+ /** One `host:port[:user[:password]]` line. */
115
+ function parseColonLine(text: string): ParsedProxyRow | null {
116
+ const row = splitColonRow(text);
117
+ if (!row) return null;
118
+ const credential =
119
+ row.user !== null ? (row.password !== null ? `${row.user}:${row.password}` : row.user) : null;
120
+ // The store requires a full URL, and a reseller row carries no scheme.
121
+ // `http://` is the safe reading: it is the default for a bare row, and an
122
+ // operator who means socks writes the scheme.
123
+ return {
124
+ kind: DEFAULT_KIND,
125
+ endpoint: `http://${row.host}:${row.port}`,
126
+ credential,
127
+ label: null,
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Parses one line.
133
+ *
134
+ * Blank lines and `#` comments are skipped without being reported: they are
135
+ * structure, not failed rows, and listing them as skipped would bury the rows
136
+ * that actually failed.
137
+ */
138
+ function parseLine(text: string): ParsedProxyRow | null {
139
+ const trimmed = text.trim();
140
+ if (trimmed === "" || trimmed.startsWith("#")) return null;
141
+ return parseUriLine(trimmed) ?? parseColonLine(trimmed);
142
+ }
143
+
144
+ /** Parses a JSON array of pool objects. */
145
+ function parseJsonArray(value: unknown): ParsedProxyFile | null {
146
+ if (!Array.isArray(value)) return null;
147
+ const rows: ParsedProxyRow[] = [];
148
+ const skipped: SkippedProxyRow[] = [];
149
+ value.forEach((entry, index) => {
150
+ if (typeof entry !== "object" || entry === null) {
151
+ skipped.push({ line: index + 1, reason: "not an object" });
152
+ return;
153
+ }
154
+ const record = entry as Record<string, unknown>;
155
+ const endpoint = typeof record["endpoint"] === "string" ? record["endpoint"].trim() : "";
156
+ if (!endpoint) {
157
+ skipped.push({ line: index + 1, reason: "no endpoint" });
158
+ return;
159
+ }
160
+ rows.push({
161
+ kind: isPoolKind(record["kind"]) ? record["kind"] : DEFAULT_KIND,
162
+ endpoint,
163
+ credential: typeof record["credential"] === "string" ? record["credential"] : null,
164
+ label: typeof record["label"] === "string" ? record["label"] : null,
165
+ });
166
+ });
167
+ return { rows, skipped };
168
+ }
169
+
170
+ /**
171
+ * Parses a pasted proxy list.
172
+ *
173
+ * JSON is tried first, because a whole file that happens to be valid JSON is
174
+ * unambiguous; anything else is treated as line-oriented text.
175
+ */
176
+ export function parseProxyFile(content: string): ParsedProxyFile {
177
+ const trimmed = content.trim();
178
+ if (trimmed === "") return { rows: [], skipped: [] };
179
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
180
+ try {
181
+ const parsed = JSON.parse(trimmed) as unknown;
182
+ if (Array.isArray(parsed)) {
183
+ const result = parseJsonArray(parsed);
184
+ if (result) return result;
185
+ }
186
+ // A single object is one pool, which is a legitimate one-line file.
187
+ const single = parseJsonArray([parsed]);
188
+ if (single && single.rows.length > 0) return single;
189
+ } catch {
190
+ // Falls through to the line parser: a file that is *nearly* JSON is more
191
+ // likely a text list than a corrupt one.
192
+ }
193
+ }
194
+ const rows: ParsedProxyRow[] = [];
195
+ const skipped: SkippedProxyRow[] = [];;
196
+ trimmed.split(/\r?\n/).forEach((line, index) => {
197
+ const parsed = parseLine(line);
198
+ if (parsed) rows.push(parsed);
199
+ else if (line.trim() !== "" && !line.trim().startsWith("#")) {
200
+ skipped.push({ line: index + 1, reason: "not a proxy URI or host:port row" });
201
+ }
202
+ });
203
+ return { rows, skipped };
204
+ }
205
+
206
+ export { isPoolKind, isPoolStatus };