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