cartethyia 2.5.3 → 2.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -4
- package/README.md +2 -2
- package/dist/dashboard/assets/{BannedPage-hfCzi-Ux.js → BannedPage-CADDq4IU.js} +1 -1
- package/dist/dashboard/assets/BannedPage-CADDq4IU.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js +1 -0
- package/dist/dashboard/assets/CliToolDetailPage-_z1hID7E.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DEDyKBlC.js → CliToolsPage-DuO1Z7dq.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DuO1Z7dq.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js +1 -0
- package/dist/dashboard/assets/CombosPage-DNEPi3xU.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-bsLuZmYH.js → ConsoleLogPage-BVdpfiz1.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-BVdpfiz1.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-_UWhwziJ.js → CustomizationPage-BR5HHvbi.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-BR5HHvbi.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-mUfcknqm.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DDVIfZGW.js → LoginPage-V5faV9V2.js} +1 -1
- package/dist/dashboard/assets/LoginPage-V5faV9V2.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js +1 -0
- package/dist/dashboard/assets/ModelPicker-C7pDb41R.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js +1 -0
- package/dist/dashboard/assets/OverviewPage-BDu20Jdx.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-Dllr_aFO.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-uPiGVTTK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js +7 -0
- package/dist/dashboard/assets/ProxyPage-BaUkO-4O.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js +1 -0
- package/dist/dashboard/assets/QuotaPage-o0IFAsFb.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js +1 -0
- package/dist/dashboard/assets/SettingsPage-CYWqbXjp.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-D6pmQ86L.js → SetupPage-UB690SE2.js} +1 -1
- package/dist/dashboard/assets/SetupPage-UB690SE2.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DWvKiuSl.js → SortableList-BLujdX7N.js} +1 -1
- package/dist/dashboard/assets/SortableList-BLujdX7N.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CmkUrgdj.js → StudioPage-D5C2raAc.js} +3 -3
- package/dist/dashboard/assets/StudioPage-D5C2raAc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js +56 -0
- package/dist/dashboard/assets/UsagePage-8uIcxa1W.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js +1 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +2 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/{assert-NHwYq95t.js → assert-C9FD_JwJ.js} +1 -1
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/{badge-6uzDDvqs.js → badge-CR72dasT.js} +1 -1
- package/dist/dashboard/assets/badge-CR72dasT.js.gz +0 -0
- package/dist/dashboard/assets/{banner-4_WNg2WZ.js → banner-CI9_eSfO.js} +1 -1
- package/dist/dashboard/assets/banner-CI9_eSfO.js.gz +0 -0
- package/dist/dashboard/assets/{card--AtFedAq.js → card-DS7CQEsO.js} +1 -1
- package/dist/dashboard/assets/card-DS7CQEsO.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-B3e7dx3N.js → cli-tools-CNqzrfdb.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-NreUx6c8.js → clipboard-button-611pSsaX.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-611pSsaX.js.gz +0 -0
- package/dist/dashboard/assets/{fingerprint-pattern-BWuHtbW4.js → concurrency-BDF0ok7y.js} +1 -1
- package/dist/dashboard/assets/concurrency-BDF0ok7y.js.gz +0 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js +1 -0
- package/dist/dashboard/assets/database-Dz9N3zGd.js.gz +0 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js +1 -0
- package/dist/dashboard/assets/dialog-k37exjNt.js.gz +0 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js +1 -0
- package/dist/dashboard/assets/drawer-CX7N8ucR.js.gz +0 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js +1 -0
- package/dist/dashboard/assets/entry-B9Xvtnfj.js.gz +0 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js +1 -0
- package/dist/dashboard/assets/entry-CTP8AyiS.js.gz +0 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js +4 -0
- package/dist/dashboard/assets/entry-DxBHnauy.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-D_eM3IgG.js → github-badge-sltk1qUi.js} +1 -1
- package/dist/dashboard/assets/github-badge-sltk1qUi.js.gz +0 -0
- package/dist/dashboard/assets/{index-BydyKaDz.js → index-CmxZr14F.js} +2 -2
- package/dist/dashboard/assets/index-CmxZr14F.js.gz +0 -0
- package/dist/dashboard/assets/{inline-BcNGF8Gr.js → inline-BhY2sSnO.js} +1 -1
- package/dist/dashboard/assets/inline-BhY2sSnO.js.gz +0 -0
- package/dist/dashboard/assets/{input-BG41et9G.js → input-V1umFiBk.js} +1 -1
- package/dist/dashboard/assets/input-V1umFiBk.js.gz +0 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js +1 -0
- package/dist/dashboard/assets/layout-BBOYycyS.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Gh_W4OR_.js → list-row-1zzZYM0B.js} +1 -1
- package/dist/dashboard/assets/list-row-1zzZYM0B.js.gz +0 -0
- package/dist/dashboard/assets/{live-BJGYRBnR.js → live-BivGfuUs.js} +1 -1
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js +1 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-BkoLM5Ba.js → mutation-46P0oWMe.js} +1 -1
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js +2 -0
- package/dist/dashboard/assets/numbers-DkfMR5Qs.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-D7wX2vM5.js → query-keys-BFhGpwR1.js} +1 -1
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CcusqXA7.js → routing-CAb4F37R.js} +1 -1
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/{select-BO6h8yNv.js → select-BYR_5K2c.js} +2 -2
- package/dist/dashboard/assets/select-BYR_5K2c.js.gz +0 -0
- package/dist/dashboard/assets/{settings-mpmXTgUa.js → settings-DNX9sydi.js} +1 -1
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/{stack-DwLmjUFK.js → stack-BHTfXdz_.js} +1 -1
- package/dist/dashboard/assets/stack-BHTfXdz_.js.gz +0 -0
- package/dist/dashboard/assets/{state-DYdQmdAl.js → state-mPDRlQkl.js} +1 -1
- package/dist/dashboard/assets/state-mPDRlQkl.js.gz +0 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js +1 -0
- package/dist/dashboard/assets/switch-DOmJxg7H.js.gz +0 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js +1 -0
- package/dist/dashboard/assets/terminal-0_IjPWTk.js.gz +0 -0
- package/dist/dashboard/assets/tokens-COzario9.js +1 -0
- package/dist/dashboard/assets/tokens-COzario9.js.gz +0 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js +1 -0
- package/dist/dashboard/assets/use-modal-focus-By7oJoGd.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-w4u4CVue.js → useMutation-Dem0lXQj.js} +1 -1
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js +1 -0
- package/dist/dashboard/assets/zap-C4EeVW4q.js.gz +0 -0
- package/dist/dashboard/donation/qris-risun-marke.png +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +57 -57
- package/migrations/README.md +52 -52
- package/migrations/lite-sqlite3/0000_baseline.sql +2 -2
- package/package.json +96 -95
- package/scripts/cli/admin.ts +125 -0
- package/scripts/cli/autostart.ts +237 -0
- package/scripts/cli/{ui/background.ts → background.ts} +1 -1
- package/scripts/cli/{command/console-menu.ts → console-menu.ts} +275 -248
- package/scripts/cli/doctor.ts +37 -0
- package/scripts/cli/{support/env.ts → env.ts} +99 -99
- package/scripts/cli/{gateway/spawn.ts → gateway.ts} +112 -24
- package/scripts/cli/instance.ts +224 -0
- package/scripts/cli/main.ts +88 -18
- package/scripts/cli/{support/platform.ts → platform.ts} +64 -64
- package/scripts/cli/port.ts +100 -0
- package/scripts/cli/{support/services.ts → services.ts} +88 -88
- package/scripts/cli/{command/setup.ts → setup.ts} +35 -34
- package/scripts/cli/{command/start.ts → start.ts} +10 -5
- package/scripts/cli/{support/startup-signal.ts → startup-signal.ts} +23 -23
- package/scripts/cli/tray/index.ts +62 -12
- package/scripts/cli/{command/tray.ts → tray.ts} +208 -208
- package/scripts/cli/update.ts +40 -0
- package/scripts/cli/version.ts +67 -0
- package/scripts/cli/{ui/wordmark.ts → wordmark.ts} +58 -58
- package/src/boot/app.ts +11 -7
- package/src/boot/dependencies.ts +50 -19
- package/src/boot/timeout.ts +2 -13
- package/src/config/cooldowns.ts +186 -138
- package/src/config/network.ts +8 -14
- package/src/config/providers.ts +7 -10
- package/src/config/runtime.ts +76 -76
- package/src/config/security.ts +15 -0
- package/src/config/spec.ts +52 -7
- package/src/config/telemetry.ts +26 -0
- package/src/config/timeouts.ts +0 -6
- package/src/console/admin/model-abuse/contracts.ts +28 -6
- package/src/console/admin/model-abuse/routes.ts +56 -3
- package/src/console/api-keys/bucket-spend.ts +15 -4
- package/src/console/api-keys/contracts.ts +410 -414
- package/src/console/api-keys/routes.ts +881 -880
- package/src/console/auth/credentials.ts +3 -3
- package/src/console/auth/setup-service.ts +2 -2
- package/src/console/backup/contracts.ts +15 -2
- package/src/console/backup/export-endpoint.ts +6 -2
- package/src/console/backup/nine-router.ts +2 -5
- package/src/console/backup/service.ts +4 -0
- package/src/console/backup/store.ts +192 -29
- package/src/console/backup/validate.ts +3 -45
- package/src/console/cli-tools/fs-ops.ts +487 -487
- package/src/console/console-router-guards.ts +1 -1
- package/src/console/console-router.ts +8 -0
- package/src/console/data-management/contracts.ts +100 -0
- package/src/console/data-management/operations.ts +234 -0
- package/src/console/data-management/routes.ts +103 -0
- package/src/console/domain-registration.ts +45 -1
- package/src/console/observability/contracts.ts +524 -485
- package/src/console/observability/live.ts +22 -0
- package/src/console/observability/store.ts +1138 -1018
- package/src/console/observability/usage-dimensions.ts +4 -4
- package/src/console/providers/catalog/catalog-projections.ts +6 -7
- package/src/console/providers/catalog/contracts.ts +6 -0
- package/src/console/providers/catalog/model-operations.ts +5 -2
- package/src/console/providers/catalog/provider-operations.ts +24 -4
- package/src/console/providers/catalog/public-model-store.ts +8 -11
- package/src/console/providers/catalog/routes.ts +7 -0
- package/src/console/providers/catalog/store.ts +1553 -1532
- package/src/console/providers/oauth/routes.ts +8 -12
- package/src/console/quota/account-quota-shared.ts +52 -39
- package/src/console/quota/account-quota-tenant.ts +6 -6
- package/src/console/quota/quota-view.ts +1 -1
- package/src/console/quota/refresh.ts +441 -429
- package/src/console/remote-support/contracts.ts +61 -0
- package/src/console/remote-support/routes.ts +65 -0
- package/src/console/remote-support/service.ts +207 -0
- package/src/console/response-compression.ts +0 -3
- package/src/console/routing/pools/import-parse.ts +206 -0
- package/src/console/routing/pools/operations.ts +543 -501
- package/src/console/routing/pools/probe-result.ts +3 -2
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -164
- package/src/console/routing/pools/store.ts +8 -8
- package/src/console/settings/contracts.ts +95 -2
- package/src/console/settings/store.ts +81 -3
- package/src/console/shared/cursor.ts +8 -8
- package/src/console/shared/errors.ts +1 -3
- package/src/console/updates/routes.ts +16 -0
- package/src/main.ts +10 -0
- package/src/network/http2-bidi.ts +2 -4
- package/src/network/pool/agent.ts +478 -478
- package/src/network/pool/selector.ts +5 -4
- package/src/network/retry.ts +1 -2
- package/src/network/ssrf.ts +5 -4
- package/src/observability/event-compaction.ts +317 -0
- package/src/observability/event-day-index.ts +440 -0
- package/src/observability/event-log.ts +155 -6
- package/src/observability/logger.ts +6 -6
- package/src/observability/payload-capture.ts +158 -33
- package/src/observability/payload-store.ts +82 -61
- package/src/observability/redaction.ts +5 -6
- package/src/observability/request-notify.ts +43 -0
- package/src/observability/telemetry-status.ts +8 -9
- package/src/observability/token-speed.ts +5 -4
- package/src/persistence/db-mode.ts +7 -7
- package/src/persistence/health-event-flush.ts +36 -0
- package/src/persistence/readiness.ts +1 -1
- package/src/persistence/schema.ts +75 -29
- package/src/persistence/shared-cache.ts +1 -2
- package/src/persistence/stores/api-key-store.ts +18 -6
- package/src/persistence/stores/share-store.ts +4 -3
- package/src/persistence/stores/telemetry-store.ts +33 -11
- package/src/protocol/canonical-model.ts +11 -3
- package/src/protocol/codecs.ts +139 -6
- package/src/protocol/gateway-error.ts +6 -4
- package/src/protocol/image-clamp.ts +19 -1
- package/src/protocol/registry.ts +8 -0
- package/src/protocol/request/chat.ts +21 -1
- package/src/protocol/request/codex.ts +10 -1
- package/src/protocol/request/gemini.ts +9 -2
- package/src/protocol/request/messages.ts +18 -7
- package/src/protocol/request/responses.ts +9 -1
- package/src/protocol/response/chat.ts +34 -4
- package/src/protocol/response/codex.ts +7 -4
- package/src/protocol/response/dsml.ts +140 -28
- package/src/protocol/response/responses.ts +11 -5
- package/src/protocol/stream-error-frames.ts +4 -5
- package/src/providers/adapters/compatible-adapter.ts +3 -0
- package/src/providers/authentication/jwt-validator.ts +9 -10
- package/src/providers/authentication/oauth-refresh-service.ts +9 -11
- package/src/providers/bundled-discovery-providers.ts +122 -119
- package/src/providers/configured-openai-providers.ts +4 -4
- package/src/providers/default-registry.ts +5 -4
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/catalog/catalog-format.ts +3 -6
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/openai-model-discovery.ts +5 -0
- package/src/providers/discovery/openrouter-free-roster.ts +15 -17
- package/src/providers/discovery/probe-phases.ts +4 -4
- package/src/providers/discovery/probe-wire.ts +6 -13
- package/src/providers/discovery/probing-service.ts +1117 -1045
- package/src/providers/integrations/alibabacp/alibabacp.ts +1 -1
- package/src/providers/integrations/antigravity/antigravity-quota.ts +5 -7
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +23 -2
- package/src/providers/integrations/buddy/buddy-checkin.ts +1 -4
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +23 -6
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +0 -1
- package/src/providers/integrations/buddy/buddy-identity.ts +52 -0
- package/src/providers/integrations/buddy/buddy-media.ts +2 -2
- package/src/providers/integrations/buddy/buddy-usage-store.ts +3 -4
- package/src/providers/integrations/buddy/codebuddy/quota.ts +2 -13
- package/src/providers/integrations/buddy/codebuddy/shared.ts +118 -115
- package/src/providers/integrations/buddy/workbuddy/quota.ts +0 -11
- package/src/providers/integrations/buddy/workbuddy/shared.ts +5 -2
- package/src/providers/integrations/claude/claude.ts +608 -608
- package/src/providers/integrations/cline/cline-quota.ts +27 -20
- package/src/providers/integrations/cline/cline.ts +537 -539
- package/src/providers/integrations/codex/codex-headers.ts +25 -39
- package/src/providers/integrations/codex/codex-oauth.ts +1 -1
- package/src/providers/integrations/codex/codex.ts +2 -29
- package/src/providers/integrations/commandcode/commandcode.ts +674 -675
- package/src/providers/integrations/commandcode/discovery.ts +117 -116
- package/src/providers/integrations/cursor/cursor-auth.ts +418 -418
- package/src/providers/integrations/cursor/cursor-quota.ts +309 -0
- package/src/providers/integrations/cursor/cursor-request.ts +616 -471
- package/src/providers/integrations/cursor/cursor-session-manager.ts +319 -281
- package/src/providers/integrations/cursor/cursor.ts +1333 -960
- package/src/providers/integrations/cursor/protocol/messages.ts +25 -0
- package/src/providers/integrations/factory/factory-models.ts +3 -5
- package/src/providers/integrations/factory/factory.ts +8 -1
- package/src/providers/integrations/gemini.ts +6 -1
- package/src/providers/integrations/grok/grok-install-id.ts +2 -3
- package/src/providers/integrations/grok/grok-quota.ts +279 -177
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -0
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -0
- package/src/providers/integrations/grok/grok.ts +6 -7
- package/src/providers/integrations/hermes.ts +14 -18
- package/src/providers/integrations/kiro/kiro-discovery.ts +1 -1
- package/src/providers/integrations/kiro/kiro.ts +1 -1
- package/src/providers/integrations/mistral.ts +6 -6
- package/src/providers/integrations/opencode/fingerprint.ts +3 -3
- package/src/providers/integrations/opencode/provider.ts +19 -20
- package/src/providers/integrations/qoder/qoder-catalog.ts +450 -450
- package/src/providers/integrations/qoder/qoder-oauth.ts +388 -310
- package/src/providers/integrations/qoder/qoder-quota.ts +192 -0
- package/src/providers/integrations/qoder/qoder.ts +884 -865
- package/src/providers/integrations/shared/connect.ts +6 -6
- package/src/providers/integrations/trae/trae-catalog.ts +1 -2
- package/src/providers/integrations/trae/trae-quota.ts +384 -0
- package/src/providers/integrations/trae/trae.ts +2 -2
- package/src/providers/integrations/zcode/oauth.ts +6 -1
- package/src/providers/operations/account-health-service.ts +75 -27
- package/src/providers/operations/account-reset-service.ts +1 -1
- package/src/providers/operations/client-versions.ts +6 -6
- package/src/providers/operations/custom-cli-headers.ts +3 -4
- package/src/providers/operations/provider-catalog-seeder.ts +95 -95
- package/src/providers/operations/provider-catalog-service.ts +3 -4
- package/src/providers/provider-contracts.ts +7 -0
- package/src/providers/provider-metadata.ts +10 -8
- package/src/providers/provider-registry.ts +6 -7
- package/src/providers/provider-usage.ts +1 -1
- package/src/providers/quota/quota-support.ts +8 -0
- package/src/security/abuse.ts +15 -10
- package/src/security/access-control.ts +144 -146
- package/src/security/admission/contracts.ts +11 -0
- package/src/security/admission/in-memory-store.ts +4 -3
- package/src/security/admission/lease-sweep.ts +6 -6
- package/src/security/admission/reasons.ts +5 -1
- package/src/security/admission/redis-store.ts +211 -210
- package/src/security/admission/service.ts +4 -0
- package/src/security/api-key-auth.ts +27 -17
- package/src/security/ip-boundary.ts +155 -15
- package/src/security/model-abuse.ts +366 -94
- package/src/security/outbound-headers.ts +3 -4
- package/src/shared/sleep.ts +4 -0
- package/src/transport/dispatch/attempt-finalize.ts +47 -5
- package/src/transport/dispatch/attempt-loop.ts +531 -530
- package/src/transport/dispatch/generation.ts +6 -0
- package/src/transport/dispatch/proxy-request.ts +24 -1
- package/src/transport/dispatch/ratelimit-capture.ts +37 -0
- package/src/transport/dispatch/stream-bridge.ts +1 -3
- package/src/transport/dispatch/streaming/finalize.ts +1 -1
- package/src/transport/dispatch/streaming/stream.ts +19 -6
- package/src/transport/dispatch/upstream.ts +9 -0
- package/src/transport/failure-policy.ts +162 -8
- package/src/transport/guards/auth-guard.ts +7 -4
- package/src/transport/guards/concurrency-guard.ts +0 -5
- package/src/transport/guards/error-lifecycle.ts +13 -6
- package/src/transport/guards/pipeline.ts +15 -3
- package/src/transport/guards/request-context.ts +8 -2
- package/src/transport/request/inflight.ts +5 -6
- package/src/transport/request/preparer.ts +9 -8
- package/src/transport/request/state.ts +21 -8
- package/src/transport/request/tenant-preferences.ts +60 -0
- package/src/transport/routing/admission.ts +4 -0
- package/src/transport/routing/route-catalog.ts +65 -10
- package/src/transport/routing/route-model.ts +46 -14
- package/src/transport/routing/router.ts +21 -14
- package/src/transport/surface/completion.ts +2 -3
- package/src/transport/surface/dialects.ts +5 -6
- package/src/transport/surface/responses/encode.ts +38 -0
- package/src/transport/surface/responses/parse.ts +4 -0
- package/src/transport/translation/capabilities.ts +2 -3
- package/src/transport/translation/quirks.ts +3 -3
- package/src/workers/buddy-growth-tasks.ts +5 -10
- package/src/workers/daily-checkin.ts +17 -23
- package/src/workers/quota-refresh-sweep.ts +26 -26
- package/dist/dashboard/assets/BannedPage-hfCzi-Ux.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js +0 -1
- package/dist/dashboard/assets/CliToolDetailPage-DLGqYEwI.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DEDyKBlC.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js +0 -1
- package/dist/dashboard/assets/CombosPage-CfuYiQ8Z.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-bsLuZmYH.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-_UWhwziJ.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-Cjfc5WFS.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DDVIfZGW.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js +0 -1
- package/dist/dashboard/assets/ModelPicker-CWkTUtpX.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js +0 -1
- package/dist/dashboard/assets/OverviewPage-B53YYMng.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-B0KaAQf0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js +0 -1
- package/dist/dashboard/assets/ProvidersPage-Dx31DgIO.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js +0 -7
- package/dist/dashboard/assets/ProxyPage-BpnR4kXT.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js +0 -1
- package/dist/dashboard/assets/QuotaPage-BpOQCnun.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js +0 -1
- package/dist/dashboard/assets/SettingsPage-DV1eo2J_.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-D6pmQ86L.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DWvKiuSl.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-CmkUrgdj.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-B47uOydr.js +0 -56
- package/dist/dashboard/assets/UsagePage-B47uOydr.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-uNywYlfv.js +0 -1
- package/dist/dashboard/assets/api-keys-uNywYlfv.js.gz +0 -0
- package/dist/dashboard/assets/app-DxivpfYG.css +0 -2
- package/dist/dashboard/assets/app-DxivpfYG.css.gz +0 -0
- package/dist/dashboard/assets/assert-NHwYq95t.js.gz +0 -0
- package/dist/dashboard/assets/badge-6uzDDvqs.js.gz +0 -0
- package/dist/dashboard/assets/banner-4_WNg2WZ.js.gz +0 -0
- package/dist/dashboard/assets/card--AtFedAq.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-B3e7dx3N.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-NreUx6c8.js.gz +0 -0
- package/dist/dashboard/assets/dialog-BtpsJU98.js +0 -1
- package/dist/dashboard/assets/dialog-BtpsJU98.js.gz +0 -0
- package/dist/dashboard/assets/download-A0SW9OCW.js +0 -1
- package/dist/dashboard/assets/download-A0SW9OCW.js.gz +0 -0
- package/dist/dashboard/assets/drawer-WwwtzihF.js +0 -1
- package/dist/dashboard/assets/drawer-WwwtzihF.js.gz +0 -0
- package/dist/dashboard/assets/entry-CkryrIea.js +0 -4
- package/dist/dashboard/assets/entry-CkryrIea.js.gz +0 -0
- package/dist/dashboard/assets/entry-TJIye_jT.js +0 -1
- package/dist/dashboard/assets/entry-TJIye_jT.js.gz +0 -0
- package/dist/dashboard/assets/entry-jy9_4-Ke.js +0 -1
- package/dist/dashboard/assets/entry-jy9_4-Ke.js.gz +0 -0
- package/dist/dashboard/assets/fingerprint-pattern-BWuHtbW4.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-D_eM3IgG.js.gz +0 -0
- package/dist/dashboard/assets/index-BydyKaDz.js.gz +0 -0
- package/dist/dashboard/assets/inline-BcNGF8Gr.js.gz +0 -0
- package/dist/dashboard/assets/input-BG41et9G.js.gz +0 -0
- package/dist/dashboard/assets/layout-C8mer_Ak.js +0 -1
- package/dist/dashboard/assets/layout-C8mer_Ak.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Gh_W4OR_.js.gz +0 -0
- package/dist/dashboard/assets/live-BJGYRBnR.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js +0 -1
- package/dist/dashboard/assets/model-bans-DeVLDA7I.js.gz +0 -0
- package/dist/dashboard/assets/mutation-BkoLM5Ba.js.gz +0 -0
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js +0 -2
- package/dist/dashboard/assets/numbers-Bs4LOOuf.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-D7wX2vM5.js.gz +0 -0
- package/dist/dashboard/assets/routing-CcusqXA7.js.gz +0 -0
- package/dist/dashboard/assets/select-BO6h8yNv.js.gz +0 -0
- package/dist/dashboard/assets/settings-mpmXTgUa.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js +0 -1
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/stack-DwLmjUFK.js.gz +0 -0
- package/dist/dashboard/assets/state-DYdQmdAl.js.gz +0 -0
- package/dist/dashboard/assets/switch-CidSfbG7.js +0 -1
- package/dist/dashboard/assets/switch-CidSfbG7.js.gz +0 -0
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js +0 -1
- package/dist/dashboard/assets/terminal-Bd4ee8TA.js.gz +0 -0
- package/dist/dashboard/assets/tokens-DDxMbSRL.js +0 -1
- package/dist/dashboard/assets/tokens-DDxMbSRL.js.gz +0 -0
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js +0 -1
- package/dist/dashboard/assets/use-presence-BSZEBx7U.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-w4u4CVue.js.gz +0 -0
- package/scripts/cli/command/admin.ts +0 -65
- package/scripts/cli/command/doctor.ts +0 -257
- package/scripts/cli/command/update.ts +0 -23
- package/scripts/cli/gateway/drain-token.ts +0 -78
- package/scripts/cli/gateway/instance-lock.ts +0 -105
- package/scripts/cli/gateway/stop.ts +0 -51
- package/scripts/cli/support/bun.ts +0 -27
- package/scripts/cli/support/version.ts +0 -37
- /package/dist/dashboard/assets/{app-CafWtVXA.js → app-BIt446yE.js} +0 -0
- /package/dist/dashboard/assets/{app-CafWtVXA.js.gz → app-BIt446yE.js.gz} +0 -0
- /package/scripts/cli/{ui/prompt.ts → prompt.ts} +0 -0
|
@@ -3,8 +3,7 @@ import { stdin as input, stdout as output } from "node:process";
|
|
|
3
3
|
import { existsSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
import { MINIMUM_BUN_VERSION, bunVersionAtLeast } from "../support/bun";
|
|
6
|
+
import { readRequiredBunVersion, bunVersionAtLeast } from "./version";
|
|
8
7
|
import {
|
|
9
8
|
ENV_PATH,
|
|
10
9
|
PROJECT_ROOT,
|
|
@@ -12,11 +11,12 @@ import {
|
|
|
12
11
|
hasPlaceholderSecrets,
|
|
13
12
|
mandatoryEnvBody,
|
|
14
13
|
readEnvFile,
|
|
15
|
-
} from "
|
|
16
|
-
import { getLocalPlatform, getOsHints } from "
|
|
17
|
-
import { detectLocalPostgres, parseServiceUrl, probeTcpService } from "
|
|
18
|
-
import type { ProbeResult } from "
|
|
19
|
-
import {
|
|
14
|
+
} from "./env";
|
|
15
|
+
import { getLocalPlatform, getOsHints } from "./platform";
|
|
16
|
+
import { detectLocalPostgres, parseServiceUrl, probeTcpService } from "./services";
|
|
17
|
+
import type { ProbeResult } from "./services";
|
|
18
|
+
import { blockedPortMessage, probeListenPort } from "./port";
|
|
19
|
+
import { resolveDataDir, resolveDbMode } from "../../src/persistence/db-mode";
|
|
20
20
|
|
|
21
21
|
async function ask(question: string, defaultValue = ""): Promise<string> {
|
|
22
22
|
const rl = createInterface({ input, output });
|
|
@@ -145,11 +145,12 @@ function setupCommand(): string {
|
|
|
145
145
|
* Refuses to continue on a Bun older than the repository declares. Printed as
|
|
146
146
|
* one sentence: a stack trace here tells a first-time installer nothing.
|
|
147
147
|
*/
|
|
148
|
-
function
|
|
148
|
+
async function assertLocalBunVersion(): Promise<void> {
|
|
149
149
|
const actual = Bun.version;
|
|
150
|
-
|
|
150
|
+
const minimum = await readRequiredBunVersion();
|
|
151
|
+
if (bunVersionAtLeast(actual, minimum)) return;
|
|
151
152
|
throw new Error(
|
|
152
|
-
`Bun ${
|
|
153
|
+
`Bun ${minimum} or newer is required (this is ${actual}). Install it from https://bun.sh and run this again.`,
|
|
153
154
|
);
|
|
154
155
|
}
|
|
155
156
|
|
|
@@ -179,25 +180,8 @@ async function chooseDbMode(envExisted: boolean): Promise<void> {
|
|
|
179
180
|
/** Port the example ships with. A VPS NAT often does not forward it. */
|
|
180
181
|
const DEFAULT_PORT = "12800";
|
|
181
182
|
|
|
182
|
-
/** Ports this installer suggests when 12800 is taken. */
|
|
183
183
|
const PORT_SUGGESTIONS: readonly string[] = ["8080", "3000", "8081"];
|
|
184
184
|
|
|
185
|
-
/**
|
|
186
|
-
* True when nothing is listening on the port yet.
|
|
187
|
-
*
|
|
188
|
-
* Binding is the test, not connecting: only a real bind proves the port is
|
|
189
|
-
* free, and the socket is closed immediately so the probe never holds it.
|
|
190
|
-
*/
|
|
191
|
-
function isPortFree(host: string, port: number): Promise<boolean> {
|
|
192
|
-
return new Promise((resolve) => {
|
|
193
|
-
const server = createServer();
|
|
194
|
-
server.once("error", () => resolve(false));
|
|
195
|
-
server.listen(port, host, () => {
|
|
196
|
-
server.close(() => resolve(true));
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
185
|
/**
|
|
202
186
|
* Asks which port the gateway should listen on. Enter keeps 12800. The
|
|
203
187
|
* candidates are probed before the prompt so the operator is told what is
|
|
@@ -205,14 +189,21 @@ function isPortFree(host: string, port: number): Promise<boolean> {
|
|
|
205
189
|
* production build; the dashboard dev server stays on 5173 and proxies to it.
|
|
206
190
|
*/
|
|
207
191
|
async function choosePort(envExisted: boolean): Promise<void> {
|
|
208
|
-
|
|
209
|
-
|
|
192
|
+
if (nonInteractive()) return;
|
|
193
|
+
const currentPort = process.env.PORT?.trim() || DEFAULT_PORT;
|
|
194
|
+
if (envExisted) {
|
|
195
|
+
const answer = await ask(`Keep the current port ${currentPort}? yes / no`, "yes");
|
|
196
|
+
if (answer.toLowerCase() === "yes" || answer.toLowerCase() === "y") return;
|
|
197
|
+
if (answer.toLowerCase() !== "no" && answer.toLowerCase() !== "n") {
|
|
198
|
+
throw new Error(`Port choice must be yes or no (got "${answer}")`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
210
201
|
|
|
211
|
-
const candidates = [DEFAULT_PORT, ...PORT_SUGGESTIONS];
|
|
202
|
+
const candidates = [...new Set([currentPort, DEFAULT_PORT, ...PORT_SUGGESTIONS])];
|
|
212
203
|
const states = await Promise.all(
|
|
213
204
|
candidates.map(async (port) => ({
|
|
214
205
|
port,
|
|
215
|
-
free: await
|
|
206
|
+
free: port === currentPort || (await probeListenPort(Number(port))).status === "free",
|
|
216
207
|
})),
|
|
217
208
|
);
|
|
218
209
|
const free = states.filter((s) => s.free).map((s) => s.port);
|
|
@@ -230,10 +221,16 @@ async function choosePort(envExisted: boolean): Promise<void> {
|
|
|
230
221
|
if (taken.length > 0) console.log(`In use: ${taken.join(", ")}`);
|
|
231
222
|
console.log("Pick 8080 (or any open port) when a VPS does not forward 12800.");
|
|
232
223
|
|
|
233
|
-
const answer = await ask("Port",
|
|
224
|
+
const answer = await ask("Port", currentPort);
|
|
234
225
|
if (!/^[1-9][0-9]{0,4}$/.test(answer) || Number(answer) > 65535) {
|
|
235
226
|
throw new Error(`Port must be a number from 1 to 65535 (got "${answer}")`);
|
|
236
227
|
}
|
|
228
|
+
if (answer !== currentPort) {
|
|
229
|
+
const selected = await probeListenPort(Number(answer));
|
|
230
|
+
if (selected.status === "blocked") {
|
|
231
|
+
throw new Error(`${blockedPortMessage(Number(answer))} Port selection was not saved.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
237
234
|
if (!defaultFree && answer === DEFAULT_PORT) {
|
|
238
235
|
console.log(`⚠ ${DEFAULT_PORT} is in use — starting will fail unless whatever holds it is stopped first.`);
|
|
239
236
|
}
|
|
@@ -302,7 +299,7 @@ async function ensureFullDatabase(timeoutMs: number): Promise<void> {
|
|
|
302
299
|
|
|
303
300
|
export async function setup(): Promise<void> {
|
|
304
301
|
console.log("🚀 Setting up Cartethyia...\n");
|
|
305
|
-
|
|
302
|
+
await assertLocalBunVersion();
|
|
306
303
|
|
|
307
304
|
// Step 1: Setup the user config file for npm installs, repo .env for dev.
|
|
308
305
|
const envPath = ENV_PATH;
|
|
@@ -323,6 +320,8 @@ export async function setup(): Promise<void> {
|
|
|
323
320
|
// Step 2: Load the file, then choose the mode. A fresh file carries the
|
|
324
321
|
// example default, which is not a choice the operator made, so it is asked.
|
|
325
322
|
const fileEnv = await readEnvFile(envPath);
|
|
323
|
+
// See main.ts: Bun's automatic PORT default must not mask a saved choice.
|
|
324
|
+
if (process.env.PORT === "12800") delete process.env.PORT;
|
|
326
325
|
for (const [key, value] of Object.entries(fileEnv)) {
|
|
327
326
|
if (!process.env[key]) process.env[key] = value;
|
|
328
327
|
}
|
|
@@ -376,6 +375,8 @@ export async function setup(): Promise<void> {
|
|
|
376
375
|
console.log("🗂️ Lite mode: embedded SQLite, no PostgreSQL to install");
|
|
377
376
|
const dataDir = resolveDataDir();
|
|
378
377
|
mkdirSync(dataDir, { recursive: true });
|
|
378
|
+
mkdirSync(process.env.CARTETHYIA_TELEMETRY_PAYLOAD_DIR?.trim() || resolve(dataDir, "telemetry-payloads"), { recursive: true });
|
|
379
|
+
mkdirSync(process.env.CARTETHYIA_INSTALL_ID_DIR?.trim() || resolve(dataDir, ".cartethyia"), { recursive: true });
|
|
379
380
|
console.log(`✓ Data directory ready at ${dataDir}\n`);
|
|
380
381
|
} else {
|
|
381
382
|
console.log("🐘 Checking PostgreSQL...");
|
|
@@ -429,7 +430,7 @@ export async function setup(): Promise<void> {
|
|
|
429
430
|
console.log(" Production. Builds the dashboard, AOT manifest, and binary first.");
|
|
430
431
|
console.log("");
|
|
431
432
|
console.log(" bun run dev");
|
|
432
|
-
console.log(" Development. No build; hot reload, dashboard on 5173, API on");
|
|
433
|
+
console.log(" Development. No build; hot reload, dashboard on 5173 (or 1931 if busy), API on");
|
|
433
434
|
console.log(` ${port}. Use this while changing source.`);
|
|
434
435
|
}
|
|
435
436
|
console.log("");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { resolvePort } from "
|
|
2
|
-
import { ensureDrainToken } from "
|
|
3
|
-
import { clearInstanceLock, pidAlive, readInstanceLock, writeInstanceLock } from "
|
|
1
|
+
import { resolvePort } from "../../src/config/runtime";
|
|
2
|
+
import { ensureDrainToken } from "./instance";
|
|
3
|
+
import { clearInstanceLock, pidAlive, readInstanceLock, writeInstanceLock } from "./instance";
|
|
4
|
+
import { blockedPortMessage, listenPortErrorMessage, probeListenPort } from "./port";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Runs the gateway in the foreground, in this process.
|
|
@@ -18,10 +19,14 @@ export async function startForeground(): Promise<void> {
|
|
|
18
19
|
const token = ensureDrainToken();
|
|
19
20
|
process.env.CARTETHYIA_DRAIN_TOKEN ??= token;
|
|
20
21
|
const port = resolvePort();
|
|
22
|
+
const probe = await probeListenPort(port);
|
|
23
|
+
if (probe.status === "blocked" && probe.code !== "EADDRINUSE") {
|
|
24
|
+
throw new Error(listenPortErrorMessage(port, probe));
|
|
25
|
+
}
|
|
21
26
|
const blocker = await inspectPortHolder(port, token);
|
|
22
27
|
if (blocker !== null) throw new Error(blocker);
|
|
23
28
|
writeInstanceLock(port, process.pid, token);
|
|
24
|
-
await import("
|
|
29
|
+
await import("../../src/main");
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
/**
|
|
@@ -50,7 +55,7 @@ async function inspectPortHolder(port: number, token: string): Promise<string |
|
|
|
50
55
|
signal: AbortSignal.timeout(3_000),
|
|
51
56
|
}).catch(() => null);
|
|
52
57
|
if (response === null) {
|
|
53
|
-
return
|
|
58
|
+
return blockedPortMessage(port);
|
|
54
59
|
}
|
|
55
60
|
if (response.status === 401) {
|
|
56
61
|
return `Another Cartethyia gateway is already running on port ${port} under a different install. Stop it from its own terminal or tray — this install will not stop a gateway it does not own.`;
|
|
@@ -1,23 +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
|
+
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,8 +1,15 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
|
-
import SysTray from "systray2";
|
|
3
|
+
import SysTray, { type MenuItem } from "systray2";
|
|
4
4
|
import { resolvePackagePath } from "../../../src/config/package-paths";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* A row as systray2 holds it. The helper numbers the rows itself and reports
|
|
8
|
+
* that number back on a click and on an update, but it is absent from the
|
|
9
|
+
* package's typings.
|
|
10
|
+
*/
|
|
11
|
+
type IdentifiedItem = MenuItem & { readonly __id?: number };
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
14
|
* One tray row. `action` rows report their id on click; separators are drawn
|
|
8
15
|
* but never fire.
|
|
@@ -162,33 +169,69 @@ class WindowsTray implements Tray {
|
|
|
162
169
|
/**
|
|
163
170
|
* macOS and Linux tray: `systray2` runs its own helper, and neither platform
|
|
164
171
|
* attaches a console window to it, so the bundled tray is used as-is.
|
|
172
|
+
*
|
|
173
|
+
* Clicks arrive only through `onClick`; a menu built without a listener shows
|
|
174
|
+
* every row and reports none of them, which is why the callbacks are wired
|
|
175
|
+
* here rather than left to the default.
|
|
165
176
|
*/
|
|
166
177
|
class UnixTray implements Tray {
|
|
167
178
|
readonly #tray: SysTray;
|
|
179
|
+
readonly #onSelect: (id: string) => void;
|
|
180
|
+
readonly #onExit: () => void;
|
|
181
|
+
/** Row objects kept by reference: a click hands back the object, not the id. */
|
|
182
|
+
readonly #items = new Map<string, IdentifiedItem>();
|
|
183
|
+
#closed = false;
|
|
168
184
|
|
|
169
185
|
constructor(options: TrayOptions) {
|
|
186
|
+
this.#onSelect = options.onSelect;
|
|
187
|
+
this.#onExit = options.onExit;
|
|
188
|
+
const items = options.items.map((item) => {
|
|
189
|
+
if (item.kind === "separator") return SysTray.separator;
|
|
190
|
+
const menuItem: IdentifiedItem = {
|
|
191
|
+
title: item.title,
|
|
192
|
+
tooltip: item.title,
|
|
193
|
+
enabled: item.enabled ?? true,
|
|
194
|
+
};
|
|
195
|
+
this.#items.set(item.id, menuItem);
|
|
196
|
+
return menuItem;
|
|
197
|
+
});
|
|
170
198
|
this.#tray = new SysTray({
|
|
171
199
|
menu: {
|
|
172
200
|
icon: options.iconPath,
|
|
173
201
|
title: "Cartethyia",
|
|
174
202
|
tooltip: options.tooltip,
|
|
175
|
-
items
|
|
176
|
-
item.kind === "separator"
|
|
177
|
-
? SysTray.separator
|
|
178
|
-
: { title: item.title, tooltip: item.title, enabled: item.enabled ?? true },
|
|
179
|
-
),
|
|
203
|
+
items,
|
|
180
204
|
},
|
|
181
205
|
copyDir: true,
|
|
182
206
|
});
|
|
183
207
|
}
|
|
184
208
|
|
|
185
|
-
ready(): Promise<void> {
|
|
186
|
-
|
|
209
|
+
async ready(): Promise<void> {
|
|
210
|
+
await this.#tray.ready();
|
|
211
|
+
// Registered after `ready()`: the helper process does not exist before
|
|
212
|
+
// that, and `onExit` attaches to it directly.
|
|
213
|
+
this.#tray.onExit(() => {
|
|
214
|
+
if (!this.#closed) this.#onExit();
|
|
215
|
+
});
|
|
216
|
+
await this.#tray.onClick((action) => {
|
|
217
|
+
for (const [id, item] of this.#items) {
|
|
218
|
+
// Identity first: `onClick` merges the clicked row into the object this
|
|
219
|
+
// class built, so a match needs no assumptions about the numbering.
|
|
220
|
+
if (item === action.item || item.__id === action.__id) {
|
|
221
|
+
this.#onSelect(id);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
187
226
|
}
|
|
188
227
|
|
|
189
228
|
update(id: string, title: string, enabled = true): void {
|
|
190
|
-
|
|
191
|
-
|
|
229
|
+
const item = this.#items.get(id);
|
|
230
|
+
// An update sent before the helper reports ready carries no row number, and
|
|
231
|
+
// systray2 then rewrites the first row instead of this one.
|
|
232
|
+
if (item?.__id === undefined) return;
|
|
233
|
+
const next: IdentifiedItem = { title, tooltip: title, enabled, __id: item.__id };
|
|
234
|
+
void this.#tray.sendAction({ type: "update-item", item: next });
|
|
192
235
|
}
|
|
193
236
|
|
|
194
237
|
setTooltip(text: string): void {
|
|
@@ -198,9 +241,16 @@ class UnixTray implements Tray {
|
|
|
198
241
|
}
|
|
199
242
|
|
|
200
243
|
async close(): Promise<void> {
|
|
201
|
-
if (this.#
|
|
244
|
+
if (this.#closed) return;
|
|
245
|
+
this.#closed = true;
|
|
246
|
+
const helper = this.#tray.process;
|
|
247
|
+
// `kill()` settles on the helper's `exit` event, so a helper that already
|
|
248
|
+
// exited would leave it pending forever and hang the shutdown after it.
|
|
249
|
+
if (helper.exitCode !== null || helper.signalCode !== null) return;
|
|
202
250
|
try {
|
|
203
|
-
|
|
251
|
+
// `false`: systray2 otherwise calls `process.exit(0)` when the helper
|
|
252
|
+
// exits, which discards the exit code this close was given.
|
|
253
|
+
await this.#tray.kill(false);
|
|
204
254
|
} catch {
|
|
205
255
|
// The helper may already be gone.
|
|
206
256
|
}
|