gloomberb 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- 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/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -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 +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- 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/types.ts +4 -0
- 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/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- 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 +4 -1
- 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 +5 -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/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- 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 +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -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 +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -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/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- 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 +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- 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/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -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 +7 -7
- 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/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/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- 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 +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- 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 +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -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 +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- 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 +36 -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/pane.tsx +7 -5
- 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 +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -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 +14 -0
- 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 +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -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 +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- 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/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- 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 +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- 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/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- 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 +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- 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/scanner/feed.ts +2 -2
- 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 +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- 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 +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- 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/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -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 +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- 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 +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- 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 +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- 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-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/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -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 +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- 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/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- 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/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- 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/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -1,17 +1,29 @@
|
|
|
1
|
+
import { closeBasis, isDailyMetric, isRelativeMetric, metricDescription, metricName, metricReadingText, validMetricRule } from "./metric-model";
|
|
2
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
3
|
+
import { benchmarkKey, parseMetricAlert, type MetricAlertInput } from "./metric-command";
|
|
4
|
+
import { firstDailyCheck, metricAvailabilityError, metricReadings } from "./metric-data";
|
|
5
|
+
import { ALERT_ADD_FORMS, ALERT_KINDS_TEXT, parseRuleAddArgs, RULE_US_DATA, ruleDelivery, type RuleAddRequest } from "./cli-forms";
|
|
6
|
+
import { alertChangeEntry, appendAlertLog, type AlertLogAction } from "./fired-log";
|
|
7
|
+
import { alertLogRule } from "./alert-log";
|
|
8
|
+
import { alertListing, alertReadingAt, alertReadingText, isoUtc, listingWithCurrency, NOT_CHECKED_YET } from "./format";
|
|
9
|
+
import { isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
|
|
10
|
+
import { quoteAlertFields } from "./quotes";
|
|
11
|
+
import { createEventAlert } from "./events";
|
|
12
|
+
import { isResearchAlertKind, readResearchRule } from "./research-rules";
|
|
1
13
|
import { withConfigData, withMarketData } from "../../../cli/scoped-context";
|
|
2
14
|
import { CLI_COMMAND_GROUPS } from "../../../cli/help";
|
|
3
15
|
import { dryRunNote } from "../../../cli/helpers";
|
|
4
16
|
import { requireArg, takeOption } from "../../../cli/commands/command-utils";
|
|
5
17
|
import { requireQuotedListing } from "../../../cli/quote-answer";
|
|
6
|
-
import { EXCHANGE_OPTION, listingReadNotes, requireListingArg, usListingsOnlyMessage } from "../../../cli/listing-arg";
|
|
7
|
-
import { notTheCoinNote } from "../../../cli/commands/crypto-hints";
|
|
18
|
+
import { coinListingNotes, EXCHANGE_OPTION, listingReadNotes, requireListingArg, usListingsOnlyMessage } from "../../../cli/listing-arg";
|
|
8
19
|
import { saveConfig } from "../../../data/config/store";
|
|
9
20
|
import { resumeStateKey } from "../../registry/plugin-state";
|
|
10
21
|
import type { CliCommandDef } from "../../../types/plugin";
|
|
11
22
|
import type { Quote } from "../../../types/financials";
|
|
12
23
|
import { getActiveQuoteDisplay } from "../../../market-data/market/status";
|
|
13
24
|
import { cliStyles } from "../../../utils/cli-output";
|
|
14
|
-
import { canonicalExchange, isUsListingExchange } from "../../../utils/exchanges";
|
|
25
|
+
import { canonicalExchange, isUsListingExchange, parsePublicTickerKey } from "../../../utils/exchanges";
|
|
26
|
+
import { notATickerSymbol } from "../../../sources/provider-errors";
|
|
15
27
|
import { meantThousandsDot, parsePastedNumber, thousandsDotReadings, type ThousandsDotReadings } from "../../../utils/decimal-comma";
|
|
16
28
|
import { newYorkToday } from "../earnings/board-model";
|
|
17
29
|
import { fetchEconCalendarFrom } from "../econ/calendar-source";
|
|
@@ -24,17 +36,20 @@ import {
|
|
|
24
36
|
macroEventNamed,
|
|
25
37
|
matchCalendarEvent,
|
|
26
38
|
releaseInstant,
|
|
39
|
+
releaseRenameNote,
|
|
27
40
|
releaseRowOn,
|
|
28
41
|
type CalendarEventChoice,
|
|
42
|
+
type ReleaseRename,
|
|
29
43
|
} from "./calendar-events";
|
|
30
|
-
import { createAlert, rearmAlert,
|
|
31
|
-
import {
|
|
44
|
+
import { createAlert, rearmAlert, sameLevelRule } from "./alert-engine";
|
|
45
|
+
import { ALERTS_PLUGIN_ID } from "./constants";
|
|
32
46
|
import { MAX_EVENT_ALERTS, eventAlertTarget, toggleEventAlert, writeEventAlertStores, type EventAlertRule } from "./events";
|
|
33
|
-
import { alertKindLabel } from "./history";
|
|
34
|
-
import {
|
|
47
|
+
import { alertKindLabel, fetchAlertHistory, ruleStateText, type AlertRuleState } from "./history";
|
|
48
|
+
import { apiClient } from "../../../api-client";
|
|
49
|
+
import { IV_LEVEL_CHECKS_KEY, ivLevelCheckText, readIvLevelChecks, rearmIvLevelAlert, type IvLevelCheck } from "./iv-level";
|
|
35
50
|
import { parseAlertPrice, readAlertPrice } from "./price";
|
|
36
51
|
import { farTargetMessage } from "./quotes";
|
|
37
|
-
import { MACRO_EVENT_NAMES, createResearchAlert, formatPremium, ivLevelUnitError, parseAmount, parseIvLevel
|
|
52
|
+
import { MACRO_EVENT_NAMES, createResearchAlert, formatPremium, ivLevelUnitError, parseAmount, parseIvLevel } from "./research-builder";
|
|
38
53
|
import { IV_LEVEL_RANGES, MAX_LEAD_DAYS, type IvLevelMetric, type MacroAlertEvent } from "./research-rules";
|
|
39
54
|
import {
|
|
40
55
|
createScheduledAlert,
|
|
@@ -58,7 +73,7 @@ import {
|
|
|
58
73
|
type ScheduledEventTarget,
|
|
59
74
|
} from "./scheduled";
|
|
60
75
|
import { scheduledEventSources } from "./scheduled-sources";
|
|
61
|
-
import { readAlertStores } from "./storage";
|
|
76
|
+
import { readAlertStores, writeLevelAlertStores } from "./storage";
|
|
62
77
|
import type { AlertCondition, AlertRule } from "./types";
|
|
63
78
|
|
|
64
79
|
/**
|
|
@@ -70,14 +85,23 @@ import type { AlertCondition, AlertRule } from "./types";
|
|
|
70
85
|
*/
|
|
71
86
|
const PRICE_DELIVERY = "the app alerts while it is running; sign in with Cloud sync for email or phone alerts when it is closed";
|
|
72
87
|
|
|
73
|
-
const ADD_USAGE =
|
|
88
|
+
const ADD_USAGE = `Usage: gloom alerts add <symbol> <above|below|crosses> <price>, or another kind. ${ALERT_KINDS_TEXT}`;
|
|
74
89
|
const DAYS_RULE = `--days takes a whole number of days from 1 to ${MAX_LEAD_DAYS}; without it the alert goes off 1 day before.`;
|
|
75
|
-
/**
|
|
76
|
-
const
|
|
90
|
+
/** How a release in the economic calendar is named, CPI and FOMC among them. */
|
|
91
|
+
const CALENDAR_EXAMPLES = `alerts add "NBS Manufacturing PMI" --country CN, alerts add CN PMI, alerts add CPI --days 1`;
|
|
77
92
|
const COUNTRY_RULE = "--country is for an alert on a calendar event, such as alerts add PMI --country CN.";
|
|
93
|
+
const BENCHMARK_RULE = "--benchmark takes the listing an RRG alert is measured against (SPY by default, as in RRG),";
|
|
94
|
+
/** `XLK/SPY`, `XLK:NYSEARCA/SPY`: two listings, or one written with a slash (BRK/B, EUR/USD). */
|
|
95
|
+
const PAIR_SHAPE = /^[^/\s]+\/[^/\s]+$/;
|
|
96
|
+
/** A listing check that hands back its refusal instead of ending the command, so a pair can be read as a ratio. */
|
|
97
|
+
class ListingRefusal extends Error {
|
|
98
|
+
constructor(message: string, readonly details?: string) { super(message); }
|
|
99
|
+
}
|
|
100
|
+
const PROBE = { fail: (message: string, details?: string): never => { throw new ListingRefusal(message, details); } };
|
|
78
101
|
|
|
79
102
|
/** What `alerts add` was asked for, before any listing or date is looked up. */
|
|
80
103
|
export type AlertAddRequest =
|
|
104
|
+
| ({ kind: "metric" } & MetricAlertInput)
|
|
81
105
|
| {
|
|
82
106
|
kind: "price"; symbol: string; condition: AlertCondition; price: number;
|
|
83
107
|
/** A price typed as 81.000, which the last price may show was 81000 with a dot between thousands. */
|
|
@@ -94,7 +118,9 @@ export type AlertAddRequest =
|
|
|
94
118
|
/** The symbol's 30-day ATM IV (`atm`, in %) or 25-delta risk reversal (`rr`, in vol points) above or below a level, checked by the running app. */
|
|
95
119
|
| { kind: "iv_level"; symbol: string; metric: IvLevelMetric; direction: "above" | "below"; level: number }
|
|
96
120
|
/** One OCC contract's IV rising by `points`. */
|
|
97
|
-
| { kind: "iv_spike"; contract: string; points: number }
|
|
121
|
+
| { kind: "iv_spike"; contract: string; points: number }
|
|
122
|
+
/** The rest of what Add Event Alert offers: 52-week range, unusual volume, filings, insiders, short interest and more. */
|
|
123
|
+
| RuleAddRequest;
|
|
98
124
|
|
|
99
125
|
/** The `alerts add` forms Gloom Cloud's research rules take: they follow US listings. */
|
|
100
126
|
type ResearchAddRequest = Extract<AlertAddRequest, { kind: "news" | "flow" | "iv_level" | "iv_spike" }>;
|
|
@@ -147,13 +173,30 @@ export function parseAlertAddArgs(
|
|
|
147
173
|
days: string | null | undefined,
|
|
148
174
|
exchange?: string | null,
|
|
149
175
|
country?: string | null,
|
|
176
|
+
benchmark?: string | null,
|
|
150
177
|
): AlertAddRequest {
|
|
151
178
|
const [first, second, third, ...rest] = args;
|
|
152
179
|
if (!first) throw new Error(ADD_USAGE);
|
|
153
180
|
const countryFilter = readCountryOption(country);
|
|
154
|
-
|
|
181
|
+
if (benchmark === null) throw new Error(`${BENCHMARK_RULE} such as alerts add XLE rrg leading --benchmark QQQ.`);
|
|
182
|
+
const rrgFlag = benchmark !== undefined && second?.toLowerCase() === "rrg";
|
|
183
|
+
const typedVs = /^(vs\.?|versus|against)$/i.test(args[3] ?? "") ? args[4] : undefined;
|
|
184
|
+
if (rrgFlag && typedVs !== undefined) {
|
|
185
|
+
// `vs QQQ --benchmark SMH` names two benchmarks; the same one twice (SPY and SPY:NYSEARCA) is one.
|
|
186
|
+
const [typed, flag] = [typedVs, benchmark].map((text) => parsePublicTickerKey(text.toUpperCase()));
|
|
187
|
+
if (typed!.symbol !== flag!.symbol || (typed!.exchange && flag!.exchange && typed!.exchange !== flag!.exchange)) {
|
|
188
|
+
throw new Error(`An RRG alert names its benchmark once: vs ${typedVs.toUpperCase()} or --benchmark ${benchmark.toUpperCase()}, not both.`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const metric = parseMetricAlert(rrgFlag && typedVs === undefined ? [...args, "vs", benchmark].join(" ") : args.join(" "));
|
|
192
|
+
if (benchmark !== undefined && metric?.metric.kind !== "rrg") throw new Error(`${BENCHMARK_RULE} such as alerts add XLE rrg leading --benchmark QQQ; a ratio names both listings, as in alerts add XLK/SPY above 0.25.`);
|
|
193
|
+
if (metric) {
|
|
194
|
+
if (days !== undefined || country !== undefined) throw new Error(`${isRelativeMetric(metric.metric) ? "Relative strength" : "Valuation and daily technical"} alerts take neither --days nor --country.`);
|
|
195
|
+
return { kind: "metric", ...metric };
|
|
196
|
+
}
|
|
197
|
+
const research = parseResearchAddArgs(args) ?? parseRuleAddArgs(args);
|
|
155
198
|
if (research) {
|
|
156
|
-
if (days !== undefined) throw new Error("--days is for earnings and calendar event alerts, not
|
|
199
|
+
if (days !== undefined) throw new Error("--days is for earnings and calendar event alerts, not this kind of alert.");
|
|
157
200
|
if (country !== undefined) throw new Error(COUNTRY_RULE);
|
|
158
201
|
return research;
|
|
159
202
|
}
|
|
@@ -181,7 +224,7 @@ export function parseAlertAddArgs(
|
|
|
181
224
|
if (!CONDITIONS.includes(condition)) {
|
|
182
225
|
// Words with no price after them name a release (CN PMI, Industrial Profits); a price ends a mistyped price alert.
|
|
183
226
|
if (parseAlertPrice(args.at(-1)!).kind === "invalid") return releaseRequest(args, countryFilter, leadDays());
|
|
184
|
-
throw new Error(`Unknown alert "${second}".
|
|
227
|
+
throw new Error(`Unknown alert "${second}". ${ALERT_KINDS_TEXT}`);
|
|
185
228
|
}
|
|
186
229
|
if (country !== undefined) throw new Error(COUNTRY_RULE);
|
|
187
230
|
if (days !== undefined) throw new Error("--days is for earnings and calendar event alerts, not price alerts.");
|
|
@@ -301,6 +344,25 @@ function researchDelivery(kind: ResearchAddRequest["kind"]): string {
|
|
|
301
344
|
return `Gloom Cloud checks it for a signed-in account with Cloud sync${plan} and sends it to your phone`;
|
|
302
345
|
}
|
|
303
346
|
|
|
347
|
+
/**
|
|
348
|
+
* The rule Add Event Alert saves for a form `alerts add` named by its words,
|
|
349
|
+
* on the listing `quote` read the symbol as: a US research rule keeps its
|
|
350
|
+
* symbol and venue, hiring and a catalyst's company their listing as typed
|
|
351
|
+
* (ADYEN:AMS).
|
|
352
|
+
*/
|
|
353
|
+
function createFormRule(request: RuleAddRequest, listing: { symbol: string; key: string; exchange: string } | null): EventAlertRule {
|
|
354
|
+
const { event } = request;
|
|
355
|
+
if (!isResearchAlertKind(event)) return createEventAlert(event, listing?.key ?? request.values.value ?? "");
|
|
356
|
+
const values = Object.fromEntries(Object.entries(request.values).map(([key, value]) => [`${event}:${key}`, value]));
|
|
357
|
+
if (listing && request.listing === "any") values[`${event}:symbol`] = listing.key;
|
|
358
|
+
else if (listing) {
|
|
359
|
+
const typed = listing.exchange ? canonicalExchange(listing.exchange) : "";
|
|
360
|
+
values[`${event}:symbol`] = listing.symbol;
|
|
361
|
+
values[`${event}:exchange`] = RESEARCH_EXCHANGES.has(typed) ? typed : "US";
|
|
362
|
+
}
|
|
363
|
+
return createResearchAlert(event, values);
|
|
364
|
+
}
|
|
365
|
+
|
|
304
366
|
const isResearchRequest = (request: AlertAddRequest): request is ResearchAddRequest =>
|
|
305
367
|
request.kind === "news" || request.kind === "flow" || request.kind === "iv_level" || request.kind === "iv_spike";
|
|
306
368
|
|
|
@@ -338,6 +400,33 @@ export function thousandsDotMessage(
|
|
|
338
400
|
return `${dot.text} could be ${dot.thousands} (thousands) or ${dot.decimal} (decimal point), and ${listing.toUpperCase()} last traded at ${Number(last!.toFixed(2))}; type ${retype(dot.thousands)} or ${retype(dot.decimal)}`;
|
|
339
401
|
}
|
|
340
402
|
|
|
403
|
+
/**
|
|
404
|
+
* What a valuation or daily technical alert read when it was added:
|
|
405
|
+
* "RSI(14) was 61.23 on the 2026-10-26 close", "50-day MA was 186.32 against
|
|
406
|
+
* the 200-day MA at 180.11 on the 2026-10-26 close", "P/E (TTM) is 24.10".
|
|
407
|
+
*/
|
|
408
|
+
function metricReadingNote(alert: AlertRule): string {
|
|
409
|
+
const metric = alert.metric!;
|
|
410
|
+
// "Ratio to SPY was 0.2434 on the 2026-10-26 close", "RRG vs SPY was Leading (strength 110.06, momentum 108.88) on the 2026-10-23 weekly close".
|
|
411
|
+
if (isRelativeMetric(metric)) {
|
|
412
|
+
const reading = metricReadingText(metric, { value: alert.lastCheckedPrice ?? Number.NaN, target: alert.lastCheckedTarget ?? Number.NaN });
|
|
413
|
+
return `${metricName(metric)} was ${reading} on the ${alert.lastDailyClose} ${closeBasis(metric) === "weekly close" ? "weekly close" : "close"}`;
|
|
414
|
+
}
|
|
415
|
+
const value = (alert.lastCheckedPrice ?? Number.NaN).toFixed(2);
|
|
416
|
+
if (!isDailyMetric(metric)) return `${metricName(metric)} is ${value}`;
|
|
417
|
+
const against = metric.kind === "ma_cross" ? metric.slowPeriod : metric.kind === "close_ma" ? metric.period : null;
|
|
418
|
+
const target = against != null && alert.lastCheckedTarget != null ? ` against the ${against}-day MA at ${alert.lastCheckedTarget.toFixed(2)}` : "";
|
|
419
|
+
return `${metricName(metric)} was ${value}${target} on the ${alert.lastDailyClose} close`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** An event alert still waiting to go off, as `alerts list` says `active`: not paused, and not alerted for its coming event or level. */
|
|
423
|
+
function eventRuleArmed(rule: EventAlertRule, now: number): boolean {
|
|
424
|
+
if (rule.status !== "active") return false;
|
|
425
|
+
const scheduled = readScheduledRule(rule);
|
|
426
|
+
if (!scheduled) return !rule.triggeredAt;
|
|
427
|
+
return scheduledAlertStatus(rule, scheduled.leadDays, scheduledToday(scheduled.target, now), scheduled.trigger) === "active";
|
|
428
|
+
}
|
|
429
|
+
|
|
341
430
|
/** One row of `alerts list`: a price alert as stored, or an event alert with its next date. */
|
|
342
431
|
type AlertListRow = Record<string, unknown> & {
|
|
343
432
|
id: string;
|
|
@@ -346,14 +435,32 @@ type AlertListRow = Record<string, unknown> & {
|
|
|
346
435
|
};
|
|
347
436
|
|
|
348
437
|
function priceRow(alert: AlertRule): AlertListRow {
|
|
349
|
-
return {
|
|
438
|
+
return {
|
|
439
|
+
...alert, kind: alert.metric?.kind ?? "price", rule: alert.metric ? metricDescription(alert) : undefined,
|
|
440
|
+
target: alert.exchange ? `${alert.symbol}:${alert.exchange}` : alert.symbol,
|
|
441
|
+
// The listing it is checked on, in its currency, and its last reading in words with when it is from.
|
|
442
|
+
listing: alertListing(alert), currency: alert.lastQuoteCurrency ?? null,
|
|
443
|
+
lastReading: alertReadingText(alert), lastReadingAt: isoUtc(alertReadingAt(alert)) || null,
|
|
444
|
+
};
|
|
350
445
|
}
|
|
351
446
|
|
|
352
|
-
function eventRow(
|
|
447
|
+
function eventRow(
|
|
448
|
+
rule: EventAlertRule, now: number, eventDate: string | null | undefined, check?: IvLevelCheck, cloud?: CloudRuleStates,
|
|
449
|
+
): AlertListRow {
|
|
353
450
|
const scheduled = readScheduledRule(rule);
|
|
354
451
|
if (!scheduled) {
|
|
355
452
|
const status = rule.status === "active" && rule.triggeredAt ? "triggered" : rule.status;
|
|
453
|
+
// An IV level is read by this app, so its last reading is this device's; Gloom Cloud reads the rest.
|
|
454
|
+
const state = cloud?.states.get(rule.id);
|
|
455
|
+
const reading = rule.kind === "iv_level" ? {
|
|
456
|
+
lastReading: ivLevelCheckText(rule, check),
|
|
457
|
+
lastReadingAt: check?.value != null ? isoUtc(check.quoteAt ? Date.parse(check.quoteAt) : check.checkedAt) || null : null,
|
|
458
|
+
} : state ? {
|
|
459
|
+
lastReading: ruleStateText(state),
|
|
460
|
+
lastReadingAt: state.asOf ? isoUtc(Date.parse(state.asOf)) || null : null,
|
|
461
|
+
} : cloud?.failed ? {} : { lastReading: NOT_CHECKED_YET };
|
|
356
462
|
return { id: rule.id, kind: rule.kind, target: eventAlertTarget(rule), status, createdAt: rule.createdAt,
|
|
463
|
+
listing: eventRuleListing(rule), ...reading,
|
|
357
464
|
...check ? { lastCheck: check } : {}, ...rule.triggeredAt ? { triggeredAt: rule.triggeredAt } : {} };
|
|
358
465
|
}
|
|
359
466
|
const date = eventDate ?? null;
|
|
@@ -369,26 +476,68 @@ function eventRow(rule: EventAlertRule, now: number, eventDate: string | null |
|
|
|
369
476
|
eventDate: date,
|
|
370
477
|
status: scheduledAlertStatus({ ...rule, eventDate: date ?? undefined }, scheduled.leadDays, scheduledToday(scheduled.target, now), scheduled.trigger),
|
|
371
478
|
createdAt: rule.createdAt,
|
|
479
|
+
listing: scheduled.target.kind === "earnings" ? scheduled.target.exchange ? `${scheduled.target.symbol}:${scheduled.target.exchange}` : scheduled.target.symbol : "",
|
|
372
480
|
};
|
|
373
481
|
}
|
|
374
482
|
|
|
483
|
+
/** Gloom Cloud's last reading of each rule it checks, as ALRT's Events tab shows them; `failed` when it could not be asked. */
|
|
484
|
+
interface CloudRuleStates {
|
|
485
|
+
states: Map<string, AlertRuleState>;
|
|
486
|
+
failed: boolean;
|
|
487
|
+
warning?: string;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Gloom Cloud checks the event rules this app does not, for a signed-in account. With no session nothing has
|
|
492
|
+
* read them, so they are not checked yet; with one, their readings are Cloud's.
|
|
493
|
+
*/
|
|
494
|
+
async function cloudRuleStates(rules: readonly EventAlertRule[]): Promise<CloudRuleStates> {
|
|
495
|
+
const empty = { states: new Map<string, AlertRuleState>(), failed: false };
|
|
496
|
+
if (!apiClient.getSessionToken() || !rules.some((rule) => rule.kind !== "iv_level" && !readScheduledRule(rule))) return empty;
|
|
497
|
+
try {
|
|
498
|
+
const history = await fetchAlertHistory(0);
|
|
499
|
+
return { states: new Map(history.states.map((state) => [state.ruleId, state])), failed: false };
|
|
500
|
+
} catch (error) {
|
|
501
|
+
return { ...empty, failed: true, warning: `Gloom Cloud's readings of event alerts could not be loaded: ${error instanceof Error ? error.message : String(error)}` };
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** The listing a research rule follows (AAPL, ADYEN:AMS for a hiring rule); empty for one on no symbol. */
|
|
506
|
+
function eventRuleListing(rule: EventAlertRule): string {
|
|
507
|
+
if (rule.target === "ticker") return rule.value;
|
|
508
|
+
if (!isResearchAlertKind(rule.kind)) return "";
|
|
509
|
+
const config = readResearchRule(rule.kind, rule.value);
|
|
510
|
+
if (!config?.symbol) return "";
|
|
511
|
+
return config.symbol.includes(":") || !config.exchange || config.exchange === "US" ? config.symbol : `${config.symbol}:${config.exchange}`;
|
|
512
|
+
}
|
|
513
|
+
|
|
375
514
|
const plural = (count: number, word: string) => `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
376
515
|
|
|
516
|
+
/**
|
|
517
|
+
* One row as text and CSV give it. CSV keeps the columns it has always had in
|
|
518
|
+
* their order, then Created (ISO UTC), the listing, its currency and the last
|
|
519
|
+
* reading, in words and dated.
|
|
520
|
+
*/
|
|
377
521
|
function textRow(row: AlertListRow): Record<string, unknown> {
|
|
378
522
|
const price = row.kind === "price";
|
|
379
523
|
return {
|
|
380
524
|
id: row.id,
|
|
381
525
|
kind: price ? "Price" : alertKindLabel(row.kind),
|
|
382
526
|
target: row.target,
|
|
383
|
-
trigger: price
|
|
527
|
+
trigger: row.rule ?? (price
|
|
384
528
|
? `${row.condition} ${row.targetPrice}`
|
|
385
529
|
: typeof row.leadDays === "number"
|
|
386
530
|
? scheduledTriggerText({ leadDays: row.leadDays, trigger: row.when === "results" ? "results" : "date", prints: row.onRelease === true })
|
|
387
|
-
: "",
|
|
531
|
+
: ""),
|
|
388
532
|
eventDate: typeof row.leadDays === "number" ? row.eventDate ?? "not known" : "",
|
|
389
533
|
status: row.status,
|
|
390
534
|
lastCheckedPrice: row.lastCheckedPrice,
|
|
391
535
|
triggeredAt: row.triggeredAt,
|
|
536
|
+
createdAt: isoUtc(typeof row.createdAt === "number" ? row.createdAt : null),
|
|
537
|
+
listing: row.listing ?? "",
|
|
538
|
+
currency: row.currency ?? "",
|
|
539
|
+
lastReading: row.lastReading ?? "",
|
|
540
|
+
lastReadingAt: row.lastReadingAt ?? "",
|
|
392
541
|
};
|
|
393
542
|
}
|
|
394
543
|
|
|
@@ -455,10 +604,16 @@ function releaseData(choice: CalendarEventChoice) {
|
|
|
455
604
|
const choiceList = (choices: readonly CalendarEventChoice[]) => choices.map((choice) => calendarEventLabel({ event: choice.name, country: choice.country }));
|
|
456
605
|
|
|
457
606
|
/** What a name read as, and what else it names in that country: said beside the alert added. */
|
|
458
|
-
function releaseReadNotes(
|
|
607
|
+
function releaseReadNotes(
|
|
608
|
+
query: string,
|
|
609
|
+
choice: CalendarEventChoice,
|
|
610
|
+
others: readonly CalendarEventChoice[],
|
|
611
|
+
renamed?: ReleaseRename,
|
|
612
|
+
): string[] {
|
|
459
613
|
const notes: string[] = [];
|
|
460
614
|
if (calendarNameWords(query).join(" ") !== calendarNameWords(choice.name).join(" ")) {
|
|
461
|
-
notes.push(`Read "${query}" as ${calendarEventLabel({ event: choice.name, country: choice.country })}, ${choice.impact} impact.`
|
|
615
|
+
notes.push(`Read "${query}" as ${calendarEventLabel({ event: choice.name, country: choice.country })}, ${choice.impact} impact.`
|
|
616
|
+
+ (renamed ? ` ${releaseRenameNote(renamed)}` : ""));
|
|
462
617
|
}
|
|
463
618
|
if (others.length > 0) {
|
|
464
619
|
const shown = choiceList(others.slice(0, 4));
|
|
@@ -468,8 +623,8 @@ function releaseReadNotes(query: string, choice: CalendarEventChoice, others: re
|
|
|
468
623
|
return notes;
|
|
469
624
|
}
|
|
470
625
|
|
|
471
|
-
/** Every
|
|
472
|
-
const ADD_FORMS =
|
|
626
|
+
/** Every kind `alerts add` takes, as a refusal of words none of them reads ends. */
|
|
627
|
+
const ADD_FORMS = ALERT_KINDS_TEXT;
|
|
473
628
|
|
|
474
629
|
/**
|
|
475
630
|
* Why a name could not be looked up. A single word (alerts add AAPL) may be
|
|
@@ -482,10 +637,21 @@ function calendarUnavailableMessage(query: string, country: CountryFilter | null
|
|
|
482
637
|
}
|
|
483
638
|
|
|
484
639
|
/** The refusal of a name nothing in the calendar matches: the nearest names, and every form `alerts add` takes. */
|
|
485
|
-
function noReleaseMessage(
|
|
640
|
+
function noReleaseMessage(
|
|
641
|
+
query: string,
|
|
642
|
+
country: CountryFilter | null,
|
|
643
|
+
suggestions: readonly CalendarEventChoice[],
|
|
644
|
+
renamed?: ReleaseRename,
|
|
645
|
+
): string {
|
|
486
646
|
const near = suggestions.length > 0 ? ` Did you mean ${choiceList(suggestions).join(", ")}?` : "";
|
|
487
|
-
|
|
488
|
-
|
|
647
|
+
// Words with a country are a release named; anything else may as well be a kind of alert mistyped.
|
|
648
|
+
if (country) {
|
|
649
|
+
return `No release in the economic calendar matches "${query}" in ${country.split(",").join(", ")}.`
|
|
650
|
+
+ `${renamed ? ` ${releaseRenameNote(renamed)}` : ""}${near} gloom econ --country <code> lists the calendar.`;
|
|
651
|
+
}
|
|
652
|
+
return `No kind of alert and no release in the economic calendar matches "${query}".`
|
|
653
|
+
+ `${renamed ? ` ${releaseRenameNote(renamed)}` : ""}${near} `
|
|
654
|
+
+ `${ADD_FORMS} Calendar releases go by name (${CALENDAR_EXAMPLES}); gloom econ --country <code> lists the calendar.`;
|
|
489
655
|
}
|
|
490
656
|
|
|
491
657
|
/** The refusal of a name listed in several countries, with the best of each. */
|
|
@@ -511,18 +677,13 @@ async function requireEventDate(
|
|
|
511
677
|
export const alertsCliCommand: CliCommandDef = {
|
|
512
678
|
name: "alerts",
|
|
513
679
|
aliases: ["alert"],
|
|
514
|
-
description: "List, add, and remove price alerts, alerts before earnings and any economic calendar release (CPI, FOMC, NBS PMI), and news, options flow and
|
|
680
|
+
description: "List, add, re-arm and remove price, valuation, daily technical and relative strength alerts, alerts before earnings and any economic calendar release (CPI, FOMC, NBS PMI), and every event alert ALRT offers: news, options flow, IV, 52-week range, unusual volume, filings, insiders, short interest, analysts, hiring, perpetuals, catalysts, Congress and 13F",
|
|
515
681
|
help: {
|
|
516
682
|
group: CLI_COMMAND_GROUPS.portfolios,
|
|
683
|
+
// Every form `alerts add` takes, as the refusal of an unknown phrase names them and the CLI docs page lists them.
|
|
517
684
|
usage: [
|
|
518
685
|
"alerts list",
|
|
519
|
-
|
|
520
|
-
"alerts add <symbol> earnings [--days <n> | results]",
|
|
521
|
-
"alerts add <calendar event> [--country <code>] [--days <n>]",
|
|
522
|
-
"alerts add [symbol] news <keyword>",
|
|
523
|
-
"alerts add [symbol] flow [premium] [calls|puts] [sweeps|blocks]",
|
|
524
|
-
"alerts add <symbol> <iv|rr> <above|below> <level>",
|
|
525
|
-
"alerts add <OCC contract> iv spike <points>",
|
|
686
|
+
...ALERT_ADD_FORMS.map((form) => `alerts add ${form.usage}`),
|
|
526
687
|
"alerts delete <id>",
|
|
527
688
|
"alerts rearm <id>",
|
|
528
689
|
],
|
|
@@ -530,10 +691,19 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
530
691
|
EXCHANGE_OPTION,
|
|
531
692
|
{ flags: "--days <n>", description: `Days before the event the alert goes off, 1 to ${MAX_LEAD_DAYS} (default 1); earnings and calendar event alerts` },
|
|
532
693
|
{ flags: "--country <code>", description: "The country of a calendar event, as econ --country takes it (CN, US, DE); a code before the name does the same (alerts add CN PMI)" },
|
|
694
|
+
{ flags: "--benchmark <symbol>", description: "The listing an RRG alert is measured against; SPY by default, as in RRG" },
|
|
533
695
|
],
|
|
534
696
|
examples: [
|
|
535
697
|
"alerts",
|
|
536
698
|
"alerts add AAPL above 250",
|
|
699
|
+
"alerts add AAPL pe below 25",
|
|
700
|
+
"alerts add AAPL ps above 8",
|
|
701
|
+
'alerts add NVDA "rsi(14)" under 30',
|
|
702
|
+
'alerts add SPY "ma(50)" crosses above "ma(200)"',
|
|
703
|
+
'alerts add TSLA closes below "ma(200)"',
|
|
704
|
+
"alerts add XLK/SPY above 0.25",
|
|
705
|
+
"alerts add XLE rrg leading",
|
|
706
|
+
"alerts add SMH rrg weakening --benchmark QQQ",
|
|
537
707
|
"alerts add SAN:EPA below 70",
|
|
538
708
|
"alerts add CRWV earnings --days 3",
|
|
539
709
|
"alerts add CRWV earnings results",
|
|
@@ -544,6 +714,17 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
544
714
|
"alerts add NVDA flow 5m calls sweeps",
|
|
545
715
|
"alerts add IBIT iv above 50",
|
|
546
716
|
"alerts add SPY rr below -6",
|
|
717
|
+
"alerts add XLE 52-week high",
|
|
718
|
+
"alerts add NVDA unusual volume 3x",
|
|
719
|
+
"alerts add AAPL filing 8-K",
|
|
720
|
+
"alerts add TSLA insider sell",
|
|
721
|
+
"alerts add GME short interest increase 20%",
|
|
722
|
+
"alerts add AAPL analyst downgrade",
|
|
723
|
+
"alerts add RIVN hiring slowing",
|
|
724
|
+
"alerts add perps hyperliquid:default:BTC funding above 0.05%",
|
|
725
|
+
"alerts add catalyst type clinical",
|
|
726
|
+
"alerts add congress \"Nancy Pelosi\"",
|
|
727
|
+
"alerts add 13f 1067983",
|
|
547
728
|
],
|
|
548
729
|
},
|
|
549
730
|
execute: async (rawArgs, ctx) => {
|
|
@@ -555,31 +736,55 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
555
736
|
const countryFlag = rawArgs.some((arg) => arg === "--country" || arg.startsWith("--country="));
|
|
556
737
|
const countryValue = takeOption(args, "--country");
|
|
557
738
|
const country = countryFlag && countryValue === undefined ? null : countryValue;
|
|
739
|
+
const benchmarkFlag = rawArgs.some((arg) => arg === "--benchmark" || arg.startsWith("--benchmark="));
|
|
740
|
+
const benchmarkValue = takeOption(args, "--benchmark");
|
|
741
|
+
const benchmark = benchmarkFlag && benchmarkValue === undefined ? null : benchmarkValue;
|
|
558
742
|
const action = args[0] ?? "list";
|
|
559
743
|
const now = Date.now();
|
|
560
744
|
const today = newYorkToday(new Date(now));
|
|
745
|
+
const failWith = (error: unknown): never => ctx.fail(error instanceof Error ? error.message : String(error));
|
|
561
746
|
|
|
562
747
|
const parseAdd = (): AlertAddRequest => {
|
|
563
748
|
try {
|
|
564
|
-
return parseAlertAddArgs(args.slice(1), days, exchangeOption, country);
|
|
749
|
+
return parseAlertAddArgs(args.slice(1), days, exchangeOption, country, benchmark);
|
|
565
750
|
} catch (error) {
|
|
566
|
-
return
|
|
751
|
+
return failWith(error);
|
|
567
752
|
}
|
|
568
753
|
};
|
|
569
|
-
const
|
|
754
|
+
const typedRequest = action === "add" ? parseAdd() : null;
|
|
755
|
+
// XLK/SPY is first a listing written with a slash (BRK/B, EUR/USD); one that quotes nothing is the ratio of the two.
|
|
756
|
+
const pairListing = typedRequest?.kind === "price" && PAIR_SHAPE.test(typedRequest.symbol)
|
|
757
|
+
? await withMarketData(ctx, (market) => requireQuotedListing(typedRequest.symbol, exchangeOption, market, PROBE, "alerts add")).catch((error: unknown) => {
|
|
758
|
+
if (!(error instanceof ListingRefusal)) throw error;
|
|
759
|
+
if (!notATickerSymbol(error.message)) return ctx.fail(error.message, error.details);
|
|
760
|
+
return null;
|
|
761
|
+
})
|
|
762
|
+
: undefined;
|
|
763
|
+
let request: AlertAddRequest | null = typedRequest;
|
|
764
|
+
if (typedRequest?.kind === "price" && pairListing === null) {
|
|
765
|
+
if (exchangeOption) ctx.fail("A ratio alert names each listing, such as alerts add XLK:NYSEARCA/SPY above 0.25, and takes no --exchange.");
|
|
766
|
+
try {
|
|
767
|
+
request = { kind: "metric", ...parseMetricAlert(`${typedRequest.symbol} ratio ${typedRequest.condition} ${typedRequest.price}`)! };
|
|
768
|
+
} catch (error) {
|
|
769
|
+
failWith(error);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
570
772
|
const research = request && isResearchRequest(request) ? request : null;
|
|
571
|
-
const
|
|
572
|
-
|
|
773
|
+
const formRule = request?.kind === "rule" ? request : null;
|
|
774
|
+
const listedSymbol = (request?.kind === "price" || request?.kind === "metric") ? request.symbol
|
|
775
|
+
: research && research.kind !== "iv_spike" ? research.symbol : formRule?.symbol ?? null;
|
|
776
|
+
// Gloom Cloud's research rules follow US listings; hiring and a catalyst's company may list anywhere.
|
|
777
|
+
const usOnly = research ? research.kind === "news" ? "News alerting" : research.kind === "flow" ? "Options flow" : "Options data"
|
|
778
|
+
: formRule?.listing === "us" ? RULE_US_DATA[formRule.event] ?? "This alert" : null;
|
|
573
779
|
// A price alert is kept for the listing `quote` prints for the symbol, and for no symbol that quotes nothing;
|
|
574
780
|
// a news, flow or IV rule on a symbol follows that listing too, and only in the US, where their data is.
|
|
575
|
-
const priced = listedSymbol
|
|
781
|
+
const priced = pairListing ? pairListing : listedSymbol
|
|
576
782
|
? await withMarketData(ctx, async (market) => {
|
|
577
783
|
const quoted = await requireQuotedListing(listedSymbol, exchangeOption, market, ctx, "alerts add");
|
|
578
|
-
if (
|
|
784
|
+
if (usOnly) {
|
|
579
785
|
const venue = quoted.listing.exchange || canonicalExchange(quoted.quote.listingExchangeName || quoted.quote.exchangeName);
|
|
580
786
|
if (!isUsListingExchange(venue)) {
|
|
581
|
-
const
|
|
582
|
-
const { message, details } = await usListingsOnlyMessage(what, quoted.listing, market,
|
|
787
|
+
const { message, details } = await usListingsOnlyMessage(usOnly, quoted.listing, market,
|
|
583
788
|
(key) => `gloom alerts add ${key} ${args.slice(2).join(" ")}`);
|
|
584
789
|
ctx.fail(message, details);
|
|
585
790
|
}
|
|
@@ -588,12 +793,56 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
588
793
|
})
|
|
589
794
|
: null;
|
|
590
795
|
const priceListing = priced?.listing ?? null;
|
|
796
|
+
// A bare coin ticker read as another listing (SOL, a stale company; BTC, the Grayscale trust) gets `quote`'s note
|
|
797
|
+
// naming the coin's pair, before a metric alert names the venue it was read on.
|
|
798
|
+
const coinNote = priced ? coinListingNotes(priced.listing, priced.quote)[0] ?? null : null;
|
|
799
|
+
// What the alert reads now, which it is saved with, so lists and ALRT show it before the app has run.
|
|
800
|
+
let reading: Partial<AlertRule> = {};
|
|
801
|
+
// A daily rule whose close could not be read yet: why, which the add says beside when it is first checked.
|
|
802
|
+
let unread: string | null = null;
|
|
803
|
+
// A ratio or RRG alert's benchmark, as its listing was read.
|
|
804
|
+
const benchmarkNotes: string[] = [];
|
|
805
|
+
if (request?.kind === "metric" && priced) {
|
|
806
|
+
const exchange = canonicalExchange(priced.listing.exchange || priced.quote.listingExchangeName || priced.quote.exchangeName);
|
|
807
|
+
const typedMetric = request.metric;
|
|
808
|
+
if (isRelativeMetric(typedMetric)) {
|
|
809
|
+
// The benchmark is held to a listing that quotes, as the symbol is.
|
|
810
|
+
const reference = await withMarketData(ctx, (market) => requireQuotedListing(benchmarkKey(typedMetric), null, market, ctx, "alerts add"));
|
|
811
|
+
const benchmarkExchange = canonicalExchange(reference.listing.exchange || reference.quote.listingExchangeName || reference.quote.exchangeName);
|
|
812
|
+
if (reference.listing.symbol === priced.listing.symbol && benchmarkExchange === exchange) {
|
|
813
|
+
ctx.fail(`${typedMetric.kind === "rrg" ? "An RRG alert" : "A ratio alert"} compares two listings; ${priced.listing.symbol} is on both sides.`);
|
|
814
|
+
}
|
|
815
|
+
request.metric = { ...typedMetric, benchmark: reference.listing.symbol, ...(benchmarkExchange ? { benchmarkExchange } : {}) };
|
|
816
|
+
// Never save a rule its own list would refuse to read back.
|
|
817
|
+
if (!validMetricRule({ ...request, targetPrice: request.price })) ctx.fail(`${reference.listing.key} cannot be a benchmark: relative strength alerts compare two US listings.`);
|
|
818
|
+
benchmarkNotes.push(...reference.notes);
|
|
819
|
+
}
|
|
820
|
+
const preview = { ...createAlert(priced.listing.symbol, request.condition, request.price, exchange), metric: request.metric };
|
|
821
|
+
// A listing without a published close calendar can never be checked; anything else is saved and checked as it can be.
|
|
822
|
+
const unavailable = metricAvailabilityError(preview);
|
|
823
|
+
if (unavailable) ctx.fail(unavailable);
|
|
824
|
+
try {
|
|
825
|
+
const value = await withMarketData(ctx, market => metricReadings(market.dataProvider, now)(preview));
|
|
826
|
+
reading = { lastCheckedPrice: value.value, lastCheckedTarget: value.target, lastQuoteUpdatedAt: value.at, lastCheckedAt: now,
|
|
827
|
+
...value.day ? { lastDailyClose: value.day } : {} };
|
|
828
|
+
} catch (error) {
|
|
829
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
830
|
+
// A valuation the inputs cannot give is refused; a daily close not yet final, or history not there yet, is read once it is.
|
|
831
|
+
if (!isDailyMetric(request.metric)) ctx.fail(message);
|
|
832
|
+
unread = message;
|
|
833
|
+
}
|
|
834
|
+
if (priced.quote.currency) reading.lastQuoteCurrency = priced.quote.currency;
|
|
835
|
+
priced.listing.exchange = exchange;
|
|
836
|
+
}
|
|
837
|
+
if (request?.kind === "price" && priced && !isQuoteStaleForCurrentSession(priced.quote)) {
|
|
838
|
+
// The price it is added at is its first reading and the baseline a crossing is measured from, as Add Alert keeps it.
|
|
839
|
+
const price = lastPrice(priced.quote);
|
|
840
|
+
if (price != null) reading = quoteAlertFields({ ...priced.quote, price }, now);
|
|
841
|
+
}
|
|
591
842
|
if (request?.kind === "price" && request.thousandsDot && priced) {
|
|
592
843
|
const question = thousandsDotMessage(request, priced.listing.key, lastPrice(priced.quote), exchangeOption);
|
|
593
844
|
if (question) ctx.fail(question);
|
|
594
845
|
}
|
|
595
|
-
// A bare coin ticker read as a fund (BTC, the Grayscale trust) gets `quote`'s note naming the coin's pair.
|
|
596
|
-
const coinNote = priced && !priced.listing.exchange ? notTheCoinNote(priced.listing.symbol, priced.quote) : null;
|
|
597
846
|
if (request?.kind === "price" && priced) {
|
|
598
847
|
// A target ten times away is a slip or another instrument's price, such as bitcoin's on the BTC fund.
|
|
599
848
|
// Against the price the alert is checked on, as Add Alert measures it.
|
|
@@ -601,7 +850,7 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
601
850
|
if (far) ctx.fail(far);
|
|
602
851
|
}
|
|
603
852
|
// What a typed listing was read as (7203:TSE as 7203:JPX, MXN as MXN:FWB2), said beside what was added.
|
|
604
|
-
let read = [...priced?.notes ?? [], ...coinNote ? [coinNote] : []];
|
|
853
|
+
let read = [...priced?.notes ?? [], ...coinNote ? [coinNote] : [], ...benchmarkNotes];
|
|
605
854
|
let newEventRule: EventAlertRule | null = null;
|
|
606
855
|
let newResearchRule: EventAlertRule | null = null;
|
|
607
856
|
if (research) {
|
|
@@ -616,6 +865,13 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
616
865
|
ctx.fail(error instanceof Error ? error.message : String(error));
|
|
617
866
|
}
|
|
618
867
|
}
|
|
868
|
+
if (formRule) {
|
|
869
|
+
try {
|
|
870
|
+
newResearchRule = createFormRule(formRule, priceListing);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
ctx.fail(error instanceof Error ? error.message : String(error));
|
|
873
|
+
}
|
|
874
|
+
}
|
|
619
875
|
// A release named from the calendar, with what the words were read as and what else they name.
|
|
620
876
|
let newRelease: CalendarEventChoice | null = null;
|
|
621
877
|
// CPI and FOMC, typed so or named by one of their calendar rows (CPI y/y, Fed Interest Rate Decision).
|
|
@@ -628,7 +884,7 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
628
884
|
const match = matchCalendarEvent(request.query, request.country, events, now);
|
|
629
885
|
if (match.kind !== "match") {
|
|
630
886
|
return ctx.fail(match.kind === "none"
|
|
631
|
-
? noReleaseMessage(request.query, request.country, match.suggestions)
|
|
887
|
+
? noReleaseMessage(request.query, request.country, match.suggestions, match.renamed)
|
|
632
888
|
: severalCountriesMessage(request.query, match.choices));
|
|
633
889
|
}
|
|
634
890
|
const { choice, others } = match;
|
|
@@ -638,7 +894,7 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
638
894
|
read = [`Read "${request.query}" as the ${MACRO_EVENT_NAMES[macroEvent]} alert, which follows ${calendarEventLabel({ event: choice.name, country: choice.country })}.`];
|
|
639
895
|
} else {
|
|
640
896
|
newRelease = choice;
|
|
641
|
-
read = releaseReadNotes(request.query, choice, others);
|
|
897
|
+
read = releaseReadNotes(request.query, choice, others, match.renamed);
|
|
642
898
|
const target: ScheduledEventTarget = { kind: "release", event: choice.name, country: choice.country, ...(choice.publisher ? { publisher: choice.publisher } : {}) };
|
|
643
899
|
try {
|
|
644
900
|
newEventRule = createScheduledAlert(target, request.leadDays, choice.next ? dateKey(choice.next.date) : null);
|
|
@@ -690,22 +946,25 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
690
946
|
const lookup = (targets: ScheduledEventTarget[]) => targets.some((target) => target.kind === "earnings")
|
|
691
947
|
? withMarketData(ctx, (market) => lookupScheduledDates(targets, scheduledEventSources(market.dataProvider), today, now))
|
|
692
948
|
: lookupScheduledDates(targets, scheduledEventSources(null), today, now);
|
|
693
|
-
const { dates, warnings } = await listEventDates(events.rules, now, lookup);
|
|
949
|
+
const [{ dates, warnings }, cloud] = await Promise.all([listEventDates(events.rules, now, lookup), cloudRuleStates(events.rules)]);
|
|
694
950
|
const rows = [
|
|
695
951
|
...alerts.map(priceRow),
|
|
696
|
-
...events.rules.map((rule) => eventRow(rule, now, dates.has(rule.id) ? dates.get(rule.id) : rule.eventDate, checks[rule.id])),
|
|
952
|
+
...events.rules.map((rule) => eventRow(rule, now, dates.has(rule.id) ? dates.get(rule.id) : rule.eventDate, checks[rule.id], cloud)),
|
|
697
953
|
];
|
|
698
|
-
const allWarnings = [...(alertsError ? [alertsError] : []), ...(events.error ? [events.error] : []), ...warnings];
|
|
954
|
+
const allWarnings = [...(alertsError ? [alertsError] : []), ...(events.error ? [events.error] : []), ...warnings, ...cloud.warning ? [cloud.warning] : []];
|
|
699
955
|
ctx.printResult({ data: rows, ...(allWarnings.length ? { warnings: allWarnings } : {}) }, {
|
|
700
956
|
rows: (data) => data.map(textRow),
|
|
701
957
|
textColumns: [
|
|
702
958
|
{ key: "id", header: "ID", shrink: false },
|
|
703
959
|
{ key: "kind", header: "Kind" },
|
|
704
|
-
|
|
960
|
+
// A price or technical alert names the listing it is checked on, in its currency.
|
|
961
|
+
{ key: "target", header: "Target", format: (value, row) => row.currency ? listingWithCurrency(String(row.listing), String(row.currency)) : String(value ?? "") },
|
|
705
962
|
{ key: "trigger", header: "Trigger" },
|
|
706
963
|
{ key: "eventDate", header: "Event Date" },
|
|
707
964
|
{ key: "status", header: "Status" },
|
|
708
|
-
{ key: "
|
|
965
|
+
{ key: "lastReading", header: "Last Reading" },
|
|
966
|
+
// ISO 8601 in UTC, as CSV writes it.
|
|
967
|
+
{ key: "createdAt", header: "Created", format: (value) => String(value ?? "") },
|
|
709
968
|
{ key: "triggeredAt", header: "Triggered", optional: true },
|
|
710
969
|
],
|
|
711
970
|
empty: "No alerts. Add one with gloom alerts add AAPL above 250, alerts add CRWV earnings --days 3, alerts add CPI --days 1, or alerts add CN PMI.",
|
|
@@ -714,16 +973,18 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
714
973
|
}
|
|
715
974
|
|
|
716
975
|
await withConfigData(ctx, async (config) => {
|
|
717
|
-
const { pluginState, alerts, alertsError, events } = readAlertStores(config.config);
|
|
976
|
+
const { pluginState, alerts, alertsError, levelErrors, events } = readAlertStores(config.config);
|
|
718
977
|
const save = async (next: { alerts?: AlertRule[]; events?: EventAlertRule[] }) => {
|
|
719
|
-
if (next.alerts
|
|
978
|
+
if (next.alerts) for (const [key, value] of Object.entries(writeLevelAlertStores(next.alerts))) {
|
|
979
|
+
if (levelErrors[key] && value !== "[]") ctx.fail(levelErrors[key]!);
|
|
980
|
+
}
|
|
720
981
|
const nextConfig = {
|
|
721
982
|
...config.config,
|
|
722
983
|
pluginConfig: {
|
|
723
984
|
...config.config.pluginConfig,
|
|
724
985
|
[ALERTS_PLUGIN_ID]: {
|
|
725
986
|
...pluginState,
|
|
726
|
-
...(next.alerts ?
|
|
987
|
+
...(next.alerts ? Object.fromEntries(Object.entries(writeLevelAlertStores(next.alerts)).filter(([key]) => !levelErrors[key])) : {}),
|
|
727
988
|
// Each list keeps what it holds of kinds this version does not know; IV levels go in their own.
|
|
728
989
|
...(next.events ? writeEventAlertStores(events.lists, next.events) : {}),
|
|
729
990
|
},
|
|
@@ -736,15 +997,45 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
736
997
|
if (events.error) ctx.fail(events.error);
|
|
737
998
|
return events.rules;
|
|
738
999
|
};
|
|
1000
|
+
// Each rule created, re-armed or deleted here goes in this device's alert log, beside the alerts that fired.
|
|
1001
|
+
const record = (action: AlertLogAction, rule: AlertRule | EventAlertRule) => {
|
|
1002
|
+
const state = config.persistence?.pluginState;
|
|
1003
|
+
if (ctx.cliOptions.dryRun || !state) return;
|
|
1004
|
+
try {
|
|
1005
|
+
appendAlertLog({
|
|
1006
|
+
get: (key) => state.get(ALERTS_PLUGIN_ID, resumeStateKey(key))?.value,
|
|
1007
|
+
set: (key, value) => state.set(ALERTS_PLUGIN_ID, resumeStateKey(key), value),
|
|
1008
|
+
}, [alertChangeEntry(action, alertLogRule(rule), Date.now())]);
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
logFailure("alerts.cli", "Record alert change failed")(error);
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
739
1013
|
|
|
740
|
-
if (request?.kind === "price") {
|
|
1014
|
+
if (request?.kind === "price" || request?.kind === "metric") {
|
|
741
1015
|
const { condition, price } = request;
|
|
742
1016
|
const symbol = requireArg(priceListing?.key, ADD_USAGE, ctx);
|
|
743
|
-
const alert = createAlert(symbol, condition, price, priceListing?.exchange);
|
|
1017
|
+
const alert: AlertRule = { ...createAlert(symbol, condition, price, priceListing?.exchange), ...(request.kind === "metric" ? { metric: request.metric } : {}), ...reading };
|
|
1018
|
+
// The same rule on the same listing is one alert: a second add names it and adds nothing.
|
|
1019
|
+
const existing = alerts.find((entry) => sameLevelRule(entry, alert));
|
|
1020
|
+
if (existing) {
|
|
1021
|
+
const rearm = existing.status === "triggered" ? `; it went off, so re-arm it with gloom alerts rearm ${existing.id}` : "";
|
|
1022
|
+
ctx.fail(`${alertLogRule(alert).rule} already exists as alert ${existing.id}${rearm}. Nothing was added.`);
|
|
1023
|
+
}
|
|
744
1024
|
await save({ alerts: [...alerts, alert] });
|
|
745
|
-
|
|
1025
|
+
record("created", alert);
|
|
1026
|
+
// A daily rule is first judged on the first close after it was set, once that close has settled.
|
|
1027
|
+
// An RRG quadrant, on the last session of that week.
|
|
1028
|
+
const weekly = alert.metric?.kind === "rrg";
|
|
1029
|
+
const firstCheck = alert.metric && isDailyMetric(alert.metric) ? firstDailyCheck(alert.exchange, now, weekly) : null;
|
|
1030
|
+
const notes = [
|
|
1031
|
+
alert.metric ? unread ? `Not read yet: ${unread.replace(/\.$/, "")}.` : `${metricReadingNote(alert)}.` : null,
|
|
1032
|
+
firstCheck ? `First checked on the ${firstCheck.day} ${weekly ? "weekly close" : "close"}, from ${isoUtc(firstCheck.at).slice(11, 16)} UTC.` : null,
|
|
1033
|
+
].filter(Boolean).join(" ");
|
|
1034
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert,
|
|
1035
|
+
...firstCheck ? { firstCheck: { close: firstCheck.day, at: isoUtc(firstCheck.at) } } : {},
|
|
1036
|
+
...unread ? { unread } : {} }, ...(read.length > 0 ? { warnings: read } : {}) }, {
|
|
746
1037
|
// The delivery, as docs/usage.md states it, before any full stop, which after the price would read as part of it (below 81.).
|
|
747
|
-
text: (data) => `Added alert ${data.alert.id}: ${symbol} ${condition} ${price} (${PRICE_DELIVERY}).${dryRunNote(data.dryRun)}`,
|
|
1038
|
+
text: (data) => `Added alert ${data.alert.id}: ${data.alert.metric ? metricDescription(data.alert) : `${symbol} ${condition} ${price}`} (${PRICE_DELIVERY}).${notes ? ` ${notes}` : ""}${dryRunNote(data.dryRun)}`,
|
|
748
1039
|
});
|
|
749
1040
|
return;
|
|
750
1041
|
}
|
|
@@ -752,12 +1043,13 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
752
1043
|
const rule = newResearchRule;
|
|
753
1044
|
const rules = requireEvents();
|
|
754
1045
|
const existing = rules.find((entry) => entry.kind === rule.kind && entry.target === rule.target && entry.value.toLowerCase() === rule.value.toLowerCase());
|
|
755
|
-
if (existing) ctx.fail(`This alert already exists (ID ${existing.id}).`);
|
|
1046
|
+
if (existing) ctx.fail(`This alert already exists (ID ${existing.id}). Nothing was added.`);
|
|
756
1047
|
if (rules.length >= MAX_EVENT_ALERTS) ctx.fail(`Keep at most ${MAX_EVENT_ALERTS} event alerts. Delete one with gloom alerts delete <id>.`);
|
|
757
1048
|
await save({ events: [...rules, rule] });
|
|
758
|
-
|
|
1049
|
+
record("created", rule);
|
|
1050
|
+
const delivery = research ? researchDelivery(research.kind) : ruleDelivery(formRule!.event);
|
|
759
1051
|
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert: rule }, ...(read.length > 0 ? { warnings: read } : {}) }, {
|
|
760
|
-
text: (data) => `Added alert ${data.alert.id}: ${alertKindLabel(rule.kind)} ${
|
|
1052
|
+
text: (data) => `Added alert ${data.alert.id}: ${alertKindLabel(rule.kind)} ${eventAlertTarget(rule)} (${delivery}).${dryRunNote(data.dryRun)}`,
|
|
761
1053
|
});
|
|
762
1054
|
return;
|
|
763
1055
|
}
|
|
@@ -768,10 +1060,11 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
768
1060
|
const name = scheduledEventName(scheduled.target);
|
|
769
1061
|
const existing = rules.find((entry) => entry.kind === rule.kind && entry.value.toLowerCase() === rule.value.toLowerCase());
|
|
770
1062
|
if (existing) ctx.fail(scheduled.trigger === "results"
|
|
771
|
-
? `An alert for ${name} results already exists (ID ${existing.id}).`
|
|
772
|
-
: `An alert ${plural(scheduled.leadDays, "day")} before ${name} already exists (ID ${existing.id}).`);
|
|
1063
|
+
? `An alert for ${name} results already exists (ID ${existing.id}). Nothing was added.`
|
|
1064
|
+
: `An alert ${plural(scheduled.leadDays, "day")} before ${name} already exists (ID ${existing.id}). Nothing was added.`);
|
|
773
1065
|
if (rules.length >= MAX_EVENT_ALERTS) ctx.fail(`Keep at most ${MAX_EVENT_ALERTS} event alerts. Delete one with gloom alerts delete <id>.`);
|
|
774
1066
|
await save({ events: [...rules, rule] });
|
|
1067
|
+
record("created", rule);
|
|
775
1068
|
const eventDate = rule.eventDate ?? null;
|
|
776
1069
|
const due = !!eventDate && scheduledPhase(eventDate, scheduled.leadDays, undefined, scheduledToday(scheduled.target, now)) === "due";
|
|
777
1070
|
const release = followsRelease(scheduled.target);
|
|
@@ -796,12 +1089,14 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
796
1089
|
}
|
|
797
1090
|
if (action === "delete" || action === "rm") {
|
|
798
1091
|
const id = requireArg(args[1], "Usage: gloom alerts delete <id>", ctx);
|
|
799
|
-
const
|
|
800
|
-
const
|
|
801
|
-
if (!
|
|
802
|
-
if (
|
|
803
|
-
if (
|
|
804
|
-
const
|
|
1092
|
+
const level = alerts.find((alert) => alert.id === id) ?? null;
|
|
1093
|
+
const rule = level ? null : events.rules.find((entry) => entry.id === id) ?? null;
|
|
1094
|
+
if (!level && !rule && alertsError) ctx.fail(alertsError);
|
|
1095
|
+
if (level) await save({ alerts: alerts.filter((alert) => alert.id !== id) });
|
|
1096
|
+
if (rule) await save({ events: requireEvents().filter((entry) => entry.id !== id) });
|
|
1097
|
+
const gone = level ?? rule;
|
|
1098
|
+
if (gone) record("deleted", gone);
|
|
1099
|
+
const changed = !!gone;
|
|
805
1100
|
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun && changed, dryRun: ctx.cliOptions.dryRun, id } }, {
|
|
806
1101
|
text: (data) => changed
|
|
807
1102
|
? `Deleted alert ${id}.${dryRunNote(data.dryRun)}`
|
|
@@ -811,22 +1106,25 @@ export const alertsCliCommand: CliCommandDef = {
|
|
|
811
1106
|
}
|
|
812
1107
|
if (action === "rearm") {
|
|
813
1108
|
const id = requireArg(args[1], "Usage: gloom alerts rearm <id>", ctx);
|
|
814
|
-
const
|
|
815
|
-
const rule =
|
|
816
|
-
if (!
|
|
817
|
-
|
|
818
|
-
|
|
1109
|
+
const level = alerts.find((alert) => alert.id === id) ?? null;
|
|
1110
|
+
const rule = level ? null : events.rules.find((entry) => entry.id === id) ?? null;
|
|
1111
|
+
if (!level && !rule && alertsError) ctx.fail(alertsError);
|
|
1112
|
+
// An alert still waiting to go off has nothing to re-arm, and is left as it is.
|
|
1113
|
+
const active = level ? level.status === "active" : rule ? eventRuleArmed(rule, now) : false;
|
|
1114
|
+
if (level && !active) await save({ alerts: alerts.map((alert) => alert.id === id ? rearmAlert(alert) : alert) });
|
|
1115
|
+
if (rule && !active) {
|
|
819
1116
|
// A scheduled alert alerts again for its coming event; any other event alert resumes.
|
|
820
1117
|
const next = readScheduledRule(rule) ? rearmScheduledAlert(rule)
|
|
821
1118
|
: rule.kind === "iv_level" && rule.triggeredAt ? rearmIvLevelAlert(rule)
|
|
822
1119
|
: rule.status === "paused" ? toggleEventAlert(rule) : rule;
|
|
823
1120
|
await save({ events: requireEvents().map((entry) => entry.id === id ? next : entry) });
|
|
824
1121
|
}
|
|
825
|
-
const found =
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
: cliStyles.muted(`
|
|
1122
|
+
const found = level ?? rule;
|
|
1123
|
+
if (found && !active) record("rearmed", found);
|
|
1124
|
+
ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun && !!found && !active, dryRun: ctx.cliOptions.dryRun, id, ...active ? { active: true } : {} } }, {
|
|
1125
|
+
text: (data) => !found ? cliStyles.muted(`No alert with ID ${id}.`)
|
|
1126
|
+
: active ? cliStyles.muted(`Alert ${id} is already active; nothing to re-arm.`)
|
|
1127
|
+
: `Re-armed alert ${id}.${dryRunNote(data.dryRun)}`,
|
|
830
1128
|
});
|
|
831
1129
|
return;
|
|
832
1130
|
}
|