gloomberb 0.16.2 → 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/account-cache.ts +45 -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/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +524 -148
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +223 -42
- 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 +200 -73
- package/src/cli/desktop-pane-shot.ts +100 -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 +40 -7
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +21 -13
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +55 -7
- package/src/cli/pane-functions/capabilities.ts +13 -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 +89 -28
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +71 -24
- package/src/cli/pane-functions/headless.ts +168 -35
- package/src/cli/pane-functions/index.ts +12 -2
- 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 +12 -1
- package/src/cli/pane-functions/resolver.ts +32 -8
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +109 -23
- package/src/cli/registry.ts +8 -4
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +63 -16
- 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/export.ts +6 -51
- package/src/components/data-table/view.tsx +31 -10
- 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 +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +16 -7
- package/src/components/layout/shell/drag/live.ts +193 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +84 -57
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +461 -180
- 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/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- 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/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- 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 +156 -11
- 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/freshness.ts +5 -0
- 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/pane-model.ts +10 -6
- 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/attention/pane.tsx +1 -1
- 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/catalysts/detail.tsx +1 -1
- 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-attention/pane.tsx +1 -1
- 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/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +32 -6
- package/src/plugins/builtin/holders/pane.tsx +25 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- 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 +121 -45
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- 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/members/pane.tsx +10 -0
- 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/perps/pane.tsx +1 -1
- 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/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- 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/research/relative-valuation-model.ts +7 -3
- 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/model.ts +2 -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 +260 -0
- 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/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- 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/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- 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/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -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 +113 -17
- package/src/renderers/dom/host/drag-shield.ts +35 -0
- package/src/renderers/dom/host/live-frame.ts +62 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +26 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
- package/src/renderers/electrobun/shared/protocol.ts +12 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +13 -7
- package/src/sources/provider-router/financials.ts +106 -16
- 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 +24 -6
- package/src/sources/sec-edgar.ts +68 -8
- package/src/state/pane-scroll-registry.tsx +67 -35
- 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/margin-account.ts +108 -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/resolution.ts +15 -0
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/financials.ts +32 -3
- 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 +16 -0
- package/src/ui/index.tsx +10 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history-integrity.ts +19 -6
- 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
|
@@ -18,8 +18,18 @@ import type {
|
|
|
18
18
|
HeadlessSeriesResult,
|
|
19
19
|
HeadlessSnapshotResult,
|
|
20
20
|
} from "../../types/plugin";
|
|
21
|
-
import { cliStyles, renderSection, renderStats, renderTable } from "../../utils/cli-output";
|
|
21
|
+
import { cliStyles, renderSection, renderStats, renderTable, wrapToTerminal } from "../../utils/cli-output";
|
|
22
22
|
import { humanizeCliKey } from "../result";
|
|
23
|
+
import {
|
|
24
|
+
exportEntriesTable,
|
|
25
|
+
exportNumber,
|
|
26
|
+
exportRowsTable,
|
|
27
|
+
float32Shortest,
|
|
28
|
+
reportFooterLines,
|
|
29
|
+
type CliReportTable,
|
|
30
|
+
type CliReportTables,
|
|
31
|
+
} from "../report-tables";
|
|
32
|
+
import { labelWithUnit } from "../../utils/display-number";
|
|
23
33
|
import { observationDate } from "../../time-series/price-comparison";
|
|
24
34
|
import type { ResolvedSeries } from "../../time-series/types";
|
|
25
35
|
import type { PaneFunctionReport } from "./report";
|
|
@@ -316,20 +326,6 @@ function displayNumber(value: number): string {
|
|
|
316
326
|
return String(float32Shortest(value) ?? Number(value.toPrecision(12)));
|
|
317
327
|
}
|
|
318
328
|
|
|
319
|
-
/**
|
|
320
|
-
* Some feeds store prices as float32, so 338.93 arrives as 338.929992676. A value
|
|
321
|
-
* that is exactly a float32 prints as the shortest decimal that rounds back to it,
|
|
322
|
-
* which leaves genuine doubles (never exactly float32 unless short) untouched.
|
|
323
|
-
*/
|
|
324
|
-
function float32Shortest(value: number): number | null {
|
|
325
|
-
if (Math.fround(value) !== value) return null;
|
|
326
|
-
for (let digits = 1; digits <= 9; digits += 1) {
|
|
327
|
-
const candidate = Number(value.toPrecision(digits));
|
|
328
|
-
if (Math.fround(candidate) === value) return candidate;
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
329
|
function displayTime(time: number): string {
|
|
334
330
|
if (!Number.isFinite(time)) return "-";
|
|
335
331
|
return formatUtcTime(new Date(time));
|
|
@@ -337,14 +333,36 @@ function displayTime(time: number): string {
|
|
|
337
333
|
|
|
338
334
|
const CALENDAR_RESOLUTIONS = new Set(["1d", "1wk", "1mo"]);
|
|
339
335
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
336
|
+
type SeriesPointDate = HeadlessSeries["points"][number]["date"];
|
|
337
|
+
|
|
338
|
+
function seriesPointTime(date: SeriesPointDate): number {
|
|
339
|
+
return date instanceof Date ? date.getTime() : typeof date === "number" ? date : Date.parse(date);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function isDateOnly(date: SeriesPointDate): boolean {
|
|
343
|
+
return typeof date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(date);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Session bars are dated by the trading date the chart and comparison notice use, not the bar's open instant. */
|
|
347
|
+
function calendarSeries(series: HeadlessSeries): (HeadlessSeries & Partial<Pick<ResolvedSeries, "historyResolution" | "timeBasis">>) | null {
|
|
344
348
|
const resolved = series as HeadlessSeries & Partial<Pick<ResolvedSeries, "historyResolution" | "timeBasis">>;
|
|
345
|
-
return resolved.historyResolution && CALENDAR_RESOLUTIONS.has(resolved.historyResolution)
|
|
346
|
-
|
|
347
|
-
|
|
349
|
+
return resolved.historyResolution && CALENDAR_RESOLUTIONS.has(resolved.historyResolution) ? resolved : null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function seriesPointDate(series: HeadlessSeries, date: SeriesPointDate): string {
|
|
353
|
+
const time = seriesPointTime(date);
|
|
354
|
+
if (!Number.isFinite(time)) return typeof date === "string" ? date : "-";
|
|
355
|
+
const calendar = calendarSeries(series);
|
|
356
|
+
return calendar ? observationDate(time, calendar) : formatUtcTime({ time, dateOnly: isDateOnly(date) });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** The same date for a spreadsheet: the trading date, the calendar date, or the ISO instant. */
|
|
360
|
+
function seriesPointExportDate(series: HeadlessSeries, date: SeriesPointDate): string {
|
|
361
|
+
const time = seriesPointTime(date);
|
|
362
|
+
if (!Number.isFinite(time)) return typeof date === "string" ? date : "";
|
|
363
|
+
const calendar = calendarSeries(series);
|
|
364
|
+
if (calendar) return observationDate(time, calendar);
|
|
365
|
+
return isDateOnly(date) ? date as string : new Date(time).toISOString().replace(".000Z", "Z");
|
|
348
366
|
}
|
|
349
367
|
|
|
350
368
|
function seriesValue(series: HeadlessSeries, value: number | null): string {
|
|
@@ -364,6 +382,7 @@ function renderRows(
|
|
|
364
382
|
header: humanizeCliKey(column.header),
|
|
365
383
|
align: column.align,
|
|
366
384
|
width: column.width,
|
|
385
|
+
shrink: column.shrink,
|
|
367
386
|
})),
|
|
368
387
|
rows.map((row) => columns.map((column) => {
|
|
369
388
|
const value = row[column.key];
|
|
@@ -377,6 +396,10 @@ function renderEntries(entries: HeadlessPaneEntry[]): string {
|
|
|
377
396
|
return renderStats(entries.map((entry) => [entry.label, entry.formatted ?? displayValue(entry.value)]));
|
|
378
397
|
}
|
|
379
398
|
|
|
399
|
+
function statisticsEntries(stats: NonNullable<HeadlessSeriesResult["stats"]>): HeadlessPaneEntry[] {
|
|
400
|
+
return Array.isArray(stats) ? stats : Object.entries(stats).map(([label, value]) => ({ label, value }));
|
|
401
|
+
}
|
|
402
|
+
|
|
380
403
|
function renderBundle(
|
|
381
404
|
definition: HeadlessPaneDefinition,
|
|
382
405
|
result: HeadlessBundleResult,
|
|
@@ -412,14 +435,118 @@ function renderSeries(result: HeadlessSeriesResult): string[] {
|
|
|
412
435
|
];
|
|
413
436
|
const lines = [renderRows(rows, columns, undefined)];
|
|
414
437
|
if (result.stats) {
|
|
415
|
-
|
|
416
|
-
? result.stats
|
|
417
|
-
: Object.entries(result.stats).map(([label, value]) => ({ label, value }));
|
|
418
|
-
lines.push("", renderSection("Statistics"), renderEntries(entries));
|
|
438
|
+
lines.push("", renderSection("Statistics"), renderEntries(statisticsEntries(result.stats)));
|
|
419
439
|
}
|
|
420
440
|
return lines;
|
|
421
441
|
}
|
|
422
442
|
|
|
443
|
+
/** The headers the text table prints. */
|
|
444
|
+
function displayedColumns(columns: HeadlessPaneColumn[]): HeadlessPaneColumn[] {
|
|
445
|
+
return columns.map((column) => ({ ...column, header: humanizeCliKey(column.header) }));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const SERIES_FIELDS = ["value", "open", "high", "low", "close", "volume"] as const;
|
|
449
|
+
|
|
450
|
+
/** Every point of a series: its date (the trading date for session bars) and its values, in the series' unit. */
|
|
451
|
+
function seriesTable(series: HeadlessSeries): CliReportTable {
|
|
452
|
+
const fields = SERIES_FIELDS.filter((field) => series.points.some((point) => point[field] != null));
|
|
453
|
+
const unit = series.unit?.trim() ?? "";
|
|
454
|
+
const dates = series.points.map((point) => seriesPointExportDate(series, point.date));
|
|
455
|
+
const header = (field: (typeof SERIES_FIELDS)[number]) => {
|
|
456
|
+
const name = `${field[0]!.toUpperCase()}${field.slice(1)}`;
|
|
457
|
+
return field === "volume" ? name : labelWithUnit(name, unit);
|
|
458
|
+
};
|
|
459
|
+
return {
|
|
460
|
+
title: series.label,
|
|
461
|
+
columns: [dates.every((date) => date.length <= 10) ? "Date" : "Time", ...fields.map(header)],
|
|
462
|
+
rows: series.points.map((point, index) => [
|
|
463
|
+
dates[index]!,
|
|
464
|
+
...fields.map((field) => {
|
|
465
|
+
const value = point[field];
|
|
466
|
+
return typeof value === "number" && Number.isFinite(value) ? exportNumber(value) : "";
|
|
467
|
+
}),
|
|
468
|
+
]),
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** What a report leaves out, for the closing `# incomplete` line. */
|
|
473
|
+
function incompleteDetail(
|
|
474
|
+
result: HeadlessPaneResult,
|
|
475
|
+
status: { complete: boolean; unavailableSymbols: string[] },
|
|
476
|
+
): boolean | string {
|
|
477
|
+
if (status.complete) return false;
|
|
478
|
+
const requested = result.metadata?.requested;
|
|
479
|
+
const available = result.metadata?.available;
|
|
480
|
+
if (typeof requested === "number" && typeof available === "number" && available < requested) {
|
|
481
|
+
return `${available} of ${requested} available`;
|
|
482
|
+
}
|
|
483
|
+
return status.unavailableSymbols.length > 0 ? `no data for ${status.unavailableSymbols.join(", ")}` : true;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function textNotices(result: HeadlessPaneResult): string[] {
|
|
487
|
+
const notices = result.metadata?.notices;
|
|
488
|
+
if (!Array.isArray(notices)) return [];
|
|
489
|
+
return [...new Set(notices.filter((notice): notice is string => typeof notice === "string" && notice.trim().length > 0))];
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* The tables of a headless report for `--csv` and `--ndjson`, with the text
|
|
494
|
+
* view's columns: a rows or snapshot report is one table, a bundle one per
|
|
495
|
+
* section (key/value sections as `Metric,Value`), a series report one per
|
|
496
|
+
* series with every point, then its statistics. The footer carries the text
|
|
497
|
+
* view's closing line, notices, notes and errors.
|
|
498
|
+
*/
|
|
499
|
+
export function headlessReportTables(
|
|
500
|
+
definition: HeadlessPaneDefinition,
|
|
501
|
+
result: HeadlessPaneResult,
|
|
502
|
+
args: HeadlessPaneLoadArgs,
|
|
503
|
+
fallbackTitle: string,
|
|
504
|
+
freshness: ReportFreshness,
|
|
505
|
+
status: { complete: boolean; unavailableSymbols: string[] },
|
|
506
|
+
): CliReportTables {
|
|
507
|
+
const title = reportTitle(definition, args, result, fallbackTitle);
|
|
508
|
+
let tables: CliReportTable[];
|
|
509
|
+
switch (definition.shape) {
|
|
510
|
+
case "rows": {
|
|
511
|
+
const rowsResult = result as HeadlessPaneResult & { rows: HeadlessPaneRow[]; columns?: HeadlessPaneColumn[] };
|
|
512
|
+
const columns = displayedColumns(columnsFor(rowsResult.rows, rowsResult.columns, definition.columns));
|
|
513
|
+
tables = [exportRowsTable(title, columns, rowsResult.rows)];
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
case "bundle":
|
|
517
|
+
tables = (result as HeadlessBundleResult).sections.map((section) => (
|
|
518
|
+
"rows" in section && section.rows
|
|
519
|
+
? exportRowsTable(section.title, displayedColumns(columnsFor(section.rows, section.columns, definition.columns)), section.rows)
|
|
520
|
+
: exportEntriesTable(section.title, section.entries)
|
|
521
|
+
));
|
|
522
|
+
break;
|
|
523
|
+
case "series": {
|
|
524
|
+
const seriesResult = result as HeadlessSeriesResult;
|
|
525
|
+
tables = seriesResult.series.map(seriesTable);
|
|
526
|
+
if (seriesResult.stats) tables.push(exportEntriesTable("Statistics", statisticsEntries(seriesResult.stats)));
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case "snapshot": {
|
|
530
|
+
const snapshot = result as HeadlessSnapshotResult;
|
|
531
|
+
tables = [exportRowsTable(title, displayedColumns(columnsFor(snapshot.items, undefined, definition.columns)), snapshot.items)];
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
default: {
|
|
535
|
+
const _exhaustive: never = definition.shape;
|
|
536
|
+
return _exhaustive;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
tables,
|
|
541
|
+
footer: reportFooterLines({
|
|
542
|
+
freshness,
|
|
543
|
+
incomplete: incompleteDetail(result, status),
|
|
544
|
+
errors: result.errors,
|
|
545
|
+
notes: [...textNotices(result), ...(result.notes ?? [])],
|
|
546
|
+
}),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
|
|
423
550
|
function reportTitle(
|
|
424
551
|
definition: HeadlessPaneDefinition,
|
|
425
552
|
args: HeadlessPaneLoadArgs,
|
|
@@ -433,8 +560,8 @@ function reportTitle(
|
|
|
433
560
|
/** One message stays on the label's line; several each get a line of their own, so none runs into the next. */
|
|
434
561
|
function renderMessages(label: string, messages: readonly string[]): string[] {
|
|
435
562
|
return messages.length === 1
|
|
436
|
-
? [cliStyles.warning(`${label}: ${messages[0]}`)]
|
|
437
|
-
: [cliStyles.warning(`${label}:`), ...messages.map((message) => ` ${message}`)];
|
|
563
|
+
? [cliStyles.warning(wrapToTerminal(`${label}: ${messages[0]}`))]
|
|
564
|
+
: [cliStyles.warning(`${label}:`), ...messages.map((message) => wrapToTerminal(` ${message}`))];
|
|
438
565
|
}
|
|
439
566
|
|
|
440
567
|
/**
|
|
@@ -450,11 +577,8 @@ export function renderHeadlessPaneText(
|
|
|
450
577
|
freshness: ReportFreshness = deriveHeadlessFreshness(definition, result),
|
|
451
578
|
): string {
|
|
452
579
|
const lines = [cliStyles.bold(reportTitle(definition, args, result, fallbackTitle)), ""];
|
|
453
|
-
const notices = result
|
|
454
|
-
if (
|
|
455
|
-
const textNotices = [...new Set(notices.filter((notice): notice is string => typeof notice === "string" && notice.trim().length > 0))];
|
|
456
|
-
if (textNotices.length) lines.push(...textNotices, "");
|
|
457
|
-
}
|
|
580
|
+
const notices = textNotices(result);
|
|
581
|
+
if (notices.length) lines.push(...notices, "");
|
|
458
582
|
switch (definition.shape) {
|
|
459
583
|
case "rows": {
|
|
460
584
|
const rowsResult = result as HeadlessPaneResult & { rows: HeadlessPaneRow[]; columns?: HeadlessPaneColumn[] };
|
|
@@ -480,7 +604,7 @@ export function renderHeadlessPaneText(
|
|
|
480
604
|
}
|
|
481
605
|
if (result.notes?.length) lines.push("", ...renderMessages("Notes", result.notes));
|
|
482
606
|
if (result.errors?.length) lines.push("", ...renderMessages("Errors", result.errors));
|
|
483
|
-
return [lines.join("\n").trimEnd(), "", cliStyles.muted(formatFreshnessLine(freshness))].join("\n");
|
|
607
|
+
return [lines.join("\n").trimEnd(), "", cliStyles.muted(wrapToTerminal(formatFreshnessLine(freshness)))].join("\n");
|
|
484
608
|
}
|
|
485
609
|
|
|
486
610
|
function resultRowCount(definition: HeadlessPaneDefinition, result: HeadlessPaneResult): number {
|
|
@@ -513,6 +637,7 @@ export async function buildHeadlessFunctionReport(
|
|
|
513
637
|
const unavailableSymbols = loaded.result.unavailableSymbols ?? (rowCount === 0 ? symbols : []);
|
|
514
638
|
const serialized = serializeHeadlessPaneResult(loaded.definition, loaded.result);
|
|
515
639
|
const freshness = deriveHeadlessFreshness(loaded.definition, loaded.result);
|
|
640
|
+
const complete = loaded.result.complete !== false && unavailableSymbols.length === 0 && !loaded.result.errors?.length;
|
|
516
641
|
return {
|
|
517
642
|
data: {
|
|
518
643
|
kind: loaded.definition.shape,
|
|
@@ -522,7 +647,7 @@ export async function buildHeadlessFunctionReport(
|
|
|
522
647
|
options: resolved.options,
|
|
523
648
|
rowCount,
|
|
524
649
|
empty: rowCount === 0,
|
|
525
|
-
complete
|
|
650
|
+
complete,
|
|
526
651
|
unavailableSymbols,
|
|
527
652
|
...serialized,
|
|
528
653
|
freshness,
|
|
@@ -534,5 +659,13 @@ export async function buildHeadlessFunctionReport(
|
|
|
534
659
|
resolved.label,
|
|
535
660
|
freshness,
|
|
536
661
|
),
|
|
662
|
+
tables: headlessReportTables(
|
|
663
|
+
loaded.definition,
|
|
664
|
+
loaded.result,
|
|
665
|
+
loaded.args,
|
|
666
|
+
resolved.label,
|
|
667
|
+
freshness,
|
|
668
|
+
{ complete, unavailableSymbols },
|
|
669
|
+
),
|
|
537
670
|
};
|
|
538
671
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { accessGateStatus, incompleteReportGateMessage } from "./access-gate";
|
|
24
24
|
import { withPersistedCloudSession } from "./cloud-session";
|
|
25
25
|
import { loadForListing } from "../listing-arg";
|
|
26
|
+
import { selectReportTables } from "../report-tables";
|
|
26
27
|
|
|
27
28
|
async function withPaneRuntime<T>(
|
|
28
29
|
ctx: CliCommandContext,
|
|
@@ -33,7 +34,7 @@ async function withPaneRuntime<T>(
|
|
|
33
34
|
registry: PaneFunctionCatalog;
|
|
34
35
|
resolved: ResolvedPaneFunction;
|
|
35
36
|
}) => Promise<T>,
|
|
36
|
-
settings: { strictHeadlessOptions?: boolean } = {},
|
|
37
|
+
settings: { strictHeadlessOptions?: boolean; tableSection?: boolean } = {},
|
|
37
38
|
): Promise<T> {
|
|
38
39
|
return withMarketData(ctx, async (market) => {
|
|
39
40
|
const context: MarketContext = ctx.cliOptions.refresh ? { ...market, refresh: true } : market;
|
|
@@ -59,6 +60,11 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
59
60
|
+ `Use "gloomberb catalog ${resolved.token}" to inspect readiness.`,
|
|
60
61
|
);
|
|
61
62
|
}
|
|
63
|
+
const tabular = ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson";
|
|
64
|
+
if (resolved.tableSection !== undefined && !tabular) {
|
|
65
|
+
throw new Error("--section picks one table of --csv or --ndjson output.");
|
|
66
|
+
}
|
|
67
|
+
if (resolved.tableSection === true) throw new Error("--section needs a section title or number.");
|
|
62
68
|
// A report that fails or comes back empty for an exchange the symbol is not listed on says so.
|
|
63
69
|
const report = await withPersistedCloudSession(
|
|
64
70
|
context,
|
|
@@ -82,8 +88,9 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
82
88
|
}
|
|
83
89
|
ctx.printResult({ data: report.data }, {
|
|
84
90
|
text: () => report.text,
|
|
91
|
+
...(tabular ? { tables: selectReportTables(report.tables, resolved.tableSection) } : {}),
|
|
85
92
|
});
|
|
86
|
-
}, { strictHeadlessOptions: true });
|
|
93
|
+
}, { strictHeadlessOptions: true, tableSection: true });
|
|
87
94
|
});
|
|
88
95
|
}
|
|
89
96
|
|
|
@@ -111,6 +118,7 @@ export async function runPaneScreenshot(args: string[], ctx: CliCommandContext)
|
|
|
111
118
|
theme: parsed.theme,
|
|
112
119
|
scale: parsed.scale,
|
|
113
120
|
watermark: parsed.watermark,
|
|
121
|
+
statusLine: parsed.status !== false,
|
|
114
122
|
options: parsed.options,
|
|
115
123
|
});
|
|
116
124
|
if (parsed.requireBotSafe && !result.usable) {
|
|
@@ -130,7 +138,9 @@ export async function runPaneScreenshot(args: string[], ctx: CliCommandContext)
|
|
|
130
138
|
["Rows", String(data.rowCount)],
|
|
131
139
|
["Status", issues.length > 0 ? cliStyles.warning(issues.join(", ")) : cliStyles.success("complete")],
|
|
132
140
|
];
|
|
141
|
+
if (data.render.statusLine) stats.push(["Status line", data.render.statusLine]);
|
|
133
142
|
if (data.unusableReason) stats.push(["Reason", data.unusableReason]);
|
|
143
|
+
if (data.notices?.length) stats.push(["Notes", data.notices.join(" ")]);
|
|
134
144
|
if (data.unavailableSymbols.length > 0) stats.push(["No data for", data.unavailableSymbols.join(", ")]);
|
|
135
145
|
if (data.render.emptyStateMarkers.length > 0) stats.push(["Empty states", data.render.emptyStateMarkers.join(", ")]);
|
|
136
146
|
if (data.render.missingExpectedText.length > 0) stats.push(["Missing text", data.render.missingExpectedText.join(", ")]);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
|
|
2
|
+
import {
|
|
3
|
+
activeUsMarketSession,
|
|
4
|
+
isAlwaysOpenExchange,
|
|
5
|
+
isRegularSessionTime,
|
|
6
|
+
latestRegularSessionClose,
|
|
7
|
+
latestRegularSessionOpen,
|
|
8
|
+
nextRegularSessionOpen,
|
|
9
|
+
} from "../../market-data/market/freshness";
|
|
10
|
+
import { currentFxClosureStart } from "../../utils/fx-market-hours";
|
|
11
|
+
import { zonedDateKey } from "../../utils/zoned-date-time";
|
|
12
|
+
import type { ReportTime } from "../../utils/utc-time";
|
|
13
|
+
|
|
14
|
+
/** Where the venues behind a quote report stand, as a report says it after the delay. */
|
|
15
|
+
type ReportMarketState = "open" | "pre-market" | "after-hours" | "closed";
|
|
16
|
+
|
|
17
|
+
export interface ReportMarket {
|
|
18
|
+
state: ReportMarketState;
|
|
19
|
+
/** While no venue is in its regular session: the next regular open of the first to open, when its calendar is known. */
|
|
20
|
+
reopensAt?: string;
|
|
21
|
+
/** That open's date at its venue, so a Monday open in Sydney reads Monday. */
|
|
22
|
+
reopensOn?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One dated row of a quote report: the venue it trades on and the session its source reports. */
|
|
26
|
+
export interface SessionObservation {
|
|
27
|
+
exchange: string;
|
|
28
|
+
marketState?: string;
|
|
29
|
+
time: ReportTime | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface VenueSession {
|
|
33
|
+
state: ReportMarketState | "always";
|
|
34
|
+
next?: { date: string; open: number };
|
|
35
|
+
/** The latest regular session that has closed: its local date and open. */
|
|
36
|
+
last?: { date: string; open: number | null };
|
|
37
|
+
timeZone?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const CLOSED_STATES = new Set(["CLOSED", "PREPRE", "POSTPOST"]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A venue's session now, from its calendar (weekends and published holidays),
|
|
44
|
+
* or from the state its source reports when the app has no calendar for it,
|
|
45
|
+
* such as an index publisher or a futures exchange. Null when neither says.
|
|
46
|
+
*/
|
|
47
|
+
function venueSession(exchange: string, marketState: string | undefined, now: number): VenueSession | null {
|
|
48
|
+
if (isAlwaysOpenExchange(exchange)) return { state: "always" };
|
|
49
|
+
const next = nextRegularSessionOpen(exchange, now);
|
|
50
|
+
if (next) {
|
|
51
|
+
const { timeZone } = next;
|
|
52
|
+
const latestClose = latestRegularSessionClose(exchange, now);
|
|
53
|
+
const last = latestClose ? { date: latestClose.date, open: latestRegularSessionOpen(exchange, latestClose.close) } : undefined;
|
|
54
|
+
if (isRegularSessionTime(exchange, now)) return { state: "open", timeZone };
|
|
55
|
+
if (isUsListingExchange(exchange)) {
|
|
56
|
+
const today = zonedDateKey(now, timeZone);
|
|
57
|
+
const session = activeUsMarketSession(now);
|
|
58
|
+
if (session === "PRE" && next.date === today) return { state: "pre-market", next, last, timeZone };
|
|
59
|
+
if (session === "POST" && last?.date === today) return { state: "after-hours", next, last, timeZone };
|
|
60
|
+
}
|
|
61
|
+
return { state: "closed", next, last, timeZone };
|
|
62
|
+
}
|
|
63
|
+
if (marketState === "REGULAR") return { state: "open" };
|
|
64
|
+
if (marketState === "PRE") return { state: "pre-market" };
|
|
65
|
+
if (marketState === "POST") return { state: "after-hours" };
|
|
66
|
+
if (marketState && CLOSED_STATES.has(marketState)) return { state: "closed" };
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The venue spot FX pairs trade on. It has no session calendar here; its week is in `fx-market-hours`. */
|
|
71
|
+
const FX_VENUE = "CCY";
|
|
72
|
+
const DAY_MS = 24 * 60 * 60_000;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The newest observation is its venue's latest close: the venue has not
|
|
76
|
+
* opened since, and the print is from that session's day, after its open (an
|
|
77
|
+
* after-hours print of that day counts). Returns that session's local date.
|
|
78
|
+
*/
|
|
79
|
+
function closeDateOf(observation: SessionObservation, session: VenueSession, now: number): string | null {
|
|
80
|
+
const time = observation.time;
|
|
81
|
+
if (!time || time.dateOnly) return null;
|
|
82
|
+
// FX closes once a week, Friday 17:00 New York time, and its trading day turns
|
|
83
|
+
// at 17:00: a print from that last day, or a quiet tick over the weekend, is
|
|
84
|
+
// that Friday's close, not the day it happens to be stamped with.
|
|
85
|
+
const weekClose = canonicalExchange(observation.exchange) === FX_VENUE ? currentFxClosureStart(now) : null;
|
|
86
|
+
if (weekClose != null) return time.time >= weekClose - DAY_MS ? zonedDateKey(weekClose - 1, "America/New_York") : null;
|
|
87
|
+
if (session.last && session.timeZone) {
|
|
88
|
+
if (session.last.open != null && time.time < session.last.open) return null;
|
|
89
|
+
return zonedDateKey(time.time, session.timeZone) === session.last.date ? session.last.date : null;
|
|
90
|
+
}
|
|
91
|
+
// No calendar: the source says the session is over, and the print is dated in UTC.
|
|
92
|
+
return session.state === "closed" && !session.last ? new Date(time.time).toISOString().slice(0, 10) : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Where the markets behind a quote report stand now, and whether the newest
|
|
97
|
+
* observation is a close. Null market when the venues disagree (Tokyo open,
|
|
98
|
+
* New York closed), trade around the clock, or none can be placed; a report
|
|
99
|
+
* then says nothing rather than guess.
|
|
100
|
+
*/
|
|
101
|
+
export function reportMarketSession(
|
|
102
|
+
observations: readonly SessionObservation[],
|
|
103
|
+
now: number,
|
|
104
|
+
): { market: ReportMarket | null; closeDate: string | null } {
|
|
105
|
+
const sessions = new Map<string, VenueSession>();
|
|
106
|
+
let newest: { observation: SessionObservation; session: VenueSession } | null = null;
|
|
107
|
+
for (const observation of observations) {
|
|
108
|
+
const exchange = canonicalExchange(observation.exchange);
|
|
109
|
+
if (!exchange) continue;
|
|
110
|
+
const key = `${exchange}:${observation.marketState ?? ""}`;
|
|
111
|
+
const session = sessions.get(key) ?? venueSession(exchange, observation.marketState, now);
|
|
112
|
+
if (!session) continue;
|
|
113
|
+
sessions.set(key, session);
|
|
114
|
+
if (observation.time && (!newest || observation.time.time > (newest.observation.time?.time ?? -Infinity))) {
|
|
115
|
+
newest = { observation, session };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const states = new Set([...sessions.values()].map((session) => session.state));
|
|
119
|
+
if (states.size !== 1 || states.has("always")) return { market: null, closeDate: null };
|
|
120
|
+
const state = [...states][0] as ReportMarketState;
|
|
121
|
+
const reopen = state === "closed" || state === "pre-market"
|
|
122
|
+
? [...sessions.values()].reduce<{ date: string; open: number } | null>((first, session) => (
|
|
123
|
+
session.next && (!first || session.next.open < first.open) ? session.next : first
|
|
124
|
+
), null)
|
|
125
|
+
: null;
|
|
126
|
+
return {
|
|
127
|
+
market: {
|
|
128
|
+
state,
|
|
129
|
+
...(reopen ? { reopensAt: new Date(reopen.open).toISOString(), reopensOn: reopen.date } : {}),
|
|
130
|
+
},
|
|
131
|
+
closeDate: (state === "closed" || state === "pre-market") && newest ? closeDateOf(newest.observation, newest.session, now) : null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -32,6 +32,8 @@ export interface ParsedPaneFunctionArgs {
|
|
|
32
32
|
scale: number;
|
|
33
33
|
/** Small label drawn in the pane title bar, e.g. a domain; null draws nothing. */
|
|
34
34
|
watermark: string | null;
|
|
35
|
+
/** A screenshot carries its dated status line in the pane footer; `--no-status` sets this false. */
|
|
36
|
+
status?: boolean;
|
|
35
37
|
requireBotSafe: boolean;
|
|
36
38
|
/** The listing a one-ticker function's argument named, once `applyListingArgument` has resolved it. */
|
|
37
39
|
listing?: ListingArg;
|
|
@@ -67,7 +69,7 @@ export function parseArgumentsOption(value: string): Record<string, string> {
|
|
|
67
69
|
return pairs;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
|
-
export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: number } = {}): ParsedPaneFunctionArgs {
|
|
72
|
+
export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: number; tail?: number } = {}): ParsedPaneFunctionArgs {
|
|
71
73
|
const positionals: string[] = [];
|
|
72
74
|
const options: Record<string, string | true> = {};
|
|
73
75
|
let outputPath: string | null = null;
|
|
@@ -76,6 +78,7 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
|
|
|
76
78
|
let theme: string | null = null;
|
|
77
79
|
let scale = 1;
|
|
78
80
|
let watermark: string | null = null;
|
|
81
|
+
let status = true;
|
|
79
82
|
let requireBotSafe = false;
|
|
80
83
|
|
|
81
84
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -94,6 +97,11 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
|
|
|
94
97
|
const equalsIndex = raw.indexOf("=");
|
|
95
98
|
const key = equalsIndex >= 0 ? raw.slice(0, equalsIndex) : raw;
|
|
96
99
|
const inlineValue = equalsIndex >= 0 ? raw.slice(equalsIndex + 1) : undefined;
|
|
100
|
+
// A switch: the token after it is the function's argument, never its value.
|
|
101
|
+
if (normalizeOptionKey(key) === "noStatus" && inlineValue === undefined) {
|
|
102
|
+
status = false;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
97
105
|
const next = args[index + 1];
|
|
98
106
|
const nextValue = inlineValue === undefined && next && !next.startsWith("--") ? args[++index] : true;
|
|
99
107
|
const value: string | true = inlineValue ?? nextValue ?? true;
|
|
@@ -118,6 +126,7 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
|
|
|
118
126
|
scale = parseScale(value);
|
|
119
127
|
} else if (normalizedKey === "watermark" && value !== true) {
|
|
120
128
|
watermark = value.trim() || null;
|
|
129
|
+
|
|
121
130
|
} else if (normalizedKey === "arguments" && value !== true) {
|
|
122
131
|
Object.assign(options, parseArgumentsOption(value));
|
|
123
132
|
} else {
|
|
@@ -127,10 +136,12 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
|
|
|
127
136
|
|
|
128
137
|
const target = positionals[0]?.trim() ?? "";
|
|
129
138
|
const arg = positionals.slice(1).join(" ").trim();
|
|
130
|
-
// The outer CLI parser consumes --limit before fn/shot sees its arguments.
|
|
131
|
-
// Restore
|
|
139
|
+
// The outer CLI parser consumes --limit and --tail before fn/shot sees its arguments.
|
|
140
|
+
// Restore them for the pane's own schema and loader, including bounds checks,
|
|
141
|
+
// so a function without a tail option says so instead of ignoring it.
|
|
132
142
|
if (globalOptions.limit != null) options.limit = String(globalOptions.limit);
|
|
133
|
-
|
|
143
|
+
if (globalOptions.tail != null) options.tail = String(globalOptions.tail);
|
|
144
|
+
return { target, arg, options, outputPath, width, height, theme, scale, watermark, status, requireBotSafe };
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
export function parsePaneCatalogArgs(args: string[]): ParsedPaneCatalogArgs {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { RemoteUiNodeSnapshot } from "../../remote/types";
|
|
2
|
+
import type { HeadlessFreshnessStatus, HeadlessPaneFreshness } from "../../types/headless";
|
|
3
|
+
import { isFiniteNumber, isRecord } from "../../utils/guards";
|
|
4
|
+
|
|
5
|
+
export interface RenderedReportNotices {
|
|
6
|
+
/** Lines a report prints under its title, such as "21 of 145 strikes". */
|
|
7
|
+
notices: string[];
|
|
8
|
+
/** The same facts by key, for `metadata` in JSON: `strikes: { shown, total, window }`. */
|
|
9
|
+
facts: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* What a rendered pane says about the rows it lists, from the `report-notice`
|
|
14
|
+
* nodes it publishes: `{ text, key?, value? }`. The pane knows what its view
|
|
15
|
+
* leaves out (the option chain's strike window); the report repeats it.
|
|
16
|
+
*/
|
|
17
|
+
export function renderedReportNotices(semanticUi: readonly RemoteUiNodeSnapshot[]): RenderedReportNotices {
|
|
18
|
+
const notices: string[] = [];
|
|
19
|
+
const facts: Record<string, unknown> = {};
|
|
20
|
+
for (const node of semanticUi) {
|
|
21
|
+
if (node.role !== "report-notice" || !node.metadata) continue;
|
|
22
|
+
const { text, key, value } = node.metadata as { text?: unknown; key?: unknown; value?: unknown };
|
|
23
|
+
if (typeof text === "string" && text.trim() && !notices.includes(text.trim())) notices.push(text.trim());
|
|
24
|
+
if (typeof key === "string" && key && value !== undefined) facts[key] = value;
|
|
25
|
+
}
|
|
26
|
+
return { notices, facts };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const FRESHNESS_STATUSES: readonly HeadlessFreshnessStatus[] = ["live", "delayed", "stale", "not-a-feed"];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How current a rendered pane says its data is, from the `report-freshness`
|
|
33
|
+
* node it publishes: `{ asOf?, status?, delayMinutes? }`. A capture keeps no
|
|
34
|
+
* footer status and few cells carry a time, so a pane that knows its feed
|
|
35
|
+
* (OMON's chain) dates the report here, as the plain command over the same
|
|
36
|
+
* data does. A pane whose figures come from several dated quotes (FXC's pair
|
|
37
|
+
* rates) publishes them instead, see `renderedReportObservations`.
|
|
38
|
+
*/
|
|
39
|
+
export function renderedReportFreshness(
|
|
40
|
+
semanticUi: readonly RemoteUiNodeSnapshot[],
|
|
41
|
+
): Pick<HeadlessPaneFreshness, "asOf" | "status" | "delayMinutes"> {
|
|
42
|
+
const metadata = semanticUi.find((node) => node.role === "report-freshness")?.metadata;
|
|
43
|
+
if (!metadata) return {};
|
|
44
|
+
const { asOf, status, delayMinutes } = metadata as { asOf?: unknown; status?: unknown; delayMinutes?: unknown };
|
|
45
|
+
return {
|
|
46
|
+
...(typeof asOf === "string" || isFiniteNumber(asOf) ? { asOf } : {}),
|
|
47
|
+
...(FRESHNESS_STATUSES.includes(status as HeadlessFreshnessStatus) ? { status: status as HeadlessFreshnessStatus } : {}),
|
|
48
|
+
...(isFiniteNumber(delayMinutes) && delayMinutes > 0 ? { delayMinutes } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The dated observations a rendered pane's figures come from, from the
|
|
54
|
+
* `observations` its `report-freshness` node publishes: one record per quote
|
|
55
|
+
* or rate, in the fields of a headless report's rows (`quoteTime`,
|
|
56
|
+
* `dataSource`, `delayMinutes`, `stale`, `sessionExchange`, `marketState`).
|
|
57
|
+
* The report reads them as it reads those rows, so it says what the newest is,
|
|
58
|
+
* how far the feed is held back, which are stale and where the market stands.
|
|
59
|
+
*/
|
|
60
|
+
export function renderedReportObservations(semanticUi: readonly RemoteUiNodeSnapshot[]): Record<string, unknown>[] {
|
|
61
|
+
const observations = semanticUi.find((node) => node.role === "report-freshness")?.metadata?.observations;
|
|
62
|
+
return Array.isArray(observations) ? observations.filter(isRecord) : [];
|
|
63
|
+
}
|
|
@@ -2,8 +2,9 @@ import type { MarketContext } from "../types";
|
|
|
2
2
|
import type { NormalizedPaneFunctionOptions } from "./capabilities";
|
|
3
3
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
4
4
|
import { buildHeadlessFunctionReport } from "./headless";
|
|
5
|
-
import { appendDomReportFooter, buildDomFunctionReport } from "./dom";
|
|
5
|
+
import { appendDomReportFooter, buildDomFunctionReport, domReportNote } from "./dom";
|
|
6
6
|
import type { ReportFreshness } from "./freshness";
|
|
7
|
+
import type { CliReportTables } from "../report-tables";
|
|
7
8
|
|
|
8
9
|
export type PaneFunctionReportSource = "headless" | "dom";
|
|
9
10
|
|
|
@@ -29,6 +30,8 @@ interface PaneFunctionReportData {
|
|
|
29
30
|
export interface PaneFunctionReport {
|
|
30
31
|
data: PaneFunctionReportData;
|
|
31
32
|
text: string;
|
|
33
|
+
/** What `--csv` and `--ndjson` write: the text view's tables as flat rows, and the closing `#` lines. */
|
|
34
|
+
tables: CliReportTables;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
export function resolvePaneFunctionReportSource(
|
|
@@ -46,6 +49,10 @@ export async function buildFunctionReport(
|
|
|
46
49
|
const startedAt = performance.now();
|
|
47
50
|
const source = resolvePaneFunctionReportSource(resolved);
|
|
48
51
|
if (!source) {
|
|
52
|
+
// HELP is what a newcomer tries first: send them to the command guide and the function list.
|
|
53
|
+
if (resolved.pane.id === "help") {
|
|
54
|
+
throw new Error("HELP is an in-app pane, so it has no report. For the commands run `gloomberb help`; for the functions run `gloomberb catalog`.");
|
|
55
|
+
}
|
|
49
56
|
throw new Error(`${resolved.token} is an interactive pane and does not expose a data report.`);
|
|
50
57
|
}
|
|
51
58
|
|
|
@@ -61,6 +68,10 @@ export async function buildFunctionReport(
|
|
|
61
68
|
report.data.truncated === true,
|
|
62
69
|
report.data.freshness,
|
|
63
70
|
);
|
|
71
|
+
report.tables = {
|
|
72
|
+
...report.tables,
|
|
73
|
+
footer: [...report.tables.footer, `note: ${domReportNote(elapsedMs, report.data.truncated === true)}`],
|
|
74
|
+
};
|
|
64
75
|
}
|
|
65
76
|
return report;
|
|
66
77
|
}
|