cartethyia 2.4.5 → 2.5.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 +4 -1
- package/README.md +63 -13
- package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js +1 -0
- package/dist/dashboard/assets/BannedPage-9igfctfZ.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-ZRr2K2sF.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-ZRr2K2sF.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-D3xqZbN-.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-D3xqZbN-.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js +1 -0
- package/dist/dashboard/assets/CombosPage-CVnQPBBz.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-u6W1UXSc.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-CTTpCb25.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-CTTpCb25.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-C5XpWlvy.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-DHoB3Agn.js} +1 -1
- package/dist/dashboard/assets/LoginPage-DHoB3Agn.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js +1 -0
- package/dist/dashboard/assets/ModelPicker-oQ-SDXp8.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BrY4SgIt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-D9VSt7T_.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js +1 -0
- package/dist/dashboard/assets/ProviderIcon-CmfzxpVM.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-Df77V5I5.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js +7 -0
- package/dist/dashboard/assets/ProxyPage-CDfxP7AC.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js +1 -0
- package/dist/dashboard/assets/QuotaPage-CY4o2t4p.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-CvjZxHE4.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-CvjZxHE4.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js +1 -0
- package/dist/dashboard/assets/SetupPage-D3nza7LH.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-Bol1ASmI.js → SortableList-Dtt12WT1.js} +1 -1
- package/dist/dashboard/assets/SortableList-Dtt12WT1.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.js +31 -0
- package/dist/dashboard/assets/StudioPage-CbK3hXvU.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js +56 -0
- package/dist/dashboard/assets/UsagePage-BQlWbWg2.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BmDbsior.js +1 -0
- package/dist/dashboard/assets/api-keys-BmDbsior.js.gz +0 -0
- package/dist/dashboard/assets/app-BbpSarVV.css +2 -0
- package/dist/dashboard/assets/app-BbpSarVV.css.gz +0 -0
- package/dist/dashboard/assets/{app-BDJWMThu.js → app-C-Y4oZT0.js} +3 -3
- package/dist/dashboard/assets/app-C-Y4oZT0.js.gz +0 -0
- package/dist/dashboard/assets/arrow-left-CpBxkoxI.js.gz +0 -0
- package/dist/dashboard/assets/arrow-right-Dl5yKnp8.js.gz +0 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js +1 -0
- package/dist/dashboard/assets/assert-BHfaei-X.js.gz +0 -0
- package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-DpXT4Ixt.js} +1 -1
- package/dist/dashboard/assets/badge-DpXT4Ixt.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-CSAAWLJv.js} +1 -1
- package/dist/dashboard/assets/banner-CSAAWLJv.js.gz +0 -0
- package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
- package/dist/dashboard/assets/{card-BTdbRFFg.js → card-DEhRXXw4.js} +1 -1
- package/dist/dashboard/assets/card-DEhRXXw4.js.gz +0 -0
- package/dist/dashboard/assets/check-D69FzdvC.js.gz +0 -0
- package/dist/dashboard/assets/chevron-right-BXIy5l3h.js.gz +0 -0
- package/dist/dashboard/assets/circle-alert-CntYHV5v.js.gz +0 -0
- package/dist/dashboard/assets/circle-check-BU3v_mFW.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js +1 -0
- package/dist/dashboard/assets/cli-tools-B0wBq-RL.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js +1 -0
- package/dist/dashboard/assets/clipboard-button-DOo6EU8O.js.gz +0 -0
- package/dist/dashboard/assets/cog-CdWhsajs.js.gz +0 -0
- package/dist/dashboard/assets/copy-ngmQ9zcZ.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-iocKA3jr.js → dialog-BvQeBMfP.js} +1 -1
- package/dist/dashboard/assets/dialog-BvQeBMfP.js.gz +0 -0
- package/dist/dashboard/assets/dialog-actions-B1axpEg7.js.gz +0 -0
- package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
- package/dist/dashboard/assets/download-dRFo5_Wu.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-CDKOTZdY.js → drawer-CMwPXwQB.js} +1 -1
- package/dist/dashboard/assets/drawer-CMwPXwQB.js.gz +0 -0
- package/dist/dashboard/assets/entry-D4O2229X.js +1 -0
- package/dist/dashboard/assets/entry-D4O2229X.js.gz +0 -0
- package/dist/dashboard/assets/entry-DKvr5Q9K.js +1 -0
- package/dist/dashboard/assets/entry-DKvr5Q9K.js.gz +0 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js +4 -0
- package/dist/dashboard/assets/entry-FQCejoDY.js.gz +0 -0
- package/dist/dashboard/assets/eye-C_hawfH4.js.gz +0 -0
- package/dist/dashboard/assets/eye-off-Bk5g2luF.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/gauge-BbIMy7MB.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-zliLvBwr.js +1 -0
- package/dist/dashboard/assets/github-badge-zliLvBwr.js.gz +0 -0
- package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
- package/dist/dashboard/assets/{index-C4hXfAMs.js → index-CXm3FIyt.js} +2 -2
- package/dist/dashboard/assets/index-CXm3FIyt.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1IlZZ8H.js} +1 -1
- package/dist/dashboard/assets/inline-D1IlZZ8H.js.gz +0 -0
- package/dist/dashboard/assets/{input-Bn4_kNav.js → input-BpVZdrbc.js} +1 -1
- package/dist/dashboard/assets/input-BpVZdrbc.js.gz +0 -0
- package/dist/dashboard/assets/jsx-runtime-CXUMngI_.js.gz +0 -0
- package/dist/dashboard/assets/key-round-UkavsGxp.js.gz +0 -0
- package/dist/dashboard/assets/layers-2-glBbjl.js.gz +0 -0
- package/dist/dashboard/assets/{layout-Bv-NxqZ5.js → layout-BioLW7gN.js} +1 -1
- package/dist/dashboard/assets/layout-BioLW7gN.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-CwVcGAIc.js} +1 -1
- package/dist/dashboard/assets/list-row-CwVcGAIc.js.gz +0 -0
- package/dist/dashboard/assets/{live-YlifPYox.js → live-yfEJFF9T.js} +1 -1
- package/dist/dashboard/assets/live-yfEJFF9T.js.gz +0 -0
- package/dist/dashboard/assets/log-in-BmRjy60c.js.gz +0 -0
- package/dist/dashboard/assets/message-circle-CCxGwRvv.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js +1 -0
- package/dist/dashboard/assets/model-bans-CT-TxSNX.js.gz +0 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js +1 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
- package/dist/dashboard/assets/numbers-CFL4_Dqc.js.gz +0 -0
- package/dist/dashboard/assets/pencil-768ysRAr.js.gz +0 -0
- package/dist/dashboard/assets/plus-DIdX_g-x.js.gz +0 -0
- package/dist/dashboard/assets/power-off-Yjw9GMJh.js +1 -0
- package/dist/dashboard/assets/power-off-Yjw9GMJh.js.gz +0 -0
- package/dist/dashboard/assets/privacy-mode-GM8bej85.js +1 -0
- package/dist/dashboard/assets/privacy-mode-GM8bej85.js.gz +0 -0
- package/dist/dashboard/assets/providers/agentrouter.svg.gz +0 -0
- package/dist/dashboard/assets/providers/aihubmix.svg.gz +0 -0
- package/dist/dashboard/assets/providers/alibaba-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/providers/alibaba.svg.gz +0 -0
- package/dist/dashboard/assets/providers/antigravity.svg.gz +0 -0
- package/dist/dashboard/assets/providers/bai.svg.gz +0 -0
- package/dist/dashboard/assets/providers/blackbox.svg.gz +0 -0
- package/dist/dashboard/assets/providers/claude-code.svg.gz +0 -0
- package/dist/dashboard/assets/providers/cursor.webp +0 -0
- package/dist/dashboard/assets/providers/exa.svg.gz +0 -0
- package/dist/dashboard/assets/providers/gmi-cloud.svg.gz +0 -0
- package/dist/dashboard/assets/providers/inferhub.svg.gz +0 -0
- package/dist/dashboard/assets/providers/muse.svg.gz +0 -0
- package/dist/dashboard/assets/providers/nous-research.svg.gz +0 -0
- package/dist/dashboard/assets/providers/openai-light.svg.gz +0 -0
- package/dist/dashboard/assets/providers/parallel.svg.gz +0 -0
- package/dist/dashboard/assets/providers/tokenharbor.svg.gz +0 -0
- package/dist/dashboard/assets/providers/trae.webp +0 -0
- package/dist/dashboard/assets/providers/zai-coding-plan.svg.gz +0 -0
- package/dist/dashboard/assets/{query-keys-ROf-g1Wi.js → query-keys-D7wX2vM5.js} +1 -1
- package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
- package/dist/dashboard/assets/quota-DZGyp0N5.js.gz +0 -0
- package/dist/dashboard/assets/refresh-cw-BqJFk4sU.js.gz +0 -0
- package/dist/dashboard/assets/rotate-ccw-CufcqITi.js.gz +0 -0
- package/dist/dashboard/assets/route-CNSNj9Zm.js.gz +0 -0
- package/dist/dashboard/assets/routing-Cs6jl49L.js +1 -0
- package/dist/dashboard/assets/routing-Cs6jl49L.js.gz +0 -0
- package/dist/dashboard/assets/scaling-nWE4Cx8f.js.gz +0 -0
- package/dist/dashboard/assets/search-JOKus9rb.js.gz +0 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js +41 -0
- package/dist/dashboard/assets/select-C0Rer3gF.js.gz +0 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js +1 -0
- package/dist/dashboard/assets/settings-CfcTcOyU.js.gz +0 -0
- package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-C9_uUjaA.js} +1 -1
- package/dist/dashboard/assets/stack-C9_uUjaA.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHKdg-pm.js → state-DA0B95UQ.js} +1 -1
- package/dist/dashboard/assets/state-DA0B95UQ.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-Cittxy-x.js} +1 -1
- package/dist/dashboard/assets/switch-Cittxy-x.js.gz +0 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js +1 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
- package/dist/dashboard/assets/toast-CNt_jzO4.js +2 -0
- package/dist/dashboard/assets/toast-CNt_jzO4.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/trash-Cppn5c3R.js.gz +0 -0
- package/dist/dashboard/assets/use-clipboard-B0Sy03fE.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/use-reduced-motion-C4B0R6Sz.js.gz +0 -0
- package/dist/dashboard/assets/use-timeout-BwcBBRrT.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-DVmwOKU-.js → useMutation-BQKMcJ1H.js} +1 -1
- package/dist/dashboard/assets/useMutation-BQKMcJ1H.js.gz +0 -0
- package/dist/dashboard/assets/wrench-B6E1oEuY.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 +122 -0
- package/migrations/full-postgres/0013_add_providers_tenant_id_index.sql +22 -0
- package/migrations/full-postgres/0014_add_provider_accounts_tenant_id_index.sql +21 -0
- package/migrations/full-postgres/0015_add_payload_stages.sql +18 -0
- package/migrations/full-postgres/0016_add_share_link_slug.sql +20 -0
- package/migrations/full-postgres/0017_add_models_pro_only.sql +18 -0
- package/migrations/full-postgres/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/full-postgres/0019_add_models_free_tier.sql +15 -0
- package/migrations/lite-sqlite3/0000_baseline.sql +4 -2
- package/migrations/lite-sqlite3/0013_add_providers_tenant_id_index.sql +4 -0
- package/migrations/lite-sqlite3/0014_add_provider_accounts_tenant_id_index.sql +4 -0
- package/migrations/lite-sqlite3/0015_add_payload_stages.sql +18 -0
- package/migrations/lite-sqlite3/0016_add_share_link_slug.sql +20 -0
- package/migrations/lite-sqlite3/0017_add_models_pro_only.sql +18 -0
- package/migrations/lite-sqlite3/0018_add_accounts_quota_plan.sql +18 -0
- package/migrations/lite-sqlite3/0019_add_models_free_tier.sql +15 -0
- package/package.json +26 -30
- package/scripts/cli/{carte-launcher.cjs → bin/carte.cjs} +8 -8
- package/scripts/cli/{cli-launcher.cjs → bin/cartethyia.cjs} +167 -134
- package/scripts/cli/command/admin.ts +65 -0
- package/scripts/cli/command/console-menu.ts +246 -0
- package/scripts/cli/{doctor.ts → command/doctor.ts} +257 -258
- package/scripts/cli/{setup.ts → command/setup.ts} +8 -12
- package/scripts/cli/command/start.ts +77 -0
- package/scripts/cli/command/tray.ts +165 -0
- package/scripts/cli/command/update.ts +23 -0
- package/scripts/cli/{runtime-state.ts → gateway/drain-token.ts} +78 -78
- package/scripts/cli/gateway/instance-lock.ts +105 -0
- package/scripts/cli/gateway/spawn.ts +173 -0
- package/scripts/cli/gateway/stop.ts +51 -0
- package/scripts/cli/main.ts +94 -0
- package/scripts/cli/support/bun.ts +27 -0
- package/scripts/cli/support/env.ts +99 -0
- package/scripts/cli/support/platform.ts +64 -0
- package/scripts/cli/support/services.ts +88 -0
- package/scripts/cli/support/startup-signal.ts +23 -0
- package/scripts/cli/{version.ts → support/version.ts} +1 -1
- package/scripts/cli/tray/hidden-window.vbs +28 -0
- package/scripts/cli/{tray.ts → tray/index.ts} +3 -3
- package/scripts/cli/{tray.ps1 → tray/windows.ps1} +107 -98
- package/scripts/cli/ui/background.ts +110 -0
- package/src/boot/app.ts +23 -7
- package/src/boot/dependencies.ts +55 -13
- package/src/boot/elysia-typebox-bridge.ts +37 -0
- package/src/boot/lifecycle.ts +2 -0
- package/src/config/cooldowns.ts +138 -138
- package/src/config/providers.ts +31 -5
- package/src/config/runtime.ts +19 -3
- package/src/config/spec.ts +20 -0
- package/src/console/api-keys/contracts.ts +414 -391
- package/src/console/api-keys/routes.ts +880 -870
- package/src/console/auth/account-reset-store.ts +14 -0
- package/src/console/auth/account-reset.ts +3 -2
- package/src/console/auth/setup-service.ts +6 -3
- package/src/console/backup/routes.ts +8 -0
- package/src/console/cli-tools/contracts.ts +6 -6
- package/src/console/console-router.ts +5 -3
- package/src/console/dashboard-assets.ts +81 -11
- package/src/console/domain-registration.ts +9 -2
- package/src/console/observability/contracts.ts +475 -423
- package/src/console/observability/routes.ts +31 -6
- package/src/console/observability/store.ts +1015 -912
- package/src/console/providers/catalog/contracts.ts +31 -0
- package/src/console/providers/catalog/provider-operations.ts +8 -0
- package/src/console/providers/catalog/public-model-store.ts +91 -60
- package/src/console/providers/catalog/store.ts +1532 -1521
- package/src/console/providers/oauth/callback-listener.ts +18 -3
- package/src/console/providers/oauth/routes.ts +38 -15
- package/src/console/quota/account-quota-tenant.ts +9 -6
- package/src/console/quota/refresh.ts +19 -0
- package/src/console/response-compression.ts +69 -2
- package/src/console/routing/pools/probe-verdict.ts +72 -0
- package/src/console/share/share-router.ts +7 -4
- package/src/console/share/share-stats.ts +12 -5
- package/src/console/shared/read-through-cache.ts +133 -7
- package/src/console/updates/repository-updates.ts +39 -34
- package/src/console/updates/routes.ts +162 -4
- package/src/main.ts +3 -0
- package/src/network/http2-bidi.ts +85 -0
- package/src/network/outbound-fetch.ts +87 -24
- package/src/network/pool/agent.ts +478 -460
- package/src/network/pool/resolver.ts +1104 -544
- package/src/network/pool-health-machine.ts +12 -5
- package/src/observability/event-log.ts +29 -8
- package/src/observability/metrics.ts +40 -13
- package/src/observability/payload-capture.ts +344 -324
- package/src/observability/runtime-metrics.ts +7 -0
- package/src/persistence/db-pg.ts +9 -3
- package/src/persistence/db-sqlite.ts +497 -495
- package/src/persistence/health-event-flush.ts +137 -0
- package/src/persistence/migrate.ts +181 -129
- package/src/persistence/schema.ts +1360 -1257
- package/src/persistence/settings-revision.ts +25 -0
- package/src/persistence/shared-cache.ts +217 -0
- package/src/persistence/stores/share-store.ts +556 -528
- package/src/persistence/usage-flush.ts +61 -1
- package/src/protocol/canonical-model.ts +23 -1
- package/src/protocol/response/chat.ts +143 -10
- package/src/protocol/response/codex.ts +9 -75
- package/src/protocol/response/omp-tool-call-markup.ts +221 -0
- package/src/protocol/tool-identity.ts +43 -9
- package/src/providers/authentication/jwt-validator.ts +130 -187
- package/src/providers/authentication/oauth-flow-store.ts +16 -9
- package/src/providers/default-registry.ts +177 -1
- package/src/providers/discovery/catalog/catalog-format.ts +20 -0
- package/src/providers/discovery/models-dev-catalog.ts +39 -22
- package/src/providers/discovery/openai-model-discovery.ts +39 -3
- package/src/providers/discovery/openrouter-free-roster.ts +98 -0
- package/src/providers/discovery/probe-wire.ts +29 -5
- package/src/providers/discovery/probing-service.ts +1022 -1001
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +42 -2
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +913 -914
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +22 -5
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +33 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +46 -0
- package/src/providers/integrations/buddy/buddy-oauth-shared.ts +37 -1
- package/src/providers/integrations/buddy/buddy-usage-store.ts +40 -5
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +4 -0
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -175
- package/src/providers/integrations/buddy/codebuddy/shared.ts +115 -73
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -204
- package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
- package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
- package/src/providers/integrations/claude/claude.ts +606 -582
- package/src/providers/integrations/cline/cline-quota.ts +18 -16
- package/src/providers/integrations/cline/cline.ts +539 -468
- package/src/providers/integrations/codex/codex.ts +5 -8
- package/src/providers/integrations/commandcode/commandcode.ts +675 -541
- package/src/providers/integrations/commandcode/discovery.ts +116 -0
- package/src/providers/integrations/commandcode/quota.ts +160 -0
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -0
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -0
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -0
- package/src/providers/integrations/cursor/cursor-identity.ts +92 -0
- package/src/providers/integrations/cursor/cursor-request.ts +471 -0
- package/src/providers/integrations/cursor/cursor-session-manager.ts +281 -0
- package/src/providers/integrations/cursor/cursor-shared.ts +108 -0
- package/src/providers/integrations/cursor/cursor.ts +960 -0
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -0
- package/src/providers/integrations/cursor/protocol/messages.ts +2355 -0
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -0
- package/src/providers/integrations/factory/factory-oauth.ts +7 -11
- package/src/providers/integrations/github/github.ts +4 -4
- package/src/providers/integrations/hermes.ts +79 -8
- package/src/providers/integrations/kilo/kilo-discovery.ts +10 -1
- package/src/providers/integrations/kilo/kilo.ts +153 -146
- package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
- package/src/providers/integrations/opencode/provider.ts +532 -499
- package/src/providers/integrations/openrouter/provider.ts +139 -281
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -0
- package/src/providers/integrations/qoder/qoder-oauth.ts +310 -263
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -0
- package/src/providers/integrations/qoder/qoder-virtual.ts +77 -0
- package/src/providers/integrations/qoder/qoder.ts +865 -730
- package/src/providers/integrations/shared/install-id.ts +29 -2
- package/src/providers/integrations/trae/trae-catalog.ts +190 -0
- package/src/providers/integrations/trae/trae-headers.ts +149 -0
- package/src/providers/integrations/trae/trae-oauth.ts +773 -0
- package/src/providers/integrations/trae/trae-protocol.ts +400 -0
- package/src/providers/integrations/trae/trae-realm.ts +220 -0
- package/src/providers/integrations/trae/trae.ts +458 -0
- package/src/providers/operations/account-health-service.ts +107 -10
- package/src/providers/operations/client-versions.ts +143 -6
- package/src/providers/operations/model-catalog-cache.ts +18 -3
- package/src/providers/operations/provider-catalog-seeder.ts +95 -86
- package/src/providers/operations/provider-catalog-service.ts +76 -0
- package/src/providers/operations/provider-credential-service.ts +46 -7
- package/src/providers/provider-contracts.ts +76 -0
- package/src/providers/provider-metadata.ts +78 -6
- package/src/providers/provider-model-definition.ts +373 -349
- package/src/security/api-key-auth.ts +126 -8
- package/src/transport/contracts.ts +3 -0
- package/src/transport/dispatch/attempt-finalize.ts +39 -2
- package/src/transport/dispatch/attempt-loop.ts +530 -513
- package/src/transport/dispatch/empty-stream.ts +17 -26
- package/src/transport/dispatch/generation.ts +88 -21
- package/src/transport/dispatch/leases.ts +99 -2
- package/src/transport/dispatch/proxy-request.ts +60 -3
- package/src/transport/dispatch/streaming/finalize.ts +16 -1
- package/src/transport/dispatch/streaming/stream.ts +22 -2
- package/src/transport/dispatch/upstream.ts +19 -1
- package/src/transport/failure-policy.ts +20 -0
- package/src/transport/guards/auth-guard.ts +26 -1
- package/src/transport/guards/body-policy.ts +5 -4
- package/src/transport/request/preparer.ts +103 -0
- package/src/transport/request/state.ts +14 -0
- package/src/transport/routing/plan-gate.ts +45 -0
- package/src/transport/routing/route-catalog.ts +799 -738
- package/src/transport/routing/route-model.ts +52 -1
- package/src/transport/routing/router.ts +51 -0
- package/src/transport/surface/chat/encode.ts +18 -31
- package/src/transport/surface/messages/stream.ts +3 -2
- package/src/transport/translation/tool-repair.ts +37 -16
- package/src/workers/account-identity-sweep.ts +14 -12
- package/src/workers/daily-checkin.ts +62 -0
- package/src/workers/quota-refresh-sweep.ts +27 -2
- package/dist/dashboard/assets/BannedPage-DUrXpp-X.js +0 -1
- package/dist/dashboard/assets/CombosPage-B7ETkfVH.js +0 -1
- package/dist/dashboard/assets/ConsoleLogPage-AisQzCI1.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BNflDm_f.js +0 -1
- package/dist/dashboard/assets/ModelPicker-CAiPR5Vd.js +0 -1
- package/dist/dashboard/assets/OverviewPage-C3NIzsDx.js +0 -1
- package/dist/dashboard/assets/ProviderDetailPage-BK8i5B5X.js +0 -3
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-CvCVgm1e.js +0 -1
- package/dist/dashboard/assets/ProxyPage-DjabkuzP.js +0 -7
- package/dist/dashboard/assets/QuotaPage-Dxe0B0iu.js +0 -1
- package/dist/dashboard/assets/SetupPage-BaD4P_jW.js +0 -1
- package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
- package/dist/dashboard/assets/UsagePage-Djes-Epy.js +0 -56
- package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
- package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
- package/dist/dashboard/assets/assert-Bwh6DJTK.js +0 -1
- package/dist/dashboard/assets/cli-tools-DccaOfhX.js +0 -1
- package/dist/dashboard/assets/clipboard-button-DQmMikNQ.js +0 -1
- package/dist/dashboard/assets/entry-Cpi0rph1.js +0 -4
- package/dist/dashboard/assets/entry-Dm3PpDCO.js +0 -1
- package/dist/dashboard/assets/entry-keNDZ_Wz.js +0 -1
- package/dist/dashboard/assets/github-badge-BuAJr8oU.js +0 -1
- package/dist/dashboard/assets/model-bans-CVvHZ-qz.js +0 -1
- package/dist/dashboard/assets/mutation-BIizFZav.js +0 -1
- package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
- package/dist/dashboard/assets/routing-jntw9-Ui.js +0 -1
- package/dist/dashboard/assets/select-CGUuTQmj.js +0 -41
- package/dist/dashboard/assets/settings-BAS6QbmJ.js +0 -1
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js +0 -1
- package/dist/dashboard/assets/terminal-C9mZ4Ugm.js +0 -1
- package/dist/dashboard/assets/toast-DKAdP2mo.js +0 -2
- package/scripts/cli/cli.ts +0 -771
- package/scripts/operations/probe-egress.ts +0 -335
- package/scripts/operations/restart.ts +0 -120
- package/scripts/shared/env.ts +0 -269
- /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.ico +0 -0
- /package/{tray-assets → scripts/cli/tray/assets}/cartethyia.png +0 -0
- /package/scripts/cli/{menu.ts → ui/prompt.ts} +0 -0
- /package/scripts/cli/{wordmark.ts → ui/wordmark.ts} +0 -0
|
@@ -1,258 +1,257 @@
|
|
|
1
|
-
import { accessSync, constants, existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const required
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
redisParsed.host
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
});
|
|
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
|
+
}
|
|
@@ -4,23 +4,19 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
6
|
import { createServer } from "node:net";
|
|
7
|
+
import { MINIMUM_BUN_VERSION, bunVersionAtLeast } from "../support/bun";
|
|
7
8
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
detectLocalPostgres,
|
|
9
|
+
ENV_PATH,
|
|
10
|
+
PROJECT_ROOT,
|
|
11
11
|
generateCartethyiaEncryptionKey,
|
|
12
|
-
getLocalPlatform,
|
|
13
|
-
getOsHints,
|
|
14
12
|
hasPlaceholderSecrets,
|
|
15
13
|
mandatoryEnvBody,
|
|
16
|
-
parseServiceUrl,
|
|
17
|
-
probeTcpService,
|
|
18
14
|
readEnvFile,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "../
|
|
22
|
-
import type { ProbeResult } from "../
|
|
23
|
-
import { resolveDataDir, resolveDbMode } from "
|
|
15
|
+
} from "../support/env";
|
|
16
|
+
import { getLocalPlatform, getOsHints } from "../support/platform";
|
|
17
|
+
import { detectLocalPostgres, parseServiceUrl, probeTcpService } from "../support/services";
|
|
18
|
+
import type { ProbeResult } from "../support/services";
|
|
19
|
+
import { resolveDataDir, resolveDbMode } from "../../../src/persistence/db-mode";
|
|
24
20
|
|
|
25
21
|
async function ask(question: string, defaultValue = ""): Promise<string> {
|
|
26
22
|
const rl = createInterface({ input, output });
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { resolvePort } from "../../../src/config/runtime";
|
|
2
|
+
import { ensureDrainToken } from "../gateway/drain-token";
|
|
3
|
+
import { clearInstanceLock, pidAlive, readInstanceLock, writeInstanceLock } from "../gateway/instance-lock";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runs the gateway in the foreground, in this process.
|
|
7
|
+
*
|
|
8
|
+
* A foreground run still needs a drain token on disk: the gateway registers
|
|
9
|
+
* `/admin/drain` only when one is set, and "Hide to tray" hands this run to a
|
|
10
|
+
* tray that drains it through that route.
|
|
11
|
+
*
|
|
12
|
+
* Unlike the menu and tray paths this one does not spawn a child, so the
|
|
13
|
+
* port-busy check has to happen here too — otherwise a stale gateway makes a
|
|
14
|
+
* plain `cartethyia start` fail from inside `src/main` with a message that says
|
|
15
|
+
* nothing about what to do.
|
|
16
|
+
*/
|
|
17
|
+
export async function startForeground(): Promise<void> {
|
|
18
|
+
const token = ensureDrainToken();
|
|
19
|
+
process.env.CARTETHYIA_DRAIN_TOKEN ??= token;
|
|
20
|
+
const port = resolvePort();
|
|
21
|
+
const blocker = await inspectPortHolder(port, token);
|
|
22
|
+
if (blocker !== null) throw new Error(blocker);
|
|
23
|
+
writeInstanceLock(port, process.pid, token);
|
|
24
|
+
await import("../../../src/main");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* What to tell the operator when the port is already taken, or `null` when the
|
|
29
|
+
* port is free (or was freed).
|
|
30
|
+
*
|
|
31
|
+
* Same rule as the spawned path: only ever stop a gateway that proves it is
|
|
32
|
+
* this install's own. A drain request is token-gated, so a 401 means another
|
|
33
|
+
* Cartethyia install and anything else is not a gateway at all — neither may be
|
|
34
|
+
* touched, and both are reported instead.
|
|
35
|
+
*/
|
|
36
|
+
async function inspectPortHolder(port: number, token: string): Promise<string | null> {
|
|
37
|
+
const up = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
38
|
+
signal: AbortSignal.timeout(1_000),
|
|
39
|
+
}).catch(() => null);
|
|
40
|
+
if (up === null) {
|
|
41
|
+
// Port is free. Drop a stale marker from a crashed run so the next start
|
|
42
|
+
// does not read a dead pid as a live gateway.
|
|
43
|
+
const lock = readInstanceLock();
|
|
44
|
+
if (lock !== null && lock.port === port && !pidAlive(lock.pid)) clearInstanceLock();
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const response = await fetch(`http://127.0.0.1:${port}/admin/drain`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { "x-drain-token": token },
|
|
50
|
+
signal: AbortSignal.timeout(3_000),
|
|
51
|
+
}).catch(() => null);
|
|
52
|
+
if (response === null) {
|
|
53
|
+
return `Port ${port} is busy and nothing there answers a drain request. Stop whatever holds it, or set a different PORT.`;
|
|
54
|
+
}
|
|
55
|
+
if (response.status === 401) {
|
|
56
|
+
return `Another Cartethyia gateway is already running on port ${port} under a different install. Stop it from its own terminal or tray — this install will not stop a gateway it does not own.`;
|
|
57
|
+
}
|
|
58
|
+
if (response.status === 403) {
|
|
59
|
+
return `Port ${port} refused a drain from loopback. Stop what holds it, then start again.`;
|
|
60
|
+
}
|
|
61
|
+
if (response.status !== 202) {
|
|
62
|
+
return `Port ${port} is busy and refused to drain (HTTP ${response.status}). Stop it manually, then start again.`;
|
|
63
|
+
}
|
|
64
|
+
// Drain accepted; wait for the port to actually close.
|
|
65
|
+
const deadline = Date.now() + 30_000;
|
|
66
|
+
while (Date.now() < deadline) {
|
|
67
|
+
const stillUp = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
68
|
+
signal: AbortSignal.timeout(500),
|
|
69
|
+
}).catch(() => null);
|
|
70
|
+
if (stillUp === null) {
|
|
71
|
+
clearInstanceLock();
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
await Bun.sleep(200);
|
|
75
|
+
}
|
|
76
|
+
return `The gateway on port ${port} accepted a drain but did not release the port within 30 seconds. Stop it manually, then start again.`;
|
|
77
|
+
}
|