gloomberb 0.16.3 → 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 (451) 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/signed-in/adapter.ts +33 -2
  30. package/src/brokers/signed-in/client.ts +10 -0
  31. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  32. package/src/brokers/signed-in/sign-in.ts +62 -5
  33. package/src/capabilities/registry.ts +8 -1
  34. package/src/capabilities/types.ts +2 -0
  35. package/src/cli/commands/crypto-hints.ts +12 -4
  36. package/src/cli/commands/market.ts +359 -99
  37. package/src/cli/commands/options-chain.ts +172 -0
  38. package/src/cli/commands/options-leaps.ts +422 -0
  39. package/src/cli/commands/overview.ts +164 -26
  40. package/src/cli/commands/search.ts +29 -8
  41. package/src/cli/commands/system.ts +99 -19
  42. package/src/cli/commands/themes.ts +64 -0
  43. package/src/cli/commands/ticker.ts +62 -30
  44. package/src/cli/desktop-pane-shot.ts +97 -16
  45. package/src/cli/errors.ts +25 -2
  46. package/src/cli/freshness.ts +7 -4
  47. package/src/cli/fx-pair.ts +57 -0
  48. package/src/cli/help.ts +14 -1
  49. package/src/cli/index-roots.ts +51 -0
  50. package/src/cli/index.ts +9 -6
  51. package/src/cli/listing-arg.ts +211 -3
  52. package/src/cli/not-a-ticker.ts +37 -0
  53. package/src/cli/options.ts +50 -6
  54. package/src/cli/pane-functions/capabilities.ts +8 -2
  55. package/src/cli/pane-functions/catalog.ts +13 -2
  56. package/src/cli/pane-functions/data.ts +36 -5
  57. package/src/cli/pane-functions/dom.ts +21 -13
  58. package/src/cli/pane-functions/freshness.ts +205 -26
  59. package/src/cli/pane-functions/function-help.ts +67 -22
  60. package/src/cli/pane-functions/headless.ts +5 -4
  61. package/src/cli/pane-functions/index.ts +3 -0
  62. package/src/cli/pane-functions/market-session.ts +133 -0
  63. package/src/cli/pane-functions/options.ts +15 -4
  64. package/src/cli/pane-functions/report-notices.ts +63 -0
  65. package/src/cli/pane-functions/report.ts +4 -0
  66. package/src/cli/pane-functions/resolver.ts +4 -6
  67. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  68. package/src/cli/pane-functions/screenshot.ts +105 -23
  69. package/src/cli/registry.ts +2 -1
  70. package/src/cli/result.ts +50 -13
  71. package/src/cli/row-window.ts +76 -0
  72. package/src/cli/scroll-truncation.ts +34 -0
  73. package/src/components/chart/composite/composite-chart.tsx +43 -4
  74. package/src/components/chart/composite/legend.tsx +39 -6
  75. package/src/components/chart/composite/rasterizer.ts +64 -4
  76. package/src/components/chart/composite/series-cues.ts +71 -0
  77. package/src/components/chart/composite/text-renderer.ts +16 -8
  78. package/src/components/chart/composite/types.ts +3 -0
  79. package/src/components/command-bar/commands/direct/index.ts +9 -1
  80. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  81. package/src/components/command-bar/commands/registry.ts +7 -0
  82. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  83. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  84. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  85. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  86. package/src/components/command-bar/routes/root/results.ts +40 -10
  87. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  88. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  89. package/src/components/command-bar/surface/index.tsx +18 -4
  90. package/src/components/command-bar/theme-picker.tsx +48 -13
  91. package/src/components/command-bar/workflow/ops.ts +5 -3
  92. package/src/components/data-table/view.tsx +28 -9
  93. package/src/components/layout/floating-pane.tsx +6 -2
  94. package/src/components/layout/market-summary.ts +23 -9
  95. package/src/components/layout/pane/content.tsx +46 -29
  96. package/src/components/layout/pane/cycle-order.ts +2 -11
  97. package/src/components/layout/pane/header.tsx +2 -1
  98. package/src/components/layout/pane/title.ts +1 -1
  99. package/src/components/layout/presentation.ts +22 -0
  100. package/src/components/layout/shell/active-drag.ts +1 -1
  101. package/src/components/layout/shell/drag/index.ts +4 -0
  102. package/src/components/layout/shell/drag/live.ts +76 -5
  103. package/src/components/layout/shell/drag/runtime.ts +8 -8
  104. package/src/components/layout/shell/index.tsx +47 -6
  105. package/src/components/layout/shell/layout-state.ts +5 -11
  106. package/src/components/layout/shell/menu.ts +28 -1
  107. package/src/components/layout/shell/pane/actions.ts +2 -1
  108. package/src/components/layout/shell/pane/layers.tsx +51 -10
  109. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  110. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  111. package/src/components/layout/shell/shortcuts.ts +5 -2
  112. package/src/components/layout/shell/visible-layout.ts +44 -1
  113. package/src/components/layout/status-bar.tsx +4 -1
  114. package/src/components/markdown-text.tsx +9 -5
  115. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  116. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  117. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  118. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  119. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  120. package/src/components/onboarding/pro-step-layout.ts +86 -0
  121. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  122. package/src/components/ui/button.tsx +10 -0
  123. package/src/components/ui/data-table/index.tsx +38 -0
  124. package/src/components/ui/data-table/types.ts +8 -1
  125. package/src/components/ui/data-table/visible-range.ts +2 -0
  126. package/src/components/ui/icon.tsx +11 -2
  127. package/src/components/ui/index.ts +1 -1
  128. package/src/core/connection-health.ts +1 -1
  129. package/src/core/state/app/layout-reducer.ts +89 -3
  130. package/src/core/state/app/state.ts +8 -5
  131. package/src/core/state/app/types.ts +13 -2
  132. package/src/data/config/layout.ts +55 -21
  133. package/src/data/config/store/fold-panes.ts +149 -0
  134. package/src/data/config/store/migrations.ts +7 -0
  135. package/src/data/config/store/node.ts +27 -6
  136. package/src/data/config/store/normalize.ts +37 -3
  137. package/src/i18n/es.ts +14 -1
  138. package/src/i18n/ja.ts +14 -1
  139. package/src/i18n/ko.ts +14 -1
  140. package/src/i18n/zh-cn.ts +14 -1
  141. package/src/i18n/zh-tw.ts +14 -1
  142. package/src/layout/desks.ts +1 -2
  143. package/src/layout/pane-layout-move.ts +264 -0
  144. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  145. package/src/layout/pane-manager/index.ts +1 -0
  146. package/src/market-data/coordinator/entries.ts +7 -1
  147. package/src/market-data/coordinator/quotes.ts +2 -2
  148. package/src/market-data/market/freshness.ts +118 -18
  149. package/src/market-data/market/status.ts +40 -0
  150. package/src/market-data/options-alternatives.ts +69 -0
  151. package/src/market-data/quotes/contributions.ts +19 -2
  152. package/src/market-data/quotes/metadata.ts +16 -0
  153. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  154. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  155. package/src/plugins/builtin/account-management/model.ts +1 -0
  156. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  157. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  158. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  159. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  160. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  161. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  162. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  163. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  164. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  165. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  166. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  167. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  168. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  169. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  170. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  171. package/src/plugins/builtin/chat/channels.ts +34 -36
  172. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  173. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  175. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  176. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  177. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  178. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  179. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  180. package/src/plugins/builtin/chat/conversations.ts +256 -0
  181. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  182. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  183. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  184. package/src/plugins/builtin/chat/index.tsx +40 -2
  185. package/src/plugins/builtin/chat/layout.ts +2 -0
  186. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  187. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  188. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  189. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  190. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  191. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  192. package/src/plugins/builtin/chat/pane.tsx +8 -0
  193. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  194. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  195. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  196. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  197. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  198. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  199. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  200. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  201. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  202. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  203. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  204. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  205. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  206. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  207. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  208. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  209. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  210. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  211. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  212. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  213. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  214. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  215. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  216. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  217. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  218. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  219. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  220. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  221. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  222. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  223. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  224. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  225. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  226. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  227. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  228. package/src/plugins/builtin/composite-plugins.ts +107 -64
  229. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  230. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  231. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  232. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  233. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  234. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  235. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  236. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  237. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  238. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  239. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  240. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  241. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  242. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  243. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  244. package/src/plugins/builtin/econ/index.tsx +68 -22
  245. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  246. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  247. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  248. package/src/plugins/builtin/executives/data.ts +17 -6
  249. package/src/plugins/builtin/executives/pane.tsx +16 -7
  250. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  251. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  254. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  255. package/src/plugins/builtin/gpu/headless.ts +9 -3
  256. package/src/plugins/builtin/gpu/model.ts +14 -5
  257. package/src/plugins/builtin/help/index.tsx +1 -0
  258. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  259. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  260. package/src/plugins/builtin/holders/headless.ts +6 -1
  261. package/src/plugins/builtin/holders/pane.tsx +3 -0
  262. package/src/plugins/builtin/insider/index.tsx +4 -4
  263. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  264. package/src/plugins/builtin/iv-history/client.ts +12 -2
  265. package/src/plugins/builtin/iv-history/format.ts +33 -2
  266. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  267. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  268. package/src/plugins/builtin/iv-history/model.ts +61 -13
  269. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  270. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  271. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  272. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  273. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  275. package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
  276. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  277. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  278. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  279. package/src/plugins/builtin/macro-day/client.ts +24 -0
  280. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  281. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  282. package/src/plugins/builtin/macro-day/model.ts +11 -6
  283. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  284. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  285. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  286. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  287. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  288. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  289. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  290. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  291. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  292. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  293. package/src/plugins/builtin/market-movers/model.ts +71 -17
  294. package/src/plugins/builtin/market-movers/table.ts +50 -23
  295. package/src/plugins/builtin/news/headless.ts +9 -2
  296. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  297. package/src/plugins/builtin/openfda/client.ts +1 -1
  298. package/src/plugins/builtin/options/carry.ts +95 -0
  299. package/src/plugins/builtin/options/index.tsx +58 -5
  300. package/src/plugins/builtin/options/strike-window.ts +183 -0
  301. package/src/plugins/builtin/options/table.ts +55 -3
  302. package/src/plugins/builtin/options/types.ts +8 -1
  303. package/src/plugins/builtin/options/view.tsx +161 -12
  304. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  305. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  306. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  307. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  308. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  309. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  310. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  311. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  312. package/src/plugins/builtin/pe-band/model.ts +2 -2
  313. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  314. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  315. package/src/plugins/builtin/perps/model.ts +16 -5
  316. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  317. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  318. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  319. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  320. package/src/plugins/builtin/plugin-module.ts +87 -4
  321. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  322. package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
  323. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  324. package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
  325. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  326. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  327. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  328. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  329. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  330. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  331. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  332. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  333. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  334. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  335. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  336. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  337. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  338. package/src/plugins/builtin/sec/index.tsx +5 -5
  339. package/src/plugins/builtin/sectors/client.ts +10 -2
  340. package/src/plugins/builtin/sectors/headless.ts +35 -6
  341. package/src/plugins/builtin/sectors/index.tsx +18 -1
  342. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  343. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  344. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  345. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  346. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  347. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  348. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  349. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  350. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  351. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  352. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  353. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  354. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  355. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  356. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  357. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  358. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  359. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  360. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  361. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  362. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  363. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  364. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  365. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  366. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  367. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  368. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  369. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  370. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  371. package/src/plugins/builtin/world-indices/client.ts +22 -4
  372. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  373. package/src/plugins/builtin/world-indices/model.ts +4 -2
  374. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  375. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  376. package/src/plugins/builtin/world-venue-map/index.ts +1 -1
  377. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  378. package/src/plugins/builtin-editorial.ts +126 -0
  379. package/src/plugins/catalog-backend.ts +16 -9
  380. package/src/plugins/catalog-browser.ts +127 -63
  381. package/src/plugins/catalog-manifest.ts +73 -40
  382. package/src/plugins/catalog-ui.ts +26 -6
  383. package/src/plugins/extracted-plugins.ts +7 -1
  384. package/src/plugins/ownership.ts +150 -7
  385. package/src/plugins/plugin-functions.ts +54 -0
  386. package/src/plugins/plugin-off.ts +36 -0
  387. package/src/plugins/registry/host-actions.ts +16 -0
  388. package/src/plugins/registry/index.ts +84 -24
  389. package/src/plugins/registry/plugin-state.ts +25 -1
  390. package/src/plugins/runtime/context.tsx +29 -0
  391. package/src/react/input.ts +48 -1
  392. package/src/remote/app-host.tsx +5 -0
  393. package/src/remote/controller.ts +3 -53
  394. package/src/remote/matching.ts +69 -0
  395. package/src/remote/schema.ts +1 -1
  396. package/src/remote/semantic-tree.tsx +14 -0
  397. package/src/remote/types.ts +2 -0
  398. package/src/renderers/dom/desktop/icons.tsx +14 -0
  399. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  400. package/src/renderers/dom/host/box.tsx +1 -1
  401. package/src/renderers/dom/host/drag-shield.ts +17 -6
  402. package/src/renderers/dom/host/live-frame.ts +10 -2
  403. package/src/renderers/dom/input-host.tsx +6 -1
  404. package/src/renderers/dom/styles.css +13 -0
  405. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  406. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  407. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  408. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  409. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  410. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  411. package/src/renderers/electrobun/view/main.tsx +4 -1
  412. package/src/renderers/opentui/input-host.tsx +6 -1
  413. package/src/renderers/opentui/start.tsx +9 -0
  414. package/src/shares/portable-layout.ts +16 -6
  415. package/src/sources/gloomberb-cloud/index.ts +19 -10
  416. package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
  417. package/src/sources/listing-symbols.ts +11 -5
  418. package/src/sources/provider-errors.ts +88 -3
  419. package/src/sources/provider-router/cached-routes.ts +8 -4
  420. package/src/sources/provider-router/financial-routes.ts +11 -5
  421. package/src/sources/provider-router/financials.ts +42 -15
  422. package/src/sources/provider-router/history.ts +27 -11
  423. package/src/sources/provider-router/index.ts +1 -1
  424. package/src/sources/provider-router/primary.ts +23 -5
  425. package/src/sources/sec-edgar.ts +68 -8
  426. package/src/sync/core-contributors.ts +4 -3
  427. package/src/test-support/cli-context.ts +2 -1
  428. package/src/test-support/data-provider.ts +34 -0
  429. package/src/test-support/shell-pane-keys.tsx +1 -0
  430. package/src/theme/series-colors.ts +46 -0
  431. package/src/theme/themes.ts +149 -0
  432. package/src/tickers/search/index.ts +22 -2
  433. package/src/tickers/search/ranking.ts +45 -1
  434. package/src/tickers/search/types.ts +3 -1
  435. package/src/time-series/resolve.ts +1 -14
  436. package/src/types/config.ts +61 -1
  437. package/src/types/headless.ts +11 -0
  438. package/src/types/instrument.ts +8 -0
  439. package/src/types/plugin.ts +49 -3
  440. package/src/ui/host.tsx +8 -4
  441. package/src/ui/index.tsx +2 -0
  442. package/src/utils/cli-output.ts +14 -1
  443. package/src/utils/exchanges.ts +13 -0
  444. package/src/utils/format.ts +2 -1
  445. package/src/utils/fx-market-hours.ts +34 -0
  446. package/src/utils/option-expiry.ts +126 -0
  447. package/src/utils/price-history.ts +39 -6
  448. package/src/utils/rpc-timeout-error.ts +6 -2
  449. package/src/utils/sec-eps-basis.ts +159 -30
  450. package/src/utils/sec.ts +38 -34
  451. package/src/utils/throttled-fetch.ts +51 -22
package/src/cli/help.ts CHANGED
@@ -34,15 +34,25 @@ const INDENT = 2;
34
34
  const MAX_HELP_WIDTH = 100;
35
35
  const PIPED_HELP_WIDTH = 80;
36
36
 
37
+ /** Commands that run as written, for someone who has just installed it. */
38
+ const START_HERE: Array<[string, string]> = [
39
+ ["gloomberb search apple", "Find a ticker from a company name"],
40
+ ["gloomberb quote AAPL", "Show the latest price"],
41
+ ["gloomberb ticker AAPL", "Open the full research report"],
42
+ ["gloomberb catalog earnings", "Find a function, then run it with fn"],
43
+ ];
44
+
37
45
  const GLOBAL_OPTIONS: Array<[string, string]> = [
38
46
  ["--json", "Print the full result as JSON"],
39
47
  ["--csv", "Print rows as CSV under the columns the text shows; fn reports and fundamentals put each table under # section: and end with # source lines"],
40
48
  ["--ndjson", "Print one JSON object per table row"],
41
- ["--limit <n>", "Show at most n rows"],
49
+ ["--limit <n>", "Show at most n rows, the first n as printed"],
50
+ ["--tail <n>", "Show the newest n rows of a dated series (history, fred, econ) in their printed order, or the last n of another list"],
42
51
  ["--refresh", "Fetch fresh data instead of reading the cache"],
43
52
  ["--dry-run", "Preview config, cache, notes, alerts, plugin on/off, and remote changes without saving"],
44
53
  ["-q, --quiet", "Print no results or errors in text mode, for commands that print results"],
45
54
  ["--color, --no-color", "Force or turn off colors (NO_COLOR=1 also turns them off)"],
55
+ ["--width <n>", "Fit tables to n columns (default: the terminal width; piped output is not fitted). shot takes its own --width in pixels"],
46
56
  ];
47
57
 
48
58
  export interface CliHelpEntry {
@@ -98,6 +108,9 @@ export function renderCliHelp(entries: CliHelpEntry[], version: string, descript
98
108
  `${cliStyles.bold("gloomberb")} ${cliStyles.muted(version)}`,
99
109
  ...wrapText(description, width),
100
110
  "",
111
+ renderSection("Start here"),
112
+ ...renderDefinitions(START_HERE, { width, termStyle: cliStyles.command }),
113
+ "",
101
114
  renderSection("Usage"),
102
115
  ...renderDefinitions([
103
116
  ["gloomberb", "Open the terminal UI"],
@@ -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
@@ -21,8 +21,8 @@ import {
21
21
  } from "./help";
22
22
  import { parsePaneFunctionArgs } from "./pane-functions/options";
23
23
  import { checkCliCommandOptions } from "./command-options";
24
- import { fail, inferCliErrorOptions, printCliError } from "./errors";
25
- import { setCliColorEnabledOverride } from "../utils/cli-output";
24
+ import { asUsageError, fail, inferCliErrorOptions, printCliError } from "./errors";
25
+ import { setCliColorEnabledOverride, setCliWidthOverride } from "../utils/cli-output";
26
26
  import { search, searchCandidatesForCli, buildSearchReport } from "./commands/search";
27
27
  import { ticker } from "./commands/ticker";
28
28
  import { requireOneArg, takeOption } from "./commands/command-utils";
@@ -176,9 +176,10 @@ function createCoreCliCommands(
176
176
  { flags: "--output <path>", description: "PNG to write; defaults to gloomberb-<function>-<argument>.png in this folder" },
177
177
  { flags: "--width <px>", description: "Image width, 720 to 2400 (default 1280)" },
178
178
  { flags: "--height <px>", description: "Image height, 360 to 1800 (default 720)" },
179
- { 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" },
180
180
  { flags: "--scale <n>", description: "Text scale from 0.5 to 4 (default 1)" },
181
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" },
182
183
  { flags: "--<option> <value>", description: "A function setting; gloomberb catalog <function> lists them" },
183
184
  EXCHANGE_OPTION,
184
185
  ],
@@ -187,6 +188,7 @@ function createCoreCliCommands(
187
188
  "shot HP BHP:ASX",
188
189
  "shot DDIS MSFT --tab history",
189
190
  "shot HP NVDA --width 1600 --theme green",
191
+ "shot QQ AAPL,MSFT --no-status",
190
192
  ],
191
193
  },
192
194
  execute: async (args, ctx) => {
@@ -336,11 +338,12 @@ export async function dispatchCli(args: string[], options: DispatchCliOptions =
336
338
  try {
337
339
  parsed = parseCliGlobalArgs(args);
338
340
  } catch (error) {
339
- printCliError(error, inferCliErrorOptions(args));
341
+ printCliError(asUsageError(error), inferCliErrorOptions(args));
340
342
  process.exitCode = 1;
341
343
  return { kind: "handled" };
342
344
  }
343
345
  setCliColorEnabledOverride(parsed.options.color);
346
+ setCliWidthOverride(parsed.options.width ?? null);
344
347
  const command = parsed.args[0] ?? (parsed.help ? "help" : undefined);
345
348
  if (!command) {
346
349
  return { kind: "unhandled" };
@@ -361,11 +364,11 @@ export async function dispatchCli(args: string[], options: DispatchCliOptions =
361
364
  if (!helpOnly && resolved.builtin) checkCliCommandOptions(resolved.command, commandArgs, parsed.literalStart - 1);
362
365
  const result = await target.command.execute(
363
366
  commandArgs,
364
- createCliCommandContext(target.ownerId, registry, parsed.options),
367
+ createCliCommandContext(target.ownerId, registry, parsed.options, commandArgs),
365
368
  );
366
369
  return normalizeCliDispatchResult(result);
367
370
  } catch (error) {
368
- printCliError(error, parsed.options, { command: resolved.command.name });
371
+ printCliError(error, parsed.options, { command: resolved.command.name, args: commandArgs });
369
372
  process.exitCode = 1;
370
373
  return { kind: "handled" };
371
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);
@@ -0,0 +1,37 @@
1
+ import { notATickerSymbol } from "../sources/provider-errors";
2
+
3
+ // "Not a ticker: APPLE." as the router words it; the symbol ends at the sentence's own period.
4
+ const NOT_A_TICKER_SENTENCE = /Not a ticker: ((?:(?!\.\s)[^\n`\x1b])+?)\.(?=\s|\x1b|$)(?! Try `gloomberb search)/g;
5
+
6
+ /** The symbol as the user typed it on the command line (`Apple`), not as the router saw it (`APPLE`). */
7
+ function typedSymbol(symbol: string, args: readonly string[]): string {
8
+ const wanted = symbol.toUpperCase();
9
+ for (const arg of args) {
10
+ for (const part of arg.split(",")) {
11
+ const typed = part.trim().replace(/^\$/, "");
12
+ if (typed.toUpperCase() === wanted) return typed;
13
+ }
14
+ }
15
+ return symbol;
16
+ }
17
+
18
+ /** "Try `gloomberb search Apple`." naming what the user typed; a venue (`Apple:NYSE`) is not part of a search. */
19
+ function searchHint(typed: string): string {
20
+ const query = typed.split(":")[0]!.trim() || typed;
21
+ return `Try \`gloomberb search ${/\s/.test(query) ? JSON.stringify(query) : query}\`.`;
22
+ }
23
+
24
+ /**
25
+ * Adds the search hint after each "Not a ticker: APPLE." in text meant for a person. The router
26
+ * only knows the symbol upper-cased, so the hint reads the user's own spelling from `args`.
27
+ */
28
+ export function withSearchHints(text: string, args: readonly string[]): string {
29
+ return text.includes("Not a ticker: ")
30
+ ? text.replace(NOT_A_TICKER_SENTENCE, (sentence, symbol: string) => `${sentence} ${searchHint(typedSymbol(symbol, args))}`)
31
+ : text;
32
+ }
33
+
34
+ /** Whether an error message says the symbol is not a ticker. */
35
+ export function isNotATickerMessage(message: string | null | undefined): boolean {
36
+ return message != null && notATickerSymbol(message) !== null;
37
+ }
@@ -5,6 +5,10 @@ export interface CliGlobalOptions {
5
5
  quiet: boolean;
6
6
  color: boolean | null;
7
7
  limit?: number;
8
+ /** `--tail <n>`: the newest n rows of a dated series, in their printed order. Never set with `limit`. */
9
+ tail?: number;
10
+ /** `--width <n>`: fit tables to n columns. Unset, a terminal's own width fits them and piped output is not fitted. */
11
+ width?: number;
8
12
  refresh: boolean;
9
13
  dryRun: boolean;
10
14
  yes: boolean;
@@ -32,20 +36,44 @@ export const DEFAULT_CLI_OPTIONS: CliGlobalOptions = {
32
36
  yes: false,
33
37
  };
34
38
 
35
- function parseLimit(value: string | undefined): number {
36
- if (!value) throw new Error("Missing value for --limit.");
39
+ function parseRowCount(flag: "--limit" | "--tail", value: string | undefined): number {
40
+ if (!value) throw new Error(`Missing value for ${flag}.`);
37
41
  const parsed = Number(value);
38
42
  if (!Number.isInteger(parsed) || parsed < 1) {
39
- throw new Error("--limit must be a positive integer.");
43
+ throw new Error(`${flag} must be a positive integer.`);
40
44
  }
41
45
  return parsed;
42
46
  }
43
47
 
48
+ /** Commands whose own `--width` is a size in pixels, so it is not the table width. */
49
+ const PIXEL_WIDTH_COMMANDS = new Set(["shot", "screenshot"]);
50
+ const MIN_TABLE_WIDTH = 20;
51
+
52
+ function parseColumnCount(value: string | undefined): number {
53
+ const parsed = Number(value);
54
+ if (!value || !Number.isInteger(parsed) || parsed < MIN_TABLE_WIDTH) {
55
+ throw new Error(`--width must be a whole number of columns, ${MIN_TABLE_WIDTH} or more, such as --width 80.`);
56
+ }
57
+ return parsed;
58
+ }
59
+
60
+ /** The first word that is not an option or the value of one: the command the arguments are for. */
61
+ function commandWord(rawArgs: readonly string[]): string | undefined {
62
+ for (let index = 0; index < rawArgs.length; index += 1) {
63
+ const arg = rawArgs[index]!;
64
+ if (arg === "--") return rawArgs[index + 1];
65
+ if (arg === "--limit" || arg === "--tail" || arg === "--width") index += 1;
66
+ else if (!arg.startsWith("-")) return arg;
67
+ }
68
+ return undefined;
69
+ }
70
+
44
71
  export function parseCliGlobalArgs(rawArgs: string[]): ParsedCliArgs {
45
72
  const options: CliGlobalOptions = { ...DEFAULT_CLI_OPTIONS };
46
73
  const args: string[] = [];
47
74
  let help = false;
48
75
  let literalStart: number | null = null;
76
+ const ownsWidth = PIXEL_WIDTH_COMMANDS.has(commandWord(rawArgs)?.toLowerCase() ?? "");
49
77
 
50
78
  for (let index = 0; index < rawArgs.length; index += 1) {
51
79
  const arg = rawArgs[index]!;
@@ -94,17 +122,33 @@ export function parseCliGlobalArgs(rawArgs: string[]): ParsedCliArgs {
94
122
  options.yes = true;
95
123
  continue;
96
124
  }
97
- if (arg === "--limit") {
125
+ if (!ownsWidth && arg === "--width") {
98
126
  index += 1;
99
- options.limit = parseLimit(rawArgs[index]);
127
+ options.width = parseColumnCount(rawArgs[index]);
128
+ continue;
129
+ }
130
+ if (!ownsWidth && arg.startsWith("--width=")) {
131
+ options.width = parseColumnCount(arg.slice("--width=".length));
132
+ continue;
133
+ }
134
+ if (arg === "--limit" || arg === "--tail") {
135
+ index += 1;
136
+ options[arg === "--limit" ? "limit" : "tail"] = parseRowCount(arg, rawArgs[index]);
100
137
  continue;
101
138
  }
102
139
  if (arg.startsWith("--limit=")) {
103
- options.limit = parseLimit(arg.slice("--limit=".length));
140
+ options.limit = parseRowCount("--limit", arg.slice("--limit=".length));
141
+ continue;
142
+ }
143
+ if (arg.startsWith("--tail=")) {
144
+ options.tail = parseRowCount("--tail", arg.slice("--tail=".length));
104
145
  continue;
105
146
  }
106
147
  args.push(arg);
107
148
  }
149
+ if (options.limit != null && options.tail != null) {
150
+ throw new Error("Use --limit or --tail, not both: --limit keeps the first rows, --tail the newest.");
151
+ }
108
152
 
109
153
  return { args, options, help, literalStart: literalStart ?? args.length };
110
154
  }
@@ -9,6 +9,7 @@ import type {
9
9
  export type PaneFunctionReadiness = "ready" | "partial" | "live-dom" | "unsupported";
10
10
  type PaneFunctionScreenshotReadiness = PaneFunctionReadiness;
11
11
  type PaneFunctionTickerCardinality = "none" | "one" | "one-or-more" | "two-or-more" | "one-or-two";
12
+ /** A headless option, or a rendered view's, which may name its value, show an example and check what is typed. */
12
13
  export type PaneFunctionOptionDef = HeadlessPaneOptionDef;
13
14
  export type NormalizedPaneFunctionOptions = Record<string, string | number | boolean>;
14
15
 
@@ -64,7 +65,7 @@ function fallbackCapability(
64
65
  limitations: [dataPane
65
66
  ? RENDERED_VIEW_LIMITATION
66
67
  : "This pane is an interactive surface and does not expose a data report."],
67
- options: [],
68
+ options: [...(pane.reportOptions ?? [])],
68
69
  };
69
70
  }
70
71
 
@@ -159,6 +160,7 @@ function normalizeOptionValue(option: PaneFunctionOptionDef, rawValue: string |
159
160
  throw new Error(`Invalid --${option.key} value "${rawValue}". Use true or false.`);
160
161
  }
161
162
  if (rawValue === true) throw new Error(`--${option.key} requires a value.`);
163
+ if (option.normalize) return option.normalize(rawValue);
162
164
  if (option.type === "enum") return normalizeEnumValue(option, rawValue);
163
165
  if (option.type === "integer") {
164
166
  const value = Number(rawValue);
@@ -180,7 +182,11 @@ export function normalizeCapabilityOptions(
180
182
  settings: { strict?: boolean } = {},
181
183
  ): NormalizedPaneFunctionOptions {
182
184
  if (!capability.botSafe) {
183
- return Object.fromEntries(Object.entries(options).map(([key, value]) => [key, value]));
185
+ // A rendered view takes any pane setting; the options it declares are checked.
186
+ return Object.fromEntries(Object.entries(options).map(([key, value]) => {
187
+ const option = resolveOptionDef(capability, key);
188
+ return option ? [option.key, normalizeOptionValue(option, value)] : [key, value];
189
+ }));
184
190
  }
185
191
 
186
192
  const normalized: Record<string, string | number | boolean> = {};
@@ -259,6 +259,11 @@ function paneCatalogSearchScore(entry: PaneCatalogEntry, query: string): number
259
259
  ].filter((value): value is string => !!value).join(" ").toLowerCase();
260
260
 
261
261
  let score = searchable.includes(query.toLowerCase()) ? 12 : 0;
262
+ // A query that is one of the function's own keywords ("hedge" for OSA) beats one found inside a
263
+ // longer keyword ("hedge funds" for 13F).
264
+ const keywords = [entry.label, ...entry.keywords, ...entry.capability.aliases]
265
+ .filter((value): value is string => !!value).map((value) => value.toLowerCase().replace(/\s+/g, " "));
266
+ if (keywords.includes(terms.join(" "))) score += 4;
262
267
  let matchedTerms = 0;
263
268
  for (const term of terms) {
264
269
  const normalized = normalizeLookupToken(term);
@@ -320,7 +325,8 @@ function optionChoices(option: PaneFunctionOptionDef): string[] {
320
325
 
321
326
  function optionFlags(option: PaneFunctionOptionDef): string {
322
327
  const choices = optionChoices(option).join("|");
323
- return `--${option.key} <${choices && choices.length <= MAX_INLINE_CHOICES ? choices : option.values?.length ? "value" : option.type}>`;
328
+ const value = choices && choices.length <= MAX_INLINE_CHOICES ? choices : option.values?.length ? "value" : option.placeholder ?? option.type;
329
+ return `--${option.key} <${value}>`;
324
330
  }
325
331
 
326
332
  function optionDescription(option: PaneFunctionOptionDef): string {
@@ -365,7 +371,12 @@ function renderCatalogEntry(entry: PaneCatalogEntry): string {
365
371
  }
366
372
 
367
373
  lines.push("", renderSection("Examples"));
368
- if (capability.reportReadiness !== "unsupported") lines.push(...wrapCommandLine(`gloomberb fn ${invocation}`, width));
374
+ if (capability.reportReadiness !== "unsupported") {
375
+ lines.push(...wrapCommandLine(`gloomberb fn ${invocation}`, width));
376
+ for (const option of capability.options) {
377
+ if (option.example) lines.push(...wrapCommandLine(`gloomberb fn ${invocation} ${option.example}`, width));
378
+ }
379
+ }
369
380
  lines.push(...wrapCommandLine(`gloomberb shot ${invocation} --output ${entry.token.toLowerCase()}.png`, width));
370
381
  return lines.join("\n");
371
382
  }