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
package/scripts/cli/cli.ts
DELETED
|
@@ -1,771 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, openSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { isNewerRelease } from "../../src/console/updates/version-compare";
|
|
6
|
-
import { PACKAGE_ROOT, resolvePackagePath, resolveUserConfigDir } from "../../src/config/package-paths";
|
|
7
|
-
import { resolvePort } from "../../src/config/runtime";
|
|
8
|
-
import { MINIMUM_BUN_VERSION, bunVersionAtLeast } from "../shared/env";
|
|
9
|
-
import { chooseFromMenu, confirmByTyping, confirmStep } from "./menu";
|
|
10
|
-
import { ensureDrainToken, readPersistedDrainToken } from "./runtime-state";
|
|
11
|
-
import { createTray } from "./tray";
|
|
12
|
-
import { readInstalledVersion, readLatestPublishedVersion } from "./version";
|
|
13
|
-
import { renderWordmark } from "./wordmark";
|
|
14
|
-
|
|
15
|
-
process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
|
|
16
|
-
|
|
17
|
-
const HELP = `Cartethyia ${MINIMUM_BUN_VERSION}+ (Bun is required)
|
|
18
|
-
|
|
19
|
-
Usage:
|
|
20
|
-
carte start, then show an arrow-key menu (no GUI needed)
|
|
21
|
-
carte --tray start with a system-tray icon instead
|
|
22
|
-
cartethyia setup [--non-interactive]
|
|
23
|
-
cartethyia start
|
|
24
|
-
cartethyia menu
|
|
25
|
-
cartethyia stop stop a gateway left running in the background
|
|
26
|
-
cartethyia update
|
|
27
|
-
|
|
28
|
-
carte starts the gateway in the background and leaves only the console menu on
|
|
29
|
-
your terminal — Open Console, Reset password, Backup, Visit
|
|
30
|
-
repository, Update, Exit. Arrow keys and Enter; no desktop environment needed,
|
|
31
|
-
so it works over SSH and headless. Exiting the menu drains the gateway.
|
|
32
|
-
carte --tray uses a system-tray icon instead, which needs a graphical session.
|
|
33
|
-
Stop a gateway whose terminal is gone with "cartethyia stop".
|
|
34
|
-
Setup explains Lite (embedded SQLite) and Full (external PostgreSQL), then saves
|
|
35
|
-
configuration under the current user's Cartethyia config directory.
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
function assertBunVersion(): void {
|
|
39
|
-
if (bunVersionAtLeast(Bun.version, MINIMUM_BUN_VERSION)) return;
|
|
40
|
-
throw new Error(
|
|
41
|
-
`Bun ${MINIMUM_BUN_VERSION} or newer is required (found ${Bun.version}). Install it from https://bun.sh and run this command again.`,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function loadUserConfig(): Promise<void> {
|
|
46
|
-
const configDir = process.env.CARTETHYIA_CONFIG_DIR;
|
|
47
|
-
if (!configDir) throw new Error("Cartethyia config directory could not be resolved");
|
|
48
|
-
mkdirSync(configDir, { recursive: true });
|
|
49
|
-
const { ENV_PATH, readEnvFile } = await import("../shared/env");
|
|
50
|
-
const values = await readEnvFile(ENV_PATH);
|
|
51
|
-
for (const [key, value] of Object.entries(values)) {
|
|
52
|
-
if (!process.env[key]) process.env[key] = value;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
type TrayStartupSignal =
|
|
58
|
-
| { readonly status: "ready"; readonly url: string }
|
|
59
|
-
| { readonly status: "failed"; readonly message: string };
|
|
60
|
-
|
|
61
|
-
function reportTrayStartup(signal: TrayStartupSignal): void {
|
|
62
|
-
const signalPath = process.env.CARTETHYIA_TRAY_STARTUP_SIGNAL;
|
|
63
|
-
if (!signalPath) return;
|
|
64
|
-
try {
|
|
65
|
-
writeFileSync(signalPath, JSON.stringify(signal), { encoding: "utf8", flag: "wx" });
|
|
66
|
-
} catch {
|
|
67
|
-
// The launcher may have timed out and removed the one-shot signal file.
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
function openUrl(url: string): Promise<void> {
|
|
74
|
-
const command = process.platform === "win32"
|
|
75
|
-
? "explorer.exe"
|
|
76
|
-
: process.platform === "darwin"
|
|
77
|
-
? "open"
|
|
78
|
-
: "xdg-open";
|
|
79
|
-
const child = Bun.spawn([command, url], {
|
|
80
|
-
stdin: "ignore",
|
|
81
|
-
stdout: "ignore",
|
|
82
|
-
stderr: "ignore",
|
|
83
|
-
});
|
|
84
|
-
return child.exited.then(() => undefined);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
async function startGateway(
|
|
90
|
-
port: number,
|
|
91
|
-
drainToken: string,
|
|
92
|
-
options: { readonly detached?: boolean } = {},
|
|
93
|
-
): Promise<Bun.Subprocess> {
|
|
94
|
-
// Starting a second gateway on the same port used to fail with "stopped
|
|
95
|
-
// before becoming ready (exit code 1)", which says nothing about the real
|
|
96
|
-
// cause. Probe the port first and say what is actually there.
|
|
97
|
-
const existing = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
98
|
-
signal: AbortSignal.timeout(1_000),
|
|
99
|
-
}).catch(() => null);
|
|
100
|
-
if (existing !== null) {
|
|
101
|
-
throw new Error(
|
|
102
|
-
`Cartethyia is already running on port ${port}. Use \`cartethyia stop\` to stop it, or open http://127.0.0.1:${port}/console.`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
// The gateway logs a startup phase line per stage. In menu mode those lines
|
|
106
|
-
// would scroll through the same terminal the menu is drawing on, so they go
|
|
107
|
-
// to a file instead and the operator sees only the menu.
|
|
108
|
-
const configDir = process.env.CARTETHYIA_CONFIG_DIR;
|
|
109
|
-
let logFd: number | null = null;
|
|
110
|
-
if (configDir) {
|
|
111
|
-
try {
|
|
112
|
-
logFd = openSync(join(configDir, "gateway.log"), "a");
|
|
113
|
-
} catch {
|
|
114
|
-
// A read-only config directory costs only the log, not startup.
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const child = Bun.spawn(
|
|
118
|
-
[process.execPath, "run", resolvePackagePath("src", "main.ts")],
|
|
119
|
-
{
|
|
120
|
-
cwd: PACKAGE_ROOT,
|
|
121
|
-
env: { ...process.env, CARTETHYIA_DRAIN_TOKEN: drainToken },
|
|
122
|
-
// "ignore", not "inherit": the gateway does not read stdin, and in menu
|
|
123
|
-
// mode the menu is the only stdin consumer. With "inherit" the gateway
|
|
124
|
-
// held the fd and swallowed every keypress after the first, so the menu
|
|
125
|
-
// rendered but never received Enter.
|
|
126
|
-
stdin: "ignore",
|
|
127
|
-
stdout: logFd ?? "ignore",
|
|
128
|
-
// stderr goes to the same file: "inherit" ties the gateway to the
|
|
129
|
-
// terminal, so closing that terminal took the gateway down with it and
|
|
130
|
-
// "Hide to tray" left nothing running.
|
|
131
|
-
stderr: logFd ?? "ignore",
|
|
132
|
-
// Attached by default. A detached child cannot be killed from an `exit`
|
|
133
|
-
// handler on Windows (measured: kill() runs, the gateway keeps serving),
|
|
134
|
-
// and a terminal's Ctrl-C only reaches children in the same process
|
|
135
|
-
// group. Staying attached means Ctrl-C and the `exit` backstop both stop
|
|
136
|
-
// the gateway. "Hide to tray" passes detached: true because that gateway
|
|
137
|
-
// must outlive this process.
|
|
138
|
-
detached: options.detached === true,
|
|
139
|
-
},
|
|
140
|
-
);
|
|
141
|
-
const startupController = new AbortController();
|
|
142
|
-
const ready = (async (): Promise<void> => {
|
|
143
|
-
const deadline = Date.now() + 30_000;
|
|
144
|
-
while (!startupController.signal.aborted && Date.now() < deadline) {
|
|
145
|
-
try {
|
|
146
|
-
const response = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
147
|
-
signal: AbortSignal.timeout(500),
|
|
148
|
-
});
|
|
149
|
-
if (response.ok) {
|
|
150
|
-
const body: unknown = await response.json();
|
|
151
|
-
if (body !== null && typeof body === "object" && "status" in body && body.status === "ok") return;
|
|
152
|
-
}
|
|
153
|
-
} catch {
|
|
154
|
-
// The gateway is not accepting health requests yet.
|
|
155
|
-
}
|
|
156
|
-
await Bun.sleep(250);
|
|
157
|
-
}
|
|
158
|
-
if (startupController.signal.aborted) throw new Error("Cartethyia exited before becoming ready");
|
|
159
|
-
throw new Error("Cartethyia did not open its configured port within 30 seconds");
|
|
160
|
-
})();
|
|
161
|
-
const exitedEarly = child.exited.then((code) => {
|
|
162
|
-
startupController.abort();
|
|
163
|
-
return { status: "exit" as const, code };
|
|
164
|
-
});
|
|
165
|
-
const readyResult = ready.then(
|
|
166
|
-
() => ({ status: "ready" as const }),
|
|
167
|
-
(error: unknown) => ({ status: "failure" as const, error }),
|
|
168
|
-
);
|
|
169
|
-
try {
|
|
170
|
-
const result = await Promise.race([readyResult, exitedEarly]);
|
|
171
|
-
if (result.status === "exit") {
|
|
172
|
-
throw new Error(`Cartethyia stopped before becoming ready (exit code ${result.code})`);
|
|
173
|
-
}
|
|
174
|
-
if (result.status === "failure") throw result.error;
|
|
175
|
-
return child;
|
|
176
|
-
} catch (error) {
|
|
177
|
-
child.kill();
|
|
178
|
-
await child.exited;
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function stopGateway(
|
|
184
|
-
current: ReturnType<typeof Bun.spawn>,
|
|
185
|
-
port: number,
|
|
186
|
-
drainToken: string,
|
|
187
|
-
): Promise<void> {
|
|
188
|
-
let drainAccepted = false;
|
|
189
|
-
try {
|
|
190
|
-
const response = await fetch(`http://127.0.0.1:${port}/admin/drain`, {
|
|
191
|
-
method: "POST",
|
|
192
|
-
headers: { "x-drain-token": drainToken },
|
|
193
|
-
signal: AbortSignal.timeout(2_000),
|
|
194
|
-
});
|
|
195
|
-
drainAccepted = response.status === 202;
|
|
196
|
-
} catch {
|
|
197
|
-
// The process may already be exiting; fall back to its OS-level termination.
|
|
198
|
-
}
|
|
199
|
-
if (!drainAccepted) {
|
|
200
|
-
console.error("Could not request a graceful gateway drain; terminating the gateway process.");
|
|
201
|
-
current.kill();
|
|
202
|
-
await current.exited;
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
207
|
-
const exit = await Promise.race([
|
|
208
|
-
current.exited,
|
|
209
|
-
new Promise<null>((resolveExit) => {
|
|
210
|
-
timer = setTimeout(() => resolveExit(null), 30_000);
|
|
211
|
-
}),
|
|
212
|
-
]);
|
|
213
|
-
if (timer !== undefined) clearTimeout(timer);
|
|
214
|
-
if (exit === null) {
|
|
215
|
-
console.error("Graceful drain timed out; terminating the gateway process.");
|
|
216
|
-
current.kill();
|
|
217
|
-
await current.exited;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* The default `carte` experience: start the gateway, then offer an arrow-key
|
|
225
|
-
* menu. This needs no desktop environment, so it works over SSH, on a headless
|
|
226
|
-
* box, and on Windows where the tray icon lands in the hidden-icons overflow
|
|
227
|
-
* and is never seen. `carte --tray` opts into the icon instead.
|
|
228
|
-
*/
|
|
229
|
-
async function startMenu(): Promise<void> {
|
|
230
|
-
// A fresh install has no config file yet, and the gateway refuses to boot
|
|
231
|
-
// without CARTETHYIA_DB_MODE — which surfaced as a stack trace about a
|
|
232
|
-
// missing database mode rather than "you have not set this up". Run setup
|
|
233
|
-
// first, the way `carte` is expected to behave on a new install.
|
|
234
|
-
const { ENV_PATH, readEnvFile } = await import("../shared/env");
|
|
235
|
-
const configured = existsSync(ENV_PATH) && (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE;
|
|
236
|
-
if (!configured) {
|
|
237
|
-
console.log(
|
|
238
|
-
existsSync(ENV_PATH)
|
|
239
|
-
? "Cartethyia is missing its database mode. Starting setup.\n"
|
|
240
|
-
: "Cartethyia is not set up yet. Starting setup.\n",
|
|
241
|
-
);
|
|
242
|
-
const { setup } = await import("./setup");
|
|
243
|
-
await setup();
|
|
244
|
-
const after = existsSync(ENV_PATH) ? (await readEnvFile(ENV_PATH)).CARTETHYIA_DB_MODE : undefined;
|
|
245
|
-
if (!after) {
|
|
246
|
-
console.error(
|
|
247
|
-
`Setup did not record CARTETHYIA_DB_MODE in ${ENV_PATH}; run \`cartethyia setup\` and try again.`,
|
|
248
|
-
);
|
|
249
|
-
process.exitCode = 1;
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
process.env.CARTETHYIA_DB_MODE = after;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const port = resolvePort();
|
|
256
|
-
const drainToken = ensureDrainToken();
|
|
257
|
-
const dashboardUrl = `http://127.0.0.1:${port}/console`;
|
|
258
|
-
const repositoryUrl = "https://github.com/risunCode/cartethyia";
|
|
259
|
-
const currentVersion = await readInstalledVersion();
|
|
260
|
-
|
|
261
|
-
// The gateway is detached and holds no stdio, so this process owns the
|
|
262
|
-
// terminal outright: nothing else can consume the keys the menu needs. The
|
|
263
|
-
// drain token is persisted before the menu opens because `cartethyia stop`
|
|
264
|
-
// runs in a later shell and has no other handle on this process.
|
|
265
|
-
const gateway = await startGateway(port, drainToken);
|
|
266
|
-
gateway.unref();
|
|
267
|
-
|
|
268
|
-
let stopping = false;
|
|
269
|
-
let handedOff = false;
|
|
270
|
-
const shutDown = async (): Promise<void> => {
|
|
271
|
-
if (stopping) return;
|
|
272
|
-
stopping = true;
|
|
273
|
-
await stopGateway(gateway, port, drainToken);
|
|
274
|
-
};
|
|
275
|
-
// Ctrl-C has to stop the gateway, and the drain endpoint cannot be called
|
|
276
|
-
// from an exit path: it is async, and enquirer's own Ctrl-C handler calls
|
|
277
|
-
// process.exit(130) while a prompt is live. So the gateway is a child in this
|
|
278
|
-
// process group — a terminal's Ctrl-C signals the whole group, and the
|
|
279
|
-
// gateway drains through the SIGINT handler it already has in src/main.ts —
|
|
280
|
-
// and `process.on("exit")` is the synchronous backstop for every other way
|
|
281
|
-
// this process can end (a closed terminal, a crash, a signal Bun cannot
|
|
282
|
-
// deliver on Windows). That handler can only call synchronous APIs, which is
|
|
283
|
-
// why it signals the child rather than awaiting the drain endpoint.
|
|
284
|
-
// `handedOff` is set by "Hide to tray": the gateway is being given to a tray
|
|
285
|
-
// that will own it, so this process must not kill it on the way out.
|
|
286
|
-
process.on("exit", () => {
|
|
287
|
-
if (stopping || handedOff) return;
|
|
288
|
-
try {
|
|
289
|
-
gateway.kill();
|
|
290
|
-
} catch {
|
|
291
|
-
// Already gone; nothing to stop.
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
const interrupt = async (): Promise<void> => {
|
|
295
|
-
process.stdout.write("\u001b[2J\u001b[H");
|
|
296
|
-
console.log("Stopping Cartethyia…");
|
|
297
|
-
await shutDown();
|
|
298
|
-
process.exit(0);
|
|
299
|
-
};
|
|
300
|
-
process.on("SIGTERM", () => void interrupt());
|
|
301
|
-
process.on("SIGINT", () => void interrupt());
|
|
302
|
-
|
|
303
|
-
let latestVersion = await readLatestPublishedVersion();
|
|
304
|
-
let running = true;
|
|
305
|
-
|
|
306
|
-
const header = [...renderWordmark(), `Cartethyia ${currentVersion} \u00b7 ${dashboardUrl}`];
|
|
307
|
-
|
|
308
|
-
while (running) {
|
|
309
|
-
const upToDate = latestVersion === null || !isNewerRelease(currentVersion, latestVersion);
|
|
310
|
-
const updateLabel =
|
|
311
|
-
latestVersion === null
|
|
312
|
-
? "Check for updates"
|
|
313
|
-
: upToDate
|
|
314
|
-
? `Up to date (${currentVersion})`
|
|
315
|
-
: `Update to ${latestVersion}`;
|
|
316
|
-
|
|
317
|
-
const actions: readonly (() => Promise<void>)[] = [
|
|
318
|
-
async () => openUrl(dashboardUrl),
|
|
319
|
-
async () => {
|
|
320
|
-
// Deleting the console account is the only real password recovery:
|
|
321
|
-
// /change-password needs the current password, and first-boot setup
|
|
322
|
-
// only runs when no user exists. Removing the user puts the wizard
|
|
323
|
-
// back. Providers, keys, and routing are untouched.
|
|
324
|
-
console.log("");
|
|
325
|
-
console.log("This deletes every console account and its sessions.");
|
|
326
|
-
console.log("The next visit to /console asks you to create a new one.");
|
|
327
|
-
console.log("Providers, API keys, and routing are not touched.");
|
|
328
|
-
console.log("");
|
|
329
|
-
if (!(await confirmStep("Continue with the reset?"))) {
|
|
330
|
-
console.log("Cancelled.");
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
if (!(await confirmByTyping("RESET", "Confirm deleting the console account"))) {
|
|
334
|
-
console.log("Cancelled.");
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
const status = await resetConsoleAccount(port, drainToken);
|
|
338
|
-
console.log(
|
|
339
|
-
status === 0
|
|
340
|
-
? `Console account removed. Open ${dashboardUrl} to create a new one.`
|
|
341
|
-
: "Reset failed; see the gateway log.",
|
|
342
|
-
);
|
|
343
|
-
},
|
|
344
|
-
async () => {
|
|
345
|
-
// Export straight to the download directory. The console page asks for
|
|
346
|
-
// the operator password, which is exactly the thing a locked-out
|
|
347
|
-
// operator does not have; the CLI already runs as the OS user who owns
|
|
348
|
-
// the database, so the loopback token is the whole gate.
|
|
349
|
-
const result = await exportBackup(port, drainToken);
|
|
350
|
-
if (result === null) return;
|
|
351
|
-
console.log(
|
|
352
|
-
`Backup written to ${result.path} (${(result.bytes / 1024).toFixed(1)} KiB, ${Object.keys(result.counts).length} tables).`,
|
|
353
|
-
);
|
|
354
|
-
console.log("Telemetry is not included: it is per-request history, not configuration.");
|
|
355
|
-
},
|
|
356
|
-
async () => openUrl(repositoryUrl),
|
|
357
|
-
async () => {
|
|
358
|
-
if (upToDate) return;
|
|
359
|
-
// npm cannot replace this package's directory while this process is
|
|
360
|
-
// running out of it: on Windows the rename fails with EBUSY and npm
|
|
361
|
-
// exits 14. Even with the gateway stopped, the CLI itself is executing
|
|
362
|
-
// from that directory, so an in-process install can never succeed.
|
|
363
|
-
// Print the command and exit, the way 9router does.
|
|
364
|
-
await shutDown();
|
|
365
|
-
console.log("");
|
|
366
|
-
console.log(`Update available: ${currentVersion} \u2192 ${latestVersion}`);
|
|
367
|
-
console.log("");
|
|
368
|
-
console.log("Run this now that Cartethyia has stopped:");
|
|
369
|
-
console.log("");
|
|
370
|
-
console.log(` ${updateCommand()}`);
|
|
371
|
-
console.log("");
|
|
372
|
-
running = false;
|
|
373
|
-
process.exitCode = 0;
|
|
374
|
-
return;
|
|
375
|
-
},
|
|
376
|
-
async () => {
|
|
377
|
-
// Start a detached `tray` process that owns the gateway from scratch,
|
|
378
|
-
// then drain this one so the new process can claim the port. This is
|
|
379
|
-
// the pattern 9router uses and it is simpler than handing a live
|
|
380
|
-
// gateway between processes: the tray is a normal first-class run, not
|
|
381
|
-
// an adopted orphan, so Exit and update behave the same whether the
|
|
382
|
-
// gateway was started here or from `carte --tray`.
|
|
383
|
-
console.log("Starting the tray in the background…");
|
|
384
|
-
const tray = spawn(
|
|
385
|
-
process.execPath,
|
|
386
|
-
["run", resolvePackagePath("scripts", "cli", "cli.ts"), "tray"],
|
|
387
|
-
{
|
|
388
|
-
cwd: tmpdir(),
|
|
389
|
-
detached: true,
|
|
390
|
-
stdio: "ignore",
|
|
391
|
-
windowsHide: true,
|
|
392
|
-
env: process.env,
|
|
393
|
-
},
|
|
394
|
-
);
|
|
395
|
-
tray.unref();
|
|
396
|
-
running = false;
|
|
397
|
-
stopping = true;
|
|
398
|
-
handedOff = true;
|
|
399
|
-
await shutDown();
|
|
400
|
-
console.log(`Cartethyia is running in the background at ${dashboardUrl}.`);
|
|
401
|
-
console.log("The tray icon owns it. Stop it with: cartethyia stop");
|
|
402
|
-
},
|
|
403
|
-
async () => {
|
|
404
|
-
running = false;
|
|
405
|
-
},
|
|
406
|
-
];
|
|
407
|
-
|
|
408
|
-
const choice = await chooseFromMenu(header, [
|
|
409
|
-
{ kind: "action", label: "Open Console", hint: `browser \u00b7 ${dashboardUrl}` },
|
|
410
|
-
{ kind: "action", label: "Reset console password", hint: "deletes the account" },
|
|
411
|
-
{ kind: "action", label: "Backup", hint: "export config to Downloads" },
|
|
412
|
-
{ kind: "action", label: "Visit repository", hint: "github.com/risunCode/cartethyia" },
|
|
413
|
-
{ kind: "action", label: updateLabel, hint: upToDate ? "no update available" : "installs now" },
|
|
414
|
-
{ kind: "action", label: "Hide to tray", hint: "keep the gateway running" },
|
|
415
|
-
{ kind: "rule" },
|
|
416
|
-
{ kind: "action", label: "Exit", hint: "stop the gateway" },
|
|
417
|
-
],
|
|
418
|
-
);
|
|
419
|
-
if (choice === -1) break;
|
|
420
|
-
const action = actions[choice];
|
|
421
|
-
if (action) await action();
|
|
422
|
-
}
|
|
423
|
-
// "Hide to tray" has already given the gateway to the tray. Draining here
|
|
424
|
-
// would stop the very process the tray just adopted, which is why the menu
|
|
425
|
-
// appeared to hand over a gateway that was not listening.
|
|
426
|
-
if (handedOff) return;
|
|
427
|
-
await shutDown();
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Asks the gateway to write a gzipped config backup into the download
|
|
432
|
-
* directory. Returns the written path and counts, or null when it failed.
|
|
433
|
-
*
|
|
434
|
-
* The file is produced by the gateway, not here: the gateway owns the database
|
|
435
|
-
* connection and the schema is dialect-dependent, so reading the tables from
|
|
436
|
-
* this process would need a second implementation of both.
|
|
437
|
-
*/
|
|
438
|
-
/** The command an operator runs to update, printed instead of run in-process. */
|
|
439
|
-
function updateCommand(): string {
|
|
440
|
-
return "npm install --global cartethyia@latest --prefer-online";
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async function exportBackup(
|
|
444
|
-
port: number,
|
|
445
|
-
token: string,
|
|
446
|
-
): Promise<{ path: string; bytes: number; counts: Record<string, number> } | null> {
|
|
447
|
-
try {
|
|
448
|
-
const response = await fetch(`http://127.0.0.1:${port}/admin/export-backup`, {
|
|
449
|
-
method: "POST",
|
|
450
|
-
headers: { "content-type": "application/json", "x-drain-token": token },
|
|
451
|
-
body: JSON.stringify({}),
|
|
452
|
-
signal: AbortSignal.timeout(60_000),
|
|
453
|
-
});
|
|
454
|
-
if (!response.ok) {
|
|
455
|
-
const detail = await response.text().catch(() => "");
|
|
456
|
-
console.error(`The gateway refused the backup (HTTP ${response.status}). ${detail}`.trim());
|
|
457
|
-
return null;
|
|
458
|
-
}
|
|
459
|
-
return (await response.json()) as {
|
|
460
|
-
path: string;
|
|
461
|
-
bytes: number;
|
|
462
|
-
counts: Record<string, number>;
|
|
463
|
-
};
|
|
464
|
-
} catch {
|
|
465
|
-
console.error("Could not reach the gateway. Is it still running?");
|
|
466
|
-
return null;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Posts the console-account reset to the running gateway.
|
|
472
|
-
*
|
|
473
|
-
* The reset has to go through the gateway, not the database file, because the
|
|
474
|
-
* gateway holds the connection and the schema is dialect-dependent. Returns 0
|
|
475
|
-
* on success and 1 on any failure so the caller can print one line either way.
|
|
476
|
-
*/
|
|
477
|
-
async function resetConsoleAccount(port: number, token: string): Promise<number> {
|
|
478
|
-
try {
|
|
479
|
-
const response = await fetch(`http://127.0.0.1:${port}/admin/reset-console-account`, {
|
|
480
|
-
method: "POST",
|
|
481
|
-
headers: { "content-type": "application/json", "x-drain-token": token },
|
|
482
|
-
body: JSON.stringify({ confirm: "RESET" }),
|
|
483
|
-
signal: AbortSignal.timeout(10_000),
|
|
484
|
-
});
|
|
485
|
-
if (!response.ok) {
|
|
486
|
-
const detail = await response.text().catch(() => "");
|
|
487
|
-
console.error(`The gateway refused the reset (HTTP ${response.status}). ${detail}`.trim());
|
|
488
|
-
return 1;
|
|
489
|
-
}
|
|
490
|
-
return 0;
|
|
491
|
-
} catch {
|
|
492
|
-
console.error("Could not reach the gateway. Is it still running?");
|
|
493
|
-
return 1;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Asks a gateway we do not own to drain, then waits for its port to close.
|
|
499
|
-
*
|
|
500
|
-
* Used by `cartethyia stop` and by an adopted tray's Exit: in both cases the
|
|
501
|
-
* gateway is a separate process, so the loopback endpoint is the only handle.
|
|
502
|
-
* Returns 0 only once the port stops answering — a drain that is accepted but
|
|
503
|
-
* leaves the listener up is not a stop.
|
|
504
|
-
*/
|
|
505
|
-
async function drainThroughEndpoint(port: number, token: string): Promise<number> {
|
|
506
|
-
try {
|
|
507
|
-
const response = await fetch(`http://127.0.0.1:${port}/admin/drain`, {
|
|
508
|
-
method: "POST",
|
|
509
|
-
headers: { "x-drain-token": token },
|
|
510
|
-
signal: AbortSignal.timeout(3_000),
|
|
511
|
-
});
|
|
512
|
-
if (response.status !== 202) {
|
|
513
|
-
console.error(
|
|
514
|
-
response.status === 401
|
|
515
|
-
? `The gateway on port ${port} rejected the drain token. It was started by a different install; stop it where it was started.`
|
|
516
|
-
: `The gateway refused the drain request (HTTP ${response.status}).`,
|
|
517
|
-
);
|
|
518
|
-
return 1;
|
|
519
|
-
}
|
|
520
|
-
} catch {
|
|
521
|
-
console.error(`No gateway is listening on port ${port}. It may already be stopped.`);
|
|
522
|
-
return 1;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const deadline = Date.now() + 20_000;
|
|
526
|
-
while (Date.now() < deadline) {
|
|
527
|
-
const alive = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
528
|
-
signal: AbortSignal.timeout(500),
|
|
529
|
-
})
|
|
530
|
-
.then(() => true)
|
|
531
|
-
.catch(() => false);
|
|
532
|
-
if (!alive) return 0;
|
|
533
|
-
await Bun.sleep(250);
|
|
534
|
-
}
|
|
535
|
-
console.error(
|
|
536
|
-
`The gateway accepted the drain but is still answering on port ${port} after 20 seconds.`,
|
|
537
|
-
);
|
|
538
|
-
return 1;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Stops a running gateway through its loopback drain endpoint.
|
|
543
|
-
*
|
|
544
|
-
* That process is not a child of this one, so it cannot be signalled directly —
|
|
545
|
-
* the token in the config directory is the only handle. A 202 means the drain
|
|
546
|
-
* was accepted, not that the process is gone, so this waits for the port to
|
|
547
|
-
* stop answering before reporting success; a `stop` that returns while the
|
|
548
|
-
* gateway is still serving is worse than one that fails.
|
|
549
|
-
*/
|
|
550
|
-
async function stopRunningGateway(): Promise<number> {
|
|
551
|
-
const port = resolvePort();
|
|
552
|
-
const token = readPersistedDrainToken();
|
|
553
|
-
if (token === null) {
|
|
554
|
-
console.error(
|
|
555
|
-
"This install has no saved drain token, so there is no background gateway to stop. Start one with `carte`, or stop a foreground run with Ctrl-C.",
|
|
556
|
-
);
|
|
557
|
-
return 1;
|
|
558
|
-
}
|
|
559
|
-
const code = await drainThroughEndpoint(port, token);
|
|
560
|
-
if (code === 0) console.log("Cartethyia stopped.");
|
|
561
|
-
return code;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Runs the gateway with a tray icon. The tray is platform-specific (`tray.ts`):
|
|
567
|
-
* on Windows it is a PowerShell NotifyIcon, because a Node tray library leaves
|
|
568
|
-
* an empty console tab open for the tray's whole lifetime, and that window is
|
|
569
|
-
* owned by Windows Terminal — not by this process, so it cannot be found and
|
|
570
|
-
* hidden from inside.
|
|
571
|
-
*/
|
|
572
|
-
async function startTray(): Promise<void> {
|
|
573
|
-
const port = resolvePort();
|
|
574
|
-
const drainToken = ensureDrainToken();
|
|
575
|
-
const dashboardUrl = `http://127.0.0.1:${port}/console`;
|
|
576
|
-
const repositoryUrl = "https://github.com/risunCode/cartethyia";
|
|
577
|
-
const currentVersion = await readInstalledVersion();
|
|
578
|
-
|
|
579
|
-
let gateway: Bun.Subprocess | null = null;
|
|
580
|
-
let closing = false;
|
|
581
|
-
let busy = false;
|
|
582
|
-
let resetArmed = false;
|
|
583
|
-
let closePromise: Promise<void> | undefined;
|
|
584
|
-
|
|
585
|
-
const tray = createTray({
|
|
586
|
-
iconPath: resolvePackagePath(
|
|
587
|
-
"tray-assets",
|
|
588
|
-
process.platform === "win32" ? "cartethyia.ico" : "cartethyia.png",
|
|
589
|
-
),
|
|
590
|
-
tooltip: `Cartethyia ${currentVersion}`,
|
|
591
|
-
items: [
|
|
592
|
-
{ kind: "action", id: "repository", title: "Visit repository" },
|
|
593
|
-
{ kind: "action", id: "update", title: `Cartethyia ${currentVersion} \u00b7 checking\u2026`, enabled: false },
|
|
594
|
-
{ kind: "separator" },
|
|
595
|
-
{ kind: "action", id: "console", title: `Open Console \u00b7 ${dashboardUrl}` },
|
|
596
|
-
{ kind: "action", id: "reset", title: "Reset console password" },
|
|
597
|
-
{ kind: "action", id: "backup", title: "Backup" },
|
|
598
|
-
{ kind: "separator" },
|
|
599
|
-
{ kind: "action", id: "exit", title: "Exit" },
|
|
600
|
-
],
|
|
601
|
-
onSelect: (id: string) => void handleSelect(id),
|
|
602
|
-
onExit: () => void close(1),
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
const close = (exitCode = 0): Promise<void> => {
|
|
606
|
-
if (closePromise) return closePromise;
|
|
607
|
-
closing = true;
|
|
608
|
-
closePromise = (async () => {
|
|
609
|
-
const current: Bun.Subprocess | null = gateway;
|
|
610
|
-
gateway = null;
|
|
611
|
-
if (current) await stopGateway(current, port, drainToken);
|
|
612
|
-
await tray.close();
|
|
613
|
-
process.exitCode = exitCode;
|
|
614
|
-
})();
|
|
615
|
-
return closePromise;
|
|
616
|
-
};
|
|
617
|
-
|
|
618
|
-
const refreshVersion = async (): Promise<void> => {
|
|
619
|
-
const fetched = await readLatestPublishedVersion();
|
|
620
|
-
if (closing) return;
|
|
621
|
-
if (fetched === null) {
|
|
622
|
-
tray.update("update", `Cartethyia ${currentVersion} \u00b7 check for updates`, true);
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
const newer = isNewerRelease(currentVersion, fetched);
|
|
626
|
-
tray.update(
|
|
627
|
-
"update",
|
|
628
|
-
newer
|
|
629
|
-
? `Cartethyia ${currentVersion} \u00b7 update to ${fetched}`
|
|
630
|
-
: `Cartethyia ${currentVersion} \u00b7 up to date`,
|
|
631
|
-
newer,
|
|
632
|
-
);
|
|
633
|
-
};
|
|
634
|
-
|
|
635
|
-
const runUpdate = async (): Promise<void> => {
|
|
636
|
-
if (busy) return;
|
|
637
|
-
busy = true;
|
|
638
|
-
// npm cannot replace this package's directory while this process is
|
|
639
|
-
// running out of it: on Windows the rename fails with EBUSY and npm exits
|
|
640
|
-
// 14, and stopping only the gateway is not enough because the tray itself
|
|
641
|
-
// is executing from that directory. Stop everything and print the command.
|
|
642
|
-
const latest = await readLatestPublishedVersion();
|
|
643
|
-
await close();
|
|
644
|
-
console.log("");
|
|
645
|
-
console.log(`Update available: ${currentVersion} \u2192 ${latest ?? "latest"}`);
|
|
646
|
-
console.log("");
|
|
647
|
-
console.log("Run this now that Cartethyia has stopped:");
|
|
648
|
-
console.log("");
|
|
649
|
-
console.log(` ${updateCommand()}`);
|
|
650
|
-
console.log("");
|
|
651
|
-
process.exit(0);
|
|
652
|
-
};
|
|
653
|
-
|
|
654
|
-
async function handleSelect(id: string): Promise<void> {
|
|
655
|
-
if (closing) return;
|
|
656
|
-
if (id === "exit") return void (await close());
|
|
657
|
-
if (id === "console") return void (await openUrl(dashboardUrl));
|
|
658
|
-
if (id === "repository") return void (await openUrl(repositoryUrl));
|
|
659
|
-
if (id === "backup") {
|
|
660
|
-
const result = await exportBackup(port, drainToken);
|
|
661
|
-
if (result !== null) {
|
|
662
|
-
console.log(
|
|
663
|
-
`Backup written to ${result.path} (${(result.bytes / 1024).toFixed(1)} KiB). Telemetry is not included.`,
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
if (id === "reset") {
|
|
669
|
-
// A tray click cannot ask for the typed confirmation the menu uses, so
|
|
670
|
-
// the first click arms the action and the second one runs it.
|
|
671
|
-
if (!resetArmed) {
|
|
672
|
-
resetArmed = true;
|
|
673
|
-
tray.update("reset", "Reset console password \u00b7 click again to delete", true);
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
resetArmed = false;
|
|
677
|
-
tray.update("reset", "Reset console password", true);
|
|
678
|
-
await resetConsoleAccount(port, drainToken);
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
if (id === "update") await runUpdate();
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
process.once("SIGINT", () => void close());
|
|
685
|
-
process.once("SIGTERM", () => void close());
|
|
686
|
-
|
|
687
|
-
try {
|
|
688
|
-
await tray.ready();
|
|
689
|
-
gateway = await startGateway(port, drainToken);
|
|
690
|
-
console.log(`Cartethyia is running at ${dashboardUrl}`);
|
|
691
|
-
void refreshVersion();
|
|
692
|
-
|
|
693
|
-
while (!closing) {
|
|
694
|
-
const current: Bun.Subprocess | null = gateway;
|
|
695
|
-
if (!current) {
|
|
696
|
-
await Bun.sleep(100);
|
|
697
|
-
continue;
|
|
698
|
-
}
|
|
699
|
-
const code = await current.exited;
|
|
700
|
-
if (gateway !== current || closing) continue;
|
|
701
|
-
gateway = null;
|
|
702
|
-
console.error(`Cartethyia exited with code ${code}.`);
|
|
703
|
-
await close(code ?? 1);
|
|
704
|
-
}
|
|
705
|
-
} catch (error) {
|
|
706
|
-
await close(1);
|
|
707
|
-
throw error;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
async function main(): Promise<void> {
|
|
713
|
-
assertBunVersion();
|
|
714
|
-
const [command] = process.argv.slice(2);
|
|
715
|
-
if (command === "setup" || command === "start" || command === "tray" || command === "menu" || command === "stop") {
|
|
716
|
-
await loadUserConfig();
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
switch (command) {
|
|
720
|
-
case "setup": {
|
|
721
|
-
const { setup } = await import("./setup");
|
|
722
|
-
await setup();
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
case "start": {
|
|
726
|
-
// A foreground run still needs a drain token on disk: `cartethyia stop`
|
|
727
|
-
// in another shell has no other handle on it, and without the token the
|
|
728
|
-
// gateway has no /admin/drain route at all.
|
|
729
|
-
process.env.CARTETHYIA_DRAIN_TOKEN ??= ensureDrainToken();
|
|
730
|
-
await import("../../src/main");
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
case "menu":
|
|
734
|
-
await startMenu();
|
|
735
|
-
return;
|
|
736
|
-
case "tray":
|
|
737
|
-
await startTray();
|
|
738
|
-
return;
|
|
739
|
-
case "stop":
|
|
740
|
-
process.exitCode = await stopRunningGateway();
|
|
741
|
-
return;
|
|
742
|
-
case "update": {
|
|
743
|
-
// Running the install from here can never work: this process is itself
|
|
744
|
-
// executing out of the package directory npm would have to rename, and on
|
|
745
|
-
// Windows that rename fails with EBUSY. Stop any running gateway and print
|
|
746
|
-
// the command instead.
|
|
747
|
-
process.exitCode = await stopRunningGateway();
|
|
748
|
-
console.log("");
|
|
749
|
-
console.log("Now run:");
|
|
750
|
-
console.log("");
|
|
751
|
-
console.log(` ${updateCommand()}`);
|
|
752
|
-
console.log("");
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
case "--help":
|
|
756
|
-
case "-h":
|
|
757
|
-
case undefined:
|
|
758
|
-
console.log(HELP);
|
|
759
|
-
return;
|
|
760
|
-
default:
|
|
761
|
-
console.error(`Unknown command: ${command}\n\n${HELP}`);
|
|
762
|
-
process.exitCode = 2;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
main().catch((error: unknown) => {
|
|
767
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
768
|
-
if (process.argv[2] === "tray") reportTrayStartup({ status: "failed", message });
|
|
769
|
-
console.error(`Cartethyia failed: ${message}`);
|
|
770
|
-
process.exitCode = 1;
|
|
771
|
-
});
|