gloomberb 0.16.6 → 0.17.0
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 +2 -1
- package/src/api-client/chat-pane-link.ts +47 -0
- package/src/api-client/chat.ts +9 -1
- package/src/api-client/data.ts +37 -1
- package/src/api-client/equity-screener.ts +3 -0
- package/src/api-client/europe-energy.ts +91 -0
- package/src/api-client/index.ts +6 -0
- package/src/api-client/letters.ts +41 -0
- package/src/api-client/normalizers.ts +36 -6
- package/src/api-client/organization-tickers.ts +44 -0
- package/src/api-client/request.ts +3 -1
- package/src/api-client/types.ts +27 -1
- package/src/app/runtime/desktop-deeplink.ts +48 -15
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/dom-cell-metrics-allowlist.json +28 -0
- package/src/ask-gloom/pane-context.ts +23 -19
- package/src/ask-gloom/pane-request.ts +2 -2
- package/src/cli/commands/fred-series.ts +77 -11
- package/src/cli/commands/market.ts +77 -20
- package/src/cli/commands/overview.ts +29 -20
- package/src/cli/commands/ticker.ts +64 -11
- package/src/cli/desktop-pane-shot-view.ts +114 -0
- package/src/cli/desktop-pane-shot.ts +30 -31
- package/src/cli/entry.ts +7 -0
- package/src/cli/export-source.ts +43 -0
- package/src/cli/freshness.ts +18 -3
- package/src/cli/glossary.ts +17 -0
- package/src/cli/history-rows.ts +174 -13
- package/src/cli/known-gaps.ts +14 -3
- package/src/cli/listing-arg.ts +20 -2
- package/src/cli/listing-suggestion.ts +159 -0
- package/src/cli/native-smoke.ts +13 -0
- package/src/cli/not-a-ticker.ts +13 -1
- package/src/cli/pane-functions/capabilities.ts +16 -8
- package/src/cli/pane-functions/dom.ts +28 -13
- package/src/cli/pane-functions/function-help.ts +51 -6
- package/src/cli/pane-functions/headless.ts +26 -7
- package/src/cli/pane-functions/index.ts +46 -3
- package/src/cli/pane-functions/report-notices.ts +6 -3
- package/src/cli/pane-functions/report.ts +16 -2
- package/src/cli/pane-functions/resolver.ts +7 -4
- package/src/cli/report-tables.ts +31 -7
- package/src/cli/result.ts +54 -11
- package/src/cli/symbol-rows.ts +18 -1
- package/src/components/chart/composite/axis-overlays.tsx +2 -0
- package/src/components/chart/composite/composite-chart.tsx +4 -21
- package/src/components/chart/composite/legend.tsx +10 -6
- package/src/components/chart/composite/price-axis-labels.tsx +1 -0
- package/src/components/command-bar/list/model.ts +2 -0
- package/src/components/command-bar/pane-templates/items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +3 -1
- package/src/components/command-bar/workflow/ops.ts +8 -97
- package/src/components/command-bar/workflow/pane-reuse.ts +177 -0
- package/src/components/data-table/view.tsx +1 -0
- package/src/components/feed-data-table/stack-view.tsx +18 -42
- package/src/components/layout/missing-pane.tsx +2 -0
- package/src/components/layout/shell/index.tsx +1 -0
- package/src/components/layout/shell/pane/ask.ts +12 -7
- package/src/components/layout/shell/pane/capture.ts +8 -34
- package/src/components/markdown-text.tsx +37 -6
- package/src/components/reader/article-reader.tsx +91 -266
- package/src/components/reader/detail-reader.tsx +210 -0
- package/src/components/reader/reader-text.tsx +239 -0
- package/src/components/table-view-shared.tsx +8 -4
- package/src/components/ticker/badge/list.tsx +15 -4
- package/src/components/ui/choice-dialog.tsx +3 -0
- package/src/components/ui/data-table/types.ts +7 -0
- package/src/components/ui/detail-scroll-body.tsx +31 -4
- package/src/components/ui/display.tsx +46 -1
- package/src/components/ui/icon.tsx +11 -1
- package/src/components/ui/list-view.tsx +3 -1
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +9 -1
- package/src/components/ui/select-button.tsx +3 -0
- package/src/data/config/store/fold-panes.ts +202 -11
- package/src/data/config/store/migrations.ts +13 -1
- package/src/docs/snapshot.json +29 -0
- package/src/i18n/es.ts +14 -0
- package/src/i18n/ja.ts +14 -0
- package/src/i18n/ko.ts +14 -0
- package/src/i18n/zh-cn.ts +14 -0
- package/src/i18n/zh-tw.ts +14 -0
- package/src/layout/desk-keys.ts +7 -0
- package/src/layout/desks.ts +2 -2
- package/src/market-data/market/status.ts +67 -6
- package/src/market-data/quotes/contributions.ts +15 -0
- package/src/market-data/quotes/regular-session-valuation.ts +131 -0
- package/src/market-data/quotes/resolution.ts +4 -0
- package/src/market-data/snapshot-provider.ts +6 -3
- package/src/notifications/app-badge.ts +30 -0
- package/src/plugins/builtin/account-management/calendar-tab.tsx +1 -1
- package/src/plugins/builtin/account-management/form-components.tsx +53 -1
- package/src/plugins/builtin/account-management/keyboard.ts +5 -0
- package/src/plugins/builtin/account-management/model.ts +27 -0
- package/src/plugins/builtin/account-management/pane.tsx +62 -2
- package/src/plugins/builtin/alerts/cli.ts +6 -4
- package/src/plugins/builtin/analytics/index.tsx +1 -1
- package/src/plugins/builtin/awards/pane.tsx +29 -18
- package/src/plugins/builtin/backtest/pane.tsx +2 -1
- package/src/plugins/builtin/bond-calculator/index.tsx +3 -5
- package/src/plugins/builtin/buildout/detail/company.tsx +7 -4
- package/src/plugins/builtin/builtin-plugin-meta.ts +5 -4
- package/src/plugins/builtin/catalysts/model.ts +92 -10
- package/src/plugins/builtin/catalysts/pane.tsx +6 -4
- package/src/plugins/builtin/cds/index.tsx +21 -30
- package/src/plugins/builtin/changelog/index.tsx +11 -12
- package/src/plugins/builtin/chart-composer/headless-schema.ts +2 -0
- package/src/plugins/builtin/chart-composer/headless.ts +87 -5
- package/src/plugins/builtin/chart-composer/pane.tsx +3 -2
- package/src/plugins/builtin/chart-composer/session-history.ts +20 -5
- package/src/plugins/builtin/chat/command-bar.ts +6 -8
- package/src/plugins/builtin/chat/content/composer-runtime.ts +2 -2
- package/src/plugins/builtin/chat/content/composer.tsx +13 -4
- package/src/plugins/builtin/chat/content/footer.tsx +15 -5
- package/src/plugins/builtin/chat/content/handle-prompt.tsx +326 -0
- package/src/plugins/builtin/chat/content/index.tsx +113 -9
- package/src/plugins/builtin/chat/content/layout-metrics.ts +4 -0
- package/src/plugins/builtin/chat/content/open-targets.ts +20 -4
- package/src/plugins/builtin/chat/content/scroll.ts +6 -1
- package/src/plugins/builtin/chat/content/transcript.tsx +11 -2
- package/src/plugins/builtin/chat/ghost-user.ts +1 -1
- package/src/plugins/builtin/chat/handle-prompt.ts +128 -0
- package/src/plugins/builtin/chat/index.tsx +44 -21
- package/src/plugins/builtin/chat/layout.ts +7 -14
- package/src/plugins/builtin/chat/message/desktop.tsx +59 -11
- package/src/plugins/builtin/chat/message/header.tsx +69 -21
- package/src/plugins/builtin/chat/message/pane-chip.tsx +223 -0
- package/src/plugins/builtin/chat/message/person-icons.tsx +53 -0
- package/src/plugins/builtin/chat/message/profile-popover.tsx +73 -9
- package/src/plugins/builtin/chat/message/render-state.ts +15 -2
- package/src/plugins/builtin/chat/message/terminal.tsx +33 -8
- package/src/plugins/builtin/chat/message/types.ts +3 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +149 -0
- package/src/plugins/builtin/chat/pane-links/open.ts +29 -0
- package/src/plugins/builtin/chat/pane-links/previews.ts +119 -0
- package/src/plugins/builtin/chat/pane-state.ts +5 -8
- package/src/plugins/builtin/chat/people.ts +202 -0
- package/src/plugins/builtin/chat/profile-popover.ts +18 -2
- package/src/plugins/builtin/chat/status-widget.tsx +3 -8
- package/src/plugins/builtin/chat/unread-badge.ts +65 -0
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +9 -6
- package/src/plugins/builtin/clinical-trials/client.ts +26 -1
- package/src/plugins/builtin/clinical-trials/index.tsx +10 -2
- package/src/plugins/builtin/clinical-trials/model.ts +269 -0
- package/src/plugins/builtin/clinical-trials/pane.tsx +247 -62
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +156 -0
- package/src/plugins/builtin/cloud/askg/attached-pane.tsx +3 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +3 -3
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -0
- package/src/plugins/builtin/comment-letters/pane.tsx +30 -11
- package/src/plugins/builtin/commodities-energy/index.tsx +179 -0
- package/src/plugins/builtin/commodity-balances/client.ts +208 -0
- package/src/plugins/builtin/commodity-balances/headless.ts +66 -0
- package/src/plugins/builtin/commodity-balances/index.tsx +32 -0
- package/src/plugins/builtin/commodity-balances/model.ts +261 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +227 -0
- package/src/plugins/builtin/composite-plugins.ts +26 -1
- package/src/plugins/builtin/credit-boards/index.tsx +21 -42
- package/src/plugins/builtin/credit-boards/model.ts +0 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +10 -19
- package/src/plugins/builtin/credit-documents/index.tsx +27 -10
- package/src/plugins/builtin/debt-maturities/index.tsx +3 -17
- package/src/plugins/builtin/debug/index.tsx +2 -2
- package/src/plugins/builtin/dividend-yield/client.ts +3 -1
- package/src/plugins/builtin/dividend-yield/headless.ts +3 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/docs/blocks.tsx +4 -3
- package/src/plugins/builtin/docs/pane.tsx +16 -8
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +2 -12
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +36 -2
- package/src/plugins/builtin/equity-screener/model.ts +121 -68
- package/src/plugins/builtin/executives/pane.tsx +3 -1
- package/src/plugins/builtin/filing-events/index.tsx +20 -0
- package/src/plugins/builtin/fixed-income/index.tsx +46 -0
- package/src/plugins/builtin/fixed-income/model.ts +27 -0
- package/src/plugins/builtin/fixed-income/pane.tsx +162 -0
- package/src/plugins/builtin/fixed-income/shortcut.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +24 -7
- package/src/plugins/builtin/futures/series.ts +23 -2
- package/src/plugins/builtin/futures/use-front-returns.ts +3 -2
- package/src/plugins/builtin/futures-curve/pane.tsx +6 -4
- package/src/plugins/builtin/help/shortcut-table.tsx +4 -2
- package/src/plugins/builtin/insider/detail.tsx +20 -12
- package/src/plugins/builtin/insider/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/index.tsx +3 -0
- package/src/plugins/builtin/iv-history/screen-pane.tsx +13 -5
- package/src/plugins/builtin/jodi/client.ts +220 -0
- package/src/plugins/builtin/jodi/headless.ts +63 -0
- package/src/plugins/builtin/jodi/index.tsx +32 -0
- package/src/plugins/builtin/jodi/model.ts +75 -0
- package/src/plugins/builtin/jodi/pane.tsx +141 -0
- package/src/plugins/builtin/market-heatmap/evidence.ts +13 -4
- package/src/plugins/builtin/market-heatmap/headless.ts +3 -3
- package/src/plugins/builtin/market-heatmap/index.tsx +41 -23
- package/src/plugins/builtin/market-heatmap/live.ts +2 -1
- package/src/plugins/builtin/market-heatmap/portfolio.ts +67 -21
- package/src/plugins/builtin/market-movers/table.ts +2 -2
- package/src/plugins/builtin/market-valuation/align.ts +10 -9
- package/src/plugins/builtin/mna/distress/model.ts +52 -1
- package/src/plugins/builtin/mna/distress/pane.tsx +17 -12
- package/src/plugins/builtin/mna/index.tsx +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +2 -2
- package/src/plugins/builtin/news/wire/industry-pane.tsx +28 -14
- package/src/plugins/builtin/news/wire/letters/client.ts +108 -0
- package/src/plugins/builtin/news/wire/letters/model.ts +69 -0
- package/src/plugins/builtin/news/wire/letters/pane.tsx +155 -0
- package/src/plugins/builtin/news/wire/mutes.ts +2 -1
- package/src/plugins/builtin/news/wire/news/query-presets.ts +4 -1
- package/src/plugins/builtin/openfda/client.ts +123 -15
- package/src/plugins/builtin/openfda/model.ts +163 -0
- package/src/plugins/builtin/openfda/pane.tsx +109 -65
- package/src/plugins/builtin/openfda/types.ts +25 -0
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/pe-band/model.ts +3 -2
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-module.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +19 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +2 -2
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +39 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +18 -5
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +25 -13
- package/src/plugins/builtin/power/evidence.ts +32 -3
- package/src/plugins/builtin/power/gas-client.ts +110 -0
- package/src/plugins/builtin/power/gas-model.ts +194 -0
- package/src/plugins/builtin/power/gas.tsx +145 -0
- package/src/plugins/builtin/power/headless.ts +4 -1
- package/src/plugins/builtin/power/index.tsx +19 -9
- package/src/plugins/builtin/power/pane.tsx +2 -3
- package/src/plugins/builtin/power/prices-headless.ts +75 -7
- package/src/plugins/builtin/power/prices.tsx +36 -13
- package/src/plugins/builtin/primary-dealers/client.ts +108 -0
- package/src/plugins/builtin/primary-dealers/headless.ts +53 -0
- package/src/plugins/builtin/primary-dealers/index.tsx +17 -0
- package/src/plugins/builtin/primary-dealers/model.ts +206 -0
- package/src/plugins/builtin/primary-dealers/pane.tsx +149 -0
- package/src/plugins/builtin/relative-rotation/index.tsx +3 -0
- package/src/plugins/builtin/research/analyst-model.ts +9 -5
- package/src/plugins/builtin/research/headless-schema.ts +2 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +68 -32
- package/src/plugins/builtin/research/relative-valuation-model.ts +45 -24
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +39 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +8 -1
- package/src/plugins/builtin/sec/filing-display.tsx +3 -36
- package/src/plugins/builtin/sec/index.tsx +131 -43
- package/src/plugins/builtin/sec/model.ts +148 -2
- package/src/plugins/builtin/sec/row-summaries.ts +105 -0
- package/src/plugins/builtin/sectors/index.tsx +2 -0
- package/src/plugins/builtin/sectors/sector-model.ts +2 -2
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +179 -43
- package/src/plugins/builtin/shared/wall-teaser.tsx +2 -2
- package/src/plugins/builtin/shared/zip-text.ts +46 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +11 -2
- package/src/plugins/builtin/themes/model.ts +2 -2
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +11 -0
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +81 -19
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +2 -2
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +7 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -6
- package/src/plugins/builtin/time-sales/headless.ts +22 -0
- package/src/plugins/builtin/trace-bonds/client.ts +196 -0
- package/src/plugins/builtin/trace-bonds/headless.ts +69 -0
- package/src/plugins/builtin/trace-bonds/index.tsx +33 -0
- package/src/plugins/builtin/trace-bonds/model.ts +115 -0
- package/src/plugins/builtin/trace-bonds/pane.tsx +191 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +33 -47
- package/src/plugins/builtin/treasury-daily/client.ts +96 -0
- package/src/plugins/builtin/treasury-daily/headless.ts +69 -0
- package/src/plugins/builtin/treasury-daily/index.tsx +17 -0
- package/src/plugins/builtin/treasury-daily/model.ts +69 -0
- package/src/plugins/builtin/treasury-daily/pane.tsx +140 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +15 -2
- package/src/plugins/builtin/world-venue-map/map.tsx +1 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/catalog-browser.ts +25 -2
- package/src/plugins/marketplace/model.ts +57 -21
- package/src/renderers/dom/build-assets.ts +14 -5
- package/src/renderers/dom/data-table/index.tsx +5 -3
- package/src/renderers/dom/desktop/controls.tsx +2 -1
- package/src/renderers/dom/desktop/icons.tsx +25 -0
- package/src/renderers/dom/desktop/list-view.tsx +1 -1
- package/src/renderers/dom/desktop/menu.tsx +1 -1
- package/src/renderers/dom/desktop/query-bar.tsx +3 -1
- package/src/renderers/dom/host/chart-surface.tsx +7 -2
- package/src/renderers/dom/host/resize-rows.ts +111 -0
- package/src/renderers/dom/host/sticky-scroll.ts +22 -3
- package/src/renderers/dom/host/style.ts +14 -0
- package/src/renderers/dom/host/surface-3d.tsx +2 -1
- package/src/renderers/dom/host/surface-fit.ts +10 -6
- package/src/renderers/dom/host/text.tsx +2 -2
- package/src/renderers/dom/styles.css +63 -6
- package/src/renderers/electrobun/bun/desktop/app-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-badge.ts +37 -0
- package/src/renderers/electrobun/bun/desktop/macos-dock-icon.ts +2 -40
- package/src/renderers/electrobun/bun/desktop/macos-objc.ts +41 -0
- package/src/renderers/electrobun/bun/tui-entry.ts +18 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/app-badge-host.ts +17 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +8 -3
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +5 -1
- package/src/series/validate.ts +1 -1
- package/src/shares/api.ts +5 -1
- package/src/shares/location.ts +31 -0
- package/src/shares/open.ts +26 -0
- package/src/shares/pane-image.ts +18 -6
- package/src/shares/personal-pane.ts +42 -54
- package/src/sources/gloomberb-cloud/index.ts +7 -2
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -0
- package/src/sources/history-coverage.ts +22 -4
- package/src/sources/history-result.ts +3 -1
- package/src/sources/provider-errors.ts +2 -1
- package/src/sources/provider-router/batches.ts +18 -2
- package/src/sources/provider-router/index.ts +2 -1
- package/src/state/app/context/index.tsx +3 -2
- package/src/test-support/builtin-panes.ts +31 -0
- package/src/tickers/listing-venues.ts +22 -0
- package/src/tickers/search/index.ts +7 -2
- package/src/tickers/search/ranking.ts +19 -0
- package/src/time-series/chart-data.ts +2 -0
- package/src/time-series/parsed-history-cache.ts +41 -11
- package/src/time-series/resolve.ts +47 -15
- package/src/types/config.ts +1 -1
- package/src/types/data-provider.ts +13 -1
- package/src/types/financials.ts +13 -0
- package/src/types/headless.ts +22 -0
- package/src/types/plugin.ts +33 -6
- package/src/types/price-history.ts +2 -0
- package/src/ui/host.tsx +6 -0
- package/src/utils/crypto-pair.ts +1 -1
- package/src/utils/currency-pair.ts +66 -0
- package/src/utils/decimal-comma.ts +57 -0
- package/src/utils/guards.ts +25 -0
- package/src/utils/local-utc-offset.ts +37 -0
- package/src/utils/plugin-proxy-hosts.json +8 -1
- package/src/components/chart/composite/crosshair-readout.tsx +0 -150
- package/src/plugins/builtin/chat/message/ai-badge.tsx +0 -18
- package/src/plugins/builtin/credit-boards/pane.tsx +0 -30
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DesktopPaneShotRenderedRow } from "../desktop-pane-shot";
|
|
2
|
-
import { cliStyles, renderSection, renderTable } from "../../utils/cli-output";
|
|
2
|
+
import { cliStyles, cliTerminalWidth, renderSection, renderTable, wrapText } from "../../utils/cli-output";
|
|
3
3
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
4
4
|
import type { MarketContext } from "../types";
|
|
5
5
|
import type { PaneFunctionReport } from "./report";
|
|
@@ -14,6 +14,8 @@ const DOM_REPORT_WIDTH = 1280;
|
|
|
14
14
|
const DOM_REPORT_HEIGHT = 720;
|
|
15
15
|
const DOM_LIMITATION =
|
|
16
16
|
"Only values visible in the rendered pane are returned; clipped or off-screen data may be omitted.";
|
|
17
|
+
/** The widest a lock note wraps to, as the catalog's text. */
|
|
18
|
+
const LOCK_NOTE_WIDTH = 100;
|
|
17
19
|
const DOM_EVERY_ROW =
|
|
18
20
|
"Every row of the pane's table is returned, including rows scrolled out of view.";
|
|
19
21
|
|
|
@@ -119,7 +121,7 @@ function renderDomTables(rows: DesktopPaneShotRenderedRow[]): string[] {
|
|
|
119
121
|
function domReportTables(
|
|
120
122
|
rows: DesktopPaneShotRenderedRow[],
|
|
121
123
|
title: string,
|
|
122
|
-
freshness: ReportFreshness,
|
|
124
|
+
freshness: ReportFreshness | null,
|
|
123
125
|
incomplete: boolean | string,
|
|
124
126
|
notices: readonly string[],
|
|
125
127
|
): CliReportTables {
|
|
@@ -130,7 +132,7 @@ function domReportTables(
|
|
|
130
132
|
table.columns.map((column) => column.header),
|
|
131
133
|
table.rows.map((cells) => table.columns.map((column) => cells.get(column.key))),
|
|
132
134
|
)),
|
|
133
|
-
footer: reportFooterLines({ freshness, incomplete, notes: notices }),
|
|
135
|
+
footer: reportFooterLines({ ...(freshness ? { freshness } : {}), incomplete, notes: notices }),
|
|
134
136
|
};
|
|
135
137
|
}
|
|
136
138
|
|
|
@@ -190,10 +192,19 @@ export function buildDomPaneReportFromRender(
|
|
|
190
192
|
const rendered = renderedReportNotices(result.render.semanticUi ?? []);
|
|
191
193
|
const notices = [...(resolved.pane.reportNotices?.(resolved.instance.settings ?? {}) ?? []), ...rendered.notices];
|
|
192
194
|
const textLines = [resolved.label, "", ...notices, ...(notices.length > 0 ? [""] : [])];
|
|
195
|
+
// A wall in place of the whole pane: nothing was read, so the report says
|
|
196
|
+
// what is locked and how to reach it, and cites no source.
|
|
197
|
+
const gate = !hasStructuredRows ? result.render.accessGate ?? null : null;
|
|
198
|
+
const lockNote = gate ? resolved.pane.reportLockNote?.({ gate, symbol: result.symbols[0] ?? null }) ?? null : null;
|
|
199
|
+
const locked = gate === "sign-in" || lockNote ? gate : null;
|
|
200
|
+
// JSON and CSV say why the report is empty in the note's words, on one line.
|
|
201
|
+
const lockReason = lockNote?.replace(/\s*\n\s*/g, " ") ?? null;
|
|
193
202
|
if (hasStructuredRows) {
|
|
194
203
|
textLines.push(...renderDomTables(rows));
|
|
204
|
+
} else if (lockNote) {
|
|
205
|
+
textLines.push(...wrapText(lockNote, Math.min(cliTerminalWidth() ?? LOCK_NOTE_WIDTH, LOCK_NOTE_WIDTH)));
|
|
195
206
|
} else if (result.render.visibleText) {
|
|
196
|
-
textLines.push(
|
|
207
|
+
textLines.push(gate === "sign-in" ? signInWallText(result.render.visibleText) : result.render.visibleText);
|
|
197
208
|
} else {
|
|
198
209
|
textLines.push(failureReason ?? "No rendered values were available.");
|
|
199
210
|
}
|
|
@@ -226,45 +237,49 @@ export function buildDomPaneReportFromRender(
|
|
|
226
237
|
truncationReasons,
|
|
227
238
|
...(everyRow ? { everyRow } : {}),
|
|
228
239
|
limitation: everyRow ? DOM_EVERY_ROW : DOM_LIMITATION,
|
|
229
|
-
...(failureReason ? { reason: failureReason } : {}),
|
|
240
|
+
...(lockReason ?? failureReason ? { reason: lockReason ?? failureReason } : {}),
|
|
230
241
|
...(notices.length > 0 ? { metadata: { ...rendered.facts, notices } } : {}),
|
|
231
|
-
freshness,
|
|
242
|
+
...(locked ? { locked } : { freshness }),
|
|
232
243
|
},
|
|
233
244
|
text: textLines.join("\n").trimEnd(),
|
|
234
245
|
tables: domReportTables(
|
|
235
246
|
rows,
|
|
236
247
|
resolved.label,
|
|
237
|
-
freshness,
|
|
238
|
-
!complete && (failureReason ?? (truncationReasons.length > 0 ? truncationReasons.join("; ") : true)),
|
|
248
|
+
locked ? null : freshness,
|
|
249
|
+
!complete && (lockReason ?? failureReason ?? (truncationReasons.length > 0 ? truncationReasons.join("; ") : true)),
|
|
239
250
|
notices,
|
|
240
251
|
),
|
|
241
252
|
};
|
|
242
253
|
}
|
|
243
254
|
|
|
255
|
+
/** The report's closing lines; a locked view, which read nothing, cites no source. */
|
|
244
256
|
export function appendDomReportFooter(
|
|
245
257
|
text: string,
|
|
246
258
|
elapsedMs: number,
|
|
247
259
|
view: DomReportView,
|
|
248
|
-
freshness: ReportFreshness,
|
|
260
|
+
freshness: ReportFreshness | undefined,
|
|
249
261
|
): string {
|
|
250
262
|
return [
|
|
251
263
|
text,
|
|
252
264
|
"",
|
|
253
265
|
domReportNote(elapsedMs, view),
|
|
254
|
-
cliStyles.muted(formatFreshnessLine(freshness)),
|
|
266
|
+
...(freshness ? [cliStyles.muted(formatFreshnessLine(freshness))] : []),
|
|
255
267
|
].join("\n");
|
|
256
268
|
}
|
|
257
269
|
|
|
258
|
-
/** How much of the pane a rendered-view report holds: cut, every row its tables publish, or what the viewport shows. */
|
|
259
|
-
export type DomReportView = "clipped" | "every-row" | "visible";
|
|
270
|
+
/** How much of the pane a rendered-view report holds: none behind a wall, cut, every row its tables publish, or what the viewport shows. */
|
|
271
|
+
export type DomReportView = "locked" | "clipped" | "every-row" | "visible";
|
|
260
272
|
|
|
261
273
|
export function domReportView(data: Readonly<Record<string, unknown>>): DomReportView {
|
|
274
|
+
if (data.locked) return "locked";
|
|
262
275
|
return data.truncated === true ? "clipped" : data.everyRow === true ? "every-row" : "visible";
|
|
263
276
|
}
|
|
264
277
|
|
|
265
278
|
/** What a rendered-view report says about where its values come from. */
|
|
266
279
|
export function domReportNote(elapsedMs: number, view: DomReportView): string {
|
|
267
|
-
const source = view === "
|
|
280
|
+
const source = view === "locked"
|
|
281
|
+
? "Rendered view: the pane is locked, so no values were read."
|
|
282
|
+
: view === "every-row"
|
|
268
283
|
? "Rendered view: every row of the pane's table, including rows scrolled out of view."
|
|
269
284
|
: `Rendered view: values come from the visible pane. ${view === "clipped" ? "The rendered view is clipped." : "The rendered view may be clipped."}`;
|
|
270
285
|
return `${source} Render time: ${elapsedMs} ms.`;
|
|
@@ -225,7 +225,8 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
225
225
|
bloomberg: ["RV"],
|
|
226
226
|
},
|
|
227
227
|
EQS: {
|
|
228
|
-
summary: "Screen covered stocks on valuation, growth, margins, liquidity, short interest, insider activity and 13F holders, then rank the matches by any metric."
|
|
228
|
+
summary: "Screen covered stocks on valuation, growth, margins, liquidity, volatility, short interest, insider activity and 13F holders, then rank the matches by any metric. "
|
|
229
|
+
+ "ATR% is the 14-session average true range as a percent of the last close. IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest.",
|
|
229
230
|
usage: ["EQS"],
|
|
230
231
|
keys: [key("a", "dd criterion"), key("m", "etric"), key("s", "ave screen"), key("o", "pen research")],
|
|
231
232
|
data: same("Daily snapshot, statements as filed"),
|
|
@@ -717,7 +718,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
717
718
|
bloomberg: ["RRG"],
|
|
718
719
|
},
|
|
719
720
|
HM: {
|
|
720
|
-
summary: "The largest US stocks and ETFs as a treemap sized by the
|
|
721
|
+
summary: "The largest US stocks and ETFs as a treemap sized by the day's move, or by market cap or its square root (assets for ETFs) in its settings, and colored by the day's move.",
|
|
721
722
|
usage: ["HM"],
|
|
722
723
|
keys: [OPEN],
|
|
723
724
|
data: QUOTES,
|
|
@@ -780,6 +781,22 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
780
781
|
data: same("Weekly, on release"),
|
|
781
782
|
bloomberg: ["DOE"],
|
|
782
783
|
},
|
|
784
|
+
PSD: {
|
|
785
|
+
summary: "World production, use, trade and ending stocks for wheat, corn, soybeans and cotton, by country and latest market year. Season-average price forecasts appear when USDA publishes them. The Crops tab of Commodities & Energy.",
|
|
786
|
+
usage: ["PSD"],
|
|
787
|
+
keys: [TABS],
|
|
788
|
+
data: same("Monthly, as USDA updates the market-year tables"),
|
|
789
|
+
bloomberg: [],
|
|
790
|
+
docs: "https://gloom.sh/docs/functions",
|
|
791
|
+
},
|
|
792
|
+
JODI: {
|
|
793
|
+
summary: "Latest monthly crude oil and natural gas balances for countries: production, imports, exports and stocks, and demand for natural gas. The Oil & Gas tab of Commodities & Energy.",
|
|
794
|
+
usage: ["JODI"],
|
|
795
|
+
keys: [TABS],
|
|
796
|
+
data: same("Monthly, as the latest country reports arrive"),
|
|
797
|
+
bloomberg: [],
|
|
798
|
+
docs: "https://gloom.sh/docs/functions",
|
|
799
|
+
},
|
|
783
800
|
POWER: {
|
|
784
801
|
summary: "Pro: global power interconnection queues, recorded MW history, completion and withdrawal cohorts, large-load requests and approvals, utility exposure and generation capacity. Open a project for primary evidence and revisions; mapped companies link to DES, FA, G and SPLC.",
|
|
785
802
|
usage: ["POWER", "POWER NEE"],
|
|
@@ -797,10 +814,10 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
797
814
|
bloomberg: [],
|
|
798
815
|
},
|
|
799
816
|
EPWR: {
|
|
800
|
-
summary: "Day-ahead power prices for 32 European markets in EUR/MWh,
|
|
801
|
-
usage: ["EPWR", "EPWR DE", "EPWR UK", "EPWR EUA"],
|
|
802
|
-
keys: [key("o", "pen Ember"), key("g", "raph"), key("c", "arbon")],
|
|
803
|
-
data: same("Daily; each market as of its latest published delivery day, usually a few days back; the EUA on each auction day"),
|
|
817
|
+
summary: "Day-ahead power prices for 32 European markets in EUR/MWh, the EU carbon allowance (EUA) auction price in EUR/tCO2, and European gas storage. Each market shows its latest delivery day, the change on the day before, 7- and 30-day averages, a year ago and where it sits in its 52-week range, with its year charted and its history from 2015; the EUA shows its latest auction day, how the auction was covered, and its auctions since 2020. EPWR GAS shows the EU and each member state's storage as of its latest gas day: % full, TWh, injection and withdrawal in GWh/day, against a year ago and the five-year range, with the year charted against last year and that range. The Prices tab of POWER.",
|
|
818
|
+
usage: ["EPWR", "EPWR DE", "EPWR UK", "EPWR EUA", "EPWR GAS", "EPWR GAS DE"],
|
|
819
|
+
keys: [key("o", "pen Ember or GIE AGSI+"), key("g", "raph"), key("c", "arbon")],
|
|
820
|
+
data: same("Daily; each market as of its latest published delivery day, usually a few days back; the EUA on each auction day; gas storage by gas day, published each evening, the latest day estimated until confirmed"),
|
|
804
821
|
bloomberg: [],
|
|
805
822
|
docs: "POWER",
|
|
806
823
|
},
|
|
@@ -927,6 +944,34 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
927
944
|
data: same("Daily, after each auction"),
|
|
928
945
|
bloomberg: ["AUCT"],
|
|
929
946
|
},
|
|
947
|
+
FI: {
|
|
948
|
+
summary: "A single workspace for credit markets, issuer debt, corporate bonds, Treasury auctions and debt, and primary dealer positions.",
|
|
949
|
+
usage: ["FI"],
|
|
950
|
+
keys: [TABS],
|
|
951
|
+
data: same("As each market publishes"),
|
|
952
|
+
bloomberg: [],
|
|
953
|
+
},
|
|
954
|
+
TRACE: {
|
|
955
|
+
summary: "Recent corporate and agency bond sales with issuer, coupon, price, yield, and grade. Add an issuer to narrow the tape.",
|
|
956
|
+
usage: ["TRACE", "TRACE Ford"],
|
|
957
|
+
keys: [SEARCH],
|
|
958
|
+
data: same("As trades are published"),
|
|
959
|
+
bloomberg: [],
|
|
960
|
+
},
|
|
961
|
+
DTS: {
|
|
962
|
+
summary: "US Treasury daily operating cash and debt held by the public, with recent business days for debt.",
|
|
963
|
+
usage: ["DTS"],
|
|
964
|
+
keys: [TABS],
|
|
965
|
+
data: same("Daily, as Treasury publishes"),
|
|
966
|
+
bloomberg: [],
|
|
967
|
+
},
|
|
968
|
+
PDLR: {
|
|
969
|
+
summary: "Weekly primary dealer net positions and settlement fails across Treasuries and mortgage-backed securities.",
|
|
970
|
+
usage: ["PDLR"],
|
|
971
|
+
keys: [TABS],
|
|
972
|
+
data: same("Weekly, as published"),
|
|
973
|
+
bloomberg: [],
|
|
974
|
+
},
|
|
930
975
|
BTMM: {
|
|
931
976
|
summary: "Funding rates (SOFR, EFFR, IORB), Treasury bill yields and their curve, and Fed liquidity from reserves to the reverse repo, each with its one-year percentile.",
|
|
932
977
|
usage: ["BTMM"],
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
type CliReportTable,
|
|
32
32
|
type CliReportTables,
|
|
33
33
|
} from "../report-tables";
|
|
34
|
-
import {
|
|
34
|
+
import type { ExportIdentity } from "../export-source";
|
|
35
35
|
import { observationDate } from "../../time-series/price-comparison";
|
|
36
36
|
import type { ResolvedSeries } from "../../time-series/types";
|
|
37
37
|
import type { PaneFunctionReport } from "./report";
|
|
@@ -268,6 +268,7 @@ export function serializeHeadlessPaneResult(
|
|
|
268
268
|
const common = {
|
|
269
269
|
...(result.errors ? { errors: result.errors } : {}),
|
|
270
270
|
...(result.notes?.length ? { notes: result.notes } : {}),
|
|
271
|
+
...(result.warnings?.length ? { warnings: result.warnings } : {}),
|
|
271
272
|
...(result.metadata ? { metadata: result.metadata } : {}),
|
|
272
273
|
};
|
|
273
274
|
switch (definition.shape) {
|
|
@@ -478,17 +479,22 @@ function displayedColumns(columns: HeadlessPaneColumn[]): HeadlessPaneColumn[] {
|
|
|
478
479
|
|
|
479
480
|
const SERIES_FIELDS = ["value", "open", "high", "low", "close", "volume"] as const;
|
|
480
481
|
|
|
481
|
-
/**
|
|
482
|
+
/**
|
|
483
|
+
* Every point of a series: its date (the trading date for session bars) and
|
|
484
|
+
* its values, in the series' unit, which the export's `currency_or_unit`
|
|
485
|
+
* column names beside the bare `Value`, `Open`... headers.
|
|
486
|
+
*/
|
|
482
487
|
function seriesTable(series: HeadlessSeries): CliReportTable {
|
|
483
488
|
const fields = SERIES_FIELDS.filter((field) => series.points.some((point) => point[field] != null));
|
|
484
|
-
const unit = series.unit?.trim() ?? "";
|
|
485
489
|
const dates = series.points.map((point) => seriesPointExportDate(series, point.date));
|
|
486
|
-
const header = (field: (typeof SERIES_FIELDS)[number]) => {
|
|
487
|
-
const name = `${field[0]!.toUpperCase()}${field.slice(1)}`;
|
|
488
|
-
return field === "volume" ? name : labelWithUnit(name, unit);
|
|
489
|
-
};
|
|
490
|
+
const header = (field: (typeof SERIES_FIELDS)[number]) => `${field[0]!.toUpperCase()}${field.slice(1)}`;
|
|
490
491
|
return {
|
|
491
492
|
title: series.label,
|
|
493
|
+
identity: {
|
|
494
|
+
...(series.symbol?.trim() ? { symbolOrSeries: series.symbol.trim() } : {}),
|
|
495
|
+
...(series.unit?.trim() ? { currencyOrUnit: series.unit.trim() } : {}),
|
|
496
|
+
...(series.interval?.trim() ? { interval: series.interval.trim() } : {}),
|
|
497
|
+
},
|
|
492
498
|
columns: [dates.every((date) => date.length <= 10) ? "Date" : "Time", ...fields.map(header)],
|
|
493
499
|
rows: series.points.map((point, index) => [
|
|
494
500
|
dates[index]!,
|
|
@@ -576,6 +582,7 @@ export function headlessReportTables(
|
|
|
576
582
|
const { locked, failed } = splitErrors(result);
|
|
577
583
|
return {
|
|
578
584
|
tables,
|
|
585
|
+
identity: reportIdentity(definition, result, args),
|
|
579
586
|
footer: reportFooterLines({
|
|
580
587
|
freshness,
|
|
581
588
|
incomplete: incompleteDetail(result, status),
|
|
@@ -586,6 +593,18 @@ export function headlessReportTables(
|
|
|
586
593
|
};
|
|
587
594
|
}
|
|
588
595
|
|
|
596
|
+
/**
|
|
597
|
+
* What a report's exported rows are of when it is about one symbol or series:
|
|
598
|
+
* the one a chart's series were asked as, else the one symbol asked for or
|
|
599
|
+
* resolved. A series table names its own symbol, unit and interval.
|
|
600
|
+
*/
|
|
601
|
+
function reportIdentity(definition: HeadlessPaneDefinition, result: HeadlessPaneResult, args: HeadlessPaneLoadArgs): ExportIdentity {
|
|
602
|
+
const series = definition.shape === "series" ? (result as HeadlessSeriesResult).series : [];
|
|
603
|
+
const charted = [...new Set(series.flatMap((entry) => entry.symbol?.trim() ? [entry.symbol.trim()] : []))];
|
|
604
|
+
const symbols = charted.length > 0 ? charted : args.symbols.length > 0 ? args.symbols : result.symbols ?? [];
|
|
605
|
+
return symbols.length === 1 ? { symbolOrSeries: symbols[0]! } : {};
|
|
606
|
+
}
|
|
607
|
+
|
|
589
608
|
function reportTitle(
|
|
590
609
|
definition: HeadlessPaneDefinition,
|
|
591
610
|
args: HeadlessPaneLoadArgs,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type PaneFunctionCatalog,
|
|
11
11
|
} from "./catalog";
|
|
12
12
|
import { createPaneCatalog } from "./discovery";
|
|
13
|
-
import { findCatalogSeries } from "../series-search";
|
|
13
|
+
import { catalogFredSeriesIds, findCatalogSeries } from "../series-search";
|
|
14
14
|
import {
|
|
15
15
|
parsePaneCatalogArgs,
|
|
16
16
|
parsePaneFunctionArgs,
|
|
@@ -106,12 +106,17 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
106
106
|
`${resolved.token} did not produce a complete bot-safe report.${unavailable}`,
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
|
+
// A chart or other series exported to CSV or NDJSON with no rows fails, rather than writing a header alone.
|
|
110
|
+
if (tabular && report.data.kind === "series" && report.data.empty) {
|
|
111
|
+
const empty = emptySeriesExport(resolved.token, parsed.arg, report.data, catalogFredSeriesIds(ctx.plugins), `--${ctx.cliOptions.format}`);
|
|
112
|
+
ctx.fail(empty.message, empty.details);
|
|
113
|
+
}
|
|
109
114
|
// --explain follows the report with the terms it shows; JSON lists them under `glossary`.
|
|
110
115
|
const glossary = parsed.explain ? reportGlossary(resolved.token, report.text) : null;
|
|
111
116
|
const data = glossary
|
|
112
117
|
? { ...report.data, glossary: glossary.map(({ term, short, definition }) => ({ term, ...(short ? { short } : {}), definition })) }
|
|
113
118
|
: report.data;
|
|
114
|
-
const warnings = [...parsed.listingNotes ?? [], ...await choice ?? []];
|
|
119
|
+
const warnings = [...parsed.listingNotes ?? [], ...await choice ?? [], ...report.data.warnings ?? []];
|
|
115
120
|
// The report states its own freshness in its data and text; JSON cites it as `source` too.
|
|
116
121
|
ctx.printResult({ data, exportFreshness: report.data.freshness, ...(warnings.length > 0 ? { warnings } : {}) }, {
|
|
117
122
|
text: () => glossary ? `${report.text}\n\n${renderReportGlossary(resolved.token, glossary)}` : report.text,
|
|
@@ -121,6 +126,41 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
121
126
|
});
|
|
122
127
|
}
|
|
123
128
|
|
|
129
|
+
/**
|
|
130
|
+
* What a series export that came back with no rows says: what was asked, why
|
|
131
|
+
* when the report knows, and what to run instead. A FRED series ID given where
|
|
132
|
+
* a ticker goes (`fn G DEXSFUS`) is charted with the `fred:` prefix.
|
|
133
|
+
*/
|
|
134
|
+
export function emptySeriesExport(
|
|
135
|
+
token: string,
|
|
136
|
+
arg: string,
|
|
137
|
+
data: { unavailableSymbols: readonly string[]; errors?: readonly string[] },
|
|
138
|
+
fredSeriesIds: ReadonlySet<string>,
|
|
139
|
+
formatFlag = "--csv",
|
|
140
|
+
): { message: string; details: string } {
|
|
141
|
+
const asked = ["fn", token, arg.trim()].filter(Boolean).join(" ");
|
|
142
|
+
const fred = data.unavailableSymbols
|
|
143
|
+
.map((symbol) => symbol.split(":")[0]!.trim().toUpperCase())
|
|
144
|
+
.filter((id) => /^[A-Z0-9]+$/.test(id) && fredSeriesIds.has(id));
|
|
145
|
+
if (fred.length > 0) {
|
|
146
|
+
const ids = [...new Set(fred)];
|
|
147
|
+
return {
|
|
148
|
+
message: `${asked} has no data to export: ${ids.join(" and ")} ${ids.length === 1 ? "is a FRED series" : "are FRED series"}, not ${ids.length === 1 ? "a ticker" : "tickers"}.`,
|
|
149
|
+
details: `Chart it with the fred: prefix: gloomberb fn G ${ids.map((id) => `fred:${id}`).join(",")} ${formatFlag}`
|
|
150
|
+
+ `${ids.length === 1 ? ` (or gloomberb fred ${ids[0]} ${formatFlag})` : ""}.`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const missing = data.unavailableSymbols.length > 0 ? ` for ${data.unavailableSymbols.join(", ")}` : "";
|
|
154
|
+
const reasons = (data.errors ?? []).map((error) => error.replace(/^[a-z0-9-]+-\d+: /, ""));
|
|
155
|
+
return {
|
|
156
|
+
message: `${asked} has no data to export${missing}.`,
|
|
157
|
+
details: [
|
|
158
|
+
...reasons,
|
|
159
|
+
`Check the symbol with gloomberb search, or run gloomberb ${asked} without ${formatFlag} to see the report.`,
|
|
160
|
+
].join("\n"),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
124
164
|
/** How long a capture's process may stay up after its output, for a report it stopped waiting for. */
|
|
125
165
|
const STALLED_REPORT_EXIT_MS = 1_000;
|
|
126
166
|
|
|
@@ -263,7 +303,10 @@ async function runPaneCliCommand(ctx: CliCommandContext, run: () => Promise<void
|
|
|
263
303
|
// A listing it cannot use says what to type instead; a name that opens nothing, what is close to it.
|
|
264
304
|
if (error instanceof ListingArgError || error instanceof UnknownPaneFunctionError) ctx.fail(error.message, error.details);
|
|
265
305
|
const message = error instanceof Error ? error.message : String(error);
|
|
266
|
-
|
|
306
|
+
// A failure the command already worded keeps its advice line.
|
|
307
|
+
const stated = error instanceof Error ? (error as Error & { details?: unknown }).details : undefined;
|
|
308
|
+
const details = typeof stated === "string" ? stated : undefined;
|
|
309
|
+
ctx.fail(message, details);
|
|
267
310
|
}
|
|
268
311
|
}
|
|
269
312
|
|
|
@@ -41,7 +41,9 @@ function publishedSources(value: unknown): NonNullable<HeadlessPaneFreshness["so
|
|
|
41
41
|
/**
|
|
42
42
|
* How current a rendered pane says its data is, from the `report-freshness`
|
|
43
43
|
* node it publishes: `{ asOf?, status?, delayMinutes?, source?, sources?,
|
|
44
|
-
* maxAgeMinutes?, oldestAge?, note? }
|
|
44
|
+
* maxAgeMinutes?, oldestAge?, note?, basis? }`; `basis` names what a pane
|
|
45
|
+
* that is not a feed lists, when one pane lists different things (NI's
|
|
46
|
+
* stories, and its LTR code's letters). A capture keeps no footer status and
|
|
45
47
|
* few cells carry a time, so a pane that knows its feed (OMON's chain) dates
|
|
46
48
|
* the report here, as the plain command over the same data does, and a pane
|
|
47
49
|
* over several venues (PERP) dates each one as its report does. A pane whose
|
|
@@ -50,10 +52,10 @@ function publishedSources(value: unknown): NonNullable<HeadlessPaneFreshness["so
|
|
|
50
52
|
*/
|
|
51
53
|
export function renderedReportFreshness(
|
|
52
54
|
semanticUi: readonly RemoteUiNodeSnapshot[],
|
|
53
|
-
): Pick<HeadlessPaneFreshness, "asOf" | "status" | "delayMinutes" | "source" | "sources" | "maxAgeMinutes" | "oldestAge" | "note"> {
|
|
55
|
+
): Pick<HeadlessPaneFreshness, "asOf" | "status" | "delayMinutes" | "source" | "sources" | "maxAgeMinutes" | "oldestAge" | "note" | "basis"> {
|
|
54
56
|
const metadata = semanticUi.find((node) => node.role === "report-freshness")?.metadata;
|
|
55
57
|
if (!metadata) return {};
|
|
56
|
-
const { asOf, status, delayMinutes, source, maxAgeMinutes, oldestAge, note } = metadata as Record<string, unknown>;
|
|
58
|
+
const { asOf, status, delayMinutes, source, maxAgeMinutes, oldestAge, note, basis } = metadata as Record<string, unknown>;
|
|
57
59
|
const sources = publishedSources(metadata.sources);
|
|
58
60
|
return {
|
|
59
61
|
...(typeof asOf === "string" || isFiniteNumber(asOf) ? { asOf } : {}),
|
|
@@ -64,6 +66,7 @@ export function renderedReportFreshness(
|
|
|
64
66
|
...(isFiniteNumber(maxAgeMinutes) && maxAgeMinutes > 0 ? { maxAgeMinutes } : {}),
|
|
65
67
|
...(oldestAge === true ? { oldestAge } : {}),
|
|
66
68
|
...(typeof note === "string" && note.trim() ? { note } : {}),
|
|
69
|
+
...(typeof basis === "string" && basis.trim() ? { basis } : {}),
|
|
67
70
|
};
|
|
68
71
|
}
|
|
69
72
|
|
|
@@ -22,8 +22,18 @@ interface PaneFunctionReportData {
|
|
|
22
22
|
empty: boolean;
|
|
23
23
|
complete: boolean;
|
|
24
24
|
unavailableSymbols: string[];
|
|
25
|
-
/**
|
|
26
|
-
|
|
25
|
+
/** Report errors: a source that did not answer, a symbol that is not one. */
|
|
26
|
+
errors?: string[];
|
|
27
|
+
/** What the report could not do as asked and did instead; `fn` prints them on stderr and in JSON's `warnings`. */
|
|
28
|
+
warnings?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Source, as-of in UTC and live/delayed/stale/not-a-feed status; the last
|
|
31
|
+
* line of the text report. Absent when a wall covered the pane (`locked`),
|
|
32
|
+
* since nothing was read.
|
|
33
|
+
*/
|
|
34
|
+
freshness?: ReportFreshness;
|
|
35
|
+
/** The wall that covered the whole pane, so the report read nothing. */
|
|
36
|
+
locked?: "sign-in" | "pro";
|
|
27
37
|
[key: string]: unknown;
|
|
28
38
|
}
|
|
29
39
|
|
|
@@ -72,6 +82,10 @@ export async function buildFunctionReport(
|
|
|
72
82
|
: await buildDomFunctionReport(resolved, context, rawArg);
|
|
73
83
|
const elapsedMs = Math.max(0, Math.round(performance.now() - startedAt));
|
|
74
84
|
report.data = { ...report.data, source, elapsedMs };
|
|
85
|
+
// A report about one symbol names it on every exported row; a chart's series name their own.
|
|
86
|
+
if (!report.tables.identity && report.data.symbols.length === 1) {
|
|
87
|
+
report.tables = { ...report.tables, identity: { symbolOrSeries: report.data.symbols[0]! } };
|
|
88
|
+
}
|
|
75
89
|
if (source === "dom") {
|
|
76
90
|
const view = domReportView(report.data);
|
|
77
91
|
report.text = appendDomReportFooter(report.text, elapsedMs, view, report.data.freshness);
|
|
@@ -143,14 +143,16 @@ export async function applyListingArgument(
|
|
|
143
143
|
const abroad = usOnly ? listings.filter((listing) => listingAbroadVenue(listing.key) != null) : [];
|
|
144
144
|
if (usOnly && abroad.length > 0) {
|
|
145
145
|
const token = found.template?.shortcut?.prefix ?? args.target.toUpperCase();
|
|
146
|
-
const answers = await Promise.all(abroad.map((listing) =>
|
|
147
|
-
usOnly, listing, context, (key) => `gloomberb ${command} ${token} ${key}
|
|
148
|
-
|
|
146
|
+
const answers = await Promise.all(abroad.map(async (listing) => {
|
|
147
|
+
const answer = await usListingsOnlyMessage(usOnly, listing, context, (key) => `gloomberb ${command} ${token} ${key}`);
|
|
148
|
+
const hint = headless?.usListingsOnlyHint?.(listing.key);
|
|
149
|
+
return hint ? { ...answer, details: `${answer.details}\n${hint}` } : answer;
|
|
150
|
+
}));
|
|
149
151
|
listings = listings.filter((listing) => !abroad.includes(listing));
|
|
150
152
|
if (listings.length === 0) {
|
|
151
153
|
throw new ListingArgError(answers.map((answer) => answer.message).join("\n"), answers.map((answer) => answer.details).join("\n"));
|
|
152
154
|
}
|
|
153
|
-
notes.push(...answers.map((answer) => `${answer.message} ${answer.details}`));
|
|
155
|
+
notes.push(...answers.map((answer) => `${answer.message} ${answer.details.replaceAll("\n", " ")}`));
|
|
154
156
|
}
|
|
155
157
|
// One listing's notes come with its choice note; a list's are said here.
|
|
156
158
|
if (listings.length > 1) notes.unshift(...listings.flatMap(listingReadNotes));
|
|
@@ -233,6 +235,7 @@ export async function resolvePaneFunction(
|
|
|
233
235
|
const normalizedOptions = normalizeCapabilityOptions(capability, args.options, {
|
|
234
236
|
strict: args.requireBotSafe || (!!headless && resolutionSettings.strictHeadlessOptions === true),
|
|
235
237
|
code: token,
|
|
238
|
+
argument: args.arg || null,
|
|
236
239
|
});
|
|
237
240
|
const settings = {
|
|
238
241
|
...optionSettings(args.options),
|
package/src/cli/report-tables.ts
CHANGED
|
@@ -17,7 +17,12 @@ import {
|
|
|
17
17
|
} from "../utils/display-number";
|
|
18
18
|
import { isEpochMilliseconds, parseReportTime } from "../utils/utc-time";
|
|
19
19
|
import type { ReportFreshness } from "./pane-functions/freshness";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
EXPORT_IDENTITY_COLUMNS,
|
|
22
|
+
exportIdentityCells,
|
|
23
|
+
exportSourceLines,
|
|
24
|
+
type ExportIdentity,
|
|
25
|
+
} from "./export-source";
|
|
21
26
|
|
|
22
27
|
type CliReportValue = string | number;
|
|
23
28
|
|
|
@@ -29,12 +34,16 @@ export interface CliReportTable {
|
|
|
29
34
|
columns: string[];
|
|
30
35
|
/** Numbers, ISO times and text; an empty string for a missing value. */
|
|
31
36
|
rows: CliReportValue[][];
|
|
37
|
+
/** What this table's rows are of, where it differs from the report's: one series of a chart. */
|
|
38
|
+
identity?: ExportIdentity;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface CliReportTables {
|
|
35
42
|
tables: CliReportTable[];
|
|
36
43
|
/** What CSV writes after the data as `#` lines: source and as-of, completeness, errors, notes. */
|
|
37
44
|
footer: string[];
|
|
45
|
+
/** What the rows are of, for the fixed columns every row ends with (`EXPORT_IDENTITY_COLUMNS`). */
|
|
46
|
+
identity?: ExportIdentity;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
/** A column as a report declares it: the text view draws `format(value, row)`, or the value itself. */
|
|
@@ -443,14 +452,25 @@ function csvLine(row: readonly unknown[]): string {
|
|
|
443
452
|
return `"${first}"${line.slice(first.length)}`;
|
|
444
453
|
}
|
|
445
454
|
|
|
455
|
+
/**
|
|
456
|
+
* A table with the fixed columns after its own, every row naming what it is
|
|
457
|
+
* and the source and as-of the footer cites. A table without columns stays empty.
|
|
458
|
+
*/
|
|
459
|
+
function identifiedTable(table: CliReportTable, report: CliReportTables, freshness: ReportFreshness | undefined): CliReportTable {
|
|
460
|
+
if (!freshness || table.columns.length === 0) return table;
|
|
461
|
+
const cells = exportIdentityCells(freshness, { ...report.identity, ...table.identity });
|
|
462
|
+
return { ...table, columns: [...table.columns, ...EXPORT_IDENTITY_COLUMNS], rows: table.rows.map((row) => [...row, ...cells]) };
|
|
463
|
+
}
|
|
464
|
+
|
|
446
465
|
/**
|
|
447
466
|
* One table writes as a plain CSV; several write as blocks, each under a
|
|
448
467
|
* `# section: <title>` line, with a blank line between. The footer follows
|
|
449
|
-
* the data as `#` lines after a blank line.
|
|
468
|
+
* the data as `#` lines after a blank line. With the `freshness` the footer
|
|
469
|
+
* cites, every row ends with the fixed columns of `EXPORT_IDENTITY_COLUMNS`.
|
|
450
470
|
*/
|
|
451
|
-
export function renderReportCsv(report: CliReportTables): string {
|
|
471
|
+
export function renderReportCsv(report: CliReportTables, freshness?: ReportFreshness): string {
|
|
452
472
|
const marked = report.tables.length > 1;
|
|
453
|
-
const blocks = report.tables.map((table) => [
|
|
473
|
+
const blocks = report.tables.map((table) => identifiedTable(table, report, freshness)).map((table) => [
|
|
454
474
|
...(marked ? [commentLine(`section: ${table.title}`)] : []),
|
|
455
475
|
...(table.columns.length > 0 ? [[table.columns, ...table.rows].map(csvLine).join("\n")] : []),
|
|
456
476
|
].join("\n"));
|
|
@@ -462,10 +482,14 @@ export function withCsvFooter(csv: string, footer: readonly string[]): string {
|
|
|
462
482
|
return [csv, footer.map(commentLine).join("\n")].filter(Boolean).join("\n\n");
|
|
463
483
|
}
|
|
464
484
|
|
|
465
|
-
/**
|
|
466
|
-
|
|
485
|
+
/**
|
|
486
|
+
* One JSON object per row, keyed by column; rows of a report with several
|
|
487
|
+
* tables name their section. With the report's `freshness`, every row ends
|
|
488
|
+
* with the fixed fields CSV ends it with.
|
|
489
|
+
*/
|
|
490
|
+
export function renderReportNdjson(report: CliReportTables, freshness?: ReportFreshness): string {
|
|
467
491
|
const tagged = report.tables.length > 1;
|
|
468
|
-
return report.tables.flatMap((table) => {
|
|
492
|
+
return report.tables.map((table) => identifiedTable(table, report, freshness)).flatMap((table) => {
|
|
469
493
|
const sectionKey = table.columns.some((column) => column.toLowerCase() === "section") ? "_section" : "section";
|
|
470
494
|
return table.rows.map((row) => {
|
|
471
495
|
const object: Record<string, CliReportValue | null> = tagged ? { [sectionKey]: table.title } : {};
|
package/src/cli/result.ts
CHANGED
|
@@ -16,7 +16,15 @@ import { formatUtcTime, isEpochMilliseconds, isZonedIsoDateTime } from "../utils
|
|
|
16
16
|
import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
|
|
17
17
|
import { withSearchHints, type FredSeriesCheck } from "./not-a-ticker";
|
|
18
18
|
import { renderReportCsv, renderReportNdjson, withCsvFooter, type CliReportTables } from "./report-tables";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
EXPORT_IDENTITY_COLUMNS,
|
|
21
|
+
exportIdentityCells,
|
|
22
|
+
exportIdentityFields,
|
|
23
|
+
exportSource,
|
|
24
|
+
exportSourceLines,
|
|
25
|
+
type ExportIdentity,
|
|
26
|
+
type ExportSource,
|
|
27
|
+
} from "./export-source";
|
|
20
28
|
import { windowRows, type RowWindow, type RowWindowOptions } from "./row-window";
|
|
21
29
|
|
|
22
30
|
export interface CliResult<T = unknown> {
|
|
@@ -28,7 +36,8 @@ export interface CliResult<T = unknown> {
|
|
|
28
36
|
* (docs/usage.md#how-current-a-report-is): the last line of the text output
|
|
29
37
|
* and `metadata.freshness` in JSON. Exports cite it too (`exportSource`):
|
|
30
38
|
* CSV ends with it as `#` lines, or `tables` footer does, and JSON carries
|
|
31
|
-
* it as `source`. NDJSON stays rows only.
|
|
39
|
+
* it as `source`. NDJSON stays rows only. CSV and NDJSON rows end with the
|
|
40
|
+
* fixed columns of `EXPORT_IDENTITY_COLUMNS`, its source and as-of among them.
|
|
32
41
|
*/
|
|
33
42
|
freshness?: ReportFreshness;
|
|
34
43
|
/**
|
|
@@ -88,6 +97,15 @@ export interface CliResultRenderOptions<T = unknown, Row = Record<string, unknow
|
|
|
88
97
|
* text view shows them, CSV with its `# section:` and closing `#` lines.
|
|
89
98
|
*/
|
|
90
99
|
tables?: CliReportTables;
|
|
100
|
+
/**
|
|
101
|
+
* What the rows are of, for the fixed columns (`EXPORT_IDENTITY_COLUMNS`)
|
|
102
|
+
* CSV and NDJSON end every row with when the result cites a source: one
|
|
103
|
+
* identity for every row, or each row's own. Without it a row's own
|
|
104
|
+
* `symbol` and `currency` fields say it, as far as they go.
|
|
105
|
+
*/
|
|
106
|
+
exportIdentity?: ExportIdentity | ((row: Row) => ExportIdentity);
|
|
107
|
+
/** What CSV says after the source lines as `# note:` lines: what the rows do not show, such as where a series begins. */
|
|
108
|
+
exportNotes?: readonly string[];
|
|
91
109
|
}
|
|
92
110
|
|
|
93
111
|
interface CliResultJsonEnvelope<T> extends Omit<CliResult<T>, "freshness" | "exportFreshness"> {
|
|
@@ -267,18 +285,34 @@ function serializeColumns<Row>(columns?: CliResultColumn<Row>[]) {
|
|
|
267
285
|
function renderCsv<Row extends Record<string, unknown>>(
|
|
268
286
|
rows: Row[],
|
|
269
287
|
columns?: CliResultColumn<Row>[],
|
|
288
|
+
identity?: (row: Row) => string[],
|
|
270
289
|
): string {
|
|
271
290
|
const resolvedColumns = columns && columns.length > 0
|
|
272
291
|
? columns
|
|
273
292
|
: inferColumns(rows as Record<string, unknown>[]) as CliResultColumn<Row>[];
|
|
274
293
|
return serializeCsv([
|
|
275
|
-
resolvedColumns.map((column) => column.header),
|
|
276
|
-
...rows.map((row) =>
|
|
277
|
-
column.value ? column.value(row) : row[column.key]
|
|
278
|
-
|
|
294
|
+
[...resolvedColumns.map((column) => column.header), ...identity ? EXPORT_IDENTITY_COLUMNS : []],
|
|
295
|
+
...rows.map((row) => [
|
|
296
|
+
...resolvedColumns.map((column) => (column.value ? column.value(row) : row[column.key])),
|
|
297
|
+
...identity?.(row) ?? [],
|
|
298
|
+
]),
|
|
279
299
|
]);
|
|
280
300
|
}
|
|
281
301
|
|
|
302
|
+
function stringField(row: Record<string, unknown>, key: string): string | null {
|
|
303
|
+
const value = row?.[key];
|
|
304
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** What a row is of: as the command says, else its own `symbol` and `currency` fields. */
|
|
308
|
+
function rowIdentity<Row extends Record<string, unknown>>(
|
|
309
|
+
row: Row,
|
|
310
|
+
declared: CliResultRenderOptions<unknown, Row>["exportIdentity"],
|
|
311
|
+
): ExportIdentity {
|
|
312
|
+
if (typeof declared === "function") return declared(row);
|
|
313
|
+
return declared ?? { symbolOrSeries: stringField(row, "symbol"), currencyOrUnit: stringField(row, "currency") };
|
|
314
|
+
}
|
|
315
|
+
|
|
282
316
|
function textColumns<Row extends Record<string, unknown>>(
|
|
283
317
|
rows: Row[],
|
|
284
318
|
columns?: CliResultColumn<Row>[],
|
|
@@ -389,16 +423,25 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
|
|
|
389
423
|
renderOptions: CliResultRenderOptions<T, Row>,
|
|
390
424
|
cited?: ReportFreshness,
|
|
391
425
|
): string {
|
|
392
|
-
if (renderOptions.tables && options.format === "csv"
|
|
393
|
-
|
|
426
|
+
if (renderOptions.tables && (options.format === "csv" || options.format === "ndjson")) {
|
|
427
|
+
// A report's tables name what they are of, else the command's one identity for every row does.
|
|
428
|
+
const declared = renderOptions.exportIdentity;
|
|
429
|
+
const tables = renderOptions.tables.identity || !declared || typeof declared === "function"
|
|
430
|
+
? renderOptions.tables
|
|
431
|
+
: { ...renderOptions.tables, identity: declared };
|
|
432
|
+
return options.format === "csv" ? renderReportCsv(tables, cited) : renderReportNdjson(tables, cited);
|
|
433
|
+
}
|
|
394
434
|
const window = windowed(asRows(result.data, renderOptions.rows), options, renderOptions);
|
|
395
435
|
const rows = window.rows;
|
|
396
436
|
if (options.format === "ndjson") {
|
|
397
|
-
return rows.map((row) => JSON.stringify(row)
|
|
437
|
+
return rows.map((row) => JSON.stringify(cited && isPlainObject(row)
|
|
438
|
+
? { ...row, ...exportIdentityFields(cited, rowIdentity(row as Row, renderOptions.exportIdentity)) }
|
|
439
|
+
: row)).join("\n");
|
|
398
440
|
}
|
|
399
441
|
if (options.format === "csv") {
|
|
400
|
-
|
|
401
|
-
|
|
442
|
+
if (!cited) return renderCsv(rows as Row[], renderOptions.columns);
|
|
443
|
+
const csv = renderCsv(rows as Row[], renderOptions.columns, (row) => exportIdentityCells(cited, rowIdentity(row, renderOptions.exportIdentity)));
|
|
444
|
+
return withCsvFooter(csv, [...exportSourceLines(cited), ...(renderOptions.exportNotes ?? []).map((note) => `note: ${note}`)]);
|
|
402
445
|
}
|
|
403
446
|
if (renderOptions.text) {
|
|
404
447
|
return renderOptions.text(result.data);
|