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
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports as flat tables, for `--csv` and `--ndjson`: the columns the text
|
|
3
|
+
* view shows, one row per row it shows, and values a spreadsheet can use.
|
|
4
|
+
* Numbers drop the thousands commas, compact scales and units the text draws
|
|
5
|
+
* (the unit moves into the header, as table CSV exports do), times are ISO,
|
|
6
|
+
* and nothing is a JSON blob. CSV writes a report with several tables as
|
|
7
|
+
* stacked blocks under `# section:` lines and ends with `#` lines for the
|
|
8
|
+
* source, as-of, notes and errors.
|
|
9
|
+
*/
|
|
10
|
+
import { serializeCsv } from "../utils/csv";
|
|
11
|
+
import {
|
|
12
|
+
BLANK_DISPLAY,
|
|
13
|
+
DISPLAY_PLACEHOLDER,
|
|
14
|
+
labelWithUnit,
|
|
15
|
+
parseDisplayNumber,
|
|
16
|
+
type DisplayNumber,
|
|
17
|
+
} from "../utils/display-number";
|
|
18
|
+
import { isEpochMilliseconds, parseReportTime } from "../utils/utc-time";
|
|
19
|
+
import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
|
|
20
|
+
|
|
21
|
+
type CliReportValue = string | number;
|
|
22
|
+
|
|
23
|
+
/** One table of a report, as CSV and NDJSON write it. */
|
|
24
|
+
export interface CliReportTable {
|
|
25
|
+
/** The section title; CSV names it in a `# section:` line when it writes several tables. */
|
|
26
|
+
title: string;
|
|
27
|
+
/** The column names the text view shows, with the unit the cells dropped. */
|
|
28
|
+
columns: string[];
|
|
29
|
+
/** Numbers, ISO times and text; an empty string for a missing value. */
|
|
30
|
+
rows: CliReportValue[][];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CliReportTables {
|
|
34
|
+
tables: CliReportTable[];
|
|
35
|
+
/** What CSV writes after the data as `#` lines: source and as-of, completeness, errors, notes. */
|
|
36
|
+
footer: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A column as a report declares it: the text view draws `format(value, row)`, or the value itself. */
|
|
40
|
+
export interface CliReportColumn<Row = Record<string, unknown>> {
|
|
41
|
+
key: string;
|
|
42
|
+
/** The header as the text view prints it. */
|
|
43
|
+
header: string;
|
|
44
|
+
align?: "left" | "right" | "center";
|
|
45
|
+
format?: (value: unknown, row: Row) => string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A label/value line of a report. `unit` names the unit of a numeric `value` when `formatted` cannot say it. */
|
|
49
|
+
export interface CliReportEntry {
|
|
50
|
+
label: string;
|
|
51
|
+
value: unknown;
|
|
52
|
+
formatted?: string;
|
|
53
|
+
key?: string;
|
|
54
|
+
unit?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type Cell =
|
|
58
|
+
| { kind: "blank" }
|
|
59
|
+
| { kind: "text"; text: string; time?: boolean }
|
|
60
|
+
| { kind: "number"; value: number; unit: string }
|
|
61
|
+
/** Text that may read as a number, once the whole column is known. */
|
|
62
|
+
| { kind: "textual"; text: string };
|
|
63
|
+
|
|
64
|
+
type ResolvedCell = Exclude<Cell, { kind: "textual" }>;
|
|
65
|
+
|
|
66
|
+
interface ResolvedColumn {
|
|
67
|
+
header: string;
|
|
68
|
+
cells: ResolvedCell[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const BLANK = { kind: "blank" } as const;
|
|
72
|
+
const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
|
|
73
|
+
// Keys whose epoch-millisecond numbers are times rather than counts or prices.
|
|
74
|
+
const TIME_KEY = /(?:At|Time|Timestamp|Updated|AsOf|Date)$|^(?:asOf|date|time|timestamp|updated)$/;
|
|
75
|
+
// A value drawn with a change beside it, "416.2 +6.4%" or "1.20 (3%)": the value is the first figure.
|
|
76
|
+
const CHANGE_NOTE = /^\(?[+\-\u2212]?[\d.,]+%?\)?$/;
|
|
77
|
+
/**
|
|
78
|
+
* How a value relates to the number drawn for it: as is, as a percent of a
|
|
79
|
+
* fraction, or in thousands, millions or billions named by the header.
|
|
80
|
+
*/
|
|
81
|
+
const DISPLAY_SCALES = [1, 100, 0.01, 1e-3, 1e-6, 1e-9, 1e-12, 1e3, 1e6, 1e4];
|
|
82
|
+
// "3M" is as often a tenor as three million.
|
|
83
|
+
const BARE_SCALED = /^[+-]?\d+[kKMBT]$/;
|
|
84
|
+
// Beside "1Y" or "6W", "3M" is three months.
|
|
85
|
+
const TENOR = /^\d+\s*[DWQY]$/i;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Some feeds store prices as float32, so 338.93 arrives as 338.929992676. A value
|
|
89
|
+
* that is exactly a float32 reads as the shortest decimal that rounds back to it,
|
|
90
|
+
* which leaves genuine doubles (never exactly float32 unless short) untouched.
|
|
91
|
+
*/
|
|
92
|
+
export function float32Shortest(value: number): number | null {
|
|
93
|
+
if (Math.fround(value) !== value) return null;
|
|
94
|
+
for (let digits = 1; digits <= 9; digits += 1) {
|
|
95
|
+
const candidate = Number(value.toPrecision(digits));
|
|
96
|
+
if (Math.fround(candidate) === value) return candidate;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A number without binary floating-point noise (47.350000000000364, 0.0615 * 100),
|
|
103
|
+
* as the text view drops it: twelve significant digits, whole numbers and
|
|
104
|
+
* amounts of a billion or more as they are.
|
|
105
|
+
*/
|
|
106
|
+
export function exportNumber(value: number): number {
|
|
107
|
+
if (Number.isInteger(value) || Math.abs(value) >= 1e9) return Object.is(value, -0) ? 0 : value;
|
|
108
|
+
const rounded = float32Shortest(value) ?? Number(value.toPrecision(12));
|
|
109
|
+
return Object.is(rounded, -0) ? 0 : rounded;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isoInstant(time: number): string {
|
|
113
|
+
return new Date(time).toISOString().replace(".000Z", "Z");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function text(value: string): Cell {
|
|
117
|
+
return { kind: "text", text: value };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function time(value: string): Cell {
|
|
121
|
+
return { kind: "text", text: value, time: true };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A drawn number read back. Leading zeros make a code ("0700", "000001"), not a number. */
|
|
125
|
+
function readDisplay(value: string): DisplayNumber | typeof BLANK_DISPLAY | null {
|
|
126
|
+
const trimmed = value.trim();
|
|
127
|
+
if (/^[+-]?0\d/.test(trimmed)) return null;
|
|
128
|
+
return parseDisplayNumber(trimmed);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The value in the units the text drew, so a fraction drawn as 6.15% exports 6.15, or null when it is not what was drawn. */
|
|
132
|
+
function valueAsDrawn(value: number, drawn: DisplayNumber): number | null {
|
|
133
|
+
const target = Number(drawn.number);
|
|
134
|
+
const tolerance = drawn.step * (1 + 1e-9) + Number.EPSILON;
|
|
135
|
+
for (const scale of DISPLAY_SCALES) {
|
|
136
|
+
const scaled = value * scale;
|
|
137
|
+
if (Math.abs(scaled - target) <= tolerance) return exportNumber(scaled);
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function timeCell(instant: number, dateOnly: boolean, shown: string | null, raw?: string): Cell {
|
|
143
|
+
if (!Number.isFinite(instant)) return shown ? { kind: "textual", text: shown } : BLANK;
|
|
144
|
+
// A date the text drew from an instant (a monthly bar) stays the date it shows.
|
|
145
|
+
if (shown != null && DATE_ONLY.test(shown)) return time(shown);
|
|
146
|
+
if (raw != null && shown == null) return time(raw);
|
|
147
|
+
return time(dateOnly ? new Date(instant).toISOString().slice(0, 10) : isoInstant(instant));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function numberCell(value: number, shown: string | null, key: string | undefined): Cell {
|
|
151
|
+
if (!Number.isFinite(value)) return shown ? { kind: "textual", text: shown } : BLANK;
|
|
152
|
+
if (key != null && TIME_KEY.test(key) && isEpochMilliseconds(value)) return timeCell(value, false, shown);
|
|
153
|
+
if (shown == null) return { kind: "number", value: exportNumber(value), unit: "" };
|
|
154
|
+
const drawn = readDisplay(shown);
|
|
155
|
+
if (drawn === BLANK_DISPLAY) return BLANK;
|
|
156
|
+
// "3M" drawn from 3 is a tenor or a code, not three million.
|
|
157
|
+
if (drawn && BARE_SCALED.test(shown) && value === Number(shown.replace(/[kKMBT]$/, ""))) return text(shown);
|
|
158
|
+
if (drawn) return { kind: "number", value: valueAsDrawn(value, drawn) ?? Number(drawn.number), unit: drawn.unit };
|
|
159
|
+
// A time drawn as a date or a clock.
|
|
160
|
+
if (isEpochMilliseconds(value)) return timeCell(value, false, shown);
|
|
161
|
+
const [first, ...rest] = shown.split(/\s+/);
|
|
162
|
+
const lead = rest.length > 0 && CHANGE_NOTE.test(rest.join(" ")) ? readDisplay(first!) : null;
|
|
163
|
+
const scaled = lead && lead !== BLANK_DISPLAY ? valueAsDrawn(value, lead) : null;
|
|
164
|
+
if (lead && lead !== BLANK_DISPLAY && scaled != null) return { kind: "number", value: scaled, unit: lead.unit };
|
|
165
|
+
return text(shown);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Text for a nested value, never JSON: `a, b` for a list, `Key: value` pairs for a record. */
|
|
169
|
+
function flattenValue(value: unknown, depth = 0): string {
|
|
170
|
+
if (value == null) return "";
|
|
171
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : isoInstant(value.getTime());
|
|
172
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(exportNumber(value)) : "";
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
return value.map((item) => flattenValue(item, depth + 1)).filter(Boolean).join(depth === 0 ? "; " : ", ");
|
|
175
|
+
}
|
|
176
|
+
if (typeof value === "object") {
|
|
177
|
+
return Object.entries(value)
|
|
178
|
+
.map(([key, entry]) => [key, flattenValue(entry, depth + 1)] as const)
|
|
179
|
+
.filter(([, entry]) => entry !== "")
|
|
180
|
+
.map(([key, entry]) => `${key}: ${entry}`)
|
|
181
|
+
.join(", ");
|
|
182
|
+
}
|
|
183
|
+
return String(value);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** JSON text a row or a format handed over, as the flat text of what it holds; null for any other text. */
|
|
187
|
+
function flattenJsonText(value: string): string | null {
|
|
188
|
+
if (!/^\s*[{[]/.test(value)) return null;
|
|
189
|
+
try {
|
|
190
|
+
const parsed: unknown = JSON.parse(value);
|
|
191
|
+
return parsed != null && typeof parsed === "object" ? flattenValue(parsed) : null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** A cell from its value and, when the column formats it, the text it draws. */
|
|
198
|
+
function exportCell(raw: unknown, display: string | null, key?: string): Cell {
|
|
199
|
+
const shown = display?.trim() ?? null;
|
|
200
|
+
if (shown != null && (shown === "" || DISPLAY_PLACEHOLDER.test(shown))) return BLANK;
|
|
201
|
+
const json = flattenJsonText(shown ?? (typeof raw === "string" ? raw : ""));
|
|
202
|
+
if (json != null) return json ? text(json) : BLANK;
|
|
203
|
+
if (raw instanceof Date) return timeCell(raw.getTime(), false, shown);
|
|
204
|
+
if (typeof raw === "number") return numberCell(raw, shown, key);
|
|
205
|
+
if (typeof raw === "string") {
|
|
206
|
+
const value = raw.trim();
|
|
207
|
+
const reportTime = parseReportTime(value);
|
|
208
|
+
if (reportTime) return timeCell(reportTime.time, reportTime.dateOnly, shown, value);
|
|
209
|
+
const drawn = shown ?? value;
|
|
210
|
+
return drawn === "" || DISPLAY_PLACEHOLDER.test(drawn) ? BLANK : { kind: "textual", text: drawn };
|
|
211
|
+
}
|
|
212
|
+
if (typeof raw === "boolean") return text(shown ?? String(raw));
|
|
213
|
+
if (raw == null) return shown == null ? BLANK : { kind: "textual", text: shown };
|
|
214
|
+
if (shown != null) return { kind: "textual", text: shown };
|
|
215
|
+
const flat = flattenValue(raw);
|
|
216
|
+
return flat ? text(flat) : BLANK;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Text that reads as a number becomes one; text that does not, such as a
|
|
221
|
+
* range "3.75-4.00%" or "N/M", stays as drawn. A bare scaled count ("-106B")
|
|
222
|
+
* becomes a number only beside other figures, never in a column of tenors
|
|
223
|
+
* ("3M, 6M, 1Y"). Identifier columns keep their text.
|
|
224
|
+
*/
|
|
225
|
+
function resolveColumn(header: string, cells: Cell[], parseText: boolean): ResolvedColumn {
|
|
226
|
+
const textual = cells.flatMap((cell) => cell.kind === "textual" ? [cell] : []);
|
|
227
|
+
const read = parseText ? textual.map((cell) => readDisplay(cell.text)) : [];
|
|
228
|
+
const numeric = read.filter((entry): entry is DisplayNumber => entry != null && entry !== BLANK_DISPLAY);
|
|
229
|
+
const bare = textual.filter((cell, index) => read[index] != null && read[index] !== BLANK_DISPLAY && BARE_SCALED.test(cell.text.trim()));
|
|
230
|
+
const otherFigures = numeric.length > bare.length || cells.some((cell) => cell.kind === "number");
|
|
231
|
+
const convertBare = otherFigures && !textual.some((cell) => TENOR.test(cell.text.trim()));
|
|
232
|
+
let textualIndex = 0;
|
|
233
|
+
return {
|
|
234
|
+
header,
|
|
235
|
+
cells: cells.map((cell): ResolvedCell => {
|
|
236
|
+
if (cell.kind !== "textual") return cell;
|
|
237
|
+
const entry = parseText ? read[textualIndex] : null;
|
|
238
|
+
textualIndex += 1;
|
|
239
|
+
if (entry === BLANK_DISPLAY) return BLANK;
|
|
240
|
+
if (entry == null || (!convertBare && BARE_SCALED.test(cell.text.trim()))) return { kind: "text", text: cell.text };
|
|
241
|
+
return { kind: "number", value: Number(entry.number), unit: entry.unit };
|
|
242
|
+
}),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function numberUnits(cells: readonly ResolvedCell[]): Set<string> {
|
|
247
|
+
return new Set(cells.flatMap((cell) => cell.kind === "number" ? [cell.unit] : []));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function cellValue(cell: ResolvedCell): CliReportValue {
|
|
251
|
+
return cell.kind === "blank" ? "" : cell.kind === "number" ? cell.value : cell.text;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const RANGE = /^[+-]?\d[\d,]*(?:\.\d+)?\s*[-\u2013]\s*[+-]?\d[\d,]*(?:\.\d+)?$/;
|
|
255
|
+
|
|
256
|
+
/** A range drawn in the unit the header now names ("3.75-4.00%" under "Rate (%)") drops it too. */
|
|
257
|
+
function withoutUnit(cell: ResolvedCell, unit: string): CliReportValue {
|
|
258
|
+
if (!unit || cell.kind !== "text") return cellValue(cell);
|
|
259
|
+
const drawn = cell.text.trim();
|
|
260
|
+
const bare = drawn.endsWith(unit) ? drawn.slice(0, -unit.length).trim()
|
|
261
|
+
: drawn.startsWith(unit) ? drawn.slice(unit.length).trim()
|
|
262
|
+
: null;
|
|
263
|
+
return bare != null && RANGE.test(bare) ? bare.replace(/,/g, "") : cell.text;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// The first column of a table of metrics, one per row: `Metric, 2025, 2024` or `Indicator, Latest, Previous`.
|
|
267
|
+
const METRIC_LABEL = /^(?:metric|indicator|ratio|measure|statistic|line item)s?$/i;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* In a table of metrics, the columns that mix units keep one per row, and the
|
|
271
|
+
* row's label names it. A symbol or a date in the first column is not a label.
|
|
272
|
+
*/
|
|
273
|
+
function rowUnits(columns: readonly ResolvedColumn[], mixed: readonly boolean[], rowCount: number): string[] | null {
|
|
274
|
+
const label = columns[0];
|
|
275
|
+
if (!label || mixed[0] || !METRIC_LABEL.test(label.header.trim())) return null;
|
|
276
|
+
if (!label.cells.some((cell) => cell.kind === "text")) return null;
|
|
277
|
+
if (label.cells.some((cell) => cell.kind === "number" || (cell.kind === "text" && cell.time))) return null;
|
|
278
|
+
const units: string[] = [];
|
|
279
|
+
for (let row = 0; row < rowCount; row += 1) {
|
|
280
|
+
const unitsInRow = numberUnits(columns.flatMap((column, index) => mixed[index] ? [column.cells[row] ?? BLANK] : []));
|
|
281
|
+
if (unitsInRow.size > 1) return null;
|
|
282
|
+
units.push([...unitsInRow][0] ?? "");
|
|
283
|
+
}
|
|
284
|
+
return units;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Numbers are bare, so their unit goes where a spreadsheet reader looks for
|
|
289
|
+
* it: the header when the column has one unit, the row's label in a table of
|
|
290
|
+
* metrics whose rows each have one, and otherwise a `<header> unit` column
|
|
291
|
+
* beside a column that mixes them (a rate in % and a spread in bp).
|
|
292
|
+
*/
|
|
293
|
+
function assembleTable(title: string, columns: readonly ResolvedColumn[], rowCount: number): CliReportTable {
|
|
294
|
+
const units = columns.map((column) => numberUnits(column.cells));
|
|
295
|
+
const mixed = units.map((columnUnits) => columnUnits.size > 1);
|
|
296
|
+
const byRow = mixed.some(Boolean) ? rowUnits(columns, mixed, rowCount) : null;
|
|
297
|
+
const layout = columns.flatMap((column, index) => {
|
|
298
|
+
const columnUnits = units[index]!;
|
|
299
|
+
const cell = (row: number) => cellValue(column.cells[row] ?? BLANK);
|
|
300
|
+
if (index === 0 && byRow) {
|
|
301
|
+
return [{ header: column.header, value: (row: number) => byRow[row] ? labelWithUnit(String(cell(row)), byRow[row]!).trim() : cell(row) }];
|
|
302
|
+
}
|
|
303
|
+
if (!mixed[index]) {
|
|
304
|
+
const unit = [...columnUnits][0] ?? "";
|
|
305
|
+
return [{ header: labelWithUnit(column.header, unit), value: (row: number) => withoutUnit(column.cells[row] ?? BLANK, unit) }];
|
|
306
|
+
}
|
|
307
|
+
if (byRow) return [{ header: column.header, value: cell }];
|
|
308
|
+
return [
|
|
309
|
+
{ header: column.header, value: cell },
|
|
310
|
+
{
|
|
311
|
+
header: `${column.header} unit`,
|
|
312
|
+
value: (row: number) => {
|
|
313
|
+
const resolved = column.cells[row];
|
|
314
|
+
return resolved?.kind === "number" ? resolved.unit : "";
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
];
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
title,
|
|
321
|
+
columns: layout.map((column) => column.header),
|
|
322
|
+
rows: Array.from({ length: rowCount }, (_, row) => layout.map((column) => column.value(row))),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Codes a spreadsheet must not turn into numbers, even when they are digits.
|
|
327
|
+
const IDENTIFIER_KEY = /^(?:id|cik|code|cusip|figi|isin|symbol|ticker)$|(?:Id|Cik|Code|Cusip|Figi|Isin|Symbol|Ticker)$/;
|
|
328
|
+
|
|
329
|
+
/** A table of rows under the columns the text view draws. */
|
|
330
|
+
export function exportRowsTable<Row extends Record<string, unknown>>(
|
|
331
|
+
title: string,
|
|
332
|
+
columns: readonly CliReportColumn<Row>[],
|
|
333
|
+
rows: readonly Row[],
|
|
334
|
+
): CliReportTable {
|
|
335
|
+
return assembleTable(title, columns.map((column) => resolveColumn(
|
|
336
|
+
column.header,
|
|
337
|
+
rows.map((row) => {
|
|
338
|
+
const value = (row as Record<string, unknown> | undefined)?.[column.key];
|
|
339
|
+
return exportCell(value, column.format ? column.format(value, row) : null, column.key);
|
|
340
|
+
}),
|
|
341
|
+
!IDENTIFIER_KEY.test(column.key),
|
|
342
|
+
)), rows.length);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Label/value lines as `Metric,Value`; a number's unit moves into its label. */
|
|
346
|
+
export function exportEntriesTable(title: string, entries: readonly CliReportEntry[]): CliReportTable {
|
|
347
|
+
return {
|
|
348
|
+
title,
|
|
349
|
+
columns: ["Metric", "Value"],
|
|
350
|
+
rows: entries.map((entry) => {
|
|
351
|
+
if (entry.unit != null && typeof entry.value === "number" && Number.isFinite(entry.value)) {
|
|
352
|
+
return [labelWithUnit(entry.label, entry.unit), exportNumber(entry.value)];
|
|
353
|
+
}
|
|
354
|
+
const [cell] = resolveColumn(entry.label, [exportCell(entry.value, entry.formatted ?? null, entry.key)], true).cells;
|
|
355
|
+
return cell?.kind === "number"
|
|
356
|
+
? [labelWithUnit(entry.label, cell.unit), cell.value]
|
|
357
|
+
: [entry.label, cellValue(cell ?? BLANK)];
|
|
358
|
+
}),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** A table known only by the text it shows, such as a rendered pane; `instant` is the time behind a shortened one. */
|
|
363
|
+
export function exportTextTable(
|
|
364
|
+
title: string,
|
|
365
|
+
headers: readonly string[],
|
|
366
|
+
rows: ReadonlyArray<ReadonlyArray<{ text: string; instant?: string } | undefined>>,
|
|
367
|
+
): CliReportTable {
|
|
368
|
+
return assembleTable(title, headers.map((header, index) => resolveColumn(
|
|
369
|
+
header,
|
|
370
|
+
rows.map((row) => {
|
|
371
|
+
const cell = row[index];
|
|
372
|
+
if (!cell) return BLANK;
|
|
373
|
+
return cell.instant ? exportCell(cell.instant, null) : exportCell(null, cell.text);
|
|
374
|
+
}),
|
|
375
|
+
true,
|
|
376
|
+
)), rows.length);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** The closing `#` lines: the text view's source and as-of line, then whatever kept the report from being whole. */
|
|
380
|
+
export function reportFooterLines(input: {
|
|
381
|
+
freshness?: ReportFreshness;
|
|
382
|
+
/** `true`, or what is missing, such as "19 of 20 available". */
|
|
383
|
+
incomplete?: boolean | string;
|
|
384
|
+
errors?: readonly string[];
|
|
385
|
+
notes?: readonly string[];
|
|
386
|
+
}): string[] {
|
|
387
|
+
return [
|
|
388
|
+
...(input.freshness ? [formatFreshnessLine(input.freshness)] : []),
|
|
389
|
+
...(input.incomplete ? [typeof input.incomplete === "string" ? `incomplete: ${input.incomplete}` : "incomplete"] : []),
|
|
390
|
+
...(input.errors ?? []).map((error) => `error: ${error}`),
|
|
391
|
+
...(input.notes ?? []).map((note) => `note: ${note}`),
|
|
392
|
+
];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function sectionList(tables: readonly CliReportTable[]): string {
|
|
396
|
+
const titles = tables.map((table) => table.title);
|
|
397
|
+
return tables.length === 1
|
|
398
|
+
? `${titles[0]} (or 1)`
|
|
399
|
+
: `${titles.join(", ")}, or a number from 1 to ${tables.length}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* `--section`: one table, by its title (any case) or its 1-based position.
|
|
404
|
+
* Without a selector the report keeps every table.
|
|
405
|
+
*/
|
|
406
|
+
export function selectReportTables(report: CliReportTables, selector: string | true | undefined): CliReportTables {
|
|
407
|
+
if (selector === undefined) return report;
|
|
408
|
+
if (selector === true || !selector.trim()) throw new Error("--section needs a section title or number.");
|
|
409
|
+
const wanted = selector.trim();
|
|
410
|
+
if (report.tables.length === 0) throw new Error(`No section "${wanted}": this report has no tables.`);
|
|
411
|
+
const index = /^\d+$/.test(wanted)
|
|
412
|
+
? Number(wanted) - 1
|
|
413
|
+
: report.tables.findIndex((table) => table.title.trim().toLowerCase() === wanted.toLowerCase());
|
|
414
|
+
const table = report.tables[index];
|
|
415
|
+
if (!table) throw new Error(`No section "${wanted}". Sections: ${sectionList(report.tables)}.`);
|
|
416
|
+
return { ...report, tables: [table] };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function commentLine(value: string): string {
|
|
420
|
+
return `# ${value.replace(/\s*[\r\n]+\s*/g, " ").trim()}`;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** A row whose first cell starts with `#` (a rank column) quotes it, so no reader takes the row for a comment. */
|
|
424
|
+
function csvLine(row: readonly unknown[]): string {
|
|
425
|
+
const line = serializeCsv([row]);
|
|
426
|
+
if (!line.startsWith("#")) return line;
|
|
427
|
+
const first = String(row[0]);
|
|
428
|
+
return `"${first}"${line.slice(first.length)}`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* One table writes as a plain CSV; several write as blocks, each under a
|
|
433
|
+
* `# section: <title>` line, with a blank line between. The footer follows
|
|
434
|
+
* the data as `#` lines after a blank line.
|
|
435
|
+
*/
|
|
436
|
+
export function renderReportCsv(report: CliReportTables): string {
|
|
437
|
+
const marked = report.tables.length > 1;
|
|
438
|
+
const blocks = report.tables.map((table) => [
|
|
439
|
+
...(marked ? [commentLine(`section: ${table.title}`)] : []),
|
|
440
|
+
...(table.columns.length > 0 ? [[table.columns, ...table.rows].map(csvLine).join("\n")] : []),
|
|
441
|
+
].join("\n"));
|
|
442
|
+
const footer = report.footer.map(commentLine).join("\n");
|
|
443
|
+
return [blocks.join("\n\n"), footer].filter(Boolean).join("\n\n");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** One JSON object per row, keyed by column; rows of a report with several tables name their section. */
|
|
447
|
+
export function renderReportNdjson(report: CliReportTables): string {
|
|
448
|
+
const tagged = report.tables.length > 1;
|
|
449
|
+
return report.tables.flatMap((table) => {
|
|
450
|
+
const sectionKey = table.columns.some((column) => column.toLowerCase() === "section") ? "_section" : "section";
|
|
451
|
+
return table.rows.map((row) => {
|
|
452
|
+
const object: Record<string, CliReportValue | null> = tagged ? { [sectionKey]: table.title } : {};
|
|
453
|
+
table.columns.forEach((column, index) => {
|
|
454
|
+
const value = row[index];
|
|
455
|
+
object[column] = value === undefined || value === "" ? null : value;
|
|
456
|
+
});
|
|
457
|
+
return JSON.stringify(object);
|
|
458
|
+
});
|
|
459
|
+
}).join("\n");
|
|
460
|
+
}
|
package/src/cli/result.ts
CHANGED
|
@@ -7,12 +7,16 @@ import {
|
|
|
7
7
|
statValueWidth,
|
|
8
8
|
visibleLength,
|
|
9
9
|
wrapText,
|
|
10
|
+
wrapToTerminal,
|
|
10
11
|
type CliStatEntry,
|
|
11
12
|
type CliTableColumn,
|
|
12
13
|
} from "../utils/cli-output";
|
|
13
14
|
import { serializeCsv } from "../utils/csv";
|
|
14
15
|
import { formatUtcTime, isEpochMilliseconds, isZonedIsoDateTime } from "../utils/utc-time";
|
|
15
16
|
import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
|
|
17
|
+
import { withSearchHints } from "./not-a-ticker";
|
|
18
|
+
import { renderReportCsv, renderReportNdjson, type CliReportTables } from "./report-tables";
|
|
19
|
+
import { windowRows, type RowWindow, type RowWindowOptions } from "./row-window";
|
|
16
20
|
|
|
17
21
|
export interface CliResult<T = unknown> {
|
|
18
22
|
data: T;
|
|
@@ -21,7 +25,8 @@ export interface CliResult<T = unknown> {
|
|
|
21
25
|
/**
|
|
22
26
|
* Source, as-of and status of market data, as every `fn` report states them
|
|
23
27
|
* (docs/usage.md#how-current-a-report-is): the last line of the text output
|
|
24
|
-
* and `metadata.freshness` in JSON. CSV and NDJSON stay rows only
|
|
28
|
+
* and `metadata.freshness` in JSON. CSV and NDJSON stay rows only, unless
|
|
29
|
+
* the command passes `tables`, whose footer carries it.
|
|
25
30
|
*/
|
|
26
31
|
freshness?: ReportFreshness;
|
|
27
32
|
}
|
|
@@ -57,6 +62,22 @@ export interface CliResultRenderOptions<T = unknown, Row = Record<string, unknow
|
|
|
57
62
|
summary?: (data: T) => string;
|
|
58
63
|
/** Text-mode line printed first, naming what the result is about, such as the listing a symbol resolved to. */
|
|
59
64
|
heading?: string;
|
|
65
|
+
/** Text-mode line printed under the rows, such as where a listing stops. Not printed with no rows: say it in `empty`. */
|
|
66
|
+
footnote?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The row key holding each row's date, which makes the rows a dated series:
|
|
69
|
+
* `--tail` keeps the newest rows whichever way the series runs, and a
|
|
70
|
+
* `--limit` that keeps the oldest says so, under the text table and as
|
|
71
|
+
* `metadata.rows` in JSON.
|
|
72
|
+
*/
|
|
73
|
+
dateKey?: string;
|
|
74
|
+
/** How many rows to show when neither `--limit` nor `--tail` is given; every row when absent. */
|
|
75
|
+
defaultLimit?: number;
|
|
76
|
+
/**
|
|
77
|
+
* What CSV and NDJSON write instead of the rows: a report's tables as the
|
|
78
|
+
* text view shows them, CSV with its `# section:` and closing `#` lines.
|
|
79
|
+
*/
|
|
80
|
+
tables?: CliReportTables;
|
|
60
81
|
}
|
|
61
82
|
|
|
62
83
|
interface CliResultJsonEnvelope<T> extends Omit<CliResult<T>, "freshness"> {
|
|
@@ -82,14 +103,28 @@ const KEY_ACRONYMS: Record<string, string> = {
|
|
|
82
103
|
usd: "USD",
|
|
83
104
|
};
|
|
84
105
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
function rowWindowOptions(options: CliGlobalOptions, renderOptions: Pick<CliResultRenderOptions, "defaultLimit">): RowWindowOptions {
|
|
107
|
+
return options.tail != null ? { tail: options.tail } : { limit: options.limit ?? renderOptions.defaultLimit };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function windowed<Row>(
|
|
111
|
+
rows: readonly Row[],
|
|
112
|
+
options: CliGlobalOptions,
|
|
113
|
+
renderOptions: Pick<CliResultRenderOptions, "dateKey" | "defaultLimit">,
|
|
114
|
+
): RowWindow<Row> {
|
|
115
|
+
const { dateKey } = renderOptions;
|
|
116
|
+
const dateOf = dateKey ? (row: Row) => (isPlainObject(row) ? row[dateKey] : undefined) : undefined;
|
|
117
|
+
return windowRows(rows, rowWindowOptions(options, renderOptions), dateOf);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function asRows<T, Row>(data: T, rows?: (data: T) => Row[]): Row[] {
|
|
121
|
+
return rows ? rows(data) : (Array.isArray(data) ? data : [data]) as Row[];
|
|
88
122
|
}
|
|
89
123
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
124
|
+
/** What JSON says about rows the window cut, under `metadata.rows`. */
|
|
125
|
+
function cutMetadata(window: RowWindow<unknown>) {
|
|
126
|
+
if (window.rows.length >= window.total) return null;
|
|
127
|
+
return { shown: window.rows.length, total: window.total, kept: window.kept, ...(window.note ? { note: window.note } : {}) };
|
|
93
128
|
}
|
|
94
129
|
|
|
95
130
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
@@ -270,6 +305,7 @@ function renderTextTable<Row extends Record<string, unknown>>(
|
|
|
270
305
|
width: column.width,
|
|
271
306
|
maxWidth: column.maxWidth,
|
|
272
307
|
optional: column.optional,
|
|
308
|
+
dropPriority: column.dropPriority,
|
|
273
309
|
shrink: column.shrink,
|
|
274
310
|
})),
|
|
275
311
|
cells.map((row) => shown.map(({ index }) => row[index]!)),
|
|
@@ -303,7 +339,7 @@ function overflowsTerminal<Row extends Record<string, unknown>>(rows: Row[]): bo
|
|
|
303
339
|
|
|
304
340
|
/** The closing source, as-of and status line of a text result, for output printed without `printResult`. */
|
|
305
341
|
export function cliFreshnessFooter(freshness: ReportFreshness): string {
|
|
306
|
-
return cliStyles.muted(formatFreshnessLine(freshness));
|
|
342
|
+
return cliStyles.muted(wrapToTerminal(formatFreshnessLine(freshness)));
|
|
307
343
|
}
|
|
308
344
|
|
|
309
345
|
export function serializeCliResult<T, Row extends Record<string, unknown> = Record<string, unknown>>(
|
|
@@ -314,11 +350,13 @@ export function serializeCliResult<T, Row extends Record<string, unknown> = Reco
|
|
|
314
350
|
const { freshness, ...rest } = result;
|
|
315
351
|
if (options.format === "json") {
|
|
316
352
|
const columns = serializeColumns(renderOptions.columns);
|
|
353
|
+
const window = Array.isArray(result.data) ? windowed(result.data as unknown[], options, renderOptions) : null;
|
|
354
|
+
const cut = window ? cutMetadata(window) : null;
|
|
317
355
|
const envelope: CliResultJsonEnvelope<T> = {
|
|
318
356
|
ok: true,
|
|
319
357
|
...rest,
|
|
320
|
-
...(freshness ? { metadata: { ...rest.metadata, freshness } } : {}),
|
|
321
|
-
data:
|
|
358
|
+
...(freshness || cut ? { metadata: { ...rest.metadata, ...(freshness ? { freshness } : {}), ...(cut ? { rows: cut } : {}) } } : {}),
|
|
359
|
+
data: window ? window.rows as T : result.data,
|
|
322
360
|
...(columns?.length ? { columns } : {}),
|
|
323
361
|
};
|
|
324
362
|
return JSON.stringify(envelope, null, 2);
|
|
@@ -333,7 +371,10 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
|
|
|
333
371
|
options: CliGlobalOptions,
|
|
334
372
|
renderOptions: CliResultRenderOptions<T, Row>,
|
|
335
373
|
): string {
|
|
336
|
-
|
|
374
|
+
if (renderOptions.tables && options.format === "csv") return renderReportCsv(renderOptions.tables);
|
|
375
|
+
if (renderOptions.tables && options.format === "ndjson") return renderReportNdjson(renderOptions.tables);
|
|
376
|
+
const window = windowed(asRows(result.data, renderOptions.rows), options, renderOptions);
|
|
377
|
+
const rows = window.rows;
|
|
337
378
|
if (options.format === "ndjson") {
|
|
338
379
|
return rows.map((row) => JSON.stringify(row)).join("\n");
|
|
339
380
|
}
|
|
@@ -348,7 +389,9 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
|
|
|
348
389
|
if (rows.length === 0) {
|
|
349
390
|
return withHeading(summary || cliStyles.muted(renderOptions.empty ?? "No results."));
|
|
350
391
|
}
|
|
351
|
-
const
|
|
392
|
+
const table = renderTextRows(rows as Row[], result.data, renderOptions);
|
|
393
|
+
const notes = [window.note, renderOptions.footnote].filter((note): note is string => !!note);
|
|
394
|
+
const body = [table, ...notes.map((note) => cliStyles.muted(note))].join("\n");
|
|
352
395
|
return withHeading(summary ? `${summary}\n\n${body}` : body);
|
|
353
396
|
}
|
|
354
397
|
|
|
@@ -373,16 +416,20 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
|
|
|
373
416
|
result: CliResult<T>,
|
|
374
417
|
options: CliGlobalOptions,
|
|
375
418
|
renderOptions: CliResultRenderOptions<T, Row> = {},
|
|
419
|
+
args: readonly string[] = [],
|
|
376
420
|
): void {
|
|
377
421
|
if (options.quiet && options.format === "text") return;
|
|
378
|
-
|
|
422
|
+
// JSON keeps the router's own words; everything else is for a person, so a "Not a ticker" gets its hint.
|
|
423
|
+
const hinted = (text: string) => options.format === "json" ? text : withSearchHints(text, args);
|
|
424
|
+
const output = hinted(serializeCliResult(result, options, renderOptions));
|
|
379
425
|
// Bun's console writer can truncate a large pipe write after stdout has been
|
|
380
426
|
// initialized. The stream queues the remaining bytes until the reader drains.
|
|
381
427
|
if (output) process.stdout.write(`${output}\n`);
|
|
382
|
-
//
|
|
383
|
-
|
|
428
|
+
// JSON carries warnings in its envelope. Text, CSV and NDJSON are only the
|
|
429
|
+
// rows, so a warning goes to stderr, out of a file the rows are piped into.
|
|
430
|
+
if (options.format !== "json" && !options.quiet) {
|
|
384
431
|
for (const warning of result.warnings ?? []) {
|
|
385
|
-
console.error(`${cliStyles.warning("warning:")} ${warning}`);
|
|
432
|
+
console.error(`${cliStyles.warning("warning:")} ${hinted(warning)}`);
|
|
386
433
|
}
|
|
387
434
|
}
|
|
388
435
|
}
|