gloomberb 0.16.2 → 0.16.4

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 (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -1,19 +1,52 @@
1
1
  import type { PricePoint } from "../types/financials";
2
2
  import { marketPriceFractionDigitCeiling, resolveAssetDisplayKind } from "../market-data/market/format";
3
- import { isIntradayResolution, type ManualChartResolution } from "../time-series/resolution";
3
+ import { TIME_RANGES, type TimeRange } from "../time-series/range";
4
+ import { subtractTimeRange } from "../time-series/date-window";
5
+ import {
6
+ CHART_RESOLUTION_STEP_MS,
7
+ isIntradayResolution,
8
+ RANGE_HISTORY_RESOLUTION,
9
+ type ManualChartResolution,
10
+ } from "../time-series/resolution";
11
+ import { currencyUnitLabel } from "../utils/currency-units";
4
12
  import { getPricePointTimestamp } from "../utils/price-history";
13
+ import { pricePointIntegrityFlags, pricePointValues } from "../utils/price-history-integrity";
14
+ import { formatUtcTime } from "../utils/utc-time";
5
15
 
6
16
  const PRICE_KEYS = ["open", "high", "low", "close"] as const;
17
+ const DAY_MS = 24 * 60 * 60 * 1000;
7
18
  /** Daily bars are a day apart even when stamped at the session open. */
8
19
  const INTRADAY_MAX_GAP_MS = 20 * 60 * 60 * 1000;
20
+ const INTRADAY_RESOLUTIONS = ["1m", "5m", "15m", "30m", "45m", "1h"] as const;
21
+ /** A first bar later than this after the range's start is a late start, not a weekend or holiday. */
22
+ const LATE_START_MIN_MS = 10 * DAY_MS;
23
+ const LATE_START_MIN_BARS = 3;
9
24
 
10
- interface HistoryRow {
25
+ const BAR_INTERVAL_LABELS: Record<ManualChartResolution, string> = {
26
+ "1m": "1-minute",
27
+ "5m": "5-minute",
28
+ "15m": "15-minute",
29
+ "30m": "30-minute",
30
+ "45m": "45-minute",
31
+ "1h": "hourly",
32
+ "1d": "daily",
33
+ "1wk": "weekly",
34
+ "1mo": "monthly",
35
+ };
36
+
37
+ export interface HistoryRow {
11
38
  date: string;
12
39
  open: number | null;
13
40
  high: number | null;
14
41
  low: number | null;
15
- close: number;
42
+ close: number | null;
16
43
  volume: number | null;
44
+ /** Currency code of the prices; null for index points or when no source says. */
45
+ currency: string | null;
46
+ /** Bar size, in the codes of `resolution`: 5m, 1h, 1d, 1wk, 1mo. */
47
+ interval: ManualChartResolution | null;
48
+ /** Each way the bar's prices contradict each other, such as `high<open`; null when they agree. */
49
+ flag: string | null;
17
50
  }
18
51
 
19
52
  function decimalPlaces(value: number): number {
@@ -43,29 +76,63 @@ function isUtcMidnight(time: number): boolean {
43
76
  return new Date(time).toISOString().endsWith("T00:00:00.000Z");
44
77
  }
45
78
 
79
+ function medianGap(times: readonly number[]): number | null {
80
+ const finite = times.filter(Number.isFinite).sort((left, right) => left - right);
81
+ const gaps = finite.slice(1).map((time, index) => time - finite[index]!).filter((gap) => gap > 0).sort((left, right) => left - right);
82
+ return gaps.length > 0 ? gaps[Math.floor((gaps.length - 1) / 2)]! : null;
83
+ }
84
+
46
85
  /** Without a declared cadence, bar spacing decides: daily bars may be stamped at the session open. */
47
86
  function looksIntraday(times: readonly number[]): boolean {
48
- const finite = times.filter(Number.isFinite).sort((left, right) => left - right);
87
+ const finite = times.filter(Number.isFinite);
49
88
  if (finite.length < 2) return finite.some((time) => !isUtcMidnight(time));
50
- const gaps = finite.slice(1).map((time, index) => time - finite[index]!).filter((gap) => gap > 0).sort((left, right) => left - right);
51
- return gaps.length > 0 && gaps[Math.floor((gaps.length - 1) / 2)]! < INTRADAY_MAX_GAP_MS;
89
+ const gap = medianGap(finite);
90
+ return gap != null && gap < INTRADAY_MAX_GAP_MS;
91
+ }
92
+
93
+ /** The bar size served: as the source declared it, otherwise read from the typical spacing of the bars. */
94
+ function historyInterval(times: readonly number[], resolution: ManualChartResolution | null): ManualChartResolution | null {
95
+ if (resolution) return resolution;
96
+ const gap = medianGap(times);
97
+ if (gap == null) return null;
98
+ if (gap < INTRADAY_MAX_GAP_MS) {
99
+ return INTRADAY_RESOLUTIONS.reduce((best, candidate) => (
100
+ Math.abs(CHART_RESOLUTION_STEP_MS[candidate] - gap) < Math.abs(CHART_RESOLUTION_STEP_MS[best] - gap) ? candidate : best
101
+ ));
102
+ }
103
+ const days = gap / DAY_MS;
104
+ return days >= 25 ? "1mo" : days >= 6 ? "1wk" : "1d";
52
105
  }
53
106
 
54
- /** Intraday bars keep their UTC time; daily and longer bars are trading dates. */
55
- export function historyRows(points: readonly PricePoint[], resolution: ManualChartResolution | null): HistoryRow[] {
107
+ /**
108
+ * Intraday bars keep their UTC time; daily and longer bars are trading dates.
109
+ * A bar whose prices contradict each other is blanked exactly as HP blanks it,
110
+ * and says why in `flag`.
111
+ */
112
+ export function historyRows(
113
+ points: readonly PricePoint[],
114
+ resolution: ManualChartResolution | null,
115
+ currency: string | null = null,
116
+ ): HistoryRow[] {
56
117
  const times = points.map(getPricePointTimestamp);
57
118
  const intraday = resolution ? isIntradayResolution(resolution) : looksIntraday(times);
119
+ const interval = historyInterval(times, resolution);
58
120
  return points.map((point, index) => {
59
121
  const time = times[index]!;
60
122
  const iso = Number.isFinite(time) ? new Date(time).toISOString() : "";
61
- const price = (value: number | undefined) => value == null ? null : cleanFloat32Price(value);
123
+ const values = pricePointValues(point);
124
+ const flags = pricePointIntegrityFlags(point);
125
+ const price = (value: number | null) => value == null ? null : cleanFloat32Price(value);
62
126
  return {
63
127
  date: intraday ? iso.replace(".000Z", "Z") : iso.slice(0, 10),
64
- open: price(point.open),
65
- high: price(point.high),
66
- low: price(point.low),
67
- close: cleanFloat32Price(point.close),
68
- volume: point.volume ?? null,
128
+ open: price(values.open),
129
+ high: price(values.high),
130
+ low: price(values.low),
131
+ close: price(values.close),
132
+ volume: values.volume,
133
+ currency,
134
+ interval,
135
+ flag: flags.length > 0 ? flags.join(",") : null,
69
136
  };
70
137
  });
71
138
  }
@@ -84,7 +151,7 @@ function neededDecimals(value: number, cap: number): number {
84
151
  * one, the largest price sets a seven significant digit ceiling (BTC keeps
85
152
  * cents, FX keeps pips, sub-cent coins keep their digits). Cents at least above 1.
86
153
  */
87
- export function historyPriceDecimals(rows: readonly HistoryRow[], assetCategory?: string): number {
154
+ export function historyPriceDecimals(rows: readonly Pick<HistoryRow, typeof PRICE_KEYS[number]>[], assetCategory?: string): number {
88
155
  const values = rows.flatMap((row) => PRICE_KEYS.flatMap((key) => {
89
156
  const value = row[key];
90
157
  return value != null && Number.isFinite(value) && value !== 0 ? [Math.abs(value)] : [];
@@ -100,3 +167,108 @@ export function historyPriceDecimals(rows: readonly HistoryRow[], assetCategory?
100
167
  const decimals = needed[Math.floor(0.9 * (needed.length - 1))]!;
101
168
  return largest >= 1 ? Math.max(2, decimals) : decimals;
102
169
  }
170
+
171
+ /** How far back ALL reaches, in whole years, from the window a history request for it asks for. */
172
+ export function historyAllRangeYears(now = new Date()): number {
173
+ return Math.round((now.getTime() - subtractTimeRange(now, "ALL").getTime()) / (365.25 * DAY_MS));
174
+ }
175
+
176
+ /** "1D 5-minute, 1W hourly, 1M to 1Y daily, 5Y weekly, ALL monthly", from the table history requests use. */
177
+ export function historyIntervalsByRange(): string {
178
+ const groups: Array<{ ranges: TimeRange[]; resolution: ManualChartResolution }> = [];
179
+ for (const range of TIME_RANGES) {
180
+ const resolution = RANGE_HISTORY_RESOLUTION[range];
181
+ const last = groups.at(-1);
182
+ if (last?.resolution === resolution) last.ranges.push(range);
183
+ else groups.push({ ranges: [range], resolution });
184
+ }
185
+ return groups.map(({ ranges, resolution }) => (
186
+ `${ranges.length > 1 ? `${ranges[0]} to ${ranges.at(-1)}` : ranges[0]} ${BAR_INTERVAL_LABELS[resolution]}`
187
+ )).join(", ");
188
+ }
189
+
190
+ export interface HistoryUnit {
191
+ kind: "currency" | "pair" | "points" | "unknown";
192
+ /** Currency code of the prices; null for index points or when no source says. */
193
+ currency: string | null;
194
+ /** What one price is: "USD", "ILA (agorot, 1/100 ILS)", "ZAR per USD", "index points"; null when unknown. */
195
+ unit: string | null;
196
+ }
197
+
198
+ /**
199
+ * What the prices are in. An FX pair is named by its symbol (ZAR=X is ZAR per
200
+ * USD, EURUSD=X is USD per EUR), an index is in points, and anything else is
201
+ * in the currency its quote states, a sub-unit named as such. Without one the
202
+ * unit is unknown rather than guessed from the exchange.
203
+ */
204
+ export function historyUnit(symbol: string, quote: { currency?: string; instrumentType?: string } | null | undefined): HistoryUnit {
205
+ const pair = /^([A-Z]{3})([A-Z]{3})?=X$/.exec(symbol.trim().toUpperCase());
206
+ if (pair) {
207
+ const [base, counter] = pair[2] ? [pair[1]!, pair[2]] : ["USD", pair[1]!];
208
+ return { kind: "pair", currency: counter, unit: `${counter} per ${base}` };
209
+ }
210
+ if (quote?.instrumentType?.trim().toUpperCase() === "INDEX") return { kind: "points", currency: null, unit: "index points" };
211
+ const code = quote?.currency?.trim();
212
+ return code
213
+ ? { kind: "currency", currency: code, unit: currencyUnitLabel(code, { ofMajor: true }) }
214
+ : { kind: "unknown", currency: null, unit: null };
215
+ }
216
+
217
+ function historyDate(value: string): string {
218
+ return value.length > 10 ? formatUtcTime(value) : value;
219
+ }
220
+
221
+ /** The facts line under a history heading: "Currency USD", "daily bars", "2025-10-09 to 2026-10-08", "251 bars". */
222
+ export function historyFacts(unit: HistoryUnit, rows: readonly Pick<HistoryRow, "date" | "interval">[]): string[] {
223
+ const first = rows[0]?.date;
224
+ const last = rows.at(-1)?.date;
225
+ const interval = rows[0]?.interval;
226
+ return [
227
+ unit.kind === "pair" ? unit.unit! : unit.kind === "points" ? "Index points" : `Currency ${unit.unit ?? "unknown"}`,
228
+ interval ? `${BAR_INTERVAL_LABELS[interval]} bars` : "",
229
+ first && last ? (first === last ? historyDate(first) : `${historyDate(first)} to ${historyDate(last)}`) : "",
230
+ `${rows.length} ${rows.length === 1 ? "bar" : "bars"}`,
231
+ ].filter(Boolean);
232
+ }
233
+
234
+ /**
235
+ * What an export should know that its rows do not show: bars coarser than the
236
+ * range is served in, or a first bar well after the start asked for, as for a
237
+ * listing younger than the range. Weekends and holidays are within tolerance.
238
+ */
239
+ export function historyNotes(
240
+ range: TimeRange,
241
+ interval: ManualChartResolution | null,
242
+ firstDate: string | undefined,
243
+ now = Date.now(),
244
+ ): string[] {
245
+ if (!firstDate) return [];
246
+ const notes: string[] = [];
247
+ const expected = RANGE_HISTORY_RESOLUTION[range];
248
+ if (interval && CHART_RESOLUTION_STEP_MS[interval] > CHART_RESOLUTION_STEP_MS[expected]) {
249
+ const from = interval === "1mo" ? firstDate.slice(0, 7) : firstDate.slice(0, 10);
250
+ notes.push(`Asked ${range}, got ${BAR_INTERVAL_LABELS[interval]} bars from ${from}`);
251
+ }
252
+ const first = Date.parse(firstDate);
253
+ if (range !== "ALL" && Number.isFinite(first)) {
254
+ const tolerance = Math.max(LATE_START_MIN_MS, LATE_START_MIN_BARS * CHART_RESOLUTION_STEP_MS[interval ?? expected]);
255
+ if (first - subtractTimeRange(new Date(now), range).getTime() > tolerance) {
256
+ notes.push(`Asked ${range}, data starts ${firstDate.slice(0, 10)} (first available bar)`);
257
+ }
258
+ }
259
+ return notes;
260
+ }
261
+
262
+ /** "37 of 251 bars have high below open or close in the source data; ...", or null when every bar agrees. */
263
+ export function historyFlagNote(rows: readonly Pick<HistoryRow, "flag">[]): string | null {
264
+ const flagged = rows.filter((row) => row.flag);
265
+ if (flagged.length === 0) return null;
266
+ const codes = new Set(flagged.flatMap((row) => row.flag!.split(",")));
267
+ const kinds = [
268
+ codes.has("high<open") || codes.has("high<close") ? "high below open or close" : "",
269
+ codes.has("low>open") || codes.has("low>close") ? "low above open or close" : "",
270
+ codes.has("low>high") ? "low above high" : "",
271
+ ].filter(Boolean);
272
+ const count = `${flagged.length} of ${rows.length} ${flagged.length === 1 && rows.length === 1 ? "bar" : "bars"}`;
273
+ return `${count} ${flagged.length === 1 ? "has" : "have"} ${kinds.join(", or ")} in the source data; their prices are left blank`;
274
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Well-known index roots that people type bare. As a bare symbol each one is
3
+ * another instrument (MOVE is Corvex, MID a fund) or nothing at all (VIX); the
4
+ * index itself is quoted as ^ROOT. Every entry is offered as a command to run,
5
+ * so only roots whose ^ symbol returns a quote belong here.
6
+ */
7
+ const INDEX_ROOTS = new Map([
8
+ ["VIX", "Cboe Volatility Index"],
9
+ ["VVIX", "Cboe VIX of VIX Index"],
10
+ ["VIX9D", "Cboe S&P 500 9-Day Volatility Index"],
11
+ ["VIX3M", "Cboe S&P 500 3-Month Volatility Index"],
12
+ ["VXN", "Cboe Nasdaq-100 Volatility Index"],
13
+ ["OVX", "Cboe Crude Oil ETF Volatility Index"],
14
+ ["GVZ", "Cboe Gold ETF Volatility Index"],
15
+ ["SKEW", "Cboe SKEW Index"],
16
+ ["TNX", "Cboe 10-Year Treasury Yield Index"],
17
+ ["SPX", "S&P 500 Index"],
18
+ ["OEX", "S&P 100 Index"],
19
+ ["MID", "S&P MidCap 400 Index"],
20
+ ["DJI", "Dow Jones Industrial Average"],
21
+ ["NDX", "Nasdaq-100 Index"],
22
+ ["SOX", "PHLX Semiconductor Index"],
23
+ ["RUT", "Russell 2000 Index"],
24
+ ["RUI", "Russell 1000 Index"],
25
+ ["RUA", "Russell 3000 Index"],
26
+ ["MOVE", "ICE BofA MOVE Index"],
27
+ ["NYA", "NYSE Composite Index"],
28
+ ]);
29
+
30
+ export interface IndexRoot {
31
+ /** The index's own symbol: ^VIX. */
32
+ symbol: string;
33
+ name: string;
34
+ }
35
+
36
+ /** The index a bare root names (VIX is ^VIX); null for any other symbol, ^VIX included. */
37
+ export function indexRootFor(symbol: string): IndexRoot | null {
38
+ const root = symbol.trim().toUpperCase();
39
+ const name = INDEX_ROOTS.get(root);
40
+ return name ? { symbol: `^${root}`, name } : null;
41
+ }
42
+
43
+ /** "^VIX Cboe Volatility Index (try: gloomberb quote ^VIX)", under a bare root that found nothing. */
44
+ export function indexRootTryLine(root: IndexRoot, command = "quote"): string {
45
+ return `${root.symbol} ${root.name} (try: gloomberb ${command} ${root.symbol})`;
46
+ }
47
+
48
+ /** "also ^MOVE ICE BofA MOVE Index (gloomberb quote ^MOVE)", under a bare root that quoted another instrument. */
49
+ export function indexRootAlsoLine(root: IndexRoot, command = "quote"): string {
50
+ return `also ${root.symbol} ${root.name} (gloomberb ${command} ${root.symbol})`;
51
+ }
package/src/cli/index.ts CHANGED
@@ -16,14 +16,16 @@ import {
16
16
  renderCliHelp,
17
17
  renderCommandHelp,
18
18
  suggestCliCommand,
19
+ TABLE_SECTION_OPTION,
19
20
  type CliHelpEntry,
20
21
  } from "./help";
21
22
  import { parsePaneFunctionArgs } from "./pane-functions/options";
22
- import { fail, inferCliErrorOptions, printCliError } from "./errors";
23
- import { setCliColorEnabledOverride } from "../utils/cli-output";
23
+ import { checkCliCommandOptions } from "./command-options";
24
+ import { asUsageError, fail, inferCliErrorOptions, printCliError } from "./errors";
25
+ import { setCliColorEnabledOverride, setCliWidthOverride } from "../utils/cli-output";
24
26
  import { search, searchCandidatesForCli, buildSearchReport } from "./commands/search";
25
27
  import { ticker } from "./commands/ticker";
26
- import { takeOption } from "./commands/command-utils";
28
+ import { requireOneArg, takeOption } from "./commands/command-utils";
27
29
  import { EXCHANGE_OPTION } from "./listing-arg";
28
30
  import { apiCliCommand } from "./commands/api";
29
31
  import { marketDataCliCommands } from "./commands/market";
@@ -107,11 +109,8 @@ function createCoreCliCommands(
107
109
  execute: async (rawArgs, ctx) => {
108
110
  const args = [...rawArgs];
109
111
  const exchange = takeOption(args, "--exchange");
110
- const symbol = args[0];
111
- if (!symbol) {
112
- ctx.fail("Usage: gloomberb ticker <symbol>");
113
- }
114
- await ticker(symbol!, {
112
+ const symbol = requireOneArg(args, "ticker <symbol>", "symbol", ctx);
113
+ await ticker(symbol, {
115
114
  exchange,
116
115
  initMarketData: ctx.initMarketData,
117
116
  fail: ctx.fail,
@@ -128,7 +127,7 @@ function createCoreCliCommands(
128
127
  usage: ["catalog [query] [--all] [--bot-safe]"],
129
128
  options: [
130
129
  { flags: "--all", description: "List every match instead of the first 25" },
131
- { flags: "--bot-safe", description: "Only functions with a verified unattended report" },
130
+ { flags: "--bot-safe, --botsafe", description: "Only functions with a verified unattended report" },
132
131
  ],
133
132
  examples: ["catalog", "catalog options", "catalog HP"],
134
133
  },
@@ -146,12 +145,17 @@ function createCoreCliCommands(
146
145
  options: [
147
146
  { flags: "--<option> <value>", description: "A function setting; gloomberb catalog <function> lists them" },
148
147
  { flags: "--require-bot-safe", description: "Fail unless the function has a verified, complete report" },
148
+ {
149
+ ...TABLE_SECTION_OPTION,
150
+ description: `${TABLE_SECTION_OPTION.description} (CALLS keeps --section for the transcript part)`,
151
+ },
149
152
  EXCHANGE_OPTION,
150
153
  ],
151
154
  examples: [
152
155
  "fn HP AAPL",
153
156
  "fn ANR SAN:EPA",
154
157
  "fn CBR --json",
158
+ "fn WEI --csv --section europe > europe.csv",
155
159
  "fn 13F AAPL --view=ticker-holdings",
156
160
  "fn OVME --spot 100 --strike 100 --days 30 --volatility 25",
157
161
  ],
@@ -172,9 +176,10 @@ function createCoreCliCommands(
172
176
  { flags: "--output <path>", description: "PNG to write; defaults to gloomberb-<function>-<argument>.png in this folder" },
173
177
  { flags: "--width <px>", description: "Image width, 720 to 2400 (default 1280)" },
174
178
  { flags: "--height <px>", description: "Image height, 360 to 1800 (default 720)" },
175
- { flags: "--theme <id>", description: "Render with another theme, such as amber or green" },
179
+ { flags: "--theme <id>", description: "Render with another theme, such as amber or colorblind; gloomberb config themes lists them" },
176
180
  { flags: "--scale <n>", description: "Text scale from 0.5 to 4 (default 1)" },
177
181
  { flags: "--watermark <label>", description: "Label drawn in the pane title bar" },
182
+ { flags: "--no-status", description: "Leave out the dated status line (as-of, delay, market hours) drawn in the pane footer" },
178
183
  { flags: "--<option> <value>", description: "A function setting; gloomberb catalog <function> lists them" },
179
184
  EXCHANGE_OPTION,
180
185
  ],
@@ -183,6 +188,7 @@ function createCoreCliCommands(
183
188
  "shot HP BHP:ASX",
184
189
  "shot DDIS MSFT --tab history",
185
190
  "shot HP NVDA --width 1600 --theme green",
191
+ "shot QQ AAPL,MSFT --no-status",
186
192
  ],
187
193
  },
188
194
  execute: async (args, ctx) => {
@@ -332,11 +338,12 @@ export async function dispatchCli(args: string[], options: DispatchCliOptions =
332
338
  try {
333
339
  parsed = parseCliGlobalArgs(args);
334
340
  } catch (error) {
335
- printCliError(error, inferCliErrorOptions(args));
341
+ printCliError(asUsageError(error), inferCliErrorOptions(args));
336
342
  process.exitCode = 1;
337
343
  return { kind: "handled" };
338
344
  }
339
345
  setCliColorEnabledOverride(parsed.options.color);
346
+ setCliWidthOverride(parsed.options.width ?? null);
340
347
  const command = parsed.args[0] ?? (parsed.help ? "help" : undefined);
341
348
  if (!command) {
342
349
  return { kind: "unhandled" };
@@ -354,13 +361,14 @@ export async function dispatchCli(args: string[], options: DispatchCliOptions =
354
361
  const target = helpOnly ? registry.lookup.get("help")! : resolved;
355
362
  const commandArgs = helpOnly ? [resolved.command.name] : parsed.args.slice(1);
356
363
  try {
364
+ if (!helpOnly && resolved.builtin) checkCliCommandOptions(resolved.command, commandArgs, parsed.literalStart - 1);
357
365
  const result = await target.command.execute(
358
366
  commandArgs,
359
- createCliCommandContext(target.ownerId, registry, parsed.options),
367
+ createCliCommandContext(target.ownerId, registry, parsed.options, commandArgs),
360
368
  );
361
369
  return normalizeCliDispatchResult(result);
362
370
  } catch (error) {
363
- printCliError(error, parsed.options, { command: resolved.command.name });
371
+ printCliError(error, parsed.options, { command: resolved.command.name, args: commandArgs });
364
372
  process.exitCode = 1;
365
373
  return { kind: "handled" };
366
374
  }
@@ -8,10 +8,13 @@ import {
8
8
  canonicalExchange,
9
9
  exchangeLabel,
10
10
  isKnownExchangeCode,
11
+ isUsListingExchange,
11
12
  parsePublicTickerKey,
12
13
  publicTickerKey,
14
+ resolveExchangeTimeZone,
13
15
  } from "../utils/exchanges";
14
16
  import { withMarketData } from "./scoped-context";
17
+ import { isNoProviderMessage } from "../sources/provider-errors";
15
18
 
16
19
  /** The `--exchange` option of every command that takes a ticker. */
17
20
  export const EXCHANGE_OPTION = {
@@ -156,32 +159,237 @@ export async function failIfNotTraded(
156
159
  if (message) ctx.fail(message);
157
160
  }
158
161
 
162
+ /** What a command asked a listing for, to name it when the listing has none: `history` asks for "history". */
163
+ export interface ListingDataRequest {
164
+ /** The command a retry runs, with any options to repeat: `history`, `history --range 5Y`. */
165
+ command: string;
166
+ /** What the listing has none of: "history", "a quote". */
167
+ noun: string;
168
+ }
169
+
170
+ /**
171
+ * The router's answer when no source serves a symbol: "No history provider available for 2222",
172
+ * or "Not a ticker: 2222." when the service found no listing for it.
173
+ */
174
+ export function isNoProviderError(error: unknown): boolean {
175
+ return isNoProviderMessage(error instanceof Error ? error.message : String(error ?? ""));
176
+ }
177
+
178
+ const MAX_OTHER_LISTINGS = 5;
179
+
180
+ type BareListing = Pick<CliListing, "symbol" | "exchange"> & Partial<Pick<CliListing, "saved">>;
181
+
182
+ /** Where a bare symbol went and where else it trades, in the order they are tried and named. */
183
+ interface BareListingVenues {
184
+ /** Canonical code of the venue the symbol went to. */
185
+ exchange: string;
186
+ /** That venue as search lists it; null for a saved venue search no longer lists. */
187
+ resolved: ListingVenue | null;
188
+ /** The symbol's other venues, in search order. */
189
+ others: ListingVenue[];
190
+ }
191
+
192
+ /** A bare symbol goes to its saved listing, else to the one search lists first. Null when it named its exchange. */
193
+ async function bareListingVenues(listing: BareListing, deps: ListingDeps): Promise<BareListingVenues | null> {
194
+ if (listing.exchange) return null;
195
+ const venues = await listingVenues(listing.symbol, deps).catch(() => []);
196
+ const savedExchange = canonicalExchange(listing.saved?.metadata.exchange);
197
+ const resolved = venues.find((venue) => venue.exchange === savedExchange) ?? (savedExchange ? null : venues[0] ?? null);
198
+ const exchange = resolved?.exchange ?? savedExchange;
199
+ if (!exchange) return null;
200
+ return { exchange, resolved, others: venues.filter((venue) => venue !== resolved && venue.exchange !== savedExchange) };
201
+ }
202
+
203
+ interface OtherListingsMessage {
204
+ message: string;
205
+ details: string;
206
+ /** SAN:EPA, for each other listing. */
207
+ others: string[];
208
+ }
209
+
210
+ function describeOtherListings(
211
+ listing: BareListing,
212
+ { exchange, resolved, others }: BareListingVenues,
213
+ request: ListingDataRequest,
214
+ ): OtherListingsMessage | null {
215
+ if (others.length === 0) return null;
216
+ const name = resolved?.name || listing.saved?.metadata.name?.trim() || "";
217
+ const [command, ...options] = request.command.split(" ");
218
+ const retry = (key: string) => ["gloomberb", command, key, ...options].join(" ");
219
+ const lines = others.slice(0, MAX_OTHER_LISTINGS).map((venue) => {
220
+ const key = `${listing.symbol}:${venue.exchange}`;
221
+ return `${key}${venue.name ? ` ${venue.name}` : ""} (try: ${retry(key)})`;
222
+ });
223
+ if (others.length > MAX_OTHER_LISTINGS) lines.push(`and ${others.length - MAX_OTHER_LISTINGS} more: gloomberb search ${listing.symbol}`);
224
+ return {
225
+ message: `${listing.symbol} resolved to ${name ? `${name} (${exchange})` : exchange}, which has no ${request.noun}.`,
226
+ details: lines.length === 1 ? `Other listings: ${lines[0]}` : ["Other listings:", ...lines.map((line) => ` ${line}`)].join("\n"),
227
+ others: others.map((venue) => `${listing.symbol}:${venue.exchange}`),
228
+ };
229
+ }
230
+
231
+ /**
232
+ * For a bare symbol whose request found no source: the listing it went to and
233
+ * the symbol's other listings, each with the command that asks for it, such
234
+ * as "2222 resolved to Kotobuki Spirits Co., Ltd. (JPX), which has no
235
+ * history." and "2222:TADAWUL Saudi Arabian Oil Co. (try: gloomberb history
236
+ * 2222:TADAWUL)". Null when the symbol named its exchange or has no other
237
+ * listing. It searches, so ask only after a request failed.
238
+ */
239
+ async function otherListingsMessage(
240
+ listing: BareListing,
241
+ deps: ListingDeps,
242
+ request: ListingDataRequest,
243
+ ): Promise<OtherListingsMessage | null> {
244
+ const venues = await bareListingVenues(listing, deps);
245
+ return venues && describeOtherListings(listing, venues, request);
246
+ }
247
+
248
+ /** The listing on one venue, as `SYM:EXCH` names it. */
249
+ function namedListing(listing: CliListing, exchange: string): CliListing {
250
+ const key = `${listing.symbol}:${exchange}`;
251
+ const saved = canonicalExchange(listing.saved?.metadata.exchange) === exchange ? listing.saved : null;
252
+ return { symbol: listing.symbol, exchange, key, saved, request: { symbol: key, exchange } };
253
+ }
254
+
255
+ async function quoteListings(
256
+ dataProvider: DataProvider,
257
+ listings: readonly CliListing[],
258
+ refresh: boolean,
259
+ ): Promise<Array<Quote | null>> {
260
+ if (listings.length === 0) return [];
261
+ const targets = listings.map((listing) => listing.request);
262
+ if (dataProvider.getQuotesBatch) {
263
+ const results = await dataProvider.getQuotesBatch(targets, { forceRefresh: refresh }).catch(() => []);
264
+ return targets.map((target, index) => (results.find((result) => result.target === target) ?? results[index])?.quote ?? null);
265
+ }
266
+ return Promise.all(targets.map((target) => dataProvider.getQuote(target.symbol, target.exchange, {
267
+ cacheMode: refresh ? "refresh" : "default",
268
+ }).catch(() => null)));
269
+ }
270
+
271
+ export type BareListingQuote =
272
+ | {
273
+ kind: "quoted";
274
+ /** The listing that has a quote: SXR8:XETRA. */
275
+ listing: CliListing;
276
+ quote: Quote;
277
+ /** "SXR8 -> XETRA (other listings: BUD, MUNICH)". */
278
+ note: string;
279
+ }
280
+ | ({ kind: "missing" } & OtherListingsMessage);
281
+
282
+ /**
283
+ * The quote for a bare symbol no source quoted. The data service reads a
284
+ * symbol with no US listing on its home listing abroad (`bareListingNote`),
285
+ * so this is left for one it reads on none, as a code several companies list
286
+ * (2222 is Kotobuki Spirits in Tokyo and Saudi Aramco in Riyadh), and for a
287
+ * home listing with no quote. Search knows where the symbol trades: each
288
+ * listing is asked again by its own key (2222:JPX), as a named one is. The
289
+ * listing the symbol resolved to goes first, then the others together, and
290
+ * the first in search order with a quote is the answer. When none has one,
291
+ * the `otherListingsMessage` failure. Null when the symbol named its exchange
292
+ * (a named venue never switches) or search knows no listing of it. It
293
+ * searches, so ask only after a request failed.
294
+ */
295
+ export async function quoteBareListing(
296
+ listing: CliListing,
297
+ deps: ListingDeps,
298
+ request: ListingDataRequest,
299
+ { refresh = false }: { refresh?: boolean } = {},
300
+ ): Promise<BareListingQuote | null> {
301
+ const venues = await bareListingVenues(listing, deps);
302
+ if (!venues) return null;
303
+ const first = [namedListing(listing, venues.exchange)];
304
+ const rest = venues.others.map((venue) => namedListing(listing, venue.exchange));
305
+ let tried = first;
306
+ let quotes = await quoteListings(deps.dataProvider, first, refresh);
307
+ if (!quotes[0] && rest.length > 0) {
308
+ tried = [...first, ...rest];
309
+ quotes = [...quotes, ...await quoteListings(deps.dataProvider, rest, refresh)];
310
+ }
311
+ const found = quotes.findIndex(Boolean);
312
+ if (found < 0) {
313
+ const other = describeOtherListings(listing, venues, request);
314
+ return other && { kind: "missing", ...other };
315
+ }
316
+ const answer = tried[found]!;
317
+ const without = tried.slice(0, found).map((entry) => entry.exchange);
318
+ // Listings not asked, or asked and quoted, are still there to name.
319
+ const others = [...first, ...rest]
320
+ .filter((entry, index) => entry !== answer && (index >= tried.length || quotes[index]))
321
+ .map((entry) => entry.exchange);
322
+ const more = others.length - MAX_OTHER_LISTINGS;
323
+ const aside = [
324
+ without.length > 0 ? `no quote on ${without.join(", ")}` : "",
325
+ others.length > 0
326
+ ? `other listings: ${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloomberb search ${listing.symbol}` : ""}`
327
+ : "",
328
+ ].filter(Boolean).join("; ");
329
+ return {
330
+ kind: "quoted",
331
+ listing: answer,
332
+ quote: quotes[found]!,
333
+ note: `${listing.symbol} -> ${answer.exchange}${aside ? ` (${aside})` : ""}`,
334
+ };
335
+ }
336
+
337
+ // The zones of the US exchanges; a venue in another is abroad.
338
+ const US_TIME_ZONES = new Set(["America/New_York", "America/Chicago"]);
339
+
340
+ /**
341
+ * "SXR8 -> XETRA" for a bare symbol the data service read on its home listing
342
+ * abroad, as it reads a symbol with no US listing whose listings belong to one
343
+ * company or fund: a row that shows the symbol as typed says which listing it
344
+ * is. Null for a named or saved venue, and for a venue the app does not place
345
+ * outside the US (a US exchange, OTC, a futures exchange).
346
+ */
347
+ export function bareListingNote(
348
+ listing: Pick<CliListing, "symbol" | "request">,
349
+ quote: Pick<Quote, "listingExchangeName" | "exchangeName"> | null | undefined,
350
+ ): string | null {
351
+ if (listing.request.exchange || !quote || !/^[A-Z0-9]{1,10}$/.test(listing.symbol)) return null;
352
+ const venue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
353
+ const zone = resolveExchangeTimeZone(venue);
354
+ return zone && !US_TIME_ZONES.has(zone) && !isUsListingExchange(venue) ? `${listing.symbol} -> ${venue}` : null;
355
+ }
356
+
159
357
  /**
160
358
  * A data request for the listings a command named. When it throws, or `isMiss`
161
359
  * calls what it returned empty, and a listing's exchange is one its symbol does
162
360
  * not trade on, the command ends in that message instead of the request's own
163
- * "unavailable". A request that succeeds costs no extra lookup.
361
+ * "unavailable". With `request`, a bare symbol no source serves ends naming
362
+ * its other listings (`otherListingsMessage`). A request that succeeds costs no
363
+ * extra lookup.
164
364
  */
165
365
  export async function loadForListing<T>(
166
- listings: Pick<ListingArg, "symbol" | "exchange"> | readonly Pick<ListingArg, "symbol" | "exchange">[],
366
+ listings: ListingForLoad | readonly ListingForLoad[],
167
367
  deps: ListingDeps,
168
368
  ctx: Pick<CliCommandContext, "fail">,
169
369
  load: () => Promise<T>,
170
370
  isMiss?: (value: T) => boolean,
371
+ request?: ListingDataRequest,
171
372
  ): Promise<T> {
172
- const named = ("symbol" in listings ? [listings] : listings).filter((listing) => listing.exchange);
373
+ const all = "symbol" in listings ? [listings] : listings;
374
+ const named = all.filter((listing) => listing.exchange);
173
375
  const checkVenues = () => Promise.all(named.map((listing) => failIfNotTraded(listing, deps, ctx)));
174
376
  let value: T;
175
377
  try {
176
378
  value = await load();
177
379
  } catch (error) {
178
380
  await checkVenues();
381
+ if (request && all.length === 1 && isNoProviderError(error)) {
382
+ const other = await otherListingsMessage(all[0]!, deps, request);
383
+ if (other) ctx.fail(other.message, other.details);
384
+ }
179
385
  throw error;
180
386
  }
181
387
  if (isMiss?.(value)) await checkVenues();
182
388
  return value;
183
389
  }
184
390
 
391
+ type ListingForLoad = Pick<ListingArg, "symbol" | "exchange"> & Partial<Pick<CliListing, "saved">>;
392
+
185
393
  /** The saved ticker for a listing: under its own key, or a saved symbol whose venue it is. */
186
394
  export async function loadSavedListing(store: ListingStore, listing: ListingArg): Promise<TickerRecord | null> {
187
395
  if (!listing.exchange) return store.loadTicker(listing.symbol);