@gloom-sh/terminal 0.19.1 → 0.19.3
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/README.md +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { logFailure } from "../utils/log-failure";
|
|
2
1
|
import type { ScannerCapture } from "../plugins/builtin/shared/scanner-capture";
|
|
3
2
|
import type { TapeCapture } from "../plugins/builtin/time-sales/snapshot-client";
|
|
4
|
-
import {
|
|
3
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
4
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
5
5
|
import { tmpdir } from "os";
|
|
6
6
|
import { join } from "path";
|
|
7
|
+
import { writeOutputFile } from "./output-file";
|
|
7
8
|
import { encodeRpcValue } from "../renderers/electrobun/shared/rpc-codec";
|
|
8
9
|
import type { AppConfig } from "../types/config";
|
|
9
10
|
import type { ChartResolutionResult } from "../time-series/types";
|
|
@@ -21,11 +22,20 @@ import type { HttpProxyRequestEnvelope, HttpProxyResponseEnvelope } from "../uti
|
|
|
21
22
|
import type { PaneAccessGate } from "./pane-functions/access-gate";
|
|
22
23
|
import { readVisibleKeyValues } from "./visible-key-values";
|
|
23
24
|
import { scrollTruncationReasons } from "./scroll-truncation";
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
SHOT_API_PROXY_PREFIX,
|
|
27
|
+
SHOT_API_SOCKET_PATH,
|
|
28
|
+
SHOT_HTTP_BRIDGE_PATH,
|
|
29
|
+
SHOT_MARKET_BRIDGE_PATH,
|
|
30
|
+
type ShotApiSocketCall,
|
|
31
|
+
type ShotApiSocketReply,
|
|
32
|
+
} from "./desktop-pane-shot-routes";
|
|
25
33
|
import { SESSION_COOKIE_NAMES } from "../api-client/session-cookie";
|
|
34
|
+
import { isShotApiRequestAllowed, isShotSessionPath } from "./desktop-pane-shot-api-policy";
|
|
26
35
|
import { renderElectrobunViewHtml } from "../renderers/dom/build-assets";
|
|
27
36
|
import { loadShotView, SHOT_VIEW_SCRIPT, SHOT_VIEW_STYLESHEET, type ShotViewFiles } from "./desktop-pane-shot-view";
|
|
28
37
|
import { errorMessage } from "../utils/errors";
|
|
38
|
+
import { findChromeExecutable, launchShotBrowser, type CdpPage } from "./desktop-pane-shot-browser";
|
|
29
39
|
|
|
30
40
|
export interface DesktopPaneShotIntradayHistory {
|
|
31
41
|
target?: InstrumentRef;
|
|
@@ -172,7 +182,8 @@ export interface DesktopPaneShotRenderResult {
|
|
|
172
182
|
|
|
173
183
|
/**
|
|
174
184
|
* The status line variants for a capture, longest first, worked out from what
|
|
175
|
-
* the pane rendered; null
|
|
185
|
+
* the pane rendered; null gives the footer row back to the pane, for a capture
|
|
186
|
+
* with nothing loaded to date (a wall, an empty state). `dates` says what the
|
|
176
187
|
* line dates (`CaptureStatusLine`): the pane's own data, whose `as of` text is
|
|
177
188
|
* then left out rather than repeat the date, or the quotes on screen.
|
|
178
189
|
*/
|
|
@@ -182,17 +193,6 @@ interface ShotStatusLineVariants {
|
|
|
182
193
|
}
|
|
183
194
|
type ShotStatusLine = (rendered: DesktopPaneShotRenderResult) => ShotStatusLineVariants | null | Promise<ShotStatusLineVariants | null>;
|
|
184
195
|
|
|
185
|
-
type CdpResponse = {
|
|
186
|
-
id?: number;
|
|
187
|
-
result?: unknown;
|
|
188
|
-
error?: { message?: string; data?: string };
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
type PendingCdpCall = {
|
|
192
|
-
resolve: (value: unknown) => void;
|
|
193
|
-
reject: (error: Error) => void;
|
|
194
|
-
};
|
|
195
|
-
|
|
196
196
|
const SHOT_MODE_CSS = [
|
|
197
197
|
"[data-gloom-role='composite-chart-toolbar']",
|
|
198
198
|
"[data-gloom-role='chart-series-quick-add']",
|
|
@@ -209,10 +209,7 @@ const SHOT_MODE_CSS = [
|
|
|
209
209
|
+ " line-height: var(--cell-h); font-size: 12px; letter-spacing: 0.02em; color: var(--gloom-text-dim, #888);"
|
|
210
210
|
+ " pointer-events: none; z-index: 1000; }";
|
|
211
211
|
|
|
212
|
-
// Chrome gets 30 s (300 polls x 100 ms) to open its DevTools port: 8 s was not enough on a busy machine.
|
|
213
|
-
const CHROME_POLL_ATTEMPTS = 300;
|
|
214
212
|
const SHOT_READY_TIMEOUT_MS = 45_000;
|
|
215
|
-
const CDP_CALL_TIMEOUT_MS = 10_000;
|
|
216
213
|
const DEFAULT_DEVICE_SCALE_FACTOR = 2;
|
|
217
214
|
|
|
218
215
|
export async function renderDesktopPaneScreenshot(
|
|
@@ -224,12 +221,12 @@ export async function renderDesktopPaneScreenshot(
|
|
|
224
221
|
const chrome = await findChromeExecutable();
|
|
225
222
|
const view = await loadShotView();
|
|
226
223
|
const tempDir = await mkdtemp(join(tmpdir(), "gloom-pane-shot-"));
|
|
227
|
-
let server:
|
|
224
|
+
let server: ShotServer | null = null;
|
|
228
225
|
try {
|
|
229
226
|
server = serveShotPage(view, shotPageHtml(view, payload), apiProxy, options.bridge ?? null);
|
|
230
227
|
return await capturePageScreenshot({
|
|
231
228
|
chrome,
|
|
232
|
-
url:
|
|
229
|
+
url: server.url,
|
|
233
230
|
outputPath,
|
|
234
231
|
widthPx: payload.widthPx,
|
|
235
232
|
heightPx: payload.heightPx,
|
|
@@ -246,7 +243,7 @@ export async function renderDesktopPaneScreenshot(
|
|
|
246
243
|
} : {}),
|
|
247
244
|
});
|
|
248
245
|
} finally {
|
|
249
|
-
server?.stop(
|
|
246
|
+
server?.stop();
|
|
250
247
|
await rm(tempDir, { recursive: true, force: true });
|
|
251
248
|
}
|
|
252
249
|
}
|
|
@@ -287,30 +284,107 @@ function shotPageHtml(view: ShotViewFiles, payload: DesktopPaneShotPayload): str
|
|
|
287
284
|
});
|
|
288
285
|
}
|
|
289
286
|
|
|
290
|
-
|
|
287
|
+
/** A capture's page server: `url` is the page Chrome opens, the only way in. */
|
|
288
|
+
export interface ShotServer {
|
|
289
|
+
url: string;
|
|
290
|
+
stop(): void;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* The route a request to the shot server asks for, or null when it is not
|
|
295
|
+
* the capture's own page asking. Any program on this machine and any website
|
|
296
|
+
* open in the user's browser can reach the port, and behind it are the
|
|
297
|
+
* capture's data, reads made with the user's session and fetches made from
|
|
298
|
+
* this machine. So every request must name the server as `127.0.0.1:<port>`
|
|
299
|
+
* (a website that rebinds its own name to this address sends its own name),
|
|
300
|
+
* come from no other origin, and start its path with the secret folder the
|
|
301
|
+
* page was opened at, which nothing but Chrome is given.
|
|
302
|
+
*/
|
|
303
|
+
function shotRequestRoute(request: Request, port: number | undefined, secret: string): URL | null {
|
|
304
|
+
if (port === undefined) return null;
|
|
305
|
+
const origin = `http://127.0.0.1:${port}`;
|
|
306
|
+
if (request.headers.get("host") !== `127.0.0.1:${port}`) return null;
|
|
307
|
+
const sentFrom = request.headers.get("origin");
|
|
308
|
+
if (sentFrom !== null && sentFrom !== origin) return null;
|
|
309
|
+
const url = new URL(request.url);
|
|
310
|
+
const folder = `/${secret}/`;
|
|
311
|
+
const given = Buffer.from(url.pathname.slice(0, folder.length));
|
|
312
|
+
const expected = Buffer.from(folder);
|
|
313
|
+
if (given.length !== expected.length || !timingSafeEqual(given, expected)) return null;
|
|
314
|
+
const route = new URL(url);
|
|
315
|
+
route.pathname = url.pathname.slice(folder.length - 1);
|
|
316
|
+
return route;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Serves one capture's page under a secret folder made for this run (see
|
|
321
|
+
* `shotRequestRoute`). The page reaches its API proxy, socket and bridges by
|
|
322
|
+
* paths relative to that folder.
|
|
323
|
+
*/
|
|
324
|
+
export function serveShotPage(
|
|
291
325
|
view: ShotViewFiles,
|
|
292
326
|
html: string,
|
|
293
327
|
apiProxy: DesktopPaneShotApiProxy,
|
|
294
328
|
bridge: DesktopPaneShotBridge | null,
|
|
295
|
-
):
|
|
296
|
-
|
|
329
|
+
): ShotServer {
|
|
330
|
+
const secret = randomBytes(32).toString("hex");
|
|
331
|
+
const server = Bun.serve({
|
|
297
332
|
hostname: "127.0.0.1",
|
|
298
333
|
port: 0,
|
|
299
334
|
idleTimeout: 60,
|
|
300
|
-
fetch(request) {
|
|
301
|
-
const url =
|
|
335
|
+
fetch(request, server) {
|
|
336
|
+
const url = shotRequestRoute(request, server.port, secret);
|
|
337
|
+
if (!url) return new Response("Forbidden", { status: 403 });
|
|
338
|
+
if (url.pathname === SHOT_API_SOCKET_PATH) {
|
|
339
|
+
// A browser always sends a socket's origin; only the page's own gets here.
|
|
340
|
+
if (request.headers.get("origin") === null) return new Response("Forbidden", { status: 403 });
|
|
341
|
+
return server.upgrade(request, { data: undefined }) ? undefined : new Response("Expected a WebSocket upgrade", { status: 426 });
|
|
342
|
+
}
|
|
302
343
|
if (url.pathname.startsWith(`${SHOT_API_PROXY_PREFIX}/`)) {
|
|
303
344
|
return proxyShotApiRequest(request, url, apiProxy);
|
|
304
345
|
}
|
|
305
|
-
if (url.pathname === SHOT_MARKET_BRIDGE_PATH) {
|
|
306
|
-
return
|
|
307
|
-
|
|
308
|
-
if (url.pathname === SHOT_HTTP_BRIDGE_PATH) {
|
|
309
|
-
return runShotHttpBridge(request, bridge);
|
|
346
|
+
if (url.pathname === SHOT_MARKET_BRIDGE_PATH || url.pathname === SHOT_HTTP_BRIDGE_PATH) {
|
|
347
|
+
if (request.method !== "POST") return new Response("Method not allowed", { status: 405 });
|
|
348
|
+
return url.pathname === SHOT_MARKET_BRIDGE_PATH ? runShotMarketBridge(request, bridge) : runShotHttpBridge(request, bridge);
|
|
310
349
|
}
|
|
350
|
+
if (request.method !== "GET" && request.method !== "HEAD") return new Response("Method not allowed", { status: 405 });
|
|
311
351
|
return serveShotAsset(url, view, html);
|
|
312
352
|
},
|
|
353
|
+
websocket: {
|
|
354
|
+
message(socket, message) {
|
|
355
|
+
void answerShotApiCall(String(message), apiProxy).then((reply) => socket.send(reply));
|
|
356
|
+
},
|
|
357
|
+
},
|
|
313
358
|
});
|
|
359
|
+
return {
|
|
360
|
+
url: `http://127.0.0.1:${server.port}/${secret}/`,
|
|
361
|
+
stop: () => server.stop(true),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* One read the page sent over its API socket (`SHOT_API_SOCKET_PATH`), made
|
|
367
|
+
* as the proxy makes it, with the session the page never holds: the reply's
|
|
368
|
+
* JSON, its body in base64. A call that is not an API path answers why.
|
|
369
|
+
*/
|
|
370
|
+
export async function answerShotApiCall(message: string, apiProxy: DesktopPaneShotApiProxy): Promise<string> {
|
|
371
|
+
let id = 0;
|
|
372
|
+
try {
|
|
373
|
+
const call = JSON.parse(message) as Partial<ShotApiSocketCall>;
|
|
374
|
+
id = typeof call.id === "number" ? call.id : 0;
|
|
375
|
+
if (typeof call.path !== "string" || !call.path.startsWith("/") || call.path.startsWith("//")) throw new Error("Not an API path.");
|
|
376
|
+
const url = new URL(`${SHOT_API_PROXY_PREFIX}${call.path}`, "http://127.0.0.1");
|
|
377
|
+
const sent = new Headers(Array.isArray(call.headers) ? call.headers : []);
|
|
378
|
+
const response = await proxyShotApiRequest(new Request(url, { headers: sent }), url, apiProxy);
|
|
379
|
+
const body = Buffer.from(await response.arrayBuffer()).toString("base64");
|
|
380
|
+
const headers: Array<[string, string]> = [];
|
|
381
|
+
response.headers.forEach((value, name) => headers.push([name, value]));
|
|
382
|
+
const reply: ShotApiSocketReply = { id, status: response.status, statusText: response.statusText, headers, body };
|
|
383
|
+
return JSON.stringify(reply);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
const reply: ShotApiSocketReply = { id, error: errorMessage(error) };
|
|
386
|
+
return JSON.stringify(reply);
|
|
387
|
+
}
|
|
314
388
|
}
|
|
315
389
|
|
|
316
390
|
async function runShotMarketBridge(
|
|
@@ -361,43 +435,49 @@ function serveShotAsset(url: URL, view: ShotViewFiles, html: string): Response {
|
|
|
361
435
|
return new Response(body, { headers: { "Content-Type": `${type}; charset=utf-8` } });
|
|
362
436
|
}
|
|
363
437
|
|
|
438
|
+
/**
|
|
439
|
+
* The request headers the proxy passes on: what the page accepts and sends, a
|
|
440
|
+
* cached copy's validators and the client's feature list. Everything else is
|
|
441
|
+
* the proxy's to set (the session cookie, the API's origin) or nobody's: a
|
|
442
|
+
* client address, a method override, a key.
|
|
443
|
+
*/
|
|
444
|
+
const SHOT_API_REQUEST_HEADERS = ["accept", "accept-language", "content-type", "if-modified-since", "if-none-match", "x-gloom-features"];
|
|
445
|
+
/** Response headers that belong to the API's own origin, not the page's. */
|
|
446
|
+
const SHOT_API_DROPPED_RESPONSE_HEADERS = [
|
|
447
|
+
"access-control-allow-credentials",
|
|
448
|
+
"access-control-allow-origin",
|
|
449
|
+
"content-encoding",
|
|
450
|
+
"content-length",
|
|
451
|
+
"set-cookie",
|
|
452
|
+
"transfer-encoding",
|
|
453
|
+
];
|
|
454
|
+
const SHOT_API_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
455
|
+
const SHOT_API_MAX_REDIRECTS = 5;
|
|
456
|
+
|
|
457
|
+
function shotApiError(message: string, status: number): Response {
|
|
458
|
+
return Response.json({ error: message }, { status, headers: { "Cache-Control": "no-store" } });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Sends one of the page's API requests with the user's session, if the page
|
|
463
|
+
* may send it (`isShotApiRequestAllowed`). A redirect is followed only within
|
|
464
|
+
* the API, to a path the page may ask for too.
|
|
465
|
+
*/
|
|
364
466
|
async function proxyShotApiRequest(
|
|
365
467
|
request: Request,
|
|
366
468
|
requestUrl: URL,
|
|
367
469
|
apiProxy: DesktopPaneShotApiProxy,
|
|
368
470
|
): Promise<Response> {
|
|
369
471
|
const baseUrl = new URL(apiProxy.baseUrl);
|
|
370
|
-
const proxiedPath = requestUrl.pathname.slice(SHOT_API_PROXY_PREFIX.length) || "/";
|
|
371
|
-
const target = new URL(baseUrl);
|
|
372
472
|
const basePath = baseUrl.pathname.replace(/\/+$/, "");
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const headers = new Headers(
|
|
377
|
-
for (const name of
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
"content-length",
|
|
381
|
-
"cookie",
|
|
382
|
-
"host",
|
|
383
|
-
"origin",
|
|
384
|
-
"proxy-authorization",
|
|
385
|
-
"referer",
|
|
386
|
-
"te",
|
|
387
|
-
"trailer",
|
|
388
|
-
"transfer-encoding",
|
|
389
|
-
"upgrade",
|
|
390
|
-
"x-forwarded-for",
|
|
391
|
-
"x-forwarded-host",
|
|
392
|
-
"x-forwarded-proto",
|
|
393
|
-
]) {
|
|
394
|
-
headers.delete(name);
|
|
473
|
+
let path = requestUrl.pathname.slice(SHOT_API_PROXY_PREFIX.length) || "/";
|
|
474
|
+
let method = request.method.toUpperCase();
|
|
475
|
+
if (!isShotApiRequestAllowed(method, path)) return shotApiError("The pane screenshot does not send this request.", 403);
|
|
476
|
+
const headers = new Headers();
|
|
477
|
+
for (const name of SHOT_API_REQUEST_HEADERS) {
|
|
478
|
+
const value = request.headers.get(name);
|
|
479
|
+
if (value !== null) headers.set(name, value);
|
|
395
480
|
}
|
|
396
|
-
const browserOwnedHeaders: string[] = [];
|
|
397
|
-
headers.forEach((_value, name) => {
|
|
398
|
-
if (name.startsWith("sec-") || name === "accept-encoding") browserOwnedHeaders.push(name);
|
|
399
|
-
});
|
|
400
|
-
for (const name of browserOwnedHeaders) headers.delete(name);
|
|
401
481
|
headers.set("Origin", baseUrl.origin);
|
|
402
482
|
if (apiProxy.sessionToken) {
|
|
403
483
|
headers.set(
|
|
@@ -405,57 +485,60 @@ async function proxyShotApiRequest(
|
|
|
405
485
|
SESSION_COOKIE_NAMES.map((name) => `${name}=${apiProxy.sessionToken}`).join("; "),
|
|
406
486
|
);
|
|
407
487
|
}
|
|
488
|
+
let target = new URL(baseUrl);
|
|
489
|
+
target.pathname = `${basePath}${path}`;
|
|
490
|
+
target.search = requestUrl.search;
|
|
491
|
+
target.hash = "";
|
|
408
492
|
|
|
409
493
|
try {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
headers,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
494
|
+
let body = method === "GET" || method === "HEAD" ? undefined : new Uint8Array(await request.arrayBuffer());
|
|
495
|
+
for (let followed = 0; ; followed += 1) {
|
|
496
|
+
const response = await fetch(target, { method, headers, body, redirect: "manual" });
|
|
497
|
+
const location = SHOT_API_REDIRECT_STATUSES.has(response.status) ? response.headers.get("location") : null;
|
|
498
|
+
if (location === null) return await shotApiResponse(response, path);
|
|
499
|
+
await response.body?.cancel().catch(() => { /* Intentional ignore: a redirect's own body is never read, only dropped. */ });
|
|
500
|
+
if (followed >= SHOT_API_MAX_REDIRECTS) return shotApiError("Cloud API redirected too many times.", 502);
|
|
501
|
+
const next = new URL(location, target);
|
|
502
|
+
if (
|
|
503
|
+
(response.status === 303 && method !== "GET" && method !== "HEAD")
|
|
504
|
+
|| ((response.status === 301 || response.status === 302) && method === "POST")
|
|
505
|
+
) {
|
|
506
|
+
method = "GET";
|
|
507
|
+
body = undefined;
|
|
508
|
+
headers.delete("content-type");
|
|
509
|
+
}
|
|
510
|
+
const inApi = next.origin === baseUrl.origin && (next.pathname === basePath || next.pathname.startsWith(`${basePath}/`));
|
|
511
|
+
path = next.pathname.slice(basePath.length) || "/";
|
|
512
|
+
if (!inApi || !isShotApiRequestAllowed(method, path)) return shotApiError("Cloud API redirected the request elsewhere.", 502);
|
|
513
|
+
target = next;
|
|
426
514
|
}
|
|
427
|
-
responseHeaders.set("Cache-Control", "no-store");
|
|
428
|
-
return new Response(response.body, {
|
|
429
|
-
status: response.status,
|
|
430
|
-
statusText: response.statusText,
|
|
431
|
-
headers: responseHeaders,
|
|
432
|
-
});
|
|
433
515
|
} catch {
|
|
434
|
-
|
|
516
|
+
// Intentional ignore: the page is told the read failed, as a dropped connection tells fetch.
|
|
517
|
+
return shotApiError("Cloud API request failed.", 502);
|
|
435
518
|
}
|
|
436
519
|
}
|
|
437
520
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const dir = process.env[name];
|
|
450
|
-
return dir ? join(dir, "Google", "Chrome", "Application", "chrome.exe") : null;
|
|
451
|
-
}),
|
|
452
|
-
].filter((value): value is string => !!value);
|
|
453
|
-
|
|
454
|
-
for (const candidate of candidates) {
|
|
455
|
-
const file = Bun.file(candidate);
|
|
456
|
-
if (await file.exists()) return candidate;
|
|
521
|
+
/**
|
|
522
|
+
* The API's answer as the page gets it: without the API origin's headers, and
|
|
523
|
+
* for the session read only its user, since the session's token rides along
|
|
524
|
+
* in that answer and the page never holds the session.
|
|
525
|
+
*/
|
|
526
|
+
async function shotApiResponse(response: Response, path: string): Promise<Response> {
|
|
527
|
+
const headers = new Headers(response.headers);
|
|
528
|
+
for (const name of SHOT_API_DROPPED_RESPONSE_HEADERS) headers.delete(name);
|
|
529
|
+
headers.set("Cache-Control", "no-store");
|
|
530
|
+
if (!isShotSessionPath(path)) {
|
|
531
|
+
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
457
532
|
}
|
|
458
|
-
|
|
533
|
+
let user: unknown = null;
|
|
534
|
+
try {
|
|
535
|
+
const answer = await response.json() as { user?: unknown } | null;
|
|
536
|
+
user = answer?.user ?? null;
|
|
537
|
+
} catch {
|
|
538
|
+
// Intentional ignore: an answer that is not JSON names no user, which reads as signed out.
|
|
539
|
+
}
|
|
540
|
+
headers.set("Content-Type", "application/json");
|
|
541
|
+
return new Response(JSON.stringify({ user }), { status: response.status, statusText: response.statusText, headers });
|
|
459
542
|
}
|
|
460
543
|
|
|
461
544
|
async function capturePageScreenshot({
|
|
@@ -481,39 +564,9 @@ async function capturePageScreenshot({
|
|
|
481
564
|
statusLine?: ShotStatusLine;
|
|
482
565
|
fitRows?: { heightCells: number; maxHeightCells: number; roles: readonly string[] };
|
|
483
566
|
}): Promise<DesktopPaneShotRenderResult> {
|
|
484
|
-
await
|
|
485
|
-
const
|
|
486
|
-
const proc = Bun.spawn([
|
|
487
|
-
chrome,
|
|
488
|
-
"--headless=new",
|
|
489
|
-
"--disable-gpu",
|
|
490
|
-
// Software WebGL for GPU-drawn panes (the 3D volatility surface).
|
|
491
|
-
"--use-angle=swiftshader",
|
|
492
|
-
"--enable-unsafe-swiftshader",
|
|
493
|
-
"--no-sandbox",
|
|
494
|
-
"--disable-dev-shm-usage",
|
|
495
|
-
"--disable-background-networking",
|
|
496
|
-
"--disable-default-apps",
|
|
497
|
-
"--disable-extensions",
|
|
498
|
-
"--disable-popup-blocking",
|
|
499
|
-
"--hide-scrollbars",
|
|
500
|
-
"--no-first-run",
|
|
501
|
-
"--no-default-browser-check",
|
|
502
|
-
"--allow-file-access-from-files",
|
|
503
|
-
`--remote-debugging-port=${port}`,
|
|
504
|
-
`--user-data-dir=${userDataDir}`,
|
|
505
|
-
`--window-size=${widthPx},${heightPx}`,
|
|
506
|
-
url,
|
|
507
|
-
], {
|
|
508
|
-
stdout: "pipe",
|
|
509
|
-
stderr: "pipe",
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
let session: CdpSession | null = null;
|
|
567
|
+
const browser = await launchShotBrowser(chrome, { widthPx, heightPx, userDataDir });
|
|
568
|
+
const session = browser.page;
|
|
513
569
|
try {
|
|
514
|
-
const wsUrl = await waitForPageWebSocket(port, url);
|
|
515
|
-
session = await CdpSession.connect(wsUrl);
|
|
516
|
-
await session.send("Page.enable");
|
|
517
570
|
await session.send("Runtime.enable");
|
|
518
571
|
await session.send("Emulation.setDeviceMetricsOverride", {
|
|
519
572
|
width: widthPx,
|
|
@@ -524,12 +577,13 @@ async function capturePageScreenshot({
|
|
|
524
577
|
await session.send("Emulation.setDefaultBackgroundColorOverride", {
|
|
525
578
|
color: { r: 0, g: 0, b: 0, a: 0 },
|
|
526
579
|
});
|
|
580
|
+
await browser.open(url);
|
|
527
581
|
await waitForShotReady(session);
|
|
528
582
|
if (fitRows) await growToFitRows(session, { widthPx, heightPx, deviceScaleFactor, ...fitRows });
|
|
529
583
|
const rendered = await readRenderedPaneState(session);
|
|
530
584
|
if (captureImage && statusLine) {
|
|
531
585
|
const line = await statusLine(rendered);
|
|
532
|
-
rendered.statusLine =
|
|
586
|
+
rendered.statusLine = (await fitShotStatusLine(session, line ?? { variants: [] })) || null;
|
|
533
587
|
}
|
|
534
588
|
if (captureImage) {
|
|
535
589
|
const screenshot = await session.send("Page.captureScreenshot", {
|
|
@@ -538,16 +592,14 @@ async function capturePageScreenshot({
|
|
|
538
592
|
captureBeyondViewport: false,
|
|
539
593
|
}) as { data?: string };
|
|
540
594
|
if (!screenshot.data) throw new Error("Chrome did not return screenshot data.");
|
|
541
|
-
await
|
|
595
|
+
await writeOutputFile(outputPath, Buffer.from(screenshot.data, "base64"));
|
|
542
596
|
}
|
|
543
597
|
return rendered;
|
|
544
598
|
} catch (error) {
|
|
545
599
|
const message = error instanceof Error ? error.message : String(error);
|
|
546
600
|
throw new Error(message);
|
|
547
601
|
} finally {
|
|
548
|
-
|
|
549
|
-
proc.kill();
|
|
550
|
-
await proc.exited.catch(() => { /* Intentional ignore: The screenshot browser was killed during cleanup; preserve the capture result. */ });
|
|
602
|
+
await browser.close();
|
|
551
603
|
}
|
|
552
604
|
}
|
|
553
605
|
|
|
@@ -557,7 +609,7 @@ async function capturePageScreenshot({
|
|
|
557
609
|
* taller pane to settle: a board a default capture would cut (FUT's last
|
|
558
610
|
* sectors) shows every row, and a docs page its whole text.
|
|
559
611
|
*/
|
|
560
|
-
async function growToFitRows(session:
|
|
612
|
+
async function growToFitRows(session: CdpPage, size: {
|
|
561
613
|
widthPx: number;
|
|
562
614
|
heightPx: number;
|
|
563
615
|
deviceScaleFactor: number;
|
|
@@ -638,7 +690,7 @@ function stateMarkers(text: string, patterns: RegExp[]): string[] {
|
|
|
638
690
|
)))];
|
|
639
691
|
}
|
|
640
692
|
|
|
641
|
-
async function readRenderedPaneState(session:
|
|
693
|
+
async function readRenderedPaneState(session: CdpPage): Promise<DesktopPaneShotRenderResult> {
|
|
642
694
|
const result = await session.send("Runtime.evaluate", {
|
|
643
695
|
expression: `(() => {
|
|
644
696
|
const root = document.getElementById("root") || document.body;
|
|
@@ -865,7 +917,7 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
865
917
|
}
|
|
866
918
|
|
|
867
919
|
/** Draws the first variant that fits the pane footer, and returns what it drew. */
|
|
868
|
-
async function fitShotStatusLine(session:
|
|
920
|
+
async function fitShotStatusLine(session: CdpPage, { variants, dates }: ShotStatusLineVariants): Promise<string | null> {
|
|
869
921
|
const result = await session.send("Runtime.evaluate", {
|
|
870
922
|
expression: `(() => {
|
|
871
923
|
const fit = window.__GLOOM_CLI_SHOT_FIT_STATUS__;
|
|
@@ -878,28 +930,7 @@ async function fitShotStatusLine(session: CdpSession, { variants, dates }: ShotS
|
|
|
878
930
|
return typeof value === "string" ? value : null;
|
|
879
931
|
}
|
|
880
932
|
|
|
881
|
-
async function
|
|
882
|
-
for (let attempt = 0; attempt < CHROME_POLL_ATTEMPTS; attempt += 1) {
|
|
883
|
-
try {
|
|
884
|
-
const response = await fetch(`http://127.0.0.1:${port}/json/list`);
|
|
885
|
-
if (response.ok) {
|
|
886
|
-
const targets = await response.json() as Array<{ url?: string; type?: string; webSocketDebuggerUrl?: string }>;
|
|
887
|
-
const page = targets.find((target) => (
|
|
888
|
-
target.type === "page"
|
|
889
|
-
&& target.webSocketDebuggerUrl
|
|
890
|
-
&& (target.url === targetUrl || target.url?.startsWith("file:"))
|
|
891
|
-
));
|
|
892
|
-
if (page?.webSocketDebuggerUrl) return page.webSocketDebuggerUrl;
|
|
893
|
-
}
|
|
894
|
-
} catch {
|
|
895
|
-
// Intentional ignore: Chrome may still be starting; the polling deadline reports failure.
|
|
896
|
-
}
|
|
897
|
-
await sleep(100);
|
|
898
|
-
}
|
|
899
|
-
throw new Error("Timed out waiting for Chrome DevTools.");
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
async function waitForShotReady(session: CdpSession): Promise<void> {
|
|
933
|
+
async function waitForShotReady(session: CdpPage): Promise<void> {
|
|
903
934
|
const startedAt = Date.now();
|
|
904
935
|
while (Date.now() - startedAt < SHOT_READY_TIMEOUT_MS) {
|
|
905
936
|
const result = await session.send("Runtime.evaluate", {
|
|
@@ -917,94 +948,3 @@ async function waitForShotReady(session: CdpSession): Promise<void> {
|
|
|
917
948
|
function sleep(ms: number): Promise<void> {
|
|
918
949
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
919
950
|
}
|
|
920
|
-
|
|
921
|
-
class CdpSession {
|
|
922
|
-
private nextId = 1;
|
|
923
|
-
private readonly pending = new Map<number, PendingCdpCall>();
|
|
924
|
-
|
|
925
|
-
private constructor(private readonly ws: WebSocket) {
|
|
926
|
-
ws.addEventListener("message", (event) => {
|
|
927
|
-
void this.handleMessage(event.data);
|
|
928
|
-
});
|
|
929
|
-
ws.addEventListener("close", () => {
|
|
930
|
-
for (const pending of this.pending.values()) {
|
|
931
|
-
pending.reject(new Error("Chrome DevTools connection closed."));
|
|
932
|
-
}
|
|
933
|
-
this.pending.clear();
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
static connect(url: string): Promise<CdpSession> {
|
|
938
|
-
return new Promise((resolve, reject) => {
|
|
939
|
-
const ws = new WebSocket(url);
|
|
940
|
-
ws.addEventListener("open", () => resolve(new CdpSession(ws)), { once: true });
|
|
941
|
-
ws.addEventListener("error", () => reject(new Error("Could not connect to Chrome DevTools.")), { once: true });
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
send(method: string, params?: Record<string, unknown>): Promise<unknown> {
|
|
946
|
-
const id = this.nextId++;
|
|
947
|
-
return new Promise((resolve, reject) => {
|
|
948
|
-
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
949
|
-
reject(new Error("Chrome DevTools connection is not open."));
|
|
950
|
-
return;
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
const timer = setTimeout(() => {
|
|
954
|
-
this.pending.delete(id);
|
|
955
|
-
reject(new Error(`Timed out waiting for Chrome DevTools method ${method}.`));
|
|
956
|
-
}, CDP_CALL_TIMEOUT_MS);
|
|
957
|
-
const settle = {
|
|
958
|
-
resolve: (value: unknown) => {
|
|
959
|
-
clearTimeout(timer);
|
|
960
|
-
resolve(value);
|
|
961
|
-
},
|
|
962
|
-
reject: (error: Error) => {
|
|
963
|
-
clearTimeout(timer);
|
|
964
|
-
reject(error);
|
|
965
|
-
},
|
|
966
|
-
};
|
|
967
|
-
|
|
968
|
-
this.pending.set(id, settle);
|
|
969
|
-
try {
|
|
970
|
-
this.ws.send(JSON.stringify({ id, method, params }));
|
|
971
|
-
} catch (error) {
|
|
972
|
-
this.pending.delete(id);
|
|
973
|
-
settle.reject(error instanceof Error ? error : new Error(String(error)));
|
|
974
|
-
}
|
|
975
|
-
});
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
close(): void {
|
|
979
|
-
this.ws.close();
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
private async handleMessage(data: unknown): Promise<void> {
|
|
983
|
-
let response: CdpResponse;
|
|
984
|
-
try {
|
|
985
|
-
response = JSON.parse(await stringifyWebSocketMessage(data)) as CdpResponse;
|
|
986
|
-
} catch (error) {
|
|
987
|
-
logFailure("cli.screenshot", "Decode screenshot browser response failed")(error);
|
|
988
|
-
return;
|
|
989
|
-
}
|
|
990
|
-
if (!response.id) return;
|
|
991
|
-
const pending = this.pending.get(response.id);
|
|
992
|
-
if (!pending) return;
|
|
993
|
-
this.pending.delete(response.id);
|
|
994
|
-
if (response.error) {
|
|
995
|
-
pending.reject(new Error([response.error.message, response.error.data].filter(Boolean).join("\n")));
|
|
996
|
-
} else {
|
|
997
|
-
pending.resolve(response.result);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
async function stringifyWebSocketMessage(data: unknown): Promise<string> {
|
|
1003
|
-
if (typeof data === "string") return data;
|
|
1004
|
-
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
1005
|
-
if (ArrayBuffer.isView(data)) {
|
|
1006
|
-
return new TextDecoder().decode(data as Uint8Array);
|
|
1007
|
-
}
|
|
1008
|
-
if (typeof Blob !== "undefined" && data instanceof Blob) return data.text();
|
|
1009
|
-
return String(data);
|
|
1010
|
-
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parseCliGlobalArgs } from "./options";
|
|
2
|
+
import { printVersion, versionCommand } from "./commands/version";
|
|
3
|
+
import { checkCliCommandOptions, foldCommandArgs } from "./command-options";
|
|
4
|
+
import { describeCliCommand, renderCommandHelp } from "./help";
|
|
5
|
+
import { printCliResult } from "./result";
|
|
6
|
+
import { printCliError } from "./errors";
|
|
7
|
+
import { setCliColorEnabledOverride, setCliPlainOverride, setCliWidthOverride } from "../utils/cli-output";
|
|
8
|
+
import { foldTypedText } from "../utils/typed-text";
|
|
9
|
+
|
|
10
|
+
/** Version and its help need neither a plugin registry nor application services. */
|
|
11
|
+
export async function dispatchEarlyCommand(args: string[]): Promise<boolean> {
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = parseCliGlobalArgs(args);
|
|
15
|
+
} catch {
|
|
16
|
+
// Intentional ignore: The regular dispatcher reports malformed global options.
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const token = foldTypedText(parsed.args[0] ?? "").trim().toLowerCase();
|
|
20
|
+
if (token !== versionCommand.name && !versionCommand.aliases?.includes(token)) return false;
|
|
21
|
+
const options = parsed.options;
|
|
22
|
+
setCliColorEnabledOverride(options.color);
|
|
23
|
+
setCliWidthOverride(options.width ?? null);
|
|
24
|
+
if (options.plain != null) setCliPlainOverride(options.plain);
|
|
25
|
+
const commandArgs = foldCommandArgs(versionCommand, parsed.args.slice(1), parsed.literalStart - 1);
|
|
26
|
+
try {
|
|
27
|
+
if (parsed.help) {
|
|
28
|
+
if (options.format === "text") {
|
|
29
|
+
if (!options.quiet) process.stdout.write(`${renderCommandHelp(versionCommand)}\n`);
|
|
30
|
+
} else {
|
|
31
|
+
printCliResult({ data: { ...describeCliCommand(versionCommand), sections: versionCommand.help?.sections ?? [] } }, options);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
checkCliCommandOptions(versionCommand, commandArgs, parsed.literalStart - 1);
|
|
35
|
+
await printVersion((result, renderOptions) => printCliResult(result, options, renderOptions, commandArgs));
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
printCliError(error, options, { command: versionCommand.name, args: commandArgs });
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|