gloomberb 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (600) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/package.json +1 -1
  4. package/patches/electrobun@1.18.1.patch +169 -5
  5. package/src/account/auth-model.ts +7 -3
  6. package/src/api-client/auth.ts +9 -4
  7. package/src/api-client/catalysts.ts +2 -1
  8. package/src/api-client/cot.ts +6 -2
  9. package/src/api-client/credit-documents.ts +6 -0
  10. package/src/api-client/data.ts +31 -6
  11. package/src/api-client/equity-screener.ts +24 -0
  12. package/src/api-client/fund-facts.ts +45 -4
  13. package/src/api-client/index.ts +171 -11
  14. package/src/api-client/live-scan.ts +91 -0
  15. package/src/api-client/market-discovery.ts +10 -0
  16. package/src/api-client/market-movers.ts +7 -0
  17. package/src/api-client/members.ts +13 -1
  18. package/src/api-client/perps.ts +43 -0
  19. package/src/api-client/request.ts +222 -41
  20. package/src/api-client/research-activity.ts +6 -12
  21. package/src/api-client/types.ts +74 -0
  22. package/src/app/keybindings/index.ts +3 -0
  23. package/src/app/keybindings/labels.ts +18 -1
  24. package/src/app/keybindings/resolve.ts +14 -0
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +42 -27
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +1 -1
  29. package/src/architecture/pane-tabs-allowlist.json +3 -0
  30. package/src/architecture/ttl-caches-allowlist.json +19 -0
  31. package/src/brokers/sync-broker-instance.ts +2 -8
  32. package/src/capabilities/chart-series.ts +1 -1
  33. package/src/cli/commands/crypto-hints.ts +40 -24
  34. package/src/cli/commands/fred-series.ts +5 -0
  35. package/src/cli/commands/keybindings.ts +23 -0
  36. package/src/cli/commands/market.ts +138 -46
  37. package/src/cli/commands/options-chain.ts +107 -10
  38. package/src/cli/commands/options-leaps.ts +9 -3
  39. package/src/cli/commands/overview.ts +176 -39
  40. package/src/cli/commands/search.ts +39 -3
  41. package/src/cli/commands/themes.ts +11 -2
  42. package/src/cli/commands/ticker.ts +57 -35
  43. package/src/cli/company-name-hint.ts +41 -0
  44. package/src/cli/earnings-calendar.ts +1 -1
  45. package/src/cli/errors.ts +10 -2
  46. package/src/cli/export-source.ts +29 -1
  47. package/src/cli/glossary.ts +73 -1
  48. package/src/cli/history-rows.ts +87 -16
  49. package/src/cli/index.ts +2 -0
  50. package/src/cli/known-gaps.ts +5 -2
  51. package/src/cli/listing-arg.ts +195 -56
  52. package/src/cli/output-file.ts +68 -0
  53. package/src/cli/pane-functions/capabilities.ts +3 -0
  54. package/src/cli/pane-functions/catalog.ts +80 -7
  55. package/src/cli/pane-functions/dom.ts +13 -3
  56. package/src/cli/pane-functions/freshness.ts +111 -16
  57. package/src/cli/pane-functions/function-help.ts +23 -13
  58. package/src/cli/pane-functions/headless.ts +56 -12
  59. package/src/cli/pane-functions/index.ts +58 -16
  60. package/src/cli/pane-functions/market-session.ts +81 -9
  61. package/src/cli/pane-functions/options.ts +19 -1
  62. package/src/cli/pane-functions/report.ts +5 -1
  63. package/src/cli/pane-functions/resolver.ts +35 -8
  64. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  65. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  66. package/src/cli/quote-answer.ts +30 -13
  67. package/src/cli/report-tables.ts +18 -7
  68. package/src/cli/restore-plugins.ts +13 -1
  69. package/src/cli/result.ts +57 -5
  70. package/src/cli/ticker-resolution.ts +3 -2
  71. package/src/components/chart/composite/composite-chart.tsx +26 -4
  72. package/src/components/chart/composite/format.ts +27 -0
  73. package/src/components/chart/composite/interactions.ts +6 -0
  74. package/src/components/chart/composite/legend.tsx +45 -7
  75. package/src/components/chart/composite/levels.ts +2 -0
  76. package/src/components/chart/composite/panel-gestures.ts +33 -0
  77. package/src/components/chart/composite/panel-series.ts +1 -0
  78. package/src/components/chart/composite/panel-surface.tsx +61 -5
  79. package/src/components/chart/composite/range-statistics.ts +62 -0
  80. package/src/components/chart/composite/rasterizer.ts +24 -13
  81. package/src/components/chart/composite/scene.ts +46 -7
  82. package/src/components/chart/composite/series-axis-tags.tsx +16 -9
  83. package/src/components/chart/composite/series-cues.ts +14 -0
  84. package/src/components/chart/composite/text-renderer.ts +9 -2
  85. package/src/components/chart/composite/tool-catalog.ts +36 -0
  86. package/src/components/chart/composite/tools.ts +203 -44
  87. package/src/components/chart/composite/types.ts +4 -0
  88. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  89. package/src/components/command-bar/assist/inventory.ts +2 -1
  90. package/src/components/command-bar/commands/direct/index.ts +28 -13
  91. package/src/components/command-bar/pane-templates/items.ts +3 -2
  92. package/src/components/command-bar/panel/prompt-binding.ts +0 -8
  93. package/src/components/command-bar/workflow/alert.ts +1 -1
  94. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  95. package/src/components/layout/header.tsx +26 -40
  96. package/src/components/layout/pane/footer/index.tsx +1 -0
  97. package/src/components/layout/pane/footer/model.ts +27 -0
  98. package/src/components/onboarding/onboarding-frame.tsx +1 -1
  99. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  100. package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
  101. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  102. package/src/components/onboarding/wizard-positions.ts +3 -3
  103. package/src/components/ticker/list-table-view.tsx +4 -1
  104. package/src/components/ui/choice-dialog.tsx +23 -18
  105. package/src/components/ui/choice-groups.ts +18 -0
  106. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  107. package/src/components/ui/data-table/opentui/model.ts +26 -1
  108. package/src/components/ui/data-table/types.ts +2 -0
  109. package/src/components/ui/display.tsx +5 -1
  110. package/src/components/ui/list-view.tsx +8 -4
  111. package/src/components/ui/query-bar-keys.tsx +2 -0
  112. package/src/components/ui/query-bar.tsx +1 -0
  113. package/src/components/ui/select-button.tsx +5 -1
  114. package/src/components/ui/select-field.tsx +1 -0
  115. package/src/components/ui/table-layout.ts +8 -5
  116. package/src/core/app-runtime.ts +2 -0
  117. package/src/core/app-services.ts +2 -2
  118. package/src/core/state/app/layout.ts +2 -0
  119. package/src/core/state/app/state.ts +9 -0
  120. package/src/core/state/app/types.ts +3 -0
  121. package/src/data/config/chart-settings.ts +1 -1
  122. package/src/data/json-ticker-repository.ts +17 -8
  123. package/src/data/saved-copies.ts +55 -16
  124. package/src/data/ticker-repository.ts +16 -9
  125. package/src/data/ticker-writes.ts +81 -0
  126. package/src/data/ttl-cache.ts +165 -0
  127. package/src/docs/snapshot.json +427 -216
  128. package/src/i18n/es.ts +19 -3
  129. package/src/i18n/ja.ts +19 -3
  130. package/src/i18n/ko.ts +19 -3
  131. package/src/i18n/zh-cn.ts +19 -3
  132. package/src/i18n/zh-tw.ts +19 -3
  133. package/src/market-data/coordinator/financials.ts +10 -2
  134. package/src/market-data/coordinator/index.ts +3 -1
  135. package/src/market-data/coordinator/quotes.ts +4 -2
  136. package/src/market-data/depositary-pair.ts +2 -2
  137. package/src/market-data/fund-facts.ts +50 -0
  138. package/src/market-data/market/change-basis.ts +48 -7
  139. package/src/market-data/market/dated-previous-close.ts +203 -0
  140. package/src/market-data/market/format.ts +25 -0
  141. package/src/market-data/market/freshness.ts +2 -1
  142. package/src/market-data/market/futures-week.ts +68 -0
  143. package/src/market-data/market/status.ts +16 -4
  144. package/src/market-data/options-alternatives.ts +141 -4
  145. package/src/market-data/quotes/contributions.ts +20 -2
  146. package/src/market-data/quotes/resolution.ts +3 -0
  147. package/src/market-data/selectors.ts +3 -0
  148. package/src/news/aggregator.ts +113 -9
  149. package/src/news/cloud-session.ts +21 -0
  150. package/src/news/hooks.ts +1 -0
  151. package/src/news/news-model.ts +1 -0
  152. package/src/news/types.ts +11 -0
  153. package/src/notifications/notification-log.ts +6 -4
  154. package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
  155. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  156. package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
  157. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  158. package/src/plugins/builtin/alerts/cli.ts +380 -82
  159. package/src/plugins/builtin/alerts/command.ts +31 -0
  160. package/src/plugins/builtin/alerts/constants.ts +1 -1
  161. package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
  162. package/src/plugins/builtin/alerts/fired-log.ts +113 -8
  163. package/src/plugins/builtin/alerts/format.ts +80 -4
  164. package/src/plugins/builtin/alerts/headless.ts +30 -7
  165. package/src/plugins/builtin/alerts/history-rows.ts +7 -3
  166. package/src/plugins/builtin/alerts/history.ts +2 -0
  167. package/src/plugins/builtin/alerts/index.tsx +135 -22
  168. package/src/plugins/builtin/alerts/live.ts +3 -3
  169. package/src/plugins/builtin/alerts/metric-command.ts +134 -0
  170. package/src/plugins/builtin/alerts/metric-data.ts +111 -0
  171. package/src/plugins/builtin/alerts/metric-model.ts +324 -0
  172. package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
  173. package/src/plugins/builtin/alerts/pane.tsx +90 -39
  174. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  175. package/src/plugins/builtin/alerts/storage.ts +37 -10
  176. package/src/plugins/builtin/alerts/types.ts +7 -0
  177. package/src/plugins/builtin/analytics/index.tsx +2 -1
  178. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  179. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  180. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  181. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  182. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  183. package/src/plugins/builtin/attention/index.tsx +2 -1
  184. package/src/plugins/builtin/attention/listing.ts +14 -0
  185. package/src/plugins/builtin/backtest/headless.ts +10 -5
  186. package/src/plugins/builtin/backtest/pane.tsx +9 -4
  187. package/src/plugins/builtin/backtest/presets.ts +29 -3
  188. package/src/plugins/builtin/broker-manager/index.tsx +10 -5
  189. package/src/plugins/builtin/broker-manager/model.ts +6 -1
  190. package/src/plugins/builtin/broker-trading/controller.ts +22 -12
  191. package/src/plugins/builtin/broker-trading/index.tsx +13 -1
  192. package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
  193. package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
  194. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  195. package/src/plugins/builtin/buildout/format.ts +38 -0
  196. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  197. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  198. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  199. package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
  200. package/src/plugins/builtin/catalysts/model.ts +2 -1
  201. package/src/plugins/builtin/changelog/index.tsx +9 -6
  202. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
  203. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
  204. package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
  205. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
  206. package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
  207. package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
  208. package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
  209. package/src/plugins/builtin/chart-composer/formula.ts +71 -18
  210. package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
  211. package/src/plugins/builtin/chart-composer/headless.ts +19 -3
  212. package/src/plugins/builtin/chart-composer/index.tsx +1 -1
  213. package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
  214. package/src/plugins/builtin/chart-composer/presets.ts +50 -15
  215. package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
  216. package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
  217. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  218. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
  219. package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
  220. package/src/plugins/builtin/chat/content/composer.tsx +13 -6
  221. package/src/plugins/builtin/chat/content/footer.tsx +6 -3
  222. package/src/plugins/builtin/chat/content/index.tsx +50 -21
  223. package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
  224. package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
  225. package/src/plugins/builtin/chat/index.tsx +23 -0
  226. package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
  227. package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
  228. package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
  229. package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
  230. package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
  231. package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
  232. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
  233. package/src/plugins/builtin/clinical-trials/model.ts +0 -9
  234. package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
  235. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
  236. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  237. package/src/plugins/builtin/cloud/plugin.tsx +1 -1
  238. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  239. package/src/plugins/builtin/company-attention/client.ts +8 -9
  240. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  241. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  242. package/src/plugins/builtin/company-attention/pane.tsx +9 -2
  243. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  244. package/src/plugins/builtin/correlation/headless.ts +13 -5
  245. package/src/plugins/builtin/correlation/history.ts +22 -0
  246. package/src/plugins/builtin/cot/client.ts +22 -18
  247. package/src/plugins/builtin/cot/headless.ts +15 -10
  248. package/src/plugins/builtin/cot/model.ts +14 -1
  249. package/src/plugins/builtin/cot/pane.tsx +22 -14
  250. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  251. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  252. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  253. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  254. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  255. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  256. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  257. package/src/plugins/builtin/credit-documents/headless.ts +7 -7
  258. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  259. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  260. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  261. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  262. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  263. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  264. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  265. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  266. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  267. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  268. package/src/plugins/builtin/docs/targets.ts +5 -1
  269. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  270. package/src/plugins/builtin/earnings/board.tsx +62 -71
  271. package/src/plugins/builtin/earnings/headless.ts +40 -4
  272. package/src/plugins/builtin/earnings/scope.ts +31 -43
  273. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  274. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  275. package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
  276. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  277. package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
  278. package/src/plugins/builtin/econ/calendar-model.ts +14 -9
  279. package/src/plugins/builtin/econ/figures.ts +72 -0
  280. package/src/plugins/builtin/econ/index.tsx +33 -3
  281. package/src/plugins/builtin/econ/types.ts +4 -0
  282. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  283. package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
  284. package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
  285. package/src/plugins/builtin/econ-statistics/view.ts +3 -0
  286. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  287. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  288. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  289. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  290. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  291. package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
  292. package/src/plugins/builtin/exposure/charts.tsx +2 -2
  293. package/src/plugins/builtin/exposure/model.ts +13 -1
  294. package/src/plugins/builtin/exposure/pane.tsx +4 -4
  295. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  296. package/src/plugins/builtin/futures/index.tsx +1 -0
  297. package/src/plugins/builtin/futures/model.ts +2 -1
  298. package/src/plugins/builtin/futures/table.tsx +2 -2
  299. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  300. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  301. package/src/plugins/builtin/fx-matrix/client.ts +11 -3
  302. package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
  303. package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
  304. package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
  305. package/src/plugins/builtin/gpu/headless.ts +8 -2
  306. package/src/plugins/builtin/help/function-index.ts +4 -1
  307. package/src/plugins/builtin/help/index.tsx +1 -0
  308. package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
  309. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  310. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  311. package/src/plugins/builtin/holders/headless.ts +18 -1
  312. package/src/plugins/builtin/iv-history/client.ts +52 -11
  313. package/src/plugins/builtin/iv-history/format.ts +36 -23
  314. package/src/plugins/builtin/iv-history/headless.ts +36 -18
  315. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  316. package/src/plugins/builtin/iv-history/model.ts +163 -27
  317. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  318. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  319. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  320. package/src/plugins/builtin/jobs/client.ts +25 -29
  321. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  322. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  323. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  324. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  325. package/src/plugins/builtin/macro-day/pane.tsx +7 -5
  326. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  327. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  328. package/src/plugins/builtin/market-movers/client.ts +25 -7
  329. package/src/plugins/builtin/market-movers/footer.ts +2 -2
  330. package/src/plugins/builtin/market-movers/headless.ts +32 -12
  331. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  332. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  333. package/src/plugins/builtin/market-movers/model.ts +4 -1
  334. package/src/plugins/builtin/market-movers/screener.ts +14 -0
  335. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  336. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  337. package/src/plugins/builtin/market-movers/table.ts +17 -5
  338. package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
  339. package/src/plugins/builtin/members/client.ts +19 -4
  340. package/src/plugins/builtin/members/headless.ts +20 -7
  341. package/src/plugins/builtin/members/index.tsx +9 -9
  342. package/src/plugins/builtin/members/model.ts +23 -4
  343. package/src/plugins/builtin/members/pane.tsx +25 -15
  344. package/src/plugins/builtin/news/index.tsx +1 -0
  345. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  346. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  347. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  348. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  349. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  350. package/src/plugins/builtin/openfda/client.ts +14 -2
  351. package/src/plugins/builtin/openfda/index.tsx +3 -2
  352. package/src/plugins/builtin/openfda/model.ts +9 -0
  353. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  354. package/src/plugins/builtin/options/analytics.ts +54 -6
  355. package/src/plugins/builtin/options/enrichment.ts +17 -5
  356. package/src/plugins/builtin/options/model-spot.ts +5 -2
  357. package/src/plugins/builtin/options/strike-window.ts +2 -1
  358. package/src/plugins/builtin/options/table.ts +6 -9
  359. package/src/plugins/builtin/options/types.ts +2 -0
  360. package/src/plugins/builtin/options/view.tsx +42 -12
  361. package/src/plugins/builtin/options-positioning/client.ts +30 -0
  362. package/src/plugins/builtin/options-positioning/headless.ts +74 -21
  363. package/src/plugins/builtin/options-positioning/index.tsx +4 -4
  364. package/src/plugins/builtin/options-positioning/model.ts +122 -14
  365. package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
  366. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  367. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  368. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  369. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  370. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  371. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  372. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  373. package/src/plugins/builtin/pe-band/model.ts +12 -49
  374. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  375. package/src/plugins/builtin/perps/cascade.ts +236 -0
  376. package/src/plugins/builtin/perps/client.ts +56 -2
  377. package/src/plugins/builtin/perps/freshness.ts +12 -10
  378. package/src/plugins/builtin/perps/headless.ts +65 -9
  379. package/src/plugins/builtin/perps/index.tsx +7 -5
  380. package/src/plugins/builtin/perps/live-rows.ts +10 -3
  381. package/src/plugins/builtin/perps/model.ts +18 -4
  382. package/src/plugins/builtin/perps/pane.tsx +32 -31
  383. package/src/plugins/builtin/perps/status-line.ts +25 -0
  384. package/src/plugins/builtin/plugin-module.ts +5 -1
  385. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  386. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  387. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  388. package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
  389. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  390. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  391. package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
  392. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  393. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  394. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
  395. package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
  396. package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
  397. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  398. package/src/plugins/builtin/rate-path/pane.tsx +2 -2
  399. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  400. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  401. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  402. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  403. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  404. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  405. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  406. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  407. package/src/plugins/builtin/research/event-model.ts +10 -8
  408. package/src/plugins/builtin/research/headless.ts +1 -1
  409. package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
  410. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  411. package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
  412. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  413. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  414. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  415. package/src/plugins/builtin/scanner/feed.ts +2 -2
  416. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  417. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  418. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  419. package/src/plugins/builtin/scanner/headless.ts +204 -35
  420. package/src/plugins/builtin/scanner/index.ts +40 -6
  421. package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
  422. package/src/plugins/builtin/scanner/live-feed.ts +168 -0
  423. package/src/plugins/builtin/scanner/live-headless.ts +138 -0
  424. package/src/plugins/builtin/scanner/live-model.ts +362 -0
  425. package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
  426. package/src/plugins/builtin/scanner/live-track.ts +186 -0
  427. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  428. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  429. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  430. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  431. package/src/plugins/builtin/sec/headless.ts +2 -1
  432. package/src/plugins/builtin/series-inventory.ts +3 -0
  433. package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
  434. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  435. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  436. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  437. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  438. package/src/plugins/builtin/shared/macro-releases.ts +65 -0
  439. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  440. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  441. package/src/plugins/builtin/shared/report-freshness.ts +3 -2
  442. package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
  443. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  444. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  445. package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
  446. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  447. package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
  448. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  449. package/src/plugins/builtin/themes/headless.ts +6 -4
  450. package/src/plugins/builtin/themes/model.ts +17 -8
  451. package/src/plugins/builtin/themes/pane.tsx +7 -7
  452. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  453. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  454. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  455. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  456. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  457. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  458. package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
  459. package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
  460. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  461. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  462. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  463. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  464. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  465. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  466. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  467. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  468. package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
  469. package/src/plugins/builtin/vol-surface/client.ts +9 -17
  470. package/src/plugins/builtin/vol-surface/headless.ts +63 -14
  471. package/src/plugins/builtin/vol-surface/index.tsx +2 -2
  472. package/src/plugins/builtin/vol-surface/model.ts +22 -7
  473. package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
  474. package/src/plugins/builtin/vol-surface/tables.ts +31 -3
  475. package/src/plugins/builtin/volatility/dvol.ts +46 -0
  476. package/src/plugins/builtin/volatility/index.tsx +2 -2
  477. package/src/plugins/builtin/volatility/pane.tsx +4 -4
  478. package/src/plugins/builtin/volatility/series.ts +26 -0
  479. package/src/plugins/builtin/world-indices/footer.ts +1 -1
  480. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  481. package/src/plugins/catalog-browser.ts +13 -8
  482. package/src/plugins/catalog-manifest.ts +2 -1
  483. package/src/plugins/marketplace/model.ts +18 -5
  484. package/src/portfolio/column-values.ts +5 -5
  485. package/src/portfolio/futures-positions.ts +6 -3
  486. package/src/portfolio/position-quantity.ts +15 -9
  487. package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
  488. package/src/remote/resources.ts +2 -1
  489. package/src/renderers/browser/app-services.ts +1 -7
  490. package/src/renderers/browser/config-host.ts +2 -55
  491. package/src/renderers/browser/main.tsx +7 -57
  492. package/src/renderers/dom/data-table/row.tsx +26 -0
  493. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  494. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  495. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  496. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  497. package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
  498. package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
  499. package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
  500. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  501. package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
  502. package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
  503. package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
  504. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  505. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
  506. package/src/renderers/electrobun/bun/index.ts +25 -1
  507. package/src/renderers/electrobun/shared/app-name.ts +30 -0
  508. package/src/renderers/electrobun/shared/protocol.ts +10 -0
  509. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  510. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
  511. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
  512. package/src/renderers/electrobun/view/main.tsx +24 -1
  513. package/src/renderers/electrobun/view/news-source.ts +3 -1
  514. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  515. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  516. package/src/renderers/opentui/start.tsx +5 -0
  517. package/src/renderers/opentui/toast-host.tsx +71 -43
  518. package/src/series/registry.ts +1 -0
  519. package/src/series/types.ts +6 -0
  520. package/src/series/validate.ts +1 -0
  521. package/src/sources/gloomberb-cloud/index.ts +1 -0
  522. package/src/sources/gloomberb-cloud/news.ts +47 -7
  523. package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
  524. package/src/sources/provider-router/batches.ts +10 -2
  525. package/src/sources/provider-router/financial-routes.ts +3 -3
  526. package/src/sources/provider-router/financials.ts +15 -3
  527. package/src/sources/provider-router/history.ts +28 -7
  528. package/src/sources/provider-router/index.ts +149 -10
  529. package/src/sources/provider-router/search.ts +13 -2
  530. package/src/state/app/context/index.tsx +3 -0
  531. package/src/state/ticker-cleanup.ts +50 -0
  532. package/src/sync/auth-transition.ts +19 -9
  533. package/src/sync/baseline.ts +10 -4
  534. package/src/sync/controller.ts +70 -19
  535. package/src/sync/core-contributors.ts +90 -31
  536. package/src/sync/react.ts +4 -1
  537. package/src/sync/types.ts +3 -0
  538. package/src/telemetry/stall-reports-dom.ts +38 -0
  539. package/src/telemetry/stall-reports-node.ts +75 -0
  540. package/src/telemetry/stall-reports.ts +214 -0
  541. package/src/telemetry/usage-counts.ts +0 -18
  542. package/src/test-support/preload.ts +14 -0
  543. package/src/theme/colors.ts +8 -0
  544. package/src/theme/themes.ts +10 -0
  545. package/src/tickers/company-name.ts +103 -3
  546. package/src/tickers/listing-venues.ts +38 -3
  547. package/src/tickers/search/bare-symbol.ts +36 -0
  548. package/src/tickers/search/index.ts +36 -7
  549. package/src/tickers/search/ranking.ts +10 -1
  550. package/src/tickers/search/types.ts +5 -0
  551. package/src/tickers/terminal-codes.ts +19 -0
  552. package/src/time-series/alignment.ts +1 -1
  553. package/src/time-series/base-series.ts +13 -5
  554. package/src/time-series/daily-valuation.ts +62 -0
  555. package/src/time-series/economic.ts +18 -4
  556. package/src/time-series/field-catalog.ts +2 -4
  557. package/src/time-series/filed-trailing-eps.ts +197 -0
  558. package/src/time-series/forward-valuation.ts +32 -0
  559. package/src/time-series/fundamentals.ts +198 -7
  560. package/src/time-series/hooks.ts +1 -0
  561. package/src/time-series/ice-credit.ts +3 -0
  562. package/src/time-series/price-comparison.ts +30 -2
  563. package/src/time-series/price-units.ts +72 -2
  564. package/src/time-series/resolve-context.ts +7 -1
  565. package/src/time-series/resolve.ts +116 -15
  566. package/src/time-series/series-presentation.ts +40 -6
  567. package/src/time-series/session-bar.ts +11 -0
  568. package/src/time-series/spec.ts +5 -3
  569. package/src/time-series/studies.ts +278 -74
  570. package/src/time-series/total-return.ts +160 -0
  571. package/src/time-series/transforms.ts +3 -3
  572. package/src/time-series/types.ts +27 -1
  573. package/src/time-series/valuation-releases.ts +39 -0
  574. package/src/types/data-provider.ts +9 -1
  575. package/src/types/desktop-window.ts +2 -0
  576. package/src/types/financials.ts +73 -2
  577. package/src/types/headless.ts +61 -5
  578. package/src/types/instrument.ts +3 -0
  579. package/src/types/news-source.ts +1 -0
  580. package/src/types/plugin.ts +10 -1
  581. package/src/ui/host.tsx +3 -0
  582. package/src/updater/command-names.ts +12 -7
  583. package/src/updater/failure.ts +4 -1
  584. package/src/utils/cli-output.ts +42 -1
  585. package/src/utils/crypto-pair.ts +44 -0
  586. package/src/utils/financial-statements.ts +6 -1
  587. package/src/utils/forward-dividend.ts +2 -0
  588. package/src/utils/fundamentals.ts +24 -3
  589. package/src/utils/fx-market-hours.ts +26 -3
  590. package/src/utils/main-thread-stall.ts +35 -2
  591. package/src/utils/market-capitalization.ts +24 -1
  592. package/src/utils/network-failure.ts +9 -6
  593. package/src/utils/option-expiry.ts +48 -1
  594. package/src/utils/options.ts +10 -0
  595. package/src/utils/plugin-proxy-hosts.json +1 -0
  596. package/src/utils/price-history.ts +12 -1
  597. package/src/utils/shell-word.ts +8 -0
  598. package/src/utils/trailing-dividend.ts +53 -4
  599. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
  600. package/src/renderers/browser/first-screen.ts +0 -193
@@ -9,17 +9,18 @@ import {
9
9
  quotedOnVenue,
10
10
  readAmbiguousCurrencyPair,
11
11
  readSharedCode,
12
+ savedRowOfBareSymbol,
12
13
  type ListingDeps,
13
14
  type ListingStore,
14
15
  type ListingVenue,
15
16
  } from "../tickers/listing-venues";
16
17
  import { findUsListingOfCompany, type UsListing } from "../tickers/us-listing";
17
- import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg } from "../tickers/company-name-listing";
18
+ import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg, type CompanyNameListing } from "../tickers/company-name-listing";
18
19
  import { movedAwayChange, startTickerChangeLookup, type MovedTickerChange } from "../tickers/ticker-changes";
19
20
  import { loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
20
21
  import { quotedListingName } from "../tickers/listing-name";
21
22
  import { cliStyles } from "../utils/cli-output";
22
- import { groupByCompany, sameCompany, shortCompanyName } from "../tickers/company-name";
23
+ import { groupByCompany, namesDepositaryReceipt, sameCompany, sameListedName, shortCompanyName } from "../tickers/company-name";
23
24
  import {
24
25
  canonicalExchange,
25
26
  exchangeCodeVenues,
@@ -34,7 +35,7 @@ import {
34
35
  import { withMarketData } from "./scoped-context";
35
36
  import { failWithFields } from "./errors";
36
37
  import { stripStrayEdges } from "./symbol-args";
37
- import { isNoProviderMessage, isProviderBusy } from "../sources/provider-errors";
38
+ import { isNoProviderMessage, isNoSourceAnswer, isProviderBusy } from "../sources/provider-errors";
38
39
  import { dashedCoinPair, MAJOR_COIN_NAMES } from "../utils/crypto-pair";
39
40
  import { boardCoinFor, primeBoardCoins } from "./board-coins";
40
41
  import { knownGapMessage } from "./known-gaps";
@@ -48,13 +49,16 @@ import {
48
49
  bloombergMarketHint,
49
50
  endsInLegalForm,
50
51
  isSearchOnlyVenue,
52
+ readLondonDottedSymbol,
51
53
  readNearMissListing,
52
54
  readTerminalListingCode,
53
55
  terminalCodeReadAs,
54
56
  unknownBloombergMarket,
57
+ type NearMissListing,
55
58
  type TerminalListingCode,
56
59
  } from "../tickers/terminal-codes";
57
60
  import { listingAbroadVenue } from "../utils/sec";
61
+ import { savedAbroadForBareSymbol, savedUsListingOf } from "../tickers/search/bare-symbol";
58
62
  import { listingSuffixCandidate, listingSuffixVenue, tickerHasListingSuffix } from "../sources/listing-symbols";
59
63
  import { notTheCoinNote } from "./commands/crypto-hints";
60
64
 
@@ -336,6 +340,15 @@ export function isNoProviderError(error: unknown): boolean {
336
340
  return isNoProviderMessage(error instanceof Error ? error.message : String(error ?? ""));
337
341
  }
338
342
 
343
+ /**
344
+ * The router's answer that no source served a request because one failed (a
345
+ * timeout, a rate limit, a refused session) rather than missed: it says
346
+ * nothing about whether the symbol exists, unlike "Not a ticker".
347
+ */
348
+ export function isUnsettledNoProvider(error: unknown): boolean {
349
+ return isNoProviderError(error) && !isNoSourceAnswer(error);
350
+ }
351
+
339
352
  const MAX_OTHER_LISTINGS = 5;
340
353
 
341
354
  type BareListing = Pick<CliListing, "symbol" | "exchange"> & Partial<Pick<CliListing, "saved">>;
@@ -357,33 +370,42 @@ function listsSeveralCompanies(venues: readonly ListingVenue[]): boolean {
357
370
  return groupByCompany(venues, (venue) => venue.name).length > 1;
358
371
  }
359
372
 
360
- /** Every other company that lists the symbol once, at its first venue in search order. */
373
+ /**
374
+ * Every other company or fund that lists the symbol, once, at its first venue
375
+ * in search order. A venue is another's only when its name says so: one with
376
+ * no name, or a depositary receipt named unlike the company (a CEDEAR's short
377
+ * form of its fund's name), is left out rather than guessed at, and one
378
+ * fund's venues are one fund however each words it (`sameListedName`).
379
+ */
361
380
  function otherCompanies(venues: readonly ListingVenue[], company: string): ListingVenue[] {
362
- return groupByCompany(venues, (venue) => venue.name)
363
- .filter((group) => !group.some((venue) => sameCompany(venue.name, company)))
381
+ if (!company.trim()) return [];
382
+ const known = venues.filter((venue) => !namesDepositaryReceipt(venue.name) || sameListedName(venue.name, company));
383
+ return groupByCompany(known, (venue) => venue.name, sameListedName)
384
+ .filter((group) => !group.some((venue) => sameListedName(venue.name, company)))
364
385
  .map((group) => group[0]!);
365
386
  }
366
387
 
367
388
  /**
368
- * "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)": the listing a bare
369
- * symbol several companies list was read on, and where the others list it.
370
- * Null when one company lists it, so a symbol that means one thing says
371
- * nothing.
389
+ * "CAR = Avis Budget Group, NASDAQ (different companies on XETRA, ASX)": the
390
+ * listing a bare symbol several companies list was read on, and where the
391
+ * others list it. Null when no other company is known to list it, so a symbol
392
+ * that means one thing (XLK, and its receipt in Buenos Aires) says nothing.
372
393
  */
373
394
  export function listingChoiceNote(
374
395
  symbol: string,
375
396
  chosen: { exchange: string; name?: string | null },
376
397
  venues: readonly ListingVenue[],
377
398
  ): string | null {
378
- if (!chosen.exchange || !listsSeveralCompanies(venues)) return null;
399
+ if (!chosen.exchange) return null;
379
400
  const chosenVenue = venues.find((venue) => venue.exchange === chosen.exchange);
380
401
  const name = chosen.name?.trim() || chosenVenue?.name || "";
381
402
  const others = otherCompanies(venues, chosenVenue?.name || name).map((venue) => venue.exchange)
382
403
  .filter((exchange) => exchange !== chosen.exchange);
383
404
  if (others.length === 0) return null;
384
405
  const more = others.length - MAX_OTHER_LISTINGS;
385
- const also = `${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloom search ${symbol}` : ""}`;
386
- return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${chosen.exchange} (also ${also})`;
406
+ const named = `${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloom search ${symbol}` : ""}`;
407
+ const which = others.length === 1 ? "a different company" : "different companies";
408
+ return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${chosen.exchange} (${which} on ${named})`;
387
409
  }
388
410
 
389
411
  // Quotes of these never name a company's listing, so they are never a choice between companies.
@@ -407,34 +429,50 @@ export function listingReadNotes(listing: Pick<ListingArg, "readAs">): string[]
407
429
  return listing.readAs ? [listing.readAs] : [];
408
430
  }
409
431
 
432
+ /**
433
+ * The coin line for a bare coin ticker (SOL, ETH) a command read as a listing that is
434
+ * not the coin, from the quote it was read on (`notTheCoinNote`). A list, so a command can
435
+ * spread it into its warnings. A named listing (`ETH:ARCA`, `--exchange`) asked for that
436
+ * listing on purpose and gets none.
437
+ */
438
+ export function coinListingNotes(
439
+ listing: Pick<CliListing, "symbol" | "exchange">,
440
+ quote: Pick<Quote, "name" | "instrumentType" | "stale" | "delisting"> | null | undefined,
441
+ ): string[] {
442
+ const note = listing.exchange ? null : notTheCoinNote(listing.symbol, quote);
443
+ return note ? [note] : [];
444
+ }
445
+
410
446
  /**
411
447
  * `listingChoiceNote` for a listing a command resolved, from the quote it was
412
448
  * read on: only a bare symbol went where the data service chose. A named
413
449
  * listing whose code names several venues says which it was read as
414
- * (`listingReadNotes`). A list, so a command can spread it into its warnings.
450
+ * (`listingReadNotes`). A bare coin ticker read as another listing ends with
451
+ * the coin's pair (`coinListingNotes`). A list, so a command can spread it into
452
+ * its warnings.
415
453
  */
416
454
  export async function listingChoiceNotes(
417
455
  listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "readAs">,
418
456
  deps: ListingDeps,
419
- quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType"> | null | undefined,
457
+ quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType" | "stale" | "delisting"> | null | undefined,
420
458
  /** The venues `loadBareVenues` already started; looked up here when not given. */
421
459
  venues?: Promise<ListingVenue[]> | null,
422
460
  ): Promise<string[]> {
423
- if (!isBareListing(listing)) return listingReadNotes(listing);
424
- if (!quote || UNLISTED_TYPES.has(quote.instrumentType?.trim().toUpperCase() ?? "")) return [];
461
+ const coin = coinListingNotes(listing, quote);
462
+ if (!isBareListing(listing)) return [...listingReadNotes(listing), ...coin];
463
+ if (!quote || UNLISTED_TYPES.has(quote.instrumentType?.trim().toUpperCase() ?? "")) return coin;
425
464
  const exchange = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
426
- if (!exchange) return [];
465
+ if (!exchange) return coin;
427
466
  const listed = await (venues ?? loadBareVenues(listing, deps) ?? []);
428
467
  const note = listingChoiceNote(listing.symbol, { exchange, name: quote.name }, listed);
429
- return note ? [note] : [];
468
+ return [...note ? [note] : [], ...coin];
430
469
  }
431
470
 
432
471
  /**
433
472
  * `listingChoiceNotes` for a command with no quote of its own to read the
434
473
  * listing from (`fn`): it loads the listing's quote, and only for a bare
435
474
  * symbol, so a named or saved listing costs nothing. A bare coin ticker read
436
- * as a fund (BTC, the Grayscale trust) ends with `quote`'s note naming the
437
- * coin's pair.
475
+ * as another listing (BTC, the Grayscale trust) ends with the coin's pair.
438
476
  */
439
477
  export async function quotedListingChoiceNotes(
440
478
  listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "request" | "readAs">,
@@ -442,9 +480,7 @@ export async function quotedListingChoiceNotes(
442
480
  ): Promise<string[]> {
443
481
  const venues = loadBareVenues(listing, deps);
444
482
  if (!venues) return listingReadNotes(listing);
445
- const quote = await loadListingQuote(deps.dataProvider, listing);
446
- const coin = notTheCoinNote(listing.symbol, quote);
447
- return [...await listingChoiceNotes(listing, deps, quote, venues), ...coin ? [coin] : []];
483
+ return listingChoiceNotes(listing, deps, await loadListingQuote(deps.dataProvider, listing), venues);
448
484
  }
449
485
 
450
486
  /**
@@ -561,31 +597,43 @@ export function futuresTryLine(symbol: string, request: ListingDataRequest): str
561
597
  return `${future.symbol} ${future.name} future${retry ? ` (try: ${retry})` : ""}`;
562
598
  }
563
599
 
600
+ /** A venue's listing as its key names it: under its own symbol there (BP:LSE for `BP.`), else the one asked for. */
601
+ function venueKey(symbol: string, venue: Pick<ListingVenue, "exchange" | "symbol">): string {
602
+ return `${venue.symbol ?? symbol}:${venue.exchange}`;
603
+ }
604
+
564
605
  function describeOtherListings(
565
606
  listing: BareListing,
566
607
  { exchange, resolved, others, choose }: BareListingVenues,
567
608
  request: ListingDataRequest,
609
+ /** Listings just asked that had none: named as having none, never offered to try. */
610
+ without: readonly ListingVenue[] = [],
568
611
  ): OtherListingsMessage | null {
569
612
  const future = futuresTryLine(listing.symbol, request);
570
- if (others.length === 0 && !future) return null;
613
+ const offered = others.filter((venue) => !without.includes(venue));
614
+ if (offered.length === 0 && !future && without.length === 0) return null;
571
615
  const name = resolved?.name || listing.saved?.metadata.name?.trim() || "";
572
- const lines = others.slice(0, MAX_OTHER_LISTINGS).map((venue) => {
573
- const key = `${listing.symbol}:${venue.exchange}`;
616
+ const lines = offered.slice(0, MAX_OTHER_LISTINGS).map((venue) => {
617
+ const key = venueKey(listing.symbol, venue);
574
618
  const retry = retryCommand(request, key);
575
619
  return `${key}${venue.name ? ` ${venue.name}` : ""}${retry ? ` (try: ${retry})` : ""}`;
576
620
  });
577
- if (others.length > MAX_OTHER_LISTINGS) lines.push(`and ${others.length - MAX_OTHER_LISTINGS} more: gloom search ${listing.symbol}`);
621
+ if (offered.length > MAX_OTHER_LISTINGS) lines.push(`and ${offered.length - MAX_OTHER_LISTINGS} more: gloom search ${listing.symbol}`);
578
622
  if (future) lines.push(future);
579
- const heading = choose ? "Listings" : "Other listings";
623
+ // Every other listing was asked and had nothing: they are named as such, with nothing to try.
624
+ const heading = choose ? "Listings" : lines.length === 0 ? `No ${request.noun} on its other listings either` : "Other listings";
625
+ const named = lines.length === 0 ? without : offered;
626
+ const details = lines.length === 0 ? `${heading}: ${without.map((venue) => venueKey(listing.symbol, venue)).join(", ")}.`
627
+ : lines.length === 1 ? `${heading}: ${lines[0]}` : [`${heading}:`, ...lines.map((line) => ` ${line}`)].join("\n");
580
628
  return {
581
629
  // The symbol went to no venue, so none of them is said to lack the data.
582
630
  message: choose
583
631
  ? `Choose an exchange for ${listing.symbol}.`
584
632
  : `${listing.symbol} resolved to ${name ? `${name} (${exchange})` : exchange}, which has no ${request.noun}.`,
585
- details: lines.length === 1 ? `${heading}: ${lines[0]}` : [`${heading}:`, ...lines.map((line) => ` ${line}`)].join("\n"),
633
+ details,
586
634
  heading,
587
- listings: [...others.map((venue) => `${listing.symbol}:${venue.exchange}`), ...future ? [futuresContractByCode(listing.symbol)!.symbol] : []],
588
- choices: others.slice(0, MAX_OTHER_LISTINGS).map((venue) => ({ key: `${listing.symbol}:${venue.exchange}`, name: venue.name || null })),
635
+ listings: [...named.map((venue) => venueKey(listing.symbol, venue)), ...future ? [futuresContractByCode(listing.symbol)!.symbol] : []],
636
+ choices: offered.slice(0, MAX_OTHER_LISTINGS).map((venue) => ({ key: venueKey(listing.symbol, venue), name: venue.name || null })),
589
637
  choose,
590
638
  };
591
639
  }
@@ -610,27 +658,44 @@ async function otherListingsMessage(
610
658
  return venues && describeOtherListings(listing, venues, request);
611
659
  }
612
660
 
613
- /** The listing on one venue, as `SYM:EXCH` names it. */
614
- function namedListing(listing: CliListing, exchange: string): CliListing {
615
- const key = `${listing.symbol}:${exchange}`;
661
+ /** The listing on one venue, as `SYM:EXCH` names it, under the venue's own symbol when search lists one. */
662
+ function namedListing(listing: CliListing, venue: Pick<ListingVenue, "exchange" | "symbol">): CliListing {
663
+ const { exchange } = venue;
664
+ const symbol = venue.symbol ?? listing.symbol;
665
+ const key = `${symbol}:${exchange}`;
616
666
  const saved = canonicalExchange(listing.saved?.metadata.exchange) === exchange ? listing.saved : null;
617
- return { symbol: listing.symbol, exchange, key, saved, request: { symbol: key, exchange } };
667
+ return { symbol, exchange, key, saved, request: { symbol: key, exchange } };
618
668
  }
619
669
 
670
+ /**
671
+ * Each listing's quote, null where it has none; `undefined` where the request
672
+ * itself failed, which says nothing of whether it has one.
673
+ */
620
674
  async function quoteListings(
621
675
  dataProvider: DataProvider,
622
676
  listings: readonly CliListing[],
623
677
  refresh: boolean,
624
- ): Promise<Array<Quote | null>> {
678
+ datesChangeBasis = false,
679
+ ): Promise<Array<Quote | null | undefined>> {
625
680
  if (listings.length === 0) return [];
626
681
  const targets = listings.map((listing) => listing.request);
627
682
  if (dataProvider.getQuotesBatch) {
628
- const results = await dataProvider.getQuotesBatch(targets, { forceRefresh: refresh }).catch(logFailure("cli.listing-arg", "Read listing quote batch failed", []));
629
- return targets.map((target, index) => (results.find((result) => result.target === target) ?? results[index])?.quote ?? null);
683
+ const results = await dataProvider.getQuotesBatch(targets, { forceRefresh: refresh, ...(datesChangeBasis ? { datesChangeBasis } : {}) })
684
+ .catch(logFailure("cli.listing-arg", "Read listing quote batch failed", null));
685
+ if (!results) return targets.map(() => undefined);
686
+ return targets.map((target, index) => {
687
+ const result = results.find((entry) => entry.target === target) ?? results[index];
688
+ // No source quotes it, which is an answer; any other failure is none.
689
+ return result?.quote ?? (result && (result.error == null || isNoProviderError(result.error)) ? null : undefined);
690
+ });
630
691
  }
631
692
  return Promise.all(targets.map((target) => dataProvider.getQuote(target.symbol, target.exchange, {
632
693
  cacheMode: refresh ? "refresh" : "default",
633
- }).catch(logFailure("cli.listing-arg", "Read listing quote failed", null))));
694
+ ...(datesChangeBasis ? { datesChangeBasis } : {}),
695
+ }).catch((error: unknown) => {
696
+ logFailure("cli.listing-arg", "Read listing quote failed")(error);
697
+ return isNoProviderError(error) ? null : undefined;
698
+ })));
634
699
  }
635
700
 
636
701
  export type BareListingQuote =
@@ -661,7 +726,7 @@ export async function quoteBareListing(
661
726
  listing: CliListing,
662
727
  deps: ListingDeps,
663
728
  request: ListingDataRequest,
664
- { refresh = false }: { refresh?: boolean } = {},
729
+ { refresh = false, datesChangeBasis = false }: { refresh?: boolean; datesChangeBasis?: boolean } = {},
665
730
  ): Promise<BareListingQuote | null> {
666
731
  const venues = await bareListingVenues(listing, deps);
667
732
  if (!venues) return null;
@@ -669,17 +734,19 @@ export async function quoteBareListing(
669
734
  const other = describeOtherListings(listing, venues, request);
670
735
  return other && { kind: "missing", ...other };
671
736
  }
672
- const first = [namedListing(listing, venues.exchange)];
673
- const rest = venues.others.map((venue) => namedListing(listing, venue.exchange));
737
+ const first = [namedListing(listing, venues.resolved ?? { exchange: venues.exchange })];
738
+ const rest = venues.others.map((venue) => namedListing(listing, venue));
674
739
  let tried = first;
675
- let quotes = await quoteListings(deps.dataProvider, first, refresh);
740
+ let quotes = await quoteListings(deps.dataProvider, first, refresh, datesChangeBasis);
676
741
  if (!quotes[0] && rest.length > 0) {
677
742
  tried = [...first, ...rest];
678
- quotes = [...quotes, ...await quoteListings(deps.dataProvider, rest, refresh)];
743
+ quotes = [...quotes, ...await quoteListings(deps.dataProvider, rest, refresh, datesChangeBasis)];
679
744
  }
680
745
  const found = quotes.findIndex(Boolean);
681
746
  if (found < 0) {
682
- const other = describeOtherListings(listing, venues, request);
747
+ // A listing just asked that had no quote is no listing to try.
748
+ const without = venues.others.filter((_venue, index) => quotes[index + 1] === null);
749
+ const other = describeOtherListings(listing, venues, request, without);
683
750
  return other && { kind: "missing", ...other };
684
751
  }
685
752
  const answer = tried[found]!;
@@ -692,7 +759,8 @@ export async function quoteBareListing(
692
759
  kind: "quoted",
693
760
  listing: answer,
694
761
  quote: quotes[found]!,
695
- note: chosenListingNote(listing.symbol, answer.exchange, others, without),
762
+ // Under the venue's own symbol (BP:LSE for `BP.`), the note names the listing, not only its venue.
763
+ note: chosenListingNote(listing.symbol, answer.symbol === listing.symbol ? answer.exchange : answer.key, others, without),
696
764
  };
697
765
  }
698
766
 
@@ -703,6 +771,8 @@ export interface ReadListing {
703
771
  exchange: string;
704
772
  /** The listing's company, from its quote. */
705
773
  name: string | null;
774
+ /** A bare coin ticker read as a listing that is not the coin (`coinListingNotes`); null otherwise. */
775
+ coinNote: string | null;
706
776
  }
707
777
 
708
778
  /**
@@ -723,10 +793,14 @@ export async function readBareListing(
723
793
  const listing = await resolveCliListing(symbol, null, deps);
724
794
  if (listing.exchange) return null;
725
795
  const answer = await loadListingQuoteAnswer(deps.dataProvider, listing);
726
- if (answer.quote) return { symbol: listing.symbol, exchange: listing.request.exchange, name: answer.quote.name?.trim() || null };
796
+ if (answer.quote) {
797
+ return { symbol: listing.symbol, exchange: listing.request.exchange, name: answer.quote.name?.trim() || null, coinNote: coinListingNotes(listing, answer.quote)[0] ?? null };
798
+ }
727
799
  if (!isNoProviderError(answer.error)) return null;
728
800
  const other = await quoteBareListing(listing, deps, request, { refresh });
729
- if (other?.kind === "quoted") return { symbol: listing.symbol, exchange: other.listing.exchange, name: other.quote.name?.trim() || null };
801
+ if (other?.kind === "quoted") {
802
+ return { symbol: listing.symbol, exchange: other.listing.exchange, name: other.quote.name?.trim() || null, coinNote: coinListingNotes(listing, other.quote)[0] ?? null };
803
+ }
730
804
  if (other?.choose) throw new ListingArgError(other.message, other.details, other.choices);
731
805
  return null;
732
806
  }
@@ -948,6 +1022,10 @@ export async function loadSavedListingOf(
948
1022
  for (const listing of listings) {
949
1023
  if (found.some((entry) => entry.listing.key === listing.key)) continue;
950
1024
  const ticker = await loadSavedListing(store, listing);
1025
+ // A bare symbol's US listing comes before a row abroad saved under its bare key (SHEL, saved from SHEL.L).
1026
+ if (ticker && !listing.exchange && savedAbroadForBareSymbol(listing.symbol, ticker)) {
1027
+ add(listing, savedUsListingOf(listing.symbol, await store.loadAllTickers()));
1028
+ }
951
1029
  add(listing, ticker);
952
1030
  // A suffixed key as typed may be saved with a venue (7203.T:JPX, GARAN.IS:XNYS): any row of that symbol.
953
1031
  if (!ticker && !listing.exchange && tickerHasListingSuffix(listing.symbol)) {
@@ -1087,7 +1165,9 @@ export async function resolveCliListing(
1087
1165
  const terminal = named ? null : read;
1088
1166
  // "AAPL GP" is written as a code, with a market code no venue answers to and no company's name: one entry, never AAPL and GP.
1089
1167
  if (!terminal && !named) await refuseUnknownMarketCode(typed, deps);
1090
- let parsed = named ?? parseListingArg(raw, exchangeOption, { ...options, terminalCode: terminal });
1168
+ // `BP.` is London's code for BP: its London line where London lists it, and `GE.` a US ticker with a stray dot.
1169
+ const dotted = terminal || named || exchangeOption?.trim() ? null : readLondonDottedSymbol(typed);
1170
+ let parsed = named ?? (dotted ? await londonDottedListing(dotted, deps) : parseListingArg(raw, exchangeOption, { ...options, terminalCode: terminal }));
1091
1171
  if (!parsed.symbol) throw new ListingArgError("Ticker symbol is required.");
1092
1172
  // $VIX is Schwab's code for ^VIX, unless a security trades as VIX: $AAPL is a cashtag.
1093
1173
  const index = named || parsed.exchange ? null : await readDollarIndexCode(raw, deps);
@@ -1115,7 +1195,9 @@ export async function resolveCliListing(
1115
1195
  parsed = market;
1116
1196
  }
1117
1197
  let listing = await settleSharedCode(parsed, deps);
1118
- const saved = await loadSavedListing(deps.store, listing) ?? (typedKey ? await loadSuffixedRow(deps.store, typedKey, listing.exchange) : null);
1198
+ const found = await loadSavedListing(deps.store, listing) ?? (typedKey ? await loadSuffixedRow(deps.store, typedKey, listing.exchange) : null);
1199
+ // A bare symbol is its US listing when it has one, whatever row abroad holds the bare key (SHEL saved from SHEL.L).
1200
+ const saved = listing.exchange ? found : await savedRowOfBareSymbol(listing.symbol, found, deps);
1119
1201
  // A code nothing could read stays the venue it is saved on.
1120
1202
  const savedExchange = canonicalExchange(saved?.metadata.exchange);
1121
1203
  if (listing.sharedCode && savedExchange) listing = { symbol: listing.symbol, exchange: savedExchange, key: `${listing.symbol}:${savedExchange}` };
@@ -1129,6 +1211,21 @@ export async function resolveCliListing(
1129
1211
  };
1130
1212
  }
1131
1213
 
1214
+ /**
1215
+ * A two-letter symbol written with London's trailing dot (`BP.`, `RR.`) as its
1216
+ * London line, said as "Read BP. as BP:LSE.", where search lists it on LSE or
1217
+ * its quote there says it trades. Elsewhere the dot was a slip after the
1218
+ * ticker, dropped as a stray edge is (`GE.` is GE).
1219
+ */
1220
+ async function londonDottedListing(dotted: NearMissListing, deps: ListingDeps): Promise<ListingArg> {
1221
+ const london = { symbol: dotted.symbol, exchange: "LSE" };
1222
+ const venues = await listingVenues(dotted.symbol, deps).catch(logFailure("cli.listing-arg", "Read listing venues failed", []));
1223
+ const listed = venues.some((venue) => venue.exchange === london.exchange) || await quotedOnVenue(london, deps);
1224
+ if (!listed) return parseListingArg(dotted.symbol);
1225
+ const key = `${london.symbol}:${london.exchange}`;
1226
+ return { ...parseListingArg(key), readAs: `Read ${dotted.typed} as ${key}.` };
1227
+ }
1228
+
1132
1229
  /**
1133
1230
  * Fails a line written as a Bloomberg code whose market code no venue answers
1134
1231
  * to (`AAPL GP`), suggesting the code of the symbol's own listing (`AAPL
@@ -1224,17 +1321,23 @@ const ONE_WORD_NAME = /^\p{L}[\p{L}\p{N}&'’.-]*$/u;
1224
1321
  * for anything else, and when search names no company or is too busy to say.
1225
1322
  */
1226
1323
  export async function oneWordCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<ListingArg | null> {
1324
+ const found = await findOneWordCompany(typed, deps);
1325
+ return found ? companyListingArg(typed, found) : null;
1326
+ }
1327
+
1328
+ /** The company `oneWordCompanyName` reads a word as, before it is read as a listing. */
1329
+ async function findOneWordCompany(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<CompanyNameListing | null> {
1227
1330
  const word = typed.trim();
1228
1331
  if (!ONE_WORD_NAME.test(word) || knownGapMessage(word) || isMajorCoinName(word)) return null;
1229
- const named = await readCompanyName(word, deps, { oneWord: true }).catch((error: unknown) => {
1332
+ const found = await findCompanyName(word, deps, { oneWord: true }).catch((error: unknown) => {
1230
1333
  if (!isProviderBusy(error)) throw error;
1231
1334
  // Intentional ignore: a busy search says nothing about the name, so the word keeps its own "Not a ticker".
1232
1335
  return null;
1233
1336
  });
1234
- if (!named) return null;
1337
+ if (!found) return null;
1235
1338
  // The board is read only for a word that names a company, and once for the run (`primeBoardCoins`).
1236
1339
  if (/^[A-Za-z0-9]+$/.test(word)) await primeBoardCoins([`Not a ticker: ${word.toUpperCase()}.`]);
1237
- return boardCoinFor(word) ? null : named;
1340
+ return boardCoinFor(word) ? null : found;
1238
1341
  }
1239
1342
 
1240
1343
  function isMajorCoinName(word: string): boolean {
@@ -1242,9 +1345,16 @@ function isMajorCoinName(word: string): boolean {
1242
1345
  return [...MAJOR_COIN_NAMES.values()].some((name) => name.toLowerCase() === wanted);
1243
1346
  }
1244
1347
 
1348
+ function findCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<CompanyNameListing | null> {
1349
+ return findListingOfCompanyName(deps.dataProvider, typed, { meant: true, quoted: true, ...options });
1350
+ }
1351
+
1245
1352
  async function readCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<ListingArg | null> {
1246
- const found = await findListingOfCompanyName(deps.dataProvider, typed, { meant: true, quoted: true, ...options });
1247
- if (!found) return null;
1353
+ const found = await findCompanyName(typed, deps, options);
1354
+ return found ? companyListingArg(typed, found) : null;
1355
+ }
1356
+
1357
+ function companyListingArg(typed: string, found: CompanyNameListing): ListingArg {
1248
1358
  const key = `${found.symbol}:${found.exchange}`;
1249
1359
  // A fund or an index carrying the name asks about the company's stock: "Did you mean JPM (JPMorgan Chase & Co.)?".
1250
1360
  const meant = found.meant ? { symbol: found.meant.symbol, name: found.meant.name } : null;
@@ -1252,6 +1362,35 @@ async function readCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvi
1252
1362
  return { ...parseListingArg(key), readAs: `Read "${typed.trim()}" as ${key} (${found.name}).${question}`, ...meant ? { meant } : {} };
1253
1363
  }
1254
1364
 
1365
+ /**
1366
+ * The listing search names for a company's name typed where a ticker goes,
1367
+ * as a sentence offers it: `PLTR (Palantir Technologies Inc.)`, a listing
1368
+ * abroad with its venue (`SIE:XETRA (Siemens AG)`), a fund or an index that
1369
+ * carries the name as the company's stock it means. Null when the words name
1370
+ * no company, search is busy, or the listing found is the word itself: a
1371
+ * real ticker is never a company's name. Never throws.
1372
+ */
1373
+ export async function companyNameSuggestion(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<{ symbol: string; name: string } | null> {
1374
+ const words = typed.trim().replace(/^\$/, "");
1375
+ if (!words) return null;
1376
+ try {
1377
+ const found = isCompanyNameInput(words) ? await findCompanyName(words, deps).catch(busyAsMiss) : await findOneWordCompany(words, deps);
1378
+ const listing = found?.meant ?? found;
1379
+ if (!listing || listing.symbol.toUpperCase() === words.toUpperCase()) return null;
1380
+ return { symbol: isUsListingExchange(listing.exchange) ? listing.symbol : `${listing.symbol}:${listing.exchange}`, name: listing.name };
1381
+ } catch (error) {
1382
+ logFailure("cli.listing-arg", "Search a company name for a suggestion failed")(error);
1383
+ return null;
1384
+ }
1385
+ }
1386
+
1387
+ /** A search too busy to answer is no company, as a name that matches none. */
1388
+ function busyAsMiss(error: unknown): null {
1389
+ if (!isProviderBusy(error)) throw error;
1390
+ // Intentional ignore: a busy search says nothing about the name, so no company is suggested.
1391
+ return null;
1392
+ }
1393
+
1255
1394
  /** `resolveCliListing` for a command: a bad exchange fails the command with its message. */
1256
1395
  export async function requireCliListing(
1257
1396
  raw: string,
@@ -0,0 +1,68 @@
1
+ import { mkdir, stat, writeFile } from "fs/promises";
2
+ import { homedir } from "os";
3
+ import { posix, win32 } from "path";
4
+
5
+ interface OutputPathContext {
6
+ cwd?: string;
7
+ home?: string;
8
+ platform?: NodeJS.Platform;
9
+ }
10
+
11
+ /** `C:\Users\me\out.csv`, `C:/out.csv`, `\\server\share\out.csv`: a path only Windows reads. */
12
+ const WINDOWS_PATH = /^(?:[A-Za-z]:[\\/]|\\\\)/;
13
+
14
+ /**
15
+ * The absolute path `--output <path>` names, in any form a shell passes it:
16
+ * relative to the folder the command runs in, absolute, or under the home
17
+ * folder (`~/Desktop/rv.csv`, which a shell leaves as typed after
18
+ * `--output=`). A Windows path is read as one on Windows; anywhere else it
19
+ * is refused, since it would write a file named after the whole path.
20
+ */
21
+ export function resolveOutputPath(raw: string, context: OutputPathContext = {}): string {
22
+ const platform = context.platform ?? process.platform;
23
+ const paths = platform === "win32" ? win32 : posix;
24
+ const typed = raw.trim();
25
+ if (!typed) throw new Error("--output needs a file to write, such as --output report.csv.");
26
+ if (platform !== "win32" && WINDOWS_PATH.test(typed)) {
27
+ throw new Error(`${typed} is a Windows path. Write a path on this computer, such as --output ~/Desktop/report.csv.`);
28
+ }
29
+ const home = context.home ?? homedir();
30
+ const homeRelative = /^~(?:$|[\\/])/.test(typed);
31
+ const expanded = homeRelative ? paths.join(home, typed.slice(1)) : typed;
32
+ return paths.resolve(context.cwd ?? process.cwd(), expanded);
33
+ }
34
+
35
+ function errorCode(error: unknown): string | undefined {
36
+ return error && typeof error === "object" && "code" in error ? String((error as { code: unknown }).code) : undefined;
37
+ }
38
+
39
+ function isFolder(path: string, paths: typeof posix): Error {
40
+ return new Error(`Cannot write ${path}: it is a folder. Name a file in it, such as ${paths.join(path, "report.csv")}.`);
41
+ }
42
+
43
+ /** Fails when `path` names a folder: checked before a report loads, so it stops at once. */
44
+ export async function checkOutputPath(path: string, platform: NodeJS.Platform = process.platform): Promise<void> {
45
+ const existing = await stat(path).catch(() => {
46
+ // Intentional ignore: a path that does not exist yet is the usual case, and writing reports any real failure.
47
+ return null;
48
+ });
49
+ if (existing?.isDirectory()) throw isFolder(path, platform === "win32" ? win32 : posix);
50
+ }
51
+
52
+ /**
53
+ * Writes a command's output to `path`, creating the folders it names as
54
+ * `shot --output` does, and failing with what to do when it cannot.
55
+ */
56
+ export async function writeOutputFile(path: string, contents: string, platform: NodeJS.Platform = process.platform): Promise<void> {
57
+ const paths = platform === "win32" ? win32 : posix;
58
+ try {
59
+ await mkdir(paths.dirname(path), { recursive: true });
60
+ await writeFile(path, contents);
61
+ } catch (error) {
62
+ const code = errorCode(error);
63
+ if (code === "EISDIR") throw isFolder(path, paths);
64
+ if (code === "EACCES" || code === "EPERM") throw new Error(`Cannot write ${path}: permission denied.`);
65
+ if (code === "ENOTDIR" || code === "EEXIST") throw new Error(`Cannot write ${path}: a file stands where one of its folders would be.`);
66
+ throw new Error(`Cannot write ${path}: ${error instanceof Error ? error.message : String(error)}`);
67
+ }
68
+ }
@@ -2,6 +2,7 @@ import type {
2
2
  HeadlessPaneArgumentDef,
3
3
  HeadlessPaneDefinition,
4
4
  HeadlessPaneOptionDef,
5
+ HeadlessPaneReference,
5
6
  PaneDef,
6
7
  PaneTemplateDef,
7
8
  } from "../../types/plugin";
@@ -26,6 +27,8 @@ export interface PaneFunctionCapability {
26
27
  screenshotReadiness: PaneFunctionScreenshotReadiness;
27
28
  dataRequirements: string[];
28
29
  limitations: string[];
30
+ /** Lists the catalog prints under the options (EQS's sector and industry names). */
31
+ reference?: HeadlessPaneReference[];
29
32
  options: PaneFunctionOptionDef[];
30
33
  /** A rendered view that refuses any option it does not declare (PaneDef.reportOptionsOnly), as a headless report does. */
31
34
  strictOptions?: boolean;