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
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { resolveUserConfigDir } from "../../src/config/package-paths";
|
|
3
|
+
import { ENV_PATH, readEnvFile } from "./support/env";
|
|
4
|
+
import { MINIMUM_BUN_VERSION, assertBunVersion } from "./support/bun";
|
|
5
|
+
import { printUpdateInstructions } from "./command/update";
|
|
6
|
+
import { reportTrayStartup } from "./support/startup-signal";
|
|
7
|
+
|
|
8
|
+
process.env.CARTETHYIA_CONFIG_DIR ??= resolveUserConfigDir();
|
|
9
|
+
|
|
10
|
+
const HELP = `Cartethyia ${MINIMUM_BUN_VERSION}+ (Bun is required)
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
carte start, then show an arrow-key menu (no GUI needed)
|
|
14
|
+
carte --tray start with a system-tray icon instead
|
|
15
|
+
cartethyia setup [--non-interactive]
|
|
16
|
+
cartethyia start
|
|
17
|
+
cartethyia menu
|
|
18
|
+
cartethyia doctor
|
|
19
|
+
cartethyia update
|
|
20
|
+
|
|
21
|
+
carte starts the gateway in the background and leaves only the console menu on
|
|
22
|
+
your terminal — Open Console, Reset password, Backup, Update, Hide to tray,
|
|
23
|
+
Exit. Arrow keys and Enter; no desktop environment needed, so it works over SSH
|
|
24
|
+
and headless. Exiting the menu drains the gateway.
|
|
25
|
+
carte --tray uses a system-tray icon instead, which needs a graphical session.
|
|
26
|
+
Stop a gateway whose terminal is gone with Ctrl-C in that terminal.
|
|
27
|
+
Setup explains Lite (embedded SQLite) and Full (external PostgreSQL), then saves
|
|
28
|
+
configuration under the current user's Cartethyia config directory.
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/** Loads the user's `.env` into the environment without overriding it. */
|
|
32
|
+
async function loadUserConfig(): Promise<void> {
|
|
33
|
+
const configDir = process.env.CARTETHYIA_CONFIG_DIR;
|
|
34
|
+
if (!configDir) throw new Error("Cartethyia config directory could not be resolved");
|
|
35
|
+
mkdirSync(configDir, { recursive: true });
|
|
36
|
+
const values = await readEnvFile(ENV_PATH);
|
|
37
|
+
for (const [key, value] of Object.entries(values)) {
|
|
38
|
+
if (!process.env[key]) process.env[key] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function main(): Promise<void> {
|
|
43
|
+
assertBunVersion();
|
|
44
|
+
const [command] = process.argv.slice(2);
|
|
45
|
+
if (command === "setup" || command === "start" || command === "tray" || command === "menu" || command === "doctor") {
|
|
46
|
+
await loadUserConfig();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
switch (command) {
|
|
50
|
+
case "setup": {
|
|
51
|
+
const { setup } = await import("./command/setup");
|
|
52
|
+
await setup();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
case "start": {
|
|
56
|
+
const { startForeground } = await import("./command/start");
|
|
57
|
+
await startForeground();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
case "menu": {
|
|
61
|
+
const { startMenu } = await import("./command/console-menu");
|
|
62
|
+
await startMenu();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
case "tray": {
|
|
66
|
+
const { startTray } = await import("./command/tray");
|
|
67
|
+
await startTray();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
case "doctor": {
|
|
71
|
+
const { doctor } = await import("./command/doctor");
|
|
72
|
+
await doctor();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
case "update":
|
|
76
|
+
printUpdateInstructions("this install", null);
|
|
77
|
+
return;
|
|
78
|
+
case "--help":
|
|
79
|
+
case "-h":
|
|
80
|
+
case undefined:
|
|
81
|
+
console.log(HELP);
|
|
82
|
+
return;
|
|
83
|
+
default:
|
|
84
|
+
console.error(`Unknown command: ${command}\n\n${HELP}`);
|
|
85
|
+
process.exitCode = 2;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
main().catch((error: unknown) => {
|
|
90
|
+
const message = error instanceof Error ? error.message : "unknown error";
|
|
91
|
+
if (process.argv[2] === "tray") reportTrayStartup({ status: "failed", message });
|
|
92
|
+
console.error(`Cartethyia failed: ${message}`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Minimum Bun the repository declares in `package.json` `engines`. */
|
|
2
|
+
export const MINIMUM_BUN_VERSION = "1.4.0";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* True when `actual` is at least `minimum`, compared as dotted integers.
|
|
6
|
+
* A non-numeric segment reads as 0, so a malformed version fails closed
|
|
7
|
+
* against a real minimum instead of passing.
|
|
8
|
+
*/
|
|
9
|
+
export function bunVersionAtLeast(actual: string, minimum: string): boolean {
|
|
10
|
+
const left = actual.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
11
|
+
const right = minimum.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
12
|
+
const length = Math.max(left.length, right.length);
|
|
13
|
+
for (let i = 0; i < length; i += 1) {
|
|
14
|
+
const a = left[i] ?? 0;
|
|
15
|
+
const b = right[i] ?? 0;
|
|
16
|
+
if (a !== b) return a > b;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Throws unless the running Bun satisfies the declared minimum. */
|
|
22
|
+
export function assertBunVersion(): void {
|
|
23
|
+
if (bunVersionAtLeast(Bun.version, MINIMUM_BUN_VERSION)) return;
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Bun ${MINIMUM_BUN_VERSION} or newer is required (found ${Bun.version}). Install it from https://bun.sh and run this command again.`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { PACKAGE_ROOT } from "../../../src/config/package-paths";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Absolute path of the repository root.
|
|
8
|
+
*
|
|
9
|
+
* Operational scripts and the npm runtime share one package-root resolver so
|
|
10
|
+
* assets and repository config do not depend on the caller's working directory.
|
|
11
|
+
*/
|
|
12
|
+
export const PROJECT_ROOT = PACKAGE_ROOT;
|
|
13
|
+
|
|
14
|
+
/** Resolve the active `.env`; npm CLI sets a user-owned config directory. */
|
|
15
|
+
export function resolveEnvPath(env: NodeJS.ProcessEnv = process.env): string {
|
|
16
|
+
const configDir = env.CARTETHYIA_CONFIG_DIR?.trim();
|
|
17
|
+
return configDir ? resolve(configDir, ".env") : resolve(PROJECT_ROOT, ".env");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Absolute path of the active `.env`. */
|
|
21
|
+
export const ENV_PATH = resolveEnvPath();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Reads a dotenv-style file without overriding already-exported variables.
|
|
25
|
+
*/
|
|
26
|
+
export async function readEnvFile(path: string): Promise<Record<string, string>> {
|
|
27
|
+
if (!existsSync(path)) return {};
|
|
28
|
+
const content = await readFile(path, "utf8");
|
|
29
|
+
const values: Record<string, string> = {};
|
|
30
|
+
for (const line of content.split(/\r?\n/)) {
|
|
31
|
+
if (line.startsWith("#")) continue;
|
|
32
|
+
const separator = line.indexOf("=");
|
|
33
|
+
if (separator <= 0) continue;
|
|
34
|
+
const key = line.slice(0, separator).trim();
|
|
35
|
+
const value = line.slice(separator + 1).trim();
|
|
36
|
+
if (key) values[key] = value;
|
|
37
|
+
}
|
|
38
|
+
return values;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Checks if a string contains a configured placeholder value.
|
|
43
|
+
*/
|
|
44
|
+
export function hasPlaceholderSecrets(value: string): boolean {
|
|
45
|
+
const normalized = value.toLowerCase();
|
|
46
|
+
return (
|
|
47
|
+
normalized.includes("replace-with") ||
|
|
48
|
+
normalized.includes("replace_me") ||
|
|
49
|
+
normalized.includes("your_")
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generates a 256-bit hex cartethyia encryption key.
|
|
55
|
+
*
|
|
56
|
+
* Hex is preferred over base64 because it never carries padding or `+`/`/`
|
|
57
|
+
* characters that confuse some dotenv shells, and it round-trips through
|
|
58
|
+
* every adapter that accepts either encoding. The install/setup helpers call
|
|
59
|
+
* this when the key is absent or still a placeholder.
|
|
60
|
+
*/
|
|
61
|
+
export function generateCartethyiaEncryptionKey(): string {
|
|
62
|
+
const bytes = new Uint8Array(32);
|
|
63
|
+
crypto.getRandomValues(bytes);
|
|
64
|
+
let hex = "";
|
|
65
|
+
for (let i = 0; i < bytes.length; i += 1) hex += bytes[i]!.toString(16).padStart(2, "0");
|
|
66
|
+
return hex;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns the mandatory-only `.env` body derived from `.env.example`.
|
|
71
|
+
*
|
|
72
|
+
* Mandatory means the key is assigned without a leading `#`. Optional entries
|
|
73
|
+
* (leading `#`) carry built-in defaults and must not be copied into a fresh
|
|
74
|
+
* `.env` — they would override the default with a literal `example` value.
|
|
75
|
+
* The generated body therefore contains only mandatory rows, plus a single
|
|
76
|
+
* `CARTETHYIA_ENCRYPTION_KEY` replacement when the caller supplies one.
|
|
77
|
+
*/
|
|
78
|
+
export async function mandatoryEnvBody(
|
|
79
|
+
examplePath: string,
|
|
80
|
+
overrides: Readonly<Record<string, string>> = {},
|
|
81
|
+
): Promise<string> {
|
|
82
|
+
const raw = await readFile(examplePath, "utf8");
|
|
83
|
+
const lines: string[] = [];
|
|
84
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
85
|
+
const trimmed = line.trim();
|
|
86
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
87
|
+
const sep = line.indexOf("=");
|
|
88
|
+
if (sep <= 0) continue;
|
|
89
|
+
const key = line.slice(0, sep).trim();
|
|
90
|
+
if (!key) continue;
|
|
91
|
+
const value = (overrides as Record<string, string>)[key] ?? line.slice(sep + 1).trim();
|
|
92
|
+
lines.push(`${key}=${value}`);
|
|
93
|
+
}
|
|
94
|
+
// Preserve overrides even when the example row was commented out (legacy case).
|
|
95
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
96
|
+
if (!lines.some((l) => l.startsWith(`${key}=`))) lines.push(`${key}=${value}`);
|
|
97
|
+
}
|
|
98
|
+
return `${lines.join("\n")}\n`;
|
|
99
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type LocalPlatform = "windows" | "macos" | "linux" | "unknown";
|
|
2
|
+
|
|
3
|
+
/** Detects the host platform once so setup/doctor share identical labels. */
|
|
4
|
+
export function getLocalPlatform(): LocalPlatform {
|
|
5
|
+
if (process.platform === "win32") return "windows";
|
|
6
|
+
if (process.platform === "darwin") return "macos";
|
|
7
|
+
if (process.platform === "linux") return "linux";
|
|
8
|
+
return "unknown";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns actionable hints for the configured local/external service.
|
|
13
|
+
* These are hints only: setup never assumes Docker or silently mutates an
|
|
14
|
+
* external service. Windows users commonly run PostgreSQL through Laragon;
|
|
15
|
+
* Redis may be native, WSL-backed, or external.
|
|
16
|
+
*/
|
|
17
|
+
export function getOsHints(service: "postgres" | "redis"): string[] {
|
|
18
|
+
const platform = getLocalPlatform();
|
|
19
|
+
if (platform === "windows") {
|
|
20
|
+
return service === "postgres"
|
|
21
|
+
? [
|
|
22
|
+
"Windows / Laragon:",
|
|
23
|
+
" Start Laragon (Start All) and ensure PostgreSQL is enabled.",
|
|
24
|
+
" Verify PostgreSQL is listening on the DATABASE_URL host/port (usually 5432).",
|
|
25
|
+
" External PostgreSQL is also supported — keep DATABASE_URL unchanged and reachable.",
|
|
26
|
+
]
|
|
27
|
+
: [
|
|
28
|
+
"Windows / Redis:",
|
|
29
|
+
" Start Redis through WSL, a native Redis-compatible service, or an external host.",
|
|
30
|
+
" Verify Redis is listening on the REDIS_URL host/port (usually 6379).",
|
|
31
|
+
" Or omit REDIS_URL to use the in-memory backend.",
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
if (platform === "macos") {
|
|
35
|
+
return service === "postgres"
|
|
36
|
+
? [
|
|
37
|
+
"macOS / Homebrew:",
|
|
38
|
+
" brew services start postgresql@16 (or the installed PostgreSQL version).",
|
|
39
|
+
" External PostgreSQL is also supported — verify DATABASE_URL reachability.",
|
|
40
|
+
]
|
|
41
|
+
: [
|
|
42
|
+
"macOS / Redis:",
|
|
43
|
+
" brew services start redis, or use an external Redis instance.",
|
|
44
|
+
" Or omit REDIS_URL to use the in-memory backend.",
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
if (platform === "linux") {
|
|
48
|
+
return service === "postgres"
|
|
49
|
+
? [
|
|
50
|
+
"Linux / systemd:",
|
|
51
|
+
" sudo systemctl start postgresql (or the installed PostgreSQL service).",
|
|
52
|
+
" External PostgreSQL is also supported — verify DATABASE_URL reachability.",
|
|
53
|
+
]
|
|
54
|
+
: [
|
|
55
|
+
"Linux / Redis:",
|
|
56
|
+
" sudo systemctl start redis-server (or the installed Redis service).",
|
|
57
|
+
" Or omit REDIS_URL to use the in-memory backend.",
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
return [
|
|
61
|
+
`${service} is not reachable on this platform.`,
|
|
62
|
+
"Verify the configured service URL or use an external service.",
|
|
63
|
+
];
|
|
64
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TCP reachability checks for the services setup and doctor ask about.
|
|
3
|
+
*
|
|
4
|
+
* These are extracted from the env helpers so the dotenv reader and the probe
|
|
5
|
+
* stay independent: doctor can be imported without pulling in `.env` parsing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ProbeResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Opens a TCP connection to `host:port` and resolves once it connects, errors,
|
|
15
|
+
* or the timeout elapses. The socket is always closed so a probe never leaves a
|
|
16
|
+
* half-open connection behind.
|
|
17
|
+
*/
|
|
18
|
+
export async function probeTcpService(
|
|
19
|
+
host: string,
|
|
20
|
+
port: number,
|
|
21
|
+
timeoutMs: number,
|
|
22
|
+
): Promise<ProbeResult> {
|
|
23
|
+
const { promise, resolve } = Promise.withResolvers<ProbeResult>();
|
|
24
|
+
|
|
25
|
+
const timeoutHandle = setTimeout(() => {
|
|
26
|
+
resolve({ success: false, error: "timeout" });
|
|
27
|
+
}, timeoutMs);
|
|
28
|
+
|
|
29
|
+
Bun.connect({
|
|
30
|
+
hostname: host,
|
|
31
|
+
port: port,
|
|
32
|
+
socket: {
|
|
33
|
+
open: () => {
|
|
34
|
+
clearTimeout(timeoutHandle);
|
|
35
|
+
resolve({ success: true });
|
|
36
|
+
},
|
|
37
|
+
data: () => {},
|
|
38
|
+
error: (err) => {
|
|
39
|
+
clearTimeout(timeoutHandle);
|
|
40
|
+
resolve({ success: false, error: String(err) });
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
.then((socket) => {
|
|
45
|
+
socket.end();
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
clearTimeout(timeoutHandle);
|
|
49
|
+
resolve({ success: false, error: String(err) });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return promise;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Local PostgreSQL ports worth probing before asking for a URL. 5432 is the
|
|
57
|
+
* server default (Laragon, Homebrew, systemd); 5433 is the usual second
|
|
58
|
+
* instance when 5432 is already taken.
|
|
59
|
+
*/
|
|
60
|
+
export const LOCAL_POSTGRES_CANDIDATES = [
|
|
61
|
+
{ host: "127.0.0.1", port: 5432 },
|
|
62
|
+
{ host: "127.0.0.1", port: 5433 },
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
/** First local PostgreSQL that accepts a TCP connection, or null. */
|
|
66
|
+
export async function detectLocalPostgres(
|
|
67
|
+
timeoutMs: number,
|
|
68
|
+
): Promise<{ host: string; port: number } | null> {
|
|
69
|
+
for (const candidate of LOCAL_POSTGRES_CANDIDATES) {
|
|
70
|
+
const probe = await probeTcpService(candidate.host, candidate.port, timeoutMs);
|
|
71
|
+
if (probe.success) return candidate;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parses a service URL and requires an explicit host and port.
|
|
78
|
+
* Returns null if the URL is invalid or incomplete.
|
|
79
|
+
*/
|
|
80
|
+
export function parseServiceUrl(url: string): { host: string; port: number } | null {
|
|
81
|
+
try {
|
|
82
|
+
const urlObj = new URL(url);
|
|
83
|
+
if (!urlObj.hostname || !urlObj.port) return null;
|
|
84
|
+
return { host: urlObj.hostname, port: Number(urlObj.port) };
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reports tray readiness to the launcher that is waiting on a signal file.
|
|
5
|
+
*
|
|
6
|
+
* The tray is spawned detached with no stdio, so the launcher cannot read its
|
|
7
|
+
* output; a one-shot file is the only channel that survives the parent exiting.
|
|
8
|
+
* `flag: "wx"` makes the write fail rather than overwrite, which keeps a late
|
|
9
|
+
* report from replacing the failure the launcher already acted on.
|
|
10
|
+
*/
|
|
11
|
+
export type TrayStartupSignal =
|
|
12
|
+
| { readonly status: "ready"; readonly url: string }
|
|
13
|
+
| { readonly status: "failed"; readonly message: string };
|
|
14
|
+
|
|
15
|
+
export function reportTrayStartup(signal: TrayStartupSignal): void {
|
|
16
|
+
const signalPath = process.env.CARTETHYIA_TRAY_STARTUP_SIGNAL;
|
|
17
|
+
if (!signalPath) return;
|
|
18
|
+
try {
|
|
19
|
+
writeFileSync(signalPath, JSON.stringify(signal), { encoding: "utf8", flag: "wx" });
|
|
20
|
+
} catch {
|
|
21
|
+
// The launcher may have timed out and removed the one-shot signal file.
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolvePackagePath } from "
|
|
1
|
+
import { resolvePackagePath } from "../../../src/config/package-paths";
|
|
2
2
|
|
|
3
3
|
/** Reads the version from the installed package manifest. */
|
|
4
4
|
export async function readInstalledVersion(): Promise<string> {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
' Launches a command with no console window at all.
|
|
2
|
+
'
|
|
3
|
+
' Why a VBS: `wscript.exe` is a GUI-subsystem host, so it never allocates a
|
|
4
|
+
' console for itself. Everything it starts with window style 0 inherits that —
|
|
5
|
+
' no console window is shown, and none is left behind. That is different from
|
|
6
|
+
' spawning the child directly: a console-subsystem child spawned from a
|
|
7
|
+
' window-less parent gets a new console unless CREATE_NO_WINDOW is passed, and
|
|
8
|
+
' when the parent lives inside Windows Terminal the tab is already open before
|
|
9
|
+
' any flag can take effect.
|
|
10
|
+
'
|
|
11
|
+
' Usage: wscript.exe //nologo launch-hidden.vbs <exe> [args...]
|
|
12
|
+
' The third argument (True) would make wscript wait for the child; it must stay
|
|
13
|
+
' False so the launcher returns immediately and the child outlives it.
|
|
14
|
+
|
|
15
|
+
Set shell = CreateObject("WScript.Shell")
|
|
16
|
+
|
|
17
|
+
command = ""
|
|
18
|
+
For Each arg In WScript.Arguments
|
|
19
|
+
If command <> "" Then command = command & " "
|
|
20
|
+
command = command & """" & arg & """"
|
|
21
|
+
Next
|
|
22
|
+
|
|
23
|
+
If command = "" Then
|
|
24
|
+
WScript.Quit 2
|
|
25
|
+
End If
|
|
26
|
+
|
|
27
|
+
' 0 = hidden window, False = do not wait.
|
|
28
|
+
shell.Run command, 0, False
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import SysTray from "systray2";
|
|
4
|
-
import { resolvePackagePath } from "
|
|
4
|
+
import { resolvePackagePath } from "../../../src/config/package-paths";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* One tray row. `action` rows report their id on click; separators are drawn
|
|
@@ -66,7 +66,7 @@ class WindowsTray implements Tray {
|
|
|
66
66
|
"-OutputFormat",
|
|
67
67
|
"Text",
|
|
68
68
|
"-File",
|
|
69
|
-
resolvePackagePath("scripts", "cli", "tray.ps1"),
|
|
69
|
+
resolvePackagePath("scripts", "cli", "tray", "windows.ps1"),
|
|
70
70
|
"-IconPath",
|
|
71
71
|
options.iconPath,
|
|
72
72
|
"-Tooltip",
|
|
@@ -112,7 +112,7 @@ class WindowsTray implements Tray {
|
|
|
112
112
|
let index = 0;
|
|
113
113
|
for (const item of options.items) {
|
|
114
114
|
if (item.kind === "separator") {
|
|
115
|
-
this.#send({ action: "add-
|
|
115
|
+
this.#send({ action: "add-separator", index });
|
|
116
116
|
index += 1;
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
@@ -1,98 +1,107 @@
|
|
|
1
|
-
# Cartethyia tray icon for Windows using NotifyIcon.
|
|
2
|
-
#
|
|
3
|
-
# Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
|
|
4
|
-
# helper binary and still leave the spawning process attached to a console, so
|
|
5
|
-
# Windows Terminal shows an empty tab for the tray's whole lifetime. A
|
|
6
|
-
# PowerShell NotifyIcon owns no console of its own when started with
|
|
7
|
-
# -WindowStyle Hidden, and the protocol here is plain JSON lines on
|
|
8
|
-
# stdin/stdout, so the parent stays in charge.
|
|
9
|
-
#
|
|
10
|
-
# Protocol: the parent writes one JSON object per line.
|
|
11
|
-
# {"action":"add-item","index":0,"title":"...","enabled":true}
|
|
12
|
-
# {"action":"update-item","index":0,"title":"...","enabled":true}
|
|
13
|
-
# {"action":"set-tooltip","text":"..."}
|
|
14
|
-
# {"action":"kill"}
|
|
15
|
-
# This script writes one JSON object per line:
|
|
16
|
-
# {"type":"started"} {"type":"ready"} {"type":"click","index":0}
|
|
17
|
-
param([string]$IconPath, [string]$Tooltip)
|
|
18
|
-
|
|
19
|
-
$ErrorActionPreference = "Stop"
|
|
20
|
-
|
|
21
|
-
Add-Type -AssemblyName System.Windows.Forms
|
|
22
|
-
Add-Type -AssemblyName System.Drawing
|
|
23
|
-
|
|
24
|
-
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
25
|
-
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
26
|
-
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
27
|
-
|
|
28
|
-
[System.Windows.Forms.Application]::EnableVisualStyles()
|
|
29
|
-
[System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
|
|
30
|
-
|
|
31
|
-
$script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
32
|
-
$script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
|
|
33
|
-
$script:notifyIcon.Text = $Tooltip
|
|
34
|
-
$script:notifyIcon.Visible = $true
|
|
35
|
-
|
|
36
|
-
$script:menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
37
|
-
$script:notifyIcon.ContextMenuStrip = $script:menu
|
|
38
|
-
$script:items = @()
|
|
39
|
-
|
|
40
|
-
function Write-Event($obj) {
|
|
41
|
-
$json = $obj | ConvertTo-Json -Compress
|
|
42
|
-
[Console]::Out.WriteLine($json)
|
|
43
|
-
[Console]::Out.Flush()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function Add-MenuItem($index, $title, $enabled) {
|
|
47
|
-
$item = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
48
|
-
$item.Text = $title
|
|
49
|
-
$item.Enabled = $enabled
|
|
50
|
-
$idx = $index
|
|
51
|
-
$item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
|
|
52
|
-
$script:menu.Items.Add($item) | Out-Null
|
|
53
|
-
$script:items += $item
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if ($
|
|
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
|
-
$script:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1
|
+
# Cartethyia tray icon for Windows using NotifyIcon.
|
|
2
|
+
#
|
|
3
|
+
# Why PowerShell instead of a Node tray library: `systray2` and friends bundle a
|
|
4
|
+
# helper binary and still leave the spawning process attached to a console, so
|
|
5
|
+
# Windows Terminal shows an empty tab for the tray's whole lifetime. A
|
|
6
|
+
# PowerShell NotifyIcon owns no console of its own when started with
|
|
7
|
+
# -WindowStyle Hidden, and the protocol here is plain JSON lines on
|
|
8
|
+
# stdin/stdout, so the parent stays in charge.
|
|
9
|
+
#
|
|
10
|
+
# Protocol: the parent writes one JSON object per line.
|
|
11
|
+
# {"action":"add-item","index":0,"title":"...","enabled":true}
|
|
12
|
+
# {"action":"update-item","index":0,"title":"...","enabled":true}
|
|
13
|
+
# {"action":"set-tooltip","text":"..."}
|
|
14
|
+
# {"action":"kill"}
|
|
15
|
+
# This script writes one JSON object per line:
|
|
16
|
+
# {"type":"started"} {"type":"ready"} {"type":"click","index":0}
|
|
17
|
+
param([string]$IconPath, [string]$Tooltip)
|
|
18
|
+
|
|
19
|
+
$ErrorActionPreference = "Stop"
|
|
20
|
+
|
|
21
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
22
|
+
Add-Type -AssemblyName System.Drawing
|
|
23
|
+
|
|
24
|
+
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
25
|
+
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
26
|
+
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
27
|
+
|
|
28
|
+
[System.Windows.Forms.Application]::EnableVisualStyles()
|
|
29
|
+
[System.Windows.Forms.Application]::SetCompatibleTextRenderingDefault($false)
|
|
30
|
+
|
|
31
|
+
$script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
32
|
+
$script:notifyIcon.Icon = New-Object System.Drawing.Icon($IconPath)
|
|
33
|
+
$script:notifyIcon.Text = $Tooltip
|
|
34
|
+
$script:notifyIcon.Visible = $true
|
|
35
|
+
|
|
36
|
+
$script:menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
37
|
+
$script:notifyIcon.ContextMenuStrip = $script:menu
|
|
38
|
+
$script:items = @()
|
|
39
|
+
|
|
40
|
+
function Write-Event($obj) {
|
|
41
|
+
$json = $obj | ConvertTo-Json -Compress
|
|
42
|
+
[Console]::Out.WriteLine($json)
|
|
43
|
+
[Console]::Out.Flush()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Add-MenuItem($index, $title, $enabled) {
|
|
47
|
+
$item = New-Object System.Windows.Forms.ToolStripMenuItem
|
|
48
|
+
$item.Text = $title
|
|
49
|
+
$item.Enabled = $enabled
|
|
50
|
+
$idx = $index
|
|
51
|
+
$item.Add_Click({ Write-Event @{ type = "click"; index = $idx } }.GetNewClosure())
|
|
52
|
+
$script:menu.Items.Add($item) | Out-Null
|
|
53
|
+
$script:items += $item
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# A real ToolStripSeparator draws the divider. Sending a "-" title instead
|
|
57
|
+
# renders a literal dash row, which is what the menu showed before.
|
|
58
|
+
function Add-MenuSeparator($index) {
|
|
59
|
+
$item = New-Object System.Windows.Forms.ToolStripSeparator
|
|
60
|
+
$script:menu.Items.Add($item) | Out-Null
|
|
61
|
+
$script:items += $item
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function Update-MenuItem($index, $title, $enabled) {
|
|
65
|
+
if ($index -lt $script:items.Count) {
|
|
66
|
+
$script:items[$index].Text = $title
|
|
67
|
+
$script:items[$index].Enabled = $enabled
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Set-Tooltip($text) {
|
|
72
|
+
# NotifyIcon.Text is capped at 63 characters.
|
|
73
|
+
if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
|
|
74
|
+
$script:notifyIcon.Text = $text
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Stdin is polled from the UI thread: a background thread reading Console.In
|
|
78
|
+
# races the message loop and deadlocks when the menu is open.
|
|
79
|
+
$script:timer = New-Object System.Windows.Forms.Timer
|
|
80
|
+
$script:timer.Interval = 100
|
|
81
|
+
$script:timer.Add_Tick({
|
|
82
|
+
try {
|
|
83
|
+
while ([Console]::In.Peek() -ne -1) {
|
|
84
|
+
$line = [Console]::In.ReadLine()
|
|
85
|
+
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
|
86
|
+
$cmd = $line | ConvertFrom-Json
|
|
87
|
+
switch ($cmd.action) {
|
|
88
|
+
"add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
|
|
89
|
+
"add-separator" { Add-MenuSeparator $cmd.index }
|
|
90
|
+
"update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
|
|
91
|
+
"set-tooltip" { Set-Tooltip $cmd.text }
|
|
92
|
+
"ready" { Write-Event @{ type = "ready" } }
|
|
93
|
+
"kill" {
|
|
94
|
+
$script:notifyIcon.Visible = $false
|
|
95
|
+
$script:notifyIcon.Dispose()
|
|
96
|
+
[System.Windows.Forms.Application]::Exit()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
Write-Event @{ type = "error"; message = $_.Exception.Message }
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
$script:timer.Start()
|
|
105
|
+
|
|
106
|
+
Write-Event @{ type = "started" }
|
|
107
|
+
[System.Windows.Forms.Application]::Run()
|