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
@@ -0,0 +1,64 @@
1
+ import { DEFAULT_THEME, listThemes, resolveThemeId, themes, type ThemeSummary } from "../../theme/themes";
2
+ import { fail } from "../errors";
3
+
4
+ const THEME_TRAIT_NOTES = { "high-contrast": "high contrast", "colorblind-safe": "color-blind safe" } as const;
5
+
6
+ /** `white (White Phosphor)`, or what the app shows instead of an id it does not know. */
7
+ export function describeThemeId(id: string): string {
8
+ const theme = themes[id];
9
+ if (theme) return `${id} (${theme.name})`;
10
+ return `${id}, not a theme: the app shows ${describeThemeId(DEFAULT_THEME)}`;
11
+ }
12
+
13
+ /** Every id with its name, from the registry, for an error that has to say what would have worked. */
14
+ function themeChoicesText(): string {
15
+ const choices = listThemes().map(({ id, name }) => `${id} (${name})`).join(", ");
16
+ return `Themes: ${choices}. gloomberb config themes lists them with notes.`;
17
+ }
18
+
19
+ /**
20
+ * The id `input` names, by id or display name in any case. Anything else
21
+ * fails with the closest id when there is one, and every id with its name.
22
+ */
23
+ export function requireThemeId(input: string): string {
24
+ const lookup = resolveThemeId(input);
25
+ if (lookup.ok) return lookup.id;
26
+ const hint = lookup.suggestion ? ` Did you mean ${describeThemeId(lookup.suggestion)}?` : "";
27
+ return fail(`Unknown theme "${input.trim()}".${hint}`, themeChoicesText());
28
+ }
29
+
30
+ export interface ThemeListRow extends Record<string, unknown> {
31
+ id: string;
32
+ name: string;
33
+ appearance: ThemeSummary["appearance"];
34
+ colorblindSafe: boolean;
35
+ highContrast: boolean;
36
+ default: boolean;
37
+ current: boolean;
38
+ }
39
+
40
+ export function themeListRows(currentThemeId: string | null): ThemeListRow[] {
41
+ return listThemes().map((theme) => ({
42
+ id: theme.id,
43
+ name: theme.name,
44
+ appearance: theme.appearance,
45
+ colorblindSafe: theme.traits.includes("colorblind-safe"),
46
+ highContrast: theme.traits.includes("high-contrast"),
47
+ default: theme.id === DEFAULT_THEME,
48
+ current: theme.id === currentThemeId,
49
+ }));
50
+ }
51
+
52
+ export function themeListNote(row: ThemeListRow): string {
53
+ const notes: string[] = [];
54
+ if (row.highContrast) notes.push(THEME_TRAIT_NOTES["high-contrast"]);
55
+ if (row.colorblindSafe) notes.push(THEME_TRAIT_NOTES["colorblind-safe"]);
56
+ if (row.default) notes.push("default");
57
+ if (row.current) notes.push("current");
58
+ return notes.join(", ");
59
+ }
60
+
61
+ /** The name of a theme id the registry knows, null for one it does not. */
62
+ export function themeName(id: string): string | null {
63
+ return themes[id]?.name ?? null;
64
+ }
@@ -22,8 +22,10 @@ import {
22
22
  type CliStatEntry,
23
23
  } from "../../utils/cli-output";
24
24
  import {
25
+ EXTENDED_SESSION_LABELS,
25
26
  exchangeShortName,
26
27
  getExtendedSessionDisplay,
28
+ getQuoteSessionFields,
27
29
  getRegularSessionDisplay,
28
30
  marketStateLabel,
29
31
  } from "../../market-data/market/status";
@@ -49,11 +51,13 @@ import {
49
51
  formatWatchlistNames,
50
52
  } from "../helpers";
51
53
  import { NotesFiles } from "../../plugins/builtin/notes/files";
52
- import { isUsEquityTicker } from "../../utils/sec";
54
+ import { isKnownNonUsListing } from "../../utils/sec";
53
55
  import { canonicalExchange, exchangeLabel, isKnownExchangeCode } from "../../utils/exchanges";
54
56
  import { failIfNotTraded, ListingArgError, listingIdentity, resolveCliListing, type CliListing } from "../listing-arg";
57
+ import { isNotATickerMessage } from "../not-a-ticker";
55
58
  import { sharesOutstandingInReceipts } from "../../utils/depositary-receipt";
56
59
  import { cliFreshnessFooter } from "../result";
60
+ import { providerMissReason } from "../../sources/provider-errors";
57
61
  import { exportEntriesTable, reportFooterLines, type CliReportTables } from "../report-tables";
58
62
  import type { ReportFreshness } from "../pane-functions/freshness";
59
63
  import { fundamentalsFreshness, quotesFreshness } from "../freshness";
@@ -178,26 +182,35 @@ function getFilingDescription(filing: SecFilingItem): string | undefined {
178
182
  return description;
179
183
  }
180
184
 
181
- function shouldFetchSecFilings(tickerFile: TickerRecord | null, financials: TickerFinancials): boolean {
182
- if (isUsEquityTicker(tickerFile)) {
183
- return true;
184
- }
185
-
186
- const quote = financials.quote;
187
- if (!quote || quote.currency.toUpperCase() !== "USD") {
188
- return false;
189
- }
190
-
191
- const exchangeHints = [
192
- tickerFile?.metadata.exchange,
193
- quote.exchangeName,
194
- quote.fullExchangeName,
195
- ]
196
- .filter((value): value is string => !!value)
197
- .join(" ")
198
- .toUpperCase();
199
-
200
- return /(NASDAQ|NYSE|AMEX|ARCA|IEX|BATS|PINK|OTC|NMS)/.test(exchangeHints);
185
+ /**
186
+ * SEC filings are asked for unless the listing is known to be outside the US:
187
+ * by the saved ticker, the requested venue, the quote, or the listing the
188
+ * source still describes when it has no current quote. A symbol nothing is
189
+ * known about yet is looked up as a US ticker.
190
+ */
191
+ function shouldFetchSecFilings(
192
+ symbol: string,
193
+ exchange: string,
194
+ tickerFile: TickerRecord | null,
195
+ financials: TickerFinancials,
196
+ ): boolean {
197
+ const saved = tickerFile?.metadata;
198
+ const { quote, quoteMetadata } = financials;
199
+ return !isKnownNonUsListing({
200
+ metadata: {
201
+ ticker: saved?.ticker ?? symbol,
202
+ exchange: saved?.exchange || exchange || quote?.listingExchangeName || quote?.exchangeName
203
+ || quoteMetadata?.listingExchangeName || "",
204
+ currency: saved?.currency || quote?.currency || quoteMetadata?.currency || "",
205
+ name: saved?.name ?? symbol,
206
+ broker_contracts: saved?.broker_contracts,
207
+ portfolios: [],
208
+ watchlists: [],
209
+ positions: [],
210
+ custom: {},
211
+ tags: [],
212
+ },
213
+ });
201
214
  }
202
215
 
203
216
  async function appendTickerPositions(lines: string[], tickerFile: TickerRecord | null, quote: TickerFinancials["quote"],
@@ -430,6 +443,7 @@ export async function buildTickerReport({
430
443
  config,
431
444
  toBase,
432
445
  notes,
446
+ quoteNote,
433
447
  recentNews = [],
434
448
  recentSecFilings = [],
435
449
  }: {
@@ -441,6 +455,8 @@ export async function buildTickerReport({
441
455
  config: AppConfig;
442
456
  toBase: (value: number, fromCurrency: string) => Promise<number>;
443
457
  notes?: string;
458
+ /** Why there is no quote, when a source said; "Quote unavailable." otherwise. */
459
+ quoteNote?: string;
444
460
  recentNews?: NewsArticle[];
445
461
  recentSecFilings?: SecFilingItem[];
446
462
  }): Promise<string> {
@@ -455,7 +471,7 @@ export async function buildTickerReport({
455
471
  const lines: string[] = [];
456
472
 
457
473
  lines.push(`${cliStyles.accent(quote?.symbol ?? symbol)} ${cliStyles.bold(name)}`);
458
- if (!quote) lines.push(cliStyles.muted("Quote unavailable."));
474
+ if (!quote) lines.push(cliStyles.muted(quoteNote ?? QUOTE_UNAVAILABLE));
459
475
 
460
476
  const summaryParts = [
461
477
  listingVenueLabel(listingExchange, quote, financials, tickerFile) || undefined,
@@ -537,8 +553,8 @@ export async function buildTickerReport({
537
553
  ]);
538
554
 
539
555
  appendMetricSection(lines, "Extended Hours", [
540
- ["Pre-Market", extendedRow("PRE")],
541
- ["After Hours", extendedRow("POST")],
556
+ [EXTENDED_SESSION_LABELS.PRE, extendedRow("PRE")],
557
+ [EXTENDED_SESSION_LABELS.POST, extendedRow("POST")],
542
558
  ]);
543
559
  }
544
560
 
@@ -627,10 +643,9 @@ function buildTickerStructuredData({
627
643
  symbol: quote.symbol,
628
644
  instrumentType: quote.instrumentType,
629
645
  name: quote.name,
630
- price: quote.price,
646
+ // Last and Change as the text report reads them, then the extended print from that close.
647
+ ...getQuoteSessionFields(quote),
631
648
  priceBasis: quote.priceBasis ?? null,
632
- change: quote.change,
633
- changePercent: quote.changePercent,
634
649
  currency: quote.currency,
635
650
  marketCap: quote.marketCap ?? null,
636
651
  volume: quote.volume ?? null,
@@ -682,6 +697,18 @@ function buildTickerStructuredData({
682
697
  };
683
698
  }
684
699
 
700
+ const QUOTE_UNAVAILABLE = "Quote unavailable.";
701
+
702
+ /** Asks for the quote the report lacks once more, for the reason the data service gave when it had none. */
703
+ async function quoteUnavailableNote(dataProvider: MarketContext["dataProvider"], symbol: string, exchange: string): Promise<string> {
704
+ try {
705
+ await dataProvider.getQuote(symbol, exchange);
706
+ } catch (error) {
707
+ return providerMissReason(error) ?? QUOTE_UNAVAILABLE;
708
+ }
709
+ return QUOTE_UNAVAILABLE;
710
+ }
711
+
685
712
  export async function ticker(symbol: string, dependencies: TickerCommandDependencies = {}) {
686
713
  const initMarketDataFn = dependencies.initMarketData ?? initMarketData;
687
714
  const failCommand = dependencies.fail ?? fail;
@@ -705,7 +732,10 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
705
732
  } catch (error) {
706
733
  // A known exchange the symbol is not listed on: say where it is.
707
734
  await failIfNotTraded(listing, { store, dataProvider }, { fail: failCommand });
708
- failCommand(`Failed to fetch data for ${normalized}.`, error instanceof Error ? error.message : String(error));
735
+ const reason = error instanceof Error ? error.message : String(error);
736
+ // A symbol no listing carries is not a failed fetch, and the message already names it.
737
+ if (isNotATickerMessage(reason)) failCommand(reason);
738
+ failCommand(`Failed to fetch data for ${normalized}.`, reason);
709
739
  }
710
740
 
711
741
  const hasResearchData = financials && (
@@ -742,7 +772,7 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
742
772
  }),
743
773
  // Outside the US the lookup checks the SEC registrant against the listing's company,
744
774
  // and refuses another company's filings.
745
- shouldFetchSecFilings(tickerFile, resolvedFinancials) && dataProvider.getSecFilings
775
+ shouldFetchSecFilings(requestSymbol, exchange, tickerFile, resolvedFinancials) && dataProvider.getSecFilings
746
776
  ? dataProvider.getSecFilings(requestSymbol, SEC_FILING_LIMIT, exchange || quote?.exchangeName || "",
747
777
  listingName ? { listingName } : undefined)
748
778
  : Promise.resolve([]),
@@ -754,10 +784,11 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
754
784
 
755
785
  // The quote is the feed in this report; without one, the fundamentals date it.
756
786
  const freshness = quote ? quotesFreshness([quote]) : fundamentalsFreshness(resolvedFinancials);
787
+ const quoteNote = quote ? undefined : await quoteUnavailableNote(dataProvider, requestSymbol, exchange);
757
788
  if (dependencies.printResult) {
758
789
  dependencies.printResult({
759
790
  freshness,
760
- warnings: quote ? undefined : ["Quote unavailable."],
791
+ warnings: quote ? undefined : [quoteNote!],
761
792
  data: buildTickerStructuredData({
762
793
  symbol: normalized,
763
794
  listing,
@@ -780,6 +811,7 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
780
811
  config,
781
812
  toBase,
782
813
  notes,
814
+ quoteNote,
783
815
  recentNews,
784
816
  recentSecFilings,
785
817
  }));
@@ -18,6 +18,7 @@ import type { DesktopExternalPluginBundle } from "../renderers/electrobun/shared
18
18
  import type { HttpProxyRequestEnvelope, HttpProxyResponseEnvelope } from "../utils/http-proxy-response";
19
19
  import type { PaneAccessGate } from "./pane-functions/access-gate";
20
20
  import { readVisibleKeyValues } from "./visible-key-values";
21
+ import { scrollTruncationReasons } from "./scroll-truncation";
21
22
  import { SHOT_API_PROXY_PREFIX, SHOT_HTTP_BRIDGE_PATH, SHOT_MARKET_BRIDGE_PATH } from "./desktop-pane-shot-routes";
22
23
  import { SESSION_COOKIE_NAMES } from "../api-client/session-cookie";
23
24
  import {
@@ -54,6 +55,11 @@ export interface DesktopPaneShotPayload {
54
55
  deviceScaleFactor?: number;
55
56
  /** Label drawn at the right edge of the pane title bar; null draws nothing. */
56
57
  watermark?: string | null;
58
+ /**
59
+ * The pane footer keeps a row for the capture's dated status line, filled
60
+ * once the pane has loaded. Absent for `--no-status` and for reports.
61
+ */
62
+ statusLine?: boolean;
57
63
  tickers: TickerRecord[];
58
64
  /** Broker accounts as last synced, by profile, so a broker portfolio shows its equity and cash. */
59
65
  brokerAccounts?: Record<string, BrokerAccount[]>;
@@ -111,6 +117,14 @@ export interface DesktopPaneShotRenderedRow {
111
117
 
112
118
  export interface DesktopPaneShotRenderResult {
113
119
  visibleText: string;
120
+ /**
121
+ * The rows a report reads, when a table reports every row (an option chain's
122
+ * strike window) rather than those in the viewport; `rows` stays what the
123
+ * capture shows. Absent when every table is read from the viewport.
124
+ */
125
+ reportRows?: DesktopPaneShotRenderedRow[];
126
+ /** `truncationReasons` without the cuts of tables whose every row is in `reportRows`. */
127
+ reportTruncationReasons?: string[];
114
128
  /** Text of the pane footers, where a pane says whether its data is live, delayed or stale. */
115
129
  footerText?: string;
116
130
  visibleKeyValues?: Array<{ label: string; text: string }>;
@@ -125,8 +139,16 @@ export interface DesktopPaneShotRenderResult {
125
139
  /** A wall that needs an account, or a Pro lock on part of the pane. */
126
140
  accessGate?: PaneAccessGate | null;
127
141
  semanticUi: RemoteUiNodeSnapshot[];
142
+ /** The status line drawn in the capture's footer, as it fit the width. */
143
+ statusLine?: string | null;
128
144
  }
129
145
 
146
+ /**
147
+ * The status line variants for a capture, longest first, worked out from what
148
+ * the pane rendered; null leaves the footer row empty.
149
+ */
150
+ type ShotStatusLine = (rendered: DesktopPaneShotRenderResult) => readonly string[] | null | Promise<readonly string[] | null>;
151
+
130
152
  type CdpResponse = {
131
153
  id?: number;
132
154
  result?: unknown;
@@ -163,7 +185,7 @@ export async function renderDesktopPaneScreenshot(
163
185
  payload: DesktopPaneShotPayload,
164
186
  outputPath: string,
165
187
  apiProxy: DesktopPaneShotApiProxy,
166
- options: { captureImage?: boolean; bridge?: DesktopPaneShotBridge } = {},
188
+ options: { captureImage?: boolean; bridge?: DesktopPaneShotBridge; statusLine?: ShotStatusLine } = {},
167
189
  ): Promise<DesktopPaneShotRenderResult> {
168
190
  const tempDir = await mkdtemp(join(tmpdir(), "gloom-pane-shot-"));
169
191
  let server: ReturnType<typeof Bun.serve> | null = null;
@@ -182,6 +204,7 @@ export async function renderDesktopPaneScreenshot(
182
204
  deviceScaleFactor: payload.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR,
183
205
  userDataDir: join(tempDir, "chrome-profile"),
184
206
  captureImage: options.captureImage !== false,
207
+ statusLine: payload.statusLine ? options.statusLine : undefined,
185
208
  });
186
209
  } finally {
187
210
  server?.stop(true);
@@ -402,6 +425,7 @@ async function capturePageScreenshot({
402
425
  deviceScaleFactor,
403
426
  userDataDir,
404
427
  captureImage,
428
+ statusLine,
405
429
  }: {
406
430
  chrome: string;
407
431
  url: string;
@@ -411,6 +435,7 @@ async function capturePageScreenshot({
411
435
  deviceScaleFactor: number;
412
436
  userDataDir: string;
413
437
  captureImage: boolean;
438
+ statusLine?: ShotStatusLine;
414
439
  }): Promise<DesktopPaneShotRenderResult> {
415
440
  await mkdir(userDataDir, { recursive: true });
416
441
  const port = 43000 + Math.floor(Math.random() * 10000);
@@ -457,6 +482,10 @@ async function capturePageScreenshot({
457
482
  });
458
483
  await waitForShotReady(session);
459
484
  const rendered = await readRenderedPaneState(session);
485
+ if (captureImage && statusLine) {
486
+ const variants = await statusLine(rendered);
487
+ rendered.statusLine = variants?.length ? await fitShotStatusLine(session, variants) : null;
488
+ }
460
489
  if (captureImage) {
461
490
  const screenshot = await session.send("Page.captureScreenshot", {
462
491
  format: "png",
@@ -515,6 +544,8 @@ const EMPTY_STATE_PATTERNS = [
515
544
  /\bNo transcribed calls(?: for [^.]+)? yet\b/gi,
516
545
  /\bNo short interest (?:data|found)\b/gi,
517
546
  /\bNo (?:House PTR |insider )?(?:trades|members|transactions|filings)\b/gi,
547
+ // OMON on an expiry the chain does not list, with the dates nearest it.
548
+ /\bNo (?:option contracts returned for )?expiry \d{4}-\d{2}-\d{2}(?:; (?:available|nearest others): \d{4}-\d{2}-\d{2}(?:, \d{4}-\d{2}-\d{2})*(?: \(\+\d+ more\))?|; the chain lists no expiries)?/gi,
518
549
  ];
519
550
 
520
551
  function stateMarkers(text: string, patterns: RegExp[]): string[] {
@@ -541,26 +572,39 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
541
572
  const semanticTables = semanticUi.filter((node) => node && node.role === "table");
542
573
  const rows = [];
543
574
  const truncationReasons = new Set();
575
+ const scrollReasons = (${scrollTruncationReasons.toString()});
576
+ const visibleTables = [...root.querySelectorAll('[data-gloom-role="data-table"]')].filter(isVisible);
577
+ // A table that publishes every row has nothing cut from a report, whatever the viewport shows.
578
+ const reportedRowsOf = (tableIndex) => {
579
+ const metadata = semanticTables[tableIndex] && semanticTables[tableIndex].metadata;
580
+ return metadata && Array.isArray(metadata.rows) ? metadata.rows : null;
581
+ };
582
+ const reportedTables = new Set(visibleTables.filter((_table, tableIndex) => reportedRowsOf(tableIndex)));
583
+ const reportRows = [];
584
+ const reportTruncationReasons = new Set();
585
+ const inReportedTable = (element) => {
586
+ const table = element.closest('[data-gloom-role="data-table"]');
587
+ return !!table && reportedTables.has(table);
588
+ };
544
589
  const markScrollTruncation = (element) => {
545
590
  if (!(element instanceof HTMLElement) || !isVisible(element)) return;
546
- if (
547
- element.getAttribute("data-gloom-scrollbar-y") === "visible"
548
- || element.scrollHeight > element.clientHeight + 1
549
- ) {
550
- truncationReasons.add("rows extend below the rendered viewport");
551
- }
552
- if (
553
- element.getAttribute("data-gloom-scrollbar-x") === "visible"
554
- || element.scrollWidth > element.clientWidth + 1
555
- ) {
556
- truncationReasons.add("columns extend beyond the rendered viewport");
557
- }
591
+ const reasons = scrollReasons({
592
+ scrollTop: element.scrollTop,
593
+ scrollHeight: element.scrollHeight,
594
+ clientHeight: element.clientHeight,
595
+ scrollLeft: element.scrollLeft,
596
+ scrollWidth: element.scrollWidth,
597
+ clientWidth: element.clientWidth,
598
+ scrollbarY: element.getAttribute("data-gloom-scrollbar-y") === "visible",
599
+ scrollbarX: element.getAttribute("data-gloom-scrollbar-x") === "visible",
600
+ });
601
+ reasons.forEach((reason) => truncationReasons.add(reason));
602
+ if (!inReportedTable(element)) reasons.forEach((reason) => reportTruncationReasons.add(reason));
558
603
  };
559
604
  [...root.querySelectorAll(
560
605
  '[data-gloom-scrollbar-x="visible"], [data-gloom-scrollbar-y="visible"], [data-gloom-role="data-table-body-scroll"]',
561
606
  )].forEach(markScrollTruncation);
562
- [...root.querySelectorAll('[data-gloom-role="data-table"]')]
563
- .filter(isVisible)
607
+ visibleTables
564
608
  .forEach((table, tableIndex) => {
565
609
  const tableMetadata = semanticTables[tableIndex] && semanticTables[tableIndex].metadata || {};
566
610
  const semanticColumns = Array.isArray(tableMetadata.columns) ? tableMetadata.columns : [];
@@ -586,6 +630,7 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
586
630
  ))
587
631
  ) {
588
632
  truncationReasons.add("one or more cells are visibly clipped");
633
+ if (!reportedTables.has(table)) reportTruncationReasons.add("one or more cells are visibly clipped");
589
634
  }
590
635
  const instant = cell.getAttribute("data-gloom-cell-instant");
591
636
  return {
@@ -598,12 +643,27 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
598
643
  };
599
644
  }).filter((cell) => cell.text.length > 0);
600
645
  if (cells.length === 0) return;
601
- rows.push({
646
+ const captured = {
602
647
  tableIndex,
603
648
  rowIndex,
604
649
  ...(rowKey ? { key: rowKey } : {}),
605
650
  selected: row.getAttribute("data-selected") === "true",
606
651
  cells,
652
+ };
653
+ rows.push(captured);
654
+ if (!reportedTables.has(table)) reportRows.push(captured);
655
+ });
656
+ const reported = reportedRowsOf(tableIndex);
657
+ if (!reported) return;
658
+ reported.forEach((row, rowIndex) => {
659
+ const cells = Array.isArray(row && row.cells) ? row.cells.filter((cell) => cell && cell.text) : [];
660
+ if (cells.length === 0) return;
661
+ reportRows.push({
662
+ tableIndex,
663
+ rowIndex,
664
+ ...(typeof row.key === "string" ? { key: row.key } : {}),
665
+ selected: row.selected === true,
666
+ cells,
607
667
  });
608
668
  });
609
669
  });
@@ -642,6 +702,7 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
642
702
  rows,
643
703
  truncated: truncationReasons.size > 0,
644
704
  truncationReasons: [...truncationReasons],
705
+ ...(reportedTables.size > 0 ? { reportRows, reportTruncationReasons: [...reportTruncationReasons] } : {}),
645
706
  semanticUi,
646
707
  };
647
708
  })()`,
@@ -660,6 +721,8 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
660
721
  rows?: DesktopPaneShotRenderedRow[];
661
722
  truncated?: boolean;
662
723
  truncationReasons?: string[];
724
+ reportRows?: DesktopPaneShotRenderedRow[];
725
+ reportTruncationReasons?: string[];
663
726
  semanticUi?: RemoteUiNodeSnapshot[];
664
727
  };
665
728
  };
@@ -687,6 +750,10 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
687
750
  rows,
688
751
  truncated: value?.truncated === true || textShowsEllipsis,
689
752
  truncationReasons,
753
+ ...(Array.isArray(value?.reportRows) ? {
754
+ reportRows: value.reportRows,
755
+ reportTruncationReasons: (value.reportTruncationReasons ?? []).filter((reason): reason is string => typeof reason === "string"),
756
+ } : {}),
690
757
  loadingStateDetected: value?.loadingStateDetected === true || loadingStateMarkers.length > 0,
691
758
  errorStateDetected: value?.errorStateDetected === true || errorStateMarkers.length > 0,
692
759
  errorStateMarkers,
@@ -697,6 +764,20 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
697
764
  };
698
765
  }
699
766
 
767
+ /** Draws the first variant that fits the pane footer, and returns what it drew. */
768
+ async function fitShotStatusLine(session: CdpSession, variants: readonly string[]): Promise<string | null> {
769
+ const result = await session.send("Runtime.evaluate", {
770
+ expression: `(() => {
771
+ const fit = window.__GLOOM_CLI_SHOT_FIT_STATUS__;
772
+ return fit ? fit(${JSON.stringify(variants)}) : null;
773
+ })()`,
774
+ awaitPromise: true,
775
+ returnByValue: true,
776
+ }) as { result?: { value?: unknown } };
777
+ const value = result.result?.value;
778
+ return typeof value === "string" ? value : null;
779
+ }
780
+
700
781
  async function waitForPageWebSocket(port: number, targetUrl: string): Promise<string> {
701
782
  for (let attempt = 0; attempt < CHROME_POLL_ATTEMPTS; attempt += 1) {
702
783
  try {
package/src/cli/errors.ts CHANGED
@@ -1,13 +1,19 @@
1
1
  import type { AppPersistence } from "../data/app-persistence";
2
+ import { ApiRequestError } from "../api-client/errors";
2
3
  import { DEFAULT_CLI_OPTIONS, type CliGlobalOptions } from "./options";
3
4
  import { serializeCliError, type CliErrorObject } from "./result";
4
5
  import { cliStyles, cliTerminalWidth, wrapText } from "../utils/cli-output";
6
+ import { withSearchHints } from "./not-a-ticker";
5
7
 
6
8
  const USAGE_PREFIX = "Usage: ";
9
+ /** The code of every error a command fails with: bad input, nothing found, an option it does not take. */
10
+ const CLI_ERROR_CODE = "cli_error";
7
11
 
8
12
  export interface CliErrorContext {
9
13
  /** The command that failed, used to point at its help. */
10
14
  command?: string;
15
+ /** What the user typed after the command, so a hint can repeat their own spelling of a symbol. */
16
+ args?: readonly string[];
11
17
  }
12
18
 
13
19
  class CliFailure extends Error {
@@ -15,7 +21,7 @@ class CliFailure extends Error {
15
21
  readonly details?: unknown;
16
22
  readonly retryable?: boolean;
17
23
 
18
- constructor(message: string, details?: unknown, code = "cli_error", retryable?: boolean) {
24
+ constructor(message: string, details?: unknown, code = CLI_ERROR_CODE, retryable?: boolean) {
19
25
  super(message);
20
26
  this.name = "CliFailure";
21
27
  this.code = code;
@@ -37,6 +43,11 @@ function isCliFailure(error: unknown): error is CliFailure {
37
43
  return error instanceof CliFailure;
38
44
  }
39
45
 
46
+ /** A global option that does not parse is a usage error like any other, not an unexpected one. */
47
+ export function asUsageError(error: unknown): Error {
48
+ return isCliFailure(error) ? error : new CliFailure(error instanceof Error ? error.message : String(error));
49
+ }
50
+
40
51
  function cliErrorObject(error: unknown): CliErrorObject {
41
52
  if (isCliFailure(error)) {
42
53
  return {
@@ -46,6 +57,10 @@ function cliErrorObject(error: unknown): CliErrorObject {
46
57
  retryable: error.retryable,
47
58
  };
48
59
  }
60
+ // Gloom Cloud refused what was asked (an unknown series, a bad date): the input was wrong, not the program.
61
+ if (error instanceof ApiRequestError && error.status === 400) {
62
+ return { code: CLI_ERROR_CODE, message: error.message };
63
+ }
49
64
  return {
50
65
  code: "unexpected_error",
51
66
  message: error instanceof Error ? error.message : String(error),
@@ -87,10 +102,18 @@ function formatCliErrorText(error: CliErrorObject, context: CliErrorContext): st
87
102
  ].join("\n");
88
103
  }
89
104
 
105
+ function hintedError(error: CliErrorObject, args: readonly string[]): CliErrorObject {
106
+ return {
107
+ ...error,
108
+ message: withSearchHints(error.message, args),
109
+ ...(typeof error.details === "string" ? { details: withSearchHints(error.details, args) } : {}),
110
+ };
111
+ }
112
+
90
113
  export function printCliError(error: unknown, options: CliGlobalOptions, context: CliErrorContext = {}): void {
91
114
  if (options.quiet && options.format === "text") return;
92
115
  const errorObject = cliErrorObject(error);
93
116
  console.error(options.format === "text"
94
- ? formatCliErrorText(errorObject, context)
117
+ ? formatCliErrorText(hintedError(errorObject, context.args ?? []), context)
95
118
  : serializeCliError(errorObject, options));
96
119
  }
@@ -25,17 +25,20 @@ export function rowsFreshness(
25
25
  }
26
26
 
27
27
  /** One row per quote shown, as `fn` quote reports carry them: live, delayed or stale for its session. */
28
- function quoteFreshnessRow(quote: Quote): HeadlessPaneRow {
29
- return { ...quoteFreshnessFields(quote), updatedAt: quote.lastUpdated };
28
+ function quoteFreshnessRow(quote: Quote, now: number): HeadlessPaneRow {
29
+ return { ...quoteFreshnessFields(quote, now), updatedAt: quote.lastUpdated };
30
30
  }
31
31
 
32
- /** Worst of across the quotes shown, dated by the newest. Quotes that failed to load say nothing. */
32
+ /**
33
+ * Worst of across the quotes shown, dated by the newest, with their delay and
34
+ * where their markets stand. Quotes that failed to load say nothing.
35
+ */
33
36
  export function quotesFreshness(
34
37
  quotes: Iterable<Quote | null | undefined>,
35
38
  declared?: HeadlessPaneFreshness,
36
39
  now = Date.now(),
37
40
  ): ReportFreshness | undefined {
38
- const rows = [...quotes].filter((quote): quote is Quote => quote != null).map(quoteFreshnessRow);
41
+ const rows = [...quotes].filter((quote): quote is Quote => quote != null).map((quote) => quoteFreshnessRow(quote, now));
39
42
  return rows.length > 0 ? rowsFreshness(rows, declared, undefined, now) : undefined;
40
43
  }
41
44
 
@@ -0,0 +1,57 @@
1
+ /**
2
+ * What `gloomberb fx` is asked for. A bare code is one unit of it in the base
3
+ * currency (`NGN`: USD per 1 NGN). A pair is market convention, BASE/QUOTE:
4
+ * `USD/NGN` is how many NGN one USD buys, and `ZAR/NGN` a cross of two codes
5
+ * with neither in USD.
6
+ */
7
+ export interface FxRequest {
8
+ /** The currency one unit of which is priced: NGN in `NGN`, USD in `USD/NGN`. */
9
+ currency: string;
10
+ /** The currency the price is in: the base currency for a bare code, the quote of a pair. */
11
+ baseCurrency: string;
12
+ }
13
+
14
+ const CURRENCY_CODE = /^[A-Z]{3}$/;
15
+
16
+ /** The request, or the sentence saying what is wrong with the argument. */
17
+ export function parseFxRequest(raw: string, baseCurrency: string): FxRequest | { error: string } {
18
+ const text = raw.trim().toUpperCase();
19
+ const parts = text.split("/").map((part) => part.trim());
20
+ if (parts.length > 2 || parts.some((part) => !part)) return { error: `"${raw}" is not a currency or a pair such as USD/NGN.` };
21
+ const bad = parts.filter((part) => !CURRENCY_CODE.test(part));
22
+ if (bad.length > 0) return { error: `${bad.join(" and ")} ${bad.length > 1 ? "are not currency codes" : "is not a currency code"}; use three letters such as NGN.` };
23
+ return parts.length === 2
24
+ ? { currency: parts[0]!, baseCurrency: parts[1]! }
25
+ : { currency: parts[0]!, baseCurrency: baseCurrency.trim().toUpperCase() };
26
+ }
27
+
28
+ /**
29
+ * The rate of `currency` in `baseCurrency` from each one's USD value (USD per
30
+ * unit), the way every cross is built. NaN when a leg is missing or unusable.
31
+ */
32
+ export function crossRate(currencyInUsd: number | null | undefined, baseInUsd: number | null | undefined): number {
33
+ if (!isUsableRate(currencyInUsd) || !isUsableRate(baseInUsd)) return Number.NaN;
34
+ return currencyInUsd / baseInUsd;
35
+ }
36
+
37
+ export function isUsableRate(value: number | null | undefined): value is number {
38
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
39
+ }
40
+
41
+ /**
42
+ * A rate to six significant digits, so a tiny one keeps its figures
43
+ * (0.000752791) and float noise goes; from 1000 up, to the cent (1328.39).
44
+ * Never in exponent notation, and without trailing zeros.
45
+ */
46
+ export function formatFxRate(rate: number): string {
47
+ if (!Number.isFinite(rate)) return String(rate);
48
+ const size = Math.abs(rate);
49
+ const decimals = size >= 1000 || size === 0 ? 2 : Math.min(15, Math.max(0, 5 - Math.floor(Math.log10(size))));
50
+ const fixed = rate.toFixed(decimals);
51
+ return fixed.includes(".") ? fixed.replace(/\.?0+$/, "") : fixed;
52
+ }
53
+
54
+ /** Both directions on one line: `1 NGN = 0.000752791 USD (USD/NGN 1328.39)`. */
55
+ export function describeFxRate(request: FxRequest, rate: number): string {
56
+ return `1 ${request.currency} = ${formatFxRate(rate)} ${request.baseCurrency} (${request.baseCurrency}/${request.currency} ${formatFxRate(1 / rate)})`;
57
+ }