@gloom-sh/terminal 0.18.5 → 0.19.1

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 (570) 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 +163 -8
  5. package/src/api-client/app-attention.ts +3 -0
  6. package/src/api-client/beneficial-owners.ts +3 -0
  7. package/src/api-client/company-kpis.ts +3 -0
  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 +19 -7
  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/hiring.ts +3 -0
  14. package/src/api-client/index.ts +15 -0
  15. package/src/api-client/ipo.ts +24 -1
  16. package/src/api-client/market-discovery.ts +10 -0
  17. package/src/api-client/market-movers.ts +7 -0
  18. package/src/api-client/members.ts +1 -1
  19. package/src/api-client/mna.ts +3 -0
  20. package/src/api-client/paths.ts +9 -1
  21. package/src/api-client/perps.ts +43 -0
  22. package/src/api-client/research-activity.ts +3 -2
  23. package/src/api-client/roadmap.ts +85 -0
  24. package/src/api-client/types.ts +82 -2
  25. package/src/app/pane-runtime/plugin-bindings.ts +9 -5
  26. package/src/app/runtime/broker-import.ts +0 -2
  27. package/src/app/runtime/plugin-bindings.ts +73 -56
  28. package/src/architecture/core-builtin-allowlist.json +2 -2
  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/market.ts +107 -38
  35. package/src/cli/commands/news-search.ts +22 -0
  36. package/src/cli/commands/options-chain.ts +48 -7
  37. package/src/cli/commands/options-leaps.ts +9 -3
  38. package/src/cli/commands/overview.ts +147 -32
  39. package/src/cli/commands/search.ts +21 -1
  40. package/src/cli/commands/themes.ts +11 -2
  41. package/src/cli/commands/ticker.ts +87 -36
  42. package/src/cli/company-name-hint.ts +41 -0
  43. package/src/cli/earnings-calendar.ts +1 -1
  44. package/src/cli/errors.ts +25 -2
  45. package/src/cli/glossary.ts +31 -2
  46. package/src/cli/history-rows.ts +57 -9
  47. package/src/cli/index-roots.ts +0 -7
  48. package/src/cli/index.ts +2 -0
  49. package/src/cli/listing-arg.ts +235 -60
  50. package/src/cli/output-file.ts +68 -0
  51. package/src/cli/pane-functions/capabilities.ts +4 -0
  52. package/src/cli/pane-functions/catalog.ts +80 -7
  53. package/src/cli/pane-functions/discovery.ts +21 -1
  54. package/src/cli/pane-functions/dom.ts +13 -3
  55. package/src/cli/pane-functions/freshness.ts +93 -4
  56. package/src/cli/pane-functions/function-help.ts +20 -10
  57. package/src/cli/pane-functions/headless.ts +17 -7
  58. package/src/cli/pane-functions/index.ts +54 -16
  59. package/src/cli/pane-functions/market-session.ts +66 -4
  60. package/src/cli/pane-functions/options.ts +19 -1
  61. package/src/cli/pane-functions/resolver.ts +19 -8
  62. package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
  63. package/src/cli/pane-functions/screenshot-types.ts +5 -0
  64. package/src/cli/quote-answer.ts +22 -11
  65. package/src/cli/saved-copy-persistence.ts +88 -0
  66. package/src/cli/ticker-resolution.ts +3 -2
  67. package/src/components/chart/composite/composite-chart.tsx +4 -0
  68. package/src/components/chart/composite/interactions.ts +6 -0
  69. package/src/components/chart/composite/legend.tsx +45 -7
  70. package/src/components/chart/composite/panel-gestures.ts +33 -0
  71. package/src/components/chart/composite/panel-surface.tsx +52 -3
  72. package/src/components/chart/composite/rasterizer.ts +24 -13
  73. package/src/components/chart/composite/scene.ts +39 -4
  74. package/src/components/chart/composite/series-cues.ts +14 -0
  75. package/src/components/chart/composite/text-renderer.ts +9 -2
  76. package/src/components/chart/composite/tool-catalog.ts +36 -0
  77. package/src/components/chart/composite/tools.ts +203 -44
  78. package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
  79. package/src/components/command-bar/assist/inventory.ts +2 -1
  80. package/src/components/command-bar/commands/direct/index.ts +28 -13
  81. package/src/components/command-bar/list/model.ts +5 -0
  82. package/src/components/command-bar/pane-templates/items.ts +3 -2
  83. package/src/components/command-bar/routes/root/runtime.ts +6 -7
  84. package/src/components/command-bar/selection-runtime.ts +52 -3
  85. package/src/components/command-bar/surface/index.tsx +2 -0
  86. package/src/components/command-bar/workflow/collection-actions.ts +3 -3
  87. package/src/components/layout/pane/capture-stamp.ts +2 -2
  88. package/src/components/layout/pane/footer/index.tsx +22 -6
  89. package/src/components/layout/pane/footer/model.ts +83 -15
  90. package/src/components/layout/pane/status-footer.ts +35 -2
  91. package/src/components/layout/status-widget-space.ts +9 -0
  92. package/src/components/onboarding/onboarding-wizard.tsx +4 -1
  93. package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
  94. package/src/components/onboarding/wizard-broker-sync.ts +0 -1
  95. package/src/components/onboarding/wizard-positions.ts +3 -3
  96. package/src/components/ticker/list-table-view.tsx +8 -21
  97. package/src/components/ui/choice-dialog.tsx +23 -18
  98. package/src/components/ui/choice-groups.ts +18 -0
  99. package/src/components/ui/data-table/opentui/index.tsx +9 -7
  100. package/src/components/ui/data-table/opentui/model.ts +26 -1
  101. package/src/components/ui/data-table/types.ts +2 -0
  102. package/src/components/ui/display.tsx +18 -3
  103. package/src/components/ui/icon.tsx +4 -1
  104. package/src/components/ui/list-view.tsx +8 -4
  105. package/src/components/ui/query-bar-keys.tsx +2 -0
  106. package/src/components/ui/query-bar.tsx +1 -0
  107. package/src/components/ui/select-button.tsx +5 -1
  108. package/src/components/ui/select-field.tsx +1 -0
  109. package/src/components/ui/table-layout.ts +8 -5
  110. package/src/core/app-runtime.ts +2 -0
  111. package/src/core/app-services.ts +2 -2
  112. package/src/data/config/chart-settings.ts +1 -1
  113. package/src/data/json-ticker-repository.ts +17 -8
  114. package/src/data/plugin-cache.ts +5 -3
  115. package/src/data/saved-copies.ts +8 -0
  116. package/src/data/ticker-repository.ts +16 -9
  117. package/src/data/ticker-writes.ts +81 -0
  118. package/src/data/ttl-cache.ts +165 -0
  119. package/src/docs/snapshot.json +103 -142
  120. package/src/i18n/es.ts +20 -6
  121. package/src/i18n/ja.ts +20 -6
  122. package/src/i18n/ko.ts +20 -6
  123. package/src/i18n/zh-cn.ts +20 -6
  124. package/src/i18n/zh-tw.ts +20 -6
  125. package/src/market-data/depositary-pair.ts +27 -3
  126. package/src/market-data/fund-facts.ts +50 -0
  127. package/src/market-data/market/change-basis.ts +16 -3
  128. package/src/market-data/market/format.ts +25 -0
  129. package/src/market-data/market/freshness.ts +2 -1
  130. package/src/market-data/options-alternatives.ts +81 -4
  131. package/src/market-data/quotes/metadata.ts +7 -0
  132. package/src/news/aggregator.ts +119 -9
  133. package/src/news/cloud-session.ts +21 -0
  134. package/src/news/hooks.ts +1 -0
  135. package/src/news/news-model.ts +1 -0
  136. package/src/news/types.ts +17 -0
  137. package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
  138. package/src/plugins/builtin/alerts/alert-log.ts +33 -0
  139. package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
  140. package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
  141. package/src/plugins/builtin/alerts/cli.ts +362 -80
  142. package/src/plugins/builtin/alerts/command.ts +25 -1
  143. package/src/plugins/builtin/alerts/event-rows.ts +24 -3
  144. package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
  145. package/src/plugins/builtin/alerts/fired-log.ts +73 -9
  146. package/src/plugins/builtin/alerts/format.ts +75 -5
  147. package/src/plugins/builtin/alerts/headless.ts +34 -12
  148. package/src/plugins/builtin/alerts/history-rows.ts +3 -2
  149. package/src/plugins/builtin/alerts/history.ts +1 -1
  150. package/src/plugins/builtin/alerts/index.tsx +79 -16
  151. package/src/plugins/builtin/alerts/metric-command.ts +77 -4
  152. package/src/plugins/builtin/alerts/metric-data.ts +54 -13
  153. package/src/plugins/builtin/alerts/metric-model.ts +163 -14
  154. package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
  155. package/src/plugins/builtin/alerts/pane.tsx +61 -23
  156. package/src/plugins/builtin/alerts/quotes.ts +6 -0
  157. package/src/plugins/builtin/alerts/storage.ts +12 -8
  158. package/src/plugins/builtin/alerts/types.ts +4 -0
  159. package/src/plugins/builtin/analytics/index.tsx +2 -1
  160. package/src/plugins/builtin/analytics/risk-client.ts +16 -5
  161. package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
  162. package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
  163. package/src/plugins/builtin/analytics/risk-model.ts +55 -9
  164. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
  165. package/src/plugins/builtin/attention/index.tsx +2 -1
  166. package/src/plugins/builtin/attention/listing.ts +14 -0
  167. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  168. package/src/plugins/builtin/buildout/cells.tsx +12 -4
  169. package/src/plugins/builtin/buildout/format.ts +38 -0
  170. package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
  171. package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
  172. package/src/plugins/builtin/buildout/table-model.ts +23 -2
  173. package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
  174. package/src/plugins/builtin/cds/client.ts +142 -30
  175. package/src/plugins/builtin/cds/headless.ts +35 -8
  176. package/src/plugins/builtin/cds/model.ts +145 -18
  177. package/src/plugins/builtin/cds/pane.tsx +16 -5
  178. package/src/plugins/builtin/changelog/index.tsx +9 -6
  179. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
  180. package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
  181. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
  182. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
  183. package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
  184. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
  185. package/src/plugins/builtin/chart-composer/headless.ts +6 -1
  186. package/src/plugins/builtin/chart-composer/index.tsx +5 -6
  187. package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
  188. package/src/plugins/builtin/chart-composer/settings.ts +27 -0
  189. package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
  190. package/src/plugins/builtin/chat/controller/index.ts +5 -1
  191. package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
  192. package/src/plugins/builtin/chat/controller/state.ts +25 -2
  193. package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
  194. package/src/plugins/builtin/chat/controller/utils.ts +7 -2
  195. package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
  196. package/src/plugins/builtin/chat/status-preview.ts +79 -0
  197. package/src/plugins/builtin/chat/status-widget.tsx +147 -10
  198. package/src/plugins/builtin/chat/unread-badge.ts +51 -10
  199. package/src/plugins/builtin/clinical-trials/client.ts +49 -4
  200. package/src/plugins/builtin/clinical-trials/model.ts +86 -31
  201. package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
  202. package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
  203. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
  204. package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
  205. package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
  206. package/src/plugins/builtin/company-attention/building.tsx +5 -1
  207. package/src/plugins/builtin/company-attention/client.ts +8 -9
  208. package/src/plugins/builtin/company-attention/evidence.ts +4 -1
  209. package/src/plugins/builtin/company-attention/headless.ts +2 -1
  210. package/src/plugins/builtin/company-attention/moves.tsx +5 -1
  211. package/src/plugins/builtin/company-attention/pane.tsx +12 -2
  212. package/src/plugins/builtin/company-attention/signals.tsx +5 -1
  213. package/src/plugins/builtin/company-kpis/headless.ts +8 -3
  214. package/src/plugins/builtin/company-kpis/model.ts +73 -5
  215. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  216. package/src/plugins/builtin/composite-plugins.ts +2 -1
  217. package/src/plugins/builtin/correlation/headless.ts +13 -5
  218. package/src/plugins/builtin/correlation/history.ts +22 -0
  219. package/src/plugins/builtin/cot/client.ts +22 -18
  220. package/src/plugins/builtin/cot/headless.ts +15 -10
  221. package/src/plugins/builtin/cot/model.ts +14 -1
  222. package/src/plugins/builtin/cot/pane.tsx +22 -14
  223. package/src/plugins/builtin/credit-boards/client.ts +6 -10
  224. package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
  225. package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
  226. package/src/plugins/builtin/credit-conditions/model.ts +4 -0
  227. package/src/plugins/builtin/credit-conditions/series.ts +3 -1
  228. package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
  229. package/src/plugins/builtin/credit-documents/export.ts +173 -0
  230. package/src/plugins/builtin/credit-documents/headless.ts +10 -10
  231. package/src/plugins/builtin/credit-documents/model.ts +104 -14
  232. package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
  233. package/src/plugins/builtin/custom-view/sources.ts +2 -0
  234. package/src/plugins/builtin/dividend-yield/client.ts +131 -23
  235. package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
  236. package/src/plugins/builtin/dividend-yield/model.ts +8 -5
  237. package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
  238. package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
  239. package/src/plugins/builtin/dividend-yield/types.ts +20 -2
  240. package/src/plugins/builtin/dividend-yield/view.ts +30 -2
  241. package/src/plugins/builtin/docs/targets.ts +5 -1
  242. package/src/plugins/builtin/doe/headless.ts +3 -2
  243. package/src/plugins/builtin/doe/pane.tsx +13 -3
  244. package/src/plugins/builtin/earnings/board-model.ts +16 -15
  245. package/src/plugins/builtin/earnings/board.tsx +62 -71
  246. package/src/plugins/builtin/earnings/headless.ts +40 -4
  247. package/src/plugins/builtin/earnings/scope.ts +31 -43
  248. package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
  249. package/src/plugins/builtin/earnings-calls/data.ts +18 -6
  250. package/src/plugins/builtin/earnings-calls/format.ts +31 -2
  251. package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
  252. package/src/plugins/builtin/earnings-calls/model.ts +6 -1
  253. package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
  254. package/src/plugins/builtin/econ/calendar-model.ts +48 -1
  255. package/src/plugins/builtin/econ/calendar-source.ts +4 -2
  256. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  257. package/src/plugins/builtin/econ/figures.ts +48 -0
  258. package/src/plugins/builtin/econ/index.tsx +14 -5
  259. package/src/plugins/builtin/econ/print-watch.ts +3 -1
  260. package/src/plugins/builtin/econ/types.ts +2 -0
  261. package/src/plugins/builtin/econ-statistics/client.ts +11 -0
  262. package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
  263. package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
  264. package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
  265. package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
  266. package/src/plugins/builtin/equity-screener/categories.ts +179 -0
  267. package/src/plugins/builtin/equity-screener/client.ts +4 -2
  268. package/src/plugins/builtin/equity-screener/headless.ts +144 -13
  269. package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
  270. package/src/plugins/builtin/equity-screener/model.ts +99 -10
  271. package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
  272. package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
  273. package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
  274. package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
  275. package/src/plugins/builtin/fx-matrix/client.ts +6 -3
  276. package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
  277. package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
  278. package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
  279. package/src/plugins/builtin/gpu/headless.ts +8 -2
  280. package/src/plugins/builtin/help/function-index.ts +4 -1
  281. package/src/plugins/builtin/help/index.tsx +62 -59
  282. package/src/plugins/builtin/help/shortcut-model.ts +1 -1
  283. package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
  284. package/src/plugins/builtin/help/table-cells.tsx +13 -2
  285. package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
  286. package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
  287. package/src/plugins/builtin/holders/fund-holders.ts +51 -0
  288. package/src/plugins/builtin/holders/headless.ts +22 -3
  289. package/src/plugins/builtin/holders/pane.tsx +3 -0
  290. package/src/plugins/builtin/holders/report-rows.ts +4 -1
  291. package/src/plugins/builtin/insider/headless.ts +2 -2
  292. package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
  293. package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
  294. package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
  295. package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
  296. package/src/plugins/builtin/iv-history/client.ts +52 -11
  297. package/src/plugins/builtin/iv-history/format.ts +36 -23
  298. package/src/plugins/builtin/iv-history/headless.ts +33 -18
  299. package/src/plugins/builtin/iv-history/index.tsx +2 -2
  300. package/src/plugins/builtin/iv-history/model.ts +163 -27
  301. package/src/plugins/builtin/iv-history/pane.tsx +10 -6
  302. package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
  303. package/src/plugins/builtin/iv-history/universe.ts +5 -1
  304. package/src/plugins/builtin/jobs/client.ts +25 -29
  305. package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
  306. package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
  307. package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
  308. package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
  309. package/src/plugins/builtin/macro-day/headless.ts +12 -5
  310. package/src/plugins/builtin/macro-day/index.tsx +1 -1
  311. package/src/plugins/builtin/macro-day/model.ts +39 -5
  312. package/src/plugins/builtin/macro-day/pane.tsx +27 -19
  313. package/src/plugins/builtin/macro-day/release-table.ts +2 -2
  314. package/src/plugins/builtin/market-heatmap/data.ts +8 -33
  315. package/src/plugins/builtin/market-movers/client.ts +27 -7
  316. package/src/plugins/builtin/market-movers/headless.ts +34 -12
  317. package/src/plugins/builtin/market-movers/index.tsx +26 -14
  318. package/src/plugins/builtin/market-movers/min-float.ts +74 -0
  319. package/src/plugins/builtin/market-movers/model.ts +4 -1
  320. package/src/plugins/builtin/market-movers/screener.ts +22 -4
  321. package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
  322. package/src/plugins/builtin/market-movers/session-table.ts +4 -3
  323. package/src/plugins/builtin/market-movers/table.ts +17 -5
  324. package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
  325. package/src/plugins/builtin/mna/headless.ts +2 -0
  326. package/src/plugins/builtin/mna/pane.tsx +3 -1
  327. package/src/plugins/builtin/news/headless.ts +3 -1
  328. package/src/plugins/builtin/news/index.tsx +4 -0
  329. package/src/plugins/builtin/news/wire/categories.ts +19 -3
  330. package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
  331. package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
  332. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
  333. package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
  334. package/src/plugins/builtin/openfda/client.ts +14 -2
  335. package/src/plugins/builtin/openfda/index.tsx +3 -2
  336. package/src/plugins/builtin/openfda/model.ts +9 -0
  337. package/src/plugins/builtin/openfda/pane.tsx +42 -6
  338. package/src/plugins/builtin/options/analytics.ts +29 -5
  339. package/src/plugins/builtin/options/enrichment.ts +17 -5
  340. package/src/plugins/builtin/options/table.ts +5 -2
  341. package/src/plugins/builtin/options/types.ts +2 -0
  342. package/src/plugins/builtin/options/view.tsx +17 -4
  343. package/src/plugins/builtin/options-positioning/client.ts +11 -0
  344. package/src/plugins/builtin/options-positioning/headless.ts +21 -8
  345. package/src/plugins/builtin/options-positioning/index.tsx +1 -1
  346. package/src/plugins/builtin/options-positioning/model.ts +78 -9
  347. package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
  348. package/src/plugins/builtin/options-scenario/client.ts +116 -47
  349. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
  350. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
  351. package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
  352. package/src/plugins/builtin/options-scenario/csp.ts +8 -2
  353. package/src/plugins/builtin/options-scenario/index.tsx +9 -7
  354. package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
  355. package/src/plugins/builtin/pe-band/model.ts +12 -49
  356. package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
  357. package/src/plugins/builtin/perps/cascade.ts +236 -0
  358. package/src/plugins/builtin/perps/client.ts +56 -2
  359. package/src/plugins/builtin/perps/headless.ts +65 -9
  360. package/src/plugins/builtin/perps/index.tsx +7 -5
  361. package/src/plugins/builtin/perps/model.ts +18 -4
  362. package/src/plugins/builtin/perps/pane.tsx +19 -22
  363. package/src/plugins/builtin/perps/status-line.ts +25 -0
  364. package/src/plugins/builtin/plugin-module.ts +5 -1
  365. package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
  366. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
  367. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
  368. package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
  369. package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
  370. package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
  371. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
  372. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
  373. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
  374. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
  375. package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
  376. package/src/plugins/builtin/primary-dealers/client.ts +13 -38
  377. package/src/plugins/builtin/realized-vol/client.ts +13 -5
  378. package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
  379. package/src/plugins/builtin/realized-vol/headless.ts +8 -4
  380. package/src/plugins/builtin/realized-vol/index.tsx +2 -2
  381. package/src/plugins/builtin/realized-vol/model.ts +37 -6
  382. package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
  383. package/src/plugins/builtin/relative-rotation/client.ts +18 -4
  384. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  385. package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
  386. package/src/plugins/builtin/relative-rotation/model.ts +16 -40
  387. package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
  388. package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
  389. package/src/plugins/builtin/research/event-model.ts +40 -12
  390. package/src/plugins/builtin/research/headless.ts +1 -1
  391. package/src/plugins/builtin/research/index.tsx +7 -3
  392. package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
  393. package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
  394. package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
  395. package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
  396. package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
  397. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
  398. package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
  399. package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
  400. package/src/plugins/builtin/risk-factors/data.ts +4 -2
  401. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  402. package/src/plugins/builtin/roadmap/index.tsx +28 -0
  403. package/src/plugins/builtin/roadmap/model.ts +67 -0
  404. package/src/plugins/builtin/roadmap/pane.tsx +244 -0
  405. package/src/plugins/builtin/scanner/flow-model.ts +50 -4
  406. package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
  407. package/src/plugins/builtin/scanner/flow-session.ts +146 -0
  408. package/src/plugins/builtin/scanner/headless.ts +204 -35
  409. package/src/plugins/builtin/scanner/index.ts +16 -5
  410. package/src/plugins/builtin/scanner/live-headless.ts +48 -5
  411. package/src/plugins/builtin/scanner/live-model.ts +11 -0
  412. package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
  413. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  414. package/src/plugins/builtin/seasonality/headless.ts +10 -1
  415. package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
  416. package/src/plugins/builtin/sec/fund-facts.ts +116 -34
  417. package/src/plugins/builtin/sec/headless.ts +4 -2
  418. package/src/plugins/builtin/sec/index.tsx +3 -0
  419. package/src/plugins/builtin/series-inventory.ts +2 -0
  420. package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
  421. package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
  422. package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
  423. package/src/plugins/builtin/shared/earnings-report.ts +2 -2
  424. package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
  425. package/src/plugins/builtin/shared/job-posting.ts +1 -0
  426. package/src/plugins/builtin/shared/members-menu.tsx +3 -2
  427. package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
  428. package/src/plugins/builtin/shared/report-freshness.ts +11 -2
  429. package/src/plugins/builtin/shared/research-cells.tsx +1 -1
  430. package/src/plugins/builtin/shared/series-cache.ts +4 -4
  431. package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
  432. package/src/plugins/builtin/shared/ticker-company.ts +77 -0
  433. package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
  434. package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
  435. package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
  436. package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
  437. package/src/plugins/builtin/themes/model.ts +16 -7
  438. package/src/plugins/builtin/themes/pane.tsx +5 -5
  439. package/src/plugins/builtin/thirteenf/api.ts +12 -1
  440. package/src/plugins/builtin/thirteenf/data.ts +7 -7
  441. package/src/plugins/builtin/thirteenf/headless.ts +31 -4
  442. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
  443. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
  444. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
  445. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
  446. package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
  447. package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
  448. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
  449. package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
  450. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
  451. package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
  452. package/src/plugins/builtin/time-sales/pane.tsx +31 -19
  453. package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
  454. package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
  455. package/src/plugins/builtin/treasury-daily/client.ts +41 -5
  456. package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
  457. package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
  458. package/src/plugins/builtin/treasury-daily/model.ts +50 -3
  459. package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
  460. package/src/plugins/builtin/vol-surface/client.ts +3 -13
  461. package/src/plugins/builtin/vol-surface/headless.ts +3 -3
  462. package/src/plugins/builtin/vol-surface/index.tsx +1 -1
  463. package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
  464. package/src/plugins/builtin/volatility/index.tsx +2 -2
  465. package/src/plugins/builtin/volatility/pane.tsx +7 -4
  466. package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
  467. package/src/plugins/builtin/yield-curve/client.ts +61 -1
  468. package/src/plugins/builtin/yield-curve/headless.ts +9 -6
  469. package/src/plugins/builtin/yield-curve/index.tsx +34 -10
  470. package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
  471. package/src/plugins/catalog-browser.ts +2 -1
  472. package/src/plugins/catalog-manifest.ts +2 -1
  473. package/src/plugins/host-link.ts +1 -1
  474. package/src/plugins/marketplace/model.ts +18 -5
  475. package/src/portfolio/column-values.ts +11 -7
  476. package/src/portfolio/futures-positions.ts +6 -3
  477. package/src/portfolio/position-quantity.ts +15 -9
  478. package/src/remote/resources.ts +2 -1
  479. package/src/renderers/dom/data-table/row.tsx +26 -0
  480. package/src/renderers/dom/desktop/icons.tsx +7 -0
  481. package/src/renderers/dom/desktop/list-view.tsx +13 -10
  482. package/src/renderers/dom/desktop/query-bar.tsx +4 -2
  483. package/src/renderers/dom/desktop/select-field.tsx +4 -2
  484. package/src/renderers/dom/host/chart-surface.tsx +21 -0
  485. package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
  486. package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
  487. package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
  488. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
  489. package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
  490. package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
  491. package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
  492. package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
  493. package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
  494. package/src/renderers/electrobun/bun/index.ts +26 -2
  495. package/src/renderers/electrobun/bun/window/create.ts +43 -4
  496. package/src/renderers/electrobun/bun/window/events.ts +20 -0
  497. package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
  498. package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
  499. package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
  500. package/src/renderers/electrobun/shared/app-name.ts +13 -0
  501. package/src/renderers/electrobun/shared/protocol.ts +12 -0
  502. package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
  503. package/src/renderers/electrobun/view/main.tsx +24 -1
  504. package/src/renderers/electrobun/view/news-source.ts +5 -1
  505. package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
  506. package/src/renderers/electrobun/view/ui-host.tsx +8 -0
  507. package/src/renderers/electrobun/view/view-ids.ts +85 -0
  508. package/src/renderers/opentui/start.tsx +5 -0
  509. package/src/series/registry.ts +1 -0
  510. package/src/series/types.ts +6 -0
  511. package/src/series/validate.ts +1 -0
  512. package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
  513. package/src/sources/gloomberb-cloud/index.ts +43 -8
  514. package/src/sources/gloomberb-cloud/news.ts +47 -7
  515. package/src/sources/provider-router/financials.ts +15 -3
  516. package/src/sources/provider-router/index.ts +8 -5
  517. package/src/state/ticker-cleanup.ts +50 -0
  518. package/src/sync/controller.ts +4 -3
  519. package/src/sync/core-contributors.ts +90 -31
  520. package/src/telemetry/stall-reports-dom.ts +38 -0
  521. package/src/telemetry/stall-reports-node.ts +75 -0
  522. package/src/telemetry/stall-reports.ts +214 -0
  523. package/src/test-support/preload.ts +14 -0
  524. package/src/theme/themes.ts +10 -0
  525. package/src/tickers/company-name-listing.ts +53 -13
  526. package/src/tickers/company-name.ts +50 -4
  527. package/src/tickers/index-roots.ts +7 -0
  528. package/src/tickers/listing-fate.ts +2 -2
  529. package/src/tickers/listing-venues.ts +15 -3
  530. package/src/tickers/search/index.ts +6 -2
  531. package/src/tickers/search/ranking.ts +5 -1
  532. package/src/tickers/search/upsert.ts +13 -0
  533. package/src/tickers/terminal-codes.ts +19 -0
  534. package/src/tickers/ticker-changes.ts +8 -4
  535. package/src/time-series/alignment.ts +1 -1
  536. package/src/time-series/economic.ts +18 -4
  537. package/src/time-series/field-catalog.ts +1 -1
  538. package/src/time-series/filed-trailing-eps.ts +197 -0
  539. package/src/time-series/fundamentals.ts +44 -1
  540. package/src/time-series/ice-credit.ts +3 -0
  541. package/src/time-series/price-comparison.ts +30 -2
  542. package/src/time-series/resolve-context.ts +3 -1
  543. package/src/time-series/resolve.ts +56 -4
  544. package/src/time-series/session-bar.ts +11 -0
  545. package/src/time-series/spec.ts +3 -2
  546. package/src/time-series/studies.ts +8 -2
  547. package/src/time-series/total-return.ts +160 -0
  548. package/src/time-series/transforms.ts +2 -3
  549. package/src/time-series/types.ts +22 -1
  550. package/src/types/company-from.ts +40 -0
  551. package/src/types/data-provider.ts +7 -0
  552. package/src/types/financials.ts +59 -1
  553. package/src/types/headless.ts +27 -3
  554. package/src/types/news-source.ts +1 -0
  555. package/src/types/plugin.ts +9 -0
  556. package/src/ui/host.tsx +3 -0
  557. package/src/updater/failure.ts +4 -1
  558. package/src/utils/cli-output.ts +42 -1
  559. package/src/utils/crypto-pair.ts +165 -7
  560. package/src/utils/declared-dividends.ts +1 -1
  561. package/src/utils/forward-dividend.ts +2 -0
  562. package/src/utils/fundamentals.ts +60 -8
  563. package/src/utils/fx-market-hours.ts +26 -3
  564. package/src/utils/main-thread-stall.ts +35 -2
  565. package/src/utils/market-capitalization.ts +24 -1
  566. package/src/utils/price-history.ts +7 -1
  567. package/src/utils/shell-word.ts +8 -0
  568. package/src/utils/trailing-dividend.ts +53 -4
  569. package/src/version.ts +2 -2
  570. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
@@ -15,12 +15,12 @@ import {
15
15
  type ListingVenue,
16
16
  } from "../tickers/listing-venues";
17
17
  import { findUsListingOfCompany, type UsListing } from "../tickers/us-listing";
18
- import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg } from "../tickers/company-name-listing";
18
+ import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg, type CompanyNameListing } from "../tickers/company-name-listing";
19
19
  import { movedAwayChange, startTickerChangeLookup, type MovedTickerChange } from "../tickers/ticker-changes";
20
- import { loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
20
+ import { lineMayHaveMoved, loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
21
21
  import { quotedListingName } from "../tickers/listing-name";
22
22
  import { cliStyles } from "../utils/cli-output";
23
- import { groupByCompany, sameCompany, shortCompanyName } from "../tickers/company-name";
23
+ import { groupByCompany, namesDepositaryReceipt, sameCompany, sameListedName, shortCompanyName } from "../tickers/company-name";
24
24
  import {
25
25
  canonicalExchange,
26
26
  exchangeCodeVenues,
@@ -35,8 +35,8 @@ import {
35
35
  import { withMarketData } from "./scoped-context";
36
36
  import { failWithFields } from "./errors";
37
37
  import { stripStrayEdges } from "./symbol-args";
38
- import { isNoProviderMessage, isProviderBusy } from "../sources/provider-errors";
39
- import { dashedCoinPair, MAJOR_COIN_NAMES } from "../utils/crypto-pair";
38
+ import { isNoProviderMessage, isNoSourceAnswer, isProviderBusy } from "../sources/provider-errors";
39
+ import { dashedCoinPair, exchangeLineCoinListing, isSpotCommodityOrCurrencyPair, MAJOR_COIN_NAMES } from "../utils/crypto-pair";
40
40
  import { boardCoinFor, primeBoardCoins } from "./board-coins";
41
41
  import { knownGapMessage } from "./known-gaps";
42
42
  import { currencyPairSpelling } from "../utils/currency-pair";
@@ -49,10 +49,12 @@ import {
49
49
  bloombergMarketHint,
50
50
  endsInLegalForm,
51
51
  isSearchOnlyVenue,
52
+ readLondonDottedSymbol,
52
53
  readNearMissListing,
53
54
  readTerminalListingCode,
54
55
  terminalCodeReadAs,
55
56
  unknownBloombergMarket,
57
+ type NearMissListing,
56
58
  type TerminalListingCode,
57
59
  } from "../tickers/terminal-codes";
58
60
  import { listingAbroadVenue } from "../utils/sec";
@@ -105,6 +107,12 @@ export interface ListingArg {
105
107
  * `readAs` asks about it, and a command the listing fails asks too.
106
108
  */
107
109
  meant?: { symbol: string; name: string };
110
+ /**
111
+ * A bare symbol a company's name was read as still says what it was read
112
+ * as (`listingChoiceNotes`): GETYQ for "getty images", whose NYSE line
113
+ * became GETYQ. Other bare symbols say where the data service chose instead.
114
+ */
115
+ readFromName?: true;
108
116
  }
109
117
 
110
118
  /** "Did you mean JPM (JPMorgan Chase & Co.)?", for a name read as a fund or an index; empty for any other listing. */
@@ -174,8 +182,13 @@ function parseCleanListingArg(
174
182
  // `SAN:` asks which listing; with --exchange it has its answer.
175
183
  if (option && typed.endsWith(":")) typed = typed.slice(0, -1);
176
184
  const venueKey = parsePublicTickerKey(typed);
177
- // BTC/USD, as crypto venues write a pair, is the coin's own BTC-USD.
178
- const parsed = { ...venueKey, symbol: dashedCoinPair(venueKey.symbol) ?? venueKey.symbol };
185
+ // A crypto exchange's line of a pair (BTC/GBP:COINBASE PRO, as search offered it) is the crypto venue's BTC-GBP:CCC.
186
+ const line = venueKey.exchange || optionCode ? exchangeLineCoinListing(venueKey.symbol, venueKey.exchange ?? optionCode) : null;
187
+ if (line) return { symbol: line.symbol, exchange: line.exchange, key: `${line.symbol}:${line.exchange}` };
188
+ // BTC/USD, as crypto venues write a pair, is the coin's own BTC-USD; spot crude (WTI/USD) and gold by the gram
189
+ // (GAU/USD:COMMODITY) are not coins, though tokens share their letters.
190
+ const coinPair = isSpotCommodityOrCurrencyPair(venueKey.symbol, venueKey.exchange ?? optionCode) ? null : dashedCoinPair(venueKey.symbol);
191
+ const parsed = { ...venueKey, symbol: coinPair ?? venueKey.symbol };
179
192
  const ownCode = parsed.exchange ? typedExchangeCode(typed) : "";
180
193
  let exchange = parsed.exchange ?? option;
181
194
  let code = ownCode || optionCode;
@@ -338,6 +351,15 @@ export function isNoProviderError(error: unknown): boolean {
338
351
  return isNoProviderMessage(error instanceof Error ? error.message : String(error ?? ""));
339
352
  }
340
353
 
354
+ /**
355
+ * The router's answer that no source served a request because one failed (a
356
+ * timeout, a rate limit, a refused session) rather than missed: it says
357
+ * nothing about whether the symbol exists, unlike "Not a ticker".
358
+ */
359
+ export function isUnsettledNoProvider(error: unknown): boolean {
360
+ return isNoProviderError(error) && !isNoSourceAnswer(error);
361
+ }
362
+
341
363
  const MAX_OTHER_LISTINGS = 5;
342
364
 
343
365
  type BareListing = Pick<CliListing, "symbol" | "exchange"> & Partial<Pick<CliListing, "saved">>;
@@ -359,33 +381,42 @@ function listsSeveralCompanies(venues: readonly ListingVenue[]): boolean {
359
381
  return groupByCompany(venues, (venue) => venue.name).length > 1;
360
382
  }
361
383
 
362
- /** Every other company that lists the symbol once, at its first venue in search order. */
384
+ /**
385
+ * Every other company or fund that lists the symbol, once, at its first venue
386
+ * in search order. A venue is another's only when its name says so: one with
387
+ * no name, or a depositary receipt named unlike the company (a CEDEAR's short
388
+ * form of its fund's name), is left out rather than guessed at, and one
389
+ * fund's venues are one fund however each words it (`sameListedName`).
390
+ */
363
391
  function otherCompanies(venues: readonly ListingVenue[], company: string): ListingVenue[] {
364
- return groupByCompany(venues, (venue) => venue.name)
365
- .filter((group) => !group.some((venue) => sameCompany(venue.name, company)))
392
+ if (!company.trim()) return [];
393
+ const known = venues.filter((venue) => !namesDepositaryReceipt(venue.name) || sameListedName(venue.name, company));
394
+ return groupByCompany(known, (venue) => venue.name, sameListedName)
395
+ .filter((group) => !group.some((venue) => sameListedName(venue.name, company)))
366
396
  .map((group) => group[0]!);
367
397
  }
368
398
 
369
399
  /**
370
- * "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)": the listing a bare
371
- * symbol several companies list was read on, and where the others list it.
372
- * Null when one company lists it, so a symbol that means one thing says
373
- * nothing.
400
+ * "CAR = Avis Budget Group, NASDAQ (different companies on XETRA, ASX)": the
401
+ * listing a bare symbol several companies list was read on, and where the
402
+ * others list it. Null when no other company is known to list it, so a symbol
403
+ * that means one thing (XLK, and its receipt in Buenos Aires) says nothing.
374
404
  */
375
405
  export function listingChoiceNote(
376
406
  symbol: string,
377
407
  chosen: { exchange: string; name?: string | null },
378
408
  venues: readonly ListingVenue[],
379
409
  ): string | null {
380
- if (!chosen.exchange || !listsSeveralCompanies(venues)) return null;
410
+ if (!chosen.exchange) return null;
381
411
  const chosenVenue = venues.find((venue) => venue.exchange === chosen.exchange);
382
412
  const name = chosen.name?.trim() || chosenVenue?.name || "";
383
413
  const others = otherCompanies(venues, chosenVenue?.name || name).map((venue) => venue.exchange)
384
414
  .filter((exchange) => exchange !== chosen.exchange);
385
415
  if (others.length === 0) return null;
386
416
  const more = others.length - MAX_OTHER_LISTINGS;
387
- const also = `${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloom search ${symbol}` : ""}`;
388
- return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${chosen.exchange} (also ${also})`;
417
+ const named = `${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloom search ${symbol}` : ""}`;
418
+ const which = others.length === 1 ? "a different company" : "different companies";
419
+ return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${chosen.exchange} (${which} on ${named})`;
389
420
  }
390
421
 
391
422
  // Quotes of these never name a company's listing, so they are never a choice between companies.
@@ -409,44 +440,60 @@ export function listingReadNotes(listing: Pick<ListingArg, "readAs">): string[]
409
440
  return listing.readAs ? [listing.readAs] : [];
410
441
  }
411
442
 
443
+ /**
444
+ * The coin line for a bare coin ticker (SOL, ETH) a command read as a listing that is
445
+ * not the coin, from the quote it was read on (`notTheCoinNote`). A list, so a command can
446
+ * spread it into its warnings. A named listing (`ETH:ARCA`, `--exchange`) asked for that
447
+ * listing on purpose and gets none.
448
+ */
449
+ export function coinListingNotes(
450
+ listing: Pick<CliListing, "symbol" | "exchange">,
451
+ quote: Pick<Quote, "name" | "instrumentType" | "stale" | "delisting"> | null | undefined,
452
+ ): string[] {
453
+ const note = listing.exchange ? null : notTheCoinNote(listing.symbol, quote);
454
+ return note ? [note] : [];
455
+ }
456
+
412
457
  /**
413
458
  * `listingChoiceNote` for a listing a command resolved, from the quote it was
414
459
  * read on: only a bare symbol went where the data service chose. A named
415
460
  * listing whose code names several venues says which it was read as
416
- * (`listingReadNotes`). A list, so a command can spread it into its warnings.
461
+ * (`listingReadNotes`). A bare coin ticker read as another listing ends with
462
+ * the coin's pair (`coinListingNotes`). A list, so a command can spread it into
463
+ * its warnings.
417
464
  */
418
465
  export async function listingChoiceNotes(
419
- listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "readAs">,
466
+ listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "readAs" | "readFromName">,
420
467
  deps: ListingDeps,
421
- quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType"> | null | undefined,
468
+ quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType" | "stale" | "delisting"> | null | undefined,
422
469
  /** The venues `loadBareVenues` already started; looked up here when not given. */
423
470
  venues?: Promise<ListingVenue[]> | null,
424
471
  ): Promise<string[]> {
425
- if (!isBareListing(listing)) return listingReadNotes(listing);
426
- if (!quote || UNLISTED_TYPES.has(quote.instrumentType?.trim().toUpperCase() ?? "")) return [];
472
+ const coin = coinListingNotes(listing, quote);
473
+ if (!isBareListing(listing)) return [...listingReadNotes(listing), ...coin];
474
+ // A bare symbol a name was read as says so: "Read "getty images" as GETYQ (...). GETY became GETYQ on 2026-10-22."
475
+ if (listing.readFromName) return [...listingReadNotes(listing), ...coin];
476
+ if (!quote || UNLISTED_TYPES.has(quote.instrumentType?.trim().toUpperCase() ?? "")) return coin;
427
477
  const exchange = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
428
- if (!exchange) return [];
478
+ if (!exchange) return coin;
429
479
  const listed = await (venues ?? loadBareVenues(listing, deps) ?? []);
430
480
  const note = listingChoiceNote(listing.symbol, { exchange, name: quote.name }, listed);
431
- return note ? [note] : [];
481
+ return [...note ? [note] : [], ...coin];
432
482
  }
433
483
 
434
484
  /**
435
485
  * `listingChoiceNotes` for a command with no quote of its own to read the
436
486
  * listing from (`fn`): it loads the listing's quote, and only for a bare
437
487
  * symbol, so a named or saved listing costs nothing. A bare coin ticker read
438
- * as a fund (BTC, the Grayscale trust) ends with `quote`'s note naming the
439
- * coin's pair.
488
+ * as another listing (BTC, the Grayscale trust) ends with the coin's pair.
440
489
  */
441
490
  export async function quotedListingChoiceNotes(
442
- listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "request" | "readAs">,
491
+ listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "request" | "readAs" | "readFromName">,
443
492
  deps: ListingDeps,
444
493
  ): Promise<string[]> {
445
494
  const venues = loadBareVenues(listing, deps);
446
495
  if (!venues) return listingReadNotes(listing);
447
- const quote = await loadListingQuote(deps.dataProvider, listing);
448
- const coin = notTheCoinNote(listing.symbol, quote);
449
- return [...await listingChoiceNotes(listing, deps, quote, venues), ...coin ? [coin] : []];
496
+ return listingChoiceNotes(listing, deps, await loadListingQuote(deps.dataProvider, listing), venues);
450
497
  }
451
498
 
452
499
  /**
@@ -563,31 +610,43 @@ export function futuresTryLine(symbol: string, request: ListingDataRequest): str
563
610
  return `${future.symbol} ${future.name} future${retry ? ` (try: ${retry})` : ""}`;
564
611
  }
565
612
 
613
+ /** A venue's listing as its key names it: under its own symbol there (BP:LSE for `BP.`), else the one asked for. */
614
+ function venueKey(symbol: string, venue: Pick<ListingVenue, "exchange" | "symbol">): string {
615
+ return `${venue.symbol ?? symbol}:${venue.exchange}`;
616
+ }
617
+
566
618
  function describeOtherListings(
567
619
  listing: BareListing,
568
620
  { exchange, resolved, others, choose }: BareListingVenues,
569
621
  request: ListingDataRequest,
622
+ /** Listings just asked that had none: named as having none, never offered to try. */
623
+ without: readonly ListingVenue[] = [],
570
624
  ): OtherListingsMessage | null {
571
625
  const future = futuresTryLine(listing.symbol, request);
572
- if (others.length === 0 && !future) return null;
626
+ const offered = others.filter((venue) => !without.includes(venue));
627
+ if (offered.length === 0 && !future && without.length === 0) return null;
573
628
  const name = resolved?.name || listing.saved?.metadata.name?.trim() || "";
574
- const lines = others.slice(0, MAX_OTHER_LISTINGS).map((venue) => {
575
- const key = `${listing.symbol}:${venue.exchange}`;
629
+ const lines = offered.slice(0, MAX_OTHER_LISTINGS).map((venue) => {
630
+ const key = venueKey(listing.symbol, venue);
576
631
  const retry = retryCommand(request, key);
577
632
  return `${key}${venue.name ? ` ${venue.name}` : ""}${retry ? ` (try: ${retry})` : ""}`;
578
633
  });
579
- if (others.length > MAX_OTHER_LISTINGS) lines.push(`and ${others.length - MAX_OTHER_LISTINGS} more: gloom search ${listing.symbol}`);
634
+ if (offered.length > MAX_OTHER_LISTINGS) lines.push(`and ${offered.length - MAX_OTHER_LISTINGS} more: gloom search ${listing.symbol}`);
580
635
  if (future) lines.push(future);
581
- const heading = choose ? "Listings" : "Other listings";
636
+ // Every other listing was asked and had nothing: they are named as such, with nothing to try.
637
+ const heading = choose ? "Listings" : lines.length === 0 ? `No ${request.noun} on its other listings either` : "Other listings";
638
+ const named = lines.length === 0 ? without : offered;
639
+ const details = lines.length === 0 ? `${heading}: ${without.map((venue) => venueKey(listing.symbol, venue)).join(", ")}.`
640
+ : lines.length === 1 ? `${heading}: ${lines[0]}` : [`${heading}:`, ...lines.map((line) => ` ${line}`)].join("\n");
582
641
  return {
583
642
  // The symbol went to no venue, so none of them is said to lack the data.
584
643
  message: choose
585
644
  ? `Choose an exchange for ${listing.symbol}.`
586
645
  : `${listing.symbol} resolved to ${name ? `${name} (${exchange})` : exchange}, which has no ${request.noun}.`,
587
- details: lines.length === 1 ? `${heading}: ${lines[0]}` : [`${heading}:`, ...lines.map((line) => ` ${line}`)].join("\n"),
646
+ details,
588
647
  heading,
589
- listings: [...others.map((venue) => `${listing.symbol}:${venue.exchange}`), ...future ? [futuresContractByCode(listing.symbol)!.symbol] : []],
590
- choices: others.slice(0, MAX_OTHER_LISTINGS).map((venue) => ({ key: `${listing.symbol}:${venue.exchange}`, name: venue.name || null })),
648
+ listings: [...named.map((venue) => venueKey(listing.symbol, venue)), ...future ? [futuresContractByCode(listing.symbol)!.symbol] : []],
649
+ choices: offered.slice(0, MAX_OTHER_LISTINGS).map((venue) => ({ key: venueKey(listing.symbol, venue), name: venue.name || null })),
591
650
  choose,
592
651
  };
593
652
  }
@@ -612,30 +671,44 @@ async function otherListingsMessage(
612
671
  return venues && describeOtherListings(listing, venues, request);
613
672
  }
614
673
 
615
- /** The listing on one venue, as `SYM:EXCH` names it. */
616
- function namedListing(listing: CliListing, exchange: string): CliListing {
617
- const key = `${listing.symbol}:${exchange}`;
674
+ /** The listing on one venue, as `SYM:EXCH` names it, under the venue's own symbol when search lists one. */
675
+ function namedListing(listing: CliListing, venue: Pick<ListingVenue, "exchange" | "symbol">): CliListing {
676
+ const { exchange } = venue;
677
+ const symbol = venue.symbol ?? listing.symbol;
678
+ const key = `${symbol}:${exchange}`;
618
679
  const saved = canonicalExchange(listing.saved?.metadata.exchange) === exchange ? listing.saved : null;
619
- return { symbol: listing.symbol, exchange, key, saved, request: { symbol: key, exchange } };
680
+ return { symbol, exchange, key, saved, request: { symbol: key, exchange } };
620
681
  }
621
682
 
683
+ /**
684
+ * Each listing's quote, null where it has none; `undefined` where the request
685
+ * itself failed, which says nothing of whether it has one.
686
+ */
622
687
  async function quoteListings(
623
688
  dataProvider: DataProvider,
624
689
  listings: readonly CliListing[],
625
690
  refresh: boolean,
626
691
  datesChangeBasis = false,
627
- ): Promise<Array<Quote | null>> {
692
+ ): Promise<Array<Quote | null | undefined>> {
628
693
  if (listings.length === 0) return [];
629
694
  const targets = listings.map((listing) => listing.request);
630
695
  if (dataProvider.getQuotesBatch) {
631
696
  const results = await dataProvider.getQuotesBatch(targets, { forceRefresh: refresh, ...(datesChangeBasis ? { datesChangeBasis } : {}) })
632
- .catch(logFailure("cli.listing-arg", "Read listing quote batch failed", []));
633
- return targets.map((target, index) => (results.find((result) => result.target === target) ?? results[index])?.quote ?? null);
697
+ .catch(logFailure("cli.listing-arg", "Read listing quote batch failed", null));
698
+ if (!results) return targets.map(() => undefined);
699
+ return targets.map((target, index) => {
700
+ const result = results.find((entry) => entry.target === target) ?? results[index];
701
+ // No source quotes it, which is an answer; any other failure is none.
702
+ return result?.quote ?? (result && (result.error == null || isNoProviderError(result.error)) ? null : undefined);
703
+ });
634
704
  }
635
705
  return Promise.all(targets.map((target) => dataProvider.getQuote(target.symbol, target.exchange, {
636
706
  cacheMode: refresh ? "refresh" : "default",
637
707
  ...(datesChangeBasis ? { datesChangeBasis } : {}),
638
- }).catch(logFailure("cli.listing-arg", "Read listing quote failed", null))));
708
+ }).catch((error: unknown) => {
709
+ logFailure("cli.listing-arg", "Read listing quote failed")(error);
710
+ return isNoProviderError(error) ? null : undefined;
711
+ })));
639
712
  }
640
713
 
641
714
  export type BareListingQuote =
@@ -674,8 +747,8 @@ export async function quoteBareListing(
674
747
  const other = describeOtherListings(listing, venues, request);
675
748
  return other && { kind: "missing", ...other };
676
749
  }
677
- const first = [namedListing(listing, venues.exchange)];
678
- const rest = venues.others.map((venue) => namedListing(listing, venue.exchange));
750
+ const first = [namedListing(listing, venues.resolved ?? { exchange: venues.exchange })];
751
+ const rest = venues.others.map((venue) => namedListing(listing, venue));
679
752
  let tried = first;
680
753
  let quotes = await quoteListings(deps.dataProvider, first, refresh, datesChangeBasis);
681
754
  if (!quotes[0] && rest.length > 0) {
@@ -684,7 +757,9 @@ export async function quoteBareListing(
684
757
  }
685
758
  const found = quotes.findIndex(Boolean);
686
759
  if (found < 0) {
687
- const other = describeOtherListings(listing, venues, request);
760
+ // A listing just asked that had no quote is no listing to try.
761
+ const without = venues.others.filter((_venue, index) => quotes[index + 1] === null);
762
+ const other = describeOtherListings(listing, venues, request, without);
688
763
  return other && { kind: "missing", ...other };
689
764
  }
690
765
  const answer = tried[found]!;
@@ -697,7 +772,8 @@ export async function quoteBareListing(
697
772
  kind: "quoted",
698
773
  listing: answer,
699
774
  quote: quotes[found]!,
700
- note: chosenListingNote(listing.symbol, answer.exchange, others, without),
775
+ // Under the venue's own symbol (BP:LSE for `BP.`), the note names the listing, not only its venue.
776
+ note: chosenListingNote(listing.symbol, answer.symbol === listing.symbol ? answer.exchange : answer.key, others, without),
701
777
  };
702
778
  }
703
779
 
@@ -708,6 +784,8 @@ export interface ReadListing {
708
784
  exchange: string;
709
785
  /** The listing's company, from its quote. */
710
786
  name: string | null;
787
+ /** A bare coin ticker read as a listing that is not the coin (`coinListingNotes`); null otherwise. */
788
+ coinNote: string | null;
711
789
  }
712
790
 
713
791
  /**
@@ -728,10 +806,14 @@ export async function readBareListing(
728
806
  const listing = await resolveCliListing(symbol, null, deps);
729
807
  if (listing.exchange) return null;
730
808
  const answer = await loadListingQuoteAnswer(deps.dataProvider, listing);
731
- if (answer.quote) return { symbol: listing.symbol, exchange: listing.request.exchange, name: answer.quote.name?.trim() || null };
809
+ if (answer.quote) {
810
+ return { symbol: listing.symbol, exchange: listing.request.exchange, name: answer.quote.name?.trim() || null, coinNote: coinListingNotes(listing, answer.quote)[0] ?? null };
811
+ }
732
812
  if (!isNoProviderError(answer.error)) return null;
733
813
  const other = await quoteBareListing(listing, deps, request, { refresh });
734
- if (other?.kind === "quoted") return { symbol: listing.symbol, exchange: other.listing.exchange, name: other.quote.name?.trim() || null };
814
+ if (other?.kind === "quoted") {
815
+ return { symbol: listing.symbol, exchange: other.listing.exchange, name: other.quote.name?.trim() || null, coinNote: coinListingNotes(listing, other.quote)[0] ?? null };
816
+ }
735
817
  if (other?.choose) throw new ListingArgError(other.message, other.details, other.choices);
736
818
  return null;
737
819
  }
@@ -1096,7 +1178,9 @@ export async function resolveCliListing(
1096
1178
  const terminal = named ? null : read;
1097
1179
  // "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.
1098
1180
  if (!terminal && !named) await refuseUnknownMarketCode(typed, deps);
1099
- let parsed = named ?? parseListingArg(raw, exchangeOption, { ...options, terminalCode: terminal });
1181
+ // `BP.` is London's code for BP: its London line where London lists it, and `GE.` a US ticker with a stray dot.
1182
+ const dotted = terminal || named || exchangeOption?.trim() ? null : readLondonDottedSymbol(typed);
1183
+ let parsed = named ?? (dotted ? await londonDottedListing(dotted, deps) : parseListingArg(raw, exchangeOption, { ...options, terminalCode: terminal }));
1100
1184
  if (!parsed.symbol) throw new ListingArgError("Ticker symbol is required.");
1101
1185
  // $VIX is Schwab's code for ^VIX, unless a security trades as VIX: $AAPL is a cashtag.
1102
1186
  const index = named || parsed.exchange ? null : await readDollarIndexCode(raw, deps);
@@ -1140,6 +1224,21 @@ export async function resolveCliListing(
1140
1224
  };
1141
1225
  }
1142
1226
 
1227
+ /**
1228
+ * A two-letter symbol written with London's trailing dot (`BP.`, `RR.`) as its
1229
+ * London line, said as "Read BP. as BP:LSE.", where search lists it on LSE or
1230
+ * its quote there says it trades. Elsewhere the dot was a slip after the
1231
+ * ticker, dropped as a stray edge is (`GE.` is GE).
1232
+ */
1233
+ async function londonDottedListing(dotted: NearMissListing, deps: ListingDeps): Promise<ListingArg> {
1234
+ const london = { symbol: dotted.symbol, exchange: "LSE" };
1235
+ const venues = await listingVenues(dotted.symbol, deps).catch(logFailure("cli.listing-arg", "Read listing venues failed", []));
1236
+ const listed = venues.some((venue) => venue.exchange === london.exchange) || await quotedOnVenue(london, deps);
1237
+ if (!listed) return parseListingArg(dotted.symbol);
1238
+ const key = `${london.symbol}:${london.exchange}`;
1239
+ return { ...parseListingArg(key), readAs: `Read ${dotted.typed} as ${key}.` };
1240
+ }
1241
+
1143
1242
  /**
1144
1243
  * Fails a line written as a Bloomberg code whose market code no venue answers
1145
1244
  * to (`AAPL GP`), suggesting the code of the symbol's own listing (`AAPL
@@ -1235,17 +1334,23 @@ const ONE_WORD_NAME = /^\p{L}[\p{L}\p{N}&'’.-]*$/u;
1235
1334
  * for anything else, and when search names no company or is too busy to say.
1236
1335
  */
1237
1336
  export async function oneWordCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<ListingArg | null> {
1337
+ const found = await findOneWordCompany(typed, deps);
1338
+ return found ? companyListingArg(typed, found) : null;
1339
+ }
1340
+
1341
+ /** The company `oneWordCompanyName` reads a word as, before it is read as a listing. */
1342
+ async function findOneWordCompany(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<CompanyNameListing | null> {
1238
1343
  const word = typed.trim();
1239
1344
  if (!ONE_WORD_NAME.test(word) || knownGapMessage(word) || isMajorCoinName(word)) return null;
1240
- const named = await readCompanyName(word, deps, { oneWord: true }).catch((error: unknown) => {
1345
+ const found = await findCompanyName(word, deps, { oneWord: true }).catch((error: unknown) => {
1241
1346
  if (!isProviderBusy(error)) throw error;
1242
1347
  // Intentional ignore: a busy search says nothing about the name, so the word keeps its own "Not a ticker".
1243
1348
  return null;
1244
1349
  });
1245
- if (!named) return null;
1350
+ if (!found) return null;
1246
1351
  // The board is read only for a word that names a company, and once for the run (`primeBoardCoins`).
1247
1352
  if (/^[A-Za-z0-9]+$/.test(word)) await primeBoardCoins([`Not a ticker: ${word.toUpperCase()}.`]);
1248
- return boardCoinFor(word) ? null : named;
1353
+ return boardCoinFor(word) ? null : found;
1249
1354
  }
1250
1355
 
1251
1356
  function isMajorCoinName(word: string): boolean {
@@ -1253,14 +1358,84 @@ function isMajorCoinName(word: string): boolean {
1253
1358
  return [...MAJOR_COIN_NAMES.values()].some((name) => name.toLowerCase() === wanted);
1254
1359
  }
1255
1360
 
1361
+ function findCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<CompanyNameListing | null> {
1362
+ return findListingOfCompanyName(deps.dataProvider, typed, { meant: true, quoted: true, movedLine: (listing) => movedLineOf(listing, deps), ...options });
1363
+ }
1364
+
1365
+ /**
1366
+ * Where the company of a US line that stopped trading, or has no quote, trades
1367
+ * now when it moved off the symbol (`loadListingFate`): GETY became GETYQ on
1368
+ * 2026-10-22, so "getty images" is GETYQ rather than its delisted NYSE line or
1369
+ * the Munich line still quoting the old shares. A line that trades is not
1370
+ * looked up.
1371
+ */
1372
+ async function movedLineOf(
1373
+ listing: { symbol: string; exchange: string; quote: Quote | null; name: string | null },
1374
+ deps: Pick<ListingDeps, "dataProvider">,
1375
+ ): Promise<{ symbol: string; note: string } | null> {
1376
+ const { quote, name, ...line } = listing;
1377
+ if (quote && !lineMayHaveMoved(quote)) return null;
1378
+ const fate = await loadListingFate(line, deps.dataProvider, { quote });
1379
+ const moved = fate.moved ?? (quote ? null : movedWithoutQuote(fate.change, name));
1380
+ return moved ? { symbol: moved.tradesAs, note: moved.note } : null;
1381
+ }
1382
+
1383
+ /**
1384
+ * The move of a line with no quote, when it is the move of the company search
1385
+ * names on the line. With no quote nothing tells the company's change from an
1386
+ * older one's: a reused symbol's change data is the company that held it
1387
+ * before, so it is followed only when that company is this one.
1388
+ */
1389
+ function movedWithoutQuote(change: ListingFate["change"], name: string | null): MovedTickerChange | null {
1390
+ const moved = movedAwayChange(change);
1391
+ return moved && sameCompany(name, moved.issuer) ? moved : null;
1392
+ }
1393
+
1256
1394
  async function readCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<ListingArg | null> {
1257
- const found = await findListingOfCompanyName(deps.dataProvider, typed, { meant: true, quoted: true, ...options });
1258
- if (!found) return null;
1259
- const key = `${found.symbol}:${found.exchange}`;
1395
+ const found = await findCompanyName(typed, deps, options);
1396
+ return found ? companyListingArg(typed, found) : null;
1397
+ }
1398
+
1399
+ function companyListingArg(typed: string, found: CompanyNameListing): ListingArg {
1400
+ const key = found.exchange ? `${found.symbol}:${found.exchange}` : found.symbol;
1260
1401
  // A fund or an index carrying the name asks about the company's stock: "Did you mean JPM (JPMorgan Chase & Co.)?".
1261
1402
  const meant = found.meant ? { symbol: found.meant.symbol, name: found.meant.name } : null;
1262
1403
  const question = meant ? ` ${meantQuestion({ meant })}` : "";
1263
- return { ...parseListingArg(key), readAs: `Read "${typed.trim()}" as ${key} (${found.name}).${question}`, ...meant ? { meant } : {} };
1404
+ // A company that moved off the line search lists says so: "GETY became GETYQ on 2026-10-22."
1405
+ const moved = found.moved ? ` ${found.moved}.` : "";
1406
+ return {
1407
+ ...parseListingArg(key), readAs: `Read "${typed.trim()}" as ${key} (${found.name}).${moved}${question}`,
1408
+ ...meant ? { meant } : {}, ...found.exchange ? {} : { readFromName: true as const },
1409
+ };
1410
+ }
1411
+
1412
+ /**
1413
+ * The listing search names for a company's name typed where a ticker goes,
1414
+ * as a sentence offers it: `PLTR (Palantir Technologies Inc.)`, a listing
1415
+ * abroad with its venue (`SIE:XETRA (Siemens AG)`), a fund or an index that
1416
+ * carries the name as the company's stock it means. Null when the words name
1417
+ * no company, search is busy, or the listing found is the word itself: a
1418
+ * real ticker is never a company's name. Never throws.
1419
+ */
1420
+ export async function companyNameSuggestion(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<{ symbol: string; name: string } | null> {
1421
+ const words = typed.trim().replace(/^\$/, "");
1422
+ if (!words) return null;
1423
+ try {
1424
+ const found = isCompanyNameInput(words) ? await findCompanyName(words, deps).catch(busyAsMiss) : await findOneWordCompany(words, deps);
1425
+ const listing = found?.meant ?? found;
1426
+ if (!listing || listing.symbol.toUpperCase() === words.toUpperCase()) return null;
1427
+ return { symbol: !listing.exchange || isUsListingExchange(listing.exchange) ? listing.symbol : `${listing.symbol}:${listing.exchange}`, name: listing.name };
1428
+ } catch (error) {
1429
+ logFailure("cli.listing-arg", "Search a company name for a suggestion failed")(error);
1430
+ return null;
1431
+ }
1432
+ }
1433
+
1434
+ /** A search too busy to answer is no company, as a name that matches none. */
1435
+ function busyAsMiss(error: unknown): null {
1436
+ if (!isProviderBusy(error)) throw error;
1437
+ // Intentional ignore: a busy search says nothing about the name, so no company is suggested.
1438
+ return null;
1264
1439
  }
1265
1440
 
1266
1441
  /** `resolveCliListing` for a command: a bad exchange fails the command with its message. */
@@ -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;
@@ -45,6 +48,7 @@ const NON_DATA_PANE_IDS = new Set([
45
48
  "local-agent-workspace",
46
49
  "macro-tv",
47
50
  "plugin-marketplace",
51
+ "roadmap",
48
52
  "world-venue-map",
49
53
  ]);
50
54