gloomberb 0.16.3 → 0.16.4
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/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { DEFAULT_THEME, listThemes, resolveThemeId, themes, type ThemeSummary } from "../../theme/themes";
|
|
2
|
+
import { fail } from "../errors";
|
|
3
|
+
|
|
4
|
+
const THEME_TRAIT_NOTES = { "high-contrast": "high contrast", "colorblind-safe": "color-blind safe" } as const;
|
|
5
|
+
|
|
6
|
+
/** `white (White Phosphor)`, or what the app shows instead of an id it does not know. */
|
|
7
|
+
export function describeThemeId(id: string): string {
|
|
8
|
+
const theme = themes[id];
|
|
9
|
+
if (theme) return `${id} (${theme.name})`;
|
|
10
|
+
return `${id}, not a theme: the app shows ${describeThemeId(DEFAULT_THEME)}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Every id with its name, from the registry, for an error that has to say what would have worked. */
|
|
14
|
+
function themeChoicesText(): string {
|
|
15
|
+
const choices = listThemes().map(({ id, name }) => `${id} (${name})`).join(", ");
|
|
16
|
+
return `Themes: ${choices}. gloomberb config themes lists them with notes.`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The id `input` names, by id or display name in any case. Anything else
|
|
21
|
+
* fails with the closest id when there is one, and every id with its name.
|
|
22
|
+
*/
|
|
23
|
+
export function requireThemeId(input: string): string {
|
|
24
|
+
const lookup = resolveThemeId(input);
|
|
25
|
+
if (lookup.ok) return lookup.id;
|
|
26
|
+
const hint = lookup.suggestion ? ` Did you mean ${describeThemeId(lookup.suggestion)}?` : "";
|
|
27
|
+
return fail(`Unknown theme "${input.trim()}".${hint}`, themeChoicesText());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ThemeListRow extends Record<string, unknown> {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
appearance: ThemeSummary["appearance"];
|
|
34
|
+
colorblindSafe: boolean;
|
|
35
|
+
highContrast: boolean;
|
|
36
|
+
default: boolean;
|
|
37
|
+
current: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function themeListRows(currentThemeId: string | null): ThemeListRow[] {
|
|
41
|
+
return listThemes().map((theme) => ({
|
|
42
|
+
id: theme.id,
|
|
43
|
+
name: theme.name,
|
|
44
|
+
appearance: theme.appearance,
|
|
45
|
+
colorblindSafe: theme.traits.includes("colorblind-safe"),
|
|
46
|
+
highContrast: theme.traits.includes("high-contrast"),
|
|
47
|
+
default: theme.id === DEFAULT_THEME,
|
|
48
|
+
current: theme.id === currentThemeId,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function themeListNote(row: ThemeListRow): string {
|
|
53
|
+
const notes: string[] = [];
|
|
54
|
+
if (row.highContrast) notes.push(THEME_TRAIT_NOTES["high-contrast"]);
|
|
55
|
+
if (row.colorblindSafe) notes.push(THEME_TRAIT_NOTES["colorblind-safe"]);
|
|
56
|
+
if (row.default) notes.push("default");
|
|
57
|
+
if (row.current) notes.push("current");
|
|
58
|
+
return notes.join(", ");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The name of a theme id the registry knows, null for one it does not. */
|
|
62
|
+
export function themeName(id: string): string | null {
|
|
63
|
+
return themes[id]?.name ?? null;
|
|
64
|
+
}
|
|
@@ -22,8 +22,10 @@ import {
|
|
|
22
22
|
type CliStatEntry,
|
|
23
23
|
} from "../../utils/cli-output";
|
|
24
24
|
import {
|
|
25
|
+
EXTENDED_SESSION_LABELS,
|
|
25
26
|
exchangeShortName,
|
|
26
27
|
getExtendedSessionDisplay,
|
|
28
|
+
getQuoteSessionFields,
|
|
27
29
|
getRegularSessionDisplay,
|
|
28
30
|
marketStateLabel,
|
|
29
31
|
} from "../../market-data/market/status";
|
|
@@ -49,11 +51,13 @@ import {
|
|
|
49
51
|
formatWatchlistNames,
|
|
50
52
|
} from "../helpers";
|
|
51
53
|
import { NotesFiles } from "../../plugins/builtin/notes/files";
|
|
52
|
-
import {
|
|
54
|
+
import { isKnownNonUsListing } from "../../utils/sec";
|
|
53
55
|
import { canonicalExchange, exchangeLabel, isKnownExchangeCode } from "../../utils/exchanges";
|
|
54
56
|
import { failIfNotTraded, ListingArgError, listingIdentity, resolveCliListing, type CliListing } from "../listing-arg";
|
|
57
|
+
import { isNotATickerMessage } from "../not-a-ticker";
|
|
55
58
|
import { sharesOutstandingInReceipts } from "../../utils/depositary-receipt";
|
|
56
59
|
import { cliFreshnessFooter } from "../result";
|
|
60
|
+
import { providerMissReason } from "../../sources/provider-errors";
|
|
57
61
|
import { exportEntriesTable, reportFooterLines, type CliReportTables } from "../report-tables";
|
|
58
62
|
import type { ReportFreshness } from "../pane-functions/freshness";
|
|
59
63
|
import { fundamentalsFreshness, quotesFreshness } from "../freshness";
|
|
@@ -178,26 +182,35 @@ function getFilingDescription(filing: SecFilingItem): string | undefined {
|
|
|
178
182
|
return description;
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
185
|
+
/**
|
|
186
|
+
* SEC filings are asked for unless the listing is known to be outside the US:
|
|
187
|
+
* by the saved ticker, the requested venue, the quote, or the listing the
|
|
188
|
+
* source still describes when it has no current quote. A symbol nothing is
|
|
189
|
+
* known about yet is looked up as a US ticker.
|
|
190
|
+
*/
|
|
191
|
+
function shouldFetchSecFilings(
|
|
192
|
+
symbol: string,
|
|
193
|
+
exchange: string,
|
|
194
|
+
tickerFile: TickerRecord | null,
|
|
195
|
+
financials: TickerFinancials,
|
|
196
|
+
): boolean {
|
|
197
|
+
const saved = tickerFile?.metadata;
|
|
198
|
+
const { quote, quoteMetadata } = financials;
|
|
199
|
+
return !isKnownNonUsListing({
|
|
200
|
+
metadata: {
|
|
201
|
+
ticker: saved?.ticker ?? symbol,
|
|
202
|
+
exchange: saved?.exchange || exchange || quote?.listingExchangeName || quote?.exchangeName
|
|
203
|
+
|| quoteMetadata?.listingExchangeName || "",
|
|
204
|
+
currency: saved?.currency || quote?.currency || quoteMetadata?.currency || "",
|
|
205
|
+
name: saved?.name ?? symbol,
|
|
206
|
+
broker_contracts: saved?.broker_contracts,
|
|
207
|
+
portfolios: [],
|
|
208
|
+
watchlists: [],
|
|
209
|
+
positions: [],
|
|
210
|
+
custom: {},
|
|
211
|
+
tags: [],
|
|
212
|
+
},
|
|
213
|
+
});
|
|
201
214
|
}
|
|
202
215
|
|
|
203
216
|
async function appendTickerPositions(lines: string[], tickerFile: TickerRecord | null, quote: TickerFinancials["quote"],
|
|
@@ -430,6 +443,7 @@ export async function buildTickerReport({
|
|
|
430
443
|
config,
|
|
431
444
|
toBase,
|
|
432
445
|
notes,
|
|
446
|
+
quoteNote,
|
|
433
447
|
recentNews = [],
|
|
434
448
|
recentSecFilings = [],
|
|
435
449
|
}: {
|
|
@@ -441,6 +455,8 @@ export async function buildTickerReport({
|
|
|
441
455
|
config: AppConfig;
|
|
442
456
|
toBase: (value: number, fromCurrency: string) => Promise<number>;
|
|
443
457
|
notes?: string;
|
|
458
|
+
/** Why there is no quote, when a source said; "Quote unavailable." otherwise. */
|
|
459
|
+
quoteNote?: string;
|
|
444
460
|
recentNews?: NewsArticle[];
|
|
445
461
|
recentSecFilings?: SecFilingItem[];
|
|
446
462
|
}): Promise<string> {
|
|
@@ -455,7 +471,7 @@ export async function buildTickerReport({
|
|
|
455
471
|
const lines: string[] = [];
|
|
456
472
|
|
|
457
473
|
lines.push(`${cliStyles.accent(quote?.symbol ?? symbol)} ${cliStyles.bold(name)}`);
|
|
458
|
-
if (!quote) lines.push(cliStyles.muted(
|
|
474
|
+
if (!quote) lines.push(cliStyles.muted(quoteNote ?? QUOTE_UNAVAILABLE));
|
|
459
475
|
|
|
460
476
|
const summaryParts = [
|
|
461
477
|
listingVenueLabel(listingExchange, quote, financials, tickerFile) || undefined,
|
|
@@ -537,8 +553,8 @@ export async function buildTickerReport({
|
|
|
537
553
|
]);
|
|
538
554
|
|
|
539
555
|
appendMetricSection(lines, "Extended Hours", [
|
|
540
|
-
[
|
|
541
|
-
[
|
|
556
|
+
[EXTENDED_SESSION_LABELS.PRE, extendedRow("PRE")],
|
|
557
|
+
[EXTENDED_SESSION_LABELS.POST, extendedRow("POST")],
|
|
542
558
|
]);
|
|
543
559
|
}
|
|
544
560
|
|
|
@@ -627,10 +643,9 @@ function buildTickerStructuredData({
|
|
|
627
643
|
symbol: quote.symbol,
|
|
628
644
|
instrumentType: quote.instrumentType,
|
|
629
645
|
name: quote.name,
|
|
630
|
-
|
|
646
|
+
// Last and Change as the text report reads them, then the extended print from that close.
|
|
647
|
+
...getQuoteSessionFields(quote),
|
|
631
648
|
priceBasis: quote.priceBasis ?? null,
|
|
632
|
-
change: quote.change,
|
|
633
|
-
changePercent: quote.changePercent,
|
|
634
649
|
currency: quote.currency,
|
|
635
650
|
marketCap: quote.marketCap ?? null,
|
|
636
651
|
volume: quote.volume ?? null,
|
|
@@ -682,6 +697,18 @@ function buildTickerStructuredData({
|
|
|
682
697
|
};
|
|
683
698
|
}
|
|
684
699
|
|
|
700
|
+
const QUOTE_UNAVAILABLE = "Quote unavailable.";
|
|
701
|
+
|
|
702
|
+
/** Asks for the quote the report lacks once more, for the reason the data service gave when it had none. */
|
|
703
|
+
async function quoteUnavailableNote(dataProvider: MarketContext["dataProvider"], symbol: string, exchange: string): Promise<string> {
|
|
704
|
+
try {
|
|
705
|
+
await dataProvider.getQuote(symbol, exchange);
|
|
706
|
+
} catch (error) {
|
|
707
|
+
return providerMissReason(error) ?? QUOTE_UNAVAILABLE;
|
|
708
|
+
}
|
|
709
|
+
return QUOTE_UNAVAILABLE;
|
|
710
|
+
}
|
|
711
|
+
|
|
685
712
|
export async function ticker(symbol: string, dependencies: TickerCommandDependencies = {}) {
|
|
686
713
|
const initMarketDataFn = dependencies.initMarketData ?? initMarketData;
|
|
687
714
|
const failCommand = dependencies.fail ?? fail;
|
|
@@ -705,7 +732,10 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
705
732
|
} catch (error) {
|
|
706
733
|
// A known exchange the symbol is not listed on: say where it is.
|
|
707
734
|
await failIfNotTraded(listing, { store, dataProvider }, { fail: failCommand });
|
|
708
|
-
|
|
735
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
736
|
+
// A symbol no listing carries is not a failed fetch, and the message already names it.
|
|
737
|
+
if (isNotATickerMessage(reason)) failCommand(reason);
|
|
738
|
+
failCommand(`Failed to fetch data for ${normalized}.`, reason);
|
|
709
739
|
}
|
|
710
740
|
|
|
711
741
|
const hasResearchData = financials && (
|
|
@@ -742,7 +772,7 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
742
772
|
}),
|
|
743
773
|
// Outside the US the lookup checks the SEC registrant against the listing's company,
|
|
744
774
|
// and refuses another company's filings.
|
|
745
|
-
shouldFetchSecFilings(tickerFile, resolvedFinancials) && dataProvider.getSecFilings
|
|
775
|
+
shouldFetchSecFilings(requestSymbol, exchange, tickerFile, resolvedFinancials) && dataProvider.getSecFilings
|
|
746
776
|
? dataProvider.getSecFilings(requestSymbol, SEC_FILING_LIMIT, exchange || quote?.exchangeName || "",
|
|
747
777
|
listingName ? { listingName } : undefined)
|
|
748
778
|
: Promise.resolve([]),
|
|
@@ -754,10 +784,11 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
754
784
|
|
|
755
785
|
// The quote is the feed in this report; without one, the fundamentals date it.
|
|
756
786
|
const freshness = quote ? quotesFreshness([quote]) : fundamentalsFreshness(resolvedFinancials);
|
|
787
|
+
const quoteNote = quote ? undefined : await quoteUnavailableNote(dataProvider, requestSymbol, exchange);
|
|
757
788
|
if (dependencies.printResult) {
|
|
758
789
|
dependencies.printResult({
|
|
759
790
|
freshness,
|
|
760
|
-
warnings: quote ? undefined : [
|
|
791
|
+
warnings: quote ? undefined : [quoteNote!],
|
|
761
792
|
data: buildTickerStructuredData({
|
|
762
793
|
symbol: normalized,
|
|
763
794
|
listing,
|
|
@@ -780,6 +811,7 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
|
|
|
780
811
|
config,
|
|
781
812
|
toBase,
|
|
782
813
|
notes,
|
|
814
|
+
quoteNote,
|
|
783
815
|
recentNews,
|
|
784
816
|
recentSecFilings,
|
|
785
817
|
}));
|
|
@@ -18,6 +18,7 @@ import type { DesktopExternalPluginBundle } from "../renderers/electrobun/shared
|
|
|
18
18
|
import type { HttpProxyRequestEnvelope, HttpProxyResponseEnvelope } from "../utils/http-proxy-response";
|
|
19
19
|
import type { PaneAccessGate } from "./pane-functions/access-gate";
|
|
20
20
|
import { readVisibleKeyValues } from "./visible-key-values";
|
|
21
|
+
import { scrollTruncationReasons } from "./scroll-truncation";
|
|
21
22
|
import { SHOT_API_PROXY_PREFIX, SHOT_HTTP_BRIDGE_PATH, SHOT_MARKET_BRIDGE_PATH } from "./desktop-pane-shot-routes";
|
|
22
23
|
import { SESSION_COOKIE_NAMES } from "../api-client/session-cookie";
|
|
23
24
|
import {
|
|
@@ -54,6 +55,11 @@ export interface DesktopPaneShotPayload {
|
|
|
54
55
|
deviceScaleFactor?: number;
|
|
55
56
|
/** Label drawn at the right edge of the pane title bar; null draws nothing. */
|
|
56
57
|
watermark?: string | null;
|
|
58
|
+
/**
|
|
59
|
+
* The pane footer keeps a row for the capture's dated status line, filled
|
|
60
|
+
* once the pane has loaded. Absent for `--no-status` and for reports.
|
|
61
|
+
*/
|
|
62
|
+
statusLine?: boolean;
|
|
57
63
|
tickers: TickerRecord[];
|
|
58
64
|
/** Broker accounts as last synced, by profile, so a broker portfolio shows its equity and cash. */
|
|
59
65
|
brokerAccounts?: Record<string, BrokerAccount[]>;
|
|
@@ -111,6 +117,14 @@ export interface DesktopPaneShotRenderedRow {
|
|
|
111
117
|
|
|
112
118
|
export interface DesktopPaneShotRenderResult {
|
|
113
119
|
visibleText: string;
|
|
120
|
+
/**
|
|
121
|
+
* The rows a report reads, when a table reports every row (an option chain's
|
|
122
|
+
* strike window) rather than those in the viewport; `rows` stays what the
|
|
123
|
+
* capture shows. Absent when every table is read from the viewport.
|
|
124
|
+
*/
|
|
125
|
+
reportRows?: DesktopPaneShotRenderedRow[];
|
|
126
|
+
/** `truncationReasons` without the cuts of tables whose every row is in `reportRows`. */
|
|
127
|
+
reportTruncationReasons?: string[];
|
|
114
128
|
/** Text of the pane footers, where a pane says whether its data is live, delayed or stale. */
|
|
115
129
|
footerText?: string;
|
|
116
130
|
visibleKeyValues?: Array<{ label: string; text: string }>;
|
|
@@ -125,8 +139,16 @@ export interface DesktopPaneShotRenderResult {
|
|
|
125
139
|
/** A wall that needs an account, or a Pro lock on part of the pane. */
|
|
126
140
|
accessGate?: PaneAccessGate | null;
|
|
127
141
|
semanticUi: RemoteUiNodeSnapshot[];
|
|
142
|
+
/** The status line drawn in the capture's footer, as it fit the width. */
|
|
143
|
+
statusLine?: string | null;
|
|
128
144
|
}
|
|
129
145
|
|
|
146
|
+
/**
|
|
147
|
+
* The status line variants for a capture, longest first, worked out from what
|
|
148
|
+
* the pane rendered; null leaves the footer row empty.
|
|
149
|
+
*/
|
|
150
|
+
type ShotStatusLine = (rendered: DesktopPaneShotRenderResult) => readonly string[] | null | Promise<readonly string[] | null>;
|
|
151
|
+
|
|
130
152
|
type CdpResponse = {
|
|
131
153
|
id?: number;
|
|
132
154
|
result?: unknown;
|
|
@@ -163,7 +185,7 @@ export async function renderDesktopPaneScreenshot(
|
|
|
163
185
|
payload: DesktopPaneShotPayload,
|
|
164
186
|
outputPath: string,
|
|
165
187
|
apiProxy: DesktopPaneShotApiProxy,
|
|
166
|
-
options: { captureImage?: boolean; bridge?: DesktopPaneShotBridge } = {},
|
|
188
|
+
options: { captureImage?: boolean; bridge?: DesktopPaneShotBridge; statusLine?: ShotStatusLine } = {},
|
|
167
189
|
): Promise<DesktopPaneShotRenderResult> {
|
|
168
190
|
const tempDir = await mkdtemp(join(tmpdir(), "gloom-pane-shot-"));
|
|
169
191
|
let server: ReturnType<typeof Bun.serve> | null = null;
|
|
@@ -182,6 +204,7 @@ export async function renderDesktopPaneScreenshot(
|
|
|
182
204
|
deviceScaleFactor: payload.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR,
|
|
183
205
|
userDataDir: join(tempDir, "chrome-profile"),
|
|
184
206
|
captureImage: options.captureImage !== false,
|
|
207
|
+
statusLine: payload.statusLine ? options.statusLine : undefined,
|
|
185
208
|
});
|
|
186
209
|
} finally {
|
|
187
210
|
server?.stop(true);
|
|
@@ -402,6 +425,7 @@ async function capturePageScreenshot({
|
|
|
402
425
|
deviceScaleFactor,
|
|
403
426
|
userDataDir,
|
|
404
427
|
captureImage,
|
|
428
|
+
statusLine,
|
|
405
429
|
}: {
|
|
406
430
|
chrome: string;
|
|
407
431
|
url: string;
|
|
@@ -411,6 +435,7 @@ async function capturePageScreenshot({
|
|
|
411
435
|
deviceScaleFactor: number;
|
|
412
436
|
userDataDir: string;
|
|
413
437
|
captureImage: boolean;
|
|
438
|
+
statusLine?: ShotStatusLine;
|
|
414
439
|
}): Promise<DesktopPaneShotRenderResult> {
|
|
415
440
|
await mkdir(userDataDir, { recursive: true });
|
|
416
441
|
const port = 43000 + Math.floor(Math.random() * 10000);
|
|
@@ -457,6 +482,10 @@ async function capturePageScreenshot({
|
|
|
457
482
|
});
|
|
458
483
|
await waitForShotReady(session);
|
|
459
484
|
const rendered = await readRenderedPaneState(session);
|
|
485
|
+
if (captureImage && statusLine) {
|
|
486
|
+
const variants = await statusLine(rendered);
|
|
487
|
+
rendered.statusLine = variants?.length ? await fitShotStatusLine(session, variants) : null;
|
|
488
|
+
}
|
|
460
489
|
if (captureImage) {
|
|
461
490
|
const screenshot = await session.send("Page.captureScreenshot", {
|
|
462
491
|
format: "png",
|
|
@@ -515,6 +544,8 @@ const EMPTY_STATE_PATTERNS = [
|
|
|
515
544
|
/\bNo transcribed calls(?: for [^.]+)? yet\b/gi,
|
|
516
545
|
/\bNo short interest (?:data|found)\b/gi,
|
|
517
546
|
/\bNo (?:House PTR |insider )?(?:trades|members|transactions|filings)\b/gi,
|
|
547
|
+
// OMON on an expiry the chain does not list, with the dates nearest it.
|
|
548
|
+
/\bNo (?:option contracts returned for )?expiry \d{4}-\d{2}-\d{2}(?:; (?:available|nearest others): \d{4}-\d{2}-\d{2}(?:, \d{4}-\d{2}-\d{2})*(?: \(\+\d+ more\))?|; the chain lists no expiries)?/gi,
|
|
518
549
|
];
|
|
519
550
|
|
|
520
551
|
function stateMarkers(text: string, patterns: RegExp[]): string[] {
|
|
@@ -541,26 +572,39 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
541
572
|
const semanticTables = semanticUi.filter((node) => node && node.role === "table");
|
|
542
573
|
const rows = [];
|
|
543
574
|
const truncationReasons = new Set();
|
|
575
|
+
const scrollReasons = (${scrollTruncationReasons.toString()});
|
|
576
|
+
const visibleTables = [...root.querySelectorAll('[data-gloom-role="data-table"]')].filter(isVisible);
|
|
577
|
+
// A table that publishes every row has nothing cut from a report, whatever the viewport shows.
|
|
578
|
+
const reportedRowsOf = (tableIndex) => {
|
|
579
|
+
const metadata = semanticTables[tableIndex] && semanticTables[tableIndex].metadata;
|
|
580
|
+
return metadata && Array.isArray(metadata.rows) ? metadata.rows : null;
|
|
581
|
+
};
|
|
582
|
+
const reportedTables = new Set(visibleTables.filter((_table, tableIndex) => reportedRowsOf(tableIndex)));
|
|
583
|
+
const reportRows = [];
|
|
584
|
+
const reportTruncationReasons = new Set();
|
|
585
|
+
const inReportedTable = (element) => {
|
|
586
|
+
const table = element.closest('[data-gloom-role="data-table"]');
|
|
587
|
+
return !!table && reportedTables.has(table);
|
|
588
|
+
};
|
|
544
589
|
const markScrollTruncation = (element) => {
|
|
545
590
|
if (!(element instanceof HTMLElement) || !isVisible(element)) return;
|
|
546
|
-
|
|
547
|
-
element.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
element.getAttribute("data-gloom-scrollbar-
|
|
554
|
-
|
|
555
|
-
)
|
|
556
|
-
|
|
557
|
-
|
|
591
|
+
const reasons = scrollReasons({
|
|
592
|
+
scrollTop: element.scrollTop,
|
|
593
|
+
scrollHeight: element.scrollHeight,
|
|
594
|
+
clientHeight: element.clientHeight,
|
|
595
|
+
scrollLeft: element.scrollLeft,
|
|
596
|
+
scrollWidth: element.scrollWidth,
|
|
597
|
+
clientWidth: element.clientWidth,
|
|
598
|
+
scrollbarY: element.getAttribute("data-gloom-scrollbar-y") === "visible",
|
|
599
|
+
scrollbarX: element.getAttribute("data-gloom-scrollbar-x") === "visible",
|
|
600
|
+
});
|
|
601
|
+
reasons.forEach((reason) => truncationReasons.add(reason));
|
|
602
|
+
if (!inReportedTable(element)) reasons.forEach((reason) => reportTruncationReasons.add(reason));
|
|
558
603
|
};
|
|
559
604
|
[...root.querySelectorAll(
|
|
560
605
|
'[data-gloom-scrollbar-x="visible"], [data-gloom-scrollbar-y="visible"], [data-gloom-role="data-table-body-scroll"]',
|
|
561
606
|
)].forEach(markScrollTruncation);
|
|
562
|
-
|
|
563
|
-
.filter(isVisible)
|
|
607
|
+
visibleTables
|
|
564
608
|
.forEach((table, tableIndex) => {
|
|
565
609
|
const tableMetadata = semanticTables[tableIndex] && semanticTables[tableIndex].metadata || {};
|
|
566
610
|
const semanticColumns = Array.isArray(tableMetadata.columns) ? tableMetadata.columns : [];
|
|
@@ -586,6 +630,7 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
586
630
|
))
|
|
587
631
|
) {
|
|
588
632
|
truncationReasons.add("one or more cells are visibly clipped");
|
|
633
|
+
if (!reportedTables.has(table)) reportTruncationReasons.add("one or more cells are visibly clipped");
|
|
589
634
|
}
|
|
590
635
|
const instant = cell.getAttribute("data-gloom-cell-instant");
|
|
591
636
|
return {
|
|
@@ -598,12 +643,27 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
598
643
|
};
|
|
599
644
|
}).filter((cell) => cell.text.length > 0);
|
|
600
645
|
if (cells.length === 0) return;
|
|
601
|
-
|
|
646
|
+
const captured = {
|
|
602
647
|
tableIndex,
|
|
603
648
|
rowIndex,
|
|
604
649
|
...(rowKey ? { key: rowKey } : {}),
|
|
605
650
|
selected: row.getAttribute("data-selected") === "true",
|
|
606
651
|
cells,
|
|
652
|
+
};
|
|
653
|
+
rows.push(captured);
|
|
654
|
+
if (!reportedTables.has(table)) reportRows.push(captured);
|
|
655
|
+
});
|
|
656
|
+
const reported = reportedRowsOf(tableIndex);
|
|
657
|
+
if (!reported) return;
|
|
658
|
+
reported.forEach((row, rowIndex) => {
|
|
659
|
+
const cells = Array.isArray(row && row.cells) ? row.cells.filter((cell) => cell && cell.text) : [];
|
|
660
|
+
if (cells.length === 0) return;
|
|
661
|
+
reportRows.push({
|
|
662
|
+
tableIndex,
|
|
663
|
+
rowIndex,
|
|
664
|
+
...(typeof row.key === "string" ? { key: row.key } : {}),
|
|
665
|
+
selected: row.selected === true,
|
|
666
|
+
cells,
|
|
607
667
|
});
|
|
608
668
|
});
|
|
609
669
|
});
|
|
@@ -642,6 +702,7 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
642
702
|
rows,
|
|
643
703
|
truncated: truncationReasons.size > 0,
|
|
644
704
|
truncationReasons: [...truncationReasons],
|
|
705
|
+
...(reportedTables.size > 0 ? { reportRows, reportTruncationReasons: [...reportTruncationReasons] } : {}),
|
|
645
706
|
semanticUi,
|
|
646
707
|
};
|
|
647
708
|
})()`,
|
|
@@ -660,6 +721,8 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
660
721
|
rows?: DesktopPaneShotRenderedRow[];
|
|
661
722
|
truncated?: boolean;
|
|
662
723
|
truncationReasons?: string[];
|
|
724
|
+
reportRows?: DesktopPaneShotRenderedRow[];
|
|
725
|
+
reportTruncationReasons?: string[];
|
|
663
726
|
semanticUi?: RemoteUiNodeSnapshot[];
|
|
664
727
|
};
|
|
665
728
|
};
|
|
@@ -687,6 +750,10 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
687
750
|
rows,
|
|
688
751
|
truncated: value?.truncated === true || textShowsEllipsis,
|
|
689
752
|
truncationReasons,
|
|
753
|
+
...(Array.isArray(value?.reportRows) ? {
|
|
754
|
+
reportRows: value.reportRows,
|
|
755
|
+
reportTruncationReasons: (value.reportTruncationReasons ?? []).filter((reason): reason is string => typeof reason === "string"),
|
|
756
|
+
} : {}),
|
|
690
757
|
loadingStateDetected: value?.loadingStateDetected === true || loadingStateMarkers.length > 0,
|
|
691
758
|
errorStateDetected: value?.errorStateDetected === true || errorStateMarkers.length > 0,
|
|
692
759
|
errorStateMarkers,
|
|
@@ -697,6 +764,20 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
|
|
|
697
764
|
};
|
|
698
765
|
}
|
|
699
766
|
|
|
767
|
+
/** Draws the first variant that fits the pane footer, and returns what it drew. */
|
|
768
|
+
async function fitShotStatusLine(session: CdpSession, variants: readonly string[]): Promise<string | null> {
|
|
769
|
+
const result = await session.send("Runtime.evaluate", {
|
|
770
|
+
expression: `(() => {
|
|
771
|
+
const fit = window.__GLOOM_CLI_SHOT_FIT_STATUS__;
|
|
772
|
+
return fit ? fit(${JSON.stringify(variants)}) : null;
|
|
773
|
+
})()`,
|
|
774
|
+
awaitPromise: true,
|
|
775
|
+
returnByValue: true,
|
|
776
|
+
}) as { result?: { value?: unknown } };
|
|
777
|
+
const value = result.result?.value;
|
|
778
|
+
return typeof value === "string" ? value : null;
|
|
779
|
+
}
|
|
780
|
+
|
|
700
781
|
async function waitForPageWebSocket(port: number, targetUrl: string): Promise<string> {
|
|
701
782
|
for (let attempt = 0; attempt < CHROME_POLL_ATTEMPTS; attempt += 1) {
|
|
702
783
|
try {
|
package/src/cli/errors.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { AppPersistence } from "../data/app-persistence";
|
|
2
|
+
import { ApiRequestError } from "../api-client/errors";
|
|
2
3
|
import { DEFAULT_CLI_OPTIONS, type CliGlobalOptions } from "./options";
|
|
3
4
|
import { serializeCliError, type CliErrorObject } from "./result";
|
|
4
5
|
import { cliStyles, cliTerminalWidth, wrapText } from "../utils/cli-output";
|
|
6
|
+
import { withSearchHints } from "./not-a-ticker";
|
|
5
7
|
|
|
6
8
|
const USAGE_PREFIX = "Usage: ";
|
|
9
|
+
/** The code of every error a command fails with: bad input, nothing found, an option it does not take. */
|
|
10
|
+
const CLI_ERROR_CODE = "cli_error";
|
|
7
11
|
|
|
8
12
|
export interface CliErrorContext {
|
|
9
13
|
/** The command that failed, used to point at its help. */
|
|
10
14
|
command?: string;
|
|
15
|
+
/** What the user typed after the command, so a hint can repeat their own spelling of a symbol. */
|
|
16
|
+
args?: readonly string[];
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
class CliFailure extends Error {
|
|
@@ -15,7 +21,7 @@ class CliFailure extends Error {
|
|
|
15
21
|
readonly details?: unknown;
|
|
16
22
|
readonly retryable?: boolean;
|
|
17
23
|
|
|
18
|
-
constructor(message: string, details?: unknown, code =
|
|
24
|
+
constructor(message: string, details?: unknown, code = CLI_ERROR_CODE, retryable?: boolean) {
|
|
19
25
|
super(message);
|
|
20
26
|
this.name = "CliFailure";
|
|
21
27
|
this.code = code;
|
|
@@ -37,6 +43,11 @@ function isCliFailure(error: unknown): error is CliFailure {
|
|
|
37
43
|
return error instanceof CliFailure;
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
/** A global option that does not parse is a usage error like any other, not an unexpected one. */
|
|
47
|
+
export function asUsageError(error: unknown): Error {
|
|
48
|
+
return isCliFailure(error) ? error : new CliFailure(error instanceof Error ? error.message : String(error));
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
function cliErrorObject(error: unknown): CliErrorObject {
|
|
41
52
|
if (isCliFailure(error)) {
|
|
42
53
|
return {
|
|
@@ -46,6 +57,10 @@ function cliErrorObject(error: unknown): CliErrorObject {
|
|
|
46
57
|
retryable: error.retryable,
|
|
47
58
|
};
|
|
48
59
|
}
|
|
60
|
+
// Gloom Cloud refused what was asked (an unknown series, a bad date): the input was wrong, not the program.
|
|
61
|
+
if (error instanceof ApiRequestError && error.status === 400) {
|
|
62
|
+
return { code: CLI_ERROR_CODE, message: error.message };
|
|
63
|
+
}
|
|
49
64
|
return {
|
|
50
65
|
code: "unexpected_error",
|
|
51
66
|
message: error instanceof Error ? error.message : String(error),
|
|
@@ -87,10 +102,18 @@ function formatCliErrorText(error: CliErrorObject, context: CliErrorContext): st
|
|
|
87
102
|
].join("\n");
|
|
88
103
|
}
|
|
89
104
|
|
|
105
|
+
function hintedError(error: CliErrorObject, args: readonly string[]): CliErrorObject {
|
|
106
|
+
return {
|
|
107
|
+
...error,
|
|
108
|
+
message: withSearchHints(error.message, args),
|
|
109
|
+
...(typeof error.details === "string" ? { details: withSearchHints(error.details, args) } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
90
113
|
export function printCliError(error: unknown, options: CliGlobalOptions, context: CliErrorContext = {}): void {
|
|
91
114
|
if (options.quiet && options.format === "text") return;
|
|
92
115
|
const errorObject = cliErrorObject(error);
|
|
93
116
|
console.error(options.format === "text"
|
|
94
|
-
? formatCliErrorText(errorObject, context)
|
|
117
|
+
? formatCliErrorText(hintedError(errorObject, context.args ?? []), context)
|
|
95
118
|
: serializeCliError(errorObject, options));
|
|
96
119
|
}
|
package/src/cli/freshness.ts
CHANGED
|
@@ -25,17 +25,20 @@ export function rowsFreshness(
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** One row per quote shown, as `fn` quote reports carry them: live, delayed or stale for its session. */
|
|
28
|
-
function quoteFreshnessRow(quote: Quote): HeadlessPaneRow {
|
|
29
|
-
return { ...quoteFreshnessFields(quote), updatedAt: quote.lastUpdated };
|
|
28
|
+
function quoteFreshnessRow(quote: Quote, now: number): HeadlessPaneRow {
|
|
29
|
+
return { ...quoteFreshnessFields(quote, now), updatedAt: quote.lastUpdated };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Worst of across the quotes shown, dated by the newest, with their delay and
|
|
34
|
+
* where their markets stand. Quotes that failed to load say nothing.
|
|
35
|
+
*/
|
|
33
36
|
export function quotesFreshness(
|
|
34
37
|
quotes: Iterable<Quote | null | undefined>,
|
|
35
38
|
declared?: HeadlessPaneFreshness,
|
|
36
39
|
now = Date.now(),
|
|
37
40
|
): ReportFreshness | undefined {
|
|
38
|
-
const rows = [...quotes].filter((quote): quote is Quote => quote != null).map(quoteFreshnessRow);
|
|
41
|
+
const rows = [...quotes].filter((quote): quote is Quote => quote != null).map((quote) => quoteFreshnessRow(quote, now));
|
|
39
42
|
return rows.length > 0 ? rowsFreshness(rows, declared, undefined, now) : undefined;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `gloomberb fx` is asked for. A bare code is one unit of it in the base
|
|
3
|
+
* currency (`NGN`: USD per 1 NGN). A pair is market convention, BASE/QUOTE:
|
|
4
|
+
* `USD/NGN` is how many NGN one USD buys, and `ZAR/NGN` a cross of two codes
|
|
5
|
+
* with neither in USD.
|
|
6
|
+
*/
|
|
7
|
+
export interface FxRequest {
|
|
8
|
+
/** The currency one unit of which is priced: NGN in `NGN`, USD in `USD/NGN`. */
|
|
9
|
+
currency: string;
|
|
10
|
+
/** The currency the price is in: the base currency for a bare code, the quote of a pair. */
|
|
11
|
+
baseCurrency: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CURRENCY_CODE = /^[A-Z]{3}$/;
|
|
15
|
+
|
|
16
|
+
/** The request, or the sentence saying what is wrong with the argument. */
|
|
17
|
+
export function parseFxRequest(raw: string, baseCurrency: string): FxRequest | { error: string } {
|
|
18
|
+
const text = raw.trim().toUpperCase();
|
|
19
|
+
const parts = text.split("/").map((part) => part.trim());
|
|
20
|
+
if (parts.length > 2 || parts.some((part) => !part)) return { error: `"${raw}" is not a currency or a pair such as USD/NGN.` };
|
|
21
|
+
const bad = parts.filter((part) => !CURRENCY_CODE.test(part));
|
|
22
|
+
if (bad.length > 0) return { error: `${bad.join(" and ")} ${bad.length > 1 ? "are not currency codes" : "is not a currency code"}; use three letters such as NGN.` };
|
|
23
|
+
return parts.length === 2
|
|
24
|
+
? { currency: parts[0]!, baseCurrency: parts[1]! }
|
|
25
|
+
: { currency: parts[0]!, baseCurrency: baseCurrency.trim().toUpperCase() };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The rate of `currency` in `baseCurrency` from each one's USD value (USD per
|
|
30
|
+
* unit), the way every cross is built. NaN when a leg is missing or unusable.
|
|
31
|
+
*/
|
|
32
|
+
export function crossRate(currencyInUsd: number | null | undefined, baseInUsd: number | null | undefined): number {
|
|
33
|
+
if (!isUsableRate(currencyInUsd) || !isUsableRate(baseInUsd)) return Number.NaN;
|
|
34
|
+
return currencyInUsd / baseInUsd;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isUsableRate(value: number | null | undefined): value is number {
|
|
38
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A rate to six significant digits, so a tiny one keeps its figures
|
|
43
|
+
* (0.000752791) and float noise goes; from 1000 up, to the cent (1328.39).
|
|
44
|
+
* Never in exponent notation, and without trailing zeros.
|
|
45
|
+
*/
|
|
46
|
+
export function formatFxRate(rate: number): string {
|
|
47
|
+
if (!Number.isFinite(rate)) return String(rate);
|
|
48
|
+
const size = Math.abs(rate);
|
|
49
|
+
const decimals = size >= 1000 || size === 0 ? 2 : Math.min(15, Math.max(0, 5 - Math.floor(Math.log10(size))));
|
|
50
|
+
const fixed = rate.toFixed(decimals);
|
|
51
|
+
return fixed.includes(".") ? fixed.replace(/\.?0+$/, "") : fixed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Both directions on one line: `1 NGN = 0.000752791 USD (USD/NGN 1328.39)`. */
|
|
55
|
+
export function describeFxRate(request: FxRequest, rate: number): string {
|
|
56
|
+
return `1 ${request.currency} = ${formatFxRate(rate)} ${request.baseCurrency} (${request.baseCurrency}/${request.currency} ${formatFxRate(1 / rate)})`;
|
|
57
|
+
}
|