@gloom-sh/terminal 0.19.2 → 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 +1 -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 +20 -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.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +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 +92 -23
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +9 -4
- package/src/cli/commands/search.ts +71 -12
- 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 +5 -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/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +154 -4
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +40 -5
- package/src/cli/listing-arg.ts +46 -9
- 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 +20 -0
- 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 +24 -1
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/header.tsx +8 -3
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +7 -2
- package/src/components/layout/pane/footer/model.ts +40 -5
- 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 +2 -1
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +54 -23
- 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/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +8 -1
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +34 -31
- package/src/data/sqlite/access.ts +19 -8
- package/src/data/sqlite/checkpointer.ts +95 -8
- package/src/data/sqlite/database.ts +40 -7
- 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/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/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/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
- 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 +26 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -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/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/index.tsx +1 -2
- 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/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- 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 +2 -0
- 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/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/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- 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-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/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- 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/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 +15 -2
- 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/help/index.tsx +1 -1
- 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/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/index.tsx +7 -1
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
- 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/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/index.tsx +1 -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/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/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 +60 -0
- 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/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- 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/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- 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 +116 -13
- package/src/plugins/builtin/thirteenf/data.ts +132 -30
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
- package/src/plugins/builtin/thirteenf/types.ts +12 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
- 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/pane.tsx +2 -2
- 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/index.tsx +1 -2
- 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/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 +15 -4
- 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 +2 -0
- 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/initialization.ts +3 -2
- 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/create.ts +4 -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/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/standalone.ts +10 -1
- package/src/series/types.ts +7 -0
- package/src/series/validate.ts +11 -2
- 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 +12 -7
- package/src/telemetry/stall-reports.ts +34 -5
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name.ts +19 -0
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +13 -2
- package/src/tickers/search/index.ts +4 -1
- 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/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +55 -6
- package/src/time-series/resolve.ts +12 -3
- 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/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +22 -0
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +11 -0
- 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/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/main-thread-stall.ts +75 -3
- package/src/utils/perf-marks.ts +36 -2
- 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/renderers/electrobun/launcher/preload.ts +0 -13
- package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
- package/src/time-series/studies.ts +0 -1628
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
type DataTableCell,
|
|
27
27
|
type PageRequest,
|
|
28
28
|
type PaneHint,
|
|
29
|
+
type QueryBarFilter,
|
|
30
|
+
TickerBadgeList,
|
|
29
31
|
} from "../../../components";
|
|
30
32
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
31
33
|
import {
|
|
@@ -36,13 +38,17 @@ import {
|
|
|
36
38
|
usePluginPaneState,
|
|
37
39
|
} from "../../../public/react";
|
|
38
40
|
import { relativeLuminance } from "../../../theme/color-utils";
|
|
41
|
+
import { displayWidth } from "../../../utils/format";
|
|
42
|
+
import { truncateWithEllipsis } from "../../../utils/text-wrap";
|
|
39
43
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
40
44
|
import type { PaneProps } from "../../../types/plugin";
|
|
41
45
|
import { Box, Text, TextAttributes, useUiCapabilities, type ScrollBoxRenderable } from "../../../ui";
|
|
42
46
|
import { formatGeoSeriesExpression } from "../chart-composer/series-expression";
|
|
43
47
|
import { CLOUD_PLAN_KEY, useCloudUpgradeAction } from "../shared/cloud-upgrade";
|
|
44
48
|
import { useOpenDescription } from "../shared/open-description";
|
|
45
|
-
import { ENTITY_PAGE_SIZE, loadEntityDetail, loadEntityPage, loadWorldVenues } from "./client";
|
|
49
|
+
import { ENTITY_PAGE_SIZE, loadEntityDetail, loadEntityPage, loadWorldVenues, scopedGeoRequest } from "./client";
|
|
50
|
+
import { isLocalGeoLayer } from "./local-layers";
|
|
51
|
+
import { useLinkedGeoScope } from "./scope";
|
|
46
52
|
import { EntityDetail } from "./entity-detail";
|
|
47
53
|
import { useWorldMapEvidence } from "./evidence";
|
|
48
54
|
import { entityCellText, entityColumns, TICKER_COLUMN_ID, type EntityColumn } from "./entity-table";
|
|
@@ -52,6 +58,7 @@ import { WORLD_VENUE_MAP_PANE_ID } from "./ids";
|
|
|
52
58
|
import {
|
|
53
59
|
entityOpenZoom,
|
|
54
60
|
entityTableBbox,
|
|
61
|
+
framePlaces,
|
|
55
62
|
featureAnchor,
|
|
56
63
|
formatGeoValue,
|
|
57
64
|
formatTickerLinks,
|
|
@@ -162,7 +169,7 @@ const SelectedEntityHeader = memo(function SelectedEntityHeader({
|
|
|
162
169
|
return (
|
|
163
170
|
<Box flexDirection="column" height={2} width={width} paddingX={1}>
|
|
164
171
|
<Box flexDirection="row" justifyContent="space-between" width="100%">
|
|
165
|
-
<Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{row.label}</Text>
|
|
172
|
+
<Text fg={colors.textBright} attributes={TextAttributes.BOLD}>{truncateWithEllipsis(row.label, Math.max(1, width - 3 - displayWidth(layer.name)))}</Text>
|
|
166
173
|
<Text fg={color}>{layer.name}</Text>
|
|
167
174
|
</Box>
|
|
168
175
|
<Text fg={colors.textMuted}>{meta}</Text>
|
|
@@ -193,7 +200,11 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
|
|
|
193
200
|
setView((current) => sameGeoView(current, next) ? current : next);
|
|
194
201
|
}, []);
|
|
195
202
|
const canAccess = useCallback((layer: GeoLayerInfo) => layer.access !== "pro" || hasProAccess, [hasProAccess]);
|
|
196
|
-
|
|
203
|
+
// A linked map narrows the app's own layers to what its source pane selects.
|
|
204
|
+
const linkedScope = useLinkedGeoScope(layers.some((layer) => isLocalGeoLayer(layer.id)));
|
|
205
|
+
const scope = linkedScope.scope;
|
|
206
|
+
const request = useMemo(() => scopedGeoRequest(scope?.key ?? null), [scope?.key]);
|
|
207
|
+
const states = useGeoLayerFeed(layers, view, visible, { canAccess, request });
|
|
197
208
|
|
|
198
209
|
// A layer coloured by class (ships) takes the first colour, so its own never meets the class colours.
|
|
199
210
|
const colorOrder = useMemo(
|
|
@@ -244,7 +255,7 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
|
|
|
244
255
|
...(sort.key ? { sort: sort.key, dir: sort.dir } : {}),
|
|
245
256
|
...(tableBbox ? { bbox: tableBbox } : {}),
|
|
246
257
|
offset,
|
|
247
|
-
}, signal);
|
|
258
|
+
}, signal, request);
|
|
248
259
|
const rows = page.rows ?? [];
|
|
249
260
|
const total = typeof page.total === "number" ? page.total : null;
|
|
250
261
|
return {
|
|
@@ -252,18 +263,28 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
|
|
|
252
263
|
rows,
|
|
253
264
|
hasMore: rows.length >= ENTITY_PAGE_SIZE && (total === null || offset + rows.length < total),
|
|
254
265
|
nextOffset: offset + rows.length,
|
|
266
|
+
scopeKey: scope?.key ?? null,
|
|
255
267
|
};
|
|
256
|
-
}, [query, sort.dir, sort.key, tableBboxKey, tableLayer.id]);
|
|
268
|
+
}, [query, request, scope?.key, sort.dir, sort.key, tableBboxKey, tableLayer.id]);
|
|
257
269
|
const pages = usePagedRows(tableReadable ? loadPage : null, { getId: (row) => row.id });
|
|
258
270
|
const rows = pages.rows;
|
|
271
|
+
// A linked map frames the places of each company or theme it moves to, once.
|
|
272
|
+
const framedScopeRef = useRef<string | null>(null);
|
|
273
|
+
const firstPage = pages.pages[0];
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
if (!firstPage?.scopeKey || !isLocalGeoLayer(tableLayer.id) || framedScopeRef.current === firstPage.scopeKey) return;
|
|
276
|
+
framedScopeRef.current = firstPage.scopeKey;
|
|
277
|
+
const frame = framePlaces(firstPage.rows);
|
|
278
|
+
if (frame) setFocus({ key: `scope:${firstPage.scopeKey}`, ...frame, exact: true });
|
|
279
|
+
}, [firstPage, tableLayer.id]);
|
|
259
280
|
useWorldMapEvidence(tokens, states, rows.length, pages.loading);
|
|
260
281
|
const refresh = useCallback(() => pages.reload(), [pages.reload]);
|
|
261
282
|
usePaneRefreshKey(refresh, { focused: focused && !searchFocused });
|
|
262
283
|
|
|
263
284
|
const detailOpen = !!open && open.layer === tableLayer.id;
|
|
264
285
|
const detailLoader = useMemo(
|
|
265
|
-
() => (detailOpen && open ? () => loadEntityDetail(open.layer, open.id) : null),
|
|
266
|
-
[detailOpen, open?.id, open?.layer],
|
|
286
|
+
() => (detailOpen && open ? () => loadEntityDetail(open.layer, open.id, undefined, request) : null),
|
|
287
|
+
[detailOpen, open?.id, open?.layer, request],
|
|
267
288
|
);
|
|
268
289
|
const detail = useAsyncResource(detailLoader);
|
|
269
290
|
const openRow = detailOpen
|
|
@@ -412,9 +433,29 @@ export function LayeredMapView({ focused, width, height, layers, tokens, plain =
|
|
|
412
433
|
|
|
413
434
|
const onScroll = useTableLoadMore(scrollRef, pages.hasMore && !detailOpen, pages.loadMore);
|
|
414
435
|
// A layer that cannot serve has nothing to search and, alone, nothing to switch to: no bar at all.
|
|
436
|
+
// What a linked map shows, before the search: a switch between the company and its theme when the
|
|
437
|
+
// source has both, else the one it has.
|
|
438
|
+
const scoped = !!scope && isLocalGeoLayer(tableLayer.id);
|
|
439
|
+
const { company: scopeCompany, theme: scopeTheme } = linkedScope;
|
|
440
|
+
const scopeFilter: QueryBarFilter | null = scoped && scopeCompany && scopeTheme ? {
|
|
441
|
+
id: "scope",
|
|
442
|
+
label: "Show",
|
|
443
|
+
inline: true,
|
|
444
|
+
value: linkedScope.mode,
|
|
445
|
+
options: [
|
|
446
|
+
{ value: "company", label: scopeCompany.label },
|
|
447
|
+
{ value: "theme", label: "Theme", description: scopeTheme.label },
|
|
448
|
+
],
|
|
449
|
+
onChange: linkedScope.setMode,
|
|
450
|
+
} : null;
|
|
451
|
+
const scopeLeading = !scoped || scopeFilter ? undefined
|
|
452
|
+
: scopeCompany ? <TickerBadgeList symbols={[scopeCompany.label]} width={displayWidth(scopeCompany.label) + 2} listed />
|
|
453
|
+
: <Text fg={colors.textDim}>{truncateWithEllipsis(scope.label, Math.max(8, Math.floor(listWidth / 3)))}</Text>;
|
|
415
454
|
const queryBar = !tableReadable && layers.length < 2 ? undefined : (
|
|
416
455
|
<QueryBar
|
|
417
456
|
width={listWidth}
|
|
457
|
+
leading={scopeLeading}
|
|
458
|
+
filters={scopeFilter ? [scopeFilter] : undefined}
|
|
418
459
|
search={tableReadable ? {
|
|
419
460
|
value: query,
|
|
420
461
|
onChange: setQuery,
|
|
@@ -142,6 +142,7 @@ export const MAP_PRESET_OPTIONS = [
|
|
|
142
142
|
{ value: "ports", label: "Ports" },
|
|
143
143
|
{ value: "energy", label: "Pipelines, fields and terminals" },
|
|
144
144
|
{ value: "air", label: "Airports and flights" },
|
|
145
|
+
{ value: "facilities", label: "AI buildout facilities" },
|
|
145
146
|
] as const;
|
|
146
147
|
|
|
147
148
|
function shortDate(iso: string): string | null {
|
|
@@ -329,6 +330,22 @@ export function entityOpenZoom(layer: Pick<GeoLayerInfo, "geometry" | "minZoom">
|
|
|
329
330
|
return layer.minZoom === undefined ? base : Math.max(base, 2 ** layer.minZoom);
|
|
330
331
|
}
|
|
331
332
|
|
|
333
|
+
/** Below this span a frame is a region, not a street: one facility shows its surroundings. */
|
|
334
|
+
const FRAME_MIN_SPAN_DEGREES = 6;
|
|
335
|
+
const FRAME_MAX_ZOOM = 8;
|
|
336
|
+
|
|
337
|
+
/** The centre and zoom that frame a set of places (a linked company's facilities); null without one. */
|
|
338
|
+
export function framePlaces(places: readonly { lon: number; lat: number }[]): { longitude: number; latitude: number; zoom: number } | null {
|
|
339
|
+
const located = places.filter((place) => Number.isFinite(place.lon) && Number.isFinite(place.lat));
|
|
340
|
+
if (!located.length) return null;
|
|
341
|
+
const lons = located.map((place) => place.lon);
|
|
342
|
+
const lats = located.map((place) => place.lat);
|
|
343
|
+
const west = Math.min(...lons), east = Math.max(...lons), south = Math.min(...lats), north = Math.max(...lats);
|
|
344
|
+
// The map is about twice as wide as tall, so latitude counts double; a margin keeps edge markers inside.
|
|
345
|
+
const span = Math.max(east - west, (north - south) * 2, FRAME_MIN_SPAN_DEGREES) * 1.5;
|
|
346
|
+
return { longitude: (west + east) / 2, latitude: (south + north) / 2, zoom: Math.min(FRAME_MAX_ZOOM, Math.max(1, 360 / span)) };
|
|
347
|
+
}
|
|
348
|
+
|
|
332
349
|
/**
|
|
333
350
|
* The area a layer's table reads. A layer drawn only from a zoom (live
|
|
334
351
|
* flights) lists what the map shows once it is that close; further out, and
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layers the app serves itself, beside the server's catalog. A plugin whose
|
|
3
|
+
* rows have a place (TheBuildout's facilities) registers one, and the map,
|
|
4
|
+
* its table and its detail read it through the same wire contract as a server
|
|
5
|
+
* layer: the catalog lists it, and its feature, entity and detail requests are
|
|
6
|
+
* answered in the app without reaching Gloom Cloud. A scope (the company or
|
|
7
|
+
* theme the pane follows) narrows what a local layer answers; server layers
|
|
8
|
+
* never see it.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
resetGeoCatalogCache,
|
|
12
|
+
type GeoBbox,
|
|
13
|
+
type GeoEntitiesPayload,
|
|
14
|
+
type GeoEntityPayload,
|
|
15
|
+
type GeoFeaturesPayload,
|
|
16
|
+
type GeoLayerInfo,
|
|
17
|
+
type GeoLayersPayload,
|
|
18
|
+
type GeoRequest,
|
|
19
|
+
} from "../../../api-client/geo";
|
|
20
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
21
|
+
|
|
22
|
+
/** What a linked map narrows a local layer to: `company:CRWV`, `theme:<slug>`. */
|
|
23
|
+
export interface GeoScope {
|
|
24
|
+
key: string;
|
|
25
|
+
/** The words the pane shows for it: a ticker or a theme's name. */
|
|
26
|
+
label: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LocalFeatureQuery {
|
|
30
|
+
bbox: GeoBbox | null;
|
|
31
|
+
zoom: number;
|
|
32
|
+
limit: number;
|
|
33
|
+
scope: string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface LocalEntityQuery {
|
|
37
|
+
q: string;
|
|
38
|
+
sort: string | null;
|
|
39
|
+
dir: "asc" | "desc";
|
|
40
|
+
limit: number;
|
|
41
|
+
offset: number;
|
|
42
|
+
bbox: GeoBbox | null;
|
|
43
|
+
scope: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface LocalGeoLayer {
|
|
47
|
+
info: GeoLayerInfo;
|
|
48
|
+
features: (query: LocalFeatureQuery, signal?: AbortSignal) => Promise<GeoFeaturesPayload>;
|
|
49
|
+
entities: (query: LocalEntityQuery, signal?: AbortSignal) => Promise<GeoEntitiesPayload>;
|
|
50
|
+
entity: (id: string, scope: string | null, signal?: AbortSignal) => Promise<GeoEntityPayload>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const localLayers = new Map<string, LocalGeoLayer>();
|
|
54
|
+
|
|
55
|
+
/** Adds a layer to every map's catalog until the returned function removes it. */
|
|
56
|
+
export function registerLocalGeoLayer(layer: LocalGeoLayer): () => void {
|
|
57
|
+
localLayers.set(layer.info.id, layer);
|
|
58
|
+
// A catalog read before the layer existed would hide it for minutes.
|
|
59
|
+
resetGeoCatalogCache();
|
|
60
|
+
return () => {
|
|
61
|
+
if (localLayers.get(layer.info.id) !== layer) return;
|
|
62
|
+
localLayers.delete(layer.info.id);
|
|
63
|
+
resetGeoCatalogCache();
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isLocalGeoLayer(layerId: string): boolean {
|
|
68
|
+
return localLayers.has(layerId);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function bboxParam(value: string | null): GeoBbox | null {
|
|
72
|
+
if (!value) return null;
|
|
73
|
+
const parts = value.split(",").map(Number);
|
|
74
|
+
return parts.length === 4 && parts.every(Number.isFinite) ? parts as GeoBbox : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function numberParam(value: string | null, fallback: number): number {
|
|
78
|
+
const parsed = value === null ? Number.NaN : Number(value);
|
|
79
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The server's catalog with the local layers after it; a server without one still lists them. */
|
|
83
|
+
async function mergedCatalog(request: GeoRequest, init?: RequestInit): Promise<GeoLayersPayload> {
|
|
84
|
+
const local = [...localLayers.values()].map((layer) => layer.info);
|
|
85
|
+
const server = await request<GeoLayersPayload>("layers", init).catch((error: unknown) => {
|
|
86
|
+
if (!local.length) throw error;
|
|
87
|
+
logFailure("cloud-geo", "Geo catalog request failed")(error);
|
|
88
|
+
return null;
|
|
89
|
+
});
|
|
90
|
+
const serverLayers = Array.isArray(server?.layers) ? server.layers : [];
|
|
91
|
+
const ids = new Set(serverLayers.map((layer) => layer?.id));
|
|
92
|
+
return { layers: [...serverLayers, ...local.filter((layer) => !ids.has(layer.id))], asOf: server?.asOf ?? null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A path segment as written when it is not valid percent-encoding (a stray `%` in an entity id). */
|
|
96
|
+
function decodeSegment(segment: string): string {
|
|
97
|
+
try {
|
|
98
|
+
return decodeURIComponent(segment);
|
|
99
|
+
} catch {
|
|
100
|
+
// Intentional ignore: a segment that does not decode is matched as it was written.
|
|
101
|
+
return segment;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A request that answers the local layers itself and sends everything else on.
|
|
107
|
+
* `scope` narrows the local layers' answers; a new scope is a new request
|
|
108
|
+
* function, so the map's feed and table ask again.
|
|
109
|
+
*/
|
|
110
|
+
export function withLocalGeoLayers(request: GeoRequest, scope: string | null = null): GeoRequest {
|
|
111
|
+
return (<T>(path: string, init?: RequestInit): Promise<T> => {
|
|
112
|
+
const [route = "", search = ""] = path.split("?");
|
|
113
|
+
const params = new URLSearchParams(search);
|
|
114
|
+
const signal = init?.signal ?? undefined;
|
|
115
|
+
if (route === "layers") return (localLayers.size ? mergedCatalog(request, init) : request(path, init)) as Promise<T>;
|
|
116
|
+
const parts = route.split("/").map(decodeSegment);
|
|
117
|
+
const layer = parts.length >= 3 ? localLayers.get(parts[1]!) ?? null : null;
|
|
118
|
+
if (parts[0] === "layers" && layer && parts[2] === "features") {
|
|
119
|
+
return layer.features({
|
|
120
|
+
bbox: bboxParam(params.get("bbox")),
|
|
121
|
+
zoom: numberParam(params.get("zoom"), 0),
|
|
122
|
+
limit: numberParam(params.get("limit"), 1500),
|
|
123
|
+
scope,
|
|
124
|
+
}, signal) as Promise<T>;
|
|
125
|
+
}
|
|
126
|
+
if (parts[0] === "layers" && layer && parts[2] === "entities") {
|
|
127
|
+
return layer.entities({
|
|
128
|
+
q: params.get("q") ?? "",
|
|
129
|
+
sort: params.get("sort"),
|
|
130
|
+
dir: params.get("dir") === "asc" ? "asc" : "desc",
|
|
131
|
+
limit: numberParam(params.get("limit"), 100),
|
|
132
|
+
offset: numberParam(params.get("offset"), 0),
|
|
133
|
+
bbox: bboxParam(params.get("bbox")),
|
|
134
|
+
scope,
|
|
135
|
+
}, signal) as Promise<T>;
|
|
136
|
+
}
|
|
137
|
+
const entityLayer = parts[0] === "entities" ? localLayers.get(parts[1] ?? "") : undefined;
|
|
138
|
+
if (entityLayer && parts[2]) return entityLayer.entity(parts.slice(2).join("/"), scope, signal) as Promise<T>;
|
|
139
|
+
return request<T>(path, init);
|
|
140
|
+
}) as GeoRequest;
|
|
141
|
+
}
|
|
@@ -18,6 +18,9 @@ export type MapSymbolId =
|
|
|
18
18
|
| "terminal"
|
|
19
19
|
| "field"
|
|
20
20
|
| "chokepoint"
|
|
21
|
+
| "rack"
|
|
22
|
+
| "bolt"
|
|
23
|
+
| "chip"
|
|
21
24
|
| "dot";
|
|
22
25
|
|
|
23
26
|
export interface MapSymbol {
|
|
@@ -85,6 +88,17 @@ export const MAP_SYMBOLS: Record<MapSymbolId, MapSymbol> = {
|
|
|
85
88
|
landmark: true,
|
|
86
89
|
cell: "◆",
|
|
87
90
|
},
|
|
91
|
+
// A server rack: the cabinet and its shelves, for a data center.
|
|
92
|
+
rack: { d: "M-5 -7.2H5V7.2H-5ZM-5 -2.4H5M-5 2.4H5M-2.6 -4.8H-0.6M-2.6 0H-0.6M-2.6 4.8H-0.6", paint: "stroke", size: 13, cell: "▤" },
|
|
93
|
+
// A lightning bolt, for a power plant or substation.
|
|
94
|
+
bolt: { d: "M1.8 -8L-5.4 1.4H-0.6L-2 8L5.4 -1.8H0.6Z", paint: "fill", size: 13, cell: "↯" },
|
|
95
|
+
// A chip and its pins, for a fab or a packaging plant.
|
|
96
|
+
chip: {
|
|
97
|
+
d: "M-4.6 -4.6H4.6V4.6H-4.6ZM-2 -4.6V-7.6M2 -4.6V-7.6M-2 4.6V7.6M2 4.6V7.6M-4.6 -2H-7.6M-4.6 2H-7.6M4.6 -2H7.6M4.6 2H7.6",
|
|
98
|
+
paint: "stroke",
|
|
99
|
+
size: 13,
|
|
100
|
+
cell: "▣",
|
|
101
|
+
},
|
|
88
102
|
dot: { d: "M0 -3.4A3.4 3.4 0 1 1 0 3.4A3.4 3.4 0 1 1 0 -3.4Z", paint: "fill", size: 11, cell: "•" },
|
|
89
103
|
};
|
|
90
104
|
|
|
@@ -162,6 +176,10 @@ const RULES: Record<string, SymbolRule> = {
|
|
|
162
176
|
},
|
|
163
177
|
terminal: () => fixed("terminal"),
|
|
164
178
|
field: () => fixed("field"),
|
|
179
|
+
// Facilities (TheBuildout): by what the site is; anything else is a dot.
|
|
180
|
+
"data-center": () => fixed("rack"),
|
|
181
|
+
"power-plant": () => fixed("bolt"),
|
|
182
|
+
fab: () => fixed("chip"),
|
|
165
183
|
};
|
|
166
184
|
|
|
167
185
|
/** The layer registry: layer ids the app knows, then the kinds their entities carry. */
|
|
@@ -43,6 +43,8 @@ export interface WorldMapFocus {
|
|
|
43
43
|
longitude: number;
|
|
44
44
|
latitude: number;
|
|
45
45
|
zoom?: number;
|
|
46
|
+
/** Exactly at `zoom`, out as well as in: a frame around a set of places. */
|
|
47
|
+
exact?: boolean;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
interface WorldVenueMapProps {
|
|
@@ -690,8 +692,8 @@ function DesktopWorldVenueMap(props: WorldVenueMapProps) {
|
|
|
690
692
|
if (!focus || !sized || appliedFocusRef.current === focus.key) return;
|
|
691
693
|
appliedFocusRef.current = focus.key;
|
|
692
694
|
setViewport((current) => {
|
|
693
|
-
const zoom = Math.max(current.zoom, focus.zoom ?? 1);
|
|
694
|
-
if (zoom <= 1) return current;
|
|
695
|
+
const zoom = focus.exact ? Math.max(1, focus.zoom ?? 1) : Math.max(current.zoom, focus.zoom ?? 1);
|
|
696
|
+
if (zoom <= 1 && !focus.exact) return current;
|
|
695
697
|
return clampWorldMapViewport({ zoom, centerLongitude: focus.longitude, centerLatitude: focus.latitude }, props.width, plotHeight, 1, maxZoom);
|
|
696
698
|
});
|
|
697
699
|
}, [focusKey, sized]);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useAppSelector, usePaneInstance } from "../../../state/app/context";
|
|
3
|
+
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
4
|
+
import { usePluginPaneState } from "../../../public/react";
|
|
5
|
+
import type { PaneInstanceConfig } from "../../../types/config";
|
|
6
|
+
import { parseLayerTokens, LAYERS_SETTING_KEY } from "./layers";
|
|
7
|
+
import { isLocalGeoLayer, type GeoScope } from "./local-layers";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Pane state a ticker source may publish beside `cursorSymbol`: the group the
|
|
11
|
+
* selected company belongs to, which a linked map can widen to. TheBuildout
|
|
12
|
+
* publishes the theme its table lists.
|
|
13
|
+
*/
|
|
14
|
+
export const GEO_SCOPE_STATE_KEY = "geoScope";
|
|
15
|
+
|
|
16
|
+
type GeoScopeMode = "company" | "theme";
|
|
17
|
+
|
|
18
|
+
function publishedScope(value: unknown): GeoScope | null {
|
|
19
|
+
if (!value || typeof value !== "object") return null;
|
|
20
|
+
const { key, label } = value as Partial<GeoScope>;
|
|
21
|
+
return typeof key === "string" && key && typeof label === "string" ? { key, label } : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A map with one of the app's own layers on can be linked to a list's or a scanner's selection. */
|
|
25
|
+
export function mapFollowsTicker(pane: PaneInstanceConfig): boolean {
|
|
26
|
+
return parseLayerTokens(pane.settings?.[LAYERS_SETTING_KEY]).some(isLocalGeoLayer);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LinkedGeoScope {
|
|
30
|
+
/** What the map's own layers show; null shows everything. */
|
|
31
|
+
scope: GeoScope | null;
|
|
32
|
+
/** The company the link resolves to. */
|
|
33
|
+
company: GeoScope | null;
|
|
34
|
+
/** The group its source publishes for that company. */
|
|
35
|
+
theme: GeoScope | null;
|
|
36
|
+
mode: GeoScopeMode;
|
|
37
|
+
setMode: (mode: GeoScopeMode) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What a linked map narrows its own layers to: the company the link resolves
|
|
42
|
+
* to, or the theme its source publishes once the pane is switched to it (and
|
|
43
|
+
* whenever there is no company). An unlinked map has neither and shows
|
|
44
|
+
* everything.
|
|
45
|
+
*/
|
|
46
|
+
export function useLinkedGeoScope(enabled: boolean): LinkedGeoScope {
|
|
47
|
+
const instance = usePaneInstance();
|
|
48
|
+
const linked = enabled && (instance?.binding?.kind === "follow" || instance?.binding?.kind === "fixed");
|
|
49
|
+
const sourceId = linked && instance?.binding?.kind === "follow" ? instance.binding.sourceInstanceId : null;
|
|
50
|
+
const published = useAppSelector((state) => (sourceId ? state.paneState[sourceId]?.[GEO_SCOPE_STATE_KEY] ?? null : null));
|
|
51
|
+
const { symbol } = usePaneTickerIdentity();
|
|
52
|
+
const [mode, setMode] = usePluginPaneState<GeoScopeMode>("geoScopeMode", "company");
|
|
53
|
+
const theme = linked ? publishedScope(published) : null;
|
|
54
|
+
const companySymbol = linked ? symbol : null;
|
|
55
|
+
const themeKey = theme?.key ?? null;
|
|
56
|
+
const themeLabel = theme?.label ?? "";
|
|
57
|
+
return useMemo(() => {
|
|
58
|
+
const company = companySymbol ? { key: `company:${companySymbol}`, label: companySymbol } : null;
|
|
59
|
+
const group = themeKey ? { key: themeKey, label: themeLabel } : null;
|
|
60
|
+
const scope = mode === "theme" ? group ?? company : company ?? group;
|
|
61
|
+
return { scope, company, theme: group, mode, setMode };
|
|
62
|
+
}, [companySymbol, mode, setMode, themeKey, themeLabel]);
|
|
63
|
+
}
|
|
@@ -44,7 +44,12 @@ export function curveOption(id: CurveId): CurveOption {
|
|
|
44
44
|
return CURVE_OPTIONS.find((option) => option.id === id) ?? CURVE_OPTIONS[0]!;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* `GC`, `GC 2026-01-02`, `GC JGB`, `GC TIPS 2024-01-02`: a curve word and a
|
|
49
|
+
* date, either order. Words with no digit are no date: one that names no
|
|
50
|
+
* curve (`GC world`, `GC "South Africa"`) lists the curves and points at the
|
|
51
|
+
* world tab, which has the other countries' government yields.
|
|
52
|
+
*/
|
|
48
53
|
export function parseCurveArgument(value: unknown, now = new Date()): { curve: CurveId | null; date: string } {
|
|
49
54
|
const words = typeof value === "string" ? value.trim().split(/\s+/).filter(Boolean) : [];
|
|
50
55
|
let curve: CurveId | null = null;
|
|
@@ -54,7 +59,16 @@ export function parseCurveArgument(value: unknown, now = new Date()): { curve: C
|
|
|
54
59
|
if (match && !curve) curve = match.id;
|
|
55
60
|
else rest.push(word);
|
|
56
61
|
}
|
|
57
|
-
|
|
62
|
+
const text = rest.join(" ");
|
|
63
|
+
if (text && !/\d/.test(text) && text.toLowerCase() !== "latest") throw new Error(unknownCurveMessage(text));
|
|
64
|
+
return { curve, date: yieldCurveDate(text || null, now) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `Unknown curve "world"`, with the curves there are and where the rest of the world's yields are. */
|
|
68
|
+
export function unknownCurveMessage(word: string): string {
|
|
69
|
+
const names = CURVE_OPTIONS.map((option) => option.aliases[0]);
|
|
70
|
+
return `Unknown curve "${word}"; the curves are ${names.slice(0, -1).join(", ")} and ${names.at(-1)}. `
|
|
71
|
+
+ "Other countries' government bond yields are on the world tab: gloom fn GC --tab world.";
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
/**
|
|
@@ -3,8 +3,8 @@ import type {
|
|
|
3
3
|
HeadlessPaneDefinition,
|
|
4
4
|
} from "../../../types/plugin";
|
|
5
5
|
import {
|
|
6
|
-
compareDate, curveCutoff,
|
|
7
|
-
parseCompareInput, parseCurveArgument, type CurveData, type CurveId,
|
|
6
|
+
compareDate, curveCutoff, curveIdOf, curveOption, curveSpreadFigures, curveTenorMove, loadComparePoints,
|
|
7
|
+
parseCompareInput, parseCurveArgument, unknownCurveMessage, type CurveData, type CurveId,
|
|
8
8
|
} from "./curves";
|
|
9
9
|
import { yieldTenorRows, type YieldLookbackCurves } from "./chart";
|
|
10
10
|
import { curveAsOf, type YieldPoint, yieldCurveErrors } from "./treasury-data";
|
|
@@ -194,7 +194,7 @@ export function createYieldCurveHeadless(
|
|
|
194
194
|
const argument = parseCurveArgument(args.argument);
|
|
195
195
|
const optionCurve = args.options.curve == null || args.options.curve === "" ? null : curveIdOf(args.options.curve);
|
|
196
196
|
if (args.options.curve && !optionCurve) {
|
|
197
|
-
throw new Error(
|
|
197
|
+
throw new Error(unknownCurveMessage(String(args.options.curve)));
|
|
198
198
|
}
|
|
199
199
|
const curve = optionCurve ?? argument.curve ?? "us";
|
|
200
200
|
const requestedDate = args.options.date != null ? yieldCurveDate(args.options.date, new Date(), "--date") : argument.date;
|
package/src/plugins/bundle.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { mkdir } from "fs/promises";
|
|
2
|
-
import { join } from "path";
|
|
2
|
+
import { basename, join } from "path";
|
|
3
3
|
|
|
4
4
|
import { explainPluginLoadError } from "./compat";
|
|
5
5
|
import { hostPublicModules } from "./host-link";
|
|
6
6
|
import { resolvePluginBrowserEntry, resolvePluginEntry } from "./loader";
|
|
7
|
+
import { siblingImportsBundlerPlugin } from "./peer-imports";
|
|
7
8
|
import { PLUGIN_HOST_GLOBAL, SHARED_SPECIFIERS, importPluginHostModule } from "./host-modules";
|
|
9
|
+
import {
|
|
10
|
+
PLUGIN_PROCESS_IDENTIFIER,
|
|
11
|
+
pluginProcessDeclaration,
|
|
12
|
+
pluginProcessModuleSource,
|
|
13
|
+
} from "./plugin-process";
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* Compiles an external plugin for a renderer that cannot read the filesystem.
|
|
@@ -90,6 +96,7 @@ export interface BundlePluginResult {
|
|
|
90
96
|
function createSharedModuleResolver(
|
|
91
97
|
exportNamesFor: (specifier: string) => Promise<readonly string[]>,
|
|
92
98
|
onShared?: (specifier: string) => void,
|
|
99
|
+
options: { providesProcess?: boolean } = {},
|
|
93
100
|
): import("bun").BunPlugin {
|
|
94
101
|
const namespace = "gloom-host";
|
|
95
102
|
const shared = new Set<string>(SHARED_SPECIFIERS);
|
|
@@ -118,6 +125,19 @@ function createSharedModuleResolver(
|
|
|
118
125
|
contents: buildSharedModuleSource(args.path, await exportNamesFor(args.path)),
|
|
119
126
|
loader: "js",
|
|
120
127
|
}));
|
|
128
|
+
|
|
129
|
+
if (!options.providesProcess) return;
|
|
130
|
+
// `import process from "node:process"` reads the same shim as the bare
|
|
131
|
+
// global. Bun's own browser stand-in has no default export, so without
|
|
132
|
+
// this such a plugin did not compile for the desktop at all.
|
|
133
|
+
build.onResolve({ filter: /^(?:node:)?process$/ }, () => ({
|
|
134
|
+
path: "process",
|
|
135
|
+
namespace: "gloom-process",
|
|
136
|
+
}));
|
|
137
|
+
build.onLoad({ filter: /.*/, namespace: "gloom-process" }, () => ({
|
|
138
|
+
contents: pluginProcessModuleSource(),
|
|
139
|
+
loader: "js",
|
|
140
|
+
}));
|
|
121
141
|
},
|
|
122
142
|
};
|
|
123
143
|
}
|
|
@@ -144,6 +164,11 @@ export async function bundleExternalPlugin(
|
|
|
144
164
|
*/
|
|
145
165
|
minify?: boolean;
|
|
146
166
|
define?: Record<string, string>;
|
|
167
|
+
/**
|
|
168
|
+
* Named in the errors the browser's `process` stand-in throws. Defaults to
|
|
169
|
+
* the plugin's folder name.
|
|
170
|
+
*/
|
|
171
|
+
pluginName?: string;
|
|
147
172
|
/**
|
|
148
173
|
* `bun` compiles the native entry the terminal imports instead. Only the
|
|
149
174
|
* registry check uses it, to cover the native half of a plugin that has
|
|
@@ -161,7 +186,16 @@ export async function bundleExternalPlugin(
|
|
|
161
186
|
|
|
162
187
|
await mkdir(outDir, { recursive: true });
|
|
163
188
|
|
|
189
|
+
// The browser has no `process`, which the terminal gives every plugin. Each
|
|
190
|
+
// bundle gets its own stand-in (see plugin-process.ts); a bundle compiled
|
|
191
|
+
// for Bun runs where the real one exists.
|
|
192
|
+
const providesProcess = (options.target ?? "browser") === "browser";
|
|
193
|
+
const pluginName = options.pluginName ?? basename(pluginDir);
|
|
194
|
+
|
|
164
195
|
const shared = new Set<string>();
|
|
196
|
+
// A sibling plugin it imports is compiled from the sibling's folder, as
|
|
197
|
+
// the loader imports it, not through the plugin's link (peer-imports.ts).
|
|
198
|
+
const siblings = siblingImportsBundlerPlugin(pluginDir);
|
|
165
199
|
const result = await Bun.build({
|
|
166
200
|
entrypoints: [entry],
|
|
167
201
|
outdir: outDir,
|
|
@@ -174,12 +208,19 @@ export async function bundleExternalPlugin(
|
|
|
174
208
|
// `errors`. Returning it lets the check below report what actually broke,
|
|
175
209
|
// such as an import this host version does not export.
|
|
176
210
|
throw: false,
|
|
177
|
-
|
|
211
|
+
// The more specific `process.env.NODE_ENV` a caller defines still wins,
|
|
212
|
+
// so dead branches keyed on it stay dropped.
|
|
213
|
+
define: {
|
|
214
|
+
...(providesProcess ? { process: PLUGIN_PROCESS_IDENTIFIER } : {}),
|
|
215
|
+
...options.define,
|
|
216
|
+
},
|
|
178
217
|
plugins: [
|
|
179
218
|
createSharedModuleResolver(
|
|
180
219
|
options.exportNamesFor ?? hostExportNames,
|
|
181
220
|
(specifier) => shared.add(specifier),
|
|
221
|
+
{ providesProcess },
|
|
182
222
|
),
|
|
223
|
+
...(siblings ? [siblings] : []),
|
|
183
224
|
],
|
|
184
225
|
});
|
|
185
226
|
|
|
@@ -188,6 +229,15 @@ export async function bundleExternalPlugin(
|
|
|
188
229
|
const output = result.outputs.find((entryOutput) => entryOutput.kind === "entry-point");
|
|
189
230
|
if (!output) throw new Error(`Bundling ${pluginDir} produced no entry point`);
|
|
190
231
|
|
|
232
|
+
// Only a bundle that reads `process` carries the stand-in, so the rest of
|
|
233
|
+
// the plugins the web app serves stay as small as they were.
|
|
234
|
+
if (providesProcess) {
|
|
235
|
+
const code = await Bun.file(output.path).text();
|
|
236
|
+
if (code.includes(PLUGIN_PROCESS_IDENTIFIER)) {
|
|
237
|
+
await Bun.write(output.path, pluginProcessDeclaration(pluginName) + code);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
191
241
|
return {
|
|
192
242
|
outputPath: output.path,
|
|
193
243
|
shared: [...shared].sort(),
|
|
@@ -43,7 +43,7 @@ import { ipoCalendarPlugin } from "./builtin/ipo-calendar";
|
|
|
43
43
|
import { clinicalTrialsPlugin } from "./builtin/clinical-trials";
|
|
44
44
|
import { commentLettersPlugin } from "./builtin/comment-letters";
|
|
45
45
|
import { openFdaPlugin } from "./builtin/openfda";
|
|
46
|
-
import {
|
|
46
|
+
import { gloomberbCloudPlugin } from "./builtin/cloud";
|
|
47
47
|
import { changelogModule } from "./builtin/changelog";
|
|
48
48
|
import { roadmapModule } from "./builtin/roadmap";
|
|
49
49
|
import { chartComposerModule } from "./builtin/chart-composer";
|
|
@@ -298,7 +298,7 @@ const browserEarningsPlugin = composeBuiltinPlugin({
|
|
|
298
298
|
* browser-safe public API are absent rather than registered behind stubs.
|
|
299
299
|
*/
|
|
300
300
|
export const browserBuiltinPlugins: readonly GloomPlugin[] = [
|
|
301
|
-
|
|
301
|
+
gloomberbCloudPlugin,
|
|
302
302
|
browserPortfolioPlugin,
|
|
303
303
|
browserTickerCorePlugin,
|
|
304
304
|
browserOptionsVolatilityPlugin,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CliCommandDef } from "../types/plugin";
|
|
2
|
+
import { fearGreedPluginMeta, newsPluginMeta, notesPluginMeta, portfolioPluginMeta } from "./builtin/builtin-plugin-meta";
|
|
3
|
+
import { ALERTS_PLUGIN_ID } from "./builtin/alerts/constants";
|
|
4
|
+
import { alertsCliCommand } from "./builtin/alerts/cli";
|
|
5
|
+
import { fearGreedCliCommand } from "./builtin/fear-greed/cli";
|
|
6
|
+
import { rssCliCommand } from "./builtin/news/wire/rss/cli";
|
|
7
|
+
import { notesCliCommand } from "./builtin/notes/cli";
|
|
8
|
+
import { portfolioCliCommand } from "./builtin/portfolio-list/cli/portfolio-command";
|
|
9
|
+
import { watchlistCliCommand } from "./builtin/portfolio-list/cli/watchlist-command";
|
|
10
|
+
|
|
11
|
+
/** Command definitions shared with the plugins, without loading their pane components. */
|
|
12
|
+
export function getBuiltinCliCommands(): { pluginId: string; commands: CliCommandDef[] }[] {
|
|
13
|
+
return [
|
|
14
|
+
{ pluginId: portfolioPluginMeta.id, commands: [portfolioCliCommand, watchlistCliCommand] },
|
|
15
|
+
{ pluginId: newsPluginMeta.id, commands: [rssCliCommand] },
|
|
16
|
+
{ pluginId: notesPluginMeta.id, commands: [notesCliCommand] },
|
|
17
|
+
{ pluginId: fearGreedPluginMeta.id, commands: [fearGreedCliCommand] },
|
|
18
|
+
{ pluginId: ALERTS_PLUGIN_ID, commands: [alertsCliCommand] },
|
|
19
|
+
];
|
|
20
|
+
}
|
package/src/plugins/host-link.ts
CHANGED
|
@@ -225,6 +225,8 @@ export interface PeerLinkState {
|
|
|
225
225
|
target: string | null;
|
|
226
226
|
/** The link resolves to the sibling's folder. */
|
|
227
227
|
linked: boolean;
|
|
228
|
+
/** Why reading through the link failed, when it did: it can be in place and still not lead anywhere. */
|
|
229
|
+
resolveError?: string;
|
|
228
230
|
/** Links this process wrote at that path, and when it last did. */
|
|
229
231
|
writes: number;
|
|
230
232
|
lastWriteAt?: number;
|
|
@@ -272,11 +274,21 @@ export function describePeerLink(pluginDir: string, peer: string, pluginsDir: st
|
|
|
272
274
|
link = stats.isSymbolicLink() ? (process.platform === "win32" ? "junction" : "symlink") : "file";
|
|
273
275
|
}
|
|
274
276
|
const isLinkType = link === "junction" || link === "symlink";
|
|
277
|
+
const linked = directory !== null && alreadyLinked(linkPath, join(pluginsDir, directory));
|
|
278
|
+
let resolveError: string | undefined;
|
|
279
|
+
if (isLinkType && !linked) {
|
|
280
|
+
try {
|
|
281
|
+
statSync(linkPath);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
resolveError = errorCode(error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
275
286
|
return {
|
|
276
287
|
...base,
|
|
277
288
|
link,
|
|
278
289
|
target: isLinkType ? describeTarget(linkPath, pluginsDir) : null,
|
|
279
|
-
linked
|
|
290
|
+
linked,
|
|
291
|
+
...(resolveError ? { resolveError } : {}),
|
|
280
292
|
};
|
|
281
293
|
}
|
|
282
294
|
|