cartethyia 2.5.9 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +15 -42
- package/README.md +11 -10
- package/dist/dashboard/assets/{BannedPage-Brvuj_Eb.js → BannedPage-l6XMnfr9.js} +1 -1
- package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-CqfXtI3y.js → CliToolDetailPage-DwOzAW45.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-GQeS91bg.js → CliToolsPage-BT3Up3QA.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BDRsTXBB.js → CombosPage-6kYIUpzR.js} +1 -1
- package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-zP8AFHX0.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-BmfrNZ14.js → CustomizationPage-C7m-H1Rz.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DoreIgm8.js → LoginPage-CbphLvjg.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C7pDb41R.js → ModelPicker-DVny4S5_.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-BnoPgA7w.js → ProvidersPage-Dfb2896L.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-Bq_TGgdb.js → ProxyPage-BaCIdAUt.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-Day3mCOn.js → SetupPage-D4g6vOGc.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DQxllnB5.js → SortableList-C93i14BD.js} +1 -1
- package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js → StudioPage-SgQpymjm.js} +1 -1
- package/dist/dashboard/assets/{StudioPage-uMExoWu1.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
- package/dist/dashboard/assets/{UsagePage-DXa1oR-4.js → UsagePage-BFME-eba.js} +2 -2
- package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/{app-BIt446yE.js → app-2KhtHCv9.js} +1 -1
- package/dist/dashboard/assets/{app-BIt446yE.js.gz → app-2KhtHCv9.js.gz} +0 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/{badge-CR72dasT.js → badge-CKsF123d.js} +1 -1
- package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
- package/dist/dashboard/assets/{banner-CI9_eSfO.js → banner-8j7uChA9.js} +1 -1
- package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
- package/dist/dashboard/assets/{card-DS7CQEsO.js → card-CdsSlWA3.js} +1 -1
- package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-611pSsaX.js → clipboard-button-uL5aYLfW.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-k37exjNt.js → dialog-C07hr5_G.js} +1 -1
- package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CX7N8ucR.js → drawer-BPO1TcKH.js} +1 -1
- package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
- package/dist/dashboard/assets/{entry-CTP8AyiS.js → entry-Bz_CtDfC.js} +1 -1
- package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-sltk1qUi.js → github-badge-DmQreSDd.js} +1 -1
- package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
- package/dist/dashboard/assets/{index-z3zDOSMM.js → index-Bblu6erR.js} +2 -2
- package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BhY2sSnO.js → inline-C9dCYaBZ.js} +1 -1
- package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
- package/dist/dashboard/assets/{input-V1umFiBk.js → input-CjCnXweJ.js} +1 -1
- package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
- package/dist/dashboard/assets/{layout-BBOYycyS.js → layout-V6y8DAkl.js} +1 -1
- package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-1zzZYM0B.js → list-row-CM1AaG_y.js} +1 -1
- package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
- package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-DkfMR5Qs.js → numbers-BOevJzdz.js} +1 -1
- package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/{select-C6MMiaio.js → select-C980c4cz.js} +1 -1
- package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
- package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/{stack-BHTfXdz_.js → stack-_gfTdLVB.js} +1 -1
- package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
- package/dist/dashboard/assets/{state-mPDRlQkl.js → state-2c5V4zo7.js} +1 -1
- package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DOmJxg7H.js → switch-Dkke7Naf.js} +1 -1
- package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
- package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -51
- package/migrations/README.md +52 -52
- package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
- package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
- package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/package.json +96 -97
- package/scripts/cli/admin.ts +126 -125
- package/scripts/cli/autostart.ts +237 -237
- package/scripts/cli/background.ts +120 -110
- package/scripts/cli/bin/carte.cjs +8 -8
- package/scripts/cli/bin/cartethyia.cjs +167 -167
- package/scripts/cli/console-menu.ts +308 -275
- package/scripts/cli/controller.ts +145 -0
- package/scripts/cli/doctor.ts +1 -1
- package/scripts/cli/env.ts +99 -99
- package/scripts/cli/gateway.ts +264 -261
- package/scripts/{health.ts → cli/health.ts} +4 -4
- package/scripts/cli/instance.ts +234 -224
- package/scripts/cli/main.ts +188 -164
- package/scripts/cli/platform.ts +64 -64
- package/scripts/cli/prompt.ts +275 -107
- package/scripts/cli/services.ts +88 -88
- package/scripts/cli/setup.ts +445 -445
- package/scripts/cli/start.ts +85 -82
- package/scripts/cli/startup-signal.ts +23 -23
- package/scripts/cli/telemetry.ts +70 -0
- package/scripts/cli/tray/index.ts +263 -263
- package/scripts/cli/tray/windows.ps1 +107 -107
- package/scripts/cli/tray.ts +192 -208
- package/scripts/cli/update.ts +39 -39
- package/scripts/cli/version.ts +67 -67
- package/scripts/cli/wordmark.ts +58 -58
- package/src/boot/app.ts +16 -1
- package/src/boot/dependencies.ts +128 -25
- package/src/config/package-paths.ts +24 -0
- package/src/config/resources.ts +137 -0
- package/src/config/runtime.ts +133 -76
- package/src/config/spec.ts +30 -29
- package/src/config/telemetry.ts +0 -25
- package/src/console/api-keys/bucket-spend.ts +14 -17
- package/src/console/api-keys/contracts.ts +410 -410
- package/src/console/api-keys/routes.ts +12 -0
- package/src/console/audit/store.ts +2 -3
- package/src/console/auth/setup-service.ts +8 -1
- package/src/console/backup/routes.ts +7 -0
- package/src/console/backup/service.ts +463 -463
- package/src/console/backup/store.ts +16 -11
- package/src/console/backup/validate.ts +1 -4
- package/src/console/cli-tools/fs-ops.ts +18 -2
- package/src/console/data-management/contracts.ts +1 -8
- package/src/console/data-management/operations.ts +61 -48
- package/src/console/data-management/routes.ts +4 -2
- package/src/console/domain-registration.ts +25 -3
- package/src/console/inferhub/client.ts +139 -0
- package/src/console/inferhub/key-store.ts +129 -0
- package/src/console/inferhub/normalize.ts +347 -0
- package/src/console/inferhub/routes.ts +411 -0
- package/src/console/inferhub/store.ts +109 -0
- package/src/console/inferhub/types.ts +183 -0
- package/src/console/observability/contracts.ts +2 -41
- package/src/console/observability/store.ts +176 -371
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/store.ts +23 -36
- package/src/console/quota/account-quota-shared.ts +52 -52
- package/src/console/quota/account-quota-tenant.ts +16 -24
- package/src/console/quota/refresh.ts +441 -441
- package/src/console/remote-support/service.ts +270 -114
- package/src/console/routing/pools/import-parse.ts +206 -206
- package/src/console/routing/pools/operations.ts +543 -543
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -181
- package/src/console/routing/pools/store.ts +1 -1
- package/src/console/settings/contracts.ts +42 -40
- package/src/console/settings/store.ts +25 -25
- package/src/console/share/share-stats.ts +22 -4
- package/src/console/share/share-usage.ts +30 -4
- package/src/console/shared/cursor.ts +15 -0
- package/src/console/updates/repository-updates.ts +40 -39
- package/src/console/updates/routes.ts +12 -0
- package/src/main.ts +13 -5
- package/src/network/pool/resolver.ts +1104 -1104
- package/src/observability/client-name.ts +22 -0
- package/src/observability/event-log.ts +5 -29
- package/src/observability/event-query.ts +690 -0
- package/src/observability/log-ring.ts +26 -2
- package/src/observability/log-sanitize.ts +118 -0
- package/src/observability/logger.ts +17 -13
- package/src/observability/metrics.ts +35 -0
- package/src/observability/payload-capture.ts +114 -41
- package/src/observability/payload-store.ts +249 -24
- package/src/observability/raw-event-writer.ts +118 -0
- package/src/observability/runtime-metrics.ts +71 -1
- package/src/observability/telemetry-buffer.ts +100 -17
- package/src/observability/telemetry-reconciliation.ts +417 -0
- package/src/persistence/boot.ts +20 -9
- package/src/persistence/db-pg.ts +13 -0
- package/src/persistence/db-sqlite.ts +569 -497
- package/src/persistence/dialect.ts +3 -2
- package/src/persistence/migrate.ts +181 -181
- package/src/persistence/schema.ts +1496 -1406
- package/src/persistence/shared-cache.ts +5 -1
- package/src/persistence/stores/api-key-store.ts +581 -581
- package/src/persistence/stores/telemetry-store.ts +124 -63
- package/src/protocol/reasoning-text.ts +30 -0
- package/src/protocol/registry.ts +2 -0
- package/src/protocol/response/chat.ts +33 -7
- package/src/protocol/response/codex.ts +14 -2
- package/src/protocol/response/messages.ts +5 -2
- package/src/providers/authentication/oauth-flow-store.ts +3 -10
- package/src/providers/bundled-discovery-providers.ts +122 -122
- package/src/providers/default-registry.ts +14 -0
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +43 -7
- package/src/providers/discovery/probing-service.ts +70 -32
- package/src/providers/integrations/antigravity/antigravity.ts +9 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
- package/src/providers/integrations/buddy/capabilities.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
- package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
- package/src/providers/integrations/claude/claude-quota.ts +2 -2
- package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
- package/src/providers/integrations/claude/claude.ts +3 -4
- package/src/providers/integrations/cline/cline.ts +94 -8
- package/src/providers/integrations/codex/codex.ts +61 -37
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
- package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
- package/src/providers/integrations/cursor/cursor-request.ts +11 -0
- package/src/providers/integrations/cursor/cursor.ts +29 -17
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
- package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
- package/src/providers/integrations/github/github.ts +3 -1
- package/src/providers/integrations/grok/grok-quota.ts +279 -279
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
- package/src/providers/integrations/hermes.ts +7 -1
- package/src/providers/integrations/inferhub.ts +201 -73
- package/src/providers/integrations/kilo/kilo.ts +152 -152
- package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
- package/src/providers/integrations/opencode/provider.ts +2 -0
- package/src/providers/integrations/openrouter/provider.ts +129 -129
- package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
- package/src/providers/integrations/trae/trae-headers.ts +17 -48
- package/src/providers/integrations/trae/trae-oauth.ts +4 -26
- package/src/providers/integrations/trae/trae-quota.ts +389 -384
- package/src/providers/integrations/trae/trae-realm.ts +15 -2
- package/src/providers/integrations/trae/trae.ts +22 -24
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/account-health-service.ts +605 -279
- package/src/providers/provider-metadata.ts +12 -1
- package/src/providers/provider-model-definition.ts +373 -373
- package/src/providers/provider-probe.ts +3 -0
- package/src/providers/quota/quota-contracts.ts +3 -7
- package/src/providers/search/search-provider.ts +2 -6
- package/src/security/admission/contracts.ts +10 -0
- package/src/security/admission/in-memory-store.ts +89 -14
- package/src/security/admission/lua.ts +191 -182
- package/src/security/admission/redis-store.ts +30 -0
- package/src/security/admission/service.ts +18 -0
- package/src/security/api-key-auth-cache.ts +96 -0
- package/src/security/api-key-auth.ts +11 -100
- package/src/security/client-router-fingerprint.ts +3 -7
- package/src/security/ip-boundary.ts +7 -3
- package/src/shared/coerce.ts +67 -0
- package/src/shared/hash.ts +6 -0
- package/src/shared/time.ts +7 -0
- package/src/transport/dispatch/attempt-finalize.ts +26 -138
- package/src/transport/dispatch/attempt-loop.ts +103 -81
- package/src/transport/dispatch/capture-policy.ts +78 -0
- package/src/transport/dispatch/proxy-request.ts +4 -2
- package/src/transport/dispatch/streaming/encoder.ts +111 -5
- package/src/transport/dispatch/streaming/stream.ts +19 -2
- package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
- package/src/transport/failure-policy.ts +27 -0
- package/src/transport/guards/abuse-guard.ts +2 -2
- package/src/transport/guards/auth-guard.ts +17 -0
- package/src/transport/guards/body-policy.ts +3 -2
- package/src/transport/guards/concurrency-guard.ts +89 -13
- package/src/transport/guards/early-rejection-capture.ts +43 -3
- package/src/transport/guards/error-lifecycle.ts +10 -2
- package/src/transport/guards/guard-errors.ts +23 -0
- package/src/transport/guards/headroom-guard.ts +41 -7
- package/src/transport/guards/pipeline.ts +24 -0
- package/src/transport/guards/request-context.ts +2 -1
- package/src/transport/routing/admission.ts +51 -9
- package/src/transport/routing/bounded-lru.ts +49 -0
- package/src/transport/routing/candidate-index.ts +88 -0
- package/src/transport/routing/reservations.ts +121 -7
- package/src/transport/routing/round-robin.ts +10 -22
- package/src/transport/routing/route-catalog.ts +26 -13
- package/src/transport/routing/route-model.ts +22 -2
- package/src/transport/routing/router.ts +53 -31
- package/src/transport/surface/chat/encode.ts +2 -3
- package/src/transport/surface/chat/parse.ts +2 -1
- package/src/transport/surface/dialects.ts +11 -3
- package/src/transport/surface/messages/encode.ts +2 -1
- package/src/transport/surface/messages/parse.ts +3 -1
- package/src/transport/surface/messages/stream.ts +2 -6
- package/src/workers/maintenance-lease.ts +200 -0
- package/src/workers/scheduler.ts +101 -1
- package/dist/dashboard/assets/BannedPage-Brvuj_Eb.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-CqfXtI3y.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-GQeS91bg.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BDRsTXBB.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-zP8AFHX0.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-BmfrNZ14.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Dn3M6WZc.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DoreIgm8.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js +0 -1
- package/dist/dashboard/assets/OverviewPage-CvvD1Ugk.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-Clbn1gsW.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-BnoPgA7w.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-Bq_TGgdb.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js +0 -1
- package/dist/dashboard/assets/QuotaPage-D9DHXvom.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js +0 -1
- package/dist/dashboard/assets/SettingsPage-7xV8WkWL.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-Day3mCOn.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DQxllnB5.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DXa1oR-4.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +0 -1
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-ByMGkR3C.js +0 -4
- package/dist/dashboard/assets/entry-ByMGkR3C.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/index-z3zDOSMM.js.gz +0 -0
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/select-C6MMiaio.js.gz +0 -0
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/scripts/build/aot.ts +0 -143
- package/scripts/build/build.ts +0 -24
- package/scripts/build/compile.ts +0 -88
- package/scripts/build/restart.ts +0 -120
- package/scripts/build/start.ts +0 -25
- package/src/observability/event-compaction.ts +0 -317
- package/src/observability/event-day-index.ts +0 -440
package/scripts/cli/prompt.ts
CHANGED
|
@@ -1,107 +1,275 @@
|
|
|
1
|
-
import Enquirer from "enquirer";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* One row in a menu: an action the caller can run, or a rule between groups.
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
1
|
+
import Enquirer from "enquirer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One row in a menu: an action the caller can run, or a rule between groups.
|
|
5
|
+
* Rules are never selectable and are not included in the returned action index.
|
|
6
|
+
*/
|
|
7
|
+
export type MenuRow =
|
|
8
|
+
| { readonly kind: "action"; readonly label: string; readonly hint?: string }
|
|
9
|
+
| { readonly kind: "rule" };
|
|
10
|
+
|
|
11
|
+
/** Options for {@link chooseFromMenu}. Indices count actions, not rules. */
|
|
12
|
+
export interface MenuOptions {
|
|
13
|
+
readonly initialIndex?: number;
|
|
14
|
+
/** Short status retained below the menu after an action completes. */
|
|
15
|
+
readonly status?: { readonly tone: "success" | "error"; readonly text: string };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface MouseEvent {
|
|
19
|
+
readonly button: number;
|
|
20
|
+
readonly column: number;
|
|
21
|
+
readonly row: number;
|
|
22
|
+
readonly release: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const CURSOR_HOME = "\u001b[H";
|
|
26
|
+
const CLEAR_SCREEN = "\u001b[2J";
|
|
27
|
+
const CLEAR_LINE = "\u001b[2K";
|
|
28
|
+
const HIDE_CURSOR = "\u001b[?25l";
|
|
29
|
+
const SHOW_CURSOR = "\u001b[?25h";
|
|
30
|
+
const ENABLE_MOUSE = "\u001b[?1000h\u001b[?1006h";
|
|
31
|
+
const DISABLE_MOUSE = "\u001b[?1006l\u001b[?1000l";
|
|
32
|
+
const RESET = "\u001b[0m";
|
|
33
|
+
const MOUSE_PATTERN = /\u001b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
34
|
+
|
|
35
|
+
function parseMouseEvents(sequence: string): MouseEvent[] {
|
|
36
|
+
return [...sequence.matchAll(MOUSE_PATTERN)].map((match) => ({
|
|
37
|
+
button: Number(match[1]),
|
|
38
|
+
column: Number(match[2]),
|
|
39
|
+
row: Number(match[3]),
|
|
40
|
+
release: match[4] === "m",
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function clamp(value: number, minimum: number, maximum: number): number {
|
|
45
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function plainText(value: string, width: number): string {
|
|
49
|
+
return value.length <= width ? value : width <= 1 ? value.slice(0, width) : `${value.slice(0, width - 1)}…`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* An interactive terminal menu with keyboard and xterm SGR mouse support.
|
|
54
|
+
*
|
|
55
|
+
* A single click focuses on press and activates on release over the same row,
|
|
56
|
+
* matching ordinary desktop buttons. Terminals without mouse reporting retain
|
|
57
|
+
* the complete arrow-key/Enter interface. stdin is restored even on Ctrl-C or
|
|
58
|
+
* Esc so the caller can drain the gateway normally.
|
|
59
|
+
*/
|
|
60
|
+
export async function chooseFromMenu(
|
|
61
|
+
header: string | readonly string[],
|
|
62
|
+
rows: readonly MenuRow[],
|
|
63
|
+
options: MenuOptions = {},
|
|
64
|
+
): Promise<number> {
|
|
65
|
+
const input = process.stdin;
|
|
66
|
+
const output = process.stdout;
|
|
67
|
+
if (!input.isTTY) return -1;
|
|
68
|
+
|
|
69
|
+
const actionRows = rows.flatMap((row, rowIndex) => row.kind === "action" ? [rowIndex] : []);
|
|
70
|
+
if (actionRows.length === 0) return -1;
|
|
71
|
+
let selected = clamp(options.initialIndex ?? 0, 0, actionRows.length - 1);
|
|
72
|
+
let pressedAction: number | null = null;
|
|
73
|
+
let settled = false;
|
|
74
|
+
let frameTop = 0;
|
|
75
|
+
let menuWidth = 0;
|
|
76
|
+
const color = output.isTTY === true && process.env.NO_COLOR === undefined && process.env.TERM !== "dumb";
|
|
77
|
+
const paint = (code: string, value: string): string => color ? `\u001b[${code}m${value}${RESET}` : value;
|
|
78
|
+
const muted = (value: string): string => paint("2;38;2;130;148;165", value);
|
|
79
|
+
const accent = (value: string): string => paint("38;2;145;200;240", value);
|
|
80
|
+
const selectedPaint = (value: string): string => paint("1;38;2;225;242;255;48;2;43;75;106", value);
|
|
81
|
+
const status = options.status === undefined ? undefined : {
|
|
82
|
+
...options.status,
|
|
83
|
+
text: options.status.text.replace(/\s+/g, " ").trim(),
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const render = (): void => {
|
|
87
|
+
const columns = Math.max(32, output.columns || 80);
|
|
88
|
+
const labelWidth = Math.max(...rows.flatMap((row) => row.kind === "action" ? [row.label.length] : [0]));
|
|
89
|
+
const contentWidth = Math.max(...rows.map((row) => row.kind === "rule"
|
|
90
|
+
? 24
|
|
91
|
+
: labelWidth + (row.hint?.length ?? 0) + 7));
|
|
92
|
+
const innerWidth = Math.min(contentWidth, columns - 4);
|
|
93
|
+
menuWidth = innerWidth + 2;
|
|
94
|
+
const lines = [accent(`╭─ Actions ${"─".repeat(Math.max(0, innerWidth - 10))}╮`)];
|
|
95
|
+
|
|
96
|
+
rows.forEach((row, rowIndex) => {
|
|
97
|
+
if (row.kind === "rule") {
|
|
98
|
+
lines.push(muted(`│ ${"─".repeat(Math.max(0, innerWidth - 4))} │`));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const hintWidth = Math.max(0, innerWidth - labelWidth - 7);
|
|
102
|
+
const hint = plainText(row.hint ?? "", hintWidth).padEnd(hintWidth);
|
|
103
|
+
const isSelected = actionRows[selected] === rowIndex;
|
|
104
|
+
const pointer = isSelected ? "❯" : " ";
|
|
105
|
+
const text = ` ${pointer} ${row.label.padEnd(labelWidth)} ${hint} `;
|
|
106
|
+
const content = plainText(text, innerWidth).padEnd(innerWidth);
|
|
107
|
+
lines.push(isSelected ? `│${selectedPaint(content)}│` : `│${content}│`);
|
|
108
|
+
});
|
|
109
|
+
lines.push(accent(`╰${"─".repeat(innerWidth)}╯`));
|
|
110
|
+
if (status !== undefined) {
|
|
111
|
+
const prefix = status.tone === "success" ? "✓ " : "✗ ";
|
|
112
|
+
const text = plainText(`${prefix}${status.text}`, columns - 2);
|
|
113
|
+
lines.push(paint(status.tone === "success" ? "38;2;135;205;160" : "38;2;240;145;145", text));
|
|
114
|
+
}
|
|
115
|
+
lines.push(muted(" ↑ ↓ navigate · Enter select · Click to open · Esc cancel"));
|
|
116
|
+
|
|
117
|
+
const cursor = `\u001b[${frameTop};1H`;
|
|
118
|
+
output.write(`${cursor}${lines.map((line) => `${CLEAR_LINE}${line}`).join("\n")}`);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const actionAtScreenPosition = (column: number, screenRow: number): number | null => {
|
|
122
|
+
if (column < 1 || column > menuWidth) return null;
|
|
123
|
+
const rowIndex = screenRow - frameTop - 1;
|
|
124
|
+
if (rowIndex < 0 || rowIndex >= rows.length) return null;
|
|
125
|
+
const actionIndex = actionRows.indexOf(rowIndex);
|
|
126
|
+
return actionIndex < 0 ? null : actionIndex;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const headerLines = typeof header === "string" ? [header] : header;
|
|
130
|
+
output.write(`${CLEAR_SCREEN}${CURSOR_HOME}`);
|
|
131
|
+
for (const line of headerLines) output.write(`${line}\n`);
|
|
132
|
+
output.write("\n");
|
|
133
|
+
frameTop = headerLines.length + 2;
|
|
134
|
+
output.write(HIDE_CURSOR + ENABLE_MOUSE);
|
|
135
|
+
render();
|
|
136
|
+
|
|
137
|
+
const wasRaw = input.isRaw;
|
|
138
|
+
input.setEncoding("utf8");
|
|
139
|
+
if (input.isTTY) input.setRawMode(true);
|
|
140
|
+
input.resume();
|
|
141
|
+
|
|
142
|
+
const onKeypress = (_sequence: string, key: { readonly name: string | undefined }): void => {
|
|
143
|
+
if (key.name === "\u0003" || key.name === "escape") {
|
|
144
|
+
finish(-1);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (key.name === "up" || key.name === "k") move(-1);
|
|
148
|
+
else if (key.name === "down" || key.name === "j") move(1);
|
|
149
|
+
else if (key.name === "home") { selected = 0; render(); }
|
|
150
|
+
else if (key.name === "end") { selected = actionRows.length - 1; render(); }
|
|
151
|
+
else if (key.name === "return" || key.name === "enter" || key.name === "space") finish(selected);
|
|
152
|
+
};
|
|
153
|
+
const onMouseSequence = (sequence: string): void => {
|
|
154
|
+
for (const mouse of parseMouseEvents(sequence)) {
|
|
155
|
+
if ((mouse.button & 32) !== 0) continue;
|
|
156
|
+
if ((mouse.button & 64) !== 0) {
|
|
157
|
+
move((mouse.button & 1) === 0 ? -1 : 1);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if ((mouse.button & 3) !== 0) continue;
|
|
161
|
+
const actionIndex = actionAtScreenPosition(mouse.column, mouse.row);
|
|
162
|
+
if (mouse.release) {
|
|
163
|
+
if (pressedAction !== null && pressedAction === actionIndex) finish(actionIndex);
|
|
164
|
+
pressedAction = null;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
pressedAction = actionIndex;
|
|
168
|
+
if (actionIndex !== null && actionIndex !== selected) {
|
|
169
|
+
selected = actionIndex;
|
|
170
|
+
render();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
let resolveMenu: (actionIndex: number) => void = () => undefined;
|
|
175
|
+
const finish = (actionIndex: number): void => {
|
|
176
|
+
if (settled) return;
|
|
177
|
+
settled = true;
|
|
178
|
+
resolveMenu(actionIndex);
|
|
179
|
+
};
|
|
180
|
+
const move = (delta: number): void => {
|
|
181
|
+
selected = (selected + delta + actionRows.length) % actionRows.length;
|
|
182
|
+
pressedAction = null;
|
|
183
|
+
render();
|
|
184
|
+
};
|
|
185
|
+
// Keypress parsing also consumes mouse sequences, so mouse reporting owns
|
|
186
|
+
// the data stream while this menu is active. Keyboard escape sequences are
|
|
187
|
+
// decoded here as well instead of relying on two competing data listeners.
|
|
188
|
+
let pendingInput = "";
|
|
189
|
+
const onRawData = (chunk: Buffer | string): void => {
|
|
190
|
+
pendingInput += String(chunk);
|
|
191
|
+
const names: Record<string, string> = {
|
|
192
|
+
"\u001b[A": "up", "\u001b[B": "down", "\u001b[H": "home", "\u001b[F": "end",
|
|
193
|
+
"\u001bOA": "up", "\u001bOB": "down", "\u001bOH": "home", "\u001bOF": "end",
|
|
194
|
+
};
|
|
195
|
+
while (pendingInput.length > 0 && !settled) {
|
|
196
|
+
if (pendingInput.startsWith("\u001b[<")) {
|
|
197
|
+
const mouse = /^\u001b\[<\d+;\d+;\d+[Mm]/.exec(pendingInput);
|
|
198
|
+
if (mouse === null) {
|
|
199
|
+
if (pendingInput.length < 32) return;
|
|
200
|
+
pendingInput = pendingInput.slice(1);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
onMouseSequence(mouse[0]);
|
|
204
|
+
pendingInput = pendingInput.slice(mouse[0].length);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (pendingInput.startsWith("\u001b[") || pendingInput.startsWith("\u001bO")) {
|
|
208
|
+
const sequence = Object.keys(names).find((candidate) => pendingInput.startsWith(candidate));
|
|
209
|
+
if (sequence === undefined) {
|
|
210
|
+
if (pendingInput.length < 3) return;
|
|
211
|
+
pendingInput = pendingInput.slice(1);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
onKeypress(sequence, { name: names[sequence] });
|
|
215
|
+
pendingInput = pendingInput.slice(sequence.length);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (pendingInput.startsWith("\r\n")) pendingInput = pendingInput.slice(1);
|
|
219
|
+
const character = pendingInput[0]!;
|
|
220
|
+
const name = character === "\r" || character === "\n" ? "return" : character === "\u001b" ? "escape" : character;
|
|
221
|
+
onKeypress(character, { name });
|
|
222
|
+
pendingInput = pendingInput.slice(1);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
input.on("data", onRawData);
|
|
228
|
+
return await new Promise<number>((resolve) => {
|
|
229
|
+
resolveMenu = resolve;
|
|
230
|
+
});
|
|
231
|
+
} finally {
|
|
232
|
+
input.removeListener("data", onRawData);
|
|
233
|
+
if (input.isTTY) input.setRawMode(wasRaw);
|
|
234
|
+
// Always release the resumed stdin. A listener-less but flowing TTY keeps
|
|
235
|
+
// the CLI alive after Hide to Tray or another terminal action completes.
|
|
236
|
+
// The next menu resumes it explicitly, so preserving a flowing state here
|
|
237
|
+
// provides no benefit and leaves the terminal looking stuck.
|
|
238
|
+
input.pause();
|
|
239
|
+
if (settled) output.write(`\n${DISABLE_MOUSE}${SHOW_CURSOR}${RESET}`);
|
|
240
|
+
else output.write(`${CLEAR_SCREEN}${CURSOR_HOME}${DISABLE_MOUSE}${SHOW_CURSOR}${RESET}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Asks the operator to type an exact word before a destructive action runs. */
|
|
245
|
+
export async function confirmByTyping(word: string, message: string): Promise<boolean> {
|
|
246
|
+
if (!process.stdin.isTTY) return false;
|
|
247
|
+
try {
|
|
248
|
+
const answer = await Enquirer.prompt<{ confirmation: string }>({
|
|
249
|
+
type: "input",
|
|
250
|
+
name: "confirmation",
|
|
251
|
+
message: `${message} (type ${word})`,
|
|
252
|
+
validate: (value: string) =>
|
|
253
|
+
value.trim() === word ? true : `type ${word} exactly to confirm, or Ctrl-C to cancel`,
|
|
254
|
+
});
|
|
255
|
+
return answer.confirmation.trim() === word;
|
|
256
|
+
} catch {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** A yes/no gate for the step before the typed confirmation. */
|
|
262
|
+
export async function confirmStep(message: string): Promise<boolean> {
|
|
263
|
+
if (!process.stdin.isTTY) return false;
|
|
264
|
+
try {
|
|
265
|
+
const answer = await Enquirer.prompt<{ proceed: boolean }>({
|
|
266
|
+
type: "confirm",
|
|
267
|
+
name: "proceed",
|
|
268
|
+
message,
|
|
269
|
+
initial: false,
|
|
270
|
+
});
|
|
271
|
+
return answer.proceed;
|
|
272
|
+
} catch {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
package/scripts/cli/services.ts
CHANGED
|
@@ -1,88 +1,88 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TCP reachability checks for the services setup and doctor ask about.
|
|
3
|
-
*
|
|
4
|
-
* These are extracted from the env helpers so the dotenv reader and the probe
|
|
5
|
-
* stay independent: doctor can be imported without pulling in `.env` parsing.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface ProbeResult {
|
|
9
|
-
success: boolean;
|
|
10
|
-
error?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Opens a TCP connection to `host:port` and resolves once it connects, errors,
|
|
15
|
-
* or the timeout elapses. The socket is always closed so a probe never leaves a
|
|
16
|
-
* half-open connection behind.
|
|
17
|
-
*/
|
|
18
|
-
export async function probeTcpService(
|
|
19
|
-
host: string,
|
|
20
|
-
port: number,
|
|
21
|
-
timeoutMs: number,
|
|
22
|
-
): Promise<ProbeResult> {
|
|
23
|
-
const { promise, resolve } = Promise.withResolvers<ProbeResult>();
|
|
24
|
-
|
|
25
|
-
const timeoutHandle = setTimeout(() => {
|
|
26
|
-
resolve({ success: false, error: "timeout" });
|
|
27
|
-
}, timeoutMs);
|
|
28
|
-
|
|
29
|
-
Bun.connect({
|
|
30
|
-
hostname: host,
|
|
31
|
-
port: port,
|
|
32
|
-
socket: {
|
|
33
|
-
open: () => {
|
|
34
|
-
clearTimeout(timeoutHandle);
|
|
35
|
-
resolve({ success: true });
|
|
36
|
-
},
|
|
37
|
-
data: () => {},
|
|
38
|
-
error: (err) => {
|
|
39
|
-
clearTimeout(timeoutHandle);
|
|
40
|
-
resolve({ success: false, error: String(err) });
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
.then((socket) => {
|
|
45
|
-
socket.end();
|
|
46
|
-
})
|
|
47
|
-
.catch((err) => {
|
|
48
|
-
clearTimeout(timeoutHandle);
|
|
49
|
-
resolve({ success: false, error: String(err) });
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return promise;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Local PostgreSQL ports worth probing before asking for a URL. 5432 is the
|
|
57
|
-
* server default (Laragon, Homebrew, systemd); 5433 is the usual second
|
|
58
|
-
* instance when 5432 is already taken.
|
|
59
|
-
*/
|
|
60
|
-
export const LOCAL_POSTGRES_CANDIDATES = [
|
|
61
|
-
{ host: "127.0.0.1", port: 5432 },
|
|
62
|
-
{ host: "127.0.0.1", port: 5433 },
|
|
63
|
-
] as const;
|
|
64
|
-
|
|
65
|
-
/** First local PostgreSQL that accepts a TCP connection, or null. */
|
|
66
|
-
export async function detectLocalPostgres(
|
|
67
|
-
timeoutMs: number,
|
|
68
|
-
): Promise<{ host: string; port: number } | null> {
|
|
69
|
-
for (const candidate of LOCAL_POSTGRES_CANDIDATES) {
|
|
70
|
-
const probe = await probeTcpService(candidate.host, candidate.port, timeoutMs);
|
|
71
|
-
if (probe.success) return candidate;
|
|
72
|
-
}
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Parses a service URL and requires an explicit host and port.
|
|
78
|
-
* Returns null if the URL is invalid or incomplete.
|
|
79
|
-
*/
|
|
80
|
-
export function parseServiceUrl(url: string): { host: string; port: number } | null {
|
|
81
|
-
try {
|
|
82
|
-
const urlObj = new URL(url);
|
|
83
|
-
if (!urlObj.hostname || !urlObj.port) return null;
|
|
84
|
-
return { host: urlObj.hostname, port: Number(urlObj.port) };
|
|
85
|
-
} catch {
|
|
86
|
-
return null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* TCP reachability checks for the services setup and doctor ask about.
|
|
3
|
+
*
|
|
4
|
+
* These are extracted from the env helpers so the dotenv reader and the probe
|
|
5
|
+
* stay independent: doctor can be imported without pulling in `.env` parsing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ProbeResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Opens a TCP connection to `host:port` and resolves once it connects, errors,
|
|
15
|
+
* or the timeout elapses. The socket is always closed so a probe never leaves a
|
|
16
|
+
* half-open connection behind.
|
|
17
|
+
*/
|
|
18
|
+
export async function probeTcpService(
|
|
19
|
+
host: string,
|
|
20
|
+
port: number,
|
|
21
|
+
timeoutMs: number,
|
|
22
|
+
): Promise<ProbeResult> {
|
|
23
|
+
const { promise, resolve } = Promise.withResolvers<ProbeResult>();
|
|
24
|
+
|
|
25
|
+
const timeoutHandle = setTimeout(() => {
|
|
26
|
+
resolve({ success: false, error: "timeout" });
|
|
27
|
+
}, timeoutMs);
|
|
28
|
+
|
|
29
|
+
Bun.connect({
|
|
30
|
+
hostname: host,
|
|
31
|
+
port: port,
|
|
32
|
+
socket: {
|
|
33
|
+
open: () => {
|
|
34
|
+
clearTimeout(timeoutHandle);
|
|
35
|
+
resolve({ success: true });
|
|
36
|
+
},
|
|
37
|
+
data: () => {},
|
|
38
|
+
error: (err) => {
|
|
39
|
+
clearTimeout(timeoutHandle);
|
|
40
|
+
resolve({ success: false, error: String(err) });
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.then((socket) => {
|
|
45
|
+
socket.end();
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
clearTimeout(timeoutHandle);
|
|
49
|
+
resolve({ success: false, error: String(err) });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return promise;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Local PostgreSQL ports worth probing before asking for a URL. 5432 is the
|
|
57
|
+
* server default (Laragon, Homebrew, systemd); 5433 is the usual second
|
|
58
|
+
* instance when 5432 is already taken.
|
|
59
|
+
*/
|
|
60
|
+
export const LOCAL_POSTGRES_CANDIDATES = [
|
|
61
|
+
{ host: "127.0.0.1", port: 5432 },
|
|
62
|
+
{ host: "127.0.0.1", port: 5433 },
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
/** First local PostgreSQL that accepts a TCP connection, or null. */
|
|
66
|
+
export async function detectLocalPostgres(
|
|
67
|
+
timeoutMs: number,
|
|
68
|
+
): Promise<{ host: string; port: number } | null> {
|
|
69
|
+
for (const candidate of LOCAL_POSTGRES_CANDIDATES) {
|
|
70
|
+
const probe = await probeTcpService(candidate.host, candidate.port, timeoutMs);
|
|
71
|
+
if (probe.success) return candidate;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parses a service URL and requires an explicit host and port.
|
|
78
|
+
* Returns null if the URL is invalid or incomplete.
|
|
79
|
+
*/
|
|
80
|
+
export function parseServiceUrl(url: string): { host: string; port: number } | null {
|
|
81
|
+
try {
|
|
82
|
+
const urlObj = new URL(url);
|
|
83
|
+
if (!urlObj.hostname || !urlObj.port) return null;
|
|
84
|
+
return { host: urlObj.hostname, port: Number(urlObj.port) };
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|