gloomberb 0.16.4 → 0.16.6

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 (852) hide show
  1. package/README.md +14 -91
  2. package/package.json +5 -1
  3. package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
  4. package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
  5. package/src/account/billing.ts +57 -0
  6. package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
  7. package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
  8. package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
  9. package/src/account/session.ts +16 -0
  10. package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
  11. package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
  12. package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
  13. package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
  14. package/src/api-client/central-bank-rates.ts +6 -1
  15. package/src/api-client/chat.ts +26 -0
  16. package/src/api-client/credit-boards.ts +2 -0
  17. package/src/api-client/data.ts +73 -3
  18. package/src/api-client/earnings.ts +6 -0
  19. package/src/api-client/europe-energy.ts +106 -0
  20. package/src/api-client/futures-curve.ts +56 -0
  21. package/src/api-client/index.ts +13 -1
  22. package/src/api-client/market-discovery.ts +13 -1
  23. package/src/api-client/normalizers.ts +32 -0
  24. package/src/api-client/paths.ts +7 -0
  25. package/src/api-client/perps.ts +8 -0
  26. package/src/api-client/request.ts +9 -2
  27. package/src/api-client/research-activity.ts +15 -10
  28. package/src/api-client/socket.ts +29 -0
  29. package/src/api-client/themes.ts +22 -1
  30. package/src/api-client/types.ts +78 -2
  31. package/src/api-client/yield-curves.ts +8 -0
  32. package/src/app/global-shortcuts.ts +50 -2
  33. package/src/app/keybindings/actions.ts +12 -1
  34. package/src/app/pane-runtime/plugin-bindings.ts +2 -2
  35. package/src/app/pane-runtime/ticker-open-runtime.ts +16 -6
  36. package/src/app/runtime/desktop-deeplink.ts +10 -5
  37. package/src/app/runtime/startup.ts +1 -1
  38. package/src/app.tsx +6 -0
  39. package/src/architecture/core-builtin-allowlist.json +8 -26
  40. package/src/ask-gloom/pane-context.ts +114 -0
  41. package/src/ask-gloom/pane-request.ts +70 -0
  42. package/src/ask-gloom/pane-text.ts +201 -0
  43. package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
  44. package/src/capabilities/chart-series.ts +19 -2
  45. package/src/capabilities/factories.ts +1 -0
  46. package/src/cli/central-bank-events.ts +23 -0
  47. package/src/cli/command-options.ts +17 -5
  48. package/src/cli/commands/crypto-hints.ts +35 -2
  49. package/src/cli/commands/fred-series.ts +146 -0
  50. package/src/cli/commands/market.ts +501 -115
  51. package/src/cli/commands/news-hints.ts +62 -0
  52. package/src/cli/commands/news-search.ts +53 -0
  53. package/src/cli/commands/options-leaps.ts +5 -2
  54. package/src/cli/commands/overview.ts +132 -26
  55. package/src/cli/commands/remote.ts +2 -1
  56. package/src/cli/commands/search.ts +317 -13
  57. package/src/cli/commands/system.ts +55 -8
  58. package/src/cli/commands/ticker.ts +155 -18
  59. package/src/cli/context.ts +45 -14
  60. package/src/cli/desktop-pane-shot.ts +76 -7
  61. package/src/cli/earnings-calendar.ts +71 -0
  62. package/src/cli/errors.ts +7 -5
  63. package/src/cli/exit-codes.ts +8 -0
  64. package/src/cli/export-source.ts +58 -0
  65. package/src/cli/freshness.ts +18 -7
  66. package/src/cli/glossary.ts +483 -0
  67. package/src/cli/help.ts +10 -0
  68. package/src/cli/helpers.ts +2 -5
  69. package/src/cli/history-rows.ts +118 -7
  70. package/src/cli/index-roots.ts +7 -0
  71. package/src/cli/index.ts +29 -9
  72. package/src/cli/known-gaps.ts +84 -0
  73. package/src/cli/listing-arg.ts +551 -97
  74. package/src/cli/not-a-ticker.ts +24 -9
  75. package/src/cli/pane-functions/access-gate.ts +13 -2
  76. package/src/cli/pane-functions/capabilities.ts +4 -3
  77. package/src/cli/pane-functions/catalog.ts +214 -40
  78. package/src/cli/pane-functions/data.ts +42 -8
  79. package/src/cli/pane-functions/discovery.ts +1 -0
  80. package/src/cli/pane-functions/dom.ts +37 -8
  81. package/src/cli/pane-functions/freshness.ts +393 -49
  82. package/src/cli/pane-functions/function-help.ts +31 -15
  83. package/src/cli/pane-functions/headless.ts +98 -30
  84. package/src/cli/pane-functions/index.ts +101 -19
  85. package/src/cli/pane-functions/market-session.ts +22 -8
  86. package/src/cli/pane-functions/options.ts +71 -22
  87. package/src/cli/pane-functions/report-notices.ts +25 -7
  88. package/src/cli/pane-functions/report.ts +15 -8
  89. package/src/cli/pane-functions/resolver.ts +74 -19
  90. package/src/cli/pane-functions/screenshot.ts +250 -27
  91. package/src/cli/perpetual-contract.ts +60 -0
  92. package/src/cli/registry.ts +3 -1
  93. package/src/cli/report-tables.ts +26 -7
  94. package/src/cli/result.ts +45 -11
  95. package/src/cli/series-search.ts +111 -0
  96. package/src/cli/staged-tickers.ts +52 -0
  97. package/src/cli/symbol-args.ts +27 -0
  98. package/src/cli/symbol-list.ts +154 -0
  99. package/src/cli/symbol-rows.ts +154 -0
  100. package/src/cli/ticker-resolution.ts +311 -27
  101. package/src/cli/types.ts +5 -0
  102. package/src/components/chart/composite/axis-overlays.tsx +14 -3
  103. package/src/components/chart/composite/composite-chart.tsx +71 -11
  104. package/src/components/chart/composite/crosshair-readout.tsx +150 -0
  105. package/src/components/chart/composite/downsample.ts +1 -1
  106. package/src/components/chart/composite/format.ts +29 -0
  107. package/src/components/chart/composite/interactions.ts +9 -2
  108. package/src/components/chart/composite/legend.tsx +100 -33
  109. package/src/components/chart/composite/panel-bitmap.ts +70 -35
  110. package/src/components/chart/composite/panel-labels.ts +18 -17
  111. package/src/components/chart/composite/panel-series.ts +1 -0
  112. package/src/components/chart/composite/panel-surface.tsx +46 -21
  113. package/src/components/chart/composite/price-axis-labels.tsx +3 -1
  114. package/src/components/chart/composite/rasterizer.ts +50 -8
  115. package/src/components/chart/composite/scene.ts +2 -1
  116. package/src/components/chart/composite/time-scale.ts +26 -9
  117. package/src/components/chart/composite/types.ts +18 -0
  118. package/src/components/chart/composite/use-composite-viewport.ts +30 -2
  119. package/src/components/chart/native/raster/crosshair.ts +8 -3
  120. package/src/components/chart/native/raster/primitives.ts +85 -21
  121. package/src/components/chart/native/surface/manager.ts +33 -8
  122. package/src/components/chart/surface3d/chart.tsx +1 -1
  123. package/src/components/command-bar/action-runtime.ts +1 -1
  124. package/src/components/command-bar/commands/collection-list.ts +210 -0
  125. package/src/components/command-bar/commands/collection.ts +40 -1
  126. package/src/components/command-bar/commands/direct/index.ts +4 -0
  127. package/src/components/command-bar/commands/registry.ts +8 -0
  128. package/src/components/command-bar/panel/index.tsx +11 -3
  129. package/src/components/command-bar/panel/runtime.ts +3 -0
  130. package/src/components/command-bar/panel/types.ts +2 -0
  131. package/src/components/command-bar/routes/root/plugin-install.ts +4 -4
  132. package/src/components/command-bar/routes/root/provider-search.ts +4 -3
  133. package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
  134. package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
  135. package/src/components/command-bar/routing/actions.ts +1 -1
  136. package/src/components/command-bar/workflow/tickers.ts +42 -17
  137. package/src/components/layout/detached-pane-shell.tsx +68 -39
  138. package/src/components/layout/floating-pane.tsx +10 -1
  139. package/src/components/layout/header.tsx +25 -5
  140. package/src/components/layout/missing-pane.tsx +3 -3
  141. package/src/components/layout/pane/capture-stamp.ts +41 -0
  142. package/src/components/layout/pane/capture-status.ts +12 -0
  143. package/src/components/layout/pane/content.tsx +7 -13
  144. package/src/components/layout/pane/footer/index.tsx +6 -2
  145. package/src/components/layout/pane/footer/keyboard.ts +3 -0
  146. package/src/components/layout/pane/footer/model.ts +41 -1
  147. package/src/components/layout/pane/header.tsx +50 -1
  148. package/src/components/layout/pane/index.tsx +21 -3
  149. package/src/components/layout/pane/sidebar.tsx +88 -21
  150. package/src/components/layout/shell/active-drag.ts +21 -5
  151. package/src/components/layout/shell/cursor-occlusion.ts +2 -1
  152. package/src/components/layout/shell/drag/index.ts +39 -9
  153. package/src/components/layout/shell/drag/live.ts +61 -51
  154. package/src/components/layout/shell/drag/runtime.ts +54 -12
  155. package/src/components/layout/shell/index.tsx +84 -65
  156. package/src/components/layout/shell/menu.ts +14 -1
  157. package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
  158. package/src/components/layout/shell/pane/actions.ts +32 -10
  159. package/src/components/layout/shell/pane/ask.ts +131 -0
  160. package/src/components/layout/shell/pane/capture.ts +162 -0
  161. package/src/components/layout/shell/pane/layers.tsx +218 -50
  162. package/src/components/layout/shell/pane/management-shortcuts.ts +46 -1
  163. package/src/components/layout/shell/shortcuts.ts +21 -4
  164. package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
  165. package/src/components/markdown-text.tsx +62 -20
  166. package/src/components/metric-treemap/heat-surface.tsx +8 -1
  167. package/src/components/onboarding/account-step/form-panel.tsx +1 -1
  168. package/src/components/onboarding/account-step/index.tsx +1 -1
  169. package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
  170. package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
  171. package/src/components/onboarding/onboarding-keyboard.ts +1 -1
  172. package/src/components/onboarding/onboarding-wizard.tsx +2 -2
  173. package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
  174. package/src/components/onboarding/wizard-account.ts +2 -2
  175. package/src/components/pane-template-wizard.tsx +9 -1
  176. package/src/components/price-selector-dialog.tsx +11 -2
  177. package/src/components/reader/article-reader.tsx +642 -0
  178. package/src/components/reader/article-text.ts +527 -0
  179. package/src/components/sign-in-gate.tsx +3 -3
  180. package/src/components/ticker/list-table-view.tsx +43 -2
  181. package/src/components/ui/action-row.tsx +2 -1
  182. package/src/components/ui/button.tsx +7 -1
  183. package/src/components/ui/choice-dialog.tsx +6 -4
  184. package/src/components/ui/confirm-dialog.tsx +28 -18
  185. package/src/components/ui/data-table/index.tsx +18 -6
  186. package/src/components/ui/data-table/opentui/index.tsx +37 -6
  187. package/src/components/ui/data-table/types.ts +12 -0
  188. package/src/components/ui/dialog-actions.tsx +64 -0
  189. package/src/components/ui/display.tsx +17 -3
  190. package/src/components/ui/frame.tsx +35 -2
  191. package/src/components/ui/icon.tsx +6 -1
  192. package/src/components/ui/query-bar.tsx +10 -1
  193. package/src/components/ui/text-prompt-dialog.tsx +11 -9
  194. package/src/data/cached-query.ts +2 -0
  195. package/src/data/config/store/migrations.ts +7 -0
  196. package/src/data/config/store/normalize.ts +7 -2
  197. package/src/data/config/store/portfolio-as-of-column.ts +57 -0
  198. package/src/data/ticker-repository.ts +5 -0
  199. package/src/data/ticker-store.ts +15 -0
  200. package/src/docs/content.ts +215 -0
  201. package/src/docs/load.ts +139 -0
  202. package/src/docs/requests.ts +39 -0
  203. package/src/docs/snapshot.chunk.ts +8 -0
  204. package/src/docs/snapshot.json +7555 -0
  205. package/src/docs/topics.ts +216 -0
  206. package/src/i18n/es.ts +65 -5
  207. package/src/i18n/ja.ts +65 -5
  208. package/src/i18n/ko.ts +65 -5
  209. package/src/i18n/zh-cn.ts +65 -5
  210. package/src/i18n/zh-tw.ts +65 -5
  211. package/src/layout/desks.ts +1 -1
  212. package/src/layout/pane-manager/dock-tree.ts +18 -0
  213. package/src/layout/pane-manager/index.ts +1 -0
  214. package/src/layout-marketplace/gallery.tsx +1 -1
  215. package/src/market-data/coordinator/chart.ts +10 -4
  216. package/src/market-data/coordinator/index.ts +6 -3
  217. package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
  218. package/src/market-data/futures-contract.ts +151 -0
  219. package/src/market-data/hooks.tsx +10 -2
  220. package/src/market-data/market/format.ts +39 -21
  221. package/src/market-data/market/freshness.ts +84 -20
  222. package/src/market-data/market/session-close-note.ts +3 -2
  223. package/src/market-data/market/status.ts +19 -23
  224. package/src/market-data/published-apac-sessions.ts +23 -1
  225. package/src/market-data/quotes/contributions.ts +5 -0
  226. package/src/market-data/quotes/metadata.ts +19 -24
  227. package/src/market-data/quotes/resolution.ts +5 -0
  228. package/src/market-data/quotes/screener-live-quotes.ts +5 -4
  229. package/src/market-data/result-types.ts +2 -0
  230. package/src/notifications/app-notifier.ts +17 -1
  231. package/src/notifications/notification-log.ts +180 -0
  232. package/src/plugins/auto-update-node.ts +2 -2
  233. package/src/plugins/auto-update.ts +2 -2
  234. package/src/plugins/builtin/account-management/index.tsx +1 -0
  235. package/src/plugins/builtin/account-management/model.ts +1 -56
  236. package/src/plugins/builtin/account-management/pane.tsx +6 -4
  237. package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
  238. package/src/plugins/builtin/alerts/catalysts.ts +1 -1
  239. package/src/plugins/builtin/alerts/cli.ts +313 -47
  240. package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
  241. package/src/plugins/builtin/alerts/events.ts +23 -2
  242. package/src/plugins/builtin/alerts/focus.ts +27 -0
  243. package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
  244. package/src/plugins/builtin/alerts/history.ts +2 -0
  245. package/src/plugins/builtin/alerts/index.tsx +47 -11
  246. package/src/plugins/builtin/alerts/pane.tsx +27 -1
  247. package/src/plugins/builtin/alerts/research-builder.ts +8 -3
  248. package/src/plugins/builtin/alerts/research-rules.ts +18 -2
  249. package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
  250. package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
  251. package/src/plugins/builtin/alerts/scheduled.ts +254 -0
  252. package/src/plugins/builtin/alerts/targets.ts +92 -0
  253. package/src/plugins/builtin/analytics/index.tsx +8 -3
  254. package/src/plugins/builtin/analytics/risk-headless.ts +60 -27
  255. package/src/plugins/builtin/analytics/risk-math.ts +91 -27
  256. package/src/plugins/builtin/analytics/risk-model.ts +35 -5
  257. package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
  258. package/src/plugins/builtin/attention/headless.ts +1 -1
  259. package/src/plugins/builtin/attention/index.tsx +2 -0
  260. package/src/plugins/builtin/attention/pane.tsx +3 -1
  261. package/src/plugins/builtin/attention/series.ts +26 -0
  262. package/src/plugins/builtin/awards/headless.ts +88 -18
  263. package/src/plugins/builtin/awards/index.tsx +2 -0
  264. package/src/plugins/builtin/awards/model.ts +41 -1
  265. package/src/plugins/builtin/awards/pane.tsx +22 -13
  266. package/src/plugins/builtin/awards/series.ts +29 -0
  267. package/src/plugins/builtin/backtest/headless.ts +2 -2
  268. package/src/plugins/builtin/bond-calculator/model.ts +2 -1
  269. package/src/plugins/builtin/broker-manager/index.tsx +1 -0
  270. package/src/plugins/builtin/catalysts/headless.ts +1 -1
  271. package/src/plugins/builtin/catalysts/pane.tsx +3 -1
  272. package/src/plugins/builtin/cds/client.ts +49 -12
  273. package/src/plugins/builtin/cds/headless.ts +119 -19
  274. package/src/plugins/builtin/cds/index.tsx +3 -1
  275. package/src/plugins/builtin/cds/model.ts +214 -32
  276. package/src/plugins/builtin/cds/pane.tsx +58 -14
  277. package/src/plugins/builtin/cds/series.ts +19 -0
  278. package/src/plugins/builtin/central-bank-rates/client.ts +4 -0
  279. package/src/plugins/builtin/central-bank-rates/headless.ts +46 -16
  280. package/src/plugins/builtin/central-bank-rates/index.tsx +3 -1
  281. package/src/plugins/builtin/central-bank-rates/model.ts +99 -0
  282. package/src/plugins/builtin/central-bank-rates/pane.tsx +1 -1
  283. package/src/plugins/builtin/central-bank-rates/series.ts +36 -0
  284. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +193 -255
  285. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
  286. package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
  287. package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
  288. package/src/plugins/builtin/chart-composer/headless-schema.ts +1 -1
  289. package/src/plugins/builtin/chart-composer/headless.ts +70 -5
  290. package/src/plugins/builtin/chart-composer/index.tsx +15 -3
  291. package/src/plugins/builtin/chart-composer/pane.tsx +81 -38
  292. package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
  293. package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
  294. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
  295. package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
  296. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  297. package/src/plugins/builtin/chat/content/composer.tsx +5 -1
  298. package/src/plugins/builtin/chat/content/footer.tsx +12 -0
  299. package/src/plugins/builtin/chat/content/index.tsx +89 -5
  300. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
  301. package/src/plugins/builtin/chat/content/new-messages.tsx +89 -0
  302. package/src/plugins/builtin/chat/content/row-activity.ts +132 -0
  303. package/src/plugins/builtin/chat/content/scroll.ts +127 -27
  304. package/src/plugins/builtin/chat/content/shortcuts.ts +2 -2
  305. package/src/plugins/builtin/chat/content/transcript.tsx +145 -78
  306. package/src/plugins/builtin/chat/content/types.ts +1 -0
  307. package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
  308. package/src/plugins/builtin/chat/controller/index.ts +123 -1
  309. package/src/plugins/builtin/chat/controller/message-loading.ts +3 -0
  310. package/src/plugins/builtin/chat/controller/messages.ts +7 -2
  311. package/src/plugins/builtin/chat/controller/realtime.ts +10 -1
  312. package/src/plugins/builtin/chat/controller/send.ts +61 -1
  313. package/src/plugins/builtin/chat/controller/state.ts +8 -0
  314. package/src/plugins/builtin/chat/ghost-user.ts +20 -0
  315. package/src/plugins/builtin/chat/index.tsx +23 -5
  316. package/src/plugins/builtin/chat/layout.ts +78 -5
  317. package/src/plugins/builtin/chat/message/ai-badge.tsx +18 -0
  318. package/src/plugins/builtin/chat/message/desktop.tsx +55 -6
  319. package/src/plugins/builtin/chat/message/header.tsx +16 -4
  320. package/src/plugins/builtin/chat/message/profile-popover.tsx +5 -1
  321. package/src/plugins/builtin/chat/message/reactions.tsx +210 -0
  322. package/src/plugins/builtin/chat/message/render-state.ts +18 -2
  323. package/src/plugins/builtin/chat/message/terminal.tsx +43 -4
  324. package/src/plugins/builtin/chat/message/types.ts +12 -0
  325. package/src/plugins/builtin/chat/reactions.ts +197 -0
  326. package/src/plugins/builtin/chat/share/flow.tsx +87 -0
  327. package/src/plugins/builtin/chat/share/targets.ts +27 -0
  328. package/src/plugins/builtin/chat/status-widget.tsx +6 -3
  329. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +148 -27
  330. package/src/plugins/builtin/chat/unread-inbox.ts +22 -0
  331. package/src/plugins/builtin/cloud/askg/attached-pane.tsx +69 -0
  332. package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
  333. package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
  334. package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
  335. package/src/plugins/builtin/cloud/askg/model.ts +27 -8
  336. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +94 -0
  337. package/src/plugins/builtin/cloud/askg/pane.tsx +141 -12
  338. package/src/plugins/builtin/cloud/askg/pending-question.ts +11 -0
  339. package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
  340. package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
  341. package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
  342. package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
  343. package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
  344. package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
  345. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  346. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
  347. package/src/plugins/builtin/cloud/plugin.tsx +19 -2
  348. package/src/plugins/builtin/cloud/team/model.ts +5 -0
  349. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  350. package/src/plugins/builtin/cloud/team/store.ts +8 -3
  351. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
  352. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
  353. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
  354. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
  355. package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
  356. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  357. package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
  358. package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
  359. package/src/plugins/builtin/company-attention/headless.ts +21 -7
  360. package/src/plugins/builtin/company-attention/index.tsx +2 -0
  361. package/src/plugins/builtin/company-attention/model.ts +26 -7
  362. package/src/plugins/builtin/company-attention/pane.tsx +12 -9
  363. package/src/plugins/builtin/company-attention/series.ts +52 -0
  364. package/src/plugins/builtin/company-kpis/headless.ts +97 -10
  365. package/src/plugins/builtin/company-kpis/model.ts +24 -2
  366. package/src/plugins/builtin/company-kpis/pane.tsx +17 -16
  367. package/src/plugins/builtin/composite-plugins.ts +19 -13
  368. package/src/plugins/builtin/connections/index.ts +1 -0
  369. package/src/plugins/builtin/cot/headless.ts +1 -1
  370. package/src/plugins/builtin/cot/index.tsx +3 -1
  371. package/src/plugins/builtin/cot/model.ts +40 -11
  372. package/src/plugins/builtin/cot/series.ts +27 -0
  373. package/src/plugins/builtin/cpi/headless.ts +3 -3
  374. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +10 -1
  375. package/src/plugins/builtin/credit-boards/client.ts +6 -2
  376. package/src/plugins/builtin/credit-boards/headless.ts +108 -31
  377. package/src/plugins/builtin/credit-boards/index.tsx +2 -0
  378. package/src/plugins/builtin/credit-boards/model.ts +11 -1
  379. package/src/plugins/builtin/credit-boards/series.ts +66 -0
  380. package/src/plugins/builtin/credit-conditions/client.ts +12 -9
  381. package/src/plugins/builtin/credit-conditions/headless.ts +13 -2
  382. package/src/plugins/builtin/credit-conditions/index.tsx +18 -9
  383. package/src/plugins/builtin/credit-conditions/model.ts +19 -11
  384. package/src/plugins/builtin/credit-conditions/series.ts +16 -0
  385. package/src/plugins/builtin/credit-documents/headless.ts +33 -3
  386. package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
  387. package/src/plugins/builtin/crypto-board/headless.ts +2 -3
  388. package/src/plugins/builtin/crypto-board/index.tsx +2 -0
  389. package/src/plugins/builtin/crypto-board/model.ts +12 -0
  390. package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
  391. package/src/plugins/builtin/crypto-board/series.ts +48 -0
  392. package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
  393. package/src/plugins/builtin/debt-maturities/headless.ts +17 -11
  394. package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
  395. package/src/plugins/builtin/debt-maturities/series.ts +45 -0
  396. package/src/plugins/builtin/dividend-yield/client.ts +3 -0
  397. package/src/plugins/builtin/dividend-yield/headless.ts +7 -4
  398. package/src/plugins/builtin/dividend-yield/index.tsx +3 -1
  399. package/src/plugins/builtin/dividend-yield/pane.tsx +3 -1
  400. package/src/plugins/builtin/dividend-yield/series.ts +20 -0
  401. package/src/plugins/builtin/dividend-yield/types.ts +2 -0
  402. package/src/plugins/builtin/docs/blocks.tsx +181 -0
  403. package/src/plugins/builtin/docs/index.tsx +68 -0
  404. package/src/plugins/builtin/docs/pane.tsx +426 -0
  405. package/src/plugins/builtin/docs/targets.ts +72 -0
  406. package/src/plugins/builtin/docs/use-docs.ts +60 -0
  407. package/src/plugins/builtin/doe/headless.ts +12 -8
  408. package/src/plugins/builtin/doe/index.tsx +2 -0
  409. package/src/plugins/builtin/doe/series.ts +62 -0
  410. package/src/plugins/builtin/earnings/client.ts +3 -2
  411. package/src/plugins/builtin/earnings/format.ts +1 -5
  412. package/src/plugins/builtin/earnings/headless.ts +58 -6
  413. package/src/plugins/builtin/earnings/history-model.ts +15 -1
  414. package/src/plugins/builtin/earnings/history-table.ts +13 -2
  415. package/src/plugins/builtin/earnings/history.tsx +7 -3
  416. package/src/plugins/builtin/earnings/index.tsx +9 -0
  417. package/src/plugins/builtin/earnings-calls/headless.ts +28 -2
  418. package/src/plugins/builtin/earnings-calls/index.tsx +1 -1
  419. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -0
  420. package/src/plugins/builtin/econ/fred-catalog.ts +198 -0
  421. package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
  422. package/src/plugins/builtin/econ/index.tsx +2 -0
  423. package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
  424. package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
  425. package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
  426. package/src/plugins/builtin/equity-screener/headless.ts +3 -3
  427. package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
  428. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -3
  429. package/src/plugins/builtin/exposure/drivers.ts +13 -0
  430. package/src/plugins/builtin/exposure/headless.ts +205 -13
  431. package/src/plugins/builtin/exposure/holdings.ts +1 -1
  432. package/src/plugins/builtin/exposure/index.tsx +1 -0
  433. package/src/plugins/builtin/exposure/model.ts +23 -3
  434. package/src/plugins/builtin/exposure/pane.tsx +14 -12
  435. package/src/plugins/builtin/fear-greed/index.tsx +18 -10
  436. package/src/plugins/builtin/fear-greed/series.ts +52 -0
  437. package/src/plugins/builtin/futures/index.tsx +43 -3
  438. package/src/plugins/builtin/futures/model.ts +1 -1
  439. package/src/plugins/builtin/futures/returns.ts +1 -1
  440. package/src/plugins/builtin/futures/series.ts +13 -0
  441. package/src/plugins/builtin/futures/table.tsx +1 -1
  442. package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
  443. package/src/plugins/builtin/futures-curve/client.ts +12 -3
  444. package/src/plugins/builtin/futures-curve/headless.ts +71 -10
  445. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  446. package/src/plugins/builtin/futures-curve/model.ts +66 -11
  447. package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
  448. package/src/plugins/builtin/fx-matrix/client.ts +63 -0
  449. package/src/plugins/builtin/fx-matrix/export.ts +2 -1
  450. package/src/plugins/builtin/fx-matrix/headless.ts +154 -0
  451. package/src/plugins/builtin/fx-matrix/index.tsx +4 -4
  452. package/src/plugins/builtin/gpu/board.tsx +13 -10
  453. package/src/plugins/builtin/gpu/changes.tsx +4 -4
  454. package/src/plugins/builtin/gpu/client.ts +19 -1
  455. package/src/plugins/builtin/gpu/equities.tsx +14 -10
  456. package/src/plugins/builtin/gpu/headless.ts +58 -27
  457. package/src/plugins/builtin/gpu/history.tsx +3 -3
  458. package/src/plugins/builtin/gpu/index.tsx +2 -0
  459. package/src/plugins/builtin/gpu/model.ts +101 -16
  460. package/src/plugins/builtin/gpu/pane.tsx +28 -12
  461. package/src/plugins/builtin/gpu/series.ts +38 -0
  462. package/src/plugins/builtin/help/function-card.tsx +25 -6
  463. package/src/plugins/builtin/help/function-index.ts +2 -0
  464. package/src/plugins/builtin/help/index.tsx +31 -9
  465. package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
  466. package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
  467. package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
  468. package/src/plugins/builtin/holders/client.ts +22 -3
  469. package/src/plugins/builtin/holders/format.ts +18 -3
  470. package/src/plugins/builtin/holders/headless.ts +36 -133
  471. package/src/plugins/builtin/holders/pane.tsx +11 -4
  472. package/src/plugins/builtin/holders/report-header.ts +40 -5
  473. package/src/plugins/builtin/holders/report-rows.ts +240 -0
  474. package/src/plugins/builtin/insider/client.ts +35 -20
  475. package/src/plugins/builtin/insider/headless.ts +41 -11
  476. package/src/plugins/builtin/insider/index.tsx +26 -9
  477. package/src/plugins/builtin/insider/insider-data.ts +10 -0
  478. package/src/plugins/builtin/insider/issuer.ts +49 -0
  479. package/src/plugins/builtin/insider/model.ts +3 -0
  480. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -1
  481. package/src/plugins/builtin/iv-history/client.ts +20 -6
  482. package/src/plugins/builtin/iv-history/format.ts +9 -0
  483. package/src/plugins/builtin/iv-history/headless.ts +56 -21
  484. package/src/plugins/builtin/iv-history/index.tsx +2 -0
  485. package/src/plugins/builtin/iv-history/model.ts +143 -8
  486. package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
  487. package/src/plugins/builtin/iv-history/series.ts +29 -0
  488. package/src/plugins/builtin/jobs/headless.ts +26 -3
  489. package/src/plugins/builtin/jobs/index.tsx +3 -1
  490. package/src/plugins/builtin/jobs/series.ts +19 -0
  491. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  492. package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
  493. package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
  494. package/src/plugins/builtin/macro-day/client.ts +44 -3
  495. package/src/plugins/builtin/macro-day/headless.ts +13 -10
  496. package/src/plugins/builtin/macro-day/model.ts +56 -10
  497. package/src/plugins/builtin/macro-day/pane.tsx +30 -20
  498. package/src/plugins/builtin/market-heatmap/headless.ts +20 -5
  499. package/src/plugins/builtin/market-heatmap/index.tsx +6 -0
  500. package/src/plugins/builtin/market-heatmap/model.ts +19 -11
  501. package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
  502. package/src/plugins/builtin/market-movers/client.ts +7 -5
  503. package/src/plugins/builtin/market-movers/index.tsx +3 -2
  504. package/src/plugins/builtin/market-movers/model.ts +30 -0
  505. package/src/plugins/builtin/market-movers/screener.ts +31 -0
  506. package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
  507. package/src/plugins/builtin/market-valuation/index.tsx +3 -3
  508. package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
  509. package/src/plugins/builtin/members/headless.ts +8 -3
  510. package/src/plugins/builtin/members/model.ts +31 -3
  511. package/src/plugins/builtin/members/pane.tsx +6 -8
  512. package/src/plugins/builtin/mna/distress/headless.ts +4 -4
  513. package/src/plugins/builtin/mna/headless.ts +18 -6
  514. package/src/plugins/builtin/money-markets/headless.ts +5 -4
  515. package/src/plugins/builtin/money-markets/index.tsx +2 -0
  516. package/src/plugins/builtin/money-markets/series.ts +41 -0
  517. package/src/plugins/builtin/news/index.tsx +0 -2
  518. package/src/plugins/builtin/news/wire/news/detail-view.tsx +53 -104
  519. package/src/plugins/builtin/news/wire/news/footer.ts +2 -25
  520. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +0 -2
  521. package/src/plugins/builtin/notes/cli.ts +12 -7
  522. package/src/plugins/builtin/notes/index.tsx +1 -0
  523. package/src/plugins/builtin/options/index.tsx +3 -0
  524. package/src/plugins/builtin/options/view.tsx +1 -1
  525. package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
  526. package/src/plugins/builtin/options-positioning/client.ts +7 -3
  527. package/src/plugins/builtin/options-positioning/headless.ts +4 -3
  528. package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
  529. package/src/plugins/builtin/options-scenario/client.ts +2 -1
  530. package/src/plugins/builtin/options-scenario/headless.ts +1 -0
  531. package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
  532. package/src/plugins/builtin/perps/client.ts +90 -16
  533. package/src/plugins/builtin/perps/equity-row.tsx +52 -17
  534. package/src/plugins/builtin/perps/freshness.ts +66 -0
  535. package/src/plugins/builtin/perps/headless.ts +47 -32
  536. package/src/plugins/builtin/perps/index.tsx +23 -4
  537. package/src/plugins/builtin/perps/list-perpetuals.ts +118 -0
  538. package/src/plugins/builtin/perps/model.ts +20 -5
  539. package/src/plugins/builtin/perps/open-market.ts +15 -0
  540. package/src/plugins/builtin/perps/pane.tsx +76 -20
  541. package/src/plugins/builtin/perps/series.ts +76 -0
  542. package/src/plugins/builtin/plugin-marketplace/index.tsx +1 -1
  543. package/src/plugins/builtin/plugin-marketplace/pane.tsx +73 -67
  544. package/src/plugins/builtin/plugin-module.ts +3 -0
  545. package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
  546. package/src/plugins/builtin/portfolio-list/allocation.ts +5 -4
  547. package/src/plugins/builtin/portfolio-list/cell-version.ts +8 -1
  548. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +161 -0
  549. package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
  550. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +384 -74
  551. package/src/plugins/builtin/portfolio-list/cli/render.ts +437 -71
  552. package/src/plugins/builtin/portfolio-list/cli/shared.ts +21 -4
  553. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +121 -47
  554. package/src/plugins/builtin/portfolio-list/column-values.ts +104 -26
  555. package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
  556. package/src/plugins/builtin/portfolio-list/headless.ts +84 -18
  557. package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
  558. package/src/plugins/builtin/portfolio-list/index.tsx +5 -1
  559. package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
  560. package/src/plugins/builtin/portfolio-list/mutations.ts +2 -9
  561. package/src/plugins/builtin/portfolio-list/pane/data.ts +5 -1
  562. package/src/plugins/builtin/portfolio-list/pane/index.tsx +52 -9
  563. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +33 -11
  564. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +21 -1
  565. package/src/plugins/builtin/portfolio-list/settings.ts +17 -0
  566. package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
  567. package/src/plugins/builtin/portfolio-list/summary/totals.ts +9 -4
  568. package/src/plugins/builtin/portfolio-list/table.tsx +37 -7
  569. package/src/plugins/builtin/power/client.ts +16 -0
  570. package/src/plugins/builtin/power/evidence.ts +63 -10
  571. package/src/plugins/builtin/power/headless.ts +77 -9
  572. package/src/plugins/builtin/power/index.tsx +28 -3
  573. package/src/plugins/builtin/power/model.ts +73 -2
  574. package/src/plugins/builtin/power/pane.tsx +37 -9
  575. package/src/plugins/builtin/power/prices-client.ts +139 -0
  576. package/src/plugins/builtin/power/prices-headless.ts +137 -0
  577. package/src/plugins/builtin/power/prices-model.ts +103 -0
  578. package/src/plugins/builtin/power/prices.tsx +179 -0
  579. package/src/plugins/builtin/rate-path/headless.ts +46 -18
  580. package/src/plugins/builtin/realized-vol/headless.ts +5 -0
  581. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  582. package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
  583. package/src/plugins/builtin/research/event-model.ts +68 -24
  584. package/src/plugins/builtin/research/events-headless.ts +2 -1
  585. package/src/plugins/builtin/research/headless.ts +2 -1
  586. package/src/plugins/builtin/research/index.tsx +2 -0
  587. package/src/plugins/builtin/research-search/article.ts +65 -0
  588. package/src/plugins/builtin/research-search/document-view.tsx +33 -215
  589. package/src/plugins/builtin/research-search/model.ts +0 -9
  590. package/src/plugins/builtin/research-search/pane.tsx +41 -30
  591. package/src/plugins/builtin/research-search/snippet.ts +0 -29
  592. package/src/plugins/builtin/revenue-breakdown/headless.ts +18 -6
  593. package/src/plugins/builtin/seasonality/client.ts +15 -7
  594. package/src/plugins/builtin/seasonality/headless.ts +36 -12
  595. package/src/plugins/builtin/seasonality/index.tsx +2 -2
  596. package/src/plugins/builtin/seasonality/model.ts +119 -0
  597. package/src/plugins/builtin/seasonality/pane.tsx +108 -37
  598. package/src/plugins/builtin/sec/headless.ts +25 -3
  599. package/src/plugins/builtin/sec/index.tsx +2 -1
  600. package/src/plugins/builtin/series-inventory.ts +93 -0
  601. package/src/plugins/builtin/shared/cloud-denied.ts +15 -0
  602. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
  603. package/src/plugins/builtin/shared/command-tab.ts +30 -12
  604. package/src/plugins/builtin/shared/earnings-report.ts +55 -0
  605. package/src/plugins/builtin/shared/fred-points.ts +20 -0
  606. package/src/plugins/builtin/shared/free-preview.ts +47 -0
  607. package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
  608. package/src/plugins/builtin/shared/open-description.ts +29 -0
  609. package/src/plugins/builtin/shared/report-freshness.ts +12 -1
  610. package/src/plugins/builtin/shared/series-warning.ts +5 -0
  611. package/src/plugins/builtin/shared/volatility/options.ts +4 -1
  612. package/src/plugins/builtin/short-interest/headless.ts +1 -0
  613. package/src/plugins/builtin/short-interest/index.tsx +2 -0
  614. package/src/plugins/builtin/short-interest/series.ts +36 -0
  615. package/src/plugins/builtin/short-interest/watch-headless.ts +3 -2
  616. package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
  617. package/src/plugins/builtin/short-volume/headless.ts +2 -1
  618. package/src/plugins/builtin/short-volume/index.tsx +2 -0
  619. package/src/plugins/builtin/short-volume/series.ts +23 -0
  620. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  621. package/src/plugins/builtin/social-mentions/index.tsx +2 -0
  622. package/src/plugins/builtin/social-mentions/series.ts +23 -0
  623. package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
  624. package/src/plugins/builtin/supply-chain/headless.ts +43 -11
  625. package/src/plugins/builtin/supply-chain/model.ts +13 -0
  626. package/src/plugins/builtin/supply-chain/pane.tsx +8 -6
  627. package/src/plugins/builtin/themes/headless.ts +74 -10
  628. package/src/plugins/builtin/themes/model.ts +71 -4
  629. package/src/plugins/builtin/themes/pane.tsx +22 -5
  630. package/src/plugins/builtin/thirteenf/api.ts +1 -0
  631. package/src/plugins/builtin/thirteenf/data.ts +6 -4
  632. package/src/plugins/builtin/thirteenf/headless.ts +217 -36
  633. package/src/plugins/builtin/thirteenf/model.ts +39 -1
  634. package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
  635. package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
  636. package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
  637. package/src/plugins/builtin/thirteenf/types.ts +2 -0
  638. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +114 -0
  639. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +57 -2
  640. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +19 -55
  641. package/src/plugins/builtin/ticker-detail/headless-schema.ts +30 -0
  642. package/src/plugins/builtin/ticker-detail/headless.ts +86 -0
  643. package/src/plugins/builtin/ticker-detail/index.tsx +26 -11
  644. package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
  645. package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
  646. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
  647. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
  648. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +49 -5
  649. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
  650. package/src/plugins/builtin/time-sales/headless.ts +2 -1
  651. package/src/plugins/builtin/time-sales/index.tsx +2 -1
  652. package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
  653. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  654. package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
  655. package/src/plugins/builtin/vol-surface/evidence.ts +7 -1
  656. package/src/plugins/builtin/vol-surface/headless.ts +51 -17
  657. package/src/plugins/builtin/vol-surface/heat-grid.tsx +113 -0
  658. package/src/plugins/builtin/vol-surface/model.ts +39 -6
  659. package/src/plugins/builtin/vol-surface/pane.tsx +14 -13
  660. package/src/plugins/builtin/vol-surface/stored.ts +63 -3
  661. package/src/plugins/builtin/vol-surface/surface.tsx +30 -2
  662. package/src/plugins/builtin/volatility/index.tsx +2 -0
  663. package/src/plugins/builtin/volatility/series.ts +84 -0
  664. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +6 -5
  665. package/src/plugins/builtin/world-venue-map/basemap.ts +6 -3
  666. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +2 -1
  667. package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
  668. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +6 -6
  669. package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
  670. package/src/plugins/builtin/world-venue-map/index.ts +2 -1
  671. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +24 -13
  672. package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
  673. package/src/plugins/builtin/world-venue-map/map.tsx +144 -13
  674. package/src/plugins/builtin/world-venue-map/model.ts +56 -19
  675. package/src/plugins/builtin/world-venue-map/pane.tsx +5 -2
  676. package/src/plugins/builtin/yield-curve/chart.ts +2 -2
  677. package/src/plugins/builtin/yield-curve/curves.ts +21 -8
  678. package/src/plugins/builtin/yield-curve/headless.ts +71 -18
  679. package/src/plugins/builtin/yield-curve/index.tsx +4 -2
  680. package/src/plugins/builtin/yield-curve/series.ts +86 -0
  681. package/src/plugins/catalog-browser.ts +2 -1
  682. package/src/plugins/installer.ts +2 -2
  683. package/src/plugins/loader.ts +15 -0
  684. package/src/plugins/manager-node.ts +1 -1
  685. package/src/plugins/{builtin/plugin-marketplace → marketplace}/feed.ts +2 -2
  686. package/src/plugins/{builtin/plugin-marketplace → marketplace}/model.ts +6 -6
  687. package/src/plugins/{builtin/plugin-marketplace → marketplace}/store.ts +1 -1
  688. package/src/plugins/registry/host-actions.ts +11 -0
  689. package/src/plugins/registry/index.ts +89 -4
  690. package/src/portfolio/collection-name.ts +31 -0
  691. package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
  692. package/src/portfolio/position-value.ts +40 -0
  693. package/src/portfolio/valuation-basis.ts +79 -0
  694. package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
  695. package/src/public/time-series.ts +17 -0
  696. package/src/react/auto-refresh.ts +13 -3
  697. package/src/react/input.ts +33 -1
  698. package/src/react/key-repeat.ts +86 -0
  699. package/src/remote/app-host.tsx +20 -4
  700. package/src/remote/confirm-change.tsx +85 -0
  701. package/src/remote/controller-utils.ts +8 -1
  702. package/src/remote/controller.ts +144 -7
  703. package/src/remote/pane-screenshot.ts +47 -0
  704. package/src/remote/schema.ts +65 -2
  705. package/src/remote/types.ts +30 -0
  706. package/src/remote/watchlist.ts +306 -0
  707. package/src/renderers/browser/main.tsx +4 -3
  708. package/src/renderers/browser/research-entry.ts +4 -2
  709. package/src/renderers/cloudflare/worker.ts +1 -1
  710. package/src/renderers/dom/data-table/frozen-column.ts +3 -1
  711. package/src/renderers/dom/data-table/index.tsx +33 -6
  712. package/src/renderers/dom/data-table/row.tsx +30 -1
  713. package/src/renderers/dom/desktop/control-styles.ts +63 -1
  714. package/src/renderers/dom/desktop/controls.tsx +30 -23
  715. package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
  716. package/src/renderers/dom/desktop/icons.tsx +16 -0
  717. package/src/renderers/dom/dom-ui-host.tsx +3 -0
  718. package/src/renderers/dom/host/block-glyph-canvas.tsx +6 -3
  719. package/src/renderers/dom/host/box.tsx +8 -0
  720. package/src/renderers/dom/host/chart-surface.tsx +33 -2
  721. package/src/renderers/dom/host/live-frame.ts +6 -2
  722. package/src/renderers/dom/host/overflow-fade.ts +14 -1
  723. package/src/renderers/dom/host/scroll-box.tsx +32 -0
  724. package/src/renderers/dom/host/size-observer.ts +41 -0
  725. package/src/renderers/dom/host/sticky-scroll.ts +128 -0
  726. package/src/renderers/dom/host/style.ts +1 -1
  727. package/src/renderers/dom/host/surface-3d.tsx +19 -7
  728. package/src/renderers/dom/host/surface-fit.ts +406 -0
  729. package/src/renderers/dom/host/text.tsx +1 -1
  730. package/src/renderers/dom/input-host.tsx +1 -0
  731. package/src/renderers/dom/styles.css +102 -5
  732. package/src/renderers/electrobun/bun/application-menu/click.ts +18 -0
  733. package/src/renderers/electrobun/bun/index.ts +3 -2
  734. package/src/renderers/electrobun/shared/protocol.ts +1 -1
  735. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +29 -2
  736. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +63 -20
  737. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
  738. package/src/renderers/electrobun/view/main.tsx +5 -3
  739. package/src/renderers/electrobun/view/remote/asset-data-client.ts +2 -0
  740. package/src/renderers/opentui/chart-surface.tsx +1 -0
  741. package/src/renderers/opentui/frame-capture.ts +33 -0
  742. package/src/renderers/opentui/host.tsx +10 -13
  743. package/src/renderers/opentui/pane-image/glyphs-license.txt +107 -0
  744. package/src/renderers/opentui/pane-image/glyphs.json +1 -0
  745. package/src/renderers/opentui/pane-image/index.ts +107 -0
  746. package/src/renderers/opentui/pane-image/raster.ts +428 -0
  747. package/src/renderers/opentui/start.tsx +3 -3
  748. package/src/renderers/opentui/test-utils.tsx +3 -0
  749. package/src/renderers/opentui/ui-host.tsx +29 -1
  750. package/src/series/capability.ts +75 -0
  751. package/src/series/csv.ts +160 -0
  752. package/src/series/files.ts +107 -0
  753. package/src/series/registry.ts +198 -0
  754. package/src/series/resolve.ts +108 -0
  755. package/src/series/standalone.ts +48 -0
  756. package/src/series/types.ts +153 -0
  757. package/src/series/validate.ts +171 -0
  758. package/src/shares/chat.ts +34 -0
  759. package/src/shares/dialog.tsx +295 -0
  760. package/src/shares/flow.tsx +56 -0
  761. package/src/shares/live.ts +14 -22
  762. package/src/shares/pane-image.ts +152 -0
  763. package/src/shares/personal-pane.ts +64 -0
  764. package/src/shares/portable-layout.ts +4 -2
  765. package/src/shares/prepare.ts +20 -0
  766. package/src/sources/gloomberb-cloud/index.ts +109 -10
  767. package/src/sources/gloomberb-cloud/normalizers.ts +3 -0
  768. package/src/sources/gloomberb-cloud/perpetuals.ts +249 -0
  769. package/src/sources/history-result.ts +1 -0
  770. package/src/sources/history-retention.ts +11 -0
  771. package/src/sources/listing-symbols.ts +4 -1
  772. package/src/sources/listing-target.ts +4 -0
  773. package/src/sources/provider-router/batches.ts +4 -1
  774. package/src/sources/provider-router/cache.ts +4 -2
  775. package/src/sources/provider-router/cached-routes.ts +1 -1
  776. package/src/sources/provider-router/financial-routes.ts +1 -0
  777. package/src/sources/provider-router/financials.ts +13 -0
  778. package/src/sources/provider-router/history.ts +43 -25
  779. package/src/sources/provider-router/index.ts +14 -0
  780. package/src/sources/provider-router/search.ts +1 -0
  781. package/src/state/hooks/inline-tickers.ts +49 -11
  782. package/src/sync/core-contributors.ts +1 -0
  783. package/src/test-support/shell-pane-keys.tsx +1 -0
  784. package/src/theme/theme-context.tsx +15 -1
  785. package/src/tickers/company-name.ts +75 -0
  786. package/src/tickers/instrument-kind.ts +34 -1
  787. package/src/tickers/listing-name.ts +34 -0
  788. package/src/tickers/listing-venues.ts +132 -0
  789. package/src/tickers/open-target.ts +13 -4
  790. package/src/tickers/perpetual-listing.ts +151 -0
  791. package/src/tickers/prediction-listing.ts +26 -0
  792. package/src/tickers/search/index.ts +72 -11
  793. package/src/tickers/search/ranking.ts +98 -20
  794. package/src/tickers/search/types.ts +7 -1
  795. package/src/tickers/ticker-changes.ts +213 -0
  796. package/src/tickers/us-listing.ts +34 -0
  797. package/src/time-series/economic.ts +2 -6
  798. package/src/time-series/hooks.ts +9 -0
  799. package/src/time-series/ice-credit.ts +39 -0
  800. package/src/time-series/resolve.ts +211 -29
  801. package/src/time-series/studies.ts +1 -0
  802. package/src/time-series/types.ts +12 -2
  803. package/src/time-series/use-chart-resolution.ts +32 -0
  804. package/src/types/config.ts +10 -2
  805. package/src/types/data-provider.ts +11 -0
  806. package/src/types/financials.ts +62 -1
  807. package/src/types/headless.ts +113 -0
  808. package/src/types/instrument.ts +4 -0
  809. package/src/types/plugin.ts +52 -2
  810. package/src/types/price-history.ts +7 -0
  811. package/src/types/ticker-changes.ts +26 -0
  812. package/src/types/ticker.ts +14 -0
  813. package/src/ui/context-menu.tsx +13 -1
  814. package/src/ui/host.tsx +57 -3
  815. package/src/ui/live-resize.ts +91 -0
  816. package/src/ui/live-stretch.ts +153 -0
  817. package/src/ui/measured-cells.ts +58 -0
  818. package/src/ui/sticky-scroll.ts +7 -0
  819. package/src/utils/basis-points.ts +17 -0
  820. package/src/utils/central-banks.ts +76 -0
  821. package/src/utils/cli-output.ts +131 -15
  822. package/src/utils/concurrency.ts +25 -0
  823. package/src/utils/crypto-pair.ts +20 -0
  824. package/src/utils/currency-pair.ts +19 -0
  825. package/src/utils/currency-units.ts +11 -0
  826. package/src/utils/datetime-format.ts +27 -6
  827. package/src/utils/decimal-comma.ts +19 -0
  828. package/src/utils/dom-screenshot-css.ts +265 -0
  829. package/src/utils/dom-screenshot-rasters.ts +276 -0
  830. package/src/utils/dom-screenshot.ts +439 -52
  831. package/src/utils/double-escape-close.ts +15 -1
  832. package/src/utils/exchange-rate-snapshot.ts +6 -2
  833. package/src/utils/exchanges.ts +143 -1
  834. package/src/utils/format.ts +35 -5
  835. package/src/utils/forward-dividend.ts +14 -0
  836. package/src/utils/fundamentals.ts +66 -0
  837. package/src/utils/futures-generic.ts +6 -12
  838. package/src/utils/fx-market-hours.ts +15 -5
  839. package/src/utils/fx-status.ts +6 -1
  840. package/src/utils/inline-content-tokenizer.ts +22 -3
  841. package/src/utils/market-capitalization.ts +8 -0
  842. package/src/utils/one-shot.ts +73 -0
  843. package/src/utils/options.ts +37 -8
  844. package/src/utils/price-history.ts +99 -42
  845. package/src/utils/sec-eps-basis.ts +24 -11
  846. package/src/utils/sec.ts +61 -7
  847. package/src/utils/utc-time.ts +63 -4
  848. package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
  849. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/activation.ts +0 -0
  850. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/ids.ts +0 -0
  851. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/packs.ts +0 -0
  852. /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
@@ -10,7 +10,7 @@ import type {
10
10
  Quote,
11
11
  TickerFinancials,
12
12
  } from "../../types/financials";
13
- import { currencyMinorDigits, formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions } from "../../market-data/market/format";
13
+ import { currencyPriceMinDigits, formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions } from "../../market-data/market/format";
14
14
  import {
15
15
  EXTENDED_SESSION_LABELS,
16
16
  getExtendedSessionDisplay,
@@ -21,7 +21,7 @@ import {
21
21
  } from "../../market-data/market/status";
22
22
  import { formatCompact, formatDistributionAmount, formatPercent, formatPercentRaw } from "../../utils/format";
23
23
  import { withCliServices, withMarketData } from "../context";
24
- import { isoDate, parsePositiveInt, rejectExtraArgs, requireOneArg, takeFlag, takeOption } from "./command-utils";
24
+ import { isoDate, parsePositiveInt, requireOneArg, takeFlag, takeOption } from "./command-utils";
25
25
  import type { BuiltinCliCommandDef } from "../command-options";
26
26
  import { CLI_COMMAND_GROUPS, TABLE_SECTION_OPTION } from "../help";
27
27
  import {
@@ -30,7 +30,7 @@ import {
30
30
  formatFractionPercentCell,
31
31
  formatPriceRange,
32
32
  } from "../helpers";
33
- import { cliStyles, renderStats } from "../../utils/cli-output";
33
+ import { cliStyles, colorBySign, renderSection, renderStats } from "../../utils/cli-output";
34
34
  import { formatPerShareNumber } from "../../utils/reported-money";
35
35
  import {
36
36
  analystTargetCurrency,
@@ -43,7 +43,9 @@ import {
43
43
  import { getPublishedUsEquityCalendarYears, getPublishedUsEquitySession } from "../../market-data/published-us-sessions";
44
44
  import { isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
45
45
  import { isOptionsUnavailableError, listedOptionsAlternative, optionsUnavailableTitle } from "../../market-data/options-alternatives";
46
- import { fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
46
+ import { cliFuturesContract, futuresContractMetrics, fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
47
+ import type { FuturesContractTermsPayload } from "../../api-client/futures-curve";
48
+ import { futuresSettleText, isFuturesInstrument } from "../../market-data/futures-contract";
47
49
  import {
48
50
  chainHasExpiry,
49
51
  chainWithModelFigures,
@@ -67,20 +69,27 @@ import {
67
69
  type CliReportTables,
68
70
  } from "../report-tables";
69
71
  import {
72
+ historyBarStatusNote,
70
73
  historyFacts,
71
74
  historyFlagNote,
75
+ historyGapNote,
72
76
  historyAllRangeYears,
73
77
  historyIntervalsByRange,
74
78
  historyNotes,
75
79
  historyPriceDecimals,
76
80
  historyRows,
77
81
  historyUnit,
82
+ historyVolume,
83
+ historyVolumeText,
78
84
  } from "../history-rows";
79
85
  import { CRYPTO_BOARD_HINT, quoteNotes } from "./crypto-hints";
86
+ import { bareFuturesCode } from "../../market-data/futures-board";
87
+ import { newsEmptyMessage } from "./news-hints";
88
+ import { newsFallbackText, newsSearchHeading, newsSearchText, newsSearchTextProblem, searchNewsStories } from "./news-search";
80
89
  import { formatUtcTime } from "../../utils/utc-time";
81
90
  import { isFiniteNumber } from "../../utils/guards";
82
91
  import { exportedFundamentals } from "../../utils/price-earnings";
83
- import { fundamentalsFreshness, quotesFreshness, rowsFreshness } from "../freshness";
92
+ import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness } from "../freshness";
84
93
  import {
85
94
  barHistoryFreshness,
86
95
  barResolutionFromDates,
@@ -91,33 +100,50 @@ import {
91
100
  } from "../../plugins/builtin/shared/report-freshness";
92
101
  import {
93
102
  bareListingNote,
103
+ tradingBoardNote,
94
104
  EXCHANGE_OPTION,
105
+ futuresTryLine,
95
106
  isNoProviderError,
96
107
  listingHeading,
97
108
  listingIdentity,
98
109
  listingTitle,
99
110
  listingVenues,
111
+ listingChoiceNotes,
112
+ listingReadNotes,
113
+ loadBareVenues,
114
+ type ListingVenue,
100
115
  loadForListing,
101
116
  loadListingQuote,
117
+ listingFateLoader,
118
+ listingFateNotes,
119
+ noLongerTradingNote,
102
120
  notTradedMessage,
103
121
  quoteBareListing,
104
122
  requireCliListing,
123
+ tradingListing,
105
124
  type CliListing,
106
125
  type ListingDataRequest,
107
126
  type ListingIdentity,
108
127
  } from "../listing-arg";
109
- import { indexRootAlsoLine, indexRootFor, indexRootTryLine } from "../index-roots";
128
+ import { indexRootAlsoLine, indexRootFor, indexRootTryLine, isYieldIndexSymbol } from "../index-roots";
129
+ import { earningsCalendarFailure, earningsChoiceNote, earningsDateLabel, isListingAbroad, noListingCalendarNote } from "../earnings-calendar";
130
+ import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
110
131
  import { providerMissReason } from "../../sources/provider-errors";
111
132
  import { isNotATickerMessage } from "../not-a-ticker";
133
+ import { splitSymbolArgs } from "../symbol-args";
134
+ import { cliPerpetualContract, perpetualContractStats, type PerpetualContractPayload } from "../perpetual-contract";
135
+ import { subMinuteDataMessage } from "../known-gaps";
112
136
  import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
113
137
  import { isCryptoPairSymbol } from "../../utils/crypto-pair";
114
138
  import { isUsListingExchange } from "../../utils/exchanges";
115
- import { nonUsSecListingVenue } from "../../utils/sec";
139
+ import { describeTickerChange, startTickerChangeLookup, tickerChangeLine, type TickerChange } from "../../tickers/ticker-changes";
140
+ import { noSecReportsNotice, nonUsSecListingVenue } from "../../utils/sec";
116
141
  import type { MarketContext } from "../types";
117
142
  import { windowRows } from "../row-window";
118
143
  import { crossRate, describeFxRate, isUsableRate, parseFxRequest } from "../fx-pair";
119
144
  import {
120
145
  holderListFacts,
146
+ holderListPeriod,
121
147
  holderShareBasisMarker,
122
148
  holderShareBasisNote,
123
149
  holderValueBasis,
@@ -125,15 +151,28 @@ import {
125
151
  nonUsHolderCaveat,
126
152
  sharedReportDate,
127
153
  } from "../../plugins/builtin/holders/report-header";
128
- import { formatHolderOwnershipPercent, holderValueCurrency } from "../../plugins/builtin/holders/format";
154
+ import { formatPercentHeld, holderStakeMarketCap, holderValueCurrency } from "../../plugins/builtin/holders/format";
155
+ import {
156
+ holderReportColumns,
157
+ holderReportDates,
158
+ holderReportNotices,
159
+ holderReportRows,
160
+ holderReportUnits,
161
+ unvaluedHoldersNote,
162
+ } from "../../plugins/builtin/holders/report-rows";
129
163
  import { fetchBeneficialOwners } from "../../plugins/builtin/holders/beneficial-client";
164
+ import { centralBankEventsMessage } from "../central-bank-events";
165
+ import { classSharesToday } from "../../plugins/builtin/holders/client";
130
166
  import { filingFormMatches, SEC_FILING_FETCH_LIMIT } from "../../plugins/builtin/sec/forms";
167
+ import { loadParsedInsiderFilings } from "../../plugins/builtin/insider/client";
168
+ import { insiderEmptyNotice } from "../../plugins/builtin/insider/issuer";
131
169
  import {
132
170
  BENEFICIAL_REPORT_COLUMNS,
133
171
  beneficialCoverageNotices,
134
172
  beneficialListComplete,
135
173
  beneficialListFacts,
136
174
  beneficialListUnreadable,
175
+ beneficialReportLegend,
137
176
  beneficialRouteForm,
138
177
  buildBeneficialReportRows,
139
178
  HOLDER_FORMS,
@@ -144,7 +183,17 @@ import {
144
183
  const VALID_RANGES = new Set<TimeRange>(TIME_RANGES);
145
184
  const VALID_NEWS_FEEDS = new Set<NewsFeed>(["latest", "top", "breaking", "ticker", "sector", "topic"]);
146
185
 
147
- type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & { error: string | null };
186
+ type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & {
187
+ error: string | null;
188
+ /** The listing the row is, named from its saved ticker when the quote names nothing. */
189
+ listing?: { name: string | null };
190
+ /** A futures row's contract, terms and latest confirmed settlement, as `ticker` reads them. */
191
+ futuresContract?: FuturesContractTermsPayload;
192
+ /** A perpetual row's market, funding per eight hours and open interest. */
193
+ perpetualContract?: PerpetualContractPayload;
194
+ /** The symbol changed: its issuer moved to another symbol, or its holder had another before. */
195
+ tickerChange?: TickerChange;
196
+ };
148
197
  type FinancialsCliData = TickerFinancials & {
149
198
  symbol: string;
150
199
  exchange: string;
@@ -161,11 +210,9 @@ function parseNewsFeed(value: string | undefined): NewsQuery["feed"] | undefined
161
210
  return value && VALID_NEWS_FEEDS.has(value as NewsFeed) ? value as NewsQuery["feed"] : undefined;
162
211
  }
163
212
 
213
+ /** The symbols of a command's arguments, one each however they were separated: spaces, commas or pasted lines. */
164
214
  function normalizeSymbols(args: string[]): string[] {
165
- return args
166
- .flatMap((arg) => arg.split(","))
167
- .map((symbol) => symbol.trim().toUpperCase())
168
- .filter(Boolean);
215
+ return splitSymbolArgs(args).map((symbol) => symbol.toUpperCase());
169
216
  }
170
217
 
171
218
  /** What JSON metadata says about the listing a command resolved. */
@@ -186,10 +233,15 @@ const QUOTE_LEAD_COLUMNS = [
186
233
  row.error && !value
187
234
  // A typo is not an outage: say what is wrong with the symbol itself.
188
235
  ? isNotATickerMessage(row.error) ? cliStyles.warning("not a ticker") : cliStyles.danger("unavailable")
189
- : String(value ?? "")
236
+ : yieldLevelCell(value, row)
190
237
  ),
191
238
  },
192
- { key: "changePercent", header: "Chg%", align: "right" as const, format: formatChangePercentCell },
239
+ {
240
+ key: "changePercent",
241
+ header: "Chg%",
242
+ align: "right" as const,
243
+ format: (value: unknown, row: QuoteRow) => row.yieldIndex ? yieldChangeCell(row) : formatChangePercentCell(value),
244
+ },
193
245
  // Text drops a column no row fills, so a table without an extended print stays as narrow as before.
194
246
  extendedColumn("PRE"),
195
247
  extendedColumn("POST"),
@@ -198,6 +250,17 @@ const QUOTE_LEAD_COLUMNS = [
198
250
 
199
251
  type QuoteRow = ReturnType<typeof quoteRows>[number];
200
252
 
253
+ /** A Cboe yield index level is a yield: `5.24%`, not a price. */
254
+ function yieldLevelCell(value: unknown, row: QuoteRow): string {
255
+ const text = String(value ?? "");
256
+ return row.yieldIndex && text ? `${text}%` : text;
257
+ }
258
+
259
+ /** A yield index moves in basis points: `+1.3bp`, from the real previous close, not a percent of a percent. */
260
+ function yieldChangeCell(row: QuoteRow): string {
261
+ return row.changeBasisPoints == null ? "" : colorBySign(formatBasisPoints(row.changeBasisPoints / 100, 1), row.changeBasisPoints);
262
+ }
263
+
201
264
  /** The pre-market or after-hours print and its move from the regular close, in its own column. */
202
265
  function extendedColumn(session: ExtendedSession) {
203
266
  return {
@@ -240,7 +303,7 @@ function quoteColumns() {
240
303
  function compareColumns() {
241
304
  return [
242
305
  ...QUOTE_LEAD_COLUMNS,
243
- { key: "previousClose", header: "Prev Close", align: "right" as const },
306
+ { key: "previousClose", header: "Prev Close", align: "right" as const, format: yieldLevelCell },
244
307
  // A narrow terminal drops these before it cuts the names short.
245
308
  { key: "dayRange", header: "Day Range", align: "right" as const, optional: true, dropPriority: 1 },
246
309
  { key: "volume", header: "Volume", align: "right" as const, format: formatCountCell, optional: true, dropPriority: 2 },
@@ -267,13 +330,14 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
267
330
  // pre-market or after-hours print is its own column, measured from that session's close.
268
331
  const display = getRegularSessionDisplay(quote);
269
332
  const extended = getExtendedSessionDisplay(quote);
333
+ const yieldIndex = isYieldIndexSymbol(result.target.symbol);
270
334
  // An index level is in points, not in the currency its members trade in.
271
335
  const indexPoints = quote?.instrumentType?.trim().toUpperCase() === "INDEX";
272
336
  // Pad to two decimals so a column lines up, but never past the currency's minor unit (JPY has none).
273
337
  // Points have no minor unit, so a yen-listed index still pads to two.
274
338
  const options = {
275
339
  ...quoteFormatOptions(quote),
276
- minimumFractionDigits: indexPoints ? 2 : Math.min(2, currencyMinorDigits(quote?.currency)),
340
+ minimumFractionDigits: indexPoints ? 2 : currencyPriceMinDigits(quote?.currency),
277
341
  };
278
342
  const price = (value: number | undefined) => (
279
343
  quote && value != null
@@ -282,13 +346,16 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
282
346
  );
283
347
  return {
284
348
  symbol: result.target.symbol,
285
- name: quote?.name ?? "",
349
+ name: quote?.name || result.listing?.name || "",
286
350
  price: price(display?.price),
287
351
  rawPrice: display?.price ?? null,
288
352
  priceBasis: quote?.priceBasis ?? null,
289
353
  instrumentType: quote?.instrumentType ?? null,
290
354
  change: display?.change ?? null,
291
355
  changePercent: roundedPercent(display?.changePercent),
356
+ // A Cboe yield index is read as a yield in percent and moves in basis points.
357
+ yieldIndex,
358
+ changeBasisPoints: yieldIndex && display?.change != null ? toBasisPoints(display.change, 1) : null,
292
359
  extendedSession: extended?.session ?? null,
293
360
  extendedPrice: price(extended?.price),
294
361
  rawExtendedPrice: extended?.price ?? null,
@@ -349,22 +416,6 @@ function filingRows(filings: SecFilingItem[]) {
349
416
  }));
350
417
  }
351
418
 
352
- function holderRows(data: HolderData, ownerTypes?: Set<string>) {
353
- const holders = ownerTypes
354
- ? data.holders.filter((holder) => ownerTypes.has(holder.ownerType))
355
- : data.holders;
356
- return holders.map((holder) => ({
357
- type: holder.ownerType,
358
- name: holder.name,
359
- reportDate: holder.reportDate ?? "",
360
- shares: holder.shares ?? null,
361
- value: holder.value ?? null,
362
- percentHeld: holder.percentHeld ?? null,
363
- changeShares: holder.changeShares ?? null,
364
- shareBasis: holder.shareBasis ?? null,
365
- }));
366
- }
367
-
368
419
  function analystSummary(data: AnalystResearchData): string {
369
420
  const target = data.priceTarget;
370
421
  const currency = analystTargetCurrency(data);
@@ -459,6 +510,8 @@ function earningsRows(events: EarningsEvent[]) {
459
510
  symbol: event.symbol,
460
511
  name: event.name,
461
512
  date: isoDate(event.earningsDate).slice(0, 10),
513
+ // Whether the company announced the date or it is projected from past reports.
514
+ status: earningsDateLabel(event),
462
515
  timing: event.timing,
463
516
  epsEstimate: event.epsEstimate,
464
517
  epsActual: event.epsActual,
@@ -467,6 +520,9 @@ function earningsRows(events: EarningsEvent[]) {
467
520
  }));
468
521
  }
469
522
 
523
+ /** Symbols at most whose venues a command looks up to name the company each bare one went to. */
524
+ const MAX_CHOICE_LOOKUPS = 10;
525
+
470
526
  async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], commandName: "quote" | "compare") {
471
527
  const args = [...rawArgs];
472
528
  const exchange = takeOption(args, "--exchange") ?? "";
@@ -479,38 +535,112 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
479
535
  symbol, exchange, market, ctx, { ownExchangeWins: symbols.length > 1 },
480
536
  )));
481
537
  const targets: QuoteSubscriptionTarget[] = listings.map((listing) => ({ symbol: listing.request.symbol, exchange: listing.request.exchange }));
538
+ // A bare symbol's venues load beside its quote, to name the company it was read as when several list it.
539
+ // Each costs a search, so a long list (a portfolio check, not a lookup) names none.
540
+ const venues = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
541
+ : listings.map((listing) => [listing, loadBareVenues(listing, market)] as const));
542
+ // So is whether a symbol changed (FB is now META), for the same lists.
543
+ const changes = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
544
+ : listings.map((listing) => [listing, startTickerChangeLookup(market.dataProvider, listing.symbol, listing.exchange)] as const));
482
545
  const results = await market.dataProvider.getQuotesBatch(targets, { forceRefresh: ctx.cliOptions.refresh });
483
546
  const listingOf = (result: QuoteBatchResult, index: number) => listings[targets.indexOf(result.target)] ?? listings[index]!;
547
+ const asked = results.map(listingOf);
484
548
  const request = { command: commandName, noun: "quote" };
485
- const answers = await Promise.all(results.map((result, index) => answerQuote(result, listingOf(result, index), market, request, ctx.cliOptions.refresh)));
549
+ const answers = await Promise.all(results.map((result, index) => {
550
+ const listing = asked[index]!;
551
+ return answerQuote(result, listing, market, request, ctx.cliOptions.refresh, venues.get(listing) ?? null);
552
+ }));
553
+ const now = Date.now();
554
+ const tickerChanges = await Promise.all(answers.map(async ({ result }, index) => {
555
+ const listing = asked[index]!;
556
+ // A bare symbol read on a listing abroad (SXR8 -> XETRA) is not the US symbol the changes are of.
557
+ if (result.quote && bareListingNote(listing, result.quote)) return null;
558
+ return describeTickerChange(listing.symbol, await changes.get(listing)?.settle() ?? null, tradingListing(result.quote, now));
559
+ }));
486
560
  const failed = answers[0]?.failure;
487
- if (listings.length === 1 && failed) ctx.fail(failed.message, failed.details);
561
+ if (listings.length === 1 && failed) {
562
+ ctx.fail(failed.message, [tickerChangeLine(tickerChanges[0]), failed.details].filter(Boolean).join("\n") || undefined);
563
+ }
564
+ // A futures quote adds its dated settlement and contract terms, as `ticker` prints them; `compare` stays a table.
565
+ const contracts = await Promise.all(answers.map(({ result }) => commandName === "quote" && result.quote
566
+ ? cliFuturesContract(result.quote.symbol || result.target.symbol, result.quote.instrumentType)
567
+ : null));
568
+ // A perpetual adds its funding per eight hours and open interest, the figures PERP lists beside its mark.
569
+ const perpetualContracts = await Promise.all(answers.map(({ result }) => commandName === "quote" && result.quote
570
+ ? cliPerpetualContract(result.quote)
571
+ : null));
488
572
  // Each row names its listing by key (SAN:EPA) and company, so the table needs no line above it.
489
- const data = answers.map(({ result, listing, failure }) => ({
573
+ const data = answers.map(({ result, listing, failure }, index) => ({
490
574
  target: result.target,
491
575
  listing: listingMetadata(listingIdentity(listing, result.quote)),
492
576
  // The figures the table shows, beside the quote as the source sent it.
493
577
  ...getQuoteSessionFields(result.quote),
494
578
  quote: result.quote,
579
+ ...contracts[index] ? { futuresContract: contracts[index] } : {},
580
+ ...perpetualContracts[index] ? { perpetualContract: perpetualContracts[index] } : {},
495
581
  error: failure?.oneLine ?? errorMessage(result.error),
582
+ ...yieldIndexFields(result),
583
+ ...tickerChanges[index] ? { tickerChange: tickerChanges[index] } : {},
496
584
  }));
497
585
  const notes = [
586
+ // FB now lists another security; the company that was FB trades as META. A symbol a
587
+ // company renamed into (META) is what was typed: its change is in the row, not here.
588
+ ...tickerChanges.flatMap((change) => tickerChangeLine(change) ?? []),
498
589
  // Which listing a bare symbol was read as, and the index a bare root is not, are in no row.
499
590
  ...answers.flatMap((answer) => answer.notes),
591
+ // A row that stopped trading long ago reads as a quote that is merely stale.
592
+ ...data.flatMap((row) => row.quote
593
+ ? noLongerTradingNote(`${row.target.symbol}${row.listing.name ? ` (${row.listing.name})` : ""}`, row.quote, now) ?? []
594
+ : []),
500
595
  // Text mode shows only "unavailable" in the cell; the JSON rows already carry each reason.
501
596
  ...ctx.cliOptions.format === "text" ? quoteNotes(data, { exchange }) : [],
502
597
  ];
598
+ const columns = commandName === "compare" ? compareColumns() : quoteColumns();
599
+ // Only yield indices: their change is in bp, so the text header drops the percent sign (CSV keeps the percent it holds).
600
+ const yieldsOnly = data.length > 0 && data.every((row) => isYieldIndexSymbol(row.target.symbol));
503
601
  ctx.printResult({
504
602
  data,
505
603
  warnings: notes.length > 0 ? notes : undefined,
506
604
  freshness: quotesFreshness(data.map((row) => row.quote)),
507
605
  }, {
508
606
  rows: quoteRows,
509
- columns: commandName === "compare" ? compareColumns() : quoteColumns(),
607
+ details: quoteFuturesContractText,
608
+ columns,
609
+ ...(yieldsOnly ? { textColumns: columns.map((column) => column.key === "changePercent" ? { ...column, header: "Chg" } : column) } : {}),
510
610
  });
511
611
  });
512
612
  }
513
613
 
614
+ /**
615
+ * Under the table, each futures row's Settle line and Contract block, worded
616
+ * as `ticker` words them. Several symbols name theirs in the block's title.
617
+ */
618
+ function quoteFuturesContractText(data: QuoteCliRecord[]): string {
619
+ return data.flatMap((row) => {
620
+ if (row.perpetualContract) {
621
+ const stats = perpetualContractStats(row.perpetualContract);
622
+ return [data.length > 1 ? `${renderSection(`${row.target.symbol} Contract`)}\n${renderStats(stats)}` : `${renderSection("Contract")}\n${renderStats(stats)}`];
623
+ }
624
+ const terms = row.futuresContract;
625
+ if (!terms || !row.quote) return [];
626
+ const settle: [string, string] = ["Settle", futuresSettleText(terms, row.quote)];
627
+ const contract = futuresContractMetrics(terms, row.quote.symbol || row.target.symbol);
628
+ if (data.length > 1) return [`${renderSection(`${row.target.symbol} Contract`)}\n${renderStats([settle, ...contract])}`];
629
+ return [[renderStats([settle]), ...contract.length ? ["", renderSection("Contract"), renderStats(contract)] : []].join("\n")];
630
+ }).join("\n\n");
631
+ }
632
+
633
+ /**
634
+ * What a Cboe yield index adds to its JSON row: the level is a yield in percent
635
+ * and its change since the previous close is in basis points (the quote's own
636
+ * `change` is the same move in percentage points).
637
+ */
638
+ function yieldIndexFields(result: QuoteBatchResult): { unit?: "percent"; changeBasisPoints?: number } {
639
+ if (!isYieldIndexSymbol(result.target.symbol)) return {};
640
+ const change = getRegularSessionDisplay(result.quote)?.change;
641
+ return { unit: "percent", ...(change == null ? {} : { changeBasisPoints: toBasisPoints(change, 1) }) };
642
+ }
643
+
514
644
  interface QuoteAnswer {
515
645
  result: QuoteBatchResult;
516
646
  /** The listing the row is: the one asked for, or the venue a bare symbol was quoted on. */
@@ -524,8 +654,10 @@ interface QuoteAnswer {
524
654
  * One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
525
655
  * is not listed on says so. A bare symbol the data service read on its home
526
656
  * listing abroad names that venue (SXR8 -> XETRA). One no source quotes is
527
- * quoted on its listings (`quoteBareListing`), else names them; a bare index
528
- * root (VIX) also names its index (^VIX). Venues are only looked up for a
657
+ * quoted on its listings (`quoteBareListing`), else names them, or asks for an
658
+ * exchange when several companies list it (2222); a bare index root (VIX) also
659
+ * names its index (^VIX), and a futures board code a security keeps (GC) its
660
+ * future (GC=F). Venues are only looked up for a
529
661
  * symbol with no quote, so a request that succeeds costs no search.
530
662
  */
531
663
  async function answerQuote(
@@ -534,12 +666,20 @@ async function answerQuote(
534
666
  market: MarketContext,
535
667
  request: ListingDataRequest,
536
668
  refresh: boolean,
669
+ venues: Promise<ListingVenue[]> | null,
537
670
  ): Promise<QuoteAnswer> {
538
671
  const root = listing.exchange ? null : indexRootFor(listing.symbol);
539
672
  // A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
540
673
  const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
541
674
  ? [indexRootAlsoLine(root, request.command)] : [];
542
- if (result.quote) return { result, listing, notes: [bareListingNote(listing, result.quote) ?? [], also(result.quote)].flat() };
675
+ if (result.quote) {
676
+ // "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
677
+ const choice = venues ? await listingChoiceNotes(listing, market, result.quote, venues) : listingReadNotes(listing);
678
+ const venue = choice.length > 0 ? null : bareListingNote(listing, result.quote);
679
+ // A row saved as 247540:KRX is quoted from KOSDAQ, the board it trades on.
680
+ const board = tradingBoardNote(listing, result.quote);
681
+ return { result, listing, notes: [...choice, venue ?? [], board ?? [], also(result.quote)].flat() };
682
+ }
543
683
  const notTraded = await notTradedMessage(listing, market);
544
684
  if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
545
685
  if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
@@ -551,14 +691,18 @@ async function answerQuote(
551
691
  notes: [other.note, ...also(other.quote)],
552
692
  };
553
693
  }
554
- const indexLine = root ? indexRootTryLine(root, request.command) : null;
555
- if (!other && !indexLine) return { result, listing, notes: [] };
694
+ // Other listings already name a futures board code's future (GC=F); with none, it is named here.
695
+ const tryLines = [
696
+ root ? indexRootTryLine(root, request.command) : null,
697
+ other || listing.exchange ? null : futuresTryLine(listing.symbol, request),
698
+ ].filter((line): line is string => !!line);
699
+ if (!other && tryLines.length === 0) return { result, listing, notes: [] };
556
700
  const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
557
- const details = [other?.details, indexLine].filter(Boolean).join("\n");
701
+ const details = [other?.details, ...tryLines].filter(Boolean).join("\n");
558
702
  const oneLine = [
559
703
  message,
560
- other ? `Other listings: ${other.others.join(", ")}.` : "",
561
- indexLine ?? "",
704
+ other ? `${other.heading}: ${other.listings.join(", ")}.` : "",
705
+ ...tryLines,
562
706
  ].filter(Boolean).join(" ");
563
707
  return { result, listing, failure: { message, details, oneLine }, notes: [] };
564
708
  }
@@ -573,6 +717,20 @@ const HISTORY_USAGE = "history <symbol> [--range <range>]";
573
717
  const HISTORY_ALL_RANGE = `ALL gives the full history the source has, up to ${historyAllRangeYears()} years`;
574
718
  /** Options people reach for to ask for dates, which history answers with a range. */
575
719
  const DATE_WINDOW_OPTIONS = new Set(["--from", "--to", "--start", "--end", "--since", "--until", "--date", "--start-date", "--end-date"]);
720
+ /** Options people reach for to pick a bar size, which history derives from the range. */
721
+ const BAR_SIZE_OPTIONS = new Set(["--interval", "--resolution", "--bar", "--bars", "--bar-size", "--granularity", "--timeframe", "--frequency", "--freq", "--tick", "--ticks"]);
722
+
723
+ /** The symbol `history` was given: the first word that is not an option or the value after one. */
724
+ function historySymbolArg(args: readonly string[]): string | undefined {
725
+ return args.find((arg, index) => !arg.startsWith("-")
726
+ && !(index > 0 && args[index - 1]!.startsWith("--") && !args[index - 1]!.includes("=")));
727
+ }
728
+
729
+ function historyOptionHint(flag: string, args: readonly string[]): string | null {
730
+ if (DATE_WINDOW_OPTIONS.has(flag)) return `history takes a --range instead of dates: ${TIME_RANGES.join(", ")}. ${HISTORY_ALL_RANGE}.`;
731
+ if (BAR_SIZE_OPTIONS.has(flag)) return `history serves each --range in one bar size. ${subMinuteDataMessage(historySymbolArg(args))}`;
732
+ return null;
733
+ }
576
734
 
577
735
  async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
578
736
  const args = [...rawArgs];
@@ -581,23 +739,31 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
581
739
  const raw = requireOneArg(args, HISTORY_USAGE, "symbol", ctx);
582
740
  await withMarketData(ctx, async (market) => {
583
741
  const listing = await requireCliListing(raw, requestedExchange, market, ctx);
742
+ const venues = loadBareVenues(listing, market);
584
743
  const { symbol, exchange } = listing.request;
585
744
  const context = { cacheMode: ctx.cliOptions.refresh ? "refresh" as const : "default" as const };
586
745
  const load = () => market.dataProvider.getPriceHistoryWithMetadata
587
746
  ? market.dataProvider.getPriceHistoryWithMetadata(symbol, exchange, range, context)
588
747
  : market.dataProvider.getPriceHistory(symbol, exchange, range, context).then((points) => ({ points, resolution: null }));
748
+ const quoted = loadListingQuote(market.dataProvider, listing);
749
+ const fate = listingFateLoader(listing, market, quoted);
589
750
  const [{ points, resolution }, quote] = await Promise.all([
590
751
  loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0, {
591
- command: range === "1Y" ? "history" : `history --range ${range}`, noun: "history",
752
+ command: range === "1Y" ? "history" : `history --range ${range}`, noun: "history", fate,
592
753
  })
593
754
  .catch((error) => failHistory(error, listing.key, ctx)),
594
- loadListingQuote(market.dataProvider, listing),
755
+ quoted,
595
756
  ]);
596
757
  const identity = listingIdentity(listing, quote);
597
758
  // Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
598
759
  const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
599
- const unit = historyUnit(listing.symbol, listed ?? quote);
600
- const data = historyRows(points, resolution, unit.currency);
760
+ const unit = historyUnit(listing.key, listed ?? quote);
761
+ const traded = {
762
+ listing: listing.key, exchange: quote?.listingExchangeName || exchange || undefined, instrumentType: quote?.instrumentType,
763
+ };
764
+ // A coin's volume is traded value in its quote currency: said in every row and on the status line.
765
+ const volume = historyVolume(unit, traded);
766
+ const data = historyRows(points, resolution, unit.currency, unit.unit, volume);
601
767
  const interval = data[0]?.interval ?? null;
602
768
  const decimals = historyPriceDecimals(data, listing.saved?.metadata.assetCategory);
603
769
  const price = (value: unknown) => typeof value === "number" ? value.toFixed(decimals) : "";
@@ -608,8 +774,17 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
608
774
  ...barHistoryFreshness(intraday ? null : barResolutionFromDates(data.map((row) => row.date))),
609
775
  observedKey: "date",
610
776
  oldest: null,
777
+ // Bars stop arriving on the listing's own trading days, not New York's.
778
+ cadenceExchange: quote?.listingExchangeName || exchange || undefined,
779
+ ...(volume ? { note: historyVolumeText(volume, interval) } : {}),
611
780
  });
612
- const notes = [...historyNotes(range, interval, data[0]?.date), historyFlagNote(data) ?? []].flat();
781
+ const gaps = historyGapNote(data, traded);
782
+ const notes = [
783
+ // FB's bars are the fund listed there now; META's from 2021 are from when it was FB.
784
+ ...listingFateNotes(await fate(), { served: data.length > 0, firstDate: data[0]?.date }),
785
+ ...await listingChoiceNotes(listing, market, quote, venues),
786
+ ...historyNotes(range, interval, data[0]?.date), gaps ?? [], historyFlagNote(data) ?? [], historyBarStatusNote(data) ?? [],
787
+ ].flat();
613
788
  const priceColumns: CliResultColumn[] = [
614
789
  intraday
615
790
  ? { key: "date", header: "Time", format: (value) => typeof value === "string" ? formatUtcTime(value) : "" }
@@ -624,6 +799,16 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
624
799
  const flagColumn: CliResultColumn = {
625
800
  key: "flag", header: "Flag", format: (value) => typeof value === "string" ? cliStyles.warning(value) : "",
626
801
  };
802
+ // Text also marks a bar still forming or rebuilt from hourly bars there; exports keep them as their own fields.
803
+ const flagTextColumn: CliResultColumn = {
804
+ ...flagColumn,
805
+ format: (value, row) => [
806
+ typeof value === "string" ? cliStyles.warning(value) : "",
807
+ row.partial ? cliStyles.muted("partial") : "",
808
+ row.filled ? cliStyles.muted("filled") : "",
809
+ ].filter(Boolean).join(" "),
810
+ };
811
+ const marked = data.some((row) => row.partial || row.filled);
627
812
  ctx.printResult({
628
813
  data,
629
814
  metadata: {
@@ -638,19 +823,23 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
638
823
  lastDate: data.at(-1)?.date ?? null,
639
824
  bars: data.length,
640
825
  asOf: freshness?.asOf ?? null,
826
+ ...(volume ? { volumeUnit: volume.unit, volumePeriod: interval === "1d" ? "UTC day" : interval } : {}),
641
827
  },
642
828
  ...(notes.length > 0 ? { warnings: notes } : {}),
643
829
  freshness,
644
830
  }, {
645
831
  heading: `${listingHeading(identity)}\n${cliStyles.muted(historyFacts(unit, data).join(" · "))}`,
646
832
  dateKey: "date",
647
- textColumns: [...priceColumns, flagColumn],
648
- // Every exported row says its currency and bar size, so it survives head and concatenation.
833
+ textColumns: [...priceColumns, flagTextColumn],
834
+ // Every exported row says its currency, what a price is in and its bar size, so it survives head and concatenation.
649
835
  columns: [
650
836
  ...priceColumns,
651
837
  { key: "currency", header: "Currency" },
838
+ { key: "unit", header: "Unit" },
652
839
  { key: "interval", header: "Interval" },
653
840
  flagColumn,
841
+ ...marked ? [{ key: "partial", header: "Partial" }, { key: "filled", header: "Filled" }] : [],
842
+ ...volume ? [{ key: "volumeUnit", header: "Volume unit" }, { key: "volumeNote", header: "Volume note" }] : [],
654
843
  ],
655
844
  });
656
845
  });
@@ -687,10 +876,12 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
687
876
  };
688
877
  await withMarketData(ctx, async (market) => {
689
878
  const listing = await requireCliListing(raw, exchangeOption, market, ctx);
879
+ const venues = loadBareVenues(listing, market);
690
880
  const financials = await loadForListing(listing, market, ctx, () => market.dataProvider.getTickerFinancials(
691
881
  listing.request.symbol, listing.request.exchange, { cacheMode: ctx.cliOptions.refresh ? "refresh" : "default" },
692
882
  ), undefined, { command: commandName, noun: commandName === "valuation" ? "valuation data" : commandName });
693
883
  const identity = listingIdentity(listing, financials.quote);
884
+ const choice = await listingChoiceNotes(listing, market, financials.quote, venues);
694
885
  const data: FinancialsCliData = {
695
886
  symbol: listing.key,
696
887
  exchange: identity.exchange,
@@ -703,6 +894,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
703
894
  // JSON reads a multiple over a loss as null with its reason, never as a number.
704
895
  data: { ...data, fundamentals: exportedFundamentals(data.fundamentals) },
705
896
  freshness,
897
+ ...(choice.length > 0 ? { warnings: choice } : {}),
706
898
  }, {
707
899
  text: () => renderFundamentalsReport(data, view),
708
900
  ...(tabular ? { tables: selectTables(fundamentalsReportTables(data, view, freshness)) } : {}),
@@ -731,6 +923,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
731
923
  profile: financials.profile,
732
924
  },
733
925
  freshness,
926
+ ...(choice.length > 0 ? { warnings: choice } : {}),
734
927
  }, {
735
928
  heading: listingHeading(identity),
736
929
  rows: () => rows,
@@ -740,16 +933,55 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
740
933
  });
741
934
  }
742
935
 
936
+ const NEWS_COLUMNS = [
937
+ // UTC with the zone named, as every CLI time prints, rather than the host zone unlabeled.
938
+ {
939
+ key: "publishedAt",
940
+ header: "Published",
941
+ format: (value: unknown) => typeof value === "string" ? formatUtcTime(value) : "",
942
+ },
943
+ { key: "source", header: "Source", maxWidth: 20 },
944
+ { key: "title", header: "Title" },
945
+ { key: "tickers", header: "Tickers", maxWidth: 16 },
946
+ { key: "url", header: "URL", optional: true },
947
+ ];
948
+
949
+ /** Stories matching text, printed as a symbol's news is, under a line saying what they match. */
950
+ async function printNewsSearch(
951
+ text: string,
952
+ articles: NewsArticle[],
953
+ { reason, empty }: { reason?: string; empty?: string },
954
+ ctx: Parameters<CliCommandDef["execute"]>[1],
955
+ ) {
956
+ ctx.printResult({
957
+ data: articles,
958
+ metadata: { ticker: null, search: text, feed: null },
959
+ freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
960
+ }, {
961
+ heading: newsSearchHeading(text, reason),
962
+ rows: newsRows,
963
+ columns: NEWS_COLUMNS,
964
+ empty: empty ?? `No stories match "${text}".`,
965
+ });
966
+ }
967
+
743
968
  async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
744
969
  const args = [...rawArgs];
745
970
  const feed = parseNewsFeed(takeOption(args, "--feed"));
746
971
  const exchangeOption = takeOption(args, "--exchange");
972
+ // Stories come newest first, so the newest n are the first n either way.
973
+ const limit = ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 20;
974
+ const text = newsSearchText(args);
975
+ if (text != null) {
976
+ if (exchangeOption || feed) ctx.fail(`${exchangeOption ? "--exchange" : "--feed"} goes with a symbol, not with text to search stories for.`, "Usage: gloomberb news <query>");
977
+ const problem = newsSearchTextProblem(text);
978
+ if (problem) ctx.fail(problem);
979
+ await printNewsSearch(text, await searchNewsStories(text, limit), {}, ctx);
980
+ return;
981
+ }
747
982
  if (exchangeOption && !args[0]) ctx.fail("--exchange needs a symbol: gloomberb news <symbol> --exchange <code>");
748
- rejectExtraArgs(args, 1, { usage: "news [symbol] [--feed <feed>]", takes: "one symbol at most", advice: "Run it once per symbol." }, ctx);
749
983
  await withMarketData(ctx, async (market) => {
750
984
  const listing = args[0] ? await requireCliListing(args[0], exchangeOption, market, ctx) : null;
751
- // Stories come newest first, so the newest n are the first n either way.
752
- const limit = ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 20;
753
985
  const loadNews = () => market.dataProvider.getNews({
754
986
  feed: feed ?? (listing ? "ticker" : "latest"),
755
987
  // Still set for news plugins that read the deprecated scope.
@@ -763,25 +995,33 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
763
995
  listing ? loadListingQuote(market.dataProvider, listing) : null,
764
996
  ]);
765
997
  const identity = listing ? listingIdentity(listing, quote) : null;
998
+ const resolved = quote !== null || identity?.name != null;
999
+ // A bare word no listing answers to is text, and so is a futures board code (TTF), which resolves
1000
+ // to its future: news is filed under company tickers, so the stories that mention the word are the news.
1001
+ const word = listing && !listing.exchange && !feed && articles.length === 0 ? newsFallbackText(args[0]!) : null;
1002
+ const futuresCode = !!word && !!bareFuturesCode(word) && isFuturesInstrument(listing!.symbol, quote?.instrumentType);
1003
+ const reason = !resolved ? "no ticker by that name" : futuresCode ? "a futures code, not a ticker" : null;
1004
+ const emptyMessage = (searched: boolean) => listing && identity
1005
+ ? newsEmptyMessage(searched && word ? word : listing.symbol, listingTitle(identity), quote, resolved, searched)
1006
+ : undefined;
1007
+ if (word && reason) {
1008
+ // The lookup that found no ticker already answered; a search that fails leaves that answer.
1009
+ const found = await searchNewsStories(word, limit).catch(() => []);
1010
+ await printNewsSearch(word, found, { reason, empty: emptyMessage(true) }, ctx);
1011
+ return;
1012
+ }
1013
+ const empty = emptyMessage(false);
1014
+ const read = listing ? listingReadNotes(listing) : [];
766
1015
  ctx.printResult({
767
1016
  data: articles,
1017
+ ...(read.length > 0 ? { warnings: read } : {}),
768
1018
  metadata: { ticker: listing?.key ?? null, ...(identity ? listingMetadata(identity) : {}), feed: feed ?? null },
769
1019
  freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
770
1020
  }, {
771
1021
  ...(identity ? { heading: listingHeading(identity) } : {}),
772
1022
  rows: newsRows,
773
- columns: [
774
- // UTC with the zone named, as every CLI time prints, rather than the host zone unlabeled.
775
- {
776
- key: "publishedAt",
777
- header: "Published",
778
- format: (value) => typeof value === "string" ? formatUtcTime(value) : "",
779
- },
780
- { key: "source", header: "Source", maxWidth: 20 },
781
- { key: "title", header: "Title" },
782
- { key: "tickers", header: "Tickers", maxWidth: 16 },
783
- { key: "url", header: "URL", optional: true },
784
- ],
1023
+ columns: NEWS_COLUMNS,
1024
+ ...(empty ? { empty } : {}),
785
1025
  });
786
1026
  });
787
1027
  }
@@ -809,6 +1049,7 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
809
1049
  const raw = requireOneArg(args, "filings <symbol> [--count <n>] [--form <form>]", "symbol", ctx);
810
1050
  await withMarketData(ctx, async (market) => {
811
1051
  const listing = await requireCliListing(raw, exchangeOption, market, ctx);
1052
+ const venues = loadBareVenues(listing, market);
812
1053
  const { symbol, exchange } = listing.request;
813
1054
  // A listing outside the US sends its company to the lookup, which needs its quote first.
814
1055
  const quotePromise = loadListingQuote(market.dataProvider, listing);
@@ -825,15 +1066,20 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
825
1066
  quotePromise,
826
1067
  ]);
827
1068
  const resolved = identity ?? listingIdentity(listing, quote);
1069
+ const choice = await listingChoiceNotes(listing, market, quote, venues);
828
1070
  ctx.printResult({
829
1071
  data: filings,
830
1072
  metadata: { ...listingMetadata(resolved), ...(form ? { form } : {}) },
831
1073
  freshness: rowsFreshness(filingRows(filings), { ...SEC_FILINGS, observedKey: "filingDate" }),
1074
+ ...(choice.length > 0 ? { warnings: choice } : {}),
832
1075
  }, {
833
1076
  heading: listingHeading(resolved),
834
1077
  rows: filingRows,
835
1078
  columns: FILING_COLUMNS,
836
- empty: form ? `No ${form} filings found for ${listingTitle(resolved)}.` : `No SEC filings found for ${listingTitle(resolved)}.`,
1079
+ // A listing abroad is looked up by its company: none found means the company files nothing with the SEC.
1080
+ empty: form ? `No ${form} filings found for ${listingTitle(resolved)}.`
1081
+ : identity ? noSecReportsNotice(resolved)
1082
+ : `No SEC filings found for ${listingTitle(resolved)}.`,
837
1083
  });
838
1084
  } catch (error) {
839
1085
  if (!(error instanceof SecRegistrantMismatchError)) throw error;
@@ -851,17 +1097,50 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
851
1097
  });
852
1098
  }
853
1099
 
1100
+ type Form4Insiders = "available" | "none" | "foreign-private-issuer" | "no-sec-reports";
1101
+
1102
+ /**
1103
+ * Whether the company has Form 4 filings of its own, read as `fn INS` reads
1104
+ * them; null when they could not be read. A listing abroad the SEC lists
1105
+ * nothing for files no SEC reports at all.
1106
+ */
1107
+ async function loadForm4Insiders(market: MarketContext, listing: CliListing): Promise<Form4Insiders | null> {
1108
+ try {
1109
+ const { parsed, foreignPrivateIssuer, noFilings } = await loadParsedInsiderFilings(market.dataProvider, listing.request.symbol, {
1110
+ limit: 1,
1111
+ exchange: listing.request.exchange,
1112
+ });
1113
+ if (parsed.length > 0) return "available";
1114
+ if (noFilings && nonUsSecListingVenue(listing.request.symbol, listing.request.exchange)) return "no-sec-reports";
1115
+ return foreignPrivateIssuer ? "foreign-private-issuer" : "none";
1116
+ } catch {
1117
+ return null;
1118
+ }
1119
+ }
1120
+
854
1121
  /**
855
1122
  * Holder lists name institutions and funds; individual insiders rarely appear.
856
- * The insider share of the company is reported either way, so say it.
1123
+ * The insider share of the company is reported either way, so say it. The
1124
+ * Form 4 trades are pointed to only when the company has some; otherwise the
1125
+ * line says why there are none, in the words `fn INS` uses.
857
1126
  */
858
- function insiderSummary(data: HolderData, ownerTypes: Set<string>): string {
1127
+ function insiderSummary(
1128
+ data: HolderData,
1129
+ ownerTypes: Set<string>,
1130
+ listing: Pick<CliListing, "key">,
1131
+ identity: ListingIdentity,
1132
+ form4: Form4Insiders | null,
1133
+ ): string {
859
1134
  const held = data.summary?.insidersPercentHeld;
860
1135
  const share = held == null || !Number.isFinite(held)
861
1136
  ? ""
862
1137
  : `Insiders hold ${formatFractionPercentCell(held)} of shares outstanding.`;
863
1138
  const listed = data.holders.some((holder) => ownerTypes.has(holder.ownerType));
864
- const transactions = listed ? "" : `Form 4 transactions: gloomberb fn INS ${data.symbol}`;
1139
+ // The listing as the command read it (CAR:ASX), not the source's spelling of it (CAR.AX), which fn does not take.
1140
+ const transactions = form4 === "available"
1141
+ ? listed ? "" : `Form 4 transactions: gloomberb fn INS ${listing.key}`
1142
+ : form4 === "no-sec-reports" ? noSecReportsNotice(identity)
1143
+ : form4 ? insiderEmptyNotice(form4 === "foreign-private-issuer") : "";
865
1144
  return [share, transactions].filter(Boolean).join("\n");
866
1145
  }
867
1146
 
@@ -878,14 +1157,18 @@ async function printBeneficialOwners(
878
1157
  const quotePromise = loadListingQuote(market.dataProvider, listing);
879
1158
  const abroad = nonUsSecListingVenue(symbol, exchange) ? listingIdentity(listing, await quotePromise) : null;
880
1159
  const request = { form: beneficialRouteForm(form), history, listing: { exchange, name: abroad?.name ?? undefined } };
881
- const [payload, holders, quote] = await Promise.all([
1160
+ const [payload, holders, quote, financials] = await Promise.all([
882
1161
  loadForListing(listing, market, ctx, () => fetchBeneficialOwners(symbol, request)),
883
1162
  market.dataProvider.getHolders?.(symbol, exchange).catch(() => null) ?? null,
884
1163
  quotePromise,
1164
+ // Today's share count, which the filed percents are set against.
1165
+ market.dataProvider.getTickerFinancials(symbol, exchange).catch(() => null),
885
1166
  ]);
886
1167
  const identity = listingIdentity(listing, quote);
887
- const rows = buildBeneficialReportRows(payload, { history, holders });
1168
+ const sharesOutstanding = classSharesToday(financials);
1169
+ const rows = buildBeneficialReportRows(payload, { history, holders, sharesOutstanding });
888
1170
  const notices = beneficialCoverageNotices(payload.coverage);
1171
+ const legend = beneficialReportLegend(rows, sharesOutstanding);
889
1172
  ctx.printResult({
890
1173
  data: rows,
891
1174
  ...(notices.length ? { warnings: notices } : {}),
@@ -898,12 +1181,16 @@ async function printBeneficialOwners(
898
1181
  asOf: payload.asOf,
899
1182
  coverage: payload.coverage,
900
1183
  complete: beneficialListComplete(payload),
1184
+ sharesOutstanding,
901
1185
  },
902
1186
  }, {
903
1187
  heading: `${listingHeading(identity)}${cliStyles.muted(beneficialListFacts(payload, form, rows.length, history).map((fact) => ` · ${fact}`).join(""))}`,
904
- textColumns: BENEFICIAL_REPORT_COLUMNS,
1188
+ // Text draws each column as the HDS report does; CSV and JSON keep the raw values.
1189
+ textColumns: BENEFICIAL_REPORT_COLUMNS.map((column) => ({ ...column, format: column.textFormat ?? column.format })),
1190
+ ...(legend ? { footnote: legend } : {}),
905
1191
  columns: [
906
- ...BENEFICIAL_REPORT_COLUMNS,
1192
+ ...BENEFICIAL_REPORT_COLUMNS.map(({ key, header, align }) => ({ key, header, ...(align ? { align } : {}) })),
1193
+ { key: "otherPersons", header: "Other persons" },
907
1194
  { key: "status", header: "Status" },
908
1195
  { key: "filerCik", header: "Filer CIK" },
909
1196
  { key: "accessionNumber", header: "Accession" },
@@ -911,6 +1198,8 @@ async function printBeneficialOwners(
911
1198
  ],
912
1199
  empty: beneficialListUnreadable(payload)
913
1200
  ? `13D/13G filings for ${listingTitle(identity)} are listed but could not be read.`
1201
+ // The SEC knows no company for a listing abroad that files there under no name of its own.
1202
+ : abroad && !payload.cik ? noSecReportsNotice(identity)
914
1203
  : `No 13D/13G filings in the last 4 years for ${listingTitle(identity)}.`,
915
1204
  });
916
1205
  }
@@ -932,7 +1221,9 @@ async function runHolders(
932
1221
  await withMarketData(ctx, async (market) => {
933
1222
  const listing = await requireCliListing(raw, exchangeOption, market, ctx);
934
1223
  if (form !== "13f") return printBeneficialOwners(listing, market, form, history, ctx);
935
- const [data, quote] = await Promise.all([
1224
+ const insiderCommand = commandName === "insider" && ownerTypes != null;
1225
+ const venues = loadBareVenues(listing, market);
1226
+ const [data, quote, form4] = await Promise.all([
936
1227
  loadForListing(
937
1228
  listing, market, ctx,
938
1229
  () => market.dataProvider.getHolders(listing.request.symbol, listing.request.exchange),
@@ -940,43 +1231,70 @@ async function runHolders(
940
1231
  { command: commandName, noun: "holder data" },
941
1232
  ),
942
1233
  loadListingQuote(market.dataProvider, listing),
1234
+ insiderCommand ? loadForm4Insiders(market, listing) : null,
943
1235
  ]);
944
1236
  const identity = listingIdentity(listing, quote);
945
- const rows = holderRows(data, ownerTypes);
946
- const shown = windowRows(rows, ctx.cliOptions).rows.length;
1237
+ const choice = await listingChoiceNotes(listing, market, quote, venues);
1238
+ // A London line's values can be dollars from the 13F filings; the listing's currency is not their unit.
1239
+ const valueCurrency = holderValueCurrency(data, "USD")!;
1240
+ // The rows, their units and their order are HDS's, so `holders --csv` and `fn HDS --csv` agree.
1241
+ const rows = holderReportRows(data, {
1242
+ marketCap: holderStakeMarketCap(quote?.marketCap, quote?.currency, valueCurrency),
1243
+ ownerTypes,
1244
+ });
1245
+ const shownRows = windowRows(rows, ctx.cliOptions).rows;
1246
+ const shown = shownRows.length;
947
1247
  const institutional = !ownerTypes?.has("insider");
948
1248
  const total = institutional ? data.summary?.institutionsCount ?? null : null;
949
1249
  const reportDate = sharedReportDate(rows);
950
- // A London line's values can be dollars from the 13F filings; the listing's currency is not their unit.
951
- const valueCurrency = holderValueCurrency(data);
952
1250
  const valueBasis = rows.length > 0 ? holderValueBasis(reportDate, data.valueBasis, valueCurrency) : null;
953
1251
  const positionsBasis = rows.length > 0 ? nonUsHolderCaveat(identity.exchange || data.exchange, data.currency) : null;
954
1252
  const shareBasisNote = holderShareBasisNote(data, rows);
955
1253
  const markedRows = rows.some((row) => holderShareBasisMarker(row));
956
1254
  // The heading names the listing; its unit, date and how much of the list follows on the same line.
1255
+ // One "as of" only when every row shown is of that date; each row carries its own.
957
1256
  const facts = holderListFacts({
958
- currency: valueCurrency, listingCurrency: data.currency, asOf: data.asOf, shown, reported: rows.length, total,
1257
+ currency: valueCurrency,
1258
+ listingCurrency: data.currency,
1259
+ period: holderListPeriod(shownRows, data.asOf),
1260
+ shown,
1261
+ reported: rows.length,
1262
+ total,
1263
+ ranked: !positionsBasis,
959
1264
  });
1265
+ const unvaluedNote = unvaluedHoldersNote(shownRows);
1266
+ const columns = holderReportColumns(shownRows, valueCurrency);
1267
+ // Dated as HDS dates it: by the list's as-of.
1268
+ const freshness = rowsFreshness(shownRows, { ...SEC_FILINGS, basis: "13F filings" }, { asOf: data.asOf ?? null });
1269
+ // Named as fn HDS takes it, so the closing notes point to the same listing.
1270
+ const requested = exchangeOption && !raw.includes(":") ? `${raw}:${exchangeOption.toUpperCase()}` : raw;
1271
+ const { notices } = holderReportNotices({ data, requested, shown: shownRows, reported: rows.length, total });
960
1272
  ctx.printResult({
961
- data,
1273
+ data: rows,
1274
+ ...(freshness ? { freshness } : {}),
962
1275
  metadata: {
963
1276
  ...listingMetadata(identity),
964
- summary: data.summary,
1277
+ summary: data.summary ?? null,
965
1278
  currency: data.currency ?? null,
966
- valueCurrency: valueCurrency ?? null,
1279
+ valueCurrency,
1280
+ units: holderReportUnits(valueCurrency),
967
1281
  asOf: data.asOf ?? null,
1282
+ // Each holder's latest period; several when the list mixes quarters.
1283
+ reportDates: holderReportDates(shownRows),
968
1284
  shown,
969
1285
  reported: rows.length,
970
1286
  total,
971
1287
  truncated: shown < (total ?? rows.length),
972
1288
  valueBasis,
973
1289
  positionsBasis,
1290
+ ...(insiderCommand ? { form4Insiders: form4 } : {}),
974
1291
  },
1292
+ ...(choice.length > 0 ? { warnings: choice } : {}),
975
1293
  }, {
976
1294
  heading: `${listingHeading(identity)}${cliStyles.muted(facts.map((fact) => ` · ${fact}`).join(""))}`,
977
- rows: () => rows,
978
- columns: [
979
- { key: "type", header: "Type" },
1295
+ columns,
1296
+ textColumns: [
1297
+ { key: "ownerType", header: "Type" },
980
1298
  { key: "name", header: "Holder" },
981
1299
  { key: "reportDate", header: "Date" },
982
1300
  { key: "shares", header: "Shares", align: "right", format: formatCountCell },
@@ -992,12 +1310,17 @@ async function runHolders(
992
1310
  key: "percentHeld",
993
1311
  header: "% Held",
994
1312
  align: "right",
995
- format: (value) => isFiniteNumber(value) ? formatHolderOwnershipPercent(value) : "",
1313
+ format: (value) => isFiniteNumber(value) ? formatPercentHeld(value) : "",
996
1314
  },
997
1315
  ],
998
- summary: (holderData: HolderData) => [
999
- [valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? "", shareBasisNote ?? ""].filter(Boolean).join(" "),
1000
- commandName === "insider" && ownerTypes ? insiderSummary(holderData, ownerTypes) : "",
1316
+ // CSV and NDJSON are the HDS report's table and closing lines.
1317
+ tables: {
1318
+ tables: [exportRowsTable("Holders", columns, shownRows)],
1319
+ footer: reportFooterLines({ freshness, notes: notices }),
1320
+ },
1321
+ summary: () => [
1322
+ [valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? "", shareBasisNote ?? "", unvaluedNote ?? ""].filter(Boolean).join(" "),
1323
+ insiderCommand ? insiderSummary(data, ownerTypes, listing, identity, form4) : "",
1001
1324
  ].filter(Boolean).join("\n"),
1002
1325
  empty: `No holders reported for ${listingTitle(identity)}.`,
1003
1326
  });
@@ -1010,6 +1333,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1010
1333
  const raw = requireOneArg(args, "analyst <symbol>", "symbol", ctx);
1011
1334
  await withMarketData(ctx, async (market) => {
1012
1335
  const listing = await requireCliListing(raw, exchangeOption, market, ctx);
1336
+ const venues = loadBareVenues(listing, market);
1013
1337
  const [data, quote] = await Promise.all([
1014
1338
  loadForListing(
1015
1339
  listing, market, ctx, () => market.dataProvider.getAnalystResearch(listing.request.symbol, listing.request.exchange),
@@ -1018,6 +1342,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1018
1342
  loadListingQuote(market.dataProvider, listing),
1019
1343
  ]);
1020
1344
  const identity = listingIdentity(listing, quote);
1345
+ const choice = await listingChoiceNotes(listing, market, quote, venues);
1021
1346
  ctx.printResult({
1022
1347
  data,
1023
1348
  metadata: {
@@ -1029,6 +1354,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1029
1354
  freshness: rowsFreshness(analystRows(data), {
1030
1355
  ...REPORTED_DATA, basis: "analyst ratings", observedKey: "date", oldest: null,
1031
1356
  }, { stale: data.stale === true }),
1357
+ ...(choice.length > 0 ? { warnings: choice } : {}),
1032
1358
  }, {
1033
1359
  heading: listingHeading(identity),
1034
1360
  rows: analystRows,
@@ -1047,9 +1373,13 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1047
1373
  async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
1048
1374
  const args = [...rawArgs];
1049
1375
  const exchangeOption = takeOption(args, "--exchange");
1376
+ // A central bank's name means the bank, not a listing that shares it; a venue still reaches the listing.
1377
+ const bank = exchangeOption ? null : centralBankEventsMessage(args.join(" "));
1378
+ if (bank) ctx.fail(bank);
1050
1379
  const raw = requireOneArg(args, "events <symbol>", "symbol", ctx);
1051
1380
  await withMarketData(ctx, async (market) => {
1052
1381
  const listing = await requireCliListing(raw, exchangeOption, market, ctx);
1382
+ const venues = loadBareVenues(listing, market);
1053
1383
  const [data, quote] = await Promise.all([
1054
1384
  loadForListing(
1055
1385
  listing, market, ctx, () => market.dataProvider.getCorporateActions(listing.request.symbol, listing.request.exchange),
@@ -1058,12 +1388,14 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
1058
1388
  loadListingQuote(market.dataProvider, listing),
1059
1389
  ]);
1060
1390
  const identity = listingIdentity(listing, quote);
1391
+ const choice = await listingChoiceNotes(listing, market, quote, venues);
1061
1392
  ctx.printResult({
1062
1393
  data,
1063
1394
  metadata: listingMetadata(identity),
1064
1395
  freshness: rowsFreshness(corporateActionRows(data), {
1065
1396
  ...REPORTED_DATA, basis: "corporate actions", observedKey: "date", oldest: null,
1066
1397
  }),
1398
+ ...(choice.length > 0 ? { warnings: choice } : {}),
1067
1399
  }, {
1068
1400
  heading: listingHeading(identity),
1069
1401
  rows: corporateActionRows,
@@ -1111,7 +1443,9 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1111
1443
  const listing = await requireCliListing(raw, exchangeOption, market, ctx);
1112
1444
  const { symbol, exchange } = listing.request;
1113
1445
  const refresh = ctx.cliOptions.refresh;
1446
+ const venues = loadBareVenues(listing, market);
1114
1447
  const quotePromise = loadListingQuote(market.dataProvider, listing);
1448
+ const fate = listingFateLoader(listing, market, quotePromise);
1115
1449
  const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quotePromise, refresh);
1116
1450
  const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
1117
1451
  const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
@@ -1122,7 +1456,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1122
1456
  cacheMode: refresh ? "refresh" : "default",
1123
1457
  }),
1124
1458
  };
1125
- }, undefined, { command: "options", noun: "options chain" }).catch((error: unknown) => {
1459
+ }, undefined, { command: "options", noun: "options chain", fate }).catch((error: unknown) => {
1126
1460
  if (!isOptionsUnavailableError(error)) throw error;
1127
1461
  // A future or commodity with no chain of its own: name the listed fund whose options stand in.
1128
1462
  const alternative = listedOptionsAlternative(listing.key);
@@ -1140,7 +1474,8 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1140
1474
  const miss = missingExpiryMessage(listing.key, expirationDate, listed);
1141
1475
  ctx.fail(miss.message, miss.details);
1142
1476
  }
1143
- const identity = listingIdentity(listing, await quotePromise);
1477
+ const quote = await quotePromise;
1478
+ const identity = listingIdentity(listing, quote);
1144
1479
  const chain = chainWithModelFigures(loaded, inputs);
1145
1480
  // A failed refresh falls back to the stored chain, which can be days old.
1146
1481
  const refreshWarning = result?.refreshError == null ? null
@@ -1150,7 +1485,13 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
1150
1485
  const modelWarning = inputs.spot == null && (chain.calls.length > 0 || chain.puts.length > 0)
1151
1486
  ? `IV and delta need a current ${listing.key} quote; those columns are blank`
1152
1487
  : null;
1153
- const warnings = [refreshWarning ?? sessionWarning, modelWarning].filter((warning): warning is string => warning != null);
1488
+ const listed = chain.expirationDates.length > 0 || chain.calls.length > 0 || chain.puts.length > 0;
1489
+ const warnings = [
1490
+ // FB's chain is the fund listed there now; a listing that stopped trading has none.
1491
+ ...listingFateNotes(await fate(), { served: listed }),
1492
+ ...await listingChoiceNotes(listing, market, quote, venues),
1493
+ ...[refreshWarning ?? sessionWarning, modelWarning].filter((warning): warning is string => warning != null),
1494
+ ];
1154
1495
  // Dated by the chain's last trade; a chain that failed to refresh is the stored one, stale.
1155
1496
  const freshness = rowsFreshness([], { asOf: chain.asOf ?? null }, {
1156
1497
  ...(chain.dataSource ? { dataSource: chain.dataSource } : {}),
@@ -1226,12 +1567,16 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
1226
1567
  const asOf = observed.length > 0 ? new Date(Math.min(...observed)).toISOString() : null;
1227
1568
  const stale = legs.some((leg) => leg != null && (leg.staleAt <= Date.now() || leg.refreshError != null));
1228
1569
  const row = { currency, baseCurrency, rate, asOf, stale, pair, inverse: 1 / rate };
1229
- ctx.printResult({ data: [row] }, {
1230
- text: () => {
1231
- const time = asOf ? `As of ${formatUtcTime(asOf)}` : "";
1232
- const when = stale ? cliStyles.warning(time ? `${time}, stale` : "Stale") : time && cliStyles.muted(time);
1233
- return [describeFxRate(request, rate), when].filter(Boolean).join("\n");
1234
- },
1570
+ // The closing line dates the rate, and says when it is stale, as `fn FXC` does.
1571
+ const freshness = fxPairFreshness(new Map(codes.flatMap((code, index) => {
1572
+ const leg = legs[index];
1573
+ return leg ? [[code, {
1574
+ rate: leg.value,
1575
+ timing: { asOf: leg.asOf, staleAt: leg.staleAt, error: leg.refreshError, delayMinutes: leg.delayMinutes },
1576
+ }] as const] : [];
1577
+ })));
1578
+ ctx.printResult({ data: [row], freshness }, {
1579
+ text: () => describeFxRate(request, rate),
1235
1580
  columns: [
1236
1581
  { key: "currency", header: "Currency" },
1237
1582
  { key: "baseCurrency", header: "Base" },
@@ -1254,24 +1599,55 @@ async function runEarnings(rawArgs: string[], ctx: Parameters<CliCommandDef["exe
1254
1599
  const listings = await Promise.all(symbols.map((symbol) => requireCliListing(
1255
1600
  symbol, exchangeOption, services, ctx, { ownExchangeWins: symbols.length > 1 },
1256
1601
  )));
1257
- const events = await loadForListing(
1258
- listings, services, ctx,
1259
- () => services.dataProvider.getEarningsCalendar(listings.map((listing) => listing.key)),
1260
- (found) => found.length === 0,
1261
- );
1602
+ const keys = listings.map((listing) => listing.key);
1603
+ // Each bare symbol's venues cost a search, so a long list (a portfolio check, not a lookup) names none.
1604
+ const venues = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
1605
+ : listings.map((listing) => [listing, loadBareVenues(listing, services)] as const));
1606
+ let events: EarningsEvent[];
1607
+ try {
1608
+ events = await loadForListing(
1609
+ listings, services, ctx,
1610
+ () => services.dataProvider.getEarningsCalendar(keys),
1611
+ (found) => found.length === 0,
1612
+ );
1613
+ } catch (error) {
1614
+ const failure = earningsCalendarFailure(error, keys);
1615
+ if (failure) ctx.fail(failure);
1616
+ throw error;
1617
+ }
1618
+ const eventOf = (listing: CliListing) => events.find((event) => event.symbol.toUpperCase() === listing.key.toUpperCase());
1619
+ const [abroadNotes, choiceNotes] = await Promise.all([
1620
+ // A listing abroad with no date of its own says so, with its US line's date when that line is the same company.
1621
+ Promise.all(listings.filter((listing) => isListingAbroad(listing) && !eventOf(listing))
1622
+ .map((listing) => noListingCalendarNote(listing, services))),
1623
+ Promise.all(listings.map(async (listing) => {
1624
+ const listed = venues.get(listing);
1625
+ return listed ? earningsChoiceNote(listing, eventOf(listing), await listed) : null;
1626
+ })),
1627
+ ]);
1628
+ const noCalendar = abroadNotes.join("\n");
1629
+ // Text prints the listings without a calendar under the table; other formats carry them as warnings.
1630
+ const warnings = [
1631
+ ...listings.flatMap(listingReadNotes),
1632
+ ...choiceNotes.filter((note): note is string => !!note),
1633
+ ...ctx.cliOptions.format === "text" ? [] : abroadNotes,
1634
+ ];
1262
1635
  ctx.printResult({
1263
1636
  data: events,
1264
1637
  freshness: rowsFreshness(earningsRows(events), { status: "not-a-feed", basis: "calendar", observedKey: "date", oldest: null }),
1638
+ ...(warnings.length > 0 ? { warnings } : {}),
1265
1639
  }, {
1266
1640
  rows: earningsRows,
1267
1641
  columns: [
1268
1642
  { key: "date", header: "Date" },
1643
+ { key: "status", header: "Status" },
1269
1644
  { key: "symbol", header: "Symbol" },
1270
1645
  { key: "name", header: "Name" },
1271
1646
  { key: "timing", header: "Timing" },
1272
1647
  { key: "epsEstimate", header: "EPS Est", align: "right", format: formatEpsCell },
1273
1648
  { key: "epsActual", header: "EPS", align: "right", format: formatEpsCell },
1274
1649
  ],
1650
+ ...(noCalendar ? { empty: noCalendar, footnote: noCalendar } : {}),
1275
1651
  });
1276
1652
  });
1277
1653
  }
@@ -1284,7 +1660,7 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1284
1660
  group: CLI_COMMAND_GROUPS.research,
1285
1661
  usage: ["quote <symbol...>"],
1286
1662
  options: [EXCHANGE_OPTION],
1287
- examples: ["quote AAPL MSFT NVDA", "quote SAN:EPA BHP:ASX", "quote BTC-USD EURUSD=X", "quote AAPL --json"],
1663
+ examples: ["quote AAPL MSFT NVDA", "quote SAN:EPA BHP:ASX", "quote BTC-USD EURUSD=X", "quote binance:BZUSDT", "quote AAPL --json"],
1288
1664
  },
1289
1665
  execute: (args, ctx) => runQuote(args, ctx, "quote"),
1290
1666
  },
@@ -1312,15 +1688,13 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1312
1688
  sections: [{
1313
1689
  title: "Bars",
1314
1690
  lines: [
1315
- `Each range comes in one bar size: ${historyIntervalsByRange()}. The line under the heading, and the --json metadata, give the currency, the bar size actually served, the first and last bar and the bar count. A warning says when the bars are coarser, or start later, than the range asked for.`,
1691
+ `Each range comes in one bar size: ${historyIntervalsByRange()}. The line under the heading, and the --json metadata, give the currency, the bar size actually served, the first and last bar and the bar count. A warning says when the bars are coarser, or start later, than the range asked for, and for a coin, which trades every day, names each day it has no bar for.`,
1316
1692
  "A bar whose prices contradict each other (high below the open or close, or low above them) is left blank and flagged, such as high<open, with the count in a warning. Every exported row (--csv, --ndjson, --json) carries currency, interval and flag.",
1317
1693
  ],
1318
1694
  }],
1319
1695
  examples: ["history AAPL", "history BHP:ASX --range 5Y", "history ZAR=X --range ALL", "history ZAR=X --tail 5", "history AAPL --range 5Y --csv > aapl.csv"],
1320
1696
  },
1321
- unknownOptionHint: (flag) => DATE_WINDOW_OPTIONS.has(flag)
1322
- ? `history takes a --range instead of dates: ${TIME_RANGES.join(", ")}. ${HISTORY_ALL_RANGE}.`
1323
- : null,
1697
+ unknownOptionHint: historyOptionHint,
1324
1698
  execute: runHistory,
1325
1699
  },
1326
1700
  {
@@ -1472,7 +1846,13 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1472
1846
  { flags: "--history", description: "With --form 13d, 13g or all, every report newest first instead of the latest per filer" },
1473
1847
  EXCHANGE_OPTION,
1474
1848
  ],
1475
- examples: ["holders AAPL", "holders SAN:EPA", "holders CAR --form all", "holders CAR --form 13g --history --json"],
1849
+ sections: [{
1850
+ title: "Output",
1851
+ lines: [
1852
+ "Holders run largest value first, as in fn HDS. --csv and --json carry the HDS report's columns: values in the currency the header names, shares, and the quarter's change and the stake in percent (0.59 is 0.59%).",
1853
+ ],
1854
+ }],
1855
+ examples: ["holders AAPL", "holders SAN:EPA", "holders SAN --csv", "holders CAR --form all", "holders CAR --form 13g --history --json"],
1476
1856
  },
1477
1857
  execute: (args, ctx) => runHolders(args, ctx, "holders"),
1478
1858
  },
@@ -1519,15 +1899,21 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
1519
1899
  },
1520
1900
  {
1521
1901
  name: "news",
1522
- description: "Fetch market headlines, or news for one symbol",
1902
+ description: "Fetch market headlines, news for one symbol, or stories matching text",
1523
1903
  help: {
1524
1904
  group: CLI_COMMAND_GROUPS.companyData,
1525
- usage: ["news [symbol] [--feed <feed>]"],
1905
+ usage: ["news [symbol] [--feed <feed>]", "news <query>"],
1526
1906
  options: [
1527
1907
  { flags: "--feed <feed>", description: "latest, top, or breaking for market news (default latest)" },
1528
1908
  EXCHANGE_OPTION,
1529
1909
  ],
1530
- examples: ["news", "news TSLA", "news SAN:EPA", "news --feed top --limit 10"],
1910
+ sections: [{
1911
+ title: "Text",
1912
+ lines: [
1913
+ "Several words, in quotes or not, search the stories that mention them. So does a single word of three letters or more that no listing answers to, and a futures board code such as TTF, a gas benchmark rather than a ticker. A real ticker always gives its own news. The best matches print newest first, 20 unless --limit says otherwise; a search takes neither --feed nor --exchange.",
1914
+ ],
1915
+ }],
1916
+ examples: ["news", "news TSLA", "news SAN:EPA", "news TTF", "news \"european gas\"", "news --feed top --limit 10"],
1531
1917
  },
1532
1918
  execute: runNews,
1533
1919
  },