@gloom-sh/terminal 0.19.1 → 0.19.3
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 +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -42,6 +42,15 @@ export async function noListingCalendarNote(
|
|
|
42
42
|
return `${lead}; ADR date ${isoDate(event.earningsDate)} (estimate)`;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* "BG, FPI: no earnings date found." for the listings at home a calendar came
|
|
47
|
+
* back without, each named once; null when it dated them all.
|
|
48
|
+
*/
|
|
49
|
+
export function undatedListingsNote(listings: readonly Pick<CliListing, "key">[]): string | null {
|
|
50
|
+
const keys = [...new Set(listings.map((listing) => listing.key))];
|
|
51
|
+
return keys.length > 0 ? `${keys.join(", ")}: no earnings date found.` : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
/**
|
|
46
55
|
* "SAN = Banco Santander, NYSE (different companies on EPA, LSE)" for a bare symbol whose
|
|
47
56
|
* calendar date is its US line's, when several companies list the symbol:
|
package/src/cli/entry.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import "./opentui-libc";
|
|
2
2
|
import { inferCliErrorOptions, printCliError } from "./errors";
|
|
3
3
|
import { isCliHelpFlag } from "./options";
|
|
4
|
-
import { plainByDefault, setCliPlainOverride } from "../utils/cli-output";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { cliAsciiEnabled, plainByDefault, setCliPlainOverride } from "../utils/cli-output";
|
|
5
|
+
import { localeMayNotReadUtf8 } from "../utils/ascii-text";
|
|
6
|
+
import { writeAsciiWhile } from "./ascii-output";
|
|
7
7
|
import { measurePerfAsync } from "../utils/perf-marks";
|
|
8
|
-
import { flushCrashReports, reportCrash } from "../telemetry/crash-reports";
|
|
9
|
-
import { CRASH_REPORT_EXIT_FLUSH_MS } from "../telemetry/crash-reports-node";
|
|
10
8
|
import type { CliLaunchRequest } from "../types/plugin";
|
|
11
9
|
import { detectUpdateAction } from "../updater";
|
|
12
10
|
import { linkStandaloneCommand } from "../updater/command-names";
|
|
@@ -24,6 +22,8 @@ import {
|
|
|
24
22
|
async function launchOpenTuiApp(cliLaunchRequest: CliLaunchRequest | null = null): Promise<void> {
|
|
25
23
|
// Before anything in the app can reach for the render library, so a temp
|
|
26
24
|
// folder that cannot run it is worked around (or named) first.
|
|
25
|
+
const { restoreExtractedPlugins } = await import("./restore-plugins");
|
|
26
|
+
const { loadExternalPlugins } = await import("../plugins/loader");
|
|
27
27
|
const { loadRenderLib } = await import("../renderers/opentui/render-lib");
|
|
28
28
|
await measurePerfAsync("startup.opentui.render-lib", loadRenderLib);
|
|
29
29
|
const { startOpenTuiApp } = await import("../renderers/opentui/start");
|
|
@@ -36,6 +36,8 @@ async function launchOpenTuiApp(cliLaunchRequest: CliLaunchRequest | null = null
|
|
|
36
36
|
} catch (error) {
|
|
37
37
|
// A failed launch still prints and exits as before; the report is only
|
|
38
38
|
// sent when the config was read far enough to install the reporter.
|
|
39
|
+
const { flushCrashReports, reportCrash } = await import("../telemetry/crash-reports");
|
|
40
|
+
const { CRASH_REPORT_EXIT_FLUSH_MS } = await import("../telemetry/crash-reports-node");
|
|
39
41
|
reportCrash(error, { kind: "uncaught" });
|
|
40
42
|
await flushCrashReports({ timeoutMs: CRASH_REPORT_EXIT_FLUSH_MS });
|
|
41
43
|
throw error;
|
|
@@ -74,21 +76,25 @@ export async function runCliEntrypoint(rawArgs = process.argv.slice(2)): Promise
|
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
//
|
|
78
|
-
// their modules are a noticeable part of what it would load before drawing.
|
|
79
|
-
const { dispatchCli, failUnknownCliCommand } = await import("./index");
|
|
80
|
-
// For a screen reader, a pipe or NO_COLOR: no rule lines, arrows in words, fixed table columns. Set
|
|
81
|
-
// for this process only; `--plain` / `--no-plain` decide it in dispatchCli, and commands run in-process keep theirs.
|
|
79
|
+
// For a screen reader, a pipe or NO_COLOR: no rule lines, arrows in words, fixed table columns.
|
|
82
80
|
setCliPlainOverride(plainByDefault());
|
|
83
|
-
|
|
84
|
-
const
|
|
81
|
+
if (inferCliErrorOptions(rawArgs).format === "text" && localeMayNotReadUtf8()) writeAsciiWhile(cliAsciiEnabled);
|
|
82
|
+
const { dispatchEarlyCommand } = await import("./early-command");
|
|
83
|
+
if (await dispatchEarlyCommand(rawArgs)) return;
|
|
84
|
+
|
|
85
|
+
const { dispatchCli, failUnknownCliCommand } = await import("./index");
|
|
86
|
+
let plugins: Promise<import("../plugins/loader").LoadedExternalPlugin[]> | undefined;
|
|
87
|
+
const options = {
|
|
88
|
+
loadExternalPlugins: () => plugins ??= import("../plugins/loader").then(({ loadExternalPlugins }) => loadExternalPlugins()),
|
|
89
|
+
};
|
|
90
|
+
const dispatchResult = await dispatchCli(rawArgs, options);
|
|
85
91
|
if (dispatchResult.kind === "handled") return;
|
|
86
92
|
if (dispatchResult.kind === "launch-ui") {
|
|
87
93
|
await launchOpenTuiApp(dispatchResult.request);
|
|
88
94
|
return;
|
|
89
95
|
}
|
|
90
96
|
|
|
91
|
-
await failUnknownCliCommand(rawArgs,
|
|
97
|
+
await failUnknownCliCommand(rawArgs, options);
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
runCliEntrypoint().catch((error) => {
|
package/src/cli/errors.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AppPersistence } from "../data/app-persistence";
|
|
2
|
-
import { ApiRequestError } from "../api-client/errors";
|
|
2
|
+
import { ApiRequestError, wasAskedTwice } from "../api-client/errors";
|
|
3
3
|
import { DEFAULT_CLI_OPTIONS, type CliGlobalOptions } from "./options";
|
|
4
4
|
import { serializeCliError, type CliErrorObject } from "./result";
|
|
5
5
|
import { cliStyles, cliTerminalWidth, wrapText } from "../utils/cli-output";
|
|
6
6
|
import { withSearchHints, type FredSeriesCheck } from "./not-a-ticker";
|
|
7
|
-
import { isProviderBusy, SEARCH_BUSY_CODE, SEARCH_BUSY_MESSAGE } from "../sources/provider-errors";
|
|
7
|
+
import { isProviderBusy, isSourceFailure, SEARCH_BUSY_CODE, SEARCH_BUSY_MESSAGE, serviceDidNotAnswerMessage } from "../sources/provider-errors";
|
|
8
8
|
import { DATA_SERVICE_UNREACHABLE_MESSAGE, isNetworkFailure } from "../utils/network-failure";
|
|
9
9
|
|
|
10
10
|
const USAGE_PREFIX = "Usage: ";
|
|
@@ -14,6 +14,8 @@ export const CLI_ERROR_CODE = "cli_error";
|
|
|
14
14
|
export const NO_DATA_CODE = "no_data";
|
|
15
15
|
/** The code of a command whose requests never reached the data service. */
|
|
16
16
|
const SERVICE_UNREACHABLE_CODE = "service_unreachable";
|
|
17
|
+
/** The code of a command whose data no source could serve just now, one having failed rather than had none. */
|
|
18
|
+
const SOURCE_UNAVAILABLE_CODE = "source_unavailable";
|
|
17
19
|
|
|
18
20
|
/** What to check when nothing answered: the connection, and the proxy when one is set. */
|
|
19
21
|
function unreachableHint(): string {
|
|
@@ -121,6 +123,14 @@ function cliErrorObject(error: unknown): CliErrorObject {
|
|
|
121
123
|
if (isNetworkFailure(error)) {
|
|
122
124
|
return unreachableError();
|
|
123
125
|
}
|
|
126
|
+
// A source failed (a timeout, a rate limit) where none had the data to answer: asked again it may.
|
|
127
|
+
if (isSourceFailure(error)) {
|
|
128
|
+
return { code: SOURCE_UNAVAILABLE_CODE, message: error instanceof Error ? error.message : String(error), retryable: true };
|
|
129
|
+
}
|
|
130
|
+
// A listing read the data service failed to answer twice: said in Gloom's words. Any other 5xx keeps the server's.
|
|
131
|
+
if (wasAskedTwice(error)) {
|
|
132
|
+
return { code: SOURCE_UNAVAILABLE_CODE, message: serviceDidNotAnswerMessage(), retryable: true };
|
|
133
|
+
}
|
|
124
134
|
// Gloom Cloud refused what was asked (an unknown series, a bad date): the input was wrong, not the program.
|
|
125
135
|
if (error instanceof ApiRequestError && error.status === 400) {
|
|
126
136
|
return { code: CLI_ERROR_CODE, message: error.message };
|
package/src/cli/glossary.ts
CHANGED
|
@@ -404,7 +404,8 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
|
|
|
404
404
|
},
|
|
405
405
|
{
|
|
406
406
|
term: "Net debt", aliases: ["net debt/ebitda", "net debt to ebitda", "net leverage"], functions: ["FA"], shown: ["Net Debt"],
|
|
407
|
-
definition: "Total debt less cash and short-term investments
|
|
407
|
+
definition: "Total debt less cash and short-term investments, with the balance sheet's lease liabilities under IFRS (\"incl. leases\"); "
|
|
408
|
+
+ "a company reporting under US GAAP (a 10-K, or a 20-F such as Alibaba's) keeps its operating leases out (\"excl. op. leases\"). Negative means more cash than debt. "
|
|
408
409
|
+ "Net debt / EBITDA is how many years of EBITDA would repay it; FA divides by a year of EBITDA, the trailing four quarters in a quarterly column.",
|
|
409
410
|
},
|
|
410
411
|
{
|
package/src/cli/help.ts
CHANGED
|
@@ -54,7 +54,7 @@ const GLOBAL_OPTIONS: Array<[string, string]> = [
|
|
|
54
54
|
["-q, --quiet", "Print no results or errors in text mode, for commands that print results"],
|
|
55
55
|
["--color, --no-color", "Force or turn off colors (NO_COLOR also turns them off)"],
|
|
56
56
|
["--plain, --no-plain", "Plain text for a screen reader or large text: no rule lines, arrows as words, every table column kept with a dash in empty cells. On by default when NO_COLOR is set or output is piped; --no-plain turns it off"],
|
|
57
|
-
["--width <n>", "Fit tables to n columns (default: the terminal width; piped output is not fitted). shot takes its own --width in pixels"],
|
|
57
|
+
["--width <n>", "Fit tables to n columns (default: the terminal width; piped output fits COLUMNS when set, else is not fitted). shot takes its own --width in pixels"],
|
|
58
58
|
];
|
|
59
59
|
|
|
60
60
|
const EXIT_CODES: Array<[string, string]> = [
|
|
@@ -21,7 +21,7 @@ export interface HistoryListRow extends HistoryRow {
|
|
|
21
21
|
type ExportedRow = Record<string, unknown>;
|
|
22
22
|
|
|
23
23
|
/** A bar's mark as an export writes it: true or false on a bar, nothing on a symbol's row with no bar. */
|
|
24
|
-
function barMark(key: "partial" | "filled" | "unsettled") {
|
|
24
|
+
function barMark(key: "partial" | "filled" | "unsettled" | "roll" | "thin") {
|
|
25
25
|
return (row: ExportedRow) => row.date ? row[key] === true : "";
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -32,7 +32,7 @@ function barMark(key: "partial" | "filled" | "unsettled") {
|
|
|
32
32
|
* whether or not any bar of this run has one, so files of different symbols
|
|
33
33
|
* or days line up when joined.
|
|
34
34
|
*/
|
|
35
|
-
|
|
35
|
+
const HISTORY_EXPORT_COLUMNS: CliResultColumn<ExportedRow>[] = [
|
|
36
36
|
{ key: "date", header: "Date" },
|
|
37
37
|
{ key: "open", header: "Open" },
|
|
38
38
|
{ key: "high", header: "High" },
|
|
@@ -47,8 +47,28 @@ export const HISTORY_EXPORT_COLUMNS: CliResultColumn<ExportedRow>[] = [
|
|
|
47
47
|
{ key: "volumeNote", header: "Volume note" },
|
|
48
48
|
{ key: "venueVolume", header: "Venue volume" },
|
|
49
49
|
{ key: "volumeVenue", header: "Volume venue" },
|
|
50
|
+
{ key: "volumeBasis", header: "Volume basis" },
|
|
50
51
|
];
|
|
51
52
|
|
|
53
|
+
/** A continuous future's contract per bar, the bar it rolled at, and a bar that traded far below those around it. */
|
|
54
|
+
const CONTRACT_EXPORT_COLUMNS: CliResultColumn<ExportedRow>[] = [
|
|
55
|
+
{ key: "contract", header: "Contract" },
|
|
56
|
+
{ key: "roll", header: "Roll", value: barMark("roll") },
|
|
57
|
+
{ key: "thin", header: "Thin", value: barMark("thin") },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The columns of an export of these rows: HISTORY_EXPORT_COLUMNS, and when a
|
|
62
|
+
* bar names its contract (a continuous future such as NG=F) Contract, Roll and
|
|
63
|
+
* Thin after them, for every row of the export. A stock's or a coin's file is
|
|
64
|
+
* as it always was.
|
|
65
|
+
*/
|
|
66
|
+
export function historyExportColumns(rows: readonly Pick<HistoryRow, "contract">[]): CliResultColumn<ExportedRow>[] {
|
|
67
|
+
return rows.some((row) => typeof row.contract === "string" && row.contract !== "")
|
|
68
|
+
? [...HISTORY_EXPORT_COLUMNS, ...CONTRACT_EXPORT_COLUMNS]
|
|
69
|
+
: HISTORY_EXPORT_COLUMNS;
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
/** Flags of a break before a bar, whose prices stay; every other code left the prices blank. */
|
|
53
73
|
const BREAK_FLAGS = new Set(["gap", "jump"]);
|
|
54
74
|
|
package/src/cli/history-rows.ts
CHANGED
|
@@ -14,8 +14,11 @@ import { isRoundTheClockCoin } from "../utils/crypto-pair";
|
|
|
14
14
|
import { currencyPairCode } from "../utils/currency-pair";
|
|
15
15
|
import { currencyUnitLabel, resolveCurrencyUnit } from "../utils/currency-units";
|
|
16
16
|
import { getPricePointTimestamp } from "../utils/price-history";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
isWickFlag, latestTradeIndex, missingVolumeNotice, pricePointIntegrityFlags, pricePointValues, pricePointWickFlags,
|
|
19
|
+
} from "../utils/price-history-integrity";
|
|
18
20
|
import { formatUtcTime } from "../utils/utc-time";
|
|
21
|
+
import { maxDrawdown, type MaxDrawdown } from "../utils/max-drawdown";
|
|
19
22
|
import { historyCoverageNotice } from "../sources/history-coverage";
|
|
20
23
|
import { barLastSession, barPeriodToDate, type BarPeriodToDate } from "../market-data/market/bar-period";
|
|
21
24
|
import { barSizeFromGap, medianBarGap } from "../time-series/bar-spacing";
|
|
@@ -79,6 +82,17 @@ export interface HistoryRow {
|
|
|
79
82
|
/** A bar without the market's volume: the trades of one venue alone (`volumeVenue`), never its volume. */
|
|
80
83
|
venueVolume?: number;
|
|
81
84
|
volumeVenue?: string;
|
|
85
|
+
/**
|
|
86
|
+
* What the bar's volume counts, where the source says: `aggregate` across venues on a coin's daily bar,
|
|
87
|
+
* `3 spot venues` on its intraday bar, whose volume is summed over those venues.
|
|
88
|
+
*/
|
|
89
|
+
volumeBasis?: string;
|
|
90
|
+
/** A continuous future's bar (NG=F): the contract it closes on (NGZ26), where the source says. */
|
|
91
|
+
contract?: string;
|
|
92
|
+
/** The first bar on a contract after another's: the series rolled into `contract` at this bar. */
|
|
93
|
+
roll?: true;
|
|
94
|
+
/** The bar traded far below the bars around it (THIN_VOLUME_SHARE of their median): a price on few trades. */
|
|
95
|
+
thin?: true;
|
|
82
96
|
}
|
|
83
97
|
|
|
84
98
|
/** A coin's volume: traded value in its quote currency, per UTC day on daily bars. */
|
|
@@ -253,10 +267,17 @@ function flagCodes(row: Pick<HistoryRow, "flag">): string[] {
|
|
|
253
267
|
return row.flag ? row.flag.split(",") : [];
|
|
254
268
|
}
|
|
255
269
|
|
|
270
|
+
/** The codes of a bar left blank for prices that contradict each other: not a break before it, nor a withheld wick. */
|
|
271
|
+
function blankingCodes(row: Pick<HistoryRow, "flag">): string[] {
|
|
272
|
+
return flagCodes(row).filter((code) => !BREAK_FLAGS.has(code) && !isWickFlag(code));
|
|
273
|
+
}
|
|
274
|
+
|
|
256
275
|
/**
|
|
257
276
|
* Intraday bars keep their UTC time; daily and longer bars are trading dates.
|
|
258
277
|
* A bar whose prices contradict each other is blanked exactly as HP blanks it,
|
|
259
|
-
* and says why in `flag`. A
|
|
278
|
+
* and says why in `flag`. A low or high too far from the open and close to
|
|
279
|
+
* have traded is left blank, the rest of the bar kept, flagged `low<close/2`
|
|
280
|
+
* or `high>2*open`. A bar after a gap or a jump in the series keeps its
|
|
260
281
|
* prices and is flagged `gap` or `jump`.
|
|
261
282
|
*/
|
|
262
283
|
export function historyRows(
|
|
@@ -277,7 +298,7 @@ export function historyRows(
|
|
|
277
298
|
const time = times[index]!;
|
|
278
299
|
const iso = Number.isFinite(time) ? new Date(time).toISOString() : "";
|
|
279
300
|
const values = pricePointValues(point);
|
|
280
|
-
const flags = pricePointIntegrityFlags(point);
|
|
301
|
+
const flags = [...pricePointIntegrityFlags(point), ...pricePointWickFlags(point)];
|
|
281
302
|
const price = (value: number | null) => value == null ? null : cleanBarPrice(value, priceDivisor);
|
|
282
303
|
return {
|
|
283
304
|
date: intraday ? iso.replace(".000Z", "Z") : iso.slice(0, 10),
|
|
@@ -297,6 +318,7 @@ export function historyRows(
|
|
|
297
318
|
...(values.volume == null && point.venueVolume
|
|
298
319
|
? { venueVolume: point.venueVolume.volume, volumeVenue: point.venueVolume.venue }
|
|
299
320
|
: {}),
|
|
321
|
+
...(values.volume != null && point.volumeBasis ? { volumeBasis: point.volumeBasis } : {}),
|
|
300
322
|
// A coin trades every day, so a bar without volume says why.
|
|
301
323
|
...(volume && values.volume == null
|
|
302
324
|
? {
|
|
@@ -307,12 +329,136 @@ export function historyRows(
|
|
|
307
329
|
};
|
|
308
330
|
});
|
|
309
331
|
const breaks = historyBreakFlags(times, rows.map((row) => row.close), interval);
|
|
332
|
+
const thin = thinVolumeBars(rows.map((row, index) => ({ ...row, contract: points[index]!.contract })));
|
|
333
|
+
let contract: string | null = null;
|
|
310
334
|
return rows.map((row, index) => {
|
|
311
335
|
const flags = [...flagCodes(row), ...breaks[index]!];
|
|
312
|
-
|
|
336
|
+
const own = points[index]!.contract;
|
|
337
|
+
// A roll is a change between two known contracts; a bar naming none (before the source knew) is no roll.
|
|
338
|
+
const rolled = !!own && contract != null && own !== contract;
|
|
339
|
+
if (own) contract = own;
|
|
340
|
+
const marked = {
|
|
341
|
+
...row,
|
|
342
|
+
...(own ? { contract: own } : {}),
|
|
343
|
+
...(rolled ? { roll: true as const } : {}),
|
|
344
|
+
...(thin[index] ? { thin: true as const } : {}),
|
|
345
|
+
};
|
|
346
|
+
return flags.length > 0 ? { ...marked, flag: flags.join(",") } : marked;
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Bars on each side of a bar whose volumes it is held against. */
|
|
351
|
+
const THIN_NEIGHBOURS = 5;
|
|
352
|
+
/** A bar under this share of its neighbours' median volume traded thin. */
|
|
353
|
+
const THIN_VOLUME_SHARE = 0.05;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* For each bar of a continuous future, whether it traded far below the bars
|
|
357
|
+
* around it: a volume under THIN_VOLUME_SHARE of the median of up to
|
|
358
|
+
* THIN_NEIGHBOURS bars either side that have one (at least four). NG=F's
|
|
359
|
+
* 2026-10-28 bar on 15 lots between days of 150,000 is a price few trades
|
|
360
|
+
* made. Only a bar naming its contract is judged, against others that do: a
|
|
361
|
+
* stock's or a coin's quiet day is not a contract's thin one. A bar still
|
|
362
|
+
* forming, or with no trade at all (no volume, or 0), is not judged.
|
|
363
|
+
*/
|
|
364
|
+
function thinVolumeBars(rows: readonly (Pick<HistoryRow, "volume" | "partial"> & { contract?: string })[]): boolean[] {
|
|
365
|
+
const judged = (row: (typeof rows)[number]): row is (typeof rows)[number] & { volume: number } =>
|
|
366
|
+
!!row.contract && !row.partial && row.volume != null && Number.isFinite(row.volume) && row.volume > 0;
|
|
367
|
+
return rows.map((row, index) => {
|
|
368
|
+
if (!judged(row)) return false;
|
|
369
|
+
const around = [...rows.slice(Math.max(0, index - THIN_NEIGHBOURS), index), ...rows.slice(index + 1, index + 1 + THIN_NEIGHBOURS)]
|
|
370
|
+
.filter(judged)
|
|
371
|
+
.map((other) => other.volume)
|
|
372
|
+
.sort((left, right) => left - right);
|
|
373
|
+
if (around.length < 4) return false;
|
|
374
|
+
const middle = around.length / 2;
|
|
375
|
+
const median = around.length % 2 ? around[Math.floor(middle)]! : (around[middle - 1]! + around[middle]!) / 2;
|
|
376
|
+
return median > 0 && row.volume < THIN_VOLUME_SHARE * median;
|
|
313
377
|
});
|
|
314
378
|
}
|
|
315
379
|
|
|
380
|
+
/** A roll of a continuous future: the bar it rolled at, and the contracts either side. */
|
|
381
|
+
interface HistoryRoll {
|
|
382
|
+
date: string;
|
|
383
|
+
from: string;
|
|
384
|
+
to: string;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Every roll in a history's bars (`roll`), oldest first. */
|
|
388
|
+
function historyRolls(rows: readonly Pick<HistoryRow, "date" | "contract" | "roll">[]): HistoryRoll[] {
|
|
389
|
+
let contract: string | null = null;
|
|
390
|
+
const rolls: HistoryRoll[] = [];
|
|
391
|
+
for (const row of rows) {
|
|
392
|
+
if (row.roll && contract && row.contract) rolls.push({ date: row.date, from: contract, to: row.contract });
|
|
393
|
+
if (row.contract) contract = row.contract;
|
|
394
|
+
}
|
|
395
|
+
return rolls;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* A history's bars split at every roll, each run one contract's, oldest
|
|
400
|
+
* first: what a drawdown or a change is read within, never across a roll.
|
|
401
|
+
* One run, named by no contract, for a history that names none.
|
|
402
|
+
*/
|
|
403
|
+
function historyContractRuns<Row extends Pick<HistoryRow, "contract" | "roll">>(rows: readonly Row[]): Array<{ contract: string | null; rows: Row[] }> {
|
|
404
|
+
const runs: Array<{ contract: string | null; rows: Row[] }> = [];
|
|
405
|
+
for (const row of rows) {
|
|
406
|
+
const current = runs.at(-1);
|
|
407
|
+
if (!current || row.roll) runs.push({ contract: row.contract ?? null, rows: [row] });
|
|
408
|
+
else {
|
|
409
|
+
current.rows.push(row);
|
|
410
|
+
current.contract ??= row.contract ?? null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return runs;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* The largest drawdown of a history's bars within one contract: each run
|
|
418
|
+
* between rolls read on its own (historyContractRuns), the deepest kept and
|
|
419
|
+
* named by its contract when the bars roll. A history naming no contract is
|
|
420
|
+
* one run, its drawdown `maxDrawdown`'s.
|
|
421
|
+
*/
|
|
422
|
+
export function historyContractDrawdown(
|
|
423
|
+
rows: readonly Pick<HistoryRow, "date" | "close" | "contract" | "roll">[],
|
|
424
|
+
): { drawdown: MaxDrawdown; contract: string | null } | null {
|
|
425
|
+
const runs = historyContractRuns(rows);
|
|
426
|
+
let deepest: { drawdown: MaxDrawdown; contract: string | null } | null = null;
|
|
427
|
+
for (const run of runs) {
|
|
428
|
+
const drawdown = maxDrawdown(run.rows);
|
|
429
|
+
if (drawdown && (!deepest || drawdown.drawdown < deepest.drawdown.drawdown)) deepest = { drawdown, contract: runs.length > 1 ? run.contract : null };
|
|
430
|
+
}
|
|
431
|
+
return deepest;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* "NG=F is the front month: rolled from NGX26 to NGZ26 on 2026-10-26 (marked
|
|
436
|
+
* roll)": the rolls a continuous future's bars make, the latest few by date,
|
|
437
|
+
* the rest counted. Null without one.
|
|
438
|
+
*/
|
|
439
|
+
export function historyRollNote(symbol: string, rows: readonly Pick<HistoryRow, "date" | "contract" | "roll">[]): string | null {
|
|
440
|
+
const rolls = historyRolls(rows);
|
|
441
|
+
if (rolls.length === 0) return null;
|
|
442
|
+
const shown = rolls.slice(-LISTED_GAPS);
|
|
443
|
+
const earlier = rolls.length - shown.length;
|
|
444
|
+
const listed = shown.map((roll) => `from ${roll.from} to ${roll.to} on ${historyDate(roll.date)}`);
|
|
445
|
+
return `${symbol} is the front month: ${earlier > 0 ? `${earlier} earlier ${earlier === 1 ? "roll" : "rolls"}, then ` : ""}`
|
|
446
|
+
+ `rolled ${listItems(listed)} (marked roll); drawdowns are read within one contract`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* "2026-10-28 traded thin: under 5% of the volume of the bars around it", the
|
|
451
|
+
* bars marked `thin`, the latest few by date. Null without one.
|
|
452
|
+
*/
|
|
453
|
+
export function historyThinNote(rows: readonly Pick<HistoryRow, "date" | "thin">[]): string | null {
|
|
454
|
+
const thin = rows.filter((row) => row.thin).map((row) => historyDate(row.date));
|
|
455
|
+
if (thin.length === 0) return null;
|
|
456
|
+
const shown = thin.slice(-LISTED_GAPS);
|
|
457
|
+
const earlier = thin.length - shown.length;
|
|
458
|
+
const dates = listItems([...earlier > 0 ? [`${earlier} earlier ${earlier === 1 ? "bar" : "bars"}`] : [], ...shown]);
|
|
459
|
+
return `${dates} traded thin: under ${THIN_VOLUME_SHARE * 100}% of the volume of the bars around ${thin.length === 1 ? "it" : "them"}`;
|
|
460
|
+
}
|
|
461
|
+
|
|
316
462
|
/**
|
|
317
463
|
* The newest weekly or monthly bar while its period has not ended at `now`
|
|
318
464
|
* (barPeriodToDate): marked `partial`, as a bar its source marks is, with
|
|
@@ -360,9 +506,22 @@ export function historyVolume(
|
|
|
360
506
|
return coin && unit.kind === "currency" && unit.currency ? { unit: unit.currency } : null;
|
|
361
507
|
}
|
|
362
508
|
|
|
363
|
-
/**
|
|
364
|
-
|
|
365
|
-
|
|
509
|
+
/**
|
|
510
|
+
* `volume in USD per UTC day` for a coin's daily bars, `volume in USD` for others, with what it counts when every bar
|
|
511
|
+
* with a volume says the same (`historyVolumeBasis`): `volume in USD per UTC day, aggregate across venues`, `volume in
|
|
512
|
+
* USD summed over 3 spot venues`.
|
|
513
|
+
*/
|
|
514
|
+
export function historyVolumeText(volume: HistoryVolume, interval: ManualChartResolution | null, basis: string | null = null): string {
|
|
515
|
+
const text = interval === "1d" ? `volume in ${volume.unit} per UTC day` : `volume in ${volume.unit}`;
|
|
516
|
+
if (!basis) return text;
|
|
517
|
+
return basis === "aggregate" ? `${text}, aggregate across venues` : `${text} summed over ${basis}`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** What the volume of every bar that has one counts, when they all say the same; null otherwise. */
|
|
521
|
+
export function historyVolumeBasis(rows: readonly Pick<HistoryRow, "volume" | "volumeBasis">[]): string | null {
|
|
522
|
+
const bases = new Set(rows.filter((row) => row.volume != null).map((row) => row.volumeBasis ?? null));
|
|
523
|
+
const [basis] = bases.size === 1 ? [...bases] : [null];
|
|
524
|
+
return basis ?? null;
|
|
366
525
|
}
|
|
367
526
|
|
|
368
527
|
function listDates(dates: readonly string[]): string {
|
|
@@ -448,7 +607,7 @@ export function historyMissingVolumeNote(
|
|
|
448
607
|
if (!historyReportsVolume(rows)) return null;
|
|
449
608
|
const latestTrade = latestTradeIndex(rows.map((row) => Date.parse(row.date)));
|
|
450
609
|
const missing = rows.filter((row, index) => row.volume == null && row.venueVolume == null && !row.partial && !row.filled
|
|
451
|
-
&&
|
|
610
|
+
&& blankingCodes(row).length === 0 && index !== latestTrade).length;
|
|
452
611
|
return missingVolumeNotice(missing, rows.length) ?? null;
|
|
453
612
|
}
|
|
454
613
|
|
|
@@ -833,10 +992,9 @@ export function historyGapNote(
|
|
|
833
992
|
* keep their prices and are not counted.
|
|
834
993
|
*/
|
|
835
994
|
export function historyFlagNote(rows: readonly Pick<HistoryRow, "flag">[]): string | null {
|
|
836
|
-
const
|
|
837
|
-
const flagged = rows.filter((row) => contradictions(row).length > 0);
|
|
995
|
+
const flagged = rows.filter((row) => blankingCodes(row).length > 0);
|
|
838
996
|
if (flagged.length === 0) return null;
|
|
839
|
-
const codes = new Set(flagged.flatMap(
|
|
997
|
+
const codes = new Set(flagged.flatMap(blankingCodes));
|
|
840
998
|
const kinds = [
|
|
841
999
|
codes.has("high<open") || codes.has("high<close") ? "high below open or close" : "",
|
|
842
1000
|
codes.has("low>open") || codes.has("low>close") ? "low above open or close" : "",
|
|
@@ -845,3 +1003,23 @@ export function historyFlagNote(rows: readonly Pick<HistoryRow, "flag">[]): stri
|
|
|
845
1003
|
const count = `${flagged.length} of ${rows.length} ${flagged.length === 1 && rows.length === 1 ? "bar" : "bars"}`;
|
|
846
1004
|
return `${count} ${flagged.length === 1 ? "has" : "have"} ${kinds.join(", or ")} in the source data; their prices are left blank`;
|
|
847
1005
|
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* "The low of 2021-11-29 is under half its open and close, too far to have
|
|
1009
|
+
* traded; left blank and out of ranges", or null when no bar has such a wick
|
|
1010
|
+
* (pricePointWickFlags). Three or more bars are counted.
|
|
1011
|
+
*/
|
|
1012
|
+
export function historyWickNote(rows: readonly Pick<HistoryRow, "date" | "flag">[]): string | null {
|
|
1013
|
+
const flagged = rows.filter((row) => flagCodes(row).some(isWickFlag));
|
|
1014
|
+
if (flagged.length === 0) return null;
|
|
1015
|
+
const sides = new Set(flagged.flatMap((row) => flagCodes(row).filter(isWickFlag).map((code) => code.slice(0, code.indexOf(code.startsWith("low") ? "<" : ">")))));
|
|
1016
|
+
const reason = "too far to have traded; left blank and out of ranges";
|
|
1017
|
+
const only = flagged[0]!;
|
|
1018
|
+
if (flagged.length === 1 && sides.size === 1) {
|
|
1019
|
+
return sides.has("low") ? `The low of ${only.date} is under half its open and close, ${reason}`
|
|
1020
|
+
: `The high of ${only.date} is over twice its open and close, ${reason}`;
|
|
1021
|
+
}
|
|
1022
|
+
const kinds = [sides.has("low") ? "a low under half" : "", sides.has("high") ? "a high over twice" : ""].filter(Boolean).join(" or ");
|
|
1023
|
+
const dates = flagged.length <= LISTED_GAPS ? ` (${listItems(flagged.map((row) => row.date))})` : "";
|
|
1024
|
+
return `${flagged.length} of ${rows.length} bars have ${kinds} their open and close${dates}, ${reason}`;
|
|
1025
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -408,6 +408,7 @@ const CLI_DESCRIPTION = "Market research and portfolio tracker for the terminal"
|
|
|
408
408
|
|
|
409
409
|
export interface DispatchCliOptions {
|
|
410
410
|
externalPlugins?: LoadedExternalPlugin[];
|
|
411
|
+
loadExternalPlugins?: () => Promise<LoadedExternalPlugin[]>;
|
|
411
412
|
}
|
|
412
413
|
|
|
413
414
|
/** Old names kept so existing scripts still run; help and suggestions leave them out. */
|
|
@@ -430,6 +431,7 @@ async function createRegistry(options: DispatchCliOptions = {}): Promise<CliComm
|
|
|
430
431
|
registry = buildCliCommandRegistry({
|
|
431
432
|
coreCommands,
|
|
432
433
|
externalPlugins: options.externalPlugins ?? [],
|
|
434
|
+
...(options.externalPlugins === undefined ? { loadExternalPlugins: options.loadExternalPlugins } : {}),
|
|
433
435
|
config,
|
|
434
436
|
});
|
|
435
437
|
return registry;
|
|
@@ -456,11 +458,25 @@ export async function dispatchCli(args: string[], options: DispatchCliOptions =
|
|
|
456
458
|
}
|
|
457
459
|
|
|
458
460
|
const registry = await createRegistry(options);
|
|
459
|
-
const
|
|
461
|
+
const token = normalizeCliCommandToken(foldTypedText(command));
|
|
462
|
+
let resolved = registry.lookup.get(token);
|
|
463
|
+
if (!resolved) {
|
|
464
|
+
await registry.ensureExternalPlugins?.();
|
|
465
|
+
resolved = registry.lookup.get(token);
|
|
466
|
+
}
|
|
460
467
|
if (!resolved) {
|
|
461
468
|
return { kind: "unhandled" };
|
|
462
469
|
}
|
|
463
470
|
|
|
471
|
+
// The overview includes installed commands; one built-in command's help only
|
|
472
|
+
// needs that command. An unknown help topic may belong to an installed plugin.
|
|
473
|
+
if (resolved.command.name === "help") {
|
|
474
|
+
const topic = parsed.args[1];
|
|
475
|
+
if (!topic || !registry.lookup.has(normalizeCliCommandToken(foldTypedText(topic)))) {
|
|
476
|
+
await registry.ensureExternalPlugins?.();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
464
480
|
// Help flags never reach the command, where they would read as a symbol, a
|
|
465
481
|
// plugin name, or a note to write.
|
|
466
482
|
const helpOnly = parsed.help && resolved.command.name !== "help";
|
|
@@ -500,6 +516,7 @@ export async function failUnknownCliCommand(args: string[], options: DispatchCli
|
|
|
500
516
|
// Intentional ignore: dispatchCli already reports invalid flags.
|
|
501
517
|
}
|
|
502
518
|
const registry = await createRegistry(options);
|
|
519
|
+
await registry.ensureExternalPlugins?.();
|
|
503
520
|
const commands = listedCommands(registry).map((entry) => entry.command);
|
|
504
521
|
// A function's code typed as a command is pointed at `fn`, and a word the catalog knows at its
|
|
505
522
|
// functions, with the arguments the reader gave.
|
package/src/cli/known-gaps.ts
CHANGED
|
@@ -87,8 +87,15 @@ export const MUNI_LINE = "Municipal bonds: no AAA muni curve (MMD and BVAL are l
|
|
|
87
87
|
* `tickers` are terms that are also a real ticker (BDI is Black Diamond Group):
|
|
88
88
|
* a query that is only that word still gets the line, but `quote` adds it only
|
|
89
89
|
* to the exchange question or the failure of the bare symbol, never to a quote.
|
|
90
|
+
*
|
|
91
|
+
* An entry with `words` matches on tokens, not only the whole query: a query
|
|
92
|
+
* holding one of its terms, every other word a term or one of `words`
|
|
93
|
+
* ("panamax rates", "bulk freight", "freight cost"). A word outside both keeps
|
|
94
|
+
* it a different question ("freight forwarder stocks", "ocean freight
|
|
95
|
+
* carriers"). An entry without `words` matches the whole query alone, since
|
|
96
|
+
* its terms are words of company names too ("carbon", "second").
|
|
90
97
|
*/
|
|
91
|
-
const KNOWN_GAPS: ReadonlyArray<{ terms: readonly string[]; tickers?: readonly string[]; message: string }> = [
|
|
98
|
+
const KNOWN_GAPS: ReadonlyArray<{ terms: readonly string[]; words?: readonly string[]; tickers?: readonly string[]; message: string }> = [
|
|
92
99
|
{
|
|
93
100
|
terms: ["EUA", "EUAs", "EU ETS", "EU allowance", "EU allowances", "EU carbon", "carbon", "carbon allowance", "carbon allowances",
|
|
94
101
|
"carbon price", "carbon prices", "carbon credits", "emission allowances", "emissions allowances"],
|
|
@@ -107,8 +114,12 @@ const KNOWN_GAPS: ReadonlyArray<{ terms: readonly string[]; tickers?: readonly s
|
|
|
107
114
|
{
|
|
108
115
|
terms: ["Baltic", "Baltic Dry", "Baltic Dry Index", "Baltic Exchange", "Baltic index", "Baltic indices", "BDI", "BDIY", "freight",
|
|
109
116
|
"freight rate", "freight rates", "freight index", "freight futures", "freight forward", "freight forwards", "freight forward agreement",
|
|
110
|
-
"freight forward agreements", "FFA", "FFAs", "capesize", "panamax", "supramax", "
|
|
111
|
-
"dry bulk rates", "dry bulk freight"
|
|
117
|
+
"freight forward agreements", "FFA", "FFAs", "capesize", "panamax", "supramax", "handysize", "kamsarmax", "newcastlemax",
|
|
118
|
+
"charter rate", "charter rates", "dry bulk", "dry bulk rates", "dry bulk freight", "shipping rate", "shipping rates", "shipping cost",
|
|
119
|
+
"shipping costs", "time charter", "TCE"],
|
|
120
|
+
words: ["rate", "rates", "cost", "costs", "price", "prices", "index", "indices", "indexes", "level", "levels", "chart", "charts",
|
|
121
|
+
"history", "historical", "data", "spot", "today", "current", "latest", "daily", "weekly", "market", "markets", "bulk", "dry",
|
|
122
|
+
"bulker", "bulkers", "vessel", "vessels", "ship", "ships", "route", "routes", "earnings", "equivalent", "futures", "the", "of", "for"],
|
|
112
123
|
tickers: ["BDI"],
|
|
113
124
|
message: "Baltic indices (BDI, Capesize, Panamax, Supramax) and freight forwards (FFA) are not covered. Traded proxies: BDRY (freight futures ETF), "
|
|
114
125
|
+ "SBLK, GNK, DSX (dry-bulk shipowners); chokepoint transits: fn CHOKE; news \"dry bulk\".",
|
|
@@ -222,6 +233,17 @@ const TOPIC_POINTERS: ReadonlyArray<{
|
|
|
222
233
|
closestLabel: "COMEX curves",
|
|
223
234
|
closest: [["CTM", "gloom fn CTM HG for copper, gloom fn CTM ALI for aluminium"]],
|
|
224
235
|
},
|
|
236
|
+
{
|
|
237
|
+
// No source serves a jet fuel (kerosene) price. Heating oil, NY Harbor ULSD, is what airlines hedge it with and
|
|
238
|
+
// analysts read it by; the EIA's weekly US stocks are a catalog series (checked 2026-10-29: quote HO=F,
|
|
239
|
+
// fn COT HO and fn G CAP:doe:jet all answer).
|
|
240
|
+
topic: "Jet fuel (kerosene)",
|
|
241
|
+
availability: "no price series; heating oil (NY Harbor ULSD) is the usual proxy, its price: gloom quote HO=F. "
|
|
242
|
+
+ "US jet fuel stocks (EIA, weekly): gloom fn G CAP:doe:jet",
|
|
243
|
+
phrases: ["jet fuel", "jet fuels", "kerosene", "kerosine", "jet kerosene", "aviation fuel", "aviation fuels", "avtur"],
|
|
244
|
+
closestLabel: "positioning",
|
|
245
|
+
closest: [["COT", "heating oil, the usual jet fuel proxy: gloom fn COT HO"]],
|
|
246
|
+
},
|
|
225
247
|
{
|
|
226
248
|
// "exchange reserves" alone reads as crypto's; "foreign exchange reserves" is a central bank's, so only whole.
|
|
227
249
|
topic: "Crypto exchange reserves and exchange flows",
|
|
@@ -315,6 +337,14 @@ const COMMAND_POINTERS: ReadonlyArray<{
|
|
|
315
337
|
"change", "ranking", "rankings", "dashboard", "lending", "staking"],
|
|
316
338
|
line: "DeFi TVL, top protocols and chains with 7D and 30D change: gloom fn CRYP --list defi (by chain: --by chain)",
|
|
317
339
|
},
|
|
340
|
+
{
|
|
341
|
+
// Ahead of DIST, which "difficulty" finds by its description; the four series are listed after it.
|
|
342
|
+
names: ["hashrate", "hash rate", "hashprice", "hash price", "difficulty", "retarget", "mining", "miner", "miners"],
|
|
343
|
+
words: ["bitcoin", "btc", "network", "crypto", "stock", "stocks", "equities", "listed", "next",
|
|
344
|
+
"estimate", "adjustment", "chart", "history"],
|
|
345
|
+
line: "Bitcoin network: gloom fn G hashrate, difficulty or hashprice; listed miners: gloom fn THEM crypto",
|
|
346
|
+
leads: true,
|
|
347
|
+
},
|
|
318
348
|
];
|
|
319
349
|
|
|
320
350
|
/** The command for a query that names a setting ("light theme") or a portfolio report ("drip"), else null. */
|
|
@@ -387,7 +417,23 @@ export function knownGapForTicker(symbol: string): string | null {
|
|
|
387
417
|
}
|
|
388
418
|
|
|
389
419
|
/**
|
|
390
|
-
*
|
|
420
|
+
* Whether a query names a known subject: as a whole ("Baltic Dry", "dry-bulk"), or, for an entry with `words`,
|
|
421
|
+
* by a term among words that only qualify it ("panamax rates", "bulk freight").
|
|
422
|
+
*/
|
|
423
|
+
function namesKnownGap(gap: (typeof KNOWN_GAPS)[number], query: string): boolean {
|
|
424
|
+
const key = lookupKey(query);
|
|
425
|
+
if (gap.terms.some((term) => lookupKey(term) === key)) return true;
|
|
426
|
+
if (!gap.words) return false;
|
|
427
|
+
const asked = words(query);
|
|
428
|
+
const named = new Set(gap.terms.flatMap((term) => {
|
|
429
|
+
const parts = words(term);
|
|
430
|
+
return containsWords(asked, parts) ? parts : [];
|
|
431
|
+
}));
|
|
432
|
+
return named.size > 0 && asked.every((word) => named.has(word) || gap.words!.includes(word));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* The line for a query that names a known subject ("carbon allowances", "EUA", "panamax rates"), a Treasury yield
|
|
391
437
|
* typed as a ticker (US10Y), a topic with no function ("defense budget"), or a macro release no series carries
|
|
392
438
|
* ("ISM manufacturing", "GDPNow"), else null.
|
|
393
439
|
*/
|
|
@@ -395,7 +441,7 @@ export function knownGapMessage(query: string): string | null {
|
|
|
395
441
|
const key = lookupKey(query);
|
|
396
442
|
if (!key) return null;
|
|
397
443
|
const tenor = treasuryYieldTenor(query);
|
|
398
|
-
return KNOWN_GAPS.find((gap) => gap
|
|
444
|
+
return KNOWN_GAPS.find((gap) => namesKnownGap(gap, query))?.message
|
|
399
445
|
?? (tenor ? treasuryYieldLine(tenor) : null)
|
|
400
446
|
?? topicPointer(query)?.message
|
|
401
447
|
?? macroReleaseTopic(query)?.line ?? null;
|