cartethyia 2.4.5 → 2.4.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 +4 -1
- package/README.md +18 -0
- package/dist/dashboard/assets/AccountCooldown-ChpdzxRH.js.gz +0 -0
- package/dist/dashboard/assets/{BannedPage-DUrXpp-X.js → BannedPage-QhvbJB9o.js} +1 -1
- package/dist/dashboard/assets/BannedPage-QhvbJB9o.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-BzGevuql.js → CliToolDetailPage-Bcy150Jo.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-Bcy150Jo.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-BaRM_7We.js → CliToolsPage-DyNXefph.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-DyNXefph.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js +1 -0
- package/dist/dashboard/assets/CombosPage-Cak0ljnO.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js +1 -0
- package/dist/dashboard/assets/ConsoleLogPage-CATpAEOz.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-DqUGcoH0.js → CustomizationPage-D6jEU7nL.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-D6jEU7nL.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-CZkpn0a7.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DbhTGfhb.js → LoginPage-BJlMVN4U.js} +1 -1
- package/dist/dashboard/assets/LoginPage-BJlMVN4U.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js +1 -0
- package/dist/dashboard/assets/ModelPicker-Br3CXEau.js.gz +0 -0
- package/dist/dashboard/assets/{OverviewPage-C3NIzsDx.js → OverviewPage-CQxXbo2g.js} +1 -1
- package/dist/dashboard/assets/OverviewPage-CQxXbo2g.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage--0RBNh99.js.gz +0 -0
- package/dist/dashboard/assets/ProviderIcon-paOCq8I0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js +1 -0
- package/dist/dashboard/assets/ProvidersPage-1M-C3mbu.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js +7 -0
- package/dist/dashboard/assets/ProxyPage-CXiumA53.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BGDIfIYP.js.gz +0 -0
- package/dist/dashboard/assets/{SettingsPage-YxRml8AT.js → SettingsPage-BHW4Zt06.js} +1 -1
- package/dist/dashboard/assets/SettingsPage-BHW4Zt06.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js +1 -0
- package/dist/dashboard/assets/SetupPage-VOYC_lIa.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js +1 -0
- package/dist/dashboard/assets/SortableList-DpOTnGuH.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js +31 -0
- package/dist/dashboard/assets/StudioPage-BNIDLiU6.js.gz +0 -0
- package/dist/dashboard/assets/ToolIcon-K1ckcDKg.js.gz +0 -0
- package/dist/dashboard/assets/{UsagePage-Djes-Epy.js → UsagePage-BAFaSDQV.js} +14 -14
- package/dist/dashboard/assets/UsagePage-BAFaSDQV.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js +1 -0
- package/dist/dashboard/assets/api-keys-BETfaMYZ.js.gz +0 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css +2 -0
- package/dist/dashboard/assets/app-BLCvGdzb.css.gz +0 -0
- package/dist/dashboard/assets/{app-BDJWMThu.js → app-Cjq7nrwu.js} +3 -3
- package/dist/dashboard/assets/app-Cjq7nrwu.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-Bwh6DJTK.js → assert-7J6_LVD3.js} +1 -1
- package/dist/dashboard/assets/assert-7J6_LVD3.js.gz +0 -0
- package/dist/dashboard/assets/backup-file-gdKIlg8U.js.gz +0 -0
- package/dist/dashboard/assets/{badge-BwmwEOgA.js → badge-BTyGk_DL.js} +1 -1
- package/dist/dashboard/assets/badge-BTyGk_DL.js.gz +0 -0
- package/dist/dashboard/assets/{banner-BKBfKTqn.js → banner-bujTWYM4.js} +1 -1
- package/dist/dashboard/assets/banner-bujTWYM4.js.gz +0 -0
- package/dist/dashboard/assets/brain-Cwed_72z.js.gz +0 -0
- package/dist/dashboard/assets/{card-BTdbRFFg.js → card-B0Bnx5CA.js} +1 -1
- package/dist/dashboard/assets/card-B0Bnx5CA.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-DE2ug812.js +1 -0
- package/dist/dashboard/assets/cli-tools-DE2ug812.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.js +1 -0
- package/dist/dashboard/assets/clipboard-button-R_rArH8S.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-BWIP_EvW.js} +1 -1
- package/dist/dashboard/assets/dialog-BWIP_EvW.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-BKcgVugF.js} +1 -1
- package/dist/dashboard/assets/drawer-BKcgVugF.js.gz +0 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js +4 -0
- package/dist/dashboard/assets/entry-7dpgvpJ3.js.gz +0 -0
- package/dist/dashboard/assets/entry-CcWkGR4a.js +1 -0
- package/dist/dashboard/assets/entry-CcWkGR4a.js.gz +0 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.js +1 -0
- package/dist/dashboard/assets/entry-kTiTFrvh.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-Booc5mNe.js +1 -0
- package/dist/dashboard/assets/github-badge-Booc5mNe.js.gz +0 -0
- package/dist/dashboard/assets/image-BPaouViG.js.gz +0 -0
- package/dist/dashboard/assets/{index-C4hXfAMs.js → index-D70pChfn.js} +2 -2
- package/dist/dashboard/assets/index-D70pChfn.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DCejSLlA.js → inline-D1TCqM2D.js} +1 -1
- package/dist/dashboard/assets/inline-D1TCqM2D.js.gz +0 -0
- package/dist/dashboard/assets/{input-Bn4_kNav.js → input-B9oyU93y.js} +1 -1
- package/dist/dashboard/assets/input-B9oyU93y.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-BArzXirQ.js} +1 -1
- package/dist/dashboard/assets/layout-BArzXirQ.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-BLA0sldD.js → list-row-BVZzaoHF.js} +1 -1
- package/dist/dashboard/assets/list-row-BVZzaoHF.js.gz +0 -0
- package/dist/dashboard/assets/{live-YlifPYox.js → live-EmuIayqp.js} +1 -1
- package/dist/dashboard/assets/live-EmuIayqp.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-CVvHZ-qz.js → model-bans-CSPQ9xB0.js} +1 -1
- package/dist/dashboard/assets/model-bans-CSPQ9xB0.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/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/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-jntw9-Ui.js → routing-BExgxxBU.js} +1 -1
- package/dist/dashboard/assets/routing-BExgxxBU.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-CGUuTQmj.js → select-C9mMoEpJ.js} +1 -1
- package/dist/dashboard/assets/select-C9mMoEpJ.js.gz +0 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js +1 -0
- package/dist/dashboard/assets/settings-C4_Gn_wq.js.gz +0 -0
- package/dist/dashboard/assets/shield-check-DVS0Foqh.js.gz +0 -0
- package/dist/dashboard/assets/sliders-horizontal-ClG7dBeJ.js.gz +0 -0
- package/dist/dashboard/assets/sparkles-BRaRUxAU.js.gz +0 -0
- package/dist/dashboard/assets/{stack-C1kTcOmP.js → stack-Ca5HxI5i.js} +1 -1
- package/dist/dashboard/assets/stack-Ca5HxI5i.js.gz +0 -0
- package/dist/dashboard/assets/{state-DHKdg-pm.js → state-CzUPfLXo.js} +1 -1
- package/dist/dashboard/assets/state-CzUPfLXo.js.gz +0 -0
- package/dist/dashboard/assets/stream-retry-5QnVuSSo.js.gz +0 -0
- package/dist/dashboard/assets/{switch-DIDeAiz9.js → switch-DOUIky_i.js} +1 -1
- package/dist/dashboard/assets/switch-DOUIky_i.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-C76WrMhc.js +2 -0
- package/dist/dashboard/assets/toast-C76WrMhc.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-D87FZwH0.js} +1 -1
- package/dist/dashboard/assets/useMutation-D87FZwH0.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 +123 -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/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/package.json +25 -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 +227 -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 +13 -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/spawn.ts +101 -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 +49 -0
- package/src/boot/app.ts +3 -1
- package/src/boot/dependencies.ts +36 -13
- package/src/config/providers.ts +27 -1
- package/src/config/spec.ts +11 -0
- 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/observability/contracts.ts +54 -22
- package/src/console/observability/routes.ts +31 -6
- package/src/console/observability/store.ts +68 -28
- package/src/console/providers/catalog/public-model-store.ts +91 -60
- package/src/console/providers/catalog/store.ts +5 -1
- package/src/console/quota/account-quota-tenant.ts +9 -6
- 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/network/outbound-fetch.ts +113 -25
- package/src/network/tls/bun-client-hello.ts +112 -0
- package/src/observability/event-log.ts +29 -8
- package/src/observability/metrics.ts +40 -13
- package/src/observability/runtime-metrics.ts +7 -0
- package/src/persistence/schema.ts +40 -2
- package/src/protocol/response/codex.ts +9 -75
- 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 +4 -0
- package/src/providers/discovery/catalog/catalog-format.ts +20 -0
- package/src/providers/discovery/models-dev-catalog.ts +39 -22
- package/src/providers/discovery/probe-wire.ts +29 -5
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +3 -2
- package/src/providers/integrations/buddy/buddy-cli-headers.ts +88 -0
- package/src/providers/integrations/buddy/buddy-conversation-headers.ts +4 -0
- 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/provider.ts +2 -2
- package/src/providers/integrations/buddy/codebuddy/shared.ts +106 -73
- package/src/providers/integrations/buddy/workbuddy/cn-provider.ts +5 -2
- package/src/providers/integrations/buddy/workbuddy/quota.ts +172 -159
- package/src/providers/integrations/buddy/workbuddy/shared.ts +200 -171
- package/src/providers/integrations/codex/codex.ts +5 -8
- package/src/providers/integrations/factory/factory-oauth.ts +7 -11
- package/src/providers/integrations/opencode/opencode-go-quota.ts +130 -0
- package/src/providers/integrations/opencode/provider.ts +42 -3
- package/src/providers/integrations/qoder/qoder-oauth.ts +3 -9
- package/src/providers/integrations/shared/install-id.ts +29 -2
- package/src/providers/operations/client-versions.ts +11 -1
- package/src/providers/operations/model-catalog-cache.ts +18 -3
- package/src/providers/operations/provider-catalog-service.ts +76 -0
- package/src/transport/dispatch/attempt-finalize.ts +7 -1
- package/src/transport/dispatch/attempt-loop.ts +9 -7
- package/src/transport/dispatch/empty-stream.ts +17 -26
- package/src/transport/failure-policy.ts +20 -0
- package/src/transport/routing/route-catalog.ts +42 -3
- 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/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/ProviderDetailPage-BK8i5B5X.js +0 -3
- 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/SortableList-Bol1ASmI.js +0 -1
- package/dist/dashboard/assets/StudioPage-CLP4le3U.js +0 -31
- package/dist/dashboard/assets/api-keys-CjIGQhe7.js +0 -1
- package/dist/dashboard/assets/app-Cjgo4-Lf.css +0 -2
- 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/mutation-BIizFZav.js +0 -1
- package/dist/dashboard/assets/privacy-mode-CQjbZ3xk.js +0 -1
- package/dist/dashboard/assets/settings-BAS6QbmJ.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,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()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { PACKAGE_ROOT, resolvePackagePath } from "../../../src/config/package-paths";
|
|
3
|
+
|
|
4
|
+
/** Opens a URL in the operator's default browser, ignoring a missing handler. */
|
|
5
|
+
export function openUrl(url: string): Promise<void> {
|
|
6
|
+
const command = process.platform === "win32"
|
|
7
|
+
? "explorer.exe"
|
|
8
|
+
: process.platform === "darwin"
|
|
9
|
+
? "open"
|
|
10
|
+
: "xdg-open";
|
|
11
|
+
const child = Bun.spawn([command, url], {
|
|
12
|
+
stdin: "ignore",
|
|
13
|
+
stdout: "ignore",
|
|
14
|
+
stderr: "ignore",
|
|
15
|
+
});
|
|
16
|
+
return child.exited.then(() => undefined);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Starts a Bun process that owns no console window.
|
|
21
|
+
*
|
|
22
|
+
* On Windows, spawning the child directly is not enough: a console-subsystem
|
|
23
|
+
* child of a window-less parent allocates a new console, and when the parent is
|
|
24
|
+
* inside Windows Terminal the tab is already on screen before any flag can take
|
|
25
|
+
* effect — measured: `detached: true` plus `windowsHide: true` still produced a
|
|
26
|
+
* Windows Terminal window titled with the bun path. `wscript.exe` is a
|
|
27
|
+
* GUI-subsystem host, so it never allocates a console, and a child launched
|
|
28
|
+
* from it with window style 0 inherits that. The VBS is that launcher.
|
|
29
|
+
*
|
|
30
|
+
* Elsewhere `detached` is enough and the helper is not used.
|
|
31
|
+
*/
|
|
32
|
+
export function launchBackground(args: readonly string[]): void {
|
|
33
|
+
if (process.platform !== "win32") {
|
|
34
|
+
const child = spawn(process.execPath, [...args], {
|
|
35
|
+
cwd: PACKAGE_ROOT,
|
|
36
|
+
detached: true,
|
|
37
|
+
stdio: "ignore",
|
|
38
|
+
});
|
|
39
|
+
child.unref();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const launcher = resolvePackagePath("scripts", "cli", "tray", "hidden-window.vbs");
|
|
43
|
+
const child = spawn(
|
|
44
|
+
"wscript.exe",
|
|
45
|
+
["//nologo", launcher, process.execPath, ...args],
|
|
46
|
+
{ cwd: PACKAGE_ROOT, detached: true, stdio: "ignore", windowsHide: true },
|
|
47
|
+
);
|
|
48
|
+
child.unref();
|
|
49
|
+
}
|
package/src/boot/app.ts
CHANGED
|
@@ -196,7 +196,9 @@ export function createGatewayApp(deps: GatewayAppDeps) {
|
|
|
196
196
|
const peerAddresses = new WeakMap<Request, string>();
|
|
197
197
|
const serveDashboard = async ({ request }: { request: Request }): Promise<Response> => {
|
|
198
198
|
const pathname = fastPathname(request.url);
|
|
199
|
-
|
|
199
|
+
// The client's `accept-encoding` is what decides whether a precompressed
|
|
200
|
+
// `.gz` sibling can be served instead of the plain asset.
|
|
201
|
+
const result = await staticHandler(pathname, request.headers.get("accept-encoding"));
|
|
200
202
|
return new Response(result.body ? (result.body as unknown as BodyInit) : null, {
|
|
201
203
|
status: result.status,
|
|
202
204
|
headers: result.headers,
|