@gloom-sh/terminal 0.18.5 → 0.19.1
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +2 -2
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +8 -21
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +18 -3
- package/src/components/ui/icon.tsx +4 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +17 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +34 -12
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +5 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +10 -10
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +22 -4
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +48 -5
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -3,7 +3,7 @@ import { extname, isAbsolute, relative, resolve } from "path";
|
|
|
3
3
|
import type { CliCommandContext, HeadlessArgumentExpansion } from "../../types/plugin";
|
|
4
4
|
import type { MarketContext } from "../types";
|
|
5
5
|
import { withMarketData } from "../context";
|
|
6
|
-
import { cliStyles, renderStats, type CliStatEntry } from "../../utils/cli-output";
|
|
6
|
+
import { buildForFile, cliStyles, renderForFile, renderStats, type CliStatEntry } from "../../utils/cli-output";
|
|
7
7
|
import {
|
|
8
8
|
filterPaneCatalogEntries,
|
|
9
9
|
findFunctionCode,
|
|
@@ -35,13 +35,14 @@ import { withPersistedCloudSession } from "./cloud-session";
|
|
|
35
35
|
import {
|
|
36
36
|
ListingArgError, listingFateLoader, listingReadNotes, loadForListing, loadListingQuoteAnswer, quotedListingChoiceNotes, refuseMovedListing,
|
|
37
37
|
} from "../listing-arg";
|
|
38
|
-
import {
|
|
39
|
-
import { isCliFailure,
|
|
40
|
-
import { DATA_SERVICE_UNREACHABLE_MESSAGE } from "../../utils/network-failure";
|
|
38
|
+
import { notATickerStop } from "../company-name-hint";
|
|
39
|
+
import { CLI_ERROR_CODE, failUnreachable, failWithError, isCliFailure, unreachableError } from "../errors";
|
|
40
|
+
import { DATA_SERVICE_UNREACHABLE_MESSAGE, isNetworkFailure } from "../../utils/network-failure";
|
|
41
41
|
import { isTickerLinkPeer } from "../../layout/ticker-navigation";
|
|
42
42
|
import { knownGapMessage, commandPointer, commandPointerLeads, topicPointer } from "../known-gaps";
|
|
43
43
|
import { selectReportTables, type CliReportTables } from "../report-tables";
|
|
44
|
-
import { cliOutputWritten, type CliErrorObject } from "../result";
|
|
44
|
+
import { cliOutputWritten, printCliResult, serializeCliResult, type CliErrorObject } from "../result";
|
|
45
|
+
import { checkOutputPath, resolveOutputPath, writeOutputFile } from "../output-file";
|
|
45
46
|
import type { CatalogCommandHit } from "../unknown-command";
|
|
46
47
|
import { CLI_EXIT_CODES } from "../exit-codes";
|
|
47
48
|
import { shotOptionRefusal } from "./shot-options";
|
|
@@ -263,7 +264,7 @@ export function reportFailure(
|
|
|
263
264
|
const reasons = [...data.errors ?? [], ...typeof data.reason === "string" ? [data.reason] : []];
|
|
264
265
|
const unreached = reasons.filter((reason) => reason.includes(DATA_SERVICE_UNREACHABLE_MESSAGE));
|
|
265
266
|
if (unreached.length > 0 && (data.empty || data.unavailableSymbols.length > 0)) {
|
|
266
|
-
return
|
|
267
|
+
return unreachableError(`${asked}: ${unreached.join("; ")}.`.replace(/\.\.$/, "."));
|
|
267
268
|
}
|
|
268
269
|
if (!data.empty && !(noListing && noValues) && !(failsWithoutData && foundNothing(data))) return null;
|
|
269
270
|
const unavailable = data.unavailableSymbols.length > 0 ? `no data for ${data.unavailableSymbols.join(", ")}` : "no data";
|
|
@@ -276,6 +277,16 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
276
277
|
if (parsed.strict) {
|
|
277
278
|
throw new Error("--strict is an option of shot. fn takes --require-bot-safe to fail on a report that is not complete.");
|
|
278
279
|
}
|
|
280
|
+
// What only a capture draws is refused, never ignored: the report would print as if it had not been given.
|
|
281
|
+
if (parsed.shotOnly?.length) {
|
|
282
|
+
const one = parsed.shotOnly.length === 1;
|
|
283
|
+
const shot = ["gloom shot", resolved.token, parsed.arg.trim() && commandWord(parsed.arg.trim())].filter(Boolean).join(" ");
|
|
284
|
+
throw new Error(`${parsed.shotOnly.join(" and ")} ${one ? "is an option" : "are options"} of shot, which saves the pane as a PNG; fn prints the report. `
|
|
285
|
+
+ `Drop ${one ? "it" : "them"} from fn, or capture the pane with ${shot}.`);
|
|
286
|
+
}
|
|
287
|
+
// The file --output names, checked before the report loads so a path that cannot be written fails at once.
|
|
288
|
+
const outputPath = parsed.outputPath === null ? null : resolveOutputPath(parsed.outputPath);
|
|
289
|
+
if (outputPath) await checkOutputPath(outputPath);
|
|
279
290
|
if (parsed.requireBotSafe && (
|
|
280
291
|
!resolved.capability.botSafe || resolved.capability.reportReadiness !== "ready"
|
|
281
292
|
)) {
|
|
@@ -305,22 +316,25 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
305
316
|
: null;
|
|
306
317
|
const moved = { command: "fn", retry: (key: string) => `gloom fn ${resolved.token} ${key}` };
|
|
307
318
|
// A report that fails or comes back empty for an exchange the symbol is not listed on says so.
|
|
319
|
+
// A report written to a file is drawn for it as it is built: no colour and no terminal width, as a redirect gets it.
|
|
320
|
+
const buildReport = () => buildFunctionReport(resolved, context, parsed.arg);
|
|
308
321
|
const report = await refuseMovedListing(fate, moved, () => withPersistedCloudSession(
|
|
309
322
|
context,
|
|
310
323
|
() => loadForListing(
|
|
311
324
|
parsed.listing ?? [],
|
|
312
325
|
context,
|
|
313
326
|
ctx,
|
|
314
|
-
() =>
|
|
327
|
+
outputPath ? () => buildForFile(buildReport) : buildReport,
|
|
315
328
|
(built) => built.data.empty || !built.data.complete,
|
|
316
329
|
),
|
|
317
330
|
));
|
|
318
331
|
// A company's name typed where its ticker goes (`fn RDCF Palantir`) found nothing: the answer is `quote`'s,
|
|
319
|
-
// "Not a ticker: PALANTIR.
|
|
320
|
-
|
|
332
|
+
// "Not a ticker: PALANTIR. Did you mean PLTR (Palantir Technologies Inc.)?", not an empty report. A source that
|
|
333
|
+
// could not answer for the word (a timeout) says nothing of it, but when search finds the company it is that.
|
|
334
|
+
if (quoteAnswer && listing && (report.data.empty || report.data.unavailableSymbols.length > 0)) {
|
|
321
335
|
const { quote, error } = await quoteAnswer();
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
336
|
+
const stop = quote ? null : await notATickerStop(error, listing, context);
|
|
337
|
+
if (stop) ctx.fail(stop);
|
|
324
338
|
}
|
|
325
339
|
if (parsed.requireBotSafe && (report.data.empty || !report.data.complete)) {
|
|
326
340
|
const unavailable = report.data.unavailableSymbols.length > 0
|
|
@@ -347,10 +361,24 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
|
|
|
347
361
|
const failure = reportFailure(report.data, ["fn", resolved.token, parsed.arg.trim()].filter(Boolean).join(" "), {
|
|
348
362
|
failsWithoutData: resolved.headless?.failsWithoutData, tables: report.tables });
|
|
349
363
|
// The report states its own freshness in its data and text; JSON cites it as `source` too.
|
|
350
|
-
|
|
364
|
+
const result = { data, exportFreshness: report.data.freshness, ...(warnings.length > 0 ? { warnings } : {}), ...(failure ? { failure } : {}) };
|
|
365
|
+
const render = {
|
|
351
366
|
text: () => glossary ? `${report.text}\n\n${renderReportGlossary(resolved.token, glossary)}` : report.text,
|
|
352
367
|
...(tabular ? { tables: selectReportTables(report.tables, resolved.tableSection) } : {}),
|
|
353
|
-
}
|
|
368
|
+
};
|
|
369
|
+
if (outputPath) {
|
|
370
|
+
// A report with no data writes no file: a header alone would read as a screen that matched nothing. It fails
|
|
371
|
+
// as plain --json fails, with its code and whether to retry.
|
|
372
|
+
if (failure) failWithError({ ...failure, details: `Nothing was written to ${outputPath}.` });
|
|
373
|
+
await writeOutputFile(outputPath, `${renderForFile(() => serializeCliResult(result, ctx.cliOptions, render))}\n`);
|
|
374
|
+
// What was written and where; warnings go to stderr as they would beside printed rows, and JSON keeps its envelope.
|
|
375
|
+
const saved = { output: outputPath, format: ctx.cliOptions.format, rows: report.data.rowCount };
|
|
376
|
+
printCliResult({ data: saved, ...(warnings.length > 0 ? { warnings } : {}) },
|
|
377
|
+
{ ...ctx.cliOptions, format: ctx.cliOptions.format === "json" ? "json" : "text" },
|
|
378
|
+
{ text: () => `Saved ${outputPath}` });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
ctx.printResult(result, render);
|
|
354
382
|
if (failure) process.exitCode = CLI_EXIT_CODES.error;
|
|
355
383
|
}, { strictHeadlessOptions: true, tableSection: true });
|
|
356
384
|
});
|
|
@@ -437,6 +465,11 @@ async function capturePaneScreenshot(args: string[], ctx: CliCommandContext, onS
|
|
|
437
465
|
fitRows: parsed.heightGiven !== true,
|
|
438
466
|
options: parsed.options,
|
|
439
467
|
onStalledReport,
|
|
468
|
+
}).catch(async (error: unknown) => {
|
|
469
|
+
// A company's name typed where its ticker goes fails as `fn` words it, with the company search finds.
|
|
470
|
+
const { quote, error: quoteError } = parsed.listing ? await loadListingQuoteAnswer(context.dataProvider, parsed.listing) : { quote: null, error: undefined };
|
|
471
|
+
const stop = parsed.listing && !quote ? await notATickerStop(quoteError, parsed.listing, context) : null;
|
|
472
|
+
throw stop ? new Error(stop) : error;
|
|
440
473
|
});
|
|
441
474
|
if (parsed.requireBotSafe && !result.usable) {
|
|
442
475
|
throw new Error(
|
|
@@ -508,7 +541,8 @@ export async function runPaneFunctionHelp(command: string, args: string[], ctx:
|
|
|
508
541
|
const found = findPaneFunction(registry, target);
|
|
509
542
|
if (!found?.pane) return undefined;
|
|
510
543
|
const { template, pane } = found;
|
|
511
|
-
|
|
544
|
+
// A function typed by its code is helped even while it is unlisted.
|
|
545
|
+
const entries = await buildPaneCatalogEntries(registry, context, { unlisted: true });
|
|
512
546
|
// A pane named directly, where functions open it, is helped by the first of them.
|
|
513
547
|
return (template ? entries.find((candidate) => candidate.templateId === template.id) : undefined)
|
|
514
548
|
?? entries.find((candidate) => candidate.paneId === pane.id);
|
|
@@ -578,11 +612,15 @@ async function runPaneCliCommand(ctx: CliCommandContext, run: () => Promise<void
|
|
|
578
612
|
try {
|
|
579
613
|
await run();
|
|
580
614
|
} catch (error) {
|
|
581
|
-
// A failure with fields for --json (`ticker_moved`, its change and retry)
|
|
582
|
-
|
|
615
|
+
// A failure with fields for --json (`ticker_moved`, its change and retry), or a code of its own (`no_data`,
|
|
616
|
+
// `service_unreachable` and whether to retry), keeps them.
|
|
617
|
+
if (isCliFailure(error) && (error.fields || error.code !== CLI_ERROR_CODE)) throw error;
|
|
583
618
|
// A listing it cannot use says what to type instead; a name that opens nothing, what is close to it.
|
|
584
619
|
if (error instanceof ListingArgError || error instanceof UnknownPaneFunctionError) ctx.fail(error.message, error.details || undefined);
|
|
585
620
|
const message = error instanceof Error ? error.message : String(error);
|
|
621
|
+
// Nothing answered: said as every command says it, with what to check.
|
|
622
|
+
if (message.includes(DATA_SERVICE_UNREACHABLE_MESSAGE)) failUnreachable(message);
|
|
623
|
+
if (isNetworkFailure(error)) failUnreachable();
|
|
586
624
|
// A failure the command already worded keeps its advice line.
|
|
587
625
|
const stated = error instanceof Error ? (error as Error & { details?: unknown }).details : undefined;
|
|
588
626
|
const details = typeof stated === "string" ? stated : undefined;
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
nextRegularSessionOpen,
|
|
9
9
|
} from "../../market-data/market/freshness";
|
|
10
10
|
import { futuresWeekendClosure } from "../../market-data/market/futures-week";
|
|
11
|
-
import { currentFxClosureStart } from "../../utils/fx-market-hours";
|
|
11
|
+
import { currentFxClosureStart, isFxMarketOpen } from "../../utils/fx-market-hours";
|
|
12
12
|
import { zonedDateKey } from "../../utils/zoned-date-time";
|
|
13
13
|
import type { ReportTime } from "../../utils/utc-time";
|
|
14
14
|
import type { QuoteIndexSession } from "../../types/financials";
|
|
@@ -60,6 +60,10 @@ const US_EXCHANGE_TIME_ZONE = "America/New_York";
|
|
|
60
60
|
*/
|
|
61
61
|
function venueSession(exchange: string, marketState: string | undefined, now: number, calendar?: QuoteIndexSession): VenueSession | null {
|
|
62
62
|
if (isAlwaysOpenExchange(exchange)) return { state: "always" };
|
|
63
|
+
// Spot FX trades its week, Sunday 17:00 to Friday 17:00 New York, by the clock as an exchange rate's line has it.
|
|
64
|
+
// A source's own state is not its week: Yahoo stamps a pair CLOSED from that open to London's midnight, which read
|
|
65
|
+
// `markets closed` beside `fx`'s `markets open` for the same pair.
|
|
66
|
+
if (exchange === FX_VENUE) return { state: isFxMarketOpen(now) ? "open" : "closed" };
|
|
63
67
|
const next = nextRegularSessionOpen(exchange, now);
|
|
64
68
|
if (next) {
|
|
65
69
|
const { timeZone } = next;
|
|
@@ -125,10 +129,13 @@ function closeDateOf(observation: SessionObservation, session: VenueSession, now
|
|
|
125
129
|
const time = observation.time;
|
|
126
130
|
if (!time || time.dateOnly) return null;
|
|
127
131
|
// FX closes once a week, Friday 17:00 New York time, and its trading day turns
|
|
128
|
-
// at 17:00: a print from that last day,
|
|
129
|
-
// that Friday's close
|
|
132
|
+
// at 17:00: a print from that last day, after Thursday's 17:00 close through
|
|
133
|
+
// Friday's, is that Friday's close. A tick after it, over the weekend, is no
|
|
134
|
+
// price of the week and keeps its own time.
|
|
130
135
|
const weekClose = canonicalExchange(observation.exchange) === FX_VENUE ? currentFxClosureStart(now) : null;
|
|
131
|
-
if (weekClose != null)
|
|
136
|
+
if (weekClose != null) {
|
|
137
|
+
return time.time > weekClose - DAY_MS && time.time <= weekClose ? zonedDateKey(weekClose - 1, "America/New_York") : null;
|
|
138
|
+
}
|
|
132
139
|
if (session.last && session.timeZone) {
|
|
133
140
|
if (session.last.open != null && time.time < session.last.open) return null;
|
|
134
141
|
return zonedDateKey(time.time, session.timeZone) === session.last.date ? session.last.date : null;
|
|
@@ -151,6 +158,22 @@ function closeMarketOf(exchange: string, session: VenueSession): string | null {
|
|
|
151
158
|
return isUsListingExchange(exchange) ? "US" : exchange;
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
/**
|
|
162
|
+
* The market a venue's sessions belong to, as a report that mixes markets
|
|
163
|
+
* names it: `US` for the US listing venues, which share one calendar (and an
|
|
164
|
+
* index publisher's sessions dated in New York), `FX` for currency pairs, the
|
|
165
|
+
* venue's code for any other the app has a calendar of (`LSE`). Null for one
|
|
166
|
+
* known only by the state its source stamps (an index publisher dated
|
|
167
|
+
* elsewhere, a futures venue), whose open a report cannot name: its sessions
|
|
168
|
+
* are still dated by venue (`reportVenueSessionDates`), as they always were.
|
|
169
|
+
*/
|
|
170
|
+
function sessionMarketOf(exchange: string, session: VenueSession): string | null {
|
|
171
|
+
if (exchange === FX_VENUE) return "FX";
|
|
172
|
+
if (session.quoted) return session.timeZone === US_EXCHANGE_TIME_ZONE ? "US" : null;
|
|
173
|
+
if (!session.timeZone) return null;
|
|
174
|
+
return isUsListingExchange(exchange) ? "US" : exchange;
|
|
175
|
+
}
|
|
176
|
+
|
|
154
177
|
/** One market's session behind a report that mixes markets: its local date, and whether that session has closed. */
|
|
155
178
|
export interface VenueSessionDate {
|
|
156
179
|
/** `US` for the US listing venues, which share one calendar, the venue's code elsewhere (`XETRA`, `EPA`). */
|
|
@@ -192,6 +215,45 @@ export function reportVenueSessionDates(observations: readonly SessionObservatio
|
|
|
192
215
|
});
|
|
193
216
|
}
|
|
194
217
|
|
|
218
|
+
/** One market behind a report that mixes markets, and where it stands now (`reportVenueMarkets`). */
|
|
219
|
+
export interface VenueMarket extends ReportMarket {
|
|
220
|
+
/** The market as `sessionMarketOf` names it: `US`, `FX`, or the venue's code (`LSE`). */
|
|
221
|
+
market: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** The fewest and most markets a report names one by one; past that a line of them would not be read. */
|
|
225
|
+
const MIN_NAMED_MARKETS = 2;
|
|
226
|
+
const MAX_NAMED_MARKETS = 3;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Where each market behind a report that mixes two or three stands now, each
|
|
230
|
+
* with its own next open (`LSE` opens 08:00 UTC, `US` 13:30 UTC), in the
|
|
231
|
+
* order the markets first appear. Empty for one market, more than three, or
|
|
232
|
+
* any that cannot be placed or named (one trading around the clock among them,
|
|
233
|
+
* an index publisher with no calendar).
|
|
234
|
+
*/
|
|
235
|
+
export function reportVenueMarkets(observations: readonly SessionObservation[], now: number): VenueMarket[] {
|
|
236
|
+
const markets = new Map<string, VenueSession>();
|
|
237
|
+
for (const observation of observations) {
|
|
238
|
+
const exchange = canonicalExchange(observation.exchange);
|
|
239
|
+
if (!exchange) continue;
|
|
240
|
+
const session = venueSession(exchange, observation.marketState, now, observation.calendar);
|
|
241
|
+
const market = session && session.state !== "always" ? sessionMarketOf(exchange, session) : null;
|
|
242
|
+
if (!session || !market) return [];
|
|
243
|
+
if (!markets.has(market)) markets.set(market, session);
|
|
244
|
+
}
|
|
245
|
+
if (markets.size < MIN_NAMED_MARKETS || markets.size > MAX_NAMED_MARKETS) return [];
|
|
246
|
+
return [...markets].map(([market, session]) => ({
|
|
247
|
+
market,
|
|
248
|
+
state: session.state as ReportMarketState,
|
|
249
|
+
...(session.next && session.state !== "open" && session.state !== "after-hours" ? {
|
|
250
|
+
reopensAt: new Date(session.next.open).toISOString(),
|
|
251
|
+
reopensOn: session.next.date,
|
|
252
|
+
...(session.next.firstTrade ? { firstTrade: true as const } : {}),
|
|
253
|
+
} : {}),
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
195
257
|
/**
|
|
196
258
|
* Where the markets behind a quote report stand now, and whether the newest
|
|
197
259
|
* observation is a close, and whose. Null market when the venues disagree
|
|
@@ -53,6 +53,8 @@ export interface ParsedPaneFunctionArgs {
|
|
|
53
53
|
requireBotSafe: boolean;
|
|
54
54
|
/** `shot --strict`: exit with the unusable-shot code when the capture shows no usable data. */
|
|
55
55
|
strict?: boolean;
|
|
56
|
+
/** The options only `shot` reads (`--theme`, `--height`), as typed, which `fn` refuses rather than ignore. */
|
|
57
|
+
shotOnly?: string[];
|
|
56
58
|
/** The listing a one-ticker function's argument named, once `applyListingArgument` has resolved it. */
|
|
57
59
|
listing?: CliListing;
|
|
58
60
|
/**
|
|
@@ -112,10 +114,18 @@ export type OptionValueWords = (key: string) => number;
|
|
|
112
114
|
/** Words in the last value of a list (`TRY -20, EUR 5` ends with two); a decimal comma (`TRY -2,5`) separates nothing. */
|
|
113
115
|
const lastValueWords = (value: string) => value.split(/(?<!\d),|,(?!\d)/).at(-1)!.trim().split(/\s+/).filter(Boolean).length;
|
|
114
116
|
|
|
117
|
+
/**
|
|
118
|
+
* What only `shot` draws: the capture's height, theme, text scale, watermark
|
|
119
|
+
* and status line. `--width` is not one: on `fn` it is the CLI's table width.
|
|
120
|
+
*/
|
|
121
|
+
const SHOT_ONLY_KEYS = new Set(["height", "theme", "scale", "watermark"]);
|
|
122
|
+
|
|
115
123
|
export function parsePaneFunctionArgs(
|
|
116
124
|
args: string[],
|
|
117
125
|
globalOptions: { limit?: number; tail?: number } = {},
|
|
118
126
|
valueWords?: OptionValueWords,
|
|
127
|
+
/** Whether an option takes a list, so its flag typed again adds to it rather than replacing it. */
|
|
128
|
+
listOption?: (key: string) => boolean,
|
|
119
129
|
): ParsedPaneFunctionArgs {
|
|
120
130
|
const positionals: string[] = [];
|
|
121
131
|
const options: Record<string, string | true> = {};
|
|
@@ -131,6 +141,7 @@ export function parsePaneFunctionArgs(
|
|
|
131
141
|
let strict = false;
|
|
132
142
|
let requireBotSafe = false;
|
|
133
143
|
const clamped: ShotSizeClamp[] = [];
|
|
144
|
+
const shotOnly: string[] = [];
|
|
134
145
|
|
|
135
146
|
for (let index = 0; index < args.length; index += 1) {
|
|
136
147
|
const token = args[index]!;
|
|
@@ -151,6 +162,7 @@ export function parsePaneFunctionArgs(
|
|
|
151
162
|
// A switch: the token after it is the function's argument, never its value.
|
|
152
163
|
if (normalizeOptionKey(key) === "noStatus" && inlineValue === undefined) {
|
|
153
164
|
status = false;
|
|
165
|
+
shotOnly.push(`--${key}`);
|
|
154
166
|
continue;
|
|
155
167
|
}
|
|
156
168
|
if (normalizeOptionKey(key) === "explain" && inlineValue === undefined) {
|
|
@@ -165,6 +177,7 @@ export function parsePaneFunctionArgs(
|
|
|
165
177
|
const nextValue = inlineValue === undefined && next && !next.startsWith("--") ? args[++index] : true;
|
|
166
178
|
let value: string | true = inlineValue ?? nextValue ?? true;
|
|
167
179
|
const normalizedKey = normalizeOptionKey(key);
|
|
180
|
+
if (SHOT_ONLY_KEYS.has(normalizedKey)) shotOnly.push(`--${key}`);
|
|
168
181
|
// A value that is a pair (`--fx-shift TRY -20`) takes the words after it, and a list of them the words after a comma.
|
|
169
182
|
const words = value === true ? 1 : valueWords?.(normalizedKey) ?? 1;
|
|
170
183
|
if (words > 1 && value !== true) {
|
|
@@ -172,13 +185,17 @@ export function parsePaneFunctionArgs(
|
|
|
172
185
|
&& (lastValueWords(value) < words || value.trimEnd().endsWith(","))) {
|
|
173
186
|
value = `${value} ${args[++index]!}`;
|
|
174
187
|
}
|
|
188
|
+
}
|
|
189
|
+
// A list's flag typed again adds to it: `--metric trailingPE --metric dividendYieldPercent`.
|
|
190
|
+
if ((words > 1 || listOption?.(normalizedKey)) && value !== true) {
|
|
175
191
|
const earlier = options[normalizedKey];
|
|
176
192
|
if (typeof earlier === "string") value = `${earlier}, ${value}`;
|
|
177
193
|
}
|
|
178
194
|
if (normalizedKey === "requireBotSafe" || normalizedKey === "botSafe") {
|
|
179
195
|
requireBotSafe = value === true || String(value).toLowerCase() === "true";
|
|
180
196
|
} else if (normalizedKey === "output" || normalizedKey === "out" || normalizedKey === "o") {
|
|
181
|
-
|
|
197
|
+
// A flag with no path is kept as an empty one, which `fn` refuses rather than print as if it had not been given.
|
|
198
|
+
outputPath = value === true ? "" : value;
|
|
182
199
|
} else if ((normalizedKey === "width" || normalizedKey === "height") && value !== true) {
|
|
183
200
|
const size = parseShotSize(normalizedKey, value);
|
|
184
201
|
if (size.used !== size.requested) clamped.push(size);
|
|
@@ -214,6 +231,7 @@ export function parsePaneFunctionArgs(
|
|
|
214
231
|
...(heightGiven ? { heightGiven: true as const } : {}),
|
|
215
232
|
...(explain ? { explain } : {}),
|
|
216
233
|
...(strict ? { strict } : {}),
|
|
234
|
+
...(shotOnly.length > 0 ? { shotOnly } : {}),
|
|
217
235
|
};
|
|
218
236
|
}
|
|
219
237
|
|
|
@@ -19,12 +19,15 @@ import { splitSymbolArgs, stripStrayEdges } from "../symbol-args";
|
|
|
19
19
|
import { readNameEntries } from "../name-entries";
|
|
20
20
|
import { bankFigurePointer } from "../known-gaps";
|
|
21
21
|
import { readTerminalCodeArg } from "../../tickers/company-name-listing";
|
|
22
|
+
import { unknownBloombergMarket } from "../../tickers/terminal-codes";
|
|
23
|
+
import { notATickerWords } from "../company-name-hint";
|
|
22
24
|
import type { MarketContext } from "../types";
|
|
23
25
|
import {
|
|
24
26
|
buildPaneFunctionLookup,
|
|
25
27
|
buildTemplateContext,
|
|
26
28
|
findFunctionCode,
|
|
27
29
|
nearFunctionCodes,
|
|
30
|
+
paneFunctionCode,
|
|
28
31
|
suggestedFunctionCodes,
|
|
29
32
|
suggestPaneFunctions,
|
|
30
33
|
type NearFunctionCode,
|
|
@@ -142,8 +145,9 @@ export function parsePaneFunctionArgsFor(
|
|
|
142
145
|
const found = parsed.target ? findPaneFunction(registry, parsed.target) : null;
|
|
143
146
|
if (!found?.pane) return parsed;
|
|
144
147
|
const capability = getPaneFunctionCapability(found.template, found.pane);
|
|
145
|
-
if (!capability.options.some((option) => (option.valueWords ?? 1) > 1)) return parsed;
|
|
146
|
-
return parsePaneFunctionArgs(args, globalOptions, (key) => findCapabilityOption(capability, key)?.valueWords ?? 1
|
|
148
|
+
if (!capability.options.some((option) => (option.valueWords ?? 1) > 1 || option.list)) return parsed;
|
|
149
|
+
return parsePaneFunctionArgs(args, globalOptions, (key) => findCapabilityOption(capability, key)?.valueWords ?? 1,
|
|
150
|
+
(key) => findCapabilityOption(capability, key)?.list === true);
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
/**
|
|
@@ -168,11 +172,17 @@ export async function applyListingArgument(
|
|
|
168
172
|
const takesOneTicker = kind === "ticker";
|
|
169
173
|
const takesList = kind === "tickers" || kind === "symbol-list" || (!headless && kind === "ticker-list");
|
|
170
174
|
// A function with an exchange option of its own (DIST) keeps it.
|
|
171
|
-
|
|
172
|
-
if (!(takesOneTicker || takesList) || (takesOneTicker && /\s/.test(args.arg) && !await readTerminalCodeArg(context.dataProvider, args.arg))
|
|
173
|
-
|| getPaneFunctionCapability(found.template, found.pane).options.some((option) => option.key === "exchange")) {
|
|
175
|
+
if (!(takesOneTicker || takesList) || getPaneFunctionCapability(found.template, found.pane).options.some((option) => option.key === "exchange")) {
|
|
174
176
|
return args;
|
|
175
177
|
}
|
|
178
|
+
// A ticker with spaces is no listing, unless it is a terminal's code for one ("JPM US Equity"). A company's name
|
|
179
|
+
// typed there (`fn RDCF "Palantir Technologies"`) stops before a report of dashes is drawn, as `quote` words a
|
|
180
|
+
// name that is no ticker, with the company search finds for it.
|
|
181
|
+
if (takesOneTicker && /\s/.test(args.arg) && !await readTerminalCodeArg(context.dataProvider, args.arg)) {
|
|
182
|
+
// A line written as a code with a market no venue answers to ("AAPL GP") is refused as the resolver words it.
|
|
183
|
+
if (unknownBloombergMarket(args.arg)) await resolveCliListing(args.arg, undefined, context);
|
|
184
|
+
throw new ListingArgError(await notATickerWords(args.arg, context));
|
|
185
|
+
}
|
|
176
186
|
const exchange = typeof args.options.exchange === "string" ? args.options.exchange : undefined;
|
|
177
187
|
const { exchange: _exchange, ...options } = args.options;
|
|
178
188
|
// A list entry with spaces, or an argument of words, is a company's name search finds, or the symbols it holds.
|
|
@@ -225,9 +235,10 @@ export async function applyListingArgument(
|
|
|
225
235
|
if (refusals.length > 0) {
|
|
226
236
|
listings = listings.filter((listing) => !refusals.some((entry) => entry.listing === listing));
|
|
227
237
|
if (listings.length === 0) {
|
|
228
|
-
|
|
238
|
+
const details = refusals.flatMap(({ refusal }) => refusal.details ? [refusal.details] : []).join("\n");
|
|
239
|
+
throw new ListingArgError(refusals.map(({ refusal }) => refusal.message).join("\n"), details || undefined);
|
|
229
240
|
}
|
|
230
|
-
notes.push(...refusals.map(({ refusal }) =>
|
|
241
|
+
notes.push(...refusals.map(({ refusal }) => [refusal.message, refusal.details].filter(Boolean).join(" ")));
|
|
231
242
|
}
|
|
232
243
|
const usOnly = headless?.usListingsOnly;
|
|
233
244
|
const abroad = usOnly ? listings.filter((listing) => listingAbroadVenue(listing.key) != null) : [];
|
|
@@ -348,7 +359,7 @@ export async function resolvePaneFunction(
|
|
|
348
359
|
tableSection = taken.tableSection;
|
|
349
360
|
}
|
|
350
361
|
// What the user types for the function, which every error about it names.
|
|
351
|
-
const token = template
|
|
362
|
+
const token = paneFunctionCode(template, pane);
|
|
352
363
|
// A word after a function that takes none is most often one of its option values (`fn JODI gas`).
|
|
353
364
|
// Every function the catalog lists with no argument refuses one, a rendered view (HALT) as a report (CBR).
|
|
354
365
|
// The catalog lists templates and the panes no template opens (HELP); a pane opened by its id stands for its templates.
|
|
@@ -4,6 +4,7 @@ import type { DesktopPaneShotPayload, DesktopPaneShotRenderResult } from "../des
|
|
|
4
4
|
import type { ResolvedPaneFunction } from "./resolver";
|
|
5
5
|
import type { RemoteUiNodeSnapshot } from "../../remote/types";
|
|
6
6
|
import { accessGateSentence, accessGateStatus } from "./access-gate";
|
|
7
|
+
import { isDataPaneForDomFallback } from "./capabilities";
|
|
7
8
|
import { paneEvidenceMismatches, paneScreenshotEvidenceHook } from "./screenshot-evidence";
|
|
8
9
|
import { collectShotSymbols } from "./data";
|
|
9
10
|
import { renderedReportNotices } from "./report-notices";
|
|
@@ -21,10 +22,9 @@ import {
|
|
|
21
22
|
import { shotExpectedChart, chartEvidenceMismatchesFor, intradayChartEvidenceMismatchesFor } from "./screenshot-chart-evidence";
|
|
22
23
|
|
|
23
24
|
export function isPaneScreenshotUsable(signals: PaneScreenshotReadinessSignals): boolean {
|
|
24
|
-
return signals.rowCount > 0
|
|
25
|
+
return (signals.interactive || (signals.rowCount > 0 && !signals.emptyStateDetected))
|
|
25
26
|
&& !signals.loadingStateDetected
|
|
26
27
|
&& !signals.errorStateDetected
|
|
27
|
-
&& !signals.emptyStateDetected
|
|
28
28
|
&& signals.complete
|
|
29
29
|
&& !signals.semanticMismatch
|
|
30
30
|
&& (!signals.requiresStructuredDataEvidence || signals.hasStructuredDataEvidence);
|
|
@@ -111,12 +111,14 @@ export function assessPaneScreenshot(
|
|
|
111
111
|
const semanticMismatch = missingExpectedText.length > 0
|
|
112
112
|
|| missingExpectedSelections.length > 0
|
|
113
113
|
|| chartEvidenceMismatches.length > 0;
|
|
114
|
+
// Layouts and the other panes that are surfaces to operate have no rows to count: a capture of one is not empty for that.
|
|
115
|
+
const interactive = !isDataPaneForDomFallback(resolved.pane);
|
|
114
116
|
const empty = render.loadingStateDetected
|
|
115
117
|
|| render.errorStateDetected
|
|
116
|
-
|| render.emptyStateDetected
|
|
117
|
-
|| rowCount === 0
|
|
118
|
+
|| (!interactive && (render.emptyStateDetected || rowCount === 0))
|
|
118
119
|
|| semanticMismatch;
|
|
119
120
|
const usable = isPaneScreenshotUsable({
|
|
121
|
+
interactive,
|
|
120
122
|
rowCount,
|
|
121
123
|
loadingStateDetected: render.loadingStateDetected,
|
|
122
124
|
errorStateDetected: render.errorStateDetected,
|
|
@@ -108,6 +108,11 @@ export type PaneScreenshotDataEvidence =
|
|
|
108
108
|
| PaneScreenshotEvidence;
|
|
109
109
|
|
|
110
110
|
export interface PaneScreenshotReadinessSignals {
|
|
111
|
+
/**
|
|
112
|
+
* A surface to operate rather than data to read (Layouts, Brokers): its rows
|
|
113
|
+
* and an empty-state marker inside it say nothing of whether it rendered.
|
|
114
|
+
*/
|
|
115
|
+
interactive?: boolean;
|
|
111
116
|
rowCount: number;
|
|
112
117
|
loadingStateDetected: boolean;
|
|
113
118
|
errorStateDetected: boolean;
|
package/src/cli/quote-answer.ts
CHANGED
|
@@ -14,8 +14,8 @@ import { knownGapForTicker } from "./known-gaps";
|
|
|
14
14
|
import {
|
|
15
15
|
futuresTryLine,
|
|
16
16
|
isNoProviderError,
|
|
17
|
+
isUnsettledNoProvider,
|
|
17
18
|
listingChoiceNotes,
|
|
18
|
-
listingReadNotes,
|
|
19
19
|
ListingArgError,
|
|
20
20
|
loadBareVenues,
|
|
21
21
|
notTradedMessage,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type ListingVenue,
|
|
30
30
|
} from "./listing-arg";
|
|
31
31
|
import { withListingSuggestion } from "./not-a-ticker";
|
|
32
|
+
import { notATickerWords } from "./company-name-hint";
|
|
32
33
|
import { isNetworkFailure } from "../utils/network-failure";
|
|
33
34
|
import type { MarketContext } from "./types";
|
|
34
35
|
|
|
@@ -85,8 +86,8 @@ export async function answerQuote(
|
|
|
85
86
|
const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
|
|
86
87
|
? [indexRootAlsoLine(root, request.command)] : [];
|
|
87
88
|
if (result.quote) {
|
|
88
|
-
// "CAR = Avis Budget Group, NASDAQ (
|
|
89
|
-
const choice =
|
|
89
|
+
// "CAR = Avis Budget Group, NASDAQ (different companies on XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
|
|
90
|
+
const choice = await listingChoiceNotes(listing, market, result.quote, venues);
|
|
90
91
|
const venue = choice.length > 0 ? null : bareListingNote(listing, result.quote);
|
|
91
92
|
// A row saved as 247540:KRX is quoted from KOSDAQ, the board it trades on.
|
|
92
93
|
const board = tradingBoardNote(listing, result.quote);
|
|
@@ -132,8 +133,12 @@ export async function answerQuote(
|
|
|
132
133
|
* A company's name typed as one word (`Apple`) that the source says is not a
|
|
133
134
|
* ticker, answered as the listing search names for it with the line saying so
|
|
134
135
|
* ("Read "Apple" as AAPL:NASDAQ (Apple Inc.)."), as a name of several words
|
|
135
|
-
* is.
|
|
136
|
-
*
|
|
136
|
+
* is. A source that could not answer for the word (a timeout, a rate limit)
|
|
137
|
+
* says nothing of it, but when search resolves it to a company, it is read the
|
|
138
|
+
* same way; when that listing has no quote either, the row says the word is not
|
|
139
|
+
* a ticker and names the company. Null when the word names no company, or that
|
|
140
|
+
* listing has no quote though the source did say "not a ticker": the word keeps
|
|
141
|
+
* its own answer.
|
|
137
142
|
*/
|
|
138
143
|
async function answerCompanyName(
|
|
139
144
|
result: QuoteBatchResult,
|
|
@@ -143,18 +148,24 @@ async function answerCompanyName(
|
|
|
143
148
|
refresh: boolean,
|
|
144
149
|
datesChangeBasis: boolean,
|
|
145
150
|
): Promise<QuoteAnswer | null> {
|
|
146
|
-
if (!listing.typed || listing.exchange
|
|
151
|
+
if (!listing.typed || listing.exchange) return null;
|
|
152
|
+
const notATicker = !!notATickerSymbol(errorMessage(result.error) ?? "");
|
|
153
|
+
const unsettled = !notATicker && isUnsettledNoProvider(result.error);
|
|
154
|
+
if (!notATicker && !unsettled) return null;
|
|
147
155
|
const named = await oneWordCompanyName(listing.typed, market);
|
|
148
|
-
|
|
156
|
+
// A real ticker a source failed to quote is not a company's name, even when the company carries the word.
|
|
157
|
+
if (!named || (unsettled && named.symbol.toUpperCase() === listing.symbol.toUpperCase())) return null;
|
|
149
158
|
// A listing on a venue the app cannot read is no answer: the word keeps its own "Not a ticker".
|
|
150
159
|
const resolved = await resolveCliListing(named.key, null, market).catch((error: unknown) => {
|
|
151
160
|
if (error instanceof ListingArgError) return null;
|
|
152
161
|
throw error;
|
|
153
162
|
});
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
const found = resolved ? { ...resolved, readAs: named.readAs, ...named.meant ? { meant: named.meant } : {}, ...named.readFromName ? { readFromName: named.readFromName } : {} } : null;
|
|
164
|
+
const [quoted] = found ? await market.dataProvider.getQuotesBatch([found.request], { forceRefresh: refresh, revalidate: true, datesChangeBasis }) : [];
|
|
165
|
+
if (found && quoted?.quote) return answerQuote(quoted, found, market, request, refresh, null, { datesChangeBasis });
|
|
166
|
+
if (!unsettled) return null;
|
|
167
|
+
const sentence = await notATickerWords(listing.typed, market);
|
|
168
|
+
return { result: { ...result, error: new Error(sentence) }, listing, notes: [] };
|
|
158
169
|
}
|
|
159
170
|
|
|
160
171
|
/** A listing a command found a quote for, with what `quote` says beside it. */
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { AppResourceStorePort } from "../core/app-service-ports";
|
|
2
|
+
import type { JsonValue } from "../data/sqlite/json";
|
|
3
|
+
import type { PersistedResourceValue } from "../types/persistence";
|
|
4
|
+
import type { PluginPersistence } from "../types/plugin";
|
|
5
|
+
import { logFailure } from "../utils/log-failure";
|
|
6
|
+
|
|
7
|
+
type SetResourceOptions = Parameters<PluginPersistence["setResource"]>[3];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A plugin's persistence in a command: what the command fetches through a
|
|
11
|
+
* plugin's cache is saved in the profile, under the namespace the app keeps
|
|
12
|
+
* it in (`plugin:<state id>`), so a later run whose refresh fails answers
|
|
13
|
+
* from that saved copy as the app does. Only a read that may take an expired
|
|
14
|
+
* copy (`allowExpired`, what a cache falls back to when its refresh fails)
|
|
15
|
+
* reaches the saved copies; any other read sees only what this run fetched,
|
|
16
|
+
* so a command still asks for current data rather than answering from the
|
|
17
|
+
* app's cache. Plugin state is not read or kept. With no resource store,
|
|
18
|
+
* nothing outlives the run.
|
|
19
|
+
*
|
|
20
|
+
* The profile's store closes when the command ends, while a plugin may keep
|
|
21
|
+
* the persistence it was set up with: `release()` leaves the store alone from
|
|
22
|
+
* then on, and a store that fails (closed, full) costs the saved copy only,
|
|
23
|
+
* never the command.
|
|
24
|
+
*/
|
|
25
|
+
export function createCommandPluginPersistence(
|
|
26
|
+
resources: AppResourceStorePort | null | undefined,
|
|
27
|
+
namespace: string,
|
|
28
|
+
): PluginPersistence & { release(): void } {
|
|
29
|
+
const fetched = new Map<string, PersistedResourceValue<unknown>>();
|
|
30
|
+
const keyOf = (kind: string, key: string) => `${kind}:${key}`;
|
|
31
|
+
let store = resources ?? null;
|
|
32
|
+
const saved = <T>(operation: string, run: (store: AppResourceStorePort) => T, fallback: T): T => {
|
|
33
|
+
if (!store) return fallback;
|
|
34
|
+
try {
|
|
35
|
+
return run(store);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return logFailure("cli.saved-copies", `${operation} a saved copy failed`, fallback)(error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
release: () => { store = null; },
|
|
42
|
+
getState: () => null,
|
|
43
|
+
setState: () => {},
|
|
44
|
+
deleteState: () => {},
|
|
45
|
+
getResource: <T = unknown>(kind: string, key: string, options?: { sourceKey?: string; schemaVersion?: number; allowExpired?: boolean }) => {
|
|
46
|
+
const own = fetched.get(keyOf(kind, key)) as PersistedResourceValue<T> | undefined;
|
|
47
|
+
if (own || !options?.allowExpired) return own ?? null;
|
|
48
|
+
const record = saved("Reading", (resources) => resources.get<T>(
|
|
49
|
+
{ namespace, kind, entityKey: key, sourceKey: options.sourceKey },
|
|
50
|
+
{ schemaVersion: options.schemaVersion, allowExpired: true },
|
|
51
|
+
), null);
|
|
52
|
+
return record && {
|
|
53
|
+
value: record.value,
|
|
54
|
+
fetchedAt: record.fetchedAt,
|
|
55
|
+
staleAt: record.staleAt,
|
|
56
|
+
expiresAt: record.expiresAt,
|
|
57
|
+
sourceKey: record.sourceKey,
|
|
58
|
+
schemaVersion: record.schemaVersion,
|
|
59
|
+
provenance: record.provenance,
|
|
60
|
+
stale: record.stale,
|
|
61
|
+
expired: record.expired,
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
setResource: <T = unknown>(kind: string, key: string, value: T, options: SetResourceOptions) => {
|
|
65
|
+
const fetchedAt = Date.now();
|
|
66
|
+
const entry: PersistedResourceValue<T> = {
|
|
67
|
+
value,
|
|
68
|
+
fetchedAt,
|
|
69
|
+
staleAt: fetchedAt + options.cachePolicy.staleMs,
|
|
70
|
+
expiresAt: fetchedAt + options.cachePolicy.expireMs,
|
|
71
|
+
sourceKey: options.sourceKey ?? "",
|
|
72
|
+
schemaVersion: options.schemaVersion ?? 1,
|
|
73
|
+
provenance: options.provenance ?? null,
|
|
74
|
+
};
|
|
75
|
+
fetched.set(keyOf(kind, key), entry);
|
|
76
|
+
saved("Writing", (resources) => resources.set<T>({ namespace, kind, entityKey: key, sourceKey: options.sourceKey }, value, {
|
|
77
|
+
cachePolicy: options.cachePolicy,
|
|
78
|
+
schemaVersion: options.schemaVersion,
|
|
79
|
+
provenance: options.provenance as JsonValue | null | undefined,
|
|
80
|
+
}), undefined);
|
|
81
|
+
return entry;
|
|
82
|
+
},
|
|
83
|
+
deleteResource: (kind: string, key: string, options?: { sourceKey?: string }) => {
|
|
84
|
+
fetched.delete(keyOf(kind, key));
|
|
85
|
+
saved("Deleting", (resources) => resources.delete({ namespace, kind, entityKey: key, sourceKey: options?.sourceKey }), undefined);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -85,7 +85,8 @@ export interface CliTicker {
|
|
|
85
85
|
/**
|
|
86
86
|
* A bare coin ticker (ETH) read as a listing that is not the coin, such as a
|
|
87
87
|
* fund named for it: "ETH is Grayscale Ethereum Mini Trust ETF, not ethereum.
|
|
88
|
-
* For ethereum use ETH-USD."
|
|
88
|
+
* For ethereum use ETH-USD." A live company that only shares the letters gets
|
|
89
|
+
* "Also a coin: LINK-USD" (`notTheCoinNote`). It is also the last of `notes`.
|
|
89
90
|
*/
|
|
90
91
|
coinNote: string | null;
|
|
91
92
|
/**
|
|
@@ -367,7 +368,7 @@ async function describeResolution(
|
|
|
367
368
|
const formerIssuer = describeFormerIssuer(listing.symbol, payload, current);
|
|
368
369
|
// One line: a company that left the symbol, else one that merged away from it.
|
|
369
370
|
const line = tickerChangeLine(change) ?? formerIssuer?.note;
|
|
370
|
-
const coinNote = listing.exchange ? null : savedListingCoinNote(listing.symbol, resolved.ticker);
|
|
371
|
+
const coinNote = listing.exchange ? null : savedListingCoinNote(listing.symbol, resolved.ticker, { ended: !!resolved.ended });
|
|
371
372
|
return { ...resolved, notes: [...line ? [line] : [], ...resolved.notes, ...coinNote ? [coinNote] : []], change, formerIssuer, coinNote,
|
|
372
373
|
readAs: listing.readAs ?? null, bareSymbol: listing.exchange ? null : listing.symbol, otherVenues: resolved.otherVenues ?? null };
|
|
373
374
|
}
|