gloomberb 0.18.1 → 0.18.3

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 (416) hide show
  1. package/package.json +1 -1
  2. package/patches/electrobun@1.18.1.patch +106 -5
  3. package/src/api-client/commodity-balances.ts +6 -0
  4. package/src/api-client/company-kpis.ts +9 -0
  5. package/src/api-client/crypto-markets.ts +9 -0
  6. package/src/api-client/data.ts +21 -5
  7. package/src/api-client/equity-screener.ts +2 -0
  8. package/src/api-client/index.ts +2 -0
  9. package/src/api-client/request.ts +5 -3
  10. package/src/api-client/telemetry.ts +17 -0
  11. package/src/api-client/types.ts +5 -0
  12. package/src/app/global-shortcuts.ts +1 -16
  13. package/src/app/pane-runtime/plugin-bindings.ts +3 -1
  14. package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
  15. package/src/app/runtime/plugin-bindings.ts +6 -2
  16. package/src/app/runtime/update.ts +28 -8
  17. package/src/architecture/pane-tabs-allowlist.json +65 -0
  18. package/src/brokers/logos.ts +66 -0
  19. package/src/brokers/profile-form.ts +3 -0
  20. package/src/brokers/remote-broker-adapter.ts +13 -3
  21. package/src/capabilities/index.ts +1 -1
  22. package/src/capabilities/service-capabilities.ts +7 -1
  23. package/src/cli/base-converter.ts +60 -18
  24. package/src/cli/commands/market.ts +354 -187
  25. package/src/cli/commands/news-search.ts +35 -8
  26. package/src/cli/commands/system.ts +35 -1
  27. package/src/cli/commands/ticker.ts +167 -60
  28. package/src/cli/currency-hints.ts +27 -0
  29. package/src/cli/fx-pair.ts +41 -4
  30. package/src/cli/glossary.ts +57 -4
  31. package/src/cli/help.ts +1 -1
  32. package/src/cli/helpers.ts +36 -3
  33. package/src/cli/index.ts +12 -1
  34. package/src/cli/issuer-membership.ts +103 -0
  35. package/src/cli/known-gaps.ts +37 -1
  36. package/src/cli/listing-arg.ts +51 -3
  37. package/src/cli/name-entries.ts +51 -12
  38. package/src/cli/pane-functions/capabilities.ts +2 -0
  39. package/src/cli/pane-functions/catalog.ts +19 -1
  40. package/src/cli/pane-functions/discovery.ts +15 -1
  41. package/src/cli/pane-functions/dom.ts +11 -5
  42. package/src/cli/pane-functions/freshness.ts +4 -4
  43. package/src/cli/pane-functions/function-help.ts +27 -3
  44. package/src/cli/pane-functions/headless.ts +31 -11
  45. package/src/cli/pane-functions/index.ts +67 -7
  46. package/src/cli/pane-functions/lock-note.ts +7 -4
  47. package/src/cli/pane-functions/resolver.ts +21 -6
  48. package/src/cli/quote-answer.ts +179 -0
  49. package/src/cli/report-tables.ts +136 -18
  50. package/src/cli/result.ts +12 -4
  51. package/src/cli/series-search.ts +2 -1
  52. package/src/cli/symbol-list.ts +47 -11
  53. package/src/cli/types.ts +4 -0
  54. package/src/components/chart/composite/composite-chart.tsx +25 -7
  55. package/src/components/chart/composite/drawing-store.ts +26 -6
  56. package/src/components/chart/composite/format.ts +13 -1
  57. package/src/components/chart/composite/interactions.ts +2 -1
  58. package/src/components/chart/composite/level-labels.ts +30 -0
  59. package/src/components/chart/composite/levels.ts +17 -6
  60. package/src/components/chart/composite/panel-surface.tsx +69 -8
  61. package/src/components/chart/composite/types.ts +6 -0
  62. package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
  63. package/src/components/command-bar/routes/root/command-items.ts +32 -6
  64. package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
  65. package/src/components/data-table/view.tsx +23 -3
  66. package/src/components/index.ts +3 -0
  67. package/src/components/layout/detached-pane-shell.tsx +1 -0
  68. package/src/components/layout/floating-pane.tsx +1 -0
  69. package/src/components/layout/header.tsx +228 -55
  70. package/src/components/layout/pane/content.tsx +16 -14
  71. package/src/components/layout/pane/footer/index.tsx +22 -3
  72. package/src/components/layout/pane/footer/model.ts +99 -4
  73. package/src/components/layout/pane/header-tabs.tsx +15 -1
  74. package/src/components/layout/pane/header.tsx +10 -3
  75. package/src/components/layout/pane/index.tsx +1 -0
  76. package/src/components/layout/pane/pane-tabs.tsx +9 -1
  77. package/src/components/onboarding/onboarding-frame.tsx +9 -11
  78. package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
  79. package/src/components/onboarding/wizard-model.ts +10 -3
  80. package/src/components/ui/button.tsx +11 -4
  81. package/src/components/ui/choice-dialog.tsx +8 -5
  82. package/src/components/ui/display.tsx +11 -6
  83. package/src/components/ui/fields.tsx +100 -20
  84. package/src/components/ui/icon.tsx +6 -1
  85. package/src/components/ui/list-row-lines.tsx +87 -0
  86. package/src/components/ui/list-view.tsx +14 -0
  87. package/src/components/ui/logo-mark.tsx +64 -0
  88. package/src/components/ui/number-step.ts +54 -0
  89. package/src/components/ui/select-button.tsx +5 -4
  90. package/src/components/ui/select-field.tsx +3 -3
  91. package/src/components/ui/stat-grid.tsx +28 -3
  92. package/src/components/ui/status.tsx +20 -4
  93. package/src/components/ui/tabs.tsx +9 -1
  94. package/src/components/ui/toggle.tsx +17 -3
  95. package/src/core/app-services.ts +4 -0
  96. package/src/core/state/app/instrument.ts +8 -3
  97. package/src/data/config/store/normalize.ts +4 -1
  98. package/src/data/plugin-cache.ts +32 -6
  99. package/src/docs/snapshot.json +5 -5
  100. package/src/i18n/es.ts +31 -3
  101. package/src/i18n/ja.ts +31 -3
  102. package/src/i18n/ko.ts +31 -3
  103. package/src/i18n/zh-cn.ts +32 -4
  104. package/src/i18n/zh-tw.ts +32 -4
  105. package/src/market-data/commodity-names.ts +80 -0
  106. package/src/market-data/coordinator/auxiliary.ts +3 -1
  107. package/src/market-data/coordinator/chart.ts +3 -1
  108. package/src/market-data/futures-board.ts +38 -1
  109. package/src/market-data/futures-contract.ts +91 -0
  110. package/src/market-data/fx-closes.ts +78 -1
  111. package/src/market-data/market/change-basis.ts +219 -0
  112. package/src/market-data/market/format.ts +28 -12
  113. package/src/market-data/market/freshness.ts +38 -0
  114. package/src/market-data/market/status.ts +55 -9
  115. package/src/market-data/market/year-to-date.ts +46 -0
  116. package/src/market-data/quotes/contributions.ts +12 -0
  117. package/src/market-data/quotes/delisting.ts +34 -0
  118. package/src/market-data/quotes/live-valuation.ts +23 -0
  119. package/src/market-data/quotes/resolution.ts +8 -2
  120. package/src/market-data/tax-equivalent.ts +10 -2
  121. package/src/notifications/app-notifier.ts +2 -2
  122. package/src/notifications/notification-log.ts +8 -6
  123. package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
  124. package/src/plugins/builtin/alerts/cli.ts +26 -18
  125. package/src/plugins/builtin/alerts/events.ts +1 -0
  126. package/src/plugins/builtin/alerts/headless.ts +1 -1
  127. package/src/plugins/builtin/alerts/index.tsx +13 -4
  128. package/src/plugins/builtin/alerts/research-rules.ts +1 -0
  129. package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
  130. package/src/plugins/builtin/attention/pane.tsx +16 -14
  131. package/src/plugins/builtin/attention/trending.tsx +8 -6
  132. package/src/plugins/builtin/awards/pane.tsx +6 -6
  133. package/src/plugins/builtin/backtest/client.ts +5 -2
  134. package/src/plugins/builtin/backtest/export.ts +1 -1
  135. package/src/plugins/builtin/backtest/window.ts +16 -3
  136. package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
  137. package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
  138. package/src/plugins/builtin/broker-manager/index.tsx +20 -9
  139. package/src/plugins/builtin/broker-manager/model.ts +5 -0
  140. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
  141. package/src/plugins/builtin/broker-manager/table.ts +5 -1
  142. package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
  143. package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
  144. package/src/plugins/builtin/broker-trading/command.ts +103 -0
  145. package/src/plugins/builtin/broker-trading/controller.ts +688 -0
  146. package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
  147. package/src/plugins/builtin/broker-trading/headless.ts +76 -0
  148. package/src/plugins/builtin/broker-trading/index.tsx +77 -0
  149. package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
  150. package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
  151. package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
  152. package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
  153. package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
  154. package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
  155. package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
  156. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
  157. package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
  158. package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
  159. package/src/plugins/builtin/buildout/format.ts +1 -0
  160. package/src/plugins/builtin/buildout/model/index.ts +16 -7
  161. package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
  162. package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
  163. package/src/plugins/builtin/catalysts/client.ts +2 -2
  164. package/src/plugins/builtin/catalysts/pane.tsx +3 -3
  165. package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
  166. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  167. package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
  168. package/src/plugins/builtin/chart-composer/headless.ts +2 -1
  169. package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
  170. package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
  171. package/src/plugins/builtin/chart-composer/presets.ts +2 -2
  172. package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
  173. package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
  174. package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
  175. package/src/plugins/builtin/chat/index.tsx +1 -1
  176. package/src/plugins/builtin/chat/status-widget.tsx +1 -1
  177. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
  178. package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
  179. package/src/plugins/builtin/commodity-balances/client.ts +6 -4
  180. package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
  181. package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
  182. package/src/plugins/builtin/commodity-balances/model.ts +22 -0
  183. package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
  184. package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
  185. package/src/plugins/builtin/company-attention/pane.tsx +13 -11
  186. package/src/plugins/builtin/company-kpis/client.ts +3 -2
  187. package/src/plugins/builtin/company-kpis/headless.ts +5 -7
  188. package/src/plugins/builtin/company-kpis/model.ts +85 -2
  189. package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
  190. package/src/plugins/builtin/composite-plugins.ts +3 -2
  191. package/src/plugins/builtin/correlation/headless.ts +2 -1
  192. package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
  193. package/src/plugins/builtin/correlation/settings.ts +1 -0
  194. package/src/plugins/builtin/cot/headless.ts +1 -1
  195. package/src/plugins/builtin/cot/index.tsx +1 -1
  196. package/src/plugins/builtin/cot/model.ts +35 -12
  197. package/src/plugins/builtin/cot/pane.tsx +4 -3
  198. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  199. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
  200. package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
  201. package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
  202. package/src/plugins/builtin/crypto-board/client.ts +33 -2
  203. package/src/plugins/builtin/crypto-board/headless.ts +5 -2
  204. package/src/plugins/builtin/crypto-board/model.ts +28 -8
  205. package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
  206. package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
  207. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  208. package/src/plugins/builtin/doe/pane.tsx +1 -1
  209. package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
  210. package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
  211. package/src/plugins/builtin/econ/calendar-model.ts +35 -0
  212. package/src/plugins/builtin/econ/index.tsx +31 -4
  213. package/src/plugins/builtin/econ/print-watch.ts +279 -0
  214. package/src/plugins/builtin/equity-screener/headless.ts +30 -5
  215. package/src/plugins/builtin/equity-screener/model.ts +68 -2
  216. package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
  217. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
  218. package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
  219. package/src/plugins/builtin/exposure/client.ts +4 -1
  220. package/src/plugins/builtin/exposure/editor.tsx +3 -3
  221. package/src/plugins/builtin/exposure/evidence.ts +4 -1
  222. package/src/plugins/builtin/exposure/holdings.ts +3 -1
  223. package/src/plugins/builtin/exposure/pane.tsx +43 -21
  224. package/src/plugins/builtin/filing-events/index.tsx +9 -3
  225. package/src/plugins/builtin/futures/table.tsx +6 -3
  226. package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
  227. package/src/plugins/builtin/gpu/board.tsx +4 -1
  228. package/src/plugins/builtin/gpu/changes.tsx +10 -10
  229. package/src/plugins/builtin/gpu/equities.tsx +4 -1
  230. package/src/plugins/builtin/gpu/history.tsx +4 -2
  231. package/src/plugins/builtin/gpu/pane.tsx +5 -3
  232. package/src/plugins/builtin/holders/client.ts +2 -1
  233. package/src/plugins/builtin/holders/headless.ts +2 -1
  234. package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
  235. package/src/plugins/builtin/insider/base-values.ts +76 -0
  236. package/src/plugins/builtin/insider/client.ts +3 -0
  237. package/src/plugins/builtin/insider/headless.ts +48 -10
  238. package/src/plugins/builtin/insider/index.tsx +17 -8
  239. package/src/plugins/builtin/insider/issuer.ts +34 -4
  240. package/src/plugins/builtin/insider/signals-client.ts +2 -2
  241. package/src/plugins/builtin/insider/table-model.ts +35 -13
  242. package/src/plugins/builtin/insider/table.tsx +5 -2
  243. package/src/plugins/builtin/iv-history/events.ts +145 -0
  244. package/src/plugins/builtin/iv-history/format.ts +3 -0
  245. package/src/plugins/builtin/iv-history/headless.ts +91 -7
  246. package/src/plugins/builtin/jobs/headless.ts +2 -2
  247. package/src/plugins/builtin/jobs/lock-note.ts +14 -3
  248. package/src/plugins/builtin/jodi/pane.tsx +4 -4
  249. package/src/plugins/builtin/market-halts/model.ts +31 -6
  250. package/src/plugins/builtin/market-halts/pane.tsx +3 -0
  251. package/src/plugins/builtin/market-movers/client.ts +4 -3
  252. package/src/plugins/builtin/market-movers/screener.ts +4 -3
  253. package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
  254. package/src/plugins/builtin/members/pane.tsx +8 -3
  255. package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
  256. package/src/plugins/builtin/news/wire/index.tsx +7 -2
  257. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
  258. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
  259. package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
  260. package/src/plugins/builtin/notes/cli.ts +1 -1
  261. package/src/plugins/builtin/notes/export.ts +17 -6
  262. package/src/plugins/builtin/notes/files.ts +16 -5
  263. package/src/plugins/builtin/options/enrichment-model.ts +10 -9
  264. package/src/plugins/builtin/options/footer.ts +25 -20
  265. package/src/plugins/builtin/options/live-quotes.ts +33 -18
  266. package/src/plugins/builtin/options/model-spot.ts +70 -4
  267. package/src/plugins/builtin/options/view.tsx +118 -35
  268. package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
  269. package/src/plugins/builtin/options-positioning/headless.ts +11 -3
  270. package/src/plugins/builtin/options-positioning/model.ts +24 -0
  271. package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
  272. package/src/plugins/builtin/options-scenario/client.ts +19 -1
  273. package/src/plugins/builtin/options-scenario/headless.ts +13 -6
  274. package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
  275. package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
  276. package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
  277. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
  278. package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
  279. package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
  280. package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
  281. package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
  282. package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
  283. package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
  284. package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
  285. package/src/plugins/builtin/power/client.ts +2 -2
  286. package/src/plugins/builtin/power/pane.tsx +36 -31
  287. package/src/plugins/builtin/primary-dealers/client.ts +137 -62
  288. package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
  289. package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
  290. package/src/plugins/builtin/primary-dealers/model.ts +179 -129
  291. package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
  292. package/src/plugins/builtin/research/analyst-model.ts +5 -1
  293. package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
  294. package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
  295. package/src/plugins/builtin/risk-factors/index.tsx +12 -3
  296. package/src/plugins/builtin/scanner/feed.ts +6 -1
  297. package/src/plugins/builtin/scanner/headless.ts +1 -1
  298. package/src/plugins/builtin/scanner/lock-note.ts +3 -2
  299. package/src/plugins/builtin/seasonality/client.ts +2 -1
  300. package/src/plugins/builtin/sectors/client.ts +4 -3
  301. package/src/plugins/builtin/series-inventory.ts +1 -0
  302. package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
  303. package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
  304. package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
  305. package/src/plugins/builtin/shared/free-routes.ts +24 -0
  306. package/src/plugins/builtin/shared/headless-options.ts +11 -0
  307. package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
  308. package/src/plugins/builtin/shared/research-trading.tsx +64 -0
  309. package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
  310. package/src/plugins/builtin/shared/volatility/options.ts +2 -1
  311. package/src/plugins/builtin/social-mentions/client.ts +2 -1
  312. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  313. package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
  314. package/src/plugins/builtin/themes/headless.ts +8 -2
  315. package/src/plugins/builtin/themes/model.ts +43 -10
  316. package/src/plugins/builtin/themes/pane.tsx +8 -6
  317. package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
  318. package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
  319. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
  320. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
  321. package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
  322. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
  323. package/src/plugins/builtin/time-sales/pane.tsx +2 -2
  324. package/src/plugins/builtin/trace-bonds/client.ts +14 -3
  325. package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
  326. package/src/plugins/builtin/vol-surface/client.ts +23 -3
  327. package/src/plugins/builtin/vol-surface/headless.ts +33 -14
  328. package/src/plugins/builtin/vol-surface/model.ts +30 -10
  329. package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
  330. package/src/plugins/builtin/vol-surface/stored.ts +34 -3
  331. package/src/plugins/builtin/vol-surface/tables.ts +14 -0
  332. package/src/plugins/builtin-editorial.ts +1 -1
  333. package/src/plugins/compat.ts +3 -0
  334. package/src/plugins/external-runtime.ts +5 -0
  335. package/src/plugins/loader.ts +6 -0
  336. package/src/plugins/plugin-icon.ts +61 -0
  337. package/src/plugins/registry/host-actions.ts +2 -1
  338. package/src/plugins/registry/index.ts +9 -1
  339. package/src/plugins/runtime/context.tsx +5 -0
  340. package/src/plugins/runtime/index.tsx +3 -0
  341. package/src/portfolio/column-values.ts +13 -4
  342. package/src/portfolio/cost-fx.ts +5 -28
  343. package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
  344. package/src/public/broker.ts +8 -0
  345. package/src/public/market-data.ts +5 -0
  346. package/src/renderers/dom/desktop/control-styles.ts +7 -1
  347. package/src/renderers/dom/desktop/controls.tsx +40 -8
  348. package/src/renderers/dom/desktop/icons.tsx +14 -0
  349. package/src/renderers/dom/desktop/list-view.tsx +51 -19
  350. package/src/renderers/dom/desktop/menu.tsx +10 -1
  351. package/src/renderers/dom/desktop/query-bar.tsx +2 -0
  352. package/src/renderers/dom/desktop/select-field.tsx +8 -6
  353. package/src/renderers/dom/host/style.ts +6 -1
  354. package/src/renderers/dom/host/tabs.tsx +22 -5
  355. package/src/renderers/dom/host/top-surface.ts +127 -0
  356. package/src/renderers/dom/styles.css +51 -5
  357. package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
  358. package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
  359. package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
  360. package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
  361. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  362. package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
  363. package/src/renderers/electrobun/view/external-plugins.ts +1 -0
  364. package/src/renderers/electrobun/view/news-source.ts +4 -2
  365. package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
  366. package/src/renderers/opentui/start.tsx +7 -0
  367. package/src/series/validate.ts +2 -2
  368. package/src/sources/gloomberb-cloud/index.ts +57 -17
  369. package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
  370. package/src/sources/history-result.ts +3 -0
  371. package/src/sources/provider-errors.ts +3 -1
  372. package/src/sources/provider-router/history.ts +49 -6
  373. package/src/sources/provider-router/index.ts +18 -1
  374. package/src/state/app/activity.ts +13 -0
  375. package/src/sync/controller.ts +4 -0
  376. package/src/sync/core-contributors.ts +62 -11
  377. package/src/sync/local-only.ts +121 -0
  378. package/src/sync/react.ts +6 -0
  379. package/src/sync/types.ts +3 -0
  380. package/src/telemetry/crash-reports.ts +4 -0
  381. package/src/telemetry/update-failures-node.ts +59 -0
  382. package/src/telemetry/update-failures.ts +307 -0
  383. package/src/tickers/company-name-listing.ts +9 -7
  384. package/src/tickers/company-name.ts +43 -0
  385. package/src/tickers/listing-fate.ts +57 -8
  386. package/src/tickers/search/index.ts +24 -3
  387. package/src/time-series/price-comparison.ts +35 -0
  388. package/src/time-series/resolve.ts +52 -11
  389. package/src/time-series/spec.ts +1 -1
  390. package/src/time-series/types.ts +5 -0
  391. package/src/types/broker.ts +56 -1
  392. package/src/types/config.ts +6 -0
  393. package/src/types/data-provider.ts +11 -1
  394. package/src/types/financials.ts +23 -0
  395. package/src/types/headless.ts +19 -2
  396. package/src/types/instrument.ts +4 -0
  397. package/src/types/issuer-listings.ts +28 -0
  398. package/src/types/plugin.ts +24 -6
  399. package/src/types/price-history.ts +6 -1
  400. package/src/types/trading.ts +67 -0
  401. package/src/ui/host.tsx +4 -0
  402. package/src/updater/failure.ts +163 -0
  403. package/src/updater/index.ts +87 -23
  404. package/src/utils/base-currency.ts +108 -0
  405. package/src/utils/currency-names.ts +128 -0
  406. package/src/utils/currency-pair.ts +28 -1
  407. package/src/utils/currency-units.ts +32 -1
  408. package/src/utils/dom-screenshot.ts +22 -9
  409. package/src/utils/exchange-notation.generated.ts +360 -0
  410. package/src/utils/exchanges.ts +15 -322
  411. package/src/utils/format.ts +31 -1
  412. package/src/utils/instrument-identity.ts +38 -1
  413. package/src/utils/options.ts +46 -1
  414. package/src/utils/price-history.ts +4 -0
  415. package/src/utils/reported-money.ts +8 -4
  416. package/src/utils/sec.ts +68 -0
@@ -1,6 +1,7 @@
1
1
  import { ConnectionHealthRegistry } from "../../core/connection-health";
2
2
  import type { TickerRepository } from "../../data/ticker-repository";
3
3
  import type { AppConfig } from "../../types/config";
4
+ import type { BrokerAdapter } from "../../types/broker";
4
5
  import type { DataProvider } from "../../types/data-provider";
5
6
  import type { PersistedResourceValue } from "../../types/persistence";
6
7
  import type {
@@ -20,6 +21,7 @@ export interface PaneDiscoveryOptions {
20
21
  tickerRepository?: TickerRepository;
21
22
  panes?: Map<string, PaneDef>;
22
23
  paneTemplates?: Map<string, PaneTemplateDef>;
24
+ brokers?: Map<string, BrokerAdapter>;
23
25
  }
24
26
 
25
27
  /** Throws only if a plugin actually reaches for a dependency discovery has no answer for. */
@@ -43,10 +45,12 @@ export function createPaneDiscoveryContext(options: PaneDiscoveryOptions): Gloom
43
45
  } {
44
46
  const panes = options.panes ?? new Map<string, PaneDef>();
45
47
  const paneTemplates = options.paneTemplates ?? new Map<string, PaneTemplateDef>();
48
+ const brokers = options.brokers ?? new Map<string, BrokerAdapter>();
46
49
  return {
47
50
  ...buildDiscoveryContext({
48
51
  panes,
49
52
  paneTemplates,
53
+ brokers,
50
54
  getConfig: options.getConfig,
51
55
  marketData: options.marketData ?? unavailable<DataProvider>("Market data"),
52
56
  tickerRepository: options.tickerRepository ?? unavailable<TickerRepository>("The ticker repository"),
@@ -60,6 +64,7 @@ export function createPaneDiscoveryContext(options: PaneDiscoveryOptions): Gloom
60
64
  function buildDiscoveryContext({
61
65
  panes,
62
66
  paneTemplates,
67
+ brokers,
63
68
  getConfig,
64
69
  marketData,
65
70
  tickerRepository,
@@ -67,6 +72,7 @@ function buildDiscoveryContext({
67
72
  }: {
68
73
  panes: Map<string, PaneDef>;
69
74
  paneTemplates: Map<string, PaneTemplateDef>;
75
+ brokers: Map<string, BrokerAdapter>;
70
76
  getConfig: () => AppConfig;
71
77
  marketData: DataProvider;
72
78
  tickerRepository: TickerRepository;
@@ -84,7 +90,8 @@ function buildDiscoveryContext({
84
90
  registerCommand: () => {},
85
91
  registerCommandBarSearchProvider: () => () => {},
86
92
  registerColumn: () => {},
87
- registerBroker: () => {},
93
+ registerBroker: (broker) => { brokers.set(broker.id, broker); },
94
+ getBrokerAdapter: (brokerType) => brokers.get(brokerType) ?? null,
88
95
  registerCapability: () => {},
89
96
  registerTickerResearchTab: () => {},
90
97
  registerShortcut: () => {},
@@ -155,10 +162,15 @@ function buildDiscoveryContext({
155
162
 
156
163
  export async function createPaneCatalog(context: MarketContext, plugins: GloomPlugin[]): Promise<PaneFunctionCatalog> {
157
164
  const setupPlugins: GloomPlugin[] = [];
165
+ const brokers = new Map<string, BrokerAdapter>();
166
+ const previousResolver = context.resolveBroker;
167
+ const resolveBroker = (brokerType: string) => brokers.get(brokerType) ?? null;
168
+ context.resolveBroker = resolveBroker;
158
169
  const { panes, paneTemplates, ...discoveryContext } = createPaneDiscoveryContext({
159
170
  getConfig: () => context.config,
160
171
  marketData: context.dataProvider,
161
172
  tickerRepository: context.store,
173
+ brokers,
162
174
  });
163
175
 
164
176
  const destroy = () => {
@@ -166,6 +178,8 @@ export async function createPaneCatalog(context: MarketContext, plugins: GloomPl
166
178
  for (const plugin of setupPlugins.splice(0).reverse()) {
167
179
  try { plugin.dispose?.(); } catch (error) { failure ??= error; }
168
180
  }
181
+ brokers.clear();
182
+ if (context.resolveBroker === resolveBroker) context.resolveBroker = previousResolver;
169
183
  if (failure) throw failure;
170
184
  };
171
185
 
@@ -6,6 +6,8 @@ import type { PaneFunctionReport } from "./report";
6
6
  import type { ResolvedPaneFunction } from "./resolver";
7
7
  import { collectShotSymbols } from "./data";
8
8
  import { lockNoteLines } from "./lock-note";
9
+ import { createHeadlessPaneContext } from "./headless";
10
+ import type { HeadlessPaneContext } from "../../types/headless";
9
11
  import { lockNoteReason } from "../../plugins/builtin/shared/pro-lock-note";
10
12
  import { renderDesktopShot, type PaneScreenshotResult } from "./screenshot";
11
13
  import { deriveRenderedFreshness, formatFreshnessLine, type ReportFreshness } from "./freshness";
@@ -204,10 +206,12 @@ function reportRows(result: PaneScreenshotResult): DesktopPaneShotRenderedRow[]
204
206
  }];
205
207
  }
206
208
 
207
- export function buildDomPaneReportFromRender(
209
+ export async function buildDomPaneReportFromRender(
208
210
  resolved: ResolvedPaneFunction,
209
211
  result: PaneScreenshotResult,
210
- ): PaneFunctionReport {
212
+ /** Reads data for the lock note, which names a free route only when it has data for the symbol. */
213
+ context?: HeadlessPaneContext,
214
+ ): Promise<PaneFunctionReport> {
211
215
  const rows = reportRows(result);
212
216
  const hasStructuredRows = (result.render.reportRows ?? result.render.rows).length > 0;
213
217
  // A table that reported every row is whole, whatever the viewport cut from the capture.
@@ -218,7 +222,8 @@ export function buildDomPaneReportFromRender(
218
222
  const truncated = truncationReasons.length > 0
219
223
  || (result.render.reportTruncationReasons ? false : result.render.truncated);
220
224
  const failureReason = renderedFailureReason(result, rows);
221
- const unavailableSymbols = failureReason && result.symbols.length > 0 ? result.symbols : [];
225
+ // A pane that drew rows beside an error or empty marker ("Quote unavailable.") has data: only one with none has no data for its symbols.
226
+ const unavailableSymbols = failureReason && rows.length === 0 && result.symbols.length > 0 ? result.symbols : [];
222
227
  // What the view leaves out and how to see more, such as OMON's strikes outside its window.
223
228
  const rendered = renderedReportNotices(result.render.semanticUi ?? []);
224
229
  const notices = [...(resolved.pane.reportNotices?.(resolved.instance.settings ?? {}) ?? []), ...rendered.notices];
@@ -227,11 +232,12 @@ export function buildDomPaneReportFromRender(
227
232
  // what is locked and how to reach it, and cites no source.
228
233
  const gate = !hasStructuredRows ? result.render.accessGate ?? null : null;
229
234
  const lockNote = gate
230
- ? resolved.pane.reportLockNote?.({
235
+ ? await resolved.pane.reportLockNote?.({
231
236
  gate,
232
237
  symbol: result.symbols[0] ?? null,
233
238
  token: resolved.token,
234
239
  ...gate === "sign-in" && asksToVerifyEmail(result.render.visibleText ?? "") ? { verifyEmail: true } : {},
240
+ ...context ? { context } : {},
235
241
  }) ?? null
236
242
  : null;
237
243
  const locked = gate === "sign-in" || lockNote ? gate : null;
@@ -399,7 +405,7 @@ export async function buildDomFunctionReport(
399
405
  const failure = renderedReportFailure(result.render.semanticUi ?? []);
400
406
  if (failure) throw new RenderedReportFailure(failure.message, failure.details);
401
407
  try {
402
- return buildDomPaneReportFromRender(resolved, result);
408
+ return await buildDomPaneReportFromRender(resolved, result, createHeadlessPaneContext(resolved, context, new AbortController().signal));
403
409
  } catch (error) {
404
410
  return failedDomReport(resolved, rawArg, error);
405
411
  }
@@ -43,7 +43,7 @@ export interface ReportFreshness {
43
43
  feed?: "live" | "delayed";
44
44
  basis?: string;
45
45
  /** Set when the dates are the start of a period: the status line then reads `Sep 2026 (monthly)`. */
46
- periodicity?: "monthly" | "quarterly" | "annual";
46
+ periodicity?: "monthly" | "quarterly" | "semiannual" | "annual";
47
47
  /**
48
48
  * The newest bar's week or month had not ended, and the last day it covers
49
49
  * so far (`asOf`, when known): the status line reads `month to date, through
@@ -335,7 +335,7 @@ function datasetLagDays(observed: ReportTime, periodicity: ReportFreshness["peri
335
335
  const month = day.getUTCMonth();
336
336
  const through = !periodicity ? Date.UTC(year, month, day.getUTCDate())
337
337
  // Day 0 of the month after the period is its last day.
338
- : periodicity === "monthly" ? Date.UTC(year, month + 1, 0)
338
+ : periodicity === "monthly" || periodicity === "semiannual" ? Date.UTC(year, month + 1, 0)
339
339
  : periodicity === "quarterly" ? Date.UTC(year, month - (month % 3) + 3, 0)
340
340
  : Date.UTC(year, 12, 0);
341
341
  const today = new Date(now);
@@ -589,13 +589,13 @@ function tradingDayText(date: string, market: string | undefined): string {
589
589
  return market ? `${market} trading day ${dayText(date)}` : dayText(date);
590
590
  }
591
591
 
592
- /** `Sep 2026`, `Q2 2026` or `2026` for the period a date starts. */
592
+ /** `Sep 2026`, `Q2 2026` or `2026` for the period a date starts; a semiannual estimate's month. */
593
593
  function periodText(date: string, periodicity: NonNullable<ReportFreshness["periodicity"]>): string {
594
594
  const day = new Date(`${date.slice(0, 10)}T00:00:00Z`);
595
595
  if (!Number.isFinite(day.getTime())) return date;
596
596
  const year = day.getUTCFullYear();
597
597
  const month = day.getUTCMonth();
598
- return periodicity === "monthly" ? `${MONTHS[month]} ${year}`
598
+ return periodicity === "monthly" || periodicity === "semiannual" ? `${MONTHS[month]} ${year}`
599
599
  : periodicity === "quarterly" ? `Q${Math.floor(month / 3) + 1} ${year}`
600
600
  : `${year}`;
601
601
  }
@@ -93,7 +93,7 @@ const POP_OUT = key("p", "op out");
93
93
  const STEP = key("←/→", " step");
94
94
  /** THEM and MEMB members: open them in RRG, CORR, SIW or RIPL, or save them as a watchlist. */
95
95
  const MEMBERS_MENU = key(".", " open members in, save as watchlist");
96
- const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("t", "imeframe"), key("f", "ormulas")];
96
+ const CHART_KEYS = [key("s", "eries"), key("i", "ndicators"), key("b", "ars"), key("f", "ormulas")];
97
97
 
98
98
  export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
99
99
  // Research companies
@@ -971,7 +971,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
971
971
  bloomberg: [],
972
972
  },
973
973
  PDLR: {
974
- summary: "Weekly primary dealer net positions and settlement fails across Treasuries and mortgage-backed securities.",
974
+ summary: "Weekly primary dealer net positions with history, by sector and Treasury tenor (bills, coupons, TIPS, FRN), and settlement fails.",
975
975
  usage: ["PDLR"],
976
976
  keys: [TABS],
977
977
  data: same("Weekly, as published"),
@@ -1110,7 +1110,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1110
1110
  docs: "PORT",
1111
1111
  },
1112
1112
  ALRT: {
1113
- summary: "Your price alerts with current price, distance to target and trigger state, plus alerts days before earnings, CPI or FOMC and market and filing event alerts.",
1113
+ summary: "Your price alerts with current price, distance to target and trigger state, plus alerts on earnings, CPI and FOMC dates, filings and 13F, news keywords, analyst changes, 52-week highs and lows, unusual volume, short interest, insider and congress trades, catalysts, IV spikes, options flow and perps.",
1114
1114
  usage: ["ALRT"],
1115
1115
  keys: [key("a", "dd alert"), key("e", "dit"), key("d", "elete"), key("m", "re-arm")],
1116
1116
  data: QUOTES,
@@ -1238,6 +1238,30 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1238
1238
  data: null,
1239
1239
  bloomberg: [],
1240
1240
  },
1241
+ BUY: {
1242
+ summary: "Open the shared Trade ticket with a buy quantity and optional limit price. Focus starts on Review; choose an account and review the broker preview before confirming.",
1243
+ usage: ["BUY AAPL 10", "BUY AAPL 10 150.25"],
1244
+ keys: [key("r", "eview"), key("o", "rders")],
1245
+ data: same("Broker account and quote"),
1246
+ bloomberg: [],
1247
+ docs: "BR",
1248
+ },
1249
+ SELL: {
1250
+ summary: "Open the shared Trade ticket with a sell quantity and optional limit price. Focus starts on Review; holdings and broker capabilities are checked before confirmation.",
1251
+ usage: ["SELL AAPL 10", "SELL AAPL 10 350.25"],
1252
+ keys: [key("r", "eview"), key("o", "rders")],
1253
+ data: same("Broker account and quote"),
1254
+ bloomberg: [],
1255
+ docs: "BR",
1256
+ },
1257
+ ORD: {
1258
+ summary: "Open orders and recent activity for a selected broker profile and account. Review a modification or confirm cancellation from an open order; uncertain outcomes require reconciliation before another action.",
1259
+ usage: ["ORD"],
1260
+ keys: [key("m", "odify"), key("c", "ancel")],
1261
+ data: same("Broker account snapshot"),
1262
+ bloomberg: [],
1263
+ docs: "BR",
1264
+ },
1241
1265
 
1242
1266
  // Layouts and settings
1243
1267
  GL: {
@@ -89,17 +89,36 @@ function normalizeSymbolList(value: string): string[] {
89
89
  return symbols;
90
90
  }
91
91
 
92
- function validateSymbolCount(
92
+ const COUNT_WORDS = ["zero", "one", "two", "three", "four", "five"];
93
+ const EXAMPLE_TICKERS = ["SPY", "QQQ", "IWM", "DIA", "TLT"];
94
+
95
+ /**
96
+ * What a function that compares several tickers answers fewer: how many it
97
+ * needs and a command that runs, the reader's own tickers first (`CMP needs
98
+ * two or more tickers: fn CMP SPY,QQQ`).
99
+ */
100
+ function tooFewTickersMessage(token: string, minimum: number, given: readonly string[], command: "fn" | "shot" = "fn"): string {
101
+ const example = [...given, ...EXAMPLE_TICKERS.filter((ticker) => !given.includes(ticker))].slice(0, Math.max(minimum, given.length + 1));
102
+ return `${token} needs ${COUNT_WORDS[minimum] ?? minimum} or more tickers: ${command} ${token} ${example.join(",")}`;
103
+ }
104
+
105
+ /** The tickers a function's argument lists, as its report reads them. */
106
+ export function argumentSymbols(rawArgument: string): string[] {
107
+ return normalizeSymbolList(rawArgument);
108
+ }
109
+
110
+ export function validateSymbolCount(
93
111
  definition: HeadlessPaneDefinition,
94
112
  token: string,
95
113
  symbols: string[],
114
+ command: "fn" | "shot" = "fn",
96
115
  ): void {
97
116
  const minimum = definition.argument.minimum
98
117
  ?? (definition.argument.optional ? 0 : 1);
99
118
  const maximum = definition.argument.maximum;
100
119
  if (symbols.length < minimum) {
101
120
  if (minimum <= 1) throw requiredArgumentError(definition, token);
102
- throw new Error(`${token} requires at least ${minimum} symbols.`);
121
+ throw new Error(tooFewTickersMessage(token, minimum, symbols, command));
103
122
  }
104
123
  if (maximum != null && symbols.length > maximum) {
105
124
  throw new Error(`${token} accepts at most ${maximum} symbols.`);
@@ -168,12 +187,12 @@ async function loadHeadlessPaneModel(
168
187
  return result;
169
188
  }
170
189
 
171
- type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
190
+ type HeadlessMarketContext = Pick<MarketContext, "config" | "store" | "refresh" | "resolveBroker"> & Partial<Pick<MarketContext, "persistence" | "seriesCapabilities">> & {
172
191
  dataProvider: HeadlessPaneContext["marketData"];
173
192
  };
174
193
 
175
194
  /** What a headless loader reads the market, the account and the saved collections through. */
176
- function createHeadlessPaneContext(
195
+ export function createHeadlessPaneContext(
177
196
  resolved: ResolvedPaneFunction,
178
197
  context: HeadlessMarketContext,
179
198
  signal: AbortSignal,
@@ -186,6 +205,7 @@ function createHeadlessPaneContext(
186
205
  ...(context.refresh ? { refresh: true } : {}),
187
206
  settings: resolved.instance.settings,
188
207
  capabilities: getSharedRegistry() ?? context.seriesCapabilities,
208
+ resolveBroker: context.resolveBroker,
189
209
  async resolvePortfolio(id) {
190
210
  const portfolio = findNamedCollection(context.config.portfolios, id);
191
211
  if (!portfolio) return null;
@@ -498,7 +518,7 @@ function renderRows(
498
518
  };
499
519
  return renderTable(
500
520
  columns.map((column) => ({
501
- header: displayedHeader(column),
521
+ header: column.textHeader ?? displayedHeader(column),
502
522
  align: column.align,
503
523
  width: column.width,
504
524
  shrink: column.shrink,
@@ -734,14 +754,14 @@ export function headlessReportTables(
734
754
  const rowsResult = result as HeadlessRowsResult;
735
755
  const columns = displayedColumns(columnsFor(rowsResult.rows, rowsResult.columns, definition.columns));
736
756
  const rowIdentities = rowIdentitiesOf(rowsResult.rows, rowsResult);
737
- const own = exportRowsTable(title, columns, rowsResult.rows);
757
+ const own = exportRowsTable(title, columns, rowsResult.rows, { status: true });
738
758
  const interval = rowsResult.interval?.trim();
739
759
  tables = [{ ...own, ...(rowIdentities ? { rowIdentities } : {}), ...(interval ? { identity: { interval } } : {}) }, ...(rowsResult.exportTables ?? []).map((extra) => {
740
760
  const identity = {
741
761
  ...(extra.unit?.trim() ? { currencyOrUnit: extra.unit.trim() } : {}),
742
762
  ...(extra.interval?.trim() ? { interval: extra.interval.trim() } : {}),
743
763
  };
744
- const table = exportRowsTable(extra.title, displayedColumns(extra.columns), extra.rows);
764
+ const table = exportRowsTable(extra.title, displayedColumns(extra.columns), extra.rows, { status: true });
745
765
  return Object.keys(identity).length > 0 ? { ...table, identity } : table;
746
766
  })];
747
767
  break;
@@ -757,7 +777,7 @@ export function headlessReportTables(
757
777
  const entries = exportEntriesTable(section.title, section.entries);
758
778
  return Object.keys(named).length > 0 ? { ...entries, identity: named } : entries;
759
779
  }
760
- const table = exportRowsTable(section.title, displayedColumns(columnsFor(section.rows, section.columns, definition.columns)), section.rows);
780
+ const table = exportRowsTable(section.title, displayedColumns(columnsFor(section.rows, section.columns, definition.columns)), section.rows, { status: true });
761
781
  const identity = {
762
782
  ...named,
763
783
  ...(section.unit?.trim() ? { currencyOrUnit: section.unit.trim() } : {}),
@@ -775,7 +795,7 @@ export function headlessReportTables(
775
795
  }
776
796
  case "snapshot": {
777
797
  const snapshot = result as HeadlessSnapshotResult;
778
- tables = [exportRowsTable(title, displayedColumns(columnsFor(snapshot.items, undefined, definition.columns)), snapshot.items)];
798
+ tables = [exportRowsTable(title, displayedColumns(columnsFor(snapshot.items, undefined, definition.columns)), snapshot.items, { status: true })];
779
799
  break;
780
800
  }
781
801
  default: {
@@ -939,9 +959,9 @@ export async function buildHeadlessFunctionReport(
939
959
  loaded = await loadResolvedHeadlessPaneModel(resolved, context, rawArgument);
940
960
  } catch (error) {
941
961
  // A Pro pane the server refused reports what its wall covers, as its rendered view does.
942
- const locked = lockedHeadlessReport(resolved, error, () => (
962
+ const locked = await lockedHeadlessReport(resolved, error, () => (
943
963
  buildHeadlessPaneLoadArgs(resolved.headless!, resolved.token, rawArgument, resolved.options).symbols
944
- ));
964
+ ), createHeadlessPaneContext(resolved, context, new AbortController().signal));
945
965
  if (locked) return locked;
946
966
  throw error;
947
967
  }
@@ -36,9 +36,9 @@ import {
36
36
  import { isNotATickerMessage } from "../not-a-ticker";
37
37
  import { isCliFailure } from "../errors";
38
38
  import { isTickerLinkPeer } from "../../layout/ticker-navigation";
39
- import { knownGapMessage, commandPointer, topicPointer } from "../known-gaps";
40
- import { selectReportTables } from "../report-tables";
41
- import { cliOutputWritten } from "../result";
39
+ import { knownGapMessage, commandPointer, commandPointerLeads, topicPointer } from "../known-gaps";
40
+ import { selectReportTables, type CliReportTables } from "../report-tables";
41
+ import { cliOutputWritten, type CliErrorObject } from "../result";
42
42
  import type { CatalogCommandHit } from "../unknown-command";
43
43
  import { CLI_EXIT_CODES } from "../exit-codes";
44
44
  import { shotOptionRefusal } from "./shot-options";
@@ -204,6 +204,63 @@ function foundNothing(data: { rowCount: number; unavailableSymbols: readonly str
204
204
  return data.rowCount === 0 || data.unavailableSymbols.length >= data.rowCount;
205
205
  }
206
206
 
207
+ /** A listing key as asked or reported (`BHP:XASX`, `fred:DGS10`, `SPY`), in one case. */
208
+ const listingKey = (symbol: string) => symbol.trim().toUpperCase();
209
+ const bareKey = (key: string) => key.split(":")[0]!;
210
+
211
+ /**
212
+ * The listings asked that a report has no data for. A report names one by its
213
+ * full key; one that names it by the bare symbol (`SAN` for `SAN:EPA`), or by
214
+ * the listing a bare symbol resolved to (`SXR8:XETRA` for `SXR8`), stands for
215
+ * the one listing asked that way, never for a listing asked by its own key:
216
+ * with `BHP:XASX` and `BHP` asked, `BHP:XASX` unavailable leaves `BHP`.
217
+ */
218
+ function missingListings(asked: readonly string[], unavailable: readonly string[]): Set<string> {
219
+ const keys = asked.map(listingKey);
220
+ const exact = new Set(keys);
221
+ const missing = new Set<string>();
222
+ for (const entry of unavailable.map(listingKey)) {
223
+ if (exact.has(entry)) {
224
+ missing.add(entry);
225
+ continue;
226
+ }
227
+ const loose = keys.filter((key) => bareKey(key) === bareKey(entry) && key.includes(":") !== entry.includes(":"));
228
+ if (loose.length === 1) missing.add(loose[0]!);
229
+ }
230
+ return missing;
231
+ }
232
+
233
+ /** Whether a table holds a value past its first column (a row's label or date) and its reasons: a figure, a time, any text. */
234
+ function tablesHoldValues(tables: CliReportTables): boolean {
235
+ return tables.tables.some((table) => {
236
+ const skipped = new Set(table.columns.flatMap((header, index) => index === 0 || header === "status" || header === "value_status" ? [index] : []));
237
+ return table.rows.some((row) => row.some((cell, index) => !skipped.has(index) && cell !== "" && cell != null));
238
+ });
239
+ }
240
+
241
+ /**
242
+ * Why `fn` fails a report it printed, or null when the report has data. It
243
+ * fails when it is empty (a pane behind a wall reads nothing), when it has no
244
+ * data for any listing asked and its tables hold no value either, or when it
245
+ * is a list (`failsWithoutData`) with no name that has any. A report with data
246
+ * for some of what was asked, warnings or errors beside it, is a result: a
247
+ * loss-maker's P/E band without a P/E, or IV history with only realized
248
+ * volatility, exits 0.
249
+ */
250
+ export function reportFailure(
251
+ data: { empty: boolean; rowCount: number; symbols: readonly string[]; unavailableSymbols: readonly string[]; errors?: readonly string[]; reason?: unknown },
252
+ asked: string,
253
+ { failsWithoutData = false, tables }: { failsWithoutData?: boolean; tables?: CliReportTables } = {},
254
+ ): CliErrorObject | null {
255
+ const missing = missingListings(data.symbols, data.unavailableSymbols);
256
+ const noListing = data.symbols.length > 0 && data.symbols.every((symbol) => missing.has(listingKey(symbol)));
257
+ const noValues = tables ? !tablesHoldValues(tables) : false;
258
+ if (!data.empty && !(noListing && noValues) && !(failsWithoutData && foundNothing(data))) return null;
259
+ const reasons = [...data.errors ?? [], ...typeof data.reason === "string" ? [data.reason] : []];
260
+ const unavailable = data.unavailableSymbols.length > 0 ? `no data for ${data.unavailableSymbols.join(", ")}` : "no data";
261
+ return { code: "no_data", message: `${asked}: ${reasons.length > 0 ? reasons.join("; ") : unavailable}.`.replace(/\.\.$/, ".") };
262
+ }
263
+
207
264
  export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
208
265
  await runPaneCliCommand(ctx, async () => {
209
266
  await withPaneRuntime(ctx, args, async ({ parsed, context, registry, resolved }) => {
@@ -277,13 +334,15 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
277
334
  ? { ...report.data, glossary: glossary.map(({ term, short, definition }) => ({ term, ...(short ? { short } : {}), definition })) }
278
335
  : report.data;
279
336
  const warnings = [...parsed.listingNotes ?? [], ...await choice ?? [], ...report.data.warnings ?? []];
337
+ // A report with no data is printed as it is, and fails: exit 1, and `ok: false` in JSON with the reason.
338
+ const failure = reportFailure(report.data, ["fn", resolved.token, parsed.arg.trim()].filter(Boolean).join(" "), {
339
+ failsWithoutData: resolved.headless?.failsWithoutData, tables: report.tables });
280
340
  // The report states its own freshness in its data and text; JSON cites it as `source` too.
281
- ctx.printResult({ data, exportFreshness: report.data.freshness, ...(warnings.length > 0 ? { warnings } : {}) }, {
341
+ ctx.printResult({ data, exportFreshness: report.data.freshness, ...(warnings.length > 0 ? { warnings } : {}), ...(failure ? { failure } : {}) }, {
282
342
  text: () => glossary ? `${report.text}\n\n${renderReportGlossary(resolved.token, glossary)}` : report.text,
283
343
  ...(tabular ? { tables: selectReportTables(report.tables, resolved.tableSection) } : {}),
284
344
  });
285
- // The report is printed as it is; a list that found data for none of its names says so in the exit status too.
286
- if (resolved.headless?.failsWithoutData && foundNothing(report.data)) process.exitCode = CLI_EXIT_CODES.error;
345
+ if (failure) process.exitCode = CLI_EXIT_CODES.error;
287
346
  }, { strictHeadlessOptions: true, tableSection: true });
288
347
  });
289
348
  }
@@ -486,6 +545,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
486
545
  // A setting or a report can be a command, not a function: "light theme" names `config themes` beside THEM,
487
546
  // "drip" names `portfolio show --drip` beside DVD.
488
547
  const setting = commandPointer(parsed.query);
548
+ const settingLeads = commandPointerLeads(parsed.query);
489
549
  // The data catalog series the query names, which `fn G` charts.
490
550
  const series = findCatalogSeries(ctx.plugins, parsed.query);
491
551
  const listed = pointer
@@ -496,7 +556,7 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
496
556
  ...series.length ? { metadata: { series } } : {},
497
557
  ...(gap || setting) && ctx.cliOptions.format !== "text" ? { warnings: [gap, setting].filter((note): note is string => !!note) } : {},
498
558
  }, {
499
- text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting),
559
+ text: () => renderPaneCatalogReport(filtered, effectiveParsed, glossary, gap, series, setting, settingLeads),
500
560
  });
501
561
  } finally {
502
562
  registry.destroy();
@@ -2,6 +2,7 @@ import { refusedLock } from "../../plugins/builtin/shared/cloud-denied";
2
2
  import { lockNoteReason } from "../../plugins/builtin/shared/pro-lock-note";
3
3
  import { cliTerminalWidth, wrapText } from "../../utils/cli-output";
4
4
  import { reportFooterLines } from "../report-tables";
5
+ import type { HeadlessPaneContext } from "../../types/headless";
5
6
  import type { PaneFunctionReport } from "./report";
6
7
  import type { ResolvedPaneFunction } from "./resolver";
7
8
 
@@ -22,19 +23,21 @@ export function lockNoteLines(note: string): string[] {
22
23
  * `reportLockNote`, no source line and `locked` in JSON. The refusal may be
23
24
  * the error itself or the cause of the one a headless read wrote for it
24
25
  * (`explainRefusal`). Null when the error is no such refusal or the pane has
25
- * no note, and the error stands.
26
+ * no note, and the error stands. The context lets the note check the free
27
+ * route it names.
26
28
  */
27
- export function lockedHeadlessReport(
29
+ export async function lockedHeadlessReport(
28
30
  resolved: ResolvedPaneFunction,
29
31
  error: unknown,
30
32
  /** The symbols the report was asked for, read once the error is known to be a refusal. */
31
33
  symbolsOf: () => string[],
32
- ): PaneFunctionReport | null {
34
+ context?: HeadlessPaneContext,
35
+ ): Promise<PaneFunctionReport | null> {
33
36
  const refused = refusedLock(error);
34
37
  if (!refused || !resolved.pane.reportLockNote) return null;
35
38
  const gate = refused.gate;
36
39
  const symbols = symbolsOf();
37
- const note = resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token });
40
+ const note = await resolved.pane.reportLockNote({ ...refused, symbol: symbols[0] ?? null, token: resolved.token, ...context ? { context } : {} });
38
41
  const reason = lockNoteReason(note);
39
42
  return {
40
43
  data: {
@@ -23,7 +23,9 @@ import type { MarketContext } from "../types";
23
23
  import {
24
24
  buildPaneFunctionLookup,
25
25
  buildTemplateContext,
26
+ findFunctionCode,
26
27
  nearFunctionCodes,
28
+ suggestedFunctionCodes,
27
29
  suggestPaneFunctions,
28
30
  type NearFunctionCode,
29
31
  type PaneFunctionCatalog,
@@ -36,6 +38,7 @@ import {
36
38
  optionSettings,
37
39
  type ParsedPaneFunctionArgs,
38
40
  } from "./options";
41
+ import { argumentSymbols, validateSymbolCount } from "./headless";
39
42
  import {
40
43
  capabilityHasOption,
41
44
  capabilityPaneSettings,
@@ -65,9 +68,10 @@ export interface ResolvedPaneFunction {
65
68
  }
66
69
 
67
70
  async function buildPaneInstance(
68
- resolved: Pick<ResolvedPaneFunction, "pane" | "template" | "headless" | "createOptions" | "optionSettings" | "capability" | "options">,
71
+ resolved: Pick<ResolvedPaneFunction, "token" | "pane" | "template" | "headless" | "createOptions" | "optionSettings" | "capability" | "options">,
69
72
  context: MarketContext,
70
73
  target: string,
74
+ command: "fn" | "shot",
71
75
  ): Promise<PaneInstanceConfig> {
72
76
  // Free text is a phrase, not a symbol. Treating it as a ticker makes the
73
77
  // pane look for a provider for values such as "HIGH GROWTH SOFTWARE".
@@ -88,7 +92,11 @@ async function buildPaneInstance(
88
92
  return { ...spec.settings, ...resolved.optionSettings };
89
93
  }
90
94
  const chartSpec = parseChartSpec(spec.settings?.chartSpec);
91
- if (!chartSpec) throw new Error("The chart template did not produce a valid chart specification.");
95
+ if (!chartSpec) {
96
+ // A chart of too few tickers (CMP of one) has none to draw: say how many it takes.
97
+ if (resolved.headless) validateSymbolCount(resolved.headless, resolved.token, argumentSymbols(target), command);
98
+ throw new Error("The chart template did not produce a valid chart specification.");
99
+ }
92
100
  return {
93
101
  ...spec.settings,
94
102
  chartSpec: applyChartComposerCapabilityOptions(
@@ -148,7 +156,8 @@ export async function applyListingArgument(
148
156
  // A list entry with spaces is a company's name search finds, or the symbols it holds.
149
157
  const typed = takesOneTicker ? [args.arg] : await readNameEntries(splitSymbolArgs([args.arg]), context.dataProvider);
150
158
  if (typed.length === 0) return args;
151
- let listings = await Promise.all(typed.map((raw) => resolveCliListing(raw, exchange, context, { ownExchangeWins: typed.length > 1 })));
159
+ // RY.TO is the listing its quote names, RY:TSX, as `quote` reads it: a function's data is read by venue.
160
+ let listings = await Promise.all(typed.map((raw) => resolveCliListing(raw, exchange, context, { ownExchangeWins: typed.length > 1, venueSuffix: true })));
152
161
  // A separator stuck to the end of a pasted list (`UPST;`) is not part of a symbol; it is named.
153
162
  const notes: string[] = takesOneTicker ? [] : stripStrayEdges(args.arg).notes;
154
163
  const token = found.template?.shortcut?.prefix ?? args.target.toUpperCase();
@@ -242,15 +251,21 @@ async function unknownPaneFunctionError(
242
251
  // NIM is one slip from NI, but a bank figure is in `fundamentals`, not in a function's statement lines.
243
252
  const bank = bankFigurePointer(name, arg);
244
253
  if (bank) return new UnknownPaneFunctionError(name, bank.details, null, bank.message);
245
- const near = nearFunctionCodes(registry, name);
254
+ // A word that asks for a function's data (IVOL for HIVG and OVDV) names them as a slip from a code names it.
255
+ const suggested = suggestedFunctionCodes(registry, name);
256
+ const near = [...suggested, ...nearFunctionCodes(registry, name).filter(({ code }) => !suggested.some((entry) => entry.code === code))];
246
257
  // A catalog that cannot be read still leaves the pointer to it.
247
258
  const closest = (await suggestPaneFunctions(registry, context, name).catch(logFailure("cli.pane-functions-resolver", "Find pane function suggestions failed", [])))
248
259
  .filter((entry) => !near.some(({ code }) => code === entry.token));
249
260
  const tokenWidth = Math.max(0, ...closest.map((entry) => entry.token.length));
250
261
  const word = /^[\w.:-]+$/.test(name) ? name : JSON.stringify(name);
251
262
  const argument = arg.trim() ? ` ${/^[\w.:,=%+@/$^-]+$/.test(arg.trim()) ? arg.trim() : JSON.stringify(arg.trim())}` : "";
263
+ // One code is run on the argument; the functions a word asks for are each.
264
+ const runs = suggested.length > 0 ? suggested : near.length === 1 ? near : [];
252
265
  return new UnknownPaneFunctionError(name, [
253
- ...near.length === 1 ? [`Run: gloomberb ${command} ${near[0]!.code}${argument}`] : [],
266
+ // A function a word asks for, typed with no argument, shows the one it takes (`<ticker>`).
267
+ ...runs.map(({ code }, index) => [index === 0 ? "Run:" : " ", "gloomberb", command, code,
268
+ argument.trim() || (suggested.length > 0 ? findFunctionCode(registry, code)?.argument : "")].filter(Boolean).join(" ")),
254
269
  ...closest.length > 0
255
270
  ? ["Closest matches:", ...closest.map((entry) => ` ${entry.token.padEnd(tokenWidth)} ${entry.label}`), `Search the catalog: gloomberb catalog ${word}`]
256
271
  : near.length > 0 ? [] : [`No function code is close to ${word}. List every function: gloomberb catalog`],
@@ -317,7 +332,7 @@ export async function resolvePaneFunction(
317
332
  options: normalizedOptions,
318
333
  ...(tableSection !== undefined ? { tableSection } : {}),
319
334
  };
320
- resolved.instance = await buildPaneInstance(resolved, context, args.arg);
335
+ resolved.instance = await buildPaneInstance(resolved, context, args.arg, resolutionSettings.command ?? "fn");
321
336
  return resolved;
322
337
  }
323
338