gloomberb 0.18.3 → 0.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1018) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +11 -11
  3. package/bin/{gloomberb → gloom} +3 -1
  4. package/package.json +6 -2
  5. package/patches/electrobun@1.18.1.patch +32 -1
  6. package/src/account/auth-model.ts +7 -3
  7. package/src/account/checkout-handoff.tsx +225 -0
  8. package/src/account/device-signin-dialog.tsx +2 -2
  9. package/src/account/device-signin.ts +2 -2
  10. package/src/account/upgrade-dialog.tsx +1 -1
  11. package/src/api-client/askg.ts +2 -2
  12. package/src/api-client/auth.ts +11 -6
  13. package/src/api-client/catalysts.ts +2 -1
  14. package/src/api-client/central-bank-rates.ts +6 -0
  15. package/src/api-client/collections.ts +5 -1
  16. package/src/api-client/cot.ts +2 -0
  17. package/src/api-client/crypto-etf-flows.ts +37 -0
  18. package/src/api-client/crypto-markets.ts +60 -0
  19. package/src/api-client/data.ts +98 -5
  20. package/src/api-client/doe.ts +15 -0
  21. package/src/api-client/equity-screener.ts +28 -0
  22. package/src/api-client/exchange-announcements.ts +38 -0
  23. package/src/api-client/fund-facts.ts +39 -0
  24. package/src/api-client/futures-curve.ts +9 -1
  25. package/src/api-client/hiring.ts +301 -0
  26. package/src/api-client/index.ts +182 -16
  27. package/src/api-client/live-scan.ts +91 -0
  28. package/src/api-client/market-discovery.ts +14 -0
  29. package/src/api-client/members.ts +18 -0
  30. package/src/api-client/mna.ts +6 -0
  31. package/src/api-client/paths.ts +18 -1
  32. package/src/api-client/request.ts +224 -26
  33. package/src/api-client/research-activity.ts +3 -10
  34. package/src/api-client/short-side-publications.ts +37 -0
  35. package/src/api-client/socket.ts +64 -4
  36. package/src/api-client/swap-curve.ts +35 -0
  37. package/src/api-client/themes.ts +7 -1
  38. package/src/api-client/types.ts +96 -2
  39. package/src/app/keybindings/index.ts +3 -0
  40. package/src/app/keybindings/labels.ts +18 -1
  41. package/src/app/keybindings/resolve.ts +14 -0
  42. package/src/app/pane-runtime/plugin-bindings.ts +9 -2
  43. package/src/app/runtime/broker-import.ts +43 -26
  44. package/src/app/runtime/desktop-deeplink.ts +5 -5
  45. package/src/app/runtime/plugin-bindings.ts +2 -2
  46. package/src/app/runtime/startup.ts +13 -0
  47. package/src/app/star-prompt/status-line.tsx +2 -2
  48. package/src/architecture/core-builtin-allowlist.json +5 -2
  49. package/src/architecture/ui-literals-allowlist.json +3 -3
  50. package/src/architecture/ui-tokens-allowlist.json +1 -1
  51. package/src/brokers/broker-portfolio-sync.ts +37 -0
  52. package/src/brokers/directory.ts +11 -0
  53. package/src/brokers/logos.ts +3 -1
  54. package/src/brokers/signed-in/adapter.ts +304 -26
  55. package/src/brokers/signed-in/client.ts +14 -2
  56. package/src/brokers/signed-in/connect.ts +10 -2
  57. package/src/brokers/sync-broker-instance.ts +9 -1
  58. package/src/capabilities/chart-series.ts +22 -6
  59. package/src/cli/bank-section.ts +1 -1
  60. package/src/cli/base-converter.ts +23 -14
  61. package/src/cli/central-bank-events.ts +1 -1
  62. package/src/cli/command-options.ts +59 -4
  63. package/src/cli/commands/api.ts +9 -6
  64. package/src/cli/commands/broker.ts +2 -2
  65. package/src/cli/commands/command-utils.ts +2 -2
  66. package/src/cli/commands/crypto-hints.ts +7 -20
  67. package/src/cli/commands/fred-series.ts +9 -2
  68. package/src/cli/commands/keybindings.ts +25 -2
  69. package/src/cli/commands/market.ts +1177 -278
  70. package/src/cli/commands/news-hints.ts +2 -2
  71. package/src/cli/commands/news-search.ts +59 -6
  72. package/src/cli/commands/options-chain.ts +59 -3
  73. package/src/cli/commands/options-leaps.ts +41 -26
  74. package/src/cli/commands/overview.ts +222 -61
  75. package/src/cli/commands/plugins.ts +9 -9
  76. package/src/cli/commands/remote.ts +13 -10
  77. package/src/cli/commands/search.ts +82 -46
  78. package/src/cli/commands/system.ts +27 -19
  79. package/src/cli/commands/themes.ts +1 -1
  80. package/src/cli/commands/ticker.ts +194 -48
  81. package/src/cli/context.ts +3 -2
  82. package/src/cli/desktop-pane-shot-view.ts +2 -2
  83. package/src/cli/desktop-pane-shot.ts +1 -1
  84. package/src/cli/earnings-calendar.ts +4 -4
  85. package/src/cli/entry.ts +18 -1
  86. package/src/cli/errors.ts +21 -3
  87. package/src/cli/exit-codes.ts +1 -1
  88. package/src/cli/export-source.ts +38 -4
  89. package/src/cli/freshness.ts +2 -8
  90. package/src/cli/fx-forward.ts +130 -0
  91. package/src/cli/fx-pair.ts +10 -9
  92. package/src/cli/glossary.ts +113 -20
  93. package/src/cli/help.ts +19 -13
  94. package/src/cli/history-export.ts +126 -0
  95. package/src/cli/history-rows.ts +226 -50
  96. package/src/cli/index-roots.ts +5 -45
  97. package/src/cli/index.ts +24 -18
  98. package/src/cli/known-gaps.ts +123 -27
  99. package/src/cli/listing-arg.ts +316 -46
  100. package/src/cli/name-entries.ts +30 -18
  101. package/src/cli/native-smoke.ts +2 -0
  102. package/src/cli/not-a-ticker.ts +19 -9
  103. package/src/cli/opentui-libc.ts +5 -0
  104. package/src/cli/options.ts +12 -0
  105. package/src/cli/pane-functions/capabilities.ts +75 -20
  106. package/src/cli/pane-functions/catalog.ts +24 -23
  107. package/src/cli/pane-functions/discovery.ts +1 -1
  108. package/src/cli/pane-functions/dom.ts +10 -7
  109. package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
  110. package/src/cli/pane-functions/freshness.ts +193 -30
  111. package/src/cli/pane-functions/function-help.ts +76 -30
  112. package/src/cli/pane-functions/headless.ts +59 -12
  113. package/src/cli/pane-functions/index.ts +28 -19
  114. package/src/cli/pane-functions/market-session.ts +15 -5
  115. package/src/cli/pane-functions/options.ts +22 -2
  116. package/src/cli/pane-functions/report-notices.ts +12 -4
  117. package/src/cli/pane-functions/report.ts +10 -6
  118. package/src/cli/pane-functions/resolver.ts +64 -11
  119. package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
  120. package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
  121. package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
  122. package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
  123. package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
  124. package/src/cli/pane-functions/screenshot-payload.ts +453 -0
  125. package/src/cli/pane-functions/screenshot-render.ts +316 -0
  126. package/src/cli/pane-functions/screenshot-types.ts +162 -0
  127. package/src/cli/pane-functions/shot-options.ts +8 -3
  128. package/src/cli/peso-dollar-rates.ts +79 -0
  129. package/src/cli/quote-answer.ts +45 -4
  130. package/src/cli/report-tables.ts +75 -19
  131. package/src/cli/restore-plugins.ts +42 -15
  132. package/src/cli/result.ts +164 -23
  133. package/src/cli/series-search.ts +5 -5
  134. package/src/cli/statement-filings.ts +19 -0
  135. package/src/cli/suffixed-listing-rows.ts +49 -0
  136. package/src/cli/symbol-args.ts +50 -15
  137. package/src/cli/symbol-cells.ts +120 -0
  138. package/src/cli/symbol-list.ts +198 -68
  139. package/src/cli/symbol-rows.ts +131 -25
  140. package/src/cli/ticker-resolution.ts +108 -11
  141. package/src/cli/treasury-yields.ts +5 -5
  142. package/src/cli/types.ts +2 -0
  143. package/src/cli/uncovered-instrument.ts +3 -3
  144. package/src/cli/unknown-command.ts +6 -6
  145. package/src/components/chart/composite/chart-layout.ts +131 -0
  146. package/src/components/chart/composite/composite-chart.tsx +395 -20
  147. package/src/components/chart/composite/format.ts +27 -0
  148. package/src/components/chart/composite/interactions.ts +7 -2
  149. package/src/components/chart/composite/legend.tsx +75 -3
  150. package/src/components/chart/composite/levels.ts +7 -0
  151. package/src/components/chart/composite/panel-gestures.ts +41 -0
  152. package/src/components/chart/composite/panel-labels.ts +2 -1
  153. package/src/components/chart/composite/panel-series.ts +1 -0
  154. package/src/components/chart/composite/panel-surface.tsx +314 -13
  155. package/src/components/chart/composite/price-axis-labels.tsx +27 -3
  156. package/src/components/chart/composite/range-statistics.ts +62 -0
  157. package/src/components/chart/composite/rasterizer.ts +44 -19
  158. package/src/components/chart/composite/scene.ts +20 -4
  159. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  160. package/src/components/chart/composite/text-renderer.ts +48 -21
  161. package/src/components/chart/composite/toolbar.tsx +52 -5
  162. package/src/components/chart/composite/types.ts +17 -0
  163. package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
  164. package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
  165. package/src/components/chart/composite/use-composite-viewport.ts +12 -3
  166. package/src/components/chart/composite/value-scale.ts +149 -0
  167. package/src/components/chart/composite/volume-profile-paint.ts +6 -3
  168. package/src/components/chart/core/pointer.ts +2 -0
  169. package/src/components/chart-table/index.ts +2 -0
  170. package/src/components/chart-table/read.tsx +74 -0
  171. package/src/components/command-bar/commands/collection-list.ts +41 -19
  172. package/src/components/command-bar/commands/collection.ts +9 -3
  173. package/src/components/command-bar/commands/direct/index.ts +1 -1
  174. package/src/components/command-bar/commands/direct/runtime.ts +3 -2
  175. package/src/components/command-bar/pane-templates/items.ts +59 -6
  176. package/src/components/command-bar/pane-templates/workflow.ts +10 -1
  177. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  178. package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
  179. package/src/components/command-bar/routes/root/provider-search.ts +42 -11
  180. package/src/components/command-bar/routes/root/results.ts +18 -4
  181. package/src/components/command-bar/routes/root/runtime.ts +15 -3
  182. package/src/components/command-bar/routes/root/search-providers.ts +25 -2
  183. package/src/components/command-bar/routes/root/selection.ts +6 -1
  184. package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
  185. package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
  186. package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
  187. package/src/components/command-bar/selection-runtime.ts +50 -10
  188. package/src/components/command-bar/surface/index.tsx +24 -2
  189. package/src/components/command-bar/view-model.ts +7 -0
  190. package/src/components/command-bar/workflow/alert.ts +1 -1
  191. package/src/components/command-bar/workflow/broker.ts +2 -2
  192. package/src/components/command-bar/workflow/collection-actions.ts +46 -7
  193. package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
  194. package/src/components/command-bar/workflow/fields.ts +8 -2
  195. package/src/components/command-bar/workflow/ops.ts +18 -4
  196. package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
  197. package/src/components/command-bar/workflow/submit.ts +3 -2
  198. package/src/components/command-bar/workflow/tickers.ts +5 -1
  199. package/src/components/company-logo.tsx +39 -0
  200. package/src/components/data-table/sections.ts +4 -2
  201. package/src/components/form-modal/confirm.tsx +2 -1
  202. package/src/components/form-modal/request.ts +5 -0
  203. package/src/components/form-modal/routes.ts +4 -1
  204. package/src/components/index.ts +12 -6
  205. package/src/components/layout/header.tsx +33 -46
  206. package/src/components/layout/market-summary.ts +9 -6
  207. package/src/components/layout/pane/capture-status.ts +1 -1
  208. package/src/components/layout/pane/footer/index.tsx +1 -0
  209. package/src/components/layout/pane/footer/model.ts +38 -5
  210. package/src/components/layout/shell/chrome.ts +19 -6
  211. package/src/components/layout/shell/index.tsx +2 -1
  212. package/src/components/layout/shell/pane/layers.tsx +19 -1
  213. package/src/components/layout/status-bar.tsx +2 -2
  214. package/src/components/market-board/index.tsx +11 -3
  215. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  216. package/src/components/onboarding/onboarding-wizard.tsx +1 -0
  217. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
  218. package/src/components/onboarding/portfolio-step/positions-panel.tsx +2 -2
  219. package/src/components/onboarding/wizard-broker-sync.ts +4 -1
  220. package/src/components/pane-settings-dialog/desktop.tsx +20 -7
  221. package/src/components/pane-settings-dialog/tui.tsx +17 -9
  222. package/src/components/pane-settings-dialog/value.ts +8 -1
  223. package/src/components/pane-settings-dialog.tsx +44 -13
  224. package/src/components/sign-in-code-panel.tsx +27 -19
  225. package/src/components/ui/column-grid.tsx +48 -4
  226. package/src/components/ui/country-flag-art.tsx +180 -0
  227. package/src/components/ui/country-flag.tsx +118 -12
  228. package/src/components/ui/data-table/opentui/index.tsx +61 -2
  229. package/src/components/ui/data-table/opentui/model.ts +109 -2
  230. package/src/components/ui/data-table/types.ts +1 -1
  231. package/src/components/ui/display.tsx +10 -2
  232. package/src/components/ui/index.ts +3 -1
  233. package/src/components/ui/logo-mark.tsx +11 -2
  234. package/src/components/ui/query-bar.tsx +1 -1
  235. package/src/components/ui/row-notes.ts +79 -0
  236. package/src/core/state/app/layout.ts +6 -4
  237. package/src/core/state/app/state.ts +9 -0
  238. package/src/core/state/app/types.ts +3 -0
  239. package/src/data/cached-query.ts +8 -1
  240. package/src/data/config/chart-settings.ts +3 -3
  241. package/src/data/config/home.ts +10 -2
  242. package/src/data/config/layout.ts +4 -1
  243. package/src/data/config/store/node.ts +4 -0
  244. package/src/data/config/store/normalize.ts +13 -3
  245. package/src/data/config/store/portfolio-as-of-column.ts +2 -2
  246. package/src/data/json-storage.ts +2 -0
  247. package/src/data/saved-copies.ts +123 -0
  248. package/src/docs/snapshot.json +422 -112
  249. package/src/i18n/es.ts +30 -13
  250. package/src/i18n/ja.ts +30 -13
  251. package/src/i18n/ko.ts +30 -13
  252. package/src/i18n/zh-cn.ts +30 -13
  253. package/src/i18n/zh-tw.ts +30 -13
  254. package/src/layout/desks.ts +1 -1
  255. package/src/layout/launch-layout.ts +1 -1
  256. package/src/layout/pane-manager/floating-actions.ts +5 -3
  257. package/src/layout/pane-manager/floating.ts +43 -1
  258. package/src/layout/pane-manager/index.ts +2 -0
  259. package/src/layout/selected-ticker.ts +74 -0
  260. package/src/market-data/coordinator/financials.ts +10 -2
  261. package/src/market-data/coordinator/index.ts +3 -1
  262. package/src/market-data/coordinator/quotes.ts +4 -2
  263. package/src/market-data/depositary-pair.ts +251 -0
  264. package/src/market-data/futures-board.ts +1 -1
  265. package/src/market-data/futures-contract.ts +124 -40
  266. package/src/market-data/fx-closes.ts +17 -2
  267. package/src/market-data/hooks.tsx +19 -11
  268. package/src/market-data/market/change-basis.ts +81 -11
  269. package/src/market-data/market/dated-previous-close.ts +203 -0
  270. package/src/market-data/market/freshness.ts +34 -5
  271. package/src/market-data/market/futures-week.ts +68 -0
  272. package/src/market-data/market/period-return.ts +163 -0
  273. package/src/market-data/market/status.ts +16 -4
  274. package/src/market-data/market/trading-sessions.ts +13 -0
  275. package/src/market-data/market/year-to-date.ts +8 -13
  276. package/src/market-data/options-alternatives.ts +60 -0
  277. package/src/market-data/performance.ts +20 -14
  278. package/src/market-data/perpetual-premium.ts +32 -0
  279. package/src/market-data/published-apac-sessions.ts +2 -1
  280. package/src/market-data/published-futures-sessions.ts +69 -0
  281. package/src/market-data/quotes/contributions.ts +25 -3
  282. package/src/market-data/quotes/freshness.ts +42 -0
  283. package/src/market-data/quotes/intraday-bar.ts +37 -0
  284. package/src/market-data/quotes/regular-session-valuation.ts +13 -8
  285. package/src/market-data/quotes/resolution.ts +7 -2
  286. package/src/market-data/selectors.ts +3 -0
  287. package/src/market-data/tax-equivalent.ts +1 -1
  288. package/src/market-data/ticker-price-returns.ts +3 -1
  289. package/src/news/aggregator.ts +5 -1
  290. package/src/news/news-model.ts +4 -0
  291. package/src/news/types.ts +11 -0
  292. package/src/notifications/app-notifier.ts +1 -1
  293. package/src/notifications/notification-log.ts +6 -4
  294. package/src/plugins/auto-update.ts +2 -2
  295. package/src/plugins/builtin/account-management/index.tsx +1 -0
  296. package/src/plugins/builtin/account-management/pane.tsx +9 -8
  297. package/src/plugins/builtin/alerts/alert-engine.ts +34 -20
  298. package/src/plugins/builtin/alerts/calendar-events.ts +429 -0
  299. package/src/plugins/builtin/alerts/cli.ts +533 -85
  300. package/src/plugins/builtin/alerts/command.ts +12 -10
  301. package/src/plugins/builtin/alerts/constants.ts +1 -1
  302. package/src/plugins/builtin/alerts/event-rows.ts +9 -5
  303. package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
  304. package/src/plugins/builtin/alerts/events.ts +107 -5
  305. package/src/plugins/builtin/alerts/fired-log.ts +119 -0
  306. package/src/plugins/builtin/alerts/format.ts +10 -3
  307. package/src/plugins/builtin/alerts/headless.ts +107 -41
  308. package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
  309. package/src/plugins/builtin/alerts/history-rows.ts +92 -0
  310. package/src/plugins/builtin/alerts/history.ts +2 -0
  311. package/src/plugins/builtin/alerts/index.tsx +107 -30
  312. package/src/plugins/builtin/alerts/iv-level.ts +365 -0
  313. package/src/plugins/builtin/alerts/levels.ts +3 -3
  314. package/src/plugins/builtin/alerts/live.ts +3 -3
  315. package/src/plugins/builtin/alerts/metric-command.ts +61 -0
  316. package/src/plugins/builtin/alerts/metric-data.ts +70 -0
  317. package/src/plugins/builtin/alerts/metric-model.ts +175 -0
  318. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  319. package/src/plugins/builtin/alerts/pane.tsx +130 -49
  320. package/src/plugins/builtin/alerts/price.ts +35 -0
  321. package/src/plugins/builtin/alerts/quotes.ts +24 -0
  322. package/src/plugins/builtin/alerts/research-builder.ts +84 -11
  323. package/src/plugins/builtin/alerts/research-rules.ts +76 -3
  324. package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
  325. package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
  326. package/src/plugins/builtin/alerts/scheduled.ts +188 -29
  327. package/src/plugins/builtin/alerts/storage.ts +38 -12
  328. package/src/plugins/builtin/alerts/targets.ts +12 -3
  329. package/src/plugins/builtin/alerts/types.ts +3 -0
  330. package/src/plugins/builtin/analytics/index.tsx +8 -2
  331. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  332. package/src/plugins/builtin/analytics/risk-book.ts +215 -0
  333. package/src/plugins/builtin/analytics/risk-client.ts +13 -6
  334. package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
  335. package/src/plugins/builtin/analytics/risk-math.ts +6 -3
  336. package/src/plugins/builtin/analytics/risk-model.ts +297 -104
  337. package/src/plugins/builtin/analytics/risk-options.ts +17 -2
  338. package/src/plugins/builtin/analytics/risk-pane.tsx +97 -36
  339. package/src/plugins/builtin/attention/pane.tsx +6 -2
  340. package/src/plugins/builtin/awards/headless.ts +7 -3
  341. package/src/plugins/builtin/awards/model.ts +18 -0
  342. package/src/plugins/builtin/awards/pane.tsx +6 -3
  343. package/src/plugins/builtin/backtest/headless.ts +13 -8
  344. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  345. package/src/plugins/builtin/backtest/presets.ts +29 -3
  346. package/src/plugins/builtin/backtest/rules.ts +41 -5
  347. package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
  348. package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
  349. package/src/plugins/builtin/broker-manager/footer.ts +27 -11
  350. package/src/plugins/builtin/broker-manager/index.tsx +24 -14
  351. package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
  352. package/src/plugins/builtin/broker-manager/model.ts +18 -3
  353. package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
  354. package/src/plugins/builtin/broker-manager/table.ts +28 -12
  355. package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
  356. package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
  357. package/src/plugins/builtin/broker-trading/controller.ts +66 -24
  358. package/src/plugins/builtin/broker-trading/dock.tsx +22 -4
  359. package/src/plugins/builtin/broker-trading/headless.ts +2 -1
  360. package/src/plugins/builtin/broker-trading/index.tsx +19 -1
  361. package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
  362. package/src/plugins/builtin/broker-trading/order-feed.ts +83 -10
  363. package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
  364. package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
  365. package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
  366. package/src/plugins/builtin/broker-trading/pane.tsx +111 -24
  367. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
  368. package/src/plugins/builtin/broker-trading/ticket.tsx +112 -31
  369. package/src/plugins/builtin/buildout/cells.tsx +4 -0
  370. package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
  371. package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
  372. package/src/plugins/builtin/buildout/pane/index.tsx +25 -12
  373. package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
  374. package/src/plugins/builtin/builtin-plugin-meta.ts +2 -2
  375. package/src/plugins/builtin/catalysts/detail.tsx +3 -2
  376. package/src/plugins/builtin/catalysts/index.tsx +1 -1
  377. package/src/plugins/builtin/catalysts/model.ts +51 -5
  378. package/src/plugins/builtin/catalysts/pane.tsx +6 -8
  379. package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
  380. package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
  381. package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
  382. package/src/plugins/builtin/changelog/index.tsx +1 -0
  383. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +4 -1
  384. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +3 -2
  385. package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
  386. package/src/plugins/builtin/chart-composer/cli-options.ts +7 -10
  387. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -3
  388. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -0
  389. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  390. package/src/plugins/builtin/chart-composer/editor.tsx +9 -4
  391. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  392. package/src/plugins/builtin/chart-composer/headless-schema.ts +42 -29
  393. package/src/plugins/builtin/chart-composer/headless.ts +266 -37
  394. package/src/plugins/builtin/chart-composer/index.tsx +12 -2
  395. package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
  396. package/src/plugins/builtin/chart-composer/pane.tsx +144 -30
  397. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  398. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  399. package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
  400. package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
  401. package/src/plugins/builtin/chart-composer/session-history.ts +31 -28
  402. package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
  403. package/src/plugins/builtin/chart-composer/settings.ts +31 -19
  404. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +44 -31
  405. package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
  406. package/src/plugins/builtin/chat/attachments/model.ts +3 -1
  407. package/src/plugins/builtin/chat/content/command-suggestions.ts +92 -0
  408. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  409. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  410. package/src/plugins/builtin/chat/content/index.tsx +51 -22
  411. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  412. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  413. package/src/plugins/builtin/chat/index.tsx +23 -0
  414. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  415. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  416. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  417. package/src/plugins/builtin/chat/pane-links/commands.ts +265 -0
  418. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  419. package/src/plugins/builtin/chat/pane-links/open.ts +9 -2
  420. package/src/plugins/builtin/chat/people.ts +1 -1
  421. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  422. package/src/plugins/builtin/clinical-trials/client.ts +5 -2
  423. package/src/plugins/builtin/clinical-trials/model.ts +13 -4
  424. package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
  425. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
  426. package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
  427. package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
  428. package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
  429. package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
  430. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  431. package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
  432. package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
  433. package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
  434. package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
  435. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  436. package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
  437. package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
  438. package/src/plugins/builtin/commodity-balances/pane.tsx +11 -4
  439. package/src/plugins/builtin/company-attention/app-rank.tsx +7 -5
  440. package/src/plugins/builtin/company-attention/building-model.ts +222 -0
  441. package/src/plugins/builtin/company-attention/building.tsx +172 -0
  442. package/src/plugins/builtin/company-attention/client.ts +117 -1
  443. package/src/plugins/builtin/company-attention/evidence.ts +16 -3
  444. package/src/plugins/builtin/company-attention/headless.ts +101 -5
  445. package/src/plugins/builtin/company-attention/index.tsx +6 -3
  446. package/src/plugins/builtin/company-attention/model.ts +33 -7
  447. package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
  448. package/src/plugins/builtin/company-attention/moves.tsx +159 -0
  449. package/src/plugins/builtin/company-attention/pane.tsx +79 -23
  450. package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
  451. package/src/plugins/builtin/company-attention/signals.tsx +160 -0
  452. package/src/plugins/builtin/company-kpis/headless.ts +3 -2
  453. package/src/plugins/builtin/company-kpis/model.ts +2 -2
  454. package/src/plugins/builtin/company-kpis/pane.tsx +1 -1
  455. package/src/plugins/builtin/composite-plugins.ts +2 -1
  456. package/src/plugins/builtin/congress-trades/headless.ts +1 -1
  457. package/src/plugins/builtin/connections/index.ts +1 -0
  458. package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
  459. package/src/plugins/builtin/correlation/headless.ts +5 -3
  460. package/src/plugins/builtin/cot/headless.ts +28 -6
  461. package/src/plugins/builtin/cot/model.ts +12 -0
  462. package/src/plugins/builtin/cot/pane.tsx +3 -2
  463. package/src/plugins/builtin/cpi/headless.ts +5 -1
  464. package/src/plugins/builtin/cpi/pane.tsx +11 -9
  465. package/src/plugins/builtin/credit-boards/model.ts +2 -0
  466. package/src/plugins/builtin/credit-documents/headless.ts +1 -1
  467. package/src/plugins/builtin/crypto-board/client.ts +86 -0
  468. package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
  469. package/src/plugins/builtin/crypto-board/defi.ts +239 -0
  470. package/src/plugins/builtin/crypto-board/headless.ts +117 -6
  471. package/src/plugins/builtin/crypto-board/index.tsx +8 -3
  472. package/src/plugins/builtin/crypto-board/model.ts +40 -8
  473. package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
  474. package/src/plugins/builtin/crypto-board/series.ts +19 -1
  475. package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
  476. package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
  477. package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
  478. package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
  479. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
  480. package/src/plugins/builtin/debug/index.tsx +1 -0
  481. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  482. package/src/plugins/builtin/docs/index.tsx +1 -1
  483. package/src/plugins/builtin/docs/pane.tsx +2 -2
  484. package/src/plugins/builtin/docs/targets.ts +1 -1
  485. package/src/plugins/builtin/doe/client.ts +5 -1
  486. package/src/plugins/builtin/doe/headless.ts +12 -6
  487. package/src/plugins/builtin/doe/model.ts +43 -6
  488. package/src/plugins/builtin/doe/pane.tsx +18 -11
  489. package/src/plugins/builtin/earnings/board-model.ts +13 -6
  490. package/src/plugins/builtin/earnings/board-table.ts +4 -1
  491. package/src/plugins/builtin/earnings/board.tsx +253 -50
  492. package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
  493. package/src/plugins/builtin/earnings/calendar.tsx +338 -0
  494. package/src/plugins/builtin/earnings/client.ts +4 -3
  495. package/src/plugins/builtin/earnings/headless.ts +53 -15
  496. package/src/plugins/builtin/earnings/history-model.ts +53 -36
  497. package/src/plugins/builtin/earnings/index.tsx +15 -22
  498. package/src/plugins/builtin/earnings/model.ts +17 -12
  499. package/src/plugins/builtin/earnings/scope.ts +89 -0
  500. package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
  501. package/src/plugins/builtin/earnings-calls/data.ts +146 -2
  502. package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
  503. package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
  504. package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
  505. package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
  506. package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
  507. package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
  508. package/src/plugins/builtin/econ/calendar-model.ts +144 -27
  509. package/src/plugins/builtin/econ/detail-view.tsx +48 -10
  510. package/src/plugins/builtin/econ/figures.ts +72 -0
  511. package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
  512. package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
  513. package/src/plugins/builtin/econ/index.tsx +144 -27
  514. package/src/plugins/builtin/econ/print-watch.ts +2 -1
  515. package/src/plugins/builtin/econ/types.ts +34 -1
  516. package/src/plugins/builtin/econ-statistics/client.ts +16 -3
  517. package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
  518. package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
  519. package/src/plugins/builtin/econ-statistics/headless.ts +87 -28
  520. package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
  521. package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
  522. package/src/plugins/builtin/econ-statistics/stats.ts +104 -0
  523. package/src/plugins/builtin/econ-statistics/view.ts +19 -1
  524. package/src/plugins/builtin/equity-screener/client.ts +21 -3
  525. package/src/plugins/builtin/equity-screener/headless.ts +68 -6
  526. package/src/plugins/builtin/equity-screener/live.ts +1 -1
  527. package/src/plugins/builtin/equity-screener/model.ts +6 -0
  528. package/src/plugins/builtin/equity-screener/pane.tsx +47 -12
  529. package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
  530. package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
  531. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
  532. package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
  533. package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
  534. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  535. package/src/plugins/builtin/exposure/headless.ts +51 -6
  536. package/src/plugins/builtin/exposure/index.tsx +28 -2
  537. package/src/plugins/builtin/exposure/model.ts +20 -1
  538. package/src/plugins/builtin/exposure/pane.tsx +10 -5
  539. package/src/plugins/builtin/fear-greed/index.tsx +1 -1
  540. package/src/plugins/builtin/filing-events/index.tsx +1 -1
  541. package/src/plugins/builtin/futures/index.tsx +1 -0
  542. package/src/plugins/builtin/futures/model.ts +12 -10
  543. package/src/plugins/builtin/futures/returns.ts +8 -36
  544. package/src/plugins/builtin/futures/table.tsx +3 -3
  545. package/src/plugins/builtin/futures-curve/client.ts +40 -9
  546. package/src/plugins/builtin/futures-curve/headless.ts +39 -17
  547. package/src/plugins/builtin/futures-curve/model.ts +258 -69
  548. package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
  549. package/src/plugins/builtin/fx-matrix/client.ts +20 -8
  550. package/src/plugins/builtin/fx-matrix/headless.ts +37 -16
  551. package/src/plugins/builtin/fx-matrix/index.tsx +25 -13
  552. package/src/plugins/builtin/fx-matrix/live-legs.ts +34 -0
  553. package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
  554. package/src/plugins/builtin/gpu/headless.ts +9 -4
  555. package/src/plugins/builtin/help/index.tsx +13 -3
  556. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  557. package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
  558. package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
  559. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  560. package/src/plugins/builtin/holders/client.ts +13 -1
  561. package/src/plugins/builtin/holders/format.ts +28 -17
  562. package/src/plugins/builtin/holders/headless.ts +46 -7
  563. package/src/plugins/builtin/holders/index-holders.ts +264 -0
  564. package/src/plugins/builtin/holders/pane.tsx +79 -15
  565. package/src/plugins/builtin/holders/report-rows.ts +35 -7
  566. package/src/plugins/builtin/holders/table-model.ts +2 -1
  567. package/src/plugins/builtin/holders/treemap.tsx +2 -2
  568. package/src/plugins/builtin/insider/headless.ts +1 -1
  569. package/src/plugins/builtin/insider/signals-client.ts +1 -1
  570. package/src/plugins/builtin/insider/signals.tsx +1 -1
  571. package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
  572. package/src/plugins/builtin/iv-history/client.ts +20 -0
  573. package/src/plugins/builtin/iv-history/headless.ts +7 -4
  574. package/src/plugins/builtin/iv-history/index.tsx +1 -1
  575. package/src/plugins/builtin/iv-history/model.ts +13 -4
  576. package/src/plugins/builtin/iv-history/series.ts +3 -1
  577. package/src/plugins/builtin/jobs/lock-note.ts +1 -1
  578. package/src/plugins/builtin/jobs/model.ts +4 -15
  579. package/src/plugins/builtin/jobs/pane.tsx +7 -5
  580. package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
  581. package/src/plugins/builtin/jodi/pane.tsx +10 -4
  582. package/src/plugins/builtin/layout-manager/index.tsx +1 -0
  583. package/src/plugins/builtin/macro-day/headless.ts +20 -7
  584. package/src/plugins/builtin/macro-day/index.tsx +3 -2
  585. package/src/plugins/builtin/macro-day/model.ts +29 -8
  586. package/src/plugins/builtin/macro-day/pane.tsx +51 -13
  587. package/src/plugins/builtin/macro-day/releases.ts +55 -13
  588. package/src/plugins/builtin/market-heatmap/data.ts +9 -2
  589. package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
  590. package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
  591. package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
  592. package/src/plugins/builtin/market-heatmap/model.ts +10 -7
  593. package/src/plugins/builtin/market-movers/client.ts +33 -19
  594. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  595. package/src/plugins/builtin/market-movers/headless.ts +8 -2
  596. package/src/plugins/builtin/market-movers/index.tsx +16 -10
  597. package/src/plugins/builtin/market-movers/screener.ts +29 -8
  598. package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
  599. package/src/plugins/builtin/market-movers/session-table.ts +4 -2
  600. package/src/plugins/builtin/market-movers/table.ts +4 -1
  601. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  602. package/src/plugins/builtin/market-valuation/view.ts +5 -0
  603. package/src/plugins/builtin/members/breadth-pane.tsx +81 -0
  604. package/src/plugins/builtin/members/client.ts +19 -4
  605. package/src/plugins/builtin/members/headless.ts +24 -10
  606. package/src/plugins/builtin/members/index.tsx +9 -9
  607. package/src/plugins/builtin/members/model.ts +23 -4
  608. package/src/plugins/builtin/members/pane.tsx +31 -14
  609. package/src/plugins/builtin/mna/client.ts +1 -0
  610. package/src/plugins/builtin/mna/detail.tsx +20 -32
  611. package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
  612. package/src/plugins/builtin/mna/distress/model.ts +3 -2
  613. package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
  614. package/src/plugins/builtin/mna/headless.ts +6 -13
  615. package/src/plugins/builtin/mna/model.ts +93 -15
  616. package/src/plugins/builtin/mna/pane.tsx +5 -4
  617. package/src/plugins/builtin/news/headless.ts +12 -6
  618. package/src/plugins/builtin/news/index.tsx +10 -1
  619. package/src/plugins/builtin/news/wire/index.tsx +3 -2
  620. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
  621. package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
  622. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
  623. package/src/plugins/builtin/notes/cli.ts +49 -14
  624. package/src/plugins/builtin/openfda/pane.tsx +1 -1
  625. package/src/plugins/builtin/options/analytics.ts +26 -2
  626. package/src/plugins/builtin/options/carry.ts +2 -2
  627. package/src/plugins/builtin/options/index.tsx +1 -1
  628. package/src/plugins/builtin/options/model-spot.ts +5 -2
  629. package/src/plugins/builtin/options/strike-window.ts +2 -1
  630. package/src/plugins/builtin/options/table.ts +1 -7
  631. package/src/plugins/builtin/options/view.tsx +30 -13
  632. package/src/plugins/builtin/options-calculator/headless.ts +46 -7
  633. package/src/plugins/builtin/options-positioning/client.ts +19 -0
  634. package/src/plugins/builtin/options-positioning/headless.ts +54 -14
  635. package/src/plugins/builtin/options-positioning/index.tsx +3 -3
  636. package/src/plugins/builtin/options-positioning/model.ts +47 -8
  637. package/src/plugins/builtin/options-positioning/pane.tsx +68 -38
  638. package/src/plugins/builtin/options-scenario/client.ts +123 -28
  639. package/src/plugins/builtin/options-scenario/collar.ts +8 -6
  640. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
  641. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
  642. package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
  643. package/src/plugins/builtin/options-scenario/csp.ts +101 -21
  644. package/src/plugins/builtin/options-scenario/headless.ts +36 -15
  645. package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
  646. package/src/plugins/builtin/options-scenario/index.tsx +28 -3
  647. package/src/plugins/builtin/options-scenario/model.ts +13 -0
  648. package/src/plugins/builtin/options-scenario/pane.tsx +9 -5
  649. package/src/plugins/builtin/perps/equity-row.tsx +11 -3
  650. package/src/plugins/builtin/perps/freshness.ts +14 -9
  651. package/src/plugins/builtin/perps/headless.ts +3 -1
  652. package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
  653. package/src/plugins/builtin/perps/live-rows.ts +168 -0
  654. package/src/plugins/builtin/perps/markets-section.tsx +154 -0
  655. package/src/plugins/builtin/perps/pane.tsx +49 -26
  656. package/src/plugins/builtin/perps/venue-markets.ts +96 -0
  657. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
  658. package/src/plugins/builtin/plugin-marketplace/pane.tsx +143 -59
  659. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +72 -24
  660. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
  661. package/src/plugins/builtin/portfolio-list/cli/render.ts +54 -22
  662. package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
  663. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
  664. package/src/plugins/builtin/portfolio-list/headless.ts +7 -4
  665. package/src/plugins/builtin/portfolio-list/income.ts +13 -2
  666. package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
  667. package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
  668. package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
  669. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  670. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
  671. package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
  672. package/src/plugins/builtin/power/client.ts +4 -1
  673. package/src/plugins/builtin/power/evidence.ts +22 -7
  674. package/src/plugins/builtin/power/gas.tsx +5 -1
  675. package/src/plugins/builtin/power/pane.tsx +8 -1
  676. package/src/plugins/builtin/power/prices.tsx +10 -3
  677. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  678. package/src/plugins/builtin/research/analyst-headless.ts +25 -8
  679. package/src/plugins/builtin/research/analyst-model.ts +68 -3
  680. package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
  681. package/src/plugins/builtin/research/event-model.ts +3 -1
  682. package/src/plugins/builtin/research/event-sources.ts +12 -4
  683. package/src/plugins/builtin/research/index.tsx +2 -1
  684. package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
  685. package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
  686. package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
  687. package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
  688. package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
  689. package/src/plugins/builtin/research/relative-valuation-pane.tsx +80 -13
  690. package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
  691. package/src/plugins/builtin/reverse-dcf/model.ts +6 -2
  692. package/src/plugins/builtin/risk-factors/index.tsx +2 -2
  693. package/src/plugins/builtin/scanner/feed.ts +2 -2
  694. package/src/plugins/builtin/scanner/index.ts +24 -1
  695. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  696. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  697. package/src/plugins/builtin/scanner/live-headless.ts +114 -0
  698. package/src/plugins/builtin/scanner/live-model.ts +351 -0
  699. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  700. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  701. package/src/plugins/builtin/scanner/lock-note.ts +1 -1
  702. package/src/plugins/builtin/sec/forms.ts +13 -0
  703. package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
  704. package/src/plugins/builtin/sec/fund-facts.ts +68 -8
  705. package/src/plugins/builtin/sec/headless.ts +85 -8
  706. package/src/plugins/builtin/sec/index.tsx +58 -12
  707. package/src/plugins/builtin/sectors/client.ts +2 -1
  708. package/src/plugins/builtin/sectors/sector-model.ts +11 -15
  709. package/src/plugins/builtin/series-inventory.ts +3 -0
  710. package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
  711. package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
  712. package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
  713. package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
  714. package/src/plugins/builtin/shared/earnings-report.ts +3 -3
  715. package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
  716. package/src/plugins/builtin/shared/job-posting.ts +129 -0
  717. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  718. package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
  719. package/src/plugins/builtin/shared/report-freshness.ts +3 -0
  720. package/src/plugins/builtin/shared/research-trading.tsx +21 -0
  721. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  722. package/src/plugins/builtin/shared/series-cache.ts +2 -0
  723. package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
  724. package/src/plugins/builtin/shared/use-quote-board.ts +8 -2
  725. package/src/plugins/builtin/shared/volatility/pricing.ts +7 -1
  726. package/src/plugins/builtin/short-interest/client.ts +2 -0
  727. package/src/plugins/builtin/short-interest/headless.ts +41 -12
  728. package/src/plugins/builtin/short-interest/index.tsx +23 -1
  729. package/src/plugins/builtin/short-interest/model.ts +70 -19
  730. package/src/plugins/builtin/short-interest/pane.tsx +19 -2
  731. package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
  732. package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
  733. package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
  734. package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
  735. package/src/plugins/builtin/short-interest/schedule.ts +35 -2
  736. package/src/plugins/builtin/short-interest/surface.tsx +11 -4
  737. package/src/plugins/builtin/short-interest/types.ts +9 -0
  738. package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
  739. package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
  740. package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
  741. package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
  742. package/src/plugins/builtin/supply-chain/model.ts +51 -3
  743. package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
  744. package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
  745. package/src/plugins/builtin/themes/headless.ts +11 -6
  746. package/src/plugins/builtin/themes/model.ts +15 -5
  747. package/src/plugins/builtin/themes/pane.tsx +27 -8
  748. package/src/plugins/builtin/thirteenf/headless.ts +5 -5
  749. package/src/plugins/builtin/thirteenf/signals.ts +3 -1
  750. package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
  751. package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
  752. package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
  753. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
  754. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
  755. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
  756. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
  757. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
  758. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
  759. package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
  760. package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
  761. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
  762. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
  763. package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
  764. package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
  765. package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
  766. package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
  767. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
  768. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
  769. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
  770. package/src/plugins/builtin/ticker-detail/quote-footer.ts +14 -4
  771. package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
  772. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  773. package/src/plugins/builtin/vol-surface/client.ts +7 -4
  774. package/src/plugins/builtin/vol-surface/headless.ts +71 -13
  775. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  776. package/src/plugins/builtin/vol-surface/model.ts +26 -9
  777. package/src/plugins/builtin/vol-surface/pane.tsx +44 -7
  778. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  779. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  780. package/src/plugins/builtin/volatility/model.ts +5 -10
  781. package/src/plugins/builtin/volatility/pane.tsx +2 -2
  782. package/src/plugins/builtin/volatility/series.ts +26 -0
  783. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  784. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
  785. package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
  786. package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
  787. package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
  788. package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
  789. package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
  790. package/src/plugins/builtin/world-venue-map/index.ts +3 -26
  791. package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
  792. package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
  793. package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
  794. package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
  795. package/src/plugins/builtin/yield-curve/client.ts +25 -0
  796. package/src/plugins/builtin/yield-curve/curves.ts +7 -4
  797. package/src/plugins/builtin/yield-curve/headless.ts +38 -4
  798. package/src/plugins/builtin/yield-curve/history.ts +4 -3
  799. package/src/plugins/builtin/yield-curve/index.tsx +12 -6
  800. package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
  801. package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
  802. package/src/plugins/bundle.ts +1 -1
  803. package/src/plugins/catalog-browser.ts +15 -9
  804. package/src/plugins/compat.ts +3 -3
  805. package/src/plugins/dependencies.ts +75 -14
  806. package/src/plugins/extracted-plugins.ts +132 -34
  807. package/src/plugins/host-link.ts +149 -34
  808. package/src/plugins/installer.ts +35 -10
  809. package/src/plugins/loader.ts +206 -38
  810. package/src/plugins/manager-node.ts +2 -2
  811. package/src/plugins/marketplace/deactivation.ts +22 -0
  812. package/src/plugins/marketplace/model.ts +2 -2
  813. package/src/plugins/marketplace/packs.ts +1 -1
  814. package/src/plugins/ownership.ts +1 -1
  815. package/src/plugins/peer-retry.ts +155 -0
  816. package/src/plugins/registry/contributions.ts +3 -1
  817. package/src/plugins/registry/index.ts +4 -3
  818. package/src/plugins/registry/plugin-state.ts +3 -0
  819. package/src/plugins/seed-notice.ts +20 -0
  820. package/src/plugins/shortcut-argument.ts +39 -0
  821. package/src/portfolio/allocation.ts +1 -1
  822. package/src/portfolio/collection-name.ts +31 -0
  823. package/src/portfolio/column-values.ts +15 -13
  824. package/src/portfolio/cost-fx.ts +296 -19
  825. package/src/portfolio/manual-lots.ts +75 -0
  826. package/src/portfolio/mutations.ts +60 -22
  827. package/src/portfolio/suffixed-listings.ts +209 -0
  828. package/src/portfolio/totals-currency.ts +24 -0
  829. package/src/public/__snapshots__/public-api.test.ts.snap +15 -0
  830. package/src/public/time-series.ts +11 -8
  831. package/src/remote/schema.ts +1 -1
  832. package/src/renderers/browser/app-services.ts +1 -7
  833. package/src/renderers/browser/config-host.ts +5 -55
  834. package/src/renderers/browser/main.tsx +30 -60
  835. package/src/renderers/dom/boot-frame-page.ts +187 -0
  836. package/src/renderers/dom/boot-frame.ts +237 -0
  837. package/src/renderers/dom/build-assets.ts +46 -8
  838. package/src/renderers/dom/dom-ui-host.tsx +4 -29
  839. package/src/renderers/dom/host/ascii-text.ts +16 -2
  840. package/src/renderers/dom/host/box.tsx +43 -2
  841. package/src/renderers/dom/host/chart-surface.tsx +2 -0
  842. package/src/renderers/dom/host/hls.chunk.ts +3 -0
  843. package/src/renderers/dom/host/image-surface.tsx +49 -0
  844. package/src/renderers/dom/host/media-surface.tsx +24 -18
  845. package/src/renderers/dom/host/mouse.ts +30 -0
  846. package/src/renderers/dom/host/size-observer.ts +1 -1
  847. package/src/renderers/dom/host/style.ts +1 -1
  848. package/src/renderers/dom/styles.css +76 -0
  849. package/src/renderers/dom/test-utils.tsx +2 -1
  850. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
  851. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  852. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
  853. package/src/renderers/electrobun/bun/desktop/backend.ts +56 -3
  854. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
  855. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
  856. package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
  857. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  858. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  859. package/src/renderers/electrobun/bun/desktop/update.ts +70 -3
  860. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  861. package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
  862. package/src/renderers/electrobun/bun/index.ts +36 -2
  863. package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
  864. package/src/renderers/electrobun/bun/window/create.ts +4 -1
  865. package/src/renderers/electrobun/shared/app-name.ts +17 -0
  866. package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
  867. package/src/renderers/electrobun/shared/protocol.ts +6 -1
  868. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  869. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  870. package/src/renderers/electrobun/view/config-host.ts +3 -0
  871. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  872. package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
  873. package/src/renderers/electrobun/view/main.tsx +30 -4
  874. package/src/renderers/electrobun/view/news-source.ts +5 -5
  875. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -1
  876. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  877. package/src/renderers/opentui/browser-open.ts +88 -0
  878. package/src/renderers/opentui/host.tsx +5 -14
  879. package/src/renderers/opentui/image/loader.ts +3 -2
  880. package/src/renderers/opentui/native-smoke.ts +10 -1
  881. package/src/renderers/opentui/pane-image/index.ts +2 -1
  882. package/src/renderers/opentui/render-lib.ts +253 -0
  883. package/src/renderers/opentui/render-timing-smoke.ts +1 -1
  884. package/src/renderers/opentui/start.tsx +1 -1
  885. package/src/renderers/opentui/toast-host.tsx +71 -43
  886. package/src/renderers/share/layout-view.tsx +2 -2
  887. package/src/renderers/share/main.tsx +4 -4
  888. package/src/renderers/share/view.tsx +1 -1
  889. package/src/series/resolve.ts +22 -6
  890. package/src/series/standalone.ts +30 -2
  891. package/src/series/types.ts +8 -1
  892. package/src/shares/api.ts +4 -1
  893. package/src/shares/pane.ts +1 -1
  894. package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
  895. package/src/sources/gloomberb-cloud/index.ts +32 -16
  896. package/src/sources/gloomberb-cloud/news.ts +39 -0
  897. package/src/sources/gloomberb-cloud/normalizers.ts +16 -1
  898. package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
  899. package/src/sources/listing-symbols.ts +37 -0
  900. package/src/sources/provider-errors.ts +16 -2
  901. package/src/sources/provider-router/batches.ts +10 -2
  902. package/src/sources/provider-router/financial-routes.ts +21 -3
  903. package/src/sources/provider-router/financials.ts +15 -2
  904. package/src/sources/provider-router/history.ts +58 -15
  905. package/src/sources/provider-router/index.ts +146 -10
  906. package/src/sources/provider-router/primary.ts +1 -1
  907. package/src/sources/provider-router/search.ts +13 -2
  908. package/src/state/app/bootstrap.ts +23 -0
  909. package/src/state/app/context/index.tsx +3 -0
  910. package/src/state/hooks/inline-tickers.ts +30 -5
  911. package/src/sync/auth-transition.ts +19 -9
  912. package/src/sync/baseline.ts +10 -4
  913. package/src/sync/controller.ts +67 -17
  914. package/src/sync/core-contributors.ts +46 -14
  915. package/src/sync/react.ts +4 -1
  916. package/src/sync/types.ts +3 -0
  917. package/src/team/collections-sync.ts +2 -1
  918. package/src/team/collections.ts +178 -17
  919. package/src/telemetry/crash-reports.ts +17 -4
  920. package/src/telemetry/usage-counts.ts +0 -18
  921. package/src/test-support/builtin-panes.ts +1 -1
  922. package/src/theme/colors.ts +11 -30
  923. package/src/theme/ui-tokens.ts +37 -1
  924. package/src/tickers/company-name-listing.ts +210 -33
  925. package/src/tickers/company-name.ts +59 -0
  926. package/src/tickers/index-roots.ts +47 -0
  927. package/src/tickers/instrument-label.ts +5 -1
  928. package/src/tickers/listing-venues.ts +23 -0
  929. package/src/tickers/metadata.ts +10 -0
  930. package/src/tickers/perpetual-listing.ts +5 -2
  931. package/src/tickers/search/bare-symbol.ts +36 -0
  932. package/src/tickers/search/home-listing.ts +75 -0
  933. package/src/tickers/search/index.ts +111 -33
  934. package/src/tickers/search/ranking.ts +64 -5
  935. package/src/tickers/search/types.ts +5 -0
  936. package/src/tickers/search/upsert.ts +29 -0
  937. package/src/tickers/terminal-codes.ts +416 -40
  938. package/src/time-series/bar-aggregation.ts +243 -0
  939. package/src/time-series/base-series.ts +315 -0
  940. package/src/time-series/chart-bounds.ts +349 -0
  941. package/src/time-series/chart-seed.ts +142 -0
  942. package/src/time-series/daily-valuation.ts +62 -0
  943. package/src/time-series/economic.ts +5 -0
  944. package/src/time-series/field-catalog.ts +1 -3
  945. package/src/time-series/forward-valuation.ts +32 -0
  946. package/src/time-series/fundamentals.ts +173 -10
  947. package/src/time-series/history-acquisition.ts +499 -0
  948. package/src/time-series/hooks.ts +1 -0
  949. package/src/time-series/leverage.ts +100 -0
  950. package/src/time-series/market-resolution.ts +4 -2
  951. package/src/time-series/market.ts +2 -1
  952. package/src/time-series/price-comparison.ts +175 -30
  953. package/src/time-series/price-units.ts +72 -2
  954. package/src/time-series/range.ts +9 -1
  955. package/src/time-series/resolution.ts +174 -16
  956. package/src/time-series/resolve-context.ts +161 -0
  957. package/src/time-series/resolve.ts +185 -1587
  958. package/src/time-series/series-name.ts +38 -0
  959. package/src/time-series/series-presentation.ts +242 -0
  960. package/src/time-series/spec.ts +13 -7
  961. package/src/time-series/studies.ts +377 -82
  962. package/src/time-series/tail-reconcile.ts +121 -0
  963. package/src/time-series/transforms.ts +1 -0
  964. package/src/time-series/types.ts +34 -2
  965. package/src/time-series/use-chart-resolution.ts +4 -5
  966. package/src/time-series/valuation-releases.ts +39 -0
  967. package/src/types/broker.ts +10 -0
  968. package/src/types/config.ts +14 -0
  969. package/src/types/data-provider.ts +23 -1
  970. package/src/types/desktop-window.ts +2 -0
  971. package/src/types/exchange-rate.ts +6 -0
  972. package/src/types/financials.ts +145 -8
  973. package/src/types/headless.ts +117 -9
  974. package/src/types/instrument.ts +6 -0
  975. package/src/types/plugin.ts +66 -9
  976. package/src/types/price-history.ts +2 -0
  977. package/src/types/ticker.ts +13 -0
  978. package/src/types/trading.ts +59 -1
  979. package/src/ui/ascii-font.ts +13 -1
  980. package/src/ui/host.tsx +6 -1
  981. package/src/updater/command-names.ts +180 -0
  982. package/src/updater/index.ts +10 -5
  983. package/src/utils/calendar-date.ts +32 -15
  984. package/src/utils/cash-runway.ts +241 -0
  985. package/src/utils/central-banks.ts +42 -0
  986. package/src/utils/cli-output.ts +259 -80
  987. package/src/utils/contract-month.ts +16 -0
  988. package/src/utils/crypto-pair.ts +61 -3
  989. package/src/utils/currency-units.ts +29 -0
  990. package/src/utils/datetime-format.ts +10 -0
  991. package/src/utils/debug-log.ts +1 -1
  992. package/src/utils/decimal-comma.ts +39 -6
  993. package/src/utils/exchange-announcements.ts +71 -0
  994. package/src/utils/exchange-rate-snapshot.ts +10 -0
  995. package/src/utils/financial-statements.ts +89 -5
  996. package/src/utils/format.ts +34 -16
  997. package/src/utils/fundamentals.ts +150 -34
  998. package/src/utils/fx-market-hours.ts +10 -1
  999. package/src/utils/latest-financial-period.ts +8 -0
  1000. package/src/utils/linux-libc.ts +54 -0
  1001. package/src/utils/market-capitalization.ts +111 -15
  1002. package/src/utils/max-drawdown.ts +64 -0
  1003. package/src/utils/network-failure.ts +86 -0
  1004. package/src/utils/option-expiry.ts +186 -7
  1005. package/src/utils/options.ts +10 -0
  1006. package/src/utils/plugin-proxy-hosts.json +1 -0
  1007. package/src/utils/price-earnings.ts +11 -5
  1008. package/src/utils/price-history.ts +23 -8
  1009. package/src/utils/price-window.ts +131 -0
  1010. package/src/utils/release-date.ts +47 -0
  1011. package/src/utils/sec.ts +3 -0
  1012. package/src/utils/throttled-fetch.ts +11 -4
  1013. package/src/utils/trailing-eps.ts +67 -0
  1014. package/src/utils/typed-text.ts +21 -0
  1015. package/src/utils/utc-time.ts +24 -1
  1016. package/src/version.ts +6 -0
  1017. package/src/cli/pane-functions/screenshot.ts +0 -2122
  1018. package/src/renderers/browser/first-screen.ts +0 -193
@@ -1,25 +1,50 @@
1
+ import { metricDescription } from "./metric-model";
2
+ import { parseMetricAlert, type MetricAlertInput } from "./metric-command";
3
+ import { metricReadings } from "./metric-data";
1
4
  import { withConfigData, withMarketData } from "../../../cli/scoped-context";
2
5
  import { CLI_COMMAND_GROUPS } from "../../../cli/help";
3
6
  import { dryRunNote } from "../../../cli/helpers";
4
7
  import { requireArg, takeOption } from "../../../cli/commands/command-utils";
5
8
  import { requireQuotedListing } from "../../../cli/quote-answer";
6
- import { EXCHANGE_OPTION, listingReadNotes, requireListingArg } from "../../../cli/listing-arg";
9
+ import { EXCHANGE_OPTION, listingReadNotes, requireListingArg, usListingsOnlyMessage } from "../../../cli/listing-arg";
10
+ import { notTheCoinNote } from "../../../cli/commands/crypto-hints";
7
11
  import { saveConfig } from "../../../data/config/store";
12
+ import { resumeStateKey } from "../../registry/plugin-state";
8
13
  import type { CliCommandDef } from "../../../types/plugin";
9
14
  import type { Quote } from "../../../types/financials";
10
15
  import { getActiveQuoteDisplay } from "../../../market-data/market/status";
11
16
  import { cliStyles } from "../../../utils/cli-output";
12
- import { ambiguousNumberMessage, meantThousandsDot, parsePastedNumber, thousandsDotReadings, type ThousandsDotReadings } from "../../../utils/decimal-comma";
17
+ import { canonicalExchange, isUsListingExchange } from "../../../utils/exchanges";
18
+ import { meantThousandsDot, parsePastedNumber, thousandsDotReadings, type ThousandsDotReadings } from "../../../utils/decimal-comma";
13
19
  import { newYorkToday } from "../earnings/board-model";
14
- import { createAlert, rearmAlert, serializeAlerts } from "./alert-engine";
15
- import { ALERTS_KEY, ALERTS_PLUGIN_ID } from "./constants";
16
- import { EVENT_ALERTS_KEY, MAX_EVENT_ALERTS, eventAlertTarget, toggleEventAlert, type EventAlertRule } from "./events";
20
+ import { fetchEconCalendarFrom } from "../econ/calendar-source";
21
+ import { COUNTRY_FILTER_HINT, dateKey, loadCalendar, parseCountryFilter, type CountryFilter } from "../econ/calendar-model";
22
+ import { isAllDayEvent, type EconEvent } from "../econ/types";
23
+ import {
24
+ calendarEventLabel,
25
+ calendarNameWords,
26
+ expectsFigures,
27
+ macroEventNamed,
28
+ matchCalendarEvent,
29
+ releaseInstant,
30
+ releaseRenameNote,
31
+ releaseRowOn,
32
+ type CalendarEventChoice,
33
+ type ReleaseRename,
34
+ } from "./calendar-events";
35
+ import { createAlert, rearmAlert } from "./alert-engine";
36
+ import { ALERTS_PLUGIN_ID } from "./constants";
37
+ import { MAX_EVENT_ALERTS, eventAlertTarget, toggleEventAlert, writeEventAlertStores, type EventAlertRule } from "./events";
17
38
  import { alertKindLabel } from "./history";
18
- import { MAX_LEAD_DAYS, type MacroAlertEvent } from "./research-rules";
39
+ import { IV_LEVEL_CHECKS_KEY, readIvLevelChecks, rearmIvLevelAlert, type IvLevelCheck } from "./iv-level";
40
+ import { parseAlertPrice, readAlertPrice } from "./price";
41
+ import { farTargetMessage } from "./quotes";
42
+ import { MACRO_EVENT_NAMES, createResearchAlert, formatPremium, ivLevelUnitError, parseAmount, parseIvLevel, researchAlertDescription } from "./research-builder";
43
+ import { IV_LEVEL_RANGES, MAX_LEAD_DAYS, type IvLevelMetric, type MacroAlertEvent } from "./research-rules";
19
44
  import {
20
- MACRO_EVENT_CLI_NAMES,
21
45
  createScheduledAlert,
22
46
  eventDateUnavailableMessage,
47
+ followsRelease,
23
48
  leadStartDate,
24
49
  lookupScheduledDates,
25
50
  noUpcomingEventMessage,
@@ -31,12 +56,14 @@ import {
31
56
  scheduledEventName,
32
57
  scheduledPhase,
33
58
  scheduledTargetKey,
59
+ scheduledToday,
60
+ scheduledTriggerText,
34
61
  type ScheduledDateLookup,
35
62
  type ScheduledEventSources,
36
63
  type ScheduledEventTarget,
37
64
  } from "./scheduled";
38
65
  import { scheduledEventSources } from "./scheduled-sources";
39
- import { readAlertStores } from "./storage";
66
+ import { readAlertStores, writeLevelAlertStores } from "./storage";
40
67
  import type { AlertCondition, AlertRule } from "./types";
41
68
 
42
69
  /**
@@ -48,58 +75,127 @@ import type { AlertCondition, AlertRule } from "./types";
48
75
  */
49
76
  const PRICE_DELIVERY = "the app alerts while it is running; sign in with Cloud sync for email or phone alerts when it is closed";
50
77
 
51
- const ADD_USAGE = "Usage: gloomberb alerts add <symbol> <above|below|crosses> <price>, alerts add <symbol> earnings [--days <n>], or alerts add <CPI|FOMC> [--days <n>]";
78
+ const ADD_USAGE = "Usage: gloom alerts add <symbol> <above|below|crosses> <price>, alerts add <symbol> earnings [--days <n> | results], alerts add <calendar event> [--country <code>] [--days <n>], alerts add [symbol] news <keyword>, alerts add [symbol] flow [premium] [calls|puts] [sweeps|blocks], alerts add <symbol> <iv|rr> <above|below> <level>, or alerts add <OCC contract> iv spike <points>";
52
79
  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.`;
80
+ /** The event form every refusal of `alerts add` names: any release in the economic calendar, CPI and FOMC among them. */
81
+ const EVENT_FORM = `a release in the economic calendar by name (alerts add "NBS Manufacturing PMI" --country CN, alerts add CN PMI, alerts add CPI --days 1)`;
82
+ const COUNTRY_RULE = "--country is for an alert on a calendar event, such as alerts add PMI --country CN.";
53
83
 
54
84
  /** What `alerts add` was asked for, before any listing or date is looked up. */
55
85
  export type AlertAddRequest =
86
+ | ({ kind: "metric" } & MetricAlertInput)
56
87
  | {
57
88
  kind: "price"; symbol: string; condition: AlertCondition; price: number;
58
89
  /** A price typed as 81.000, which the last price may show was 81000 with a dot between thousands. */
59
90
  thousandsDot?: ThousandsDotReadings & { text: string };
60
91
  }
61
- | { kind: "earnings"; symbol: string; leadDays: number }
62
- | { kind: "macro"; event: MacroAlertEvent; leadDays: number };
92
+ /** `results`: once the report's actual EPS is out, rather than `leadDays` before the date. */
93
+ | { kind: "earnings"; symbol: string; leadDays: number; results?: true }
94
+ | { kind: "macro"; event: MacroAlertEvent; leadDays: number }
95
+ /** A release in the economic calendar by name, matched against the calendar once it is read; `country` narrows it. */
96
+ | { kind: "release"; query: string; country: CountryFilter | null; leadDays: number }
97
+ /** Gloom Cloud's research rules, as Add Event Alert makes them; a news or flow rule without a symbol follows every name (news) or your lists (flow). */
98
+ | { kind: "news"; symbol: string | null; keyword: string }
99
+ | { kind: "flow"; symbol: string | null; premium: number; side: "any" | "calls" | "puts"; print: "any" | "sweep" | "block" }
100
+ /** 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. */
101
+ | { kind: "iv_level"; symbol: string; metric: IvLevelMetric; direction: "above" | "below"; level: number }
102
+ /** One OCC contract's IV rising by `points`. */
103
+ | { kind: "iv_spike"; contract: string; points: number };
104
+
105
+ /** The `alerts add` forms Gloom Cloud's research rules take: they follow US listings. */
106
+ type ResearchAddRequest = Extract<AlertAddRequest, { kind: "news" | "flow" | "iv_level" | "iv_spike" }>;
63
107
 
64
108
  const CONDITIONS: readonly AlertCondition[] = ["above", "below", "crosses"];
65
109
 
66
110
  /** The first word of a price typed with a space between thousands and no quotes (`81 000`): one to three digits. */
67
111
  const LEADING_GROUP = /^[+-]?\d{1,3}$/;
68
112
 
113
+ /** `--country` as typed: a country code or the calendar's regions (`CN`, `uk`, `EU`); null when not given. Throws when it names none. */
114
+ function readCountryOption(country: string | null | undefined): CountryFilter | null {
115
+ if (country === undefined) return null;
116
+ if (country === null) throw new Error(`--country takes a two-letter country code, such as --country CN. ${COUNTRY_FILTER_HINT}`);
117
+ const parsed = parseCountryFilter(country);
118
+ if ("error" in parsed) throw new Error(`${parsed.error} ${COUNTRY_FILTER_HINT}`);
119
+ return parsed.filter === "all" ? null : parsed.filter;
120
+ }
121
+
69
122
  /**
70
- * Reads `alerts add` arguments: `<symbol> above 250`, `<symbol> earnings`, or
71
- * `CPI` / `FOMC`, with `--days` (already taken out of `args`, `null` when the
72
- * flag came without a value) for the event forms only, and `--exchange`, which
73
- * a command to type again keeps. Throws a message that names what the command
74
- * takes.
123
+ * A release named by its words (`NBS PMI`, `Industrial Profits`), a country
124
+ * code first standing for `--country` (`CN PMI`). CPI and FOMC, in the US or
125
+ * with no country, are the macro alerts whose dates come from their own
126
+ * calendars.
75
127
  */
76
- export function parseAlertAddArgs(args: readonly string[], days: string | null | undefined, exchange?: string | null): AlertAddRequest {
128
+ function releaseRequest(args: readonly string[], country: CountryFilter | null, leadDays: number): AlertAddRequest {
129
+ const words = args.join(" ").trim().split(/\s+/);
130
+ let query = words.join(" ");
131
+ if (!country && words.length >= 2 && /^[a-z]{2}$/i.test(words[0]!)) {
132
+ const parsed = parseCountryFilter(words[0]!);
133
+ if (!("error" in parsed) && parsed.filter !== "all") {
134
+ country = parsed.filter;
135
+ query = words.slice(1).join(" ");
136
+ }
137
+ }
138
+ const event = parseMacroEventName(query);
139
+ if (event && (!country || country === "US")) return { kind: "macro", event, leadDays };
140
+ return { kind: "release", query, country, leadDays };
141
+ }
142
+
143
+ /**
144
+ * Reads `alerts add` arguments: `<symbol> above 250`, `<symbol> earnings`,
145
+ * `<symbol> earnings results`, or a release in the economic calendar by name
146
+ * (`CPI`, `FOMC`, `NBS PMI`, `CN PMI`), with `--days` (already taken out of
147
+ * `args`, `null` when the flag came without a value) and `--country` for the
148
+ * event forms only, and `--exchange`, which a command to type again keeps.
149
+ * Throws a message that names what the command takes.
150
+ */
151
+ export function parseAlertAddArgs(
152
+ args: readonly string[],
153
+ days: string | null | undefined,
154
+ exchange?: string | null,
155
+ country?: string | null,
156
+ ): AlertAddRequest {
77
157
  const [first, second, third, ...rest] = args;
78
158
  if (!first) throw new Error(ADD_USAGE);
159
+ const countryFilter = readCountryOption(country);
160
+ const metric = parseMetricAlert(args.join(" "));
161
+ if (metric) {
162
+ if (days !== undefined || country !== undefined) throw new Error("Valuation and daily technical alerts take neither --days nor --country.");
163
+ return { kind: "metric", ...metric };
164
+ }
165
+ const research = parseResearchAddArgs(args);
166
+ if (research) {
167
+ if (days !== undefined) throw new Error("--days is for earnings and calendar event alerts, not news, flow or IV alerts.");
168
+ if (country !== undefined) throw new Error(COUNTRY_RULE);
169
+ return research;
170
+ }
79
171
  const leadDays = () => {
80
172
  const parsed = days === null ? null : parseLeadDays(days);
81
173
  if (parsed == null) throw new Error(DAYS_RULE);
82
174
  return parsed;
83
175
  };
84
176
  if (second === undefined) {
85
- const event = parseMacroEventName(first);
86
- if (event) return { kind: "macro", event, leadDays: leadDays() };
87
177
  if (first.toLowerCase() === "earnings") {
88
- throw new Error("Name the company: gloomberb alerts add <symbol> earnings, such as alerts add CRWV earnings --days 3.");
178
+ throw new Error("Name the company: gloom alerts add <symbol> earnings, such as alerts add CRWV earnings --days 3.");
89
179
  }
90
- throw new Error(
91
- `Unknown event "${first}". Event alerts take ${MACRO_EVENT_CLI_NAMES} (alerts add CPI --days 1) or a symbol with earnings (alerts add CRWV earnings --days 3); price alerts take a condition and a price (alerts add AAPL above 250).`,
92
- );
180
+ return releaseRequest(args, countryFilter, leadDays());
93
181
  }
182
+ if (second.toLowerCase() === "earnings" && country !== undefined) throw new Error(COUNTRY_RULE);
94
183
  if (second.toLowerCase() === "earnings") {
95
- if (third !== undefined) throw new Error(`Earnings alerts take a symbol and --days, not "${third}". ${ADD_USAGE}`);
184
+ if (third?.toLowerCase() === "results" && rest.length === 0) {
185
+ if (days !== undefined) throw new Error("An alert for results goes off once they are out and takes no --days.");
186
+ return { kind: "earnings", symbol: first, leadDays: 0, results: true };
187
+ }
188
+ if (third !== undefined) throw new Error(`Earnings alerts take a symbol and --days or results, not "${[third, ...rest].join(" ")}". ${ADD_USAGE}`);
96
189
  return { kind: "earnings", symbol: first, leadDays: leadDays() };
97
190
  }
98
191
  const condition = second.toLowerCase() as AlertCondition;
99
192
  if (!CONDITIONS.includes(condition)) {
100
- throw new Error(`Unknown alert "${second}". Use above, below or crosses with a price, or earnings with --days.`);
193
+ // Words with no price after them name a release (CN PMI, Industrial Profits); a price ends a mistyped price alert.
194
+ if (parseAlertPrice(args.at(-1)!).kind === "invalid") return releaseRequest(args, countryFilter, leadDays());
195
+ throw new Error(`Unknown alert "${second}". Use above, below or crosses with a price, earnings with --days, news with a keyword, flow, iv or rr with above or below and a level, iv spike on an OCC contract, or ${EVENT_FORM}.`);
101
196
  }
102
- if (days !== undefined) throw new Error("--days is for earnings and CPI or FOMC alerts, not price alerts.");
197
+ if (country !== undefined) throw new Error(COUNTRY_RULE);
198
+ if (days !== undefined) throw new Error("--days is for earnings and calendar event alerts, not price alerts.");
103
199
  if (third === undefined) throw new Error(ADD_USAGE);
104
200
  const retype = (price: number) => retypeCommand(first, condition, price, exchange);
105
201
  // `81 000` without quotes arrives as 81 and a stray 000; neither reading is taken for granted.
@@ -109,17 +205,122 @@ export function parseAlertAddArgs(args: readonly string[], days: string | null |
109
205
  throw new Error(`${[third, ...rest].join(" ")} could be ${spaced.value} (thousands) or ${typed} and a stray ${rest.join(" ")}; type ${retype(spaced.value)} or ${retype(typed)}`);
110
206
  }
111
207
  // A price pasted from a sheet (1.350,5, 1,350.5, 1 350,5) reads as it is written; `1,350` could be either, so it asks.
112
- const pasted = parsePastedNumber(third);
113
- if (pasted.kind === "ambiguous") throw new Error(ambiguousNumberMessage(third, pasted));
114
- if (pasted.kind === "invalid") throw new Error("Alert price must be a finite number.");
115
- if (rest.length > 0) throw new Error(`A price alert takes one price, not "${rest.join(" ")}" after ${third}; type ${retype(pasted.value)}`);
208
+ const price = readAlertPrice(third);
209
+ if (rest.length > 0) throw new Error(`A price alert takes one price, not "${rest.join(" ")}" after ${third}; type ${retype(price)}`);
116
210
  const thousandsDot = thousandsDotReadings(third);
117
- return { kind: "price", symbol: first, condition, price: pasted.value, ...(thousandsDot ? { thousandsDot: { ...thousandsDot, text: third.trim() } } : {}) };
211
+ return { kind: "price", symbol: first, condition, price, ...(thousandsDot ? { thousandsDot: { ...thousandsDot, text: third.trim() } } : {}) };
212
+ }
213
+
214
+ const OCC_CONTRACT = /^(?:O:)?[A-Z.]{1,6}\d{6}[CP]\d{8}$/i;
215
+ const LEVEL_DIRECTIONS = ["above", "below"] as const;
216
+
217
+ /** A level typed for an IV level rule, with its unit or without (`50`, `50%`, `-5.5 pts`), refused with its unit and range when it does not read. */
218
+ function readLevel(metric: IvLevelMetric, typed: string | undefined, symbol: string): number {
219
+ const word = metric === "atm" ? "iv" : "rr";
220
+ const example = metric === "atm" ? `alerts add ${symbol} iv above 50` : `alerts add ${symbol} rr below -5`;
221
+ if (!typed) throw new Error(`Name the level: gloom ${example}.`);
222
+ const unitError = ivLevelUnitError(metric, typed);
223
+ if (unitError) throw new Error(`${unitError}: gloom ${example}.`);
224
+ const level = parseIvLevel(typed);
225
+ const { min, max } = IV_LEVEL_RANGES[metric];
226
+ if (!Number.isFinite(level) || level < min || level > max) {
227
+ throw new Error(metric === "atm"
228
+ ? `An ${word} level is the 30-day ATM IV in percent, ${min} to ${max}, not "${typed}": gloom ${example}.`
229
+ : `An ${word} level is the 30-day 25-delta risk reversal in vol points, ${min} to ${max}, not "${typed}": gloom ${example}.`);
230
+ }
231
+ return level;
232
+ }
233
+
234
+ /**
235
+ * The research forms of `alerts add`, the ones Add Event Alert makes: `[symbol] news <keyword>`,
236
+ * `[symbol] flow [premium] [calls|puts] [sweeps|blocks]`, `<symbol> iv|rr <above|below> <level>` and
237
+ * `<OCC contract> iv spike <points>`. Null for any other form. A bare `news` or `flow` with no symbol
238
+ * follows every name or your lists; `NEWS above 10` is still a price alert on a symbol.
239
+ */
240
+ function parseResearchAddArgs(args: readonly string[]): ResearchAddRequest | null {
241
+ const [first = "", second, third, ...rest] = args;
242
+ const word = (value: string | undefined) => value?.toLowerCase() ?? "";
243
+ const isCondition = (value: string | undefined) => (CONDITIONS as readonly string[]).includes(word(value)) || word(value) === "earnings";
244
+ const lead = word(first);
245
+ const [symbol, kind, tail] = (lead === "news" || lead === "flow") && !isCondition(second)
246
+ ? [null, lead, args.slice(1)] as const
247
+ : [first, word(second), args.slice(2)] as const;
248
+ if (kind === "news") {
249
+ const keyword = tail.join(" ").trim();
250
+ if (!keyword) throw new Error(`Name a keyword or phrase: gloom alerts add ${symbol ?? "AAPL"} news buyback, or alerts add news "rate cut" for any company.`);
251
+ return { kind: "news", symbol, keyword };
252
+ }
253
+ if (kind === "flow") {
254
+ let premium = 1_000_000;
255
+ let side: "any" | "calls" | "puts" = "any";
256
+ let print: "any" | "sweep" | "block" = "any";
257
+ for (const token of tail) {
258
+ const lower = token.toLowerCase();
259
+ if (lower === "calls" || lower === "call") side = "calls";
260
+ else if (lower === "puts" || lower === "put") side = "puts";
261
+ else if (lower === "sweeps" || lower === "sweep") print = "sweep";
262
+ else if (lower === "blocks" || lower === "block") print = "block";
263
+ else if (Number.isFinite(parseAmount(token))) premium = parseAmount(token);
264
+ else throw new Error(`A flow alert takes a minimum premium (1m, $250k), calls or puts, and sweeps or blocks, not "${token}".`);
265
+ }
266
+ if (premium < 50_000) throw new Error(`The minimum premium is $50,000, not ${formatPremium(premium)}: Gloom Cloud records no smaller print.`);
267
+ return { kind: "flow", symbol, premium, side, print };
268
+ }
269
+ if (!symbol || (kind !== "iv" && kind !== "rr")) return null;
270
+ if (OCC_CONTRACT.test(symbol)) {
271
+ if (kind !== "iv" || word(third) !== "spike") {
272
+ throw new Error(`An option contract takes an IV spike in vol points: gloom alerts add ${symbol.toUpperCase()} iv spike 5. For a level, name the underlying: alerts add ${symbol.replace(/^O:/i, "").match(/^[A-Z.]+/i)![0]!.toUpperCase()} iv above 50.`);
273
+ }
274
+ // Vol points, with the unit or without it: `5`, `5 pts`.
275
+ const typed = rest.join(" ");
276
+ const value = typed && !/%/.test(typed) ? parseIvLevel(typed) : Number.NaN;
277
+ if (!Number.isFinite(value) || value < 0.1 || value > 100) {
278
+ throw new Error(`An IV spike is a rise of 0.1 to 100 vol points: gloom alerts add ${symbol.toUpperCase()} iv spike 5.`);
279
+ }
280
+ return { kind: "iv_spike", contract: symbol.toUpperCase(), points: value };
281
+ }
282
+ const metric: IvLevelMetric = kind === "iv" ? "atm" : "rr";
283
+ const direction = word(third);
284
+ if (!(LEVEL_DIRECTIONS as readonly string[]).includes(direction)) {
285
+ throw new Error(`${kind === "iv" ? "An iv" : "An rr"} alert takes above or below and a level: gloom alerts add ${symbol} ${kind} ${kind === "iv" ? "above 50" : "below -5"}${kind === "iv" ? ", or iv spike on one OCC contract" : ""}.`);
286
+ }
287
+ // The level, with its unit or without it: `50`, `50%`, `-5.5 pts`.
288
+ return { kind: "iv_level", symbol, metric, direction: direction as "above" | "below", level: readLevel(metric, rest.join(" "), symbol) };
289
+ }
290
+
291
+ /** The Add Event Alert form's values for a research request, so the CLI makes the rule the dialog makes. */
292
+ function researchFormValues(request: ResearchAddRequest, exchange: string): { kind: string; values: Record<string, string> } {
293
+ switch (request.kind) {
294
+ case "news": return { kind: "news_keyword", values: { "news_keyword:keyword": request.keyword,
295
+ ...request.symbol ? { "news_keyword:symbol": request.symbol, "news_keyword:exchange": exchange } : {} } };
296
+ case "flow": return { kind: "options_flow", values: { "options_flow:threshold": String(request.premium),
297
+ "options_flow:direction": request.side, "options_flow:print": request.print,
298
+ ...request.symbol ? { "options_flow:symbol": request.symbol, "options_flow:exchange": exchange } : {} } };
299
+ case "iv_level": return { kind: "iv_level", values: { "iv_level:symbol": request.symbol, "iv_level:exchange": exchange,
300
+ "iv_level:metric": request.metric, "iv_level:direction": request.direction, "iv_level:threshold": String(request.level) } };
301
+ case "iv_spike": return { kind: "iv_spike", values: { "iv_spike:contract": request.contract, "iv_spike:threshold": String(request.points) } };
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Who checks a research rule and how it reaches you, as docs/usage.md states it: an IV level is read by the
307
+ * running app; the rest by Gloom Cloud for a signed-in account with Cloud sync, IV spikes and flow on Pro.
308
+ */
309
+ function researchDelivery(kind: ResearchAddRequest["kind"]): string {
310
+ if (kind === "iv_level") return "the app checks it every 15 minutes while it is running, on the same option fits as OVDV";
311
+ const plan = kind === "news" ? "" : " on Gloom Pro";
312
+ return `Gloom Cloud checks it for a signed-in account with Cloud sync${plan} and sends it to your phone`;
118
313
  }
119
314
 
315
+ const isResearchRequest = (request: AlertAddRequest): request is ResearchAddRequest =>
316
+ request.kind === "news" || request.kind === "flow" || request.kind === "iv_level" || request.kind === "iv_spike";
317
+
318
+ /** The US venue a research rule keeps: one typed and known to it, else any US venue. */
319
+ const RESEARCH_EXCHANGES = new Set(["NASDAQ", "NYSE", "AMEX", "ARCA", "BATS", "OTC"]);
320
+
120
321
  /** `alerts add` as typed, with another price: what an error tells someone to type. */
121
322
  function retypeCommand(symbol: string, condition: AlertCondition, price: number, exchange: string | null | undefined): string {
122
- return `gloomberb alerts add ${symbol} ${condition} ${price}${exchange ? ` --exchange ${exchange}` : ""}`;
323
+ return `gloom alerts add ${symbol} ${condition} ${price}${exchange ? ` --exchange ${exchange}` : ""}`;
123
324
  }
124
325
 
125
326
  /**
@@ -156,22 +357,28 @@ type AlertListRow = Record<string, unknown> & {
156
357
  };
157
358
 
158
359
  function priceRow(alert: AlertRule): AlertListRow {
159
- return { ...alert, kind: "price", target: alert.exchange ? `${alert.symbol}:${alert.exchange}` : alert.symbol };
360
+ return { ...alert, kind: alert.metric?.kind ?? "price", rule: alert.metric ? metricDescription(alert) : undefined, target: alert.exchange ? `${alert.symbol}:${alert.exchange}` : alert.symbol };
160
361
  }
161
362
 
162
- function eventRow(rule: EventAlertRule, today: string, eventDate: string | null | undefined): AlertListRow {
363
+ function eventRow(rule: EventAlertRule, now: number, eventDate: string | null | undefined, check?: IvLevelCheck): AlertListRow {
163
364
  const scheduled = readScheduledRule(rule);
164
365
  if (!scheduled) {
165
- return { id: rule.id, kind: rule.kind, target: eventAlertTarget(rule), status: rule.status, createdAt: rule.createdAt };
366
+ const status = rule.status === "active" && rule.triggeredAt ? "triggered" : rule.status;
367
+ return { id: rule.id, kind: rule.kind, target: eventAlertTarget(rule), status, createdAt: rule.createdAt,
368
+ ...check ? { lastCheck: check } : {}, ...rule.triggeredAt ? { triggeredAt: rule.triggeredAt } : {} };
166
369
  }
167
370
  const date = eventDate ?? null;
168
371
  return {
169
372
  id: rule.id,
170
373
  kind: rule.kind,
171
- target: scheduled.target.kind === "earnings" ? scheduled.target.symbol : scheduled.target.event.toUpperCase(),
374
+ target: scheduled.target.kind === "earnings" ? scheduled.target.symbol
375
+ : scheduled.target.kind === "release" ? calendarEventLabel(scheduled.target) : scheduled.target.event.toUpperCase(),
172
376
  leadDays: scheduled.leadDays,
377
+ when: scheduled.trigger === "results" ? "results" : "before",
378
+ // CPI, FOMC and calendar releases also alert with their figures once they print.
379
+ ...followsRelease(scheduled.target) ? { onRelease: true } : {},
173
380
  eventDate: date,
174
- status: scheduledAlertStatus({ ...rule, eventDate: date ?? undefined }, scheduled.leadDays, today),
381
+ status: scheduledAlertStatus({ ...rule, eventDate: date ?? undefined }, scheduled.leadDays, scheduledToday(scheduled.target, now), scheduled.trigger),
175
382
  createdAt: rule.createdAt,
176
383
  };
177
384
  }
@@ -184,9 +391,11 @@ function textRow(row: AlertListRow): Record<string, unknown> {
184
391
  id: row.id,
185
392
  kind: price ? "Price" : alertKindLabel(row.kind),
186
393
  target: row.target,
187
- trigger: price
394
+ trigger: row.rule ?? (price
188
395
  ? `${row.condition} ${row.targetPrice}`
189
- : typeof row.leadDays === "number" ? `${plural(row.leadDays, "day")} before` : "",
396
+ : typeof row.leadDays === "number"
397
+ ? scheduledTriggerText({ leadDays: row.leadDays, trigger: row.when === "results" ? "results" : "date", prints: row.onRelease === true })
398
+ : ""),
190
399
  eventDate: typeof row.leadDays === "number" ? row.eventDate ?? "not known" : "",
191
400
  status: row.status,
192
401
  lastCheckedPrice: row.lastCheckedPrice,
@@ -197,7 +406,7 @@ function textRow(row: AlertListRow): Record<string, unknown> {
197
406
  /** The next event date of each scheduled rule: the saved one while it is still ahead, otherwise looked up. */
198
407
  async function listEventDates(
199
408
  rules: readonly EventAlertRule[],
200
- today: string,
409
+ now: number,
201
410
  lookup: (targets: ScheduledEventTarget[]) => Promise<Map<string, ScheduledDateLookup>>,
202
411
  ): Promise<{ dates: Map<string, string | null>; warnings: string[] }> {
203
412
  const dates = new Map<string, string | null>();
@@ -205,7 +414,7 @@ async function listEventDates(
205
414
  for (const rule of rules) {
206
415
  const scheduled = readScheduledRule(rule);
207
416
  if (!scheduled) continue;
208
- if (rule.eventDate && rule.eventDate >= today) dates.set(rule.id, rule.eventDate);
417
+ if (rule.eventDate && rule.eventDate >= scheduledToday(scheduled.target, now)) dates.set(rule.id, rule.eventDate);
209
418
  else stale.push({ rule, target: scheduled.target });
210
419
  }
211
420
  if (stale.length === 0) return { dates, warnings: [] };
@@ -219,6 +428,96 @@ async function listEventDates(
219
428
  return { dates, warnings: [...warnings] };
220
429
  }
221
430
 
431
+ /** How far back a release is looked for: a monthly one is named in the calendar a month back before its next date is listed. */
432
+ const CALENDAR_LOOKBACK_DAYS = 40;
433
+
434
+ /**
435
+ * The calendar a release is named from: the stored past five weeks and the
436
+ * listing ahead, or the listing alone from a server that keeps no past.
437
+ */
438
+ async function loadAlertCalendar(now: number): Promise<readonly EconEvent[]> {
439
+ const from = new Date(now - CALENDAR_LOOKBACK_DAYS * 86_400_000).toISOString().slice(0, 10);
440
+ const stored = await fetchEconCalendarFrom(from);
441
+ if (stored.events) return stored.events;
442
+ return (await loadCalendar()).data;
443
+ }
444
+
445
+ const quoted = (text: string) => (/\s/.test(text) ? `"${text}"` : text);
446
+ const weekday = new Intl.DateTimeFormat("en-US", { weekday: "short", timeZone: "UTC" });
447
+
448
+ /** "on Sat 2026-10-31 01:30 UTC", "on Mon 2026-10-26, all day": when a release is next listed. */
449
+ function releaseWhen(event: EconEvent): string {
450
+ const day = `on ${weekday.format(event.date)} ${dateKey(event.date)}`;
451
+ if (isAllDayEvent(event)) return `${day}, all day`;
452
+ return event.timeNote ? day : `${day} ${event.date.toISOString().slice(11, 16)} UTC`;
453
+ }
454
+
455
+ /** The release an alert was added for, as `--json` gives it. */
456
+ function releaseData(choice: CalendarEventChoice) {
457
+ return {
458
+ name: choice.name,
459
+ country: choice.country,
460
+ publisher: choice.publisher,
461
+ impact: choice.impact,
462
+ next: choice.next ? { at: choice.next.date.toISOString(), allDay: isAllDayEvent(choice.next) } : null,
463
+ };
464
+ }
465
+
466
+ const choiceList = (choices: readonly CalendarEventChoice[]) => choices.map((choice) => calendarEventLabel({ event: choice.name, country: choice.country }));
467
+
468
+ /** What a name read as, and what else it names in that country: said beside the alert added. */
469
+ function releaseReadNotes(
470
+ query: string,
471
+ choice: CalendarEventChoice,
472
+ others: readonly CalendarEventChoice[],
473
+ renamed?: ReleaseRename,
474
+ ): string[] {
475
+ const notes: string[] = [];
476
+ if (calendarNameWords(query).join(" ") !== calendarNameWords(choice.name).join(" ")) {
477
+ notes.push(`Read "${query}" as ${calendarEventLabel({ event: choice.name, country: choice.country })}, ${choice.impact} impact.`
478
+ + (renamed ? ` ${releaseRenameNote(renamed)}` : ""));
479
+ }
480
+ if (others.length > 0) {
481
+ const shown = choiceList(others.slice(0, 4));
482
+ const more = others.length > 4 ? ` and ${others.length - 4} more` : "";
483
+ notes.push(`Also matching: ${shown.join(", ")}${more}. To follow one of them instead: alerts add ${quoted(others[0]!.name)} --country ${others[0]!.country}.`);
484
+ }
485
+ return notes;
486
+ }
487
+
488
+ /** Every form `alerts add` takes but a price alert's details, as a refusal ends. */
489
+ const ADD_FORMS = `Event alerts take ${EVENT_FORM} or a symbol with earnings (alerts add CRWV earnings --days 3); price alerts take a condition and a price (alerts add AAPL above 250).`;
490
+
491
+ /**
492
+ * Why a name could not be looked up. A single word (alerts add AAPL) may be
493
+ * a slip of another form as much as a release, so it also says what each
494
+ * form takes.
495
+ */
496
+ function calendarUnavailableMessage(query: string, country: CountryFilter | null, error: string): string {
497
+ if (country || /\s/.test(query.trim())) return eventDateUnavailableMessage({ kind: "release", event: query, country: country ?? "" }, error);
498
+ return `Unknown event "${query}": the economic calendar could not be loaded to look it up (${error}). ${ADD_FORMS}`;
499
+ }
500
+
501
+ /** The refusal of a name nothing in the calendar matches: the nearest names, and every form `alerts add` takes. */
502
+ function noReleaseMessage(
503
+ query: string,
504
+ country: CountryFilter | null,
505
+ suggestions: readonly CalendarEventChoice[],
506
+ renamed?: ReleaseRename,
507
+ ): string {
508
+ const near = suggestions.length > 0 ? ` Did you mean ${choiceList(suggestions).join(", ")}?` : "";
509
+ return `No release in the economic calendar matches "${query}"${country ? ` in ${country.split(",").join(", ")}` : ""}.`
510
+ + `${renamed ? ` ${releaseRenameNote(renamed)}` : ""}${near} `
511
+ + `${ADD_FORMS} gloom econ --country <code> lists the calendar.`;
512
+ }
513
+
514
+ /** The refusal of a name listed in several countries, with the best of each. */
515
+ function severalCountriesMessage(query: string, choices: readonly CalendarEventChoice[]): string {
516
+ const shown = choiceList(choices.slice(0, 6));
517
+ const more = choices.length > 6 ? ` and ${choices.length - 6} more` : "";
518
+ return `"${query}" is listed for several countries: ${shown.join(", ")}${more}. Name the country: alerts add ${quoted(query)} --country ${choices[0]!.country}.`;
519
+ }
520
+
222
521
  /** Looks up the next date for a new event alert, or fails saying why there is none. */
223
522
  async function requireEventDate(
224
523
  target: ScheduledEventTarget,
@@ -235,27 +534,47 @@ async function requireEventDate(
235
534
  export const alertsCliCommand: CliCommandDef = {
236
535
  name: "alerts",
237
536
  aliases: ["alert"],
238
- description: "List, add, and remove price alerts and alerts before earnings, CPI and FOMC",
537
+ description: "List, add, and remove price, valuation and daily technical alerts, alerts before earnings and any economic calendar release (CPI, FOMC, NBS PMI), and news, options flow and IV alerts",
239
538
  help: {
240
539
  group: CLI_COMMAND_GROUPS.portfolios,
241
540
  usage: [
242
541
  "alerts list",
243
542
  "alerts add <symbol> <above|below|crosses> <price>",
244
- "alerts add <symbol> earnings [--days <n>]",
245
- "alerts add <CPI|FOMC> [--days <n>]",
543
+ "alerts add <symbol> <pe|ps|rsi(14)> <above|below> <level>",
544
+ "alerts add <symbol> ma(50) crosses <above|below> ma(200)",
545
+ "alerts add <symbol> closes <above|below> ma(200)",
546
+ "alerts add <symbol> earnings [--days <n> | results]",
547
+ "alerts add <calendar event> [--country <code>] [--days <n>]",
548
+ "alerts add [symbol] news <keyword>",
549
+ "alerts add [symbol] flow [premium] [calls|puts] [sweeps|blocks]",
550
+ "alerts add <symbol> <iv|rr> <above|below> <level>",
551
+ "alerts add <OCC contract> iv spike <points>",
246
552
  "alerts delete <id>",
247
553
  "alerts rearm <id>",
248
554
  ],
249
555
  options: [
250
556
  EXCHANGE_OPTION,
251
- { flags: "--days <n>", description: `Days before the event the alert goes off, 1 to ${MAX_LEAD_DAYS} (default 1); earnings, CPI and FOMC alerts` },
557
+ { flags: "--days <n>", description: `Days before the event the alert goes off, 1 to ${MAX_LEAD_DAYS} (default 1); earnings and calendar event alerts` },
558
+ { 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)" },
252
559
  ],
253
560
  examples: [
254
561
  "alerts",
255
562
  "alerts add AAPL above 250",
563
+ "alerts add AAPL pe below 25",
564
+ "alerts add AAPL ps above 8",
565
+ 'alerts add NVDA "rsi(14)" under 30',
566
+ 'alerts add SPY "ma(50)" crosses above "ma(200)"',
567
+ 'alerts add TSLA closes below "ma(200)"',
256
568
  "alerts add SAN:EPA below 70",
257
569
  "alerts add CRWV earnings --days 3",
570
+ "alerts add CRWV earnings results",
258
571
  "alerts add CPI --days 1",
572
+ "alerts add \"NBS PMI\"",
573
+ "alerts add \"Industrial Profits\" --country CN --days 2",
574
+ "alerts add AAPL news buyback",
575
+ "alerts add NVDA flow 5m calls sweeps",
576
+ "alerts add IBIT iv above 50",
577
+ "alerts add SPY rr below -6",
259
578
  ],
260
579
  },
261
580
  execute: async (rawArgs, ctx) => {
@@ -264,61 +583,156 @@ export const alertsCliCommand: CliCommandDef = {
264
583
  const daysValue = takeOption(args, "--days");
265
584
  const days = daysFlag && daysValue === undefined ? null : daysValue;
266
585
  const exchangeOption = takeOption(args, "--exchange");
586
+ const countryFlag = rawArgs.some((arg) => arg === "--country" || arg.startsWith("--country="));
587
+ const countryValue = takeOption(args, "--country");
588
+ const country = countryFlag && countryValue === undefined ? null : countryValue;
267
589
  const action = args[0] ?? "list";
268
- const today = newYorkToday();
590
+ const now = Date.now();
591
+ const today = newYorkToday(new Date(now));
269
592
 
270
593
  const parseAdd = (): AlertAddRequest => {
271
594
  try {
272
- return parseAlertAddArgs(args.slice(1), days, exchangeOption);
595
+ return parseAlertAddArgs(args.slice(1), days, exchangeOption, country);
273
596
  } catch (error) {
274
597
  return ctx.fail(error instanceof Error ? error.message : String(error));
275
598
  }
276
599
  };
277
600
  const request = action === "add" ? parseAdd() : null;
278
- // A price alert is kept for the listing `quote` prints for the symbol, and for no symbol that quotes nothing.
279
- const priced = request?.kind === "price"
280
- ? await withMarketData(ctx, (market) => requireQuotedListing(request.symbol, exchangeOption, market, ctx, "alerts add"))
601
+ const research = request && isResearchRequest(request) ? request : null;
602
+ const listedSymbol = (request?.kind === "price" || request?.kind === "metric") ? request.symbol
603
+ : research && research.kind !== "iv_spike" ? research.symbol : null;
604
+ // A price alert is kept for the listing `quote` prints for the symbol, and for no symbol that quotes nothing;
605
+ // a news, flow or IV rule on a symbol follows that listing too, and only in the US, where their data is.
606
+ const priced = listedSymbol
607
+ ? await withMarketData(ctx, async (market) => {
608
+ const quoted = await requireQuotedListing(listedSymbol, exchangeOption, market, ctx, "alerts add");
609
+ if (research) {
610
+ const venue = quoted.listing.exchange || canonicalExchange(quoted.quote.listingExchangeName || quoted.quote.exchangeName);
611
+ if (!isUsListingExchange(venue)) {
612
+ const what = research.kind === "news" ? "News alerting" : research.kind === "flow" ? "Options flow" : "Options data";
613
+ const { message, details } = await usListingsOnlyMessage(what, quoted.listing, market,
614
+ (key) => `gloom alerts add ${key} ${args.slice(2).join(" ")}`);
615
+ ctx.fail(message, details);
616
+ }
617
+ }
618
+ return quoted;
619
+ })
281
620
  : null;
282
621
  const priceListing = priced?.listing ?? null;
622
+ if (request?.kind === "metric" && priced) {
623
+ const exchange = canonicalExchange(priced.listing.exchange || priced.quote.listingExchangeName || priced.quote.exchangeName);
624
+ const preview = { ...createAlert(priced.listing.symbol, request.condition, request.price, exchange), metric: request.metric };
625
+ await withMarketData(ctx, market => metricReadings(market.dataProvider)(preview)).catch(error => ctx.fail(error instanceof Error ? error.message : String(error)));
626
+ priced.listing.exchange = exchange;
627
+ }
283
628
  if (request?.kind === "price" && request.thousandsDot && priced) {
284
629
  const question = thousandsDotMessage(request, priced.listing.key, lastPrice(priced.quote), exchangeOption);
285
630
  if (question) ctx.fail(question);
286
631
  }
632
+ // A bare coin ticker read as a fund (BTC, the Grayscale trust) gets `quote`'s note naming the coin's pair.
633
+ const coinNote = priced && !priced.listing.exchange ? notTheCoinNote(priced.listing.symbol, priced.quote) : null;
634
+ if (request?.kind === "price" && priced) {
635
+ // A target ten times away is a slip or another instrument's price, such as bitcoin's on the BTC fund.
636
+ // Against the price the alert is checked on, as Add Alert measures it.
637
+ const far = farTargetMessage(priced.listing.key, request.price, priced.quote.price, coinNote);
638
+ if (far) ctx.fail(far);
639
+ }
287
640
  // What a typed listing was read as (7203:TSE as 7203:JPX, MXN as MXN:FWB2), said beside what was added.
288
- let read = priced?.notes ?? [];
641
+ let read = [...priced?.notes ?? [], ...coinNote ? [coinNote] : []];
289
642
  let newEventRule: EventAlertRule | null = null;
290
- if (request && request.kind !== "price") {
291
- const target: ScheduledEventTarget = request.kind === "macro"
292
- ? { kind: "macro", event: request.event }
293
- : await requireListingArg(request.symbol, exchangeOption, ctx)
643
+ let newResearchRule: EventAlertRule | null = null;
644
+ if (research) {
645
+ const typed = priceListing?.exchange ? canonicalExchange(priceListing.exchange) : "";
646
+ const { kind, values } = researchFormValues(
647
+ research.kind === "iv_spike" ? research : { ...research, symbol: priceListing?.symbol ?? null } as ResearchAddRequest,
648
+ RESEARCH_EXCHANGES.has(typed) ? typed : "US",
649
+ );
650
+ try {
651
+ newResearchRule = createResearchAlert(kind, values);
652
+ } catch (error) {
653
+ ctx.fail(error instanceof Error ? error.message : String(error));
654
+ }
655
+ }
656
+ // A release named from the calendar, with what the words were read as and what else they name.
657
+ let newRelease: CalendarEventChoice | null = null;
658
+ // CPI and FOMC, typed so or named by one of their calendar rows (CPI y/y, Fed Interest Rate Decision).
659
+ let macro: { event: MacroAlertEvent; leadDays: number } | null = request?.kind === "macro" ? request : null;
660
+ let calendar: readonly EconEvent[] | null = null;
661
+ if (request?.kind === "release") {
662
+ const events = await loadAlertCalendar(now).catch((error: unknown) =>
663
+ ctx.fail(calendarUnavailableMessage(request.query, request.country, error instanceof Error ? error.message : String(error))));
664
+ calendar = events;
665
+ const match = matchCalendarEvent(request.query, request.country, events, now);
666
+ if (match.kind !== "match") {
667
+ return ctx.fail(match.kind === "none"
668
+ ? noReleaseMessage(request.query, request.country, match.suggestions, match.renamed)
669
+ : severalCountriesMessage(request.query, match.choices));
670
+ }
671
+ const { choice, others } = match;
672
+ const macroEvent = macroEventNamed(choice);
673
+ if (macroEvent) {
674
+ macro = { event: macroEvent, leadDays: request.leadDays };
675
+ read = [`Read "${request.query}" as the ${MACRO_EVENT_NAMES[macroEvent]} alert, which follows ${calendarEventLabel({ event: choice.name, country: choice.country })}.`];
676
+ } else {
677
+ newRelease = choice;
678
+ read = releaseReadNotes(request.query, choice, others, match.renamed);
679
+ const target: ScheduledEventTarget = { kind: "release", event: choice.name, country: choice.country, ...(choice.publisher ? { publisher: choice.publisher } : {}) };
680
+ try {
681
+ newEventRule = createScheduledAlert(target, request.leadDays, choice.next ? dateKey(choice.next.date) : null);
682
+ } catch (error) {
683
+ ctx.fail(error instanceof Error ? error.message : String(error));
684
+ }
685
+ }
686
+ }
687
+ // When CPI or FOMC came out earlier today: its figures are not sent to an alert set after them.
688
+ let macroOutAt: number | null = null;
689
+ const earnings = request?.kind === "earnings" ? request : null;
690
+ if (macro || earnings) {
691
+ const target: ScheduledEventTarget = macro
692
+ ? { kind: "macro", event: macro.event }
693
+ : await requireListingArg(earnings!.symbol, exchangeOption, ctx)
294
694
  .then((listing) => {
295
695
  read = listingReadNotes(listing);
296
696
  return { kind: "earnings" as const, symbol: listing.symbol, exchange: listing.exchange };
297
697
  });
698
+ const lead = earnings?.results ? "results" as const : (macro ?? earnings!).leadDays;
298
699
  // A listing the rule cannot follow fails before any request.
299
700
  try {
300
- createScheduledAlert(target, request.leadDays, null);
701
+ createScheduledAlert(target, lead, null);
301
702
  } catch (error) {
302
703
  ctx.fail(error instanceof Error ? error.message : String(error));
303
704
  }
304
705
  const eventDate = target.kind === "earnings"
305
706
  ? await withMarketData(ctx, (market) => requireEventDate(target, scheduledEventSources(market.dataProvider), today, (message) => ctx.fail(message)))
306
707
  : await requireEventDate(target, scheduledEventSources(null), today, (message) => ctx.fail(message));
307
- newEventRule = createScheduledAlert(target, request.leadDays, eventDate);
708
+ newEventRule = createScheduledAlert(target, lead, eventDate);
709
+ if (target.kind === "macro" && eventDate === today) {
710
+ const events = calendar ?? await loadAlertCalendar(now).catch(() => {
711
+ // Intentional ignore: without the calendar the alert is added as before, only without saying today's release is out.
712
+ return null;
713
+ });
714
+ const row = events ? releaseRowOn(target, eventDate, events) : null;
715
+ const at = row ? releaseInstant(row) : null;
716
+ if (at != null && at <= now) macroOutAt = at;
717
+ }
308
718
  }
309
719
 
310
720
  if (action === "list") {
311
- const { alerts, events } = await withConfigData(ctx, (config) => readAlertStores(config.config));
312
- // Earnings dates come through market data; CPI and FOMC dates do not need it.
721
+ // An IV level's last reading is this device's, beside the rules.
722
+ const { alerts, alertsError, events, checks } = await withConfigData(ctx, (config) => ({
723
+ ...readAlertStores(config.config),
724
+ checks: readIvLevelChecks(config.persistence?.pluginState?.get(ALERTS_PLUGIN_ID, resumeStateKey(IV_LEVEL_CHECKS_KEY))?.value),
725
+ }));
726
+ // Earnings dates come through market data; CPI, FOMC and calendar dates do not need it.
313
727
  const lookup = (targets: ScheduledEventTarget[]) => targets.some((target) => target.kind === "earnings")
314
- ? withMarketData(ctx, (market) => lookupScheduledDates(targets, scheduledEventSources(market.dataProvider), today))
315
- : lookupScheduledDates(targets, scheduledEventSources(null), today);
316
- const { dates, warnings } = await listEventDates(events.rules, today, lookup);
728
+ ? withMarketData(ctx, (market) => lookupScheduledDates(targets, scheduledEventSources(market.dataProvider), today, now))
729
+ : lookupScheduledDates(targets, scheduledEventSources(null), today, now);
730
+ const { dates, warnings } = await listEventDates(events.rules, now, lookup);
317
731
  const rows = [
318
732
  ...alerts.map(priceRow),
319
- ...events.rules.map((rule) => eventRow(rule, today, dates.has(rule.id) ? dates.get(rule.id) : rule.eventDate)),
733
+ ...events.rules.map((rule) => eventRow(rule, now, dates.has(rule.id) ? dates.get(rule.id) : rule.eventDate, checks[rule.id])),
320
734
  ];
321
- const allWarnings = [...(events.error ? [events.error] : []), ...warnings];
735
+ const allWarnings = [...(alertsError ? [alertsError] : []), ...(events.error ? [events.error] : []), ...warnings];
322
736
  ctx.printResult({ data: rows, ...(allWarnings.length ? { warnings: allWarnings } : {}) }, {
323
737
  rows: (data) => data.map(textRow),
324
738
  textColumns: [
@@ -331,22 +745,26 @@ export const alertsCliCommand: CliCommandDef = {
331
745
  { key: "lastCheckedPrice", header: "Last Price", align: "right", optional: true },
332
746
  { key: "triggeredAt", header: "Triggered", optional: true },
333
747
  ],
334
- empty: "No alerts. Add one with gloomberb alerts add AAPL above 250, alerts add CRWV earnings --days 3, or alerts add CPI --days 1.",
748
+ 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.",
335
749
  });
336
750
  return;
337
751
  }
338
752
 
339
753
  await withConfigData(ctx, async (config) => {
340
- const { pluginState, alerts, events } = readAlertStores(config.config);
754
+ const { pluginState, alerts, alertsError, levelErrors, events } = readAlertStores(config.config);
341
755
  const save = async (next: { alerts?: AlertRule[]; events?: EventAlertRule[] }) => {
756
+ if (next.alerts) for (const [key, value] of Object.entries(writeLevelAlertStores(next.alerts))) {
757
+ if (levelErrors[key] && value !== "[]") ctx.fail(levelErrors[key]!);
758
+ }
342
759
  const nextConfig = {
343
760
  ...config.config,
344
761
  pluginConfig: {
345
762
  ...config.config.pluginConfig,
346
763
  [ALERTS_PLUGIN_ID]: {
347
764
  ...pluginState,
348
- ...(next.alerts ? { [ALERTS_KEY]: serializeAlerts(next.alerts) } : {}),
349
- ...(next.events ? { [EVENT_ALERTS_KEY]: JSON.stringify(next.events) } : {}),
765
+ ...(next.alerts ? Object.fromEntries(Object.entries(writeLevelAlertStores(next.alerts)).filter(([key]) => !levelErrors[key])) : {}),
766
+ // Each list keeps what it holds of kinds this version does not know; IV levels go in their own.
767
+ ...(next.events ? writeEventAlertStores(events.lists, next.events) : {}),
350
768
  },
351
769
  },
352
770
  };
@@ -358,14 +776,27 @@ export const alertsCliCommand: CliCommandDef = {
358
776
  return events.rules;
359
777
  };
360
778
 
361
- if (request?.kind === "price") {
779
+ if (request?.kind === "price" || request?.kind === "metric") {
362
780
  const { condition, price } = request;
363
781
  const symbol = requireArg(priceListing?.key, ADD_USAGE, ctx);
364
- const alert = createAlert(symbol, condition, price, priceListing?.exchange);
782
+ const alert = { ...createAlert(symbol, condition, price, priceListing?.exchange), ...(request.kind === "metric" ? { metric: request.metric } : {}) };
365
783
  await save({ alerts: [...alerts, alert] });
366
784
  ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert }, ...(read.length > 0 ? { warnings: read } : {}) }, {
367
785
  // The delivery, as docs/usage.md states it, before any full stop, which after the price would read as part of it (below 81.).
368
- text: (data) => `Added alert ${data.alert.id}: ${symbol} ${condition} ${price} (${PRICE_DELIVERY}).${dryRunNote(data.dryRun)}`,
786
+ text: (data) => `Added alert ${data.alert.id}: ${data.alert.metric ? metricDescription(data.alert) : `${symbol} ${condition} ${price}`} (${PRICE_DELIVERY}).${dryRunNote(data.dryRun)}`,
787
+ });
788
+ return;
789
+ }
790
+ if (newResearchRule) {
791
+ const rule = newResearchRule;
792
+ const rules = requireEvents();
793
+ const existing = rules.find((entry) => entry.kind === rule.kind && entry.target === rule.target && entry.value.toLowerCase() === rule.value.toLowerCase());
794
+ if (existing) ctx.fail(`This alert already exists (ID ${existing.id}).`);
795
+ if (rules.length >= MAX_EVENT_ALERTS) ctx.fail(`Keep at most ${MAX_EVENT_ALERTS} event alerts. Delete one with gloom alerts delete <id>.`);
796
+ await save({ events: [...rules, rule] });
797
+ const kind = research!.kind;
798
+ ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert: rule }, ...(read.length > 0 ? { warnings: read } : {}) }, {
799
+ text: (data) => `Added alert ${data.alert.id}: ${alertKindLabel(rule.kind)} ${researchAlertDescription(rule)} (${researchDelivery(kind)}).${dryRunNote(data.dryRun)}`,
369
800
  });
370
801
  return;
371
802
  }
@@ -375,24 +806,38 @@ export const alertsCliCommand: CliCommandDef = {
375
806
  const scheduled = readScheduledRule(rule)!;
376
807
  const name = scheduledEventName(scheduled.target);
377
808
  const existing = rules.find((entry) => entry.kind === rule.kind && entry.value.toLowerCase() === rule.value.toLowerCase());
378
- if (existing) ctx.fail(`An alert ${plural(scheduled.leadDays, "day")} before ${name} already exists (ID ${existing.id}).`);
379
- if (rules.length >= MAX_EVENT_ALERTS) ctx.fail(`Keep at most ${MAX_EVENT_ALERTS} event alerts. Delete one with gloomberb alerts delete <id>.`);
809
+ if (existing) ctx.fail(scheduled.trigger === "results"
810
+ ? `An alert for ${name} results already exists (ID ${existing.id}).`
811
+ : `An alert ${plural(scheduled.leadDays, "day")} before ${name} already exists (ID ${existing.id}).`);
812
+ if (rules.length >= MAX_EVENT_ALERTS) ctx.fail(`Keep at most ${MAX_EVENT_ALERTS} event alerts. Delete one with gloom alerts delete <id>.`);
380
813
  await save({ events: [...rules, rule] });
381
- const eventDate = rule.eventDate!;
382
- const due = scheduledPhase(eventDate, scheduled.leadDays, undefined, today) === "due";
383
- const delivery = scheduled.target.kind === "macro" ? ", only while it is running" : " while it is running, or on your phone with Cloud sync";
384
- ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert: rule }, ...(read.length > 0 ? { warnings: read } : {}) }, {
385
- // Delivery, as docs/usage.md states it: CPI and FOMC notify in the running app only; earnings also reach the phone.
386
- text: (data) => `Added alert ${data.alert.id}: ${name} on ${eventDate}, ${plural(scheduled.leadDays, "day")} before (${due
387
- ? "already reached, so the app alerts at its next check"
388
- : `the app alerts on ${leadStartDate(eventDate, scheduled.leadDays)}`}${delivery}).${dryRunNote(data.dryRun)}`,
814
+ const eventDate = rule.eventDate ?? null;
815
+ const due = !!eventDate && scheduledPhase(eventDate, scheduled.leadDays, undefined, scheduledToday(scheduled.target, now)) === "due";
816
+ const release = followsRelease(scheduled.target);
817
+ // A release that prints no figures (a plenum, a speech) alerts only before it.
818
+ const prints = release && (!newRelease?.next || expectsFigures(newRelease.next));
819
+ const delivery = release ? ", only while it is running" : " while it is running, or on your phone with Cloud sync";
820
+ const when = !eventDate ? null : newRelease?.next ? releaseWhen(newRelease.next) : `on ${eventDate}`;
821
+ const reminder = !eventDate
822
+ ? "the app alerts once the calendar lists its next date, about two weeks ahead"
823
+ : macroOutAt != null ? `it came out today at ${new Date(macroOutAt).toISOString().slice(11, 16)} UTC, so the app alerts before the next one`
824
+ : due ? "already reached, so the app alerts at its next check" : `the app alerts on ${leadStartDate(eventDate, scheduled.leadDays)}`;
825
+ ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, alert: rule,
826
+ ...newRelease ? { release: releaseData(newRelease) } : {} }, ...(read.length > 0 ? { warnings: read } : {}) }, {
827
+ // Delivery, as docs/usage.md states it: CPI, FOMC and calendar releases notify in the running app only; earnings also reach the phone,
828
+ // which Gloom Cloud alerts with the same results once the actual EPS is stored for a results alert.
829
+ text: (data) => scheduled.trigger === "results"
830
+ ? `Added alert ${data.alert.id}: ${name} on ${eventDate}, when results are out (the app alerts once the actual EPS is in while it is running, and with Cloud sync your phone gets the same alert).${dryRunNote(data.dryRun)}`
831
+ : `Added alert ${data.alert.id}: ${name}${when ? ` ${when}` : ""}, ${plural(scheduled.leadDays, "day")} before${prints ? " and when it prints" : ""} (${reminder}${
832
+ prints ? ", and with the actual, forecast and prior once out" : ""}${delivery}).${dryRunNote(data.dryRun)}`,
389
833
  });
390
834
  return;
391
835
  }
392
836
  if (action === "delete" || action === "rm") {
393
- const id = requireArg(args[1], "Usage: gloomberb alerts delete <id>", ctx);
837
+ const id = requireArg(args[1], "Usage: gloom alerts delete <id>", ctx);
394
838
  const isPrice = alerts.some((alert) => alert.id === id);
395
839
  const isEvent = !isPrice && events.rules.some((rule) => rule.id === id);
840
+ if (!isPrice && !isEvent && alertsError) ctx.fail(alertsError);
396
841
  if (isPrice) await save({ alerts: alerts.filter((alert) => alert.id !== id) });
397
842
  if (isEvent) await save({ events: requireEvents().filter((rule) => rule.id !== id) });
398
843
  const changed = isPrice || isEvent;
@@ -404,13 +849,16 @@ export const alertsCliCommand: CliCommandDef = {
404
849
  return;
405
850
  }
406
851
  if (action === "rearm") {
407
- const id = requireArg(args[1], "Usage: gloomberb alerts rearm <id>", ctx);
852
+ const id = requireArg(args[1], "Usage: gloom alerts rearm <id>", ctx);
408
853
  const isPrice = alerts.some((alert) => alert.id === id);
409
854
  const rule = isPrice ? null : events.rules.find((entry) => entry.id === id) ?? null;
855
+ if (!isPrice && !rule && alertsError) ctx.fail(alertsError);
410
856
  if (isPrice) await save({ alerts: alerts.map((alert) => alert.id === id ? rearmAlert(alert) : alert) });
411
857
  if (rule) {
412
858
  // A scheduled alert alerts again for its coming event; any other event alert resumes.
413
- const next = readScheduledRule(rule) ? rearmScheduledAlert(rule) : rule.status === "paused" ? toggleEventAlert(rule) : rule;
859
+ const next = readScheduledRule(rule) ? rearmScheduledAlert(rule)
860
+ : rule.kind === "iv_level" && rule.triggeredAt ? rearmIvLevelAlert(rule)
861
+ : rule.status === "paused" ? toggleEventAlert(rule) : rule;
414
862
  await save({ events: requireEvents().map((entry) => entry.id === id ? next : entry) });
415
863
  }
416
864
  const found = isPrice || !!rule;
@@ -421,7 +869,7 @@ export const alertsCliCommand: CliCommandDef = {
421
869
  });
422
870
  return;
423
871
  }
424
- ctx.fail("Usage: gloomberb alerts list|add|delete|rearm");
872
+ ctx.fail("Usage: gloom alerts list|add|delete|rearm");
425
873
  });
426
874
  },
427
875
  };