gloomberb 0.16.4 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -91
- package/package.json +4 -1
- package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
- package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
- package/src/account/billing.ts +57 -0
- package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
- package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
- package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
- package/src/account/session.ts +16 -0
- package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
- package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
- package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
- package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
- package/src/api-client/data.ts +24 -2
- package/src/api-client/earnings.ts +6 -0
- package/src/api-client/futures-curve.ts +56 -0
- package/src/api-client/index.ts +4 -1
- package/src/api-client/market-discovery.ts +10 -1
- package/src/api-client/paths.ts +7 -0
- package/src/api-client/research-activity.ts +15 -10
- package/src/api-client/types.ts +17 -1
- package/src/api-client/yield-curves.ts +8 -0
- package/src/app/global-shortcuts.ts +15 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
- package/src/architecture/core-builtin-allowlist.json +4 -13
- package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
- package/src/capabilities/chart-series.ts +11 -1
- package/src/cli/commands/crypto-hints.ts +7 -0
- package/src/cli/commands/fred-series.ts +61 -0
- package/src/cli/commands/market.ts +407 -104
- package/src/cli/commands/news-hints.ts +62 -0
- package/src/cli/commands/news-search.ts +53 -0
- package/src/cli/commands/options-leaps.ts +5 -2
- package/src/cli/commands/overview.ts +93 -20
- package/src/cli/commands/remote.ts +2 -1
- package/src/cli/commands/search.ts +82 -9
- package/src/cli/commands/system.ts +55 -8
- package/src/cli/commands/ticker.ts +88 -11
- package/src/cli/context.ts +45 -14
- package/src/cli/desktop-pane-shot.ts +59 -7
- package/src/cli/earnings-calendar.ts +71 -0
- package/src/cli/freshness.ts +18 -7
- package/src/cli/glossary.ts +429 -0
- package/src/cli/history-rows.ts +48 -7
- package/src/cli/index-roots.ts +7 -0
- package/src/cli/index.ts +21 -7
- package/src/cli/known-gaps.ts +27 -0
- package/src/cli/listing-arg.ts +417 -56
- package/src/cli/not-a-ticker.ts +12 -5
- package/src/cli/pane-functions/access-gate.ts +5 -0
- package/src/cli/pane-functions/capabilities.ts +3 -2
- package/src/cli/pane-functions/catalog.ts +35 -5
- package/src/cli/pane-functions/data.ts +12 -8
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/dom.ts +20 -6
- package/src/cli/pane-functions/freshness.ts +363 -43
- package/src/cli/pane-functions/function-help.ts +12 -5
- package/src/cli/pane-functions/headless.ts +45 -17
- package/src/cli/pane-functions/index.ts +63 -10
- package/src/cli/pane-functions/market-session.ts +22 -8
- package/src/cli/pane-functions/options.ts +63 -22
- package/src/cli/pane-functions/report-notices.ts +25 -7
- package/src/cli/pane-functions/report.ts +15 -8
- package/src/cli/pane-functions/resolver.ts +49 -12
- package/src/cli/pane-functions/screenshot.ts +110 -23
- package/src/cli/report-tables.ts +3 -0
- package/src/cli/result.ts +4 -1
- package/src/cli/staged-tickers.ts +52 -0
- package/src/cli/symbol-args.ts +9 -0
- package/src/cli/symbol-list.ts +172 -0
- package/src/cli/ticker-resolution.ts +223 -30
- package/src/cli/types.ts +5 -0
- package/src/components/chart/composite/axis-overlays.tsx +6 -2
- package/src/components/chart/composite/composite-chart.tsx +40 -6
- package/src/components/chart/composite/legend.tsx +49 -2
- package/src/components/chart/composite/panel-surface.tsx +44 -17
- package/src/components/chart/composite/price-axis-labels.tsx +3 -1
- package/src/components/command-bar/panel/index.tsx +11 -3
- package/src/components/command-bar/panel/runtime.ts +3 -0
- package/src/components/command-bar/panel/types.ts +2 -0
- package/src/components/command-bar/routes/root/provider-search.ts +4 -3
- package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
- package/src/components/command-bar/workflow/tickers.ts +23 -4
- package/src/components/layout/detached-pane-shell.tsx +9 -0
- package/src/components/layout/floating-pane.tsx +3 -0
- package/src/components/layout/header.tsx +20 -5
- package/src/components/layout/pane/content.tsx +7 -13
- package/src/components/layout/pane/footer/index.tsx +6 -2
- package/src/components/layout/pane/footer/model.ts +35 -1
- package/src/components/layout/pane/header.tsx +24 -1
- package/src/components/layout/pane/index.tsx +12 -2
- package/src/components/layout/pane/sidebar.tsx +78 -10
- package/src/components/layout/shell/active-drag.ts +21 -5
- package/src/components/layout/shell/drag/index.ts +39 -9
- package/src/components/layout/shell/drag/live.ts +61 -51
- package/src/components/layout/shell/drag/runtime.ts +54 -12
- package/src/components/layout/shell/index.tsx +25 -7
- package/src/components/layout/shell/menu.ts +5 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
- package/src/components/layout/shell/pane/layers.tsx +192 -45
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
- package/src/components/markdown-text.tsx +62 -20
- package/src/components/metric-treemap/heat-surface.tsx +10 -1
- package/src/components/onboarding/account-step/form-panel.tsx +1 -1
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-keyboard.ts +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +2 -2
- package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
- package/src/components/onboarding/wizard-account.ts +2 -2
- package/src/components/pane-template-wizard.tsx +9 -1
- package/src/components/price-selector-dialog.tsx +11 -2
- package/src/components/sign-in-gate.tsx +3 -3
- package/src/components/ticker/list-table-view.tsx +24 -1
- package/src/components/ui/action-row.tsx +2 -1
- package/src/components/ui/button.tsx +7 -1
- package/src/components/ui/choice-dialog.tsx +6 -4
- package/src/components/ui/confirm-dialog.tsx +28 -18
- package/src/components/ui/data-table/index.tsx +17 -6
- package/src/components/ui/data-table/opentui/index.tsx +37 -6
- package/src/components/ui/data-table/types.ts +12 -0
- package/src/components/ui/dialog-actions.tsx +64 -0
- package/src/components/ui/display.tsx +17 -3
- package/src/components/ui/frame.tsx +35 -2
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/text-prompt-dialog.tsx +11 -9
- package/src/data/cached-query.ts +2 -0
- package/src/data/config/store/normalize.ts +7 -2
- package/src/data/ticker-repository.ts +5 -0
- package/src/data/ticker-store.ts +15 -0
- package/src/docs/content.ts +215 -0
- package/src/docs/load.ts +139 -0
- package/src/docs/requests.ts +39 -0
- package/src/docs/snapshot.chunk.ts +8 -0
- package/src/docs/snapshot.json +7416 -0
- package/src/docs/topics.ts +216 -0
- package/src/i18n/es.ts +61 -4
- package/src/i18n/ja.ts +61 -4
- package/src/i18n/ko.ts +61 -4
- package/src/i18n/zh-cn.ts +61 -4
- package/src/i18n/zh-tw.ts +61 -4
- package/src/layout/desks.ts +1 -1
- package/src/layout/pane-manager/dock-tree.ts +18 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/chart.ts +10 -4
- package/src/market-data/coordinator/index.ts +6 -3
- package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
- package/src/market-data/futures-contract.ts +151 -0
- package/src/market-data/hooks.tsx +10 -2
- package/src/market-data/market/format.ts +39 -21
- package/src/market-data/market/freshness.ts +81 -18
- package/src/market-data/market/status.ts +19 -23
- package/src/market-data/published-apac-sessions.ts +23 -1
- package/src/market-data/quotes/contributions.ts +3 -0
- package/src/market-data/quotes/metadata.ts +7 -24
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +5 -4
- package/src/market-data/result-types.ts +2 -0
- package/src/plugins/builtin/account-management/model.ts +1 -56
- package/src/plugins/builtin/account-management/pane.tsx +6 -4
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/cli.ts +313 -47
- package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
- package/src/plugins/builtin/alerts/events.ts +23 -2
- package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +41 -9
- package/src/plugins/builtin/alerts/pane.tsx +8 -0
- package/src/plugins/builtin/alerts/research-builder.ts +8 -3
- package/src/plugins/builtin/alerts/research-rules.ts +18 -2
- package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
- package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
- package/src/plugins/builtin/alerts/scheduled.ts +254 -0
- package/src/plugins/builtin/alerts/targets.ts +92 -0
- package/src/plugins/builtin/analytics/index.tsx +4 -2
- package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
- package/src/plugins/builtin/analytics/risk-math.ts +91 -27
- package/src/plugins/builtin/analytics/risk-model.ts +35 -5
- package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
- package/src/plugins/builtin/attention/index.tsx +2 -0
- package/src/plugins/builtin/attention/pane.tsx +3 -1
- package/src/plugins/builtin/attention/series.ts +26 -0
- package/src/plugins/builtin/awards/index.tsx +2 -0
- package/src/plugins/builtin/awards/pane.tsx +3 -1
- package/src/plugins/builtin/awards/series.ts +29 -0
- package/src/plugins/builtin/bond-calculator/model.ts +2 -1
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/client.ts +70 -13
- package/src/plugins/builtin/cds/headless.ts +116 -19
- package/src/plugins/builtin/cds/index.tsx +3 -1
- package/src/plugins/builtin/cds/model.ts +193 -31
- package/src/plugins/builtin/cds/pane.tsx +56 -14
- package/src/plugins/builtin/cds/series.ts +19 -0
- package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
- package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
- package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
- package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
- package/src/plugins/builtin/chart-composer/headless.ts +8 -3
- package/src/plugins/builtin/chart-composer/index.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
- package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
- package/src/plugins/builtin/chat/controller/index.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
- package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
- package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
- package/src/plugins/builtin/cloud/askg/model.ts +27 -8
- package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
- package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
- package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
- package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
- package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
- package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
- package/src/plugins/builtin/company-attention/headless.ts +19 -5
- package/src/plugins/builtin/company-attention/index.tsx +2 -0
- package/src/plugins/builtin/company-attention/model.ts +26 -7
- package/src/plugins/builtin/company-attention/pane.tsx +12 -9
- package/src/plugins/builtin/company-attention/series.ts +52 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
- package/src/plugins/builtin/composite-plugins.ts +19 -13
- package/src/plugins/builtin/cot/index.tsx +3 -1
- package/src/plugins/builtin/cot/model.ts +40 -11
- package/src/plugins/builtin/cot/series.ts +27 -0
- package/src/plugins/builtin/credit-boards/index.tsx +2 -0
- package/src/plugins/builtin/credit-boards/series.ts +66 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
- package/src/plugins/builtin/credit-conditions/series.ts +26 -0
- package/src/plugins/builtin/credit-documents/headless.ts +31 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
- package/src/plugins/builtin/crypto-board/headless.ts +2 -3
- package/src/plugins/builtin/crypto-board/index.tsx +2 -0
- package/src/plugins/builtin/crypto-board/model.ts +12 -0
- package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
- package/src/plugins/builtin/crypto-board/series.ts +48 -0
- package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
- package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
- package/src/plugins/builtin/debt-maturities/series.ts +45 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
- package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
- package/src/plugins/builtin/dividend-yield/series.ts +20 -0
- package/src/plugins/builtin/docs/blocks.tsx +181 -0
- package/src/plugins/builtin/docs/index.tsx +68 -0
- package/src/plugins/builtin/docs/pane.tsx +426 -0
- package/src/plugins/builtin/docs/targets.ts +72 -0
- package/src/plugins/builtin/docs/use-docs.ts +60 -0
- package/src/plugins/builtin/doe/index.tsx +2 -0
- package/src/plugins/builtin/doe/series.ts +62 -0
- package/src/plugins/builtin/earnings/client.ts +3 -2
- package/src/plugins/builtin/earnings/format.ts +1 -5
- package/src/plugins/builtin/earnings/headless.ts +58 -6
- package/src/plugins/builtin/earnings/history-model.ts +15 -1
- package/src/plugins/builtin/earnings/history-table.ts +13 -2
- package/src/plugins/builtin/earnings/history.tsx +7 -3
- package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
- package/src/plugins/builtin/econ/index.tsx +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
- package/src/plugins/builtin/exposure/holdings.ts +1 -1
- package/src/plugins/builtin/exposure/pane.tsx +3 -1
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +43 -3
- package/src/plugins/builtin/futures/model.ts +1 -1
- package/src/plugins/builtin/futures/returns.ts +1 -1
- package/src/plugins/builtin/futures/series.ts +13 -0
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +70 -9
- package/src/plugins/builtin/futures-curve/index.tsx +1 -1
- package/src/plugins/builtin/futures-curve/model.ts +66 -11
- package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
- package/src/plugins/builtin/gpu/board.tsx +13 -10
- package/src/plugins/builtin/gpu/changes.tsx +4 -4
- package/src/plugins/builtin/gpu/client.ts +19 -1
- package/src/plugins/builtin/gpu/equities.tsx +14 -10
- package/src/plugins/builtin/gpu/headless.ts +56 -25
- package/src/plugins/builtin/gpu/history.tsx +3 -3
- package/src/plugins/builtin/gpu/index.tsx +2 -0
- package/src/plugins/builtin/gpu/model.ts +101 -16
- package/src/plugins/builtin/gpu/pane.tsx +28 -12
- package/src/plugins/builtin/gpu/series.ts +38 -0
- package/src/plugins/builtin/help/function-card.tsx +25 -6
- package/src/plugins/builtin/help/index.tsx +17 -8
- package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
- package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
- package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
- package/src/plugins/builtin/holders/client.ts +22 -3
- package/src/plugins/builtin/holders/format.ts +18 -3
- package/src/plugins/builtin/holders/headless.ts +36 -133
- package/src/plugins/builtin/holders/pane.tsx +11 -4
- package/src/plugins/builtin/holders/report-header.ts +40 -5
- package/src/plugins/builtin/holders/report-rows.ts +240 -0
- package/src/plugins/builtin/insider/client.ts +35 -20
- package/src/plugins/builtin/insider/headless.ts +41 -11
- package/src/plugins/builtin/insider/index.tsx +26 -9
- package/src/plugins/builtin/insider/insider-data.ts +10 -0
- package/src/plugins/builtin/insider/issuer.ts +49 -0
- package/src/plugins/builtin/insider/model.ts +3 -0
- package/src/plugins/builtin/iv-history/client.ts +14 -4
- package/src/plugins/builtin/iv-history/headless.ts +7 -4
- package/src/plugins/builtin/iv-history/index.tsx +2 -0
- package/src/plugins/builtin/iv-history/model.ts +111 -7
- package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
- package/src/plugins/builtin/iv-history/series.ts +29 -0
- package/src/plugins/builtin/jobs/headless.ts +28 -3
- package/src/plugins/builtin/jobs/index.tsx +3 -1
- package/src/plugins/builtin/jobs/series.ts +19 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
- package/src/plugins/builtin/macro-day/client.ts +44 -3
- package/src/plugins/builtin/macro-day/headless.ts +13 -10
- package/src/plugins/builtin/macro-day/model.ts +56 -10
- package/src/plugins/builtin/macro-day/pane.tsx +30 -20
- package/src/plugins/builtin/market-heatmap/model.ts +19 -11
- package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
- package/src/plugins/builtin/market-movers/client.ts +7 -5
- package/src/plugins/builtin/market-movers/index.tsx +3 -2
- package/src/plugins/builtin/market-movers/model.ts +30 -0
- package/src/plugins/builtin/market-movers/screener.ts +31 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
- package/src/plugins/builtin/market-valuation/index.tsx +3 -3
- package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
- package/src/plugins/builtin/members/headless.ts +7 -2
- package/src/plugins/builtin/members/model.ts +31 -3
- package/src/plugins/builtin/members/pane.tsx +3 -6
- package/src/plugins/builtin/mna/headless.ts +15 -3
- package/src/plugins/builtin/money-markets/index.tsx +2 -0
- package/src/plugins/builtin/money-markets/series.ts +41 -0
- package/src/plugins/builtin/notes/cli.ts +12 -7
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +7 -3
- package/src/plugins/builtin/options-positioning/headless.ts +1 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +2 -1
- package/src/plugins/builtin/options-scenario/headless.ts +1 -0
- package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
- package/src/plugins/builtin/perps/client.ts +40 -6
- package/src/plugins/builtin/perps/equity-row.tsx +33 -11
- package/src/plugins/builtin/perps/freshness.ts +66 -0
- package/src/plugins/builtin/perps/headless.ts +45 -30
- package/src/plugins/builtin/perps/index.tsx +18 -1
- package/src/plugins/builtin/perps/model.ts +20 -5
- package/src/plugins/builtin/perps/pane.tsx +76 -20
- package/src/plugins/builtin/perps/series.ts +76 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
- package/src/plugins/builtin/plugin-module.ts +3 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
- package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
- package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
- package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
- package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
- package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
- package/src/plugins/builtin/power/client.ts +16 -0
- package/src/plugins/builtin/power/evidence.ts +6 -2
- package/src/plugins/builtin/power/headless.ts +71 -7
- package/src/plugins/builtin/power/index.tsx +4 -1
- package/src/plugins/builtin/power/model.ts +72 -1
- package/src/plugins/builtin/power/pane.tsx +32 -9
- package/src/plugins/builtin/rate-path/headless.ts +11 -3
- package/src/plugins/builtin/realized-vol/headless.ts +5 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
- package/src/plugins/builtin/research/event-model.ts +68 -24
- package/src/plugins/builtin/research/events-headless.ts +2 -1
- package/src/plugins/builtin/research/headless.ts +2 -1
- package/src/plugins/builtin/seasonality/client.ts +15 -7
- package/src/plugins/builtin/seasonality/headless.ts +36 -12
- package/src/plugins/builtin/seasonality/index.tsx +2 -2
- package/src/plugins/builtin/seasonality/model.ts +119 -0
- package/src/plugins/builtin/seasonality/pane.tsx +108 -37
- package/src/plugins/builtin/series-inventory.ts +91 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
- package/src/plugins/builtin/shared/command-tab.ts +30 -12
- package/src/plugins/builtin/shared/earnings-report.ts +55 -0
- package/src/plugins/builtin/shared/fred-points.ts +20 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
- package/src/plugins/builtin/shared/open-description.ts +29 -0
- package/src/plugins/builtin/shared/report-freshness.ts +9 -0
- package/src/plugins/builtin/shared/series-warning.ts +5 -0
- package/src/plugins/builtin/short-interest/headless.ts +1 -0
- package/src/plugins/builtin/short-interest/index.tsx +2 -0
- package/src/plugins/builtin/short-interest/series.ts +36 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/short-volume/index.tsx +2 -0
- package/src/plugins/builtin/short-volume/series.ts +23 -0
- package/src/plugins/builtin/social-mentions/index.tsx +2 -0
- package/src/plugins/builtin/social-mentions/series.ts +23 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
- package/src/plugins/builtin/thirteenf/api.ts +1 -0
- package/src/plugins/builtin/thirteenf/data.ts +6 -4
- package/src/plugins/builtin/thirteenf/headless.ts +217 -36
- package/src/plugins/builtin/thirteenf/model.ts +39 -1
- package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
- package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
- package/src/plugins/builtin/time-sales/headless.ts +1 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
- package/src/plugins/builtin/vol-surface/headless.ts +1 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -0
- package/src/plugins/builtin/volatility/series.ts +84 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
- package/src/plugins/builtin/world-venue-map/index.ts +2 -1
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
- package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
- package/src/plugins/builtin/yield-curve/chart.ts +2 -2
- package/src/plugins/builtin/yield-curve/curves.ts +21 -8
- package/src/plugins/builtin/yield-curve/headless.ts +71 -18
- package/src/plugins/builtin/yield-curve/index.tsx +3 -1
- package/src/plugins/builtin/yield-curve/series.ts +86 -0
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/registry/index.ts +89 -4
- package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
- package/src/portfolio/position-value.ts +40 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
- package/src/public/time-series.ts +17 -0
- package/src/react/auto-refresh.ts +13 -3
- package/src/remote/app-host.tsx +20 -4
- package/src/remote/confirm-change.tsx +85 -0
- package/src/remote/controller-utils.ts +8 -1
- package/src/remote/controller.ts +123 -6
- package/src/remote/pane-screenshot.ts +47 -0
- package/src/remote/schema.ts +62 -0
- package/src/remote/types.ts +30 -0
- package/src/remote/watchlist.ts +306 -0
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/dom/data-table/frozen-column.ts +3 -1
- package/src/renderers/dom/data-table/index.tsx +33 -6
- package/src/renderers/dom/data-table/row.tsx +27 -1
- package/src/renderers/dom/desktop/control-styles.ts +63 -1
- package/src/renderers/dom/desktop/controls.tsx +30 -23
- package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/dom-ui-host.tsx +3 -0
- package/src/renderers/dom/host/box.tsx +3 -0
- package/src/renderers/dom/host/chart-surface.tsx +6 -2
- package/src/renderers/dom/host/live-frame.ts +6 -2
- package/src/renderers/dom/host/overflow-fade.ts +14 -1
- package/src/renderers/dom/host/size-observer.ts +26 -0
- package/src/renderers/dom/host/surface-3d.tsx +7 -2
- package/src/renderers/dom/host/text.tsx +1 -1
- package/src/renderers/dom/styles.css +61 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/series/capability.ts +75 -0
- package/src/series/csv.ts +160 -0
- package/src/series/files.ts +107 -0
- package/src/series/registry.ts +198 -0
- package/src/series/resolve.ts +107 -0
- package/src/series/standalone.ts +48 -0
- package/src/series/types.ts +145 -0
- package/src/series/validate.ts +171 -0
- package/src/sources/gloomberb-cloud/index.ts +7 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
- package/src/sources/history-result.ts +1 -0
- package/src/sources/listing-symbols.ts +4 -1
- package/src/sources/provider-router/batches.ts +4 -1
- package/src/sources/provider-router/cache.ts +4 -2
- package/src/sources/provider-router/cached-routes.ts +1 -1
- package/src/sources/provider-router/financial-routes.ts +1 -0
- package/src/sources/provider-router/financials.ts +13 -0
- package/src/sources/provider-router/history.ts +43 -25
- package/src/sources/provider-router/search.ts +1 -0
- package/src/sync/core-contributors.ts +1 -0
- package/src/tickers/company-name.ts +75 -0
- package/src/tickers/instrument-kind.ts +25 -0
- package/src/tickers/open-target.ts +3 -2
- package/src/tickers/search/index.ts +45 -7
- package/src/tickers/search/ranking.ts +33 -17
- package/src/tickers/search/types.ts +7 -1
- package/src/tickers/us-listing.ts +34 -0
- package/src/time-series/hooks.ts +9 -0
- package/src/time-series/resolve.ts +95 -16
- package/src/time-series/types.ts +4 -2
- package/src/time-series/use-chart-resolution.ts +32 -0
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -0
- package/src/types/financials.ts +52 -1
- package/src/types/headless.ts +61 -0
- package/src/types/plugin.ts +19 -0
- package/src/types/price-history.ts +7 -0
- package/src/types/ticker.ts +14 -0
- package/src/ui/host.tsx +18 -0
- package/src/ui/live-resize.ts +81 -0
- package/src/ui/live-stretch.ts +121 -0
- package/src/ui/measured-cells.ts +58 -0
- package/src/ui/pane-fill.ts +11 -0
- package/src/utils/basis-points.ts +17 -0
- package/src/utils/cli-output.ts +7 -1
- package/src/utils/concurrency.ts +25 -0
- package/src/utils/crypto-pair.ts +20 -0
- package/src/utils/currency-pair.ts +19 -0
- package/src/utils/decimal-comma.ts +19 -0
- package/src/utils/dom-screenshot-css.ts +265 -0
- package/src/utils/dom-screenshot-rasters.ts +276 -0
- package/src/utils/dom-screenshot.ts +437 -52
- package/src/utils/exchange-rate-snapshot.ts +6 -2
- package/src/utils/exchanges.ts +107 -1
- package/src/utils/format.ts +16 -3
- package/src/utils/fundamentals.ts +66 -0
- package/src/utils/futures-generic.ts +6 -12
- package/src/utils/fx-market-hours.ts +15 -5
- package/src/utils/price-history.ts +99 -42
- package/src/utils/sec-eps-basis.ts +24 -11
- package/src/utils/sec.ts +61 -7
- package/src/utils/utc-time.ts +51 -4
- package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
- /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { futuresBoardContract, type FuturesContract, type FuturesSector } from "../../market-data/futures-board";
|
|
2
|
+
import { isFuturesInstrument } from "../../market-data/futures-contract";
|
|
3
|
+
import type { Quote } from "../../types/financials";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A company whose news follows a futures market, for the hint `news` gives a
|
|
7
|
+
* futures code: news is filed under company tickers, and funds such as UNG
|
|
8
|
+
* rarely carry stories of their own. Index, rates and currency futures have
|
|
9
|
+
* none; market headlines fit them better.
|
|
10
|
+
*/
|
|
11
|
+
const CONTRACT_COMPANIES: Readonly<Record<string, string>> = {
|
|
12
|
+
NG: "EQT", TTF: "LNG", CL: "XOM", BZ: "XOM", RB: "XOM", HO: "XOM",
|
|
13
|
+
GC: "NEM", SI: "NEM", PL: "NEM", PA: "NEM", HG: "FCX", ALI: "AA",
|
|
14
|
+
};
|
|
15
|
+
const SECTOR_COMPANIES: Partial<Record<FuturesSector, string>> = {
|
|
16
|
+
energy: "XOM",
|
|
17
|
+
metals: "NEM",
|
|
18
|
+
agriculture: "DE",
|
|
19
|
+
livestock: "TSN",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** "try news LNG", or market headlines where no company follows the contract. */
|
|
23
|
+
function contractSuggestion(contract: FuturesContract): string {
|
|
24
|
+
const company = CONTRACT_COMPANIES[contract.code] ?? SECTOR_COMPANIES[contract.sector];
|
|
25
|
+
return company ? `try news ${company}` : "for market headlines run news with no symbol";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* What `news <symbol>` says when it finds no stories: news is filed under
|
|
30
|
+
* company tickers, so a futures board code (TTF), a futures contract (CL=F)
|
|
31
|
+
* or a symbol no listing answers to gets told so, with something to try.
|
|
32
|
+
* `searched` says a text search for the word found no story either.
|
|
33
|
+
*/
|
|
34
|
+
export function newsEmptyMessage(
|
|
35
|
+
symbol: string,
|
|
36
|
+
title: string,
|
|
37
|
+
quote: Pick<Quote, "instrumentType"> | null,
|
|
38
|
+
resolved: boolean,
|
|
39
|
+
searched = false,
|
|
40
|
+
): string {
|
|
41
|
+
const contract = futuresBoardContract(symbol);
|
|
42
|
+
const typed = symbol.trim().toUpperCase();
|
|
43
|
+
if (!resolved) {
|
|
44
|
+
if (searched) {
|
|
45
|
+
return contract
|
|
46
|
+
? `No stories match ${typed}, the futures board's code for ${contract.name}. It is not a ticker: ${contractSuggestion(contract)}.`
|
|
47
|
+
: `No listing answers to ${typed} and no stories match it. Try \`gloomberb search ${typed}\`.`;
|
|
48
|
+
}
|
|
49
|
+
return contract
|
|
50
|
+
? `${typed} is the futures board's code for ${contract.name}, not a ticker. news takes a company ticker: ${contractSuggestion(contract)}.`
|
|
51
|
+
: `No listing answers to ${typed}. news takes a company ticker, such as news AAPL. Try \`gloomberb search ${typed}\`.`;
|
|
52
|
+
}
|
|
53
|
+
if (isFuturesInstrument(symbol, quote?.instrumentType)) {
|
|
54
|
+
if (searched) {
|
|
55
|
+
return `No stories match ${typed}, and news is filed under company tickers, not futures${contract ? `; ${contractSuggestion(contract)}` : ""}.`;
|
|
56
|
+
}
|
|
57
|
+
return `No news for ${title}: news is filed under company tickers, not futures${contract ? `; ${contractSuggestion(contract)}` : ""}.`;
|
|
58
|
+
}
|
|
59
|
+
return contract
|
|
60
|
+
? `No news for ${title}. ${typed} is also the futures board's code for ${contract.name}: ${contractSuggestion(contract)}.`
|
|
61
|
+
: `No news for ${title}.`;
|
|
62
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { apiClient } from "../../api-client";
|
|
2
|
+
import type { NewsArticle } from "../../news/types";
|
|
3
|
+
import { mapCloudNewsArticle } from "../../sources/gloomberb-cloud/news";
|
|
4
|
+
import { cliStyles } from "../../utils/cli-output";
|
|
5
|
+
|
|
6
|
+
/** What the service accepts of a search: two characters to two hundred, with a letter or a number in them. */
|
|
7
|
+
const MIN_TEXT_LENGTH = 2;
|
|
8
|
+
const MAX_TEXT_LENGTH = 200;
|
|
9
|
+
const MIN_FALLBACK_LENGTH = 3;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The text `news` was asked to search for rather than a symbol: several words,
|
|
13
|
+
* quoted or not (`news "european gas"`, `news european gas`). A single word
|
|
14
|
+
* is a symbol until nothing answers to it (`newsFallbackText`).
|
|
15
|
+
*/
|
|
16
|
+
export function newsSearchText(args: readonly string[]): string | null {
|
|
17
|
+
const text = args.join(" ").trim().replace(/\s+/g, " ");
|
|
18
|
+
return args.length > 1 || /\s/.test(text) ? text : null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Why the service would refuse the text, or null when it can search it. */
|
|
22
|
+
export function newsSearchTextProblem(text: string): string | null {
|
|
23
|
+
if (text.length < MIN_TEXT_LENGTH || !/[\p{L}\p{N}]/u.test(text)) return "Search text needs at least two characters, with a letter or a number.";
|
|
24
|
+
if (text.length > MAX_TEXT_LENGTH) return `Search text is limited to ${MAX_TEXT_LENGTH} characters.`;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The word a symbol lookup found no stories for, as text to search stories for.
|
|
30
|
+
* Null under three characters, where the fuzzy match finds names that merely
|
|
31
|
+
* contain it (ZN, Telomir-Zn) and the lookup's own answer is the better one.
|
|
32
|
+
*/
|
|
33
|
+
export function newsFallbackText(symbol: string): string | null {
|
|
34
|
+
const text = symbol.trim().replace(/^\$/, "");
|
|
35
|
+
return text.length < MIN_FALLBACK_LENGTH || newsSearchTextProblem(text) ? null : text;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The stories matching the text, newest first. The service answers best match
|
|
40
|
+
* first and holds stories back from an account without real-time access, as
|
|
41
|
+
* every other news request.
|
|
42
|
+
*/
|
|
43
|
+
export async function searchNewsStories(text: string, limit: number): Promise<NewsArticle[]> {
|
|
44
|
+
const response = await apiClient.searchCloudNews(text, limit);
|
|
45
|
+
return response.items
|
|
46
|
+
.map((item) => mapCloudNewsArticle(item))
|
|
47
|
+
.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The line above the stories: "Stories matching "european gas"", with why a word typed as a symbol is searched. */
|
|
51
|
+
export function newsSearchHeading(text: string, reason?: string): string {
|
|
52
|
+
return `${cliStyles.bold(`Stories matching "${text}"`)}${reason ? cliStyles.muted(` · ${reason}`) : ""}`;
|
|
53
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { OptionContract, OptionsChain } from "../../types/financials";
|
|
|
2
2
|
import type { CliCommandContext } from "../../types/plugin";
|
|
3
3
|
import type { MarketContext } from "../types";
|
|
4
4
|
import type { CliResultColumn } from "../result";
|
|
5
|
-
import { requireCliListing, loadListingQuote, type CliListing } from "../listing-arg";
|
|
5
|
+
import { listingReadNotes, requireCliListing, loadListingQuote, type CliListing } from "../listing-arg";
|
|
6
6
|
import { rowsFreshness } from "../freshness";
|
|
7
7
|
import { formatCountCell } from "../helpers";
|
|
8
8
|
import { cliStyles } from "../../utils/cli-output";
|
|
@@ -388,7 +388,10 @@ export async function runLeapsScreen(
|
|
|
388
388
|
ctx.fail(screened.map((entry) => entry.warning).filter(Boolean).join("\n"));
|
|
389
389
|
}
|
|
390
390
|
const rows = sortLeapsRows(screened.flatMap((entry) => entry.rows), criteria.sort);
|
|
391
|
-
const warnings =
|
|
391
|
+
const warnings = [
|
|
392
|
+
...listings.flatMap(listingReadNotes),
|
|
393
|
+
...screened.map((entry) => entry.warning).filter((warning): warning is string => warning != null),
|
|
394
|
+
];
|
|
392
395
|
const { format, limit: rowLimit, tail } = ctx.cliOptions;
|
|
393
396
|
const capped = format === "text" && rowLimit == null && tail == null && rows.length > LEAPS_TEXT_ROWS;
|
|
394
397
|
const describe = (data: readonly LeapsRow[]) => {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
rankScreenerQuotes,
|
|
11
11
|
type ScreenerCategory,
|
|
12
12
|
} from "../../plugins/builtin/market-movers/screener";
|
|
13
|
-
import { formatMoverPrice, moverReferencePrice } from "../../plugins/builtin/market-movers/model";
|
|
13
|
+
import { formatMoverPrice, moverReferencePrice, moverRegularSession } from "../../plugins/builtin/market-movers/model";
|
|
14
14
|
import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
|
|
15
15
|
import {
|
|
16
16
|
COUNTRY_CYCLE,
|
|
@@ -27,14 +27,24 @@ import { buildCorrelationSeries } from "../../plugins/builtin/correlation/matrix
|
|
|
27
27
|
import { alignDailyCloses, correlateDailyCloses } from "../../plugins/builtin/correlation/compute";
|
|
28
28
|
import { mixedSessionCloseNote } from "../../market-data/market/session-close-note";
|
|
29
29
|
import { CORRELATION_RETURN_BASIS, loadCorrelationHistory } from "../../plugins/builtin/correlation/history";
|
|
30
|
-
import { EXCHANGE_OPTION, listingIdentity, loadForListing, loadListingQuote, requireCliListing, type CliListing } from "../listing-arg";
|
|
30
|
+
import { EXCHANGE_OPTION, listingIdentity, listingReadNotes, loadForListing, loadListingQuote, requireCliListing, type CliListing } from "../listing-arg";
|
|
31
31
|
import { CLI_COMMAND_GROUPS } from "../help";
|
|
32
32
|
import { formatChangePercentCell, formatCompactCell } from "../helpers";
|
|
33
33
|
import { WORLD_INDICES } from "../../plugins/builtin/world-indices/indices";
|
|
34
34
|
import { getSectorCollection, SECTOR_COLLECTIONS } from "../../plugins/builtin/sectors/sector-data";
|
|
35
35
|
import { DAILY_CLOSES } from "../../plugins/builtin/shared/report-freshness";
|
|
36
|
-
import { newestReportTime, oldestReportTime } from "../../utils/utc-time";
|
|
36
|
+
import { localTimeSuffix, newestReportTime, oldestReportTime } from "../../utils/utc-time";
|
|
37
37
|
import { quotesFreshness, rowsFreshness } from "../freshness";
|
|
38
|
+
import { periodicityOf } from "../pane-functions/freshness";
|
|
39
|
+
import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
40
|
+
import {
|
|
41
|
+
DEFAULT_FRED_START,
|
|
42
|
+
fredEmptyMessage,
|
|
43
|
+
fredHistoryNotes,
|
|
44
|
+
isSpreadSeries,
|
|
45
|
+
laggingSeriesNote,
|
|
46
|
+
spreadBasisPointsCell,
|
|
47
|
+
} from "./fred-series";
|
|
38
48
|
|
|
39
49
|
// Batch quotes often omit names for indices and ETFs; these baskets are fixed, so name them here.
|
|
40
50
|
const BASKET_NAMES = new Map<string, string>([
|
|
@@ -80,7 +90,10 @@ const MOVER_COLUMNS = [
|
|
|
80
90
|
{ key: "volume", header: "Volume", align: "right" as const, format: formatCompactCell },
|
|
81
91
|
{ ...MARKET_CAP_CELL, format: formatCompactCell },
|
|
82
92
|
];
|
|
83
|
-
const START_OPTION = {
|
|
93
|
+
const START_OPTION = {
|
|
94
|
+
flags: "--start <yyyy-mm-dd>",
|
|
95
|
+
description: `First observation date; yyyy or yyyy-mm start at the first of that year or month (default ${DEFAULT_FRED_START})`,
|
|
96
|
+
};
|
|
84
97
|
|
|
85
98
|
const MOVER_LISTS: Record<string, ScreenerCategory | "trending"> = {
|
|
86
99
|
gainers: "day_gainers",
|
|
@@ -150,13 +163,25 @@ async function runMoverCommand(args: string[], ctx: Parameters<CliCommandDef["ex
|
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
const screener = await fetchScreenerResult(category, limit, undefined, { forceRefresh: ctx.cliOptions.refresh });
|
|
153
|
-
|
|
166
|
+
// Ranked by the live print, as the list is; then Last and Chg% are the regular
|
|
167
|
+
// session, as `ticker` reads it, and a pre-market or after-hours print is its own column.
|
|
168
|
+
const rows = rankScreenerQuotes(category, screener.data).map((quote) => {
|
|
169
|
+
const { extended, ...row } = moverRegularSession(quote);
|
|
170
|
+
return {
|
|
171
|
+
...row,
|
|
172
|
+
extendedSession: extended?.session ?? null,
|
|
173
|
+
extendedPrice: extended?.price ?? null,
|
|
174
|
+
extendedChange: extended?.change ?? null,
|
|
175
|
+
extendedChangePercent: extended?.changePercent ?? null,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
154
178
|
// A screener snapshot, as the MOST report reads it: delayed, each row dated by its own last price.
|
|
155
179
|
ctx.printResult({ data: rows, freshness: rowsFreshness(rows, { status: "delayed" }, { stale: screener.stale === true }) }, {
|
|
156
180
|
columns: [
|
|
157
181
|
...MOVER_COLUMNS.slice(0, 4),
|
|
182
|
+
...EXTENDED_COLUMNS,
|
|
158
183
|
{ key: "volume", header: "Volume", align: "right", value: (row) => formatCompact(Number(row.volume)) },
|
|
159
|
-
{
|
|
184
|
+
{ ...MARKET_CAP_CELL, value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
|
|
160
185
|
],
|
|
161
186
|
});
|
|
162
187
|
}
|
|
@@ -180,12 +205,12 @@ async function runQuoteBasket(symbols: string[], ctx: Parameters<CliCommandDef["
|
|
|
180
205
|
});
|
|
181
206
|
}
|
|
182
207
|
|
|
183
|
-
/** The UTC date, or the UTC time with its zone named, of an event's timestamp. */
|
|
208
|
+
/** The UTC date, or the UTC time with its zone named (and the reader's own beside it), of an event's timestamp. */
|
|
184
209
|
function utcDateTimePart(value: unknown, part: "date" | "time"): string {
|
|
185
210
|
const date = new Date(String(value));
|
|
186
211
|
if (Number.isNaN(date.getTime())) return "";
|
|
187
212
|
const iso = date.toISOString();
|
|
188
|
-
return part === "date" ? iso.slice(0, 10) : `${iso.slice(11, 16)} UTC`;
|
|
213
|
+
return part === "date" ? iso.slice(0, 10) : `${iso.slice(11, 16)} UTC${localTimeSuffix(date.getTime())}`;
|
|
189
214
|
}
|
|
190
215
|
|
|
191
216
|
const ECON_USAGE = "econ [--country <region>] [--impact <level>] [--from <yyyy-mm-dd>]";
|
|
@@ -204,13 +229,22 @@ function econEmptyMessage(from: string | null, listedThrough: string | null, sto
|
|
|
204
229
|
: `No matching events${range} through ${listedThrough}.`;
|
|
205
230
|
}
|
|
206
231
|
|
|
207
|
-
/**
|
|
208
|
-
|
|
232
|
+
/**
|
|
233
|
+
* A `yyyy-mm-dd` option, checked here; null when it was not given. `expand`
|
|
234
|
+
* reads a shorthand as a full date first, and `form` is how the message names
|
|
235
|
+
* what the option takes.
|
|
236
|
+
*/
|
|
237
|
+
function parseDateOption(
|
|
238
|
+
value: string | undefined,
|
|
239
|
+
flag: string,
|
|
240
|
+
ctx: Parameters<CliCommandDef["execute"]>[1],
|
|
241
|
+
{ expand = (text: string) => text, form = "yyyy-mm-dd" }: { expand?: (text: string) => string; form?: string } = {},
|
|
242
|
+
): string | null {
|
|
209
243
|
if (value == null) return null;
|
|
210
|
-
const date = value.trim();
|
|
244
|
+
const date = expand(value.trim());
|
|
211
245
|
const time = /^\d{4}-\d{2}-\d{2}$/.test(date) ? Date.parse(`${date}T00:00:00Z`) : Number.NaN;
|
|
212
246
|
if (!Number.isFinite(time) || new Date(time).toISOString().slice(0, 10) !== date) {
|
|
213
|
-
ctx.fail(`${flag} takes a date as
|
|
247
|
+
ctx.fail(`${flag} takes a date as ${form}, got "${value}".`);
|
|
214
248
|
}
|
|
215
249
|
return date;
|
|
216
250
|
}
|
|
@@ -281,9 +315,15 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
|
|
|
281
315
|
const FRED_USAGE = "fred <series-id> [--start <yyyy-mm-dd>] [--sort desc|asc]";
|
|
282
316
|
const FRED_LIST_USAGE = "fred --list [filter]";
|
|
283
317
|
|
|
284
|
-
/**
|
|
318
|
+
/** A year or a month stands for the first day of it. */
|
|
319
|
+
function startOfPeriod(text: string): string {
|
|
320
|
+
if (/^\d{4}$/.test(text)) return `${text}-01-01`;
|
|
321
|
+
return /^\d{4}-\d{2}$/.test(text) ? `${text}-01` : text;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** `--start`, checked here: Gloom Cloud cannot read anything but yyyy-mm-dd, so yyyy and yyyy-mm are spelled out. */
|
|
285
325
|
function parseStartDate(value: string | undefined, ctx: Parameters<CliCommandDef["execute"]>[1]): string {
|
|
286
|
-
return parseDateOption(value, "--start", ctx
|
|
326
|
+
return parseDateOption(value, "--start", ctx, { expand: startOfPeriod, form: "yyyy-mm-dd, yyyy-mm or yyyy" }) ?? DEFAULT_FRED_START;
|
|
287
327
|
}
|
|
288
328
|
|
|
289
329
|
/** Gloom Cloud serves a curated set of FRED series and answers 400 for any other id. */
|
|
@@ -336,15 +376,37 @@ async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
336
376
|
throw error;
|
|
337
377
|
});
|
|
338
378
|
const rows = data.observations;
|
|
379
|
+
// The default start hides older history and a start before the series begins is moved up silently: say where it really starts.
|
|
380
|
+
const notes = [
|
|
381
|
+
...fredHistoryNotes({ startDate, startGiven: start != null, seriesStart: data.info?.observationStart }),
|
|
382
|
+
laggingSeriesNote(seriesId),
|
|
383
|
+
].filter((note): note is string => note != null);
|
|
384
|
+
// Nothing on or after the start: name where the series ends, so an empty answer is not a dead end.
|
|
385
|
+
const latest = rows.length > 0 ? null : await apiClient.getCloudFredSeries(seriesId, { limit: 1, sortOrder: "desc" })
|
|
386
|
+
.then((payload) => payload.observations[0] ?? null).catch(() => null);
|
|
387
|
+
const periodicity = periodicityOf(data.info?.frequency);
|
|
339
388
|
ctx.printResult({
|
|
340
389
|
data: rows,
|
|
341
|
-
metadata: {
|
|
342
|
-
|
|
390
|
+
metadata: {
|
|
391
|
+
info: data.info, seriesId, startDate, sortOrder,
|
|
392
|
+
...(notes.length ? { notes } : {}),
|
|
393
|
+
...(latest ? { latestObservation: latest } : {}),
|
|
394
|
+
},
|
|
395
|
+
freshness: rowsFreshness(rows, {
|
|
396
|
+
source: "FRED", status: "not-a-feed", basis: "published statistics", observedKey: "date", oldest: null,
|
|
397
|
+
// A monthly or quarterly observation is dated the first of its period, not a day it was seen.
|
|
398
|
+
...(periodicity ? { periodicity } : {}),
|
|
399
|
+
}),
|
|
343
400
|
}, {
|
|
344
401
|
dateKey: "date",
|
|
402
|
+
empty: fredEmptyMessage(startDate, latest),
|
|
403
|
+
...(notes.length ? { footnote: notes.join("\n") } : {}),
|
|
345
404
|
textColumns: [
|
|
346
405
|
{ key: "date", header: "Date" },
|
|
347
|
-
|
|
406
|
+
isSpreadSeries(seriesId)
|
|
407
|
+
// A spread is read in basis points; JSON and CSV keep FRED's percentage points.
|
|
408
|
+
? { key: "value", header: "Value (bp)", align: "right", format: spreadBasisPointsCell }
|
|
409
|
+
: { key: "value", header: data.info?.units ? `Value (${data.info.units})` : "Value", align: "right" },
|
|
348
410
|
],
|
|
349
411
|
});
|
|
350
412
|
}
|
|
@@ -358,12 +420,16 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
358
420
|
const series = Object.keys(YIELD_TENORS);
|
|
359
421
|
const results = await Promise.all(series.map(async (seriesId) => {
|
|
360
422
|
const data = await apiClient.getCloudFredSeries(seriesId, { startDate, sortOrder: "desc" });
|
|
361
|
-
const latest = data.observations
|
|
423
|
+
const [latest, previous] = data.observations;
|
|
362
424
|
return {
|
|
363
425
|
seriesId,
|
|
364
426
|
date: latest?.date ?? "",
|
|
365
427
|
value: latest?.value ?? null,
|
|
366
428
|
title: data.info?.title ?? "",
|
|
429
|
+
// The move since the previous observation FRED has, not calendar yesterday: a Monday reads against Friday.
|
|
430
|
+
previousDate: previous?.date ?? null,
|
|
431
|
+
previousValue: previous?.value ?? null,
|
|
432
|
+
changeBasisPoints: latest?.value != null && previous?.value != null ? toBasisPoints(latest.value - previous.value, 1) : null,
|
|
367
433
|
};
|
|
368
434
|
}));
|
|
369
435
|
ctx.printResult({
|
|
@@ -373,7 +439,13 @@ async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
373
439
|
}, {
|
|
374
440
|
textColumns: [
|
|
375
441
|
{ key: "tenor", header: "Tenor", value: (row) => YIELD_TENORS[String(row.seriesId)] ?? row.seriesId },
|
|
376
|
-
{ key: "value", header: "Yield %", align: "right" },
|
|
442
|
+
{ key: "value", header: "Yield %", align: "right", format: (value) => typeof value === "number" ? value.toFixed(2) : "-" },
|
|
443
|
+
{
|
|
444
|
+
key: "changeBasisPoints",
|
|
445
|
+
header: "1D bp",
|
|
446
|
+
align: "right",
|
|
447
|
+
format: (value) => typeof value === "number" ? formatBasisPoints(value / 100) : "-",
|
|
448
|
+
},
|
|
377
449
|
{ key: "date", header: "Date" },
|
|
378
450
|
{ key: "seriesId", header: "Series" },
|
|
379
451
|
],
|
|
@@ -419,9 +491,10 @@ async function runCorrelation(rawArgs: string[], ctx: Parameters<CliCommandDef["
|
|
|
419
491
|
asOf: newestReportTime(lastBars),
|
|
420
492
|
oldest: oldestReportTime(lastBars),
|
|
421
493
|
});
|
|
494
|
+
const warnings = [...listingReadNotes(leftListing!), ...listingReadNotes(rightListing!), ...sessionNote ? [sessionNote] : []];
|
|
422
495
|
ctx.printResult({
|
|
423
496
|
data: [{ left, right, samples: sampleSize, correlation }],
|
|
424
|
-
...(
|
|
497
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
425
498
|
metadata: { range: "1Y", basis: CORRELATION_RETURN_BASIS },
|
|
426
499
|
freshness,
|
|
427
500
|
}, {
|
|
@@ -41,9 +41,10 @@ export const remoteCliCommand: CliCommandDef = {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
// No data folder means this profile never opened the app, so none is running to drive.
|
|
44
45
|
const dataDir = await getDataDir();
|
|
45
46
|
if (!dataDir) {
|
|
46
|
-
ctx.fail("No
|
|
47
|
+
ctx.fail("No running Gloomberb app found.", "Open the desktop app or run gloomberb, then try again.");
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
import type { DataProvider } from "../../types/data-provider";
|
|
7
7
|
import type { TickerRecord } from "../../types/ticker";
|
|
8
8
|
import {
|
|
9
|
+
LISTING_SEARCH_LIMIT,
|
|
9
10
|
searchTickerCandidates,
|
|
10
11
|
type TickerSearchCandidate,
|
|
11
12
|
} from "../../tickers/search";
|
|
@@ -14,6 +15,10 @@ import { fail } from "../errors";
|
|
|
14
15
|
import type { MarketContext } from "../types";
|
|
15
16
|
import type { CliCommandContext } from "../../types/plugin";
|
|
16
17
|
import { indexRootFor, indexRootTryLine } from "../index-roots";
|
|
18
|
+
import { knownGapMessage } from "../known-gaps";
|
|
19
|
+
import { instrumentTypeLabel } from "../../tickers/instrument-kind";
|
|
20
|
+
import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
|
|
21
|
+
import { groupByCompany } from "../../tickers/company-name";
|
|
17
22
|
|
|
18
23
|
interface SearchCommandDependencies {
|
|
19
24
|
initMarketData?: () => Promise<MarketContext>;
|
|
@@ -41,18 +46,64 @@ export async function searchCandidatesForCli({
|
|
|
41
46
|
tickers.map((ticker) => [ticker.metadata.ticker.toUpperCase(), ticker] as const),
|
|
42
47
|
);
|
|
43
48
|
try {
|
|
44
|
-
|
|
49
|
+
const candidates = await searchTickerCandidates({
|
|
45
50
|
query: trimmedQuery,
|
|
46
51
|
tickers: localTickerMap,
|
|
47
52
|
dataProvider,
|
|
48
53
|
localLimit,
|
|
49
|
-
|
|
54
|
+
// Every company's listing of a symbol several use (CAR's ASX line is past the first ten), cut after ordering.
|
|
55
|
+
totalLimit: Math.max(totalLimit, LISTING_SEARCH_LIMIT),
|
|
56
|
+
searchContext: { limit: LISTING_SEARCH_LIMIT },
|
|
50
57
|
});
|
|
58
|
+
return (await withPrimaryListingFirst(trimmedQuery, candidates, dataProvider)).slice(0, totalLimit);
|
|
51
59
|
} catch {
|
|
52
60
|
return [];
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
|
|
64
|
+
function candidateExchange(candidate: TickerSearchCandidate): string {
|
|
65
|
+
return canonicalExchange(candidate.result?.exchange === "SMART"
|
|
66
|
+
? candidate.result.primaryExchange
|
|
67
|
+
: candidate.result?.exchange || candidate.result?.primaryExchange || candidate.ticker?.metadata.exchange || candidate.right);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* For a bare symbol several companies list (CAR: Avis, Carrefour, CAR Group),
|
|
72
|
+
* the listing `quote CAR` reads goes first as the primary listing, then each
|
|
73
|
+
* other company's first listing, then the rest of their venues, so a short
|
|
74
|
+
* list still shows every company. A saved listing is the user's choice and
|
|
75
|
+
* stays where search put it. Any other query keeps search's order.
|
|
76
|
+
*/
|
|
77
|
+
async function withPrimaryListingFirst(
|
|
78
|
+
query: string,
|
|
79
|
+
candidates: TickerSearchCandidate[],
|
|
80
|
+
dataProvider: DataProvider,
|
|
81
|
+
): Promise<TickerSearchCandidate[]> {
|
|
82
|
+
const symbol = query.toUpperCase();
|
|
83
|
+
if (!/^[A-Z0-9][A-Z0-9.\-]{0,11}$/.test(symbol)) return candidates;
|
|
84
|
+
const exact = candidates.filter((candidate) => candidate.symbol.toUpperCase() === symbol);
|
|
85
|
+
if (exact.some((candidate) => candidate.kind === "ticker" || candidate.saved)) return candidates;
|
|
86
|
+
const listed = exact.filter((candidate) => candidate.kind === "search" && (candidate.instrumentClass === "equity" || candidate.instrumentClass === "other"));
|
|
87
|
+
const companies = groupByCompany(listed, resolveSearchName);
|
|
88
|
+
if (companies.length < 2) return candidates;
|
|
89
|
+
const quote = await dataProvider.getQuote(symbol, "").catch(() => null);
|
|
90
|
+
const quoted = canonicalExchange(quote?.listingExchangeName || quote?.exchangeName);
|
|
91
|
+
if (!quoted) return candidates;
|
|
92
|
+
// The US exchanges share one symbol: a quote on any of them is the symbol's one US line.
|
|
93
|
+
const primary = listed.find((candidate) => candidateExchange(candidate) === quoted)
|
|
94
|
+
?? (isUsListingExchange(quoted) ? listed.find((candidate) => isUsListingExchange(candidateExchange(candidate))) : undefined);
|
|
95
|
+
if (!primary) return candidates;
|
|
96
|
+
const firsts = companies.filter((group) => !group.includes(primary)).map((group) => group[0]!);
|
|
97
|
+
const ordered = [primary, ...firsts, ...listed.filter((candidate) => candidate !== primary && !firsts.includes(candidate))];
|
|
98
|
+
const at = candidates.indexOf(listed[0]!);
|
|
99
|
+
const rest = candidates.filter((candidate) => !listed.includes(candidate));
|
|
100
|
+
return [...rest.slice(0, at), ...ordered, ...rest.slice(at)].map((candidate) => (
|
|
101
|
+
candidate === primary ? { ...candidate, category: "Primary Listing" as const }
|
|
102
|
+
: listed.includes(candidate) || candidate.category === "Primary Listing" ? { ...candidate, category: "Other Listings" as const }
|
|
103
|
+
: candidate
|
|
104
|
+
));
|
|
105
|
+
}
|
|
106
|
+
|
|
56
107
|
/** "Saved", or the broker a row came from; never the data service's internal id. */
|
|
57
108
|
function searchSourceLabel(candidate: TickerSearchCandidate): string {
|
|
58
109
|
return candidate.kind === "ticker" ? "Saved" : candidate.result?.brokerLabel || "";
|
|
@@ -62,6 +113,13 @@ function resolveSearchName(candidate: TickerSearchCandidate): string {
|
|
|
62
113
|
return candidate.detail.split(" | ")[0] || candidate.detail || "—";
|
|
63
114
|
}
|
|
64
115
|
|
|
116
|
+
/** The listing's type as the table shows it; a perpetual price a provider types as a coin says what it is. */
|
|
117
|
+
function searchType(candidate: TickerSearchCandidate): string | undefined {
|
|
118
|
+
return instrumentTypeLabel(candidate.result?.type
|
|
119
|
+
|| candidate.result?.brokerContract?.secType
|
|
120
|
+
|| candidate.ticker?.metadata.assetCategory, resolveSearchName(candidate));
|
|
121
|
+
}
|
|
122
|
+
|
|
65
123
|
function searchCandidateRows(candidates: TickerSearchCandidate[]) {
|
|
66
124
|
return candidates.map((candidate) => ({
|
|
67
125
|
category: candidate.category,
|
|
@@ -92,6 +150,22 @@ function indexRootNote(query: string, candidates: TickerSearchCandidate[]): stri
|
|
|
92
150
|
return indexRootTryLine(root);
|
|
93
151
|
}
|
|
94
152
|
|
|
153
|
+
/** Search finds tickers and companies; a function, a dataset or a term is in the catalog, so an empty search points there. */
|
|
154
|
+
function catalogHint(query: string): string {
|
|
155
|
+
return `Search finds tickers and company names. Try: gloomberb catalog "${query.replace(/"/g, "")}"`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The notes beside the results: the index a bare root names, data the query
|
|
160
|
+
* asks for that no source carries yet, and for an empty search the catalog,
|
|
161
|
+
* unless a known gap already said where to look instead.
|
|
162
|
+
*/
|
|
163
|
+
function searchNotes(query: string, candidates: TickerSearchCandidate[]): string[] {
|
|
164
|
+
const gap = knownGapMessage(query);
|
|
165
|
+
return [indexRootNote(query, candidates), gap, candidates.length === 0 && !gap ? catalogHint(query) : null]
|
|
166
|
+
.filter((note): note is string => !!note);
|
|
167
|
+
}
|
|
168
|
+
|
|
95
169
|
export function buildSearchReport({
|
|
96
170
|
query,
|
|
97
171
|
candidates,
|
|
@@ -102,9 +176,11 @@ export function buildSearchReport({
|
|
|
102
176
|
const lines = [renderSection(`Search: ${query}`)];
|
|
103
177
|
const indexNote = indexRootNote(query, candidates);
|
|
104
178
|
if (indexNote) lines.push("", indexNote);
|
|
179
|
+
const gap = knownGapMessage(query);
|
|
180
|
+
if (gap) lines.push("", gap);
|
|
105
181
|
|
|
106
182
|
if (candidates.length === 0) {
|
|
107
|
-
lines.push(cliStyles.muted("No matches found."));
|
|
183
|
+
if (!gap) lines.push(cliStyles.muted("No matches found."), cliStyles.muted(catalogHint(query)));
|
|
108
184
|
return lines.join("\n");
|
|
109
185
|
}
|
|
110
186
|
|
|
@@ -131,10 +207,7 @@ export function buildSearchReport({
|
|
|
131
207
|
|| candidate.ticker?.metadata.exchange
|
|
132
208
|
|| candidate.right
|
|
133
209
|
|| "—",
|
|
134
|
-
candidate
|
|
135
|
-
|| candidate.result?.brokerContract?.secType
|
|
136
|
-
|| candidate.ticker?.metadata.assetCategory
|
|
137
|
-
|| "—",
|
|
210
|
+
searchType(candidate) ?? "—",
|
|
138
211
|
...(showSource ? [searchSourceLabel(candidate) || "—"] : []),
|
|
139
212
|
]),
|
|
140
213
|
));
|
|
@@ -160,11 +233,11 @@ export async function search(query: string, dependencies: SearchCommandDependenc
|
|
|
160
233
|
});
|
|
161
234
|
|
|
162
235
|
if (dependencies.printResult) {
|
|
163
|
-
const
|
|
236
|
+
const warnings = searchNotes(trimmedQuery, candidates);
|
|
164
237
|
dependencies.printResult({
|
|
165
238
|
data: searchCandidateRows(candidates),
|
|
166
239
|
metadata: { query: trimmedQuery },
|
|
167
|
-
warnings:
|
|
240
|
+
warnings: warnings.length ? warnings : undefined,
|
|
168
241
|
}, {
|
|
169
242
|
columns: [
|
|
170
243
|
{ key: "category", header: "Category" },
|