cartethyia 2.5.10 → 2.6.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 +8 -0
- package/dist/dashboard/assets/{BannedPage-Cd30Z0b9.js → BannedPage-l6XMnfr9.js} +1 -1
- package/dist/dashboard/assets/BannedPage-l6XMnfr9.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage-wf5u-ww6.js → CliToolDetailPage-DwOzAW45.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DwOzAW45.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-DykPvuck.js → CliToolsPage-BT3Up3QA.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-BT3Up3QA.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-DwvSD3vs.js → CombosPage-6kYIUpzR.js} +1 -1
- package/dist/dashboard/assets/CombosPage-6kYIUpzR.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-CIr7KDex.js → ConsoleLogPage-zRnj1mlR.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-zRnj1mlR.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-82X0DDdH.js → CustomizationPage-C7m-H1Rz.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-C7m-H1Rz.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js +1 -0
- package/dist/dashboard/assets/HealthEventsModal-C8MR_BdB.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-CS5mPuVF.js → LoginPage-CbphLvjg.js} +1 -1
- package/dist/dashboard/assets/LoginPage-CbphLvjg.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-C0UCUsVJ.js → ModelPicker-DVny4S5_.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DVny4S5_.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js +1 -0
- package/dist/dashboard/assets/OverviewPage-DABu2Wgt.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js +3 -0
- package/dist/dashboard/assets/ProviderDetailPage-C15x9q13.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-vdja5ZPK.js → ProvidersPage-Dfb2896L.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-Dfb2896L.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-BB4CNm4-.js → ProxyPage-BaCIdAUt.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-BaCIdAUt.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js +1 -0
- package/dist/dashboard/assets/QuotaPage-BsdoCIw1.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js +1 -0
- package/dist/dashboard/assets/SettingsPage-KVJFh3Ip.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-FadUOl9S.js → SetupPage-D4g6vOGc.js} +1 -1
- package/dist/dashboard/assets/SetupPage-D4g6vOGc.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-Cfy6_Khv.js → SortableList-C93i14BD.js} +1 -1
- package/dist/dashboard/assets/SortableList-C93i14BD.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-CM92fgJf.js → StudioPage-SgQpymjm.js} +1 -1
- package/dist/dashboard/assets/{StudioPage-CM92fgJf.js.gz → StudioPage-SgQpymjm.js.gz} +0 -0
- package/dist/dashboard/assets/{UsagePage-BT4MyQeT.js → UsagePage-BFME-eba.js} +2 -2
- package/dist/dashboard/assets/UsagePage-BFME-eba.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-DQYLQxpW.js → api-keys-C23S9sgp.js} +1 -1
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/{app-B7pVRhJ1.js → app-2KhtHCv9.js} +1 -1
- package/dist/dashboard/assets/{app-B7pVRhJ1.js.gz → app-2KhtHCv9.js.gz} +0 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css +2 -0
- package/dist/dashboard/assets/app-CEeaMHOy.css.gz +0 -0
- package/dist/dashboard/assets/{assert-C9FD_JwJ.js → assert-DU6qrMA_.js} +1 -1
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Bvyr7D0u.js → badge-CKsF123d.js} +1 -1
- package/dist/dashboard/assets/badge-CKsF123d.js.gz +0 -0
- package/dist/dashboard/assets/{banner-DIvKAZih.js → banner-8j7uChA9.js} +1 -1
- package/dist/dashboard/assets/banner-8j7uChA9.js.gz +0 -0
- package/dist/dashboard/assets/{card-D5_gYIS_.js → card-CdsSlWA3.js} +1 -1
- package/dist/dashboard/assets/card-CdsSlWA3.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-CNqzrfdb.js → cli-tools-BaFUuFFF.js} +1 -1
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-B6vDBtbm.js → clipboard-button-uL5aYLfW.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-uL5aYLfW.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-CpjrgdwJ.js → dialog-C07hr5_G.js} +1 -1
- package/dist/dashboard/assets/dialog-C07hr5_G.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-BWmt3b5y.js → drawer-BPO1TcKH.js} +1 -1
- package/dist/dashboard/assets/drawer-BPO1TcKH.js.gz +0 -0
- package/dist/dashboard/assets/{entry-C_oakV3z.js → entry-Bz_CtDfC.js} +1 -1
- package/dist/dashboard/assets/entry-Bz_CtDfC.js.gz +0 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js +4 -0
- package/dist/dashboard/assets/entry-CwPRKk0d.js.gz +0 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js +1 -0
- package/dist/dashboard/assets/entry-GJRbmrpA.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-RRHHWGxh.js → github-badge-DmQreSDd.js} +1 -1
- package/dist/dashboard/assets/github-badge-DmQreSDd.js.gz +0 -0
- package/dist/dashboard/assets/{index-Bhey_rg_.js → index-Bblu6erR.js} +2 -2
- package/dist/dashboard/assets/index-Bblu6erR.js.gz +0 -0
- package/dist/dashboard/assets/{inline-C2_EQ5Uf.js → inline-C9dCYaBZ.js} +1 -1
- package/dist/dashboard/assets/inline-C9dCYaBZ.js.gz +0 -0
- package/dist/dashboard/assets/{input-BMRlQ450.js → input-CjCnXweJ.js} +1 -1
- package/dist/dashboard/assets/input-CjCnXweJ.js.gz +0 -0
- package/dist/dashboard/assets/{layout-XR5xP8ew.js → layout-V6y8DAkl.js} +1 -1
- package/dist/dashboard/assets/layout-V6y8DAkl.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-Cw07dioc.js → list-row-CM1AaG_y.js} +1 -1
- package/dist/dashboard/assets/list-row-CM1AaG_y.js.gz +0 -0
- package/dist/dashboard/assets/{live-BivGfuUs.js → live-ta6GMb-8.js} +1 -1
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/{model-bans-CmffjLuG.js → model-bans-BmYTmbAk.js} +1 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-46P0oWMe.js → mutation-g_fduiNw.js} +1 -1
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-DrdA9oSM.js → numbers-BOevJzdz.js} +1 -1
- package/dist/dashboard/assets/numbers-BOevJzdz.js.gz +0 -0
- package/dist/dashboard/assets/{query-keys-BFhGpwR1.js → query-keys-UETqfytp.js} +1 -1
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/{routing-CAb4F37R.js → routing-BPUo-W8d.js} +1 -1
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/{select-CxVVB__N.js → select-C980c4cz.js} +1 -1
- package/dist/dashboard/assets/select-C980c4cz.js.gz +0 -0
- package/dist/dashboard/assets/{settings-DNX9sydi.js → settings--3hxSyry.js} +1 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/{stack-uPOwM9Hs.js → stack-_gfTdLVB.js} +1 -1
- package/dist/dashboard/assets/stack-_gfTdLVB.js.gz +0 -0
- package/dist/dashboard/assets/{state-D1Yz79iL.js → state-2c5V4zo7.js} +1 -1
- package/dist/dashboard/assets/state-2c5V4zo7.js.gz +0 -0
- package/dist/dashboard/assets/{switch-sevsRrRo.js → switch-Dkke7Naf.js} +1 -1
- package/dist/dashboard/assets/switch-Dkke7Naf.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-Dem0lXQj.js → useMutation-EDFRkn1i.js} +1 -1
- package/dist/dashboard/assets/useMutation-EDFRkn1i.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 +51 -51
- package/migrations/README.md +52 -52
- package/migrations/full-postgres/0020_add_telemetry_event_analytics.sql +77 -0
- package/migrations/full-postgres/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/full-postgres/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/full-postgres/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0020_add_telemetry_event_analytics.sql +69 -0
- package/migrations/lite-sqlite3/0021_add_api_key_concurrency_tolerance.sql +13 -0
- package/migrations/lite-sqlite3/0022_add_inferhub_config_key.sql +17 -0
- package/migrations/lite-sqlite3/0023_drop_api_key_concurrency_tolerance.sql +13 -0
- package/package.json +96 -95
- package/scripts/cli/admin.ts +126 -128
- package/scripts/cli/autostart.ts +237 -237
- package/scripts/cli/background.ts +120 -110
- package/scripts/cli/bin/carte.cjs +8 -8
- package/scripts/cli/bin/cartethyia.cjs +167 -167
- package/scripts/cli/console-menu.ts +39 -30
- package/scripts/cli/controller.ts +145 -0
- package/scripts/cli/env.ts +99 -99
- package/scripts/cli/gateway.ts +264 -260
- package/scripts/cli/instance.ts +234 -224
- package/scripts/cli/main.ts +188 -167
- package/scripts/cli/platform.ts +64 -64
- package/scripts/cli/prompt.ts +217 -79
- package/scripts/cli/services.ts +88 -88
- package/scripts/cli/setup.ts +445 -445
- package/scripts/cli/start.ts +85 -85
- package/scripts/cli/startup-signal.ts +23 -23
- package/scripts/cli/telemetry.ts +70 -0
- package/scripts/cli/tray/index.ts +263 -263
- package/scripts/cli/tray/windows.ps1 +107 -107
- package/scripts/cli/tray.ts +29 -4
- package/scripts/cli/update.ts +39 -39
- package/scripts/cli/version.ts +67 -67
- package/scripts/cli/wordmark.ts +58 -58
- package/src/boot/app.ts +16 -1
- package/src/boot/dependencies.ts +128 -25
- package/src/config/resources.ts +137 -0
- package/src/config/runtime.ts +133 -76
- package/src/config/spec.ts +30 -29
- package/src/config/telemetry.ts +0 -25
- package/src/console/api-keys/bucket-spend.ts +14 -17
- package/src/console/api-keys/contracts.ts +410 -410
- package/src/console/api-keys/routes.ts +12 -0
- package/src/console/audit/store.ts +2 -3
- package/src/console/auth/setup-service.ts +8 -1
- package/src/console/backup/routes.ts +7 -0
- package/src/console/backup/service.ts +463 -463
- package/src/console/backup/store.ts +16 -11
- package/src/console/backup/validate.ts +1 -4
- package/src/console/cli-tools/fs-ops.ts +18 -2
- package/src/console/data-management/contracts.ts +1 -8
- package/src/console/data-management/operations.ts +61 -48
- package/src/console/data-management/routes.ts +4 -2
- package/src/console/domain-registration.ts +25 -3
- package/src/console/inferhub/client.ts +139 -0
- package/src/console/inferhub/key-store.ts +129 -0
- package/src/console/inferhub/normalize.ts +347 -0
- package/src/console/inferhub/routes.ts +411 -0
- package/src/console/inferhub/store.ts +109 -0
- package/src/console/inferhub/types.ts +183 -0
- package/src/console/observability/contracts.ts +2 -41
- package/src/console/observability/store.ts +176 -371
- package/src/console/providers/catalog/public-model-store.ts +0 -0
- package/src/console/providers/catalog/store.ts +23 -36
- package/src/console/quota/account-quota-shared.ts +52 -52
- package/src/console/quota/account-quota-tenant.ts +16 -24
- package/src/console/quota/refresh.ts +441 -441
- package/src/console/routing/pools/import-parse.ts +206 -206
- package/src/console/routing/pools/operations.ts +543 -543
- package/src/console/routing/pools/probe-verdict.ts +132 -132
- package/src/console/routing/pools/routes.ts +181 -181
- package/src/console/settings/contracts.ts +42 -40
- package/src/console/settings/store.ts +25 -25
- package/src/console/share/share-stats.ts +22 -4
- package/src/console/share/share-usage.ts +30 -4
- package/src/console/shared/cursor.ts +15 -0
- package/src/console/updates/routes.ts +12 -0
- package/src/main.ts +13 -5
- package/src/network/pool/resolver.ts +1104 -1104
- package/src/observability/client-name.ts +22 -0
- package/src/observability/event-log.ts +5 -29
- package/src/observability/event-query.ts +690 -0
- package/src/observability/log-ring.ts +26 -2
- package/src/observability/log-sanitize.ts +118 -0
- package/src/observability/logger.ts +17 -13
- package/src/observability/metrics.ts +35 -0
- package/src/observability/payload-capture.ts +114 -41
- package/src/observability/payload-store.ts +249 -24
- package/src/observability/raw-event-writer.ts +118 -0
- package/src/observability/runtime-metrics.ts +71 -1
- package/src/observability/telemetry-buffer.ts +100 -17
- package/src/observability/telemetry-reconciliation.ts +417 -0
- package/src/persistence/boot.ts +20 -9
- package/src/persistence/db-pg.ts +13 -0
- package/src/persistence/db-sqlite.ts +569 -497
- package/src/persistence/dialect.ts +3 -2
- package/src/persistence/migrate.ts +181 -181
- package/src/persistence/schema.ts +1496 -1406
- package/src/persistence/shared-cache.ts +5 -1
- package/src/persistence/stores/api-key-store.ts +581 -581
- package/src/persistence/stores/telemetry-store.ts +124 -63
- package/src/protocol/reasoning-text.ts +30 -0
- package/src/protocol/response/chat.ts +33 -7
- package/src/protocol/response/codex.ts +14 -2
- package/src/protocol/response/messages.ts +5 -2
- package/src/providers/authentication/oauth-flow-store.ts +3 -10
- package/src/providers/bundled-discovery-providers.ts +122 -122
- package/src/providers/default-registry.ts +10 -0
- package/src/providers/discovery/catalog/catalog-blob.ts +18 -18
- package/src/providers/discovery/models-dev-catalog.ts +354 -354
- package/src/providers/discovery/probe-phases.ts +43 -7
- package/src/providers/discovery/probing-service.ts +70 -32
- package/src/providers/integrations/antigravity/antigravity.ts +9 -2
- package/src/providers/integrations/buddy/buddy-catalog-shared.ts +85 -17
- package/src/providers/integrations/buddy/buddy-chat-shared.ts +934 -934
- package/src/providers/integrations/buddy/buddy-checkin.ts +5 -9
- package/src/providers/integrations/buddy/buddy-discovery-shared.ts +3 -7
- package/src/providers/integrations/buddy/buddy-quota-shared.ts +3 -1
- package/src/providers/integrations/buddy/codebuddy/cn-provider.ts +57 -49
- package/src/providers/integrations/buddy/codebuddy/provider.ts +180 -180
- package/src/providers/integrations/buddy/workbuddy/provider.ts +206 -206
- package/src/providers/integrations/buddy/workbuddy/quota.ts +161 -161
- package/src/providers/integrations/claude/claude-quota.ts +2 -2
- package/src/providers/integrations/claude/claude-user-profiles.ts +3 -2
- package/src/providers/integrations/claude/claude.ts +3 -4
- package/src/providers/integrations/cline/cline.ts +94 -8
- package/src/providers/integrations/codex/codex.ts +61 -37
- package/src/providers/integrations/cursor/cursor-catalog.ts +368 -368
- package/src/providers/integrations/cursor/cursor-http2.ts +300 -300
- package/src/providers/integrations/cursor/cursor-identity.ts +2 -4
- package/src/providers/integrations/cursor/cursor-request.ts +11 -0
- package/src/providers/integrations/cursor/cursor.ts +29 -17
- package/src/providers/integrations/cursor/protocol/framing.ts +129 -129
- package/src/providers/integrations/cursor/protocol/messages.ts +2380 -2380
- package/src/providers/integrations/cursor/protocol/struct.ts +305 -305
- package/src/providers/integrations/cursor/protocol/tool-canonical.ts +275 -0
- package/src/providers/integrations/grok/grok-quota.ts +279 -279
- package/src/providers/integrations/grok/grok-ratelimit-store.ts +73 -73
- package/src/providers/integrations/grok/grok-ratelimit.ts +218 -218
- package/src/providers/integrations/hermes.ts +7 -1
- package/src/providers/integrations/inferhub.ts +201 -73
- package/src/providers/integrations/kilo/kilo.ts +152 -152
- package/src/providers/integrations/kiro/kiro-machine-id.ts +2 -4
- package/src/providers/integrations/openrouter/provider.ts +129 -129
- package/src/providers/integrations/qoder/qoder-quota.ts +193 -192
- package/src/providers/integrations/qoder/qoder-realm.ts +106 -106
- package/src/providers/integrations/trae/trae-oauth.ts +4 -26
- package/src/providers/integrations/trae/trae-quota.ts +389 -384
- package/src/providers/integrations/trae/trae-realm.ts +15 -2
- package/src/providers/integrations/xiaomi-mimo/think-stream.ts +100 -24
- package/src/providers/integrations/zcode/oauth.ts +1 -1
- package/src/providers/operations/account-health-service.ts +605 -279
- package/src/providers/provider-metadata.ts +12 -1
- package/src/providers/provider-model-definition.ts +373 -373
- package/src/providers/quota/quota-contracts.ts +3 -7
- package/src/providers/search/search-provider.ts +2 -6
- package/src/security/admission/contracts.ts +10 -0
- package/src/security/admission/in-memory-store.ts +89 -14
- package/src/security/admission/lua.ts +191 -182
- package/src/security/admission/redis-store.ts +30 -0
- package/src/security/admission/service.ts +18 -0
- package/src/security/api-key-auth-cache.ts +96 -0
- package/src/security/api-key-auth.ts +11 -100
- package/src/security/client-router-fingerprint.ts +3 -7
- package/src/security/ip-boundary.ts +7 -3
- package/src/shared/coerce.ts +67 -0
- package/src/shared/hash.ts +6 -0
- package/src/shared/time.ts +7 -0
- package/src/transport/dispatch/attempt-finalize.ts +26 -138
- package/src/transport/dispatch/attempt-loop.ts +103 -81
- package/src/transport/dispatch/capture-policy.ts +78 -0
- package/src/transport/dispatch/proxy-request.ts +4 -2
- package/src/transport/dispatch/streaming/encoder.ts +111 -5
- package/src/transport/dispatch/streaming/stream.ts +19 -2
- package/src/transport/dispatch/streaming/thinking-loop.ts +300 -0
- package/src/transport/failure-policy.ts +27 -0
- package/src/transport/guards/abuse-guard.ts +2 -2
- package/src/transport/guards/auth-guard.ts +17 -0
- package/src/transport/guards/body-policy.ts +3 -2
- package/src/transport/guards/concurrency-guard.ts +89 -13
- package/src/transport/guards/early-rejection-capture.ts +43 -3
- package/src/transport/guards/error-lifecycle.ts +10 -2
- package/src/transport/guards/guard-errors.ts +23 -0
- package/src/transport/guards/headroom-guard.ts +41 -7
- package/src/transport/guards/pipeline.ts +24 -0
- package/src/transport/guards/request-context.ts +2 -1
- package/src/transport/routing/admission.ts +51 -9
- package/src/transport/routing/bounded-lru.ts +49 -0
- package/src/transport/routing/candidate-index.ts +88 -0
- package/src/transport/routing/reservations.ts +121 -7
- package/src/transport/routing/round-robin.ts +10 -22
- package/src/transport/routing/route-catalog.ts +26 -13
- package/src/transport/routing/route-model.ts +22 -2
- package/src/transport/routing/router.ts +53 -31
- package/src/transport/surface/chat/encode.ts +2 -3
- package/src/transport/surface/chat/parse.ts +2 -1
- package/src/transport/surface/dialects.ts +11 -3
- package/src/transport/surface/messages/encode.ts +2 -1
- package/src/transport/surface/messages/parse.ts +3 -1
- package/src/transport/surface/messages/stream.ts +2 -6
- package/src/workers/maintenance-lease.ts +200 -0
- package/src/workers/scheduler.ts +101 -1
- package/dist/dashboard/assets/BannedPage-Cd30Z0b9.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage-wf5u-ww6.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-DykPvuck.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-DwvSD3vs.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-CIr7KDex.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-82X0DDdH.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js +0 -1
- package/dist/dashboard/assets/HealthEventsModal-BYPucTTn.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-CS5mPuVF.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-C0UCUsVJ.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js +0 -1
- package/dist/dashboard/assets/OverviewPage-Dk_ew3YU.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js +0 -3
- package/dist/dashboard/assets/ProviderDetailPage-CAiRn2Q0.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-vdja5ZPK.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-BB4CNm4-.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js +0 -1
- package/dist/dashboard/assets/QuotaPage-FEc4iNyq.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js +0 -1
- package/dist/dashboard/assets/SettingsPage-Dlfi5L4N.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-FadUOl9S.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-Cfy6_Khv.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-BT4MyQeT.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-DQYLQxpW.js.gz +0 -0
- package/dist/dashboard/assets/app-CX_Z8cMs.css +0 -2
- package/dist/dashboard/assets/app-CX_Z8cMs.css.gz +0 -0
- package/dist/dashboard/assets/assert-C9FD_JwJ.js.gz +0 -0
- package/dist/dashboard/assets/badge-Bvyr7D0u.js.gz +0 -0
- package/dist/dashboard/assets/banner-DIvKAZih.js.gz +0 -0
- package/dist/dashboard/assets/card-D5_gYIS_.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-CNqzrfdb.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-B6vDBtbm.js.gz +0 -0
- package/dist/dashboard/assets/dialog-CpjrgdwJ.js.gz +0 -0
- package/dist/dashboard/assets/drawer-BWmt3b5y.js.gz +0 -0
- package/dist/dashboard/assets/entry-B-gvimrg.js +0 -1
- package/dist/dashboard/assets/entry-B-gvimrg.js.gz +0 -0
- package/dist/dashboard/assets/entry-C_oakV3z.js.gz +0 -0
- package/dist/dashboard/assets/entry-Dj0cHfEc.js +0 -4
- package/dist/dashboard/assets/entry-Dj0cHfEc.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-RRHHWGxh.js.gz +0 -0
- package/dist/dashboard/assets/index-Bhey_rg_.js.gz +0 -0
- package/dist/dashboard/assets/inline-C2_EQ5Uf.js.gz +0 -0
- package/dist/dashboard/assets/input-BMRlQ450.js.gz +0 -0
- package/dist/dashboard/assets/layout-XR5xP8ew.js.gz +0 -0
- package/dist/dashboard/assets/list-row-Cw07dioc.js.gz +0 -0
- package/dist/dashboard/assets/live-BivGfuUs.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-CmffjLuG.js.gz +0 -0
- package/dist/dashboard/assets/mutation-46P0oWMe.js.gz +0 -0
- package/dist/dashboard/assets/numbers-DrdA9oSM.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-BFhGpwR1.js.gz +0 -0
- package/dist/dashboard/assets/routing-CAb4F37R.js.gz +0 -0
- package/dist/dashboard/assets/select-CxVVB__N.js.gz +0 -0
- package/dist/dashboard/assets/settings-DNX9sydi.js.gz +0 -0
- package/dist/dashboard/assets/stack-uPOwM9Hs.js.gz +0 -0
- package/dist/dashboard/assets/state-D1Yz79iL.js.gz +0 -0
- package/dist/dashboard/assets/switch-sevsRrRo.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-Dem0lXQj.js.gz +0 -0
- package/src/observability/event-compaction.ts +0 -317
- package/src/observability/event-day-index.ts +0 -439
|
@@ -1,263 +1,263 @@
|
|
|
1
|
-
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
|
-
import { createInterface } from "node:readline";
|
|
3
|
-
import SysTray, { type MenuItem } from "systray2";
|
|
4
|
-
import { resolvePackagePath } from "../../../src/config/package-paths";
|
|
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
|
-
|
|
13
|
-
/**
|
|
14
|
-
* One tray row. `action` rows report their id on click; separators are drawn
|
|
15
|
-
* but never fire.
|
|
16
|
-
*/
|
|
17
|
-
export type TrayItem =
|
|
18
|
-
| { readonly kind: "action"; readonly id: string; readonly title: string; readonly enabled?: boolean }
|
|
19
|
-
| { readonly kind: "separator" };
|
|
20
|
-
|
|
21
|
-
export interface TrayOptions {
|
|
22
|
-
readonly iconPath: string;
|
|
23
|
-
readonly tooltip: string;
|
|
24
|
-
readonly items: readonly TrayItem[];
|
|
25
|
-
readonly onSelect: (id: string) => void;
|
|
26
|
-
readonly onExit: () => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface Tray {
|
|
30
|
-
/** Resolves once the icon is on screen and clicks can arrive. */
|
|
31
|
-
ready(): Promise<void>;
|
|
32
|
-
/** Replaces the title and enabled state of an existing row. */
|
|
33
|
-
update(id: string, title: string, enabled?: boolean): void;
|
|
34
|
-
setTooltip(text: string): void;
|
|
35
|
-
/** Removes the icon. Idempotent. */
|
|
36
|
-
close(): Promise<void>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Windows tray: a PowerShell NotifyIcon driven over JSON lines.
|
|
41
|
-
*
|
|
42
|
-
* `systray2` on Windows leaves the spawning process attached to a console, so
|
|
43
|
-
* Windows Terminal keeps an empty tab open for the tray's whole lifetime — the
|
|
44
|
-
* window cannot be hidden from inside the process because it is owned by the
|
|
45
|
-
* terminal, and `MainWindowHandle` reads 0 so it cannot be found and closed
|
|
46
|
-
* either. A PowerShell NotifyIcon started with `-WindowStyle Hidden` owns no
|
|
47
|
-
* console at all, which is the only arrangement that shows an icon and no
|
|
48
|
-
* window.
|
|
49
|
-
*/
|
|
50
|
-
class WindowsTray implements Tray {
|
|
51
|
-
readonly #process: ChildProcess;
|
|
52
|
-
readonly #onSelect: (id: string) => void;
|
|
53
|
-
readonly #onExit: () => void;
|
|
54
|
-
readonly #index = new Map<string, number>();
|
|
55
|
-
#ready: Promise<void>;
|
|
56
|
-
#closed = false;
|
|
57
|
-
|
|
58
|
-
constructor(options: TrayOptions) {
|
|
59
|
-
this.#onSelect = options.onSelect;
|
|
60
|
-
this.#onExit = options.onExit;
|
|
61
|
-
|
|
62
|
-
this.#process = spawn(
|
|
63
|
-
"powershell.exe",
|
|
64
|
-
[
|
|
65
|
-
"-NoProfile",
|
|
66
|
-
"-NonInteractive",
|
|
67
|
-
"-ExecutionPolicy",
|
|
68
|
-
"Bypass",
|
|
69
|
-
"-WindowStyle",
|
|
70
|
-
"Hidden",
|
|
71
|
-
"-InputFormat",
|
|
72
|
-
"Text",
|
|
73
|
-
"-OutputFormat",
|
|
74
|
-
"Text",
|
|
75
|
-
"-File",
|
|
76
|
-
resolvePackagePath("scripts", "cli", "tray", "windows.ps1"),
|
|
77
|
-
"-IconPath",
|
|
78
|
-
options.iconPath,
|
|
79
|
-
"-Tooltip",
|
|
80
|
-
options.tooltip,
|
|
81
|
-
],
|
|
82
|
-
{ windowsHide: true, stdio: ["pipe", "pipe", "pipe"] },
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
86
|
-
this.#ready = promise;
|
|
87
|
-
|
|
88
|
-
const lines = createInterface({ input: this.#process.stdout! });
|
|
89
|
-
lines.on("line", (line) => {
|
|
90
|
-
let event: unknown;
|
|
91
|
-
try {
|
|
92
|
-
event = JSON.parse(line);
|
|
93
|
-
} catch {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (event === null || typeof event !== "object" || !("type" in event)) return;
|
|
97
|
-
if (event.type === "ready") {
|
|
98
|
-
resolve();
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (event.type === "click" && "index" in event && typeof event.index === "number") {
|
|
102
|
-
for (const [id, index] of this.#index) {
|
|
103
|
-
if (index === event.index) {
|
|
104
|
-
this.#onSelect(id);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
this.#process.once("error", reject);
|
|
112
|
-
this.#process.once("exit", () => {
|
|
113
|
-
if (!this.#closed) this.#onExit();
|
|
114
|
-
reject(new Error("the tray process exited before it was ready"));
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
// Rows are added after "started": the script creates its NotifyIcon first
|
|
118
|
-
// and would drop commands that arrive before it is listening.
|
|
119
|
-
let index = 0;
|
|
120
|
-
for (const item of options.items) {
|
|
121
|
-
if (item.kind === "separator") {
|
|
122
|
-
this.#send({ action: "add-separator", index });
|
|
123
|
-
index += 1;
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
this.#index.set(item.id, index);
|
|
127
|
-
this.#send({ action: "add-item", index, title: item.title, enabled: item.enabled ?? true });
|
|
128
|
-
index += 1;
|
|
129
|
-
}
|
|
130
|
-
this.#send({ action: "ready" });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
#send(command: Record<string, unknown>): void {
|
|
134
|
-
if (this.#closed || !this.#process.stdin?.writable) return;
|
|
135
|
-
this.#process.stdin.write(`${JSON.stringify(command)}\n`, "utf8");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
ready(): Promise<void> {
|
|
139
|
-
return this.#ready;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
update(id: string, title: string, enabled = true): void {
|
|
143
|
-
const index = this.#index.get(id);
|
|
144
|
-
if (index === undefined) return;
|
|
145
|
-
this.#send({ action: "update-item", index, title, enabled });
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
setTooltip(text: string): void {
|
|
149
|
-
this.#send({ action: "set-tooltip", text });
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async close(): Promise<void> {
|
|
153
|
-
if (this.#closed) return;
|
|
154
|
-
this.#closed = true;
|
|
155
|
-
this.#send({ action: "kill" });
|
|
156
|
-
await new Promise<void>((resolve) => {
|
|
157
|
-
const timer = setTimeout(() => {
|
|
158
|
-
this.#process.kill();
|
|
159
|
-
resolve();
|
|
160
|
-
}, 1_500);
|
|
161
|
-
this.#process.once("exit", () => {
|
|
162
|
-
clearTimeout(timer);
|
|
163
|
-
resolve();
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* macOS and Linux tray: `systray2` runs its own helper, and neither platform
|
|
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.
|
|
176
|
-
*/
|
|
177
|
-
class UnixTray implements Tray {
|
|
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;
|
|
184
|
-
|
|
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
|
-
});
|
|
198
|
-
this.#tray = new SysTray({
|
|
199
|
-
menu: {
|
|
200
|
-
icon: options.iconPath,
|
|
201
|
-
title: "Cartethyia",
|
|
202
|
-
tooltip: options.tooltip,
|
|
203
|
-
items,
|
|
204
|
-
},
|
|
205
|
-
copyDir: true,
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
|
|
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
|
-
});
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
update(id: string, title: string, enabled = true): void {
|
|
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 });
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
setTooltip(text: string): void {
|
|
238
|
-
// systray2 has no tooltip-only action; the tooltip travels with the title,
|
|
239
|
-
// which `update()` already sends.
|
|
240
|
-
void text;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async close(): Promise<void> {
|
|
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;
|
|
250
|
-
try {
|
|
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);
|
|
254
|
-
} catch {
|
|
255
|
-
// The helper may already be gone.
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/** Creates the tray for this platform. */
|
|
261
|
-
export function createTray(options: TrayOptions): Tray {
|
|
262
|
-
return process.platform === "win32" ? new WindowsTray(options) : new UnixTray(options);
|
|
263
|
-
}
|
|
1
|
+
import { spawn, type ChildProcess } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import SysTray, { type MenuItem } from "systray2";
|
|
4
|
+
import { resolvePackagePath } from "../../../src/config/package-paths";
|
|
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
|
+
|
|
13
|
+
/**
|
|
14
|
+
* One tray row. `action` rows report their id on click; separators are drawn
|
|
15
|
+
* but never fire.
|
|
16
|
+
*/
|
|
17
|
+
export type TrayItem =
|
|
18
|
+
| { readonly kind: "action"; readonly id: string; readonly title: string; readonly enabled?: boolean }
|
|
19
|
+
| { readonly kind: "separator" };
|
|
20
|
+
|
|
21
|
+
export interface TrayOptions {
|
|
22
|
+
readonly iconPath: string;
|
|
23
|
+
readonly tooltip: string;
|
|
24
|
+
readonly items: readonly TrayItem[];
|
|
25
|
+
readonly onSelect: (id: string) => void;
|
|
26
|
+
readonly onExit: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Tray {
|
|
30
|
+
/** Resolves once the icon is on screen and clicks can arrive. */
|
|
31
|
+
ready(): Promise<void>;
|
|
32
|
+
/** Replaces the title and enabled state of an existing row. */
|
|
33
|
+
update(id: string, title: string, enabled?: boolean): void;
|
|
34
|
+
setTooltip(text: string): void;
|
|
35
|
+
/** Removes the icon. Idempotent. */
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Windows tray: a PowerShell NotifyIcon driven over JSON lines.
|
|
41
|
+
*
|
|
42
|
+
* `systray2` on Windows leaves the spawning process attached to a console, so
|
|
43
|
+
* Windows Terminal keeps an empty tab open for the tray's whole lifetime — the
|
|
44
|
+
* window cannot be hidden from inside the process because it is owned by the
|
|
45
|
+
* terminal, and `MainWindowHandle` reads 0 so it cannot be found and closed
|
|
46
|
+
* either. A PowerShell NotifyIcon started with `-WindowStyle Hidden` owns no
|
|
47
|
+
* console at all, which is the only arrangement that shows an icon and no
|
|
48
|
+
* window.
|
|
49
|
+
*/
|
|
50
|
+
class WindowsTray implements Tray {
|
|
51
|
+
readonly #process: ChildProcess;
|
|
52
|
+
readonly #onSelect: (id: string) => void;
|
|
53
|
+
readonly #onExit: () => void;
|
|
54
|
+
readonly #index = new Map<string, number>();
|
|
55
|
+
#ready: Promise<void>;
|
|
56
|
+
#closed = false;
|
|
57
|
+
|
|
58
|
+
constructor(options: TrayOptions) {
|
|
59
|
+
this.#onSelect = options.onSelect;
|
|
60
|
+
this.#onExit = options.onExit;
|
|
61
|
+
|
|
62
|
+
this.#process = spawn(
|
|
63
|
+
"powershell.exe",
|
|
64
|
+
[
|
|
65
|
+
"-NoProfile",
|
|
66
|
+
"-NonInteractive",
|
|
67
|
+
"-ExecutionPolicy",
|
|
68
|
+
"Bypass",
|
|
69
|
+
"-WindowStyle",
|
|
70
|
+
"Hidden",
|
|
71
|
+
"-InputFormat",
|
|
72
|
+
"Text",
|
|
73
|
+
"-OutputFormat",
|
|
74
|
+
"Text",
|
|
75
|
+
"-File",
|
|
76
|
+
resolvePackagePath("scripts", "cli", "tray", "windows.ps1"),
|
|
77
|
+
"-IconPath",
|
|
78
|
+
options.iconPath,
|
|
79
|
+
"-Tooltip",
|
|
80
|
+
options.tooltip,
|
|
81
|
+
],
|
|
82
|
+
{ windowsHide: true, stdio: ["pipe", "pipe", "pipe"] },
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
86
|
+
this.#ready = promise;
|
|
87
|
+
|
|
88
|
+
const lines = createInterface({ input: this.#process.stdout! });
|
|
89
|
+
lines.on("line", (line) => {
|
|
90
|
+
let event: unknown;
|
|
91
|
+
try {
|
|
92
|
+
event = JSON.parse(line);
|
|
93
|
+
} catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (event === null || typeof event !== "object" || !("type" in event)) return;
|
|
97
|
+
if (event.type === "ready") {
|
|
98
|
+
resolve();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (event.type === "click" && "index" in event && typeof event.index === "number") {
|
|
102
|
+
for (const [id, index] of this.#index) {
|
|
103
|
+
if (index === event.index) {
|
|
104
|
+
this.#onSelect(id);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.#process.once("error", reject);
|
|
112
|
+
this.#process.once("exit", () => {
|
|
113
|
+
if (!this.#closed) this.#onExit();
|
|
114
|
+
reject(new Error("the tray process exited before it was ready"));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Rows are added after "started": the script creates its NotifyIcon first
|
|
118
|
+
// and would drop commands that arrive before it is listening.
|
|
119
|
+
let index = 0;
|
|
120
|
+
for (const item of options.items) {
|
|
121
|
+
if (item.kind === "separator") {
|
|
122
|
+
this.#send({ action: "add-separator", index });
|
|
123
|
+
index += 1;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
this.#index.set(item.id, index);
|
|
127
|
+
this.#send({ action: "add-item", index, title: item.title, enabled: item.enabled ?? true });
|
|
128
|
+
index += 1;
|
|
129
|
+
}
|
|
130
|
+
this.#send({ action: "ready" });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#send(command: Record<string, unknown>): void {
|
|
134
|
+
if (this.#closed || !this.#process.stdin?.writable) return;
|
|
135
|
+
this.#process.stdin.write(`${JSON.stringify(command)}\n`, "utf8");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
ready(): Promise<void> {
|
|
139
|
+
return this.#ready;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
update(id: string, title: string, enabled = true): void {
|
|
143
|
+
const index = this.#index.get(id);
|
|
144
|
+
if (index === undefined) return;
|
|
145
|
+
this.#send({ action: "update-item", index, title, enabled });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
setTooltip(text: string): void {
|
|
149
|
+
this.#send({ action: "set-tooltip", text });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async close(): Promise<void> {
|
|
153
|
+
if (this.#closed) return;
|
|
154
|
+
this.#closed = true;
|
|
155
|
+
this.#send({ action: "kill" });
|
|
156
|
+
await new Promise<void>((resolve) => {
|
|
157
|
+
const timer = setTimeout(() => {
|
|
158
|
+
this.#process.kill();
|
|
159
|
+
resolve();
|
|
160
|
+
}, 1_500);
|
|
161
|
+
this.#process.once("exit", () => {
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
resolve();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* macOS and Linux tray: `systray2` runs its own helper, and neither platform
|
|
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.
|
|
176
|
+
*/
|
|
177
|
+
class UnixTray implements Tray {
|
|
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;
|
|
184
|
+
|
|
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
|
+
});
|
|
198
|
+
this.#tray = new SysTray({
|
|
199
|
+
menu: {
|
|
200
|
+
icon: options.iconPath,
|
|
201
|
+
title: "Cartethyia",
|
|
202
|
+
tooltip: options.tooltip,
|
|
203
|
+
items,
|
|
204
|
+
},
|
|
205
|
+
copyDir: true,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
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
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
update(id: string, title: string, enabled = true): void {
|
|
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 });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
setTooltip(text: string): void {
|
|
238
|
+
// systray2 has no tooltip-only action; the tooltip travels with the title,
|
|
239
|
+
// which `update()` already sends.
|
|
240
|
+
void text;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async close(): Promise<void> {
|
|
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;
|
|
250
|
+
try {
|
|
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);
|
|
254
|
+
} catch {
|
|
255
|
+
// The helper may already be gone.
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Creates the tray for this platform. */
|
|
261
|
+
export function createTray(options: TrayOptions): Tray {
|
|
262
|
+
return process.platform === "win32" ? new WindowsTray(options) : new UnixTray(options);
|
|
263
|
+
}
|