gloomberb 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -18,8 +18,18 @@ import type {
18
18
  HeadlessSeriesResult,
19
19
  HeadlessSnapshotResult,
20
20
  } from "../../types/plugin";
21
- import { cliStyles, renderSection, renderStats, renderTable } from "../../utils/cli-output";
21
+ import { cliStyles, renderSection, renderStats, renderTable, wrapToTerminal } from "../../utils/cli-output";
22
22
  import { humanizeCliKey } from "../result";
23
+ import {
24
+ exportEntriesTable,
25
+ exportNumber,
26
+ exportRowsTable,
27
+ float32Shortest,
28
+ reportFooterLines,
29
+ type CliReportTable,
30
+ type CliReportTables,
31
+ } from "../report-tables";
32
+ import { labelWithUnit } from "../../utils/display-number";
23
33
  import { observationDate } from "../../time-series/price-comparison";
24
34
  import type { ResolvedSeries } from "../../time-series/types";
25
35
  import type { PaneFunctionReport } from "./report";
@@ -316,20 +326,6 @@ function displayNumber(value: number): string {
316
326
  return String(float32Shortest(value) ?? Number(value.toPrecision(12)));
317
327
  }
318
328
 
319
- /**
320
- * Some feeds store prices as float32, so 338.93 arrives as 338.929992676. A value
321
- * that is exactly a float32 prints as the shortest decimal that rounds back to it,
322
- * which leaves genuine doubles (never exactly float32 unless short) untouched.
323
- */
324
- function float32Shortest(value: number): number | null {
325
- if (Math.fround(value) !== value) return null;
326
- for (let digits = 1; digits <= 9; digits += 1) {
327
- const candidate = Number(value.toPrecision(digits));
328
- if (Math.fround(candidate) === value) return candidate;
329
- }
330
- return null;
331
- }
332
-
333
329
  function displayTime(time: number): string {
334
330
  if (!Number.isFinite(time)) return "-";
335
331
  return formatUtcTime(new Date(time));
@@ -337,14 +333,36 @@ function displayTime(time: number): string {
337
333
 
338
334
  const CALENDAR_RESOLUTIONS = new Set(["1d", "1wk", "1mo"]);
339
335
 
340
- /** Session bars print the trading date the chart and comparison notice use, not the bar's open instant. */
341
- function seriesPointDate(series: HeadlessSeries, date: HeadlessSeries["points"][number]["date"]): string {
342
- const time = date instanceof Date ? date.getTime() : typeof date === "number" ? date : Date.parse(date);
343
- if (!Number.isFinite(time)) return typeof date === "string" ? date : "-";
336
+ type SeriesPointDate = HeadlessSeries["points"][number]["date"];
337
+
338
+ function seriesPointTime(date: SeriesPointDate): number {
339
+ return date instanceof Date ? date.getTime() : typeof date === "number" ? date : Date.parse(date);
340
+ }
341
+
342
+ function isDateOnly(date: SeriesPointDate): boolean {
343
+ return typeof date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(date);
344
+ }
345
+
346
+ /** Session bars are dated by the trading date the chart and comparison notice use, not the bar's open instant. */
347
+ function calendarSeries(series: HeadlessSeries): (HeadlessSeries & Partial<Pick<ResolvedSeries, "historyResolution" | "timeBasis">>) | null {
344
348
  const resolved = series as HeadlessSeries & Partial<Pick<ResolvedSeries, "historyResolution" | "timeBasis">>;
345
- return resolved.historyResolution && CALENDAR_RESOLUTIONS.has(resolved.historyResolution)
346
- ? observationDate(time, resolved)
347
- : formatUtcTime({ time, dateOnly: typeof date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(date) });
349
+ return resolved.historyResolution && CALENDAR_RESOLUTIONS.has(resolved.historyResolution) ? resolved : null;
350
+ }
351
+
352
+ function seriesPointDate(series: HeadlessSeries, date: SeriesPointDate): string {
353
+ const time = seriesPointTime(date);
354
+ if (!Number.isFinite(time)) return typeof date === "string" ? date : "-";
355
+ const calendar = calendarSeries(series);
356
+ return calendar ? observationDate(time, calendar) : formatUtcTime({ time, dateOnly: isDateOnly(date) });
357
+ }
358
+
359
+ /** The same date for a spreadsheet: the trading date, the calendar date, or the ISO instant. */
360
+ function seriesPointExportDate(series: HeadlessSeries, date: SeriesPointDate): string {
361
+ const time = seriesPointTime(date);
362
+ if (!Number.isFinite(time)) return typeof date === "string" ? date : "";
363
+ const calendar = calendarSeries(series);
364
+ if (calendar) return observationDate(time, calendar);
365
+ return isDateOnly(date) ? date as string : new Date(time).toISOString().replace(".000Z", "Z");
348
366
  }
349
367
 
350
368
  function seriesValue(series: HeadlessSeries, value: number | null): string {
@@ -364,6 +382,7 @@ function renderRows(
364
382
  header: humanizeCliKey(column.header),
365
383
  align: column.align,
366
384
  width: column.width,
385
+ shrink: column.shrink,
367
386
  })),
368
387
  rows.map((row) => columns.map((column) => {
369
388
  const value = row[column.key];
@@ -377,6 +396,10 @@ function renderEntries(entries: HeadlessPaneEntry[]): string {
377
396
  return renderStats(entries.map((entry) => [entry.label, entry.formatted ?? displayValue(entry.value)]));
378
397
  }
379
398
 
399
+ function statisticsEntries(stats: NonNullable<HeadlessSeriesResult["stats"]>): HeadlessPaneEntry[] {
400
+ return Array.isArray(stats) ? stats : Object.entries(stats).map(([label, value]) => ({ label, value }));
401
+ }
402
+
380
403
  function renderBundle(
381
404
  definition: HeadlessPaneDefinition,
382
405
  result: HeadlessBundleResult,
@@ -412,14 +435,118 @@ function renderSeries(result: HeadlessSeriesResult): string[] {
412
435
  ];
413
436
  const lines = [renderRows(rows, columns, undefined)];
414
437
  if (result.stats) {
415
- const entries = Array.isArray(result.stats)
416
- ? result.stats
417
- : Object.entries(result.stats).map(([label, value]) => ({ label, value }));
418
- lines.push("", renderSection("Statistics"), renderEntries(entries));
438
+ lines.push("", renderSection("Statistics"), renderEntries(statisticsEntries(result.stats)));
419
439
  }
420
440
  return lines;
421
441
  }
422
442
 
443
+ /** The headers the text table prints. */
444
+ function displayedColumns(columns: HeadlessPaneColumn[]): HeadlessPaneColumn[] {
445
+ return columns.map((column) => ({ ...column, header: humanizeCliKey(column.header) }));
446
+ }
447
+
448
+ const SERIES_FIELDS = ["value", "open", "high", "low", "close", "volume"] as const;
449
+
450
+ /** Every point of a series: its date (the trading date for session bars) and its values, in the series' unit. */
451
+ function seriesTable(series: HeadlessSeries): CliReportTable {
452
+ const fields = SERIES_FIELDS.filter((field) => series.points.some((point) => point[field] != null));
453
+ const unit = series.unit?.trim() ?? "";
454
+ const dates = series.points.map((point) => seriesPointExportDate(series, point.date));
455
+ const header = (field: (typeof SERIES_FIELDS)[number]) => {
456
+ const name = `${field[0]!.toUpperCase()}${field.slice(1)}`;
457
+ return field === "volume" ? name : labelWithUnit(name, unit);
458
+ };
459
+ return {
460
+ title: series.label,
461
+ columns: [dates.every((date) => date.length <= 10) ? "Date" : "Time", ...fields.map(header)],
462
+ rows: series.points.map((point, index) => [
463
+ dates[index]!,
464
+ ...fields.map((field) => {
465
+ const value = point[field];
466
+ return typeof value === "number" && Number.isFinite(value) ? exportNumber(value) : "";
467
+ }),
468
+ ]),
469
+ };
470
+ }
471
+
472
+ /** What a report leaves out, for the closing `# incomplete` line. */
473
+ function incompleteDetail(
474
+ result: HeadlessPaneResult,
475
+ status: { complete: boolean; unavailableSymbols: string[] },
476
+ ): boolean | string {
477
+ if (status.complete) return false;
478
+ const requested = result.metadata?.requested;
479
+ const available = result.metadata?.available;
480
+ if (typeof requested === "number" && typeof available === "number" && available < requested) {
481
+ return `${available} of ${requested} available`;
482
+ }
483
+ return status.unavailableSymbols.length > 0 ? `no data for ${status.unavailableSymbols.join(", ")}` : true;
484
+ }
485
+
486
+ function textNotices(result: HeadlessPaneResult): string[] {
487
+ const notices = result.metadata?.notices;
488
+ if (!Array.isArray(notices)) return [];
489
+ return [...new Set(notices.filter((notice): notice is string => typeof notice === "string" && notice.trim().length > 0))];
490
+ }
491
+
492
+ /**
493
+ * The tables of a headless report for `--csv` and `--ndjson`, with the text
494
+ * view's columns: a rows or snapshot report is one table, a bundle one per
495
+ * section (key/value sections as `Metric,Value`), a series report one per
496
+ * series with every point, then its statistics. The footer carries the text
497
+ * view's closing line, notices, notes and errors.
498
+ */
499
+ export function headlessReportTables(
500
+ definition: HeadlessPaneDefinition,
501
+ result: HeadlessPaneResult,
502
+ args: HeadlessPaneLoadArgs,
503
+ fallbackTitle: string,
504
+ freshness: ReportFreshness,
505
+ status: { complete: boolean; unavailableSymbols: string[] },
506
+ ): CliReportTables {
507
+ const title = reportTitle(definition, args, result, fallbackTitle);
508
+ let tables: CliReportTable[];
509
+ switch (definition.shape) {
510
+ case "rows": {
511
+ const rowsResult = result as HeadlessPaneResult & { rows: HeadlessPaneRow[]; columns?: HeadlessPaneColumn[] };
512
+ const columns = displayedColumns(columnsFor(rowsResult.rows, rowsResult.columns, definition.columns));
513
+ tables = [exportRowsTable(title, columns, rowsResult.rows)];
514
+ break;
515
+ }
516
+ case "bundle":
517
+ tables = (result as HeadlessBundleResult).sections.map((section) => (
518
+ "rows" in section && section.rows
519
+ ? exportRowsTable(section.title, displayedColumns(columnsFor(section.rows, section.columns, definition.columns)), section.rows)
520
+ : exportEntriesTable(section.title, section.entries)
521
+ ));
522
+ break;
523
+ case "series": {
524
+ const seriesResult = result as HeadlessSeriesResult;
525
+ tables = seriesResult.series.map(seriesTable);
526
+ if (seriesResult.stats) tables.push(exportEntriesTable("Statistics", statisticsEntries(seriesResult.stats)));
527
+ break;
528
+ }
529
+ case "snapshot": {
530
+ const snapshot = result as HeadlessSnapshotResult;
531
+ tables = [exportRowsTable(title, displayedColumns(columnsFor(snapshot.items, undefined, definition.columns)), snapshot.items)];
532
+ break;
533
+ }
534
+ default: {
535
+ const _exhaustive: never = definition.shape;
536
+ return _exhaustive;
537
+ }
538
+ }
539
+ return {
540
+ tables,
541
+ footer: reportFooterLines({
542
+ freshness,
543
+ incomplete: incompleteDetail(result, status),
544
+ errors: result.errors,
545
+ notes: [...textNotices(result), ...(result.notes ?? [])],
546
+ }),
547
+ };
548
+ }
549
+
423
550
  function reportTitle(
424
551
  definition: HeadlessPaneDefinition,
425
552
  args: HeadlessPaneLoadArgs,
@@ -433,8 +560,8 @@ function reportTitle(
433
560
  /** One message stays on the label's line; several each get a line of their own, so none runs into the next. */
434
561
  function renderMessages(label: string, messages: readonly string[]): string[] {
435
562
  return messages.length === 1
436
- ? [cliStyles.warning(`${label}: ${messages[0]}`)]
437
- : [cliStyles.warning(`${label}:`), ...messages.map((message) => ` ${message}`)];
563
+ ? [cliStyles.warning(wrapToTerminal(`${label}: ${messages[0]}`))]
564
+ : [cliStyles.warning(`${label}:`), ...messages.map((message) => wrapToTerminal(` ${message}`))];
438
565
  }
439
566
 
440
567
  /**
@@ -450,11 +577,8 @@ export function renderHeadlessPaneText(
450
577
  freshness: ReportFreshness = deriveHeadlessFreshness(definition, result),
451
578
  ): string {
452
579
  const lines = [cliStyles.bold(reportTitle(definition, args, result, fallbackTitle)), ""];
453
- const notices = result.metadata?.notices;
454
- if (Array.isArray(notices)) {
455
- const textNotices = [...new Set(notices.filter((notice): notice is string => typeof notice === "string" && notice.trim().length > 0))];
456
- if (textNotices.length) lines.push(...textNotices, "");
457
- }
580
+ const notices = textNotices(result);
581
+ if (notices.length) lines.push(...notices, "");
458
582
  switch (definition.shape) {
459
583
  case "rows": {
460
584
  const rowsResult = result as HeadlessPaneResult & { rows: HeadlessPaneRow[]; columns?: HeadlessPaneColumn[] };
@@ -480,7 +604,7 @@ export function renderHeadlessPaneText(
480
604
  }
481
605
  if (result.notes?.length) lines.push("", ...renderMessages("Notes", result.notes));
482
606
  if (result.errors?.length) lines.push("", ...renderMessages("Errors", result.errors));
483
- return [lines.join("\n").trimEnd(), "", cliStyles.muted(formatFreshnessLine(freshness))].join("\n");
607
+ return [lines.join("\n").trimEnd(), "", cliStyles.muted(wrapToTerminal(formatFreshnessLine(freshness)))].join("\n");
484
608
  }
485
609
 
486
610
  function resultRowCount(definition: HeadlessPaneDefinition, result: HeadlessPaneResult): number {
@@ -513,6 +637,7 @@ export async function buildHeadlessFunctionReport(
513
637
  const unavailableSymbols = loaded.result.unavailableSymbols ?? (rowCount === 0 ? symbols : []);
514
638
  const serialized = serializeHeadlessPaneResult(loaded.definition, loaded.result);
515
639
  const freshness = deriveHeadlessFreshness(loaded.definition, loaded.result);
640
+ const complete = loaded.result.complete !== false && unavailableSymbols.length === 0 && !loaded.result.errors?.length;
516
641
  return {
517
642
  data: {
518
643
  kind: loaded.definition.shape,
@@ -522,7 +647,7 @@ export async function buildHeadlessFunctionReport(
522
647
  options: resolved.options,
523
648
  rowCount,
524
649
  empty: rowCount === 0,
525
- complete: loaded.result.complete !== false && unavailableSymbols.length === 0 && !loaded.result.errors?.length,
650
+ complete,
526
651
  unavailableSymbols,
527
652
  ...serialized,
528
653
  freshness,
@@ -534,5 +659,13 @@ export async function buildHeadlessFunctionReport(
534
659
  resolved.label,
535
660
  freshness,
536
661
  ),
662
+ tables: headlessReportTables(
663
+ loaded.definition,
664
+ loaded.result,
665
+ loaded.args,
666
+ resolved.label,
667
+ freshness,
668
+ { complete, unavailableSymbols },
669
+ ),
537
670
  };
538
671
  }
@@ -23,6 +23,7 @@ import {
23
23
  import { accessGateStatus, incompleteReportGateMessage } from "./access-gate";
24
24
  import { withPersistedCloudSession } from "./cloud-session";
25
25
  import { loadForListing } from "../listing-arg";
26
+ import { selectReportTables } from "../report-tables";
26
27
 
27
28
  async function withPaneRuntime<T>(
28
29
  ctx: CliCommandContext,
@@ -33,7 +34,7 @@ async function withPaneRuntime<T>(
33
34
  registry: PaneFunctionCatalog;
34
35
  resolved: ResolvedPaneFunction;
35
36
  }) => Promise<T>,
36
- settings: { strictHeadlessOptions?: boolean } = {},
37
+ settings: { strictHeadlessOptions?: boolean; tableSection?: boolean } = {},
37
38
  ): Promise<T> {
38
39
  return withMarketData(ctx, async (market) => {
39
40
  const context: MarketContext = ctx.cliOptions.refresh ? { ...market, refresh: true } : market;
@@ -59,6 +60,11 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
59
60
  + `Use "gloomberb catalog ${resolved.token}" to inspect readiness.`,
60
61
  );
61
62
  }
63
+ const tabular = ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson";
64
+ if (resolved.tableSection !== undefined && !tabular) {
65
+ throw new Error("--section picks one table of --csv or --ndjson output.");
66
+ }
67
+ if (resolved.tableSection === true) throw new Error("--section needs a section title or number.");
62
68
  // A report that fails or comes back empty for an exchange the symbol is not listed on says so.
63
69
  const report = await withPersistedCloudSession(
64
70
  context,
@@ -82,8 +88,9 @@ export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
82
88
  }
83
89
  ctx.printResult({ data: report.data }, {
84
90
  text: () => report.text,
91
+ ...(tabular ? { tables: selectReportTables(report.tables, resolved.tableSection) } : {}),
85
92
  });
86
- }, { strictHeadlessOptions: true });
93
+ }, { strictHeadlessOptions: true, tableSection: true });
87
94
  });
88
95
  }
89
96
 
@@ -111,6 +118,7 @@ export async function runPaneScreenshot(args: string[], ctx: CliCommandContext)
111
118
  theme: parsed.theme,
112
119
  scale: parsed.scale,
113
120
  watermark: parsed.watermark,
121
+ statusLine: parsed.status !== false,
114
122
  options: parsed.options,
115
123
  });
116
124
  if (parsed.requireBotSafe && !result.usable) {
@@ -130,7 +138,9 @@ export async function runPaneScreenshot(args: string[], ctx: CliCommandContext)
130
138
  ["Rows", String(data.rowCount)],
131
139
  ["Status", issues.length > 0 ? cliStyles.warning(issues.join(", ")) : cliStyles.success("complete")],
132
140
  ];
141
+ if (data.render.statusLine) stats.push(["Status line", data.render.statusLine]);
133
142
  if (data.unusableReason) stats.push(["Reason", data.unusableReason]);
143
+ if (data.notices?.length) stats.push(["Notes", data.notices.join(" ")]);
134
144
  if (data.unavailableSymbols.length > 0) stats.push(["No data for", data.unavailableSymbols.join(", ")]);
135
145
  if (data.render.emptyStateMarkers.length > 0) stats.push(["Empty states", data.render.emptyStateMarkers.join(", ")]);
136
146
  if (data.render.missingExpectedText.length > 0) stats.push(["Missing text", data.render.missingExpectedText.join(", ")]);
@@ -0,0 +1,133 @@
1
+ import { canonicalExchange, isUsListingExchange } from "../../utils/exchanges";
2
+ import {
3
+ activeUsMarketSession,
4
+ isAlwaysOpenExchange,
5
+ isRegularSessionTime,
6
+ latestRegularSessionClose,
7
+ latestRegularSessionOpen,
8
+ nextRegularSessionOpen,
9
+ } from "../../market-data/market/freshness";
10
+ import { currentFxClosureStart } from "../../utils/fx-market-hours";
11
+ import { zonedDateKey } from "../../utils/zoned-date-time";
12
+ import type { ReportTime } from "../../utils/utc-time";
13
+
14
+ /** Where the venues behind a quote report stand, as a report says it after the delay. */
15
+ type ReportMarketState = "open" | "pre-market" | "after-hours" | "closed";
16
+
17
+ export interface ReportMarket {
18
+ state: ReportMarketState;
19
+ /** While no venue is in its regular session: the next regular open of the first to open, when its calendar is known. */
20
+ reopensAt?: string;
21
+ /** That open's date at its venue, so a Monday open in Sydney reads Monday. */
22
+ reopensOn?: string;
23
+ }
24
+
25
+ /** One dated row of a quote report: the venue it trades on and the session its source reports. */
26
+ export interface SessionObservation {
27
+ exchange: string;
28
+ marketState?: string;
29
+ time: ReportTime | null;
30
+ }
31
+
32
+ interface VenueSession {
33
+ state: ReportMarketState | "always";
34
+ next?: { date: string; open: number };
35
+ /** The latest regular session that has closed: its local date and open. */
36
+ last?: { date: string; open: number | null };
37
+ timeZone?: string;
38
+ }
39
+
40
+ const CLOSED_STATES = new Set(["CLOSED", "PREPRE", "POSTPOST"]);
41
+
42
+ /**
43
+ * A venue's session now, from its calendar (weekends and published holidays),
44
+ * or from the state its source reports when the app has no calendar for it,
45
+ * such as an index publisher or a futures exchange. Null when neither says.
46
+ */
47
+ function venueSession(exchange: string, marketState: string | undefined, now: number): VenueSession | null {
48
+ if (isAlwaysOpenExchange(exchange)) return { state: "always" };
49
+ const next = nextRegularSessionOpen(exchange, now);
50
+ if (next) {
51
+ const { timeZone } = next;
52
+ const latestClose = latestRegularSessionClose(exchange, now);
53
+ const last = latestClose ? { date: latestClose.date, open: latestRegularSessionOpen(exchange, latestClose.close) } : undefined;
54
+ if (isRegularSessionTime(exchange, now)) return { state: "open", timeZone };
55
+ if (isUsListingExchange(exchange)) {
56
+ const today = zonedDateKey(now, timeZone);
57
+ const session = activeUsMarketSession(now);
58
+ if (session === "PRE" && next.date === today) return { state: "pre-market", next, last, timeZone };
59
+ if (session === "POST" && last?.date === today) return { state: "after-hours", next, last, timeZone };
60
+ }
61
+ return { state: "closed", next, last, timeZone };
62
+ }
63
+ if (marketState === "REGULAR") return { state: "open" };
64
+ if (marketState === "PRE") return { state: "pre-market" };
65
+ if (marketState === "POST") return { state: "after-hours" };
66
+ if (marketState && CLOSED_STATES.has(marketState)) return { state: "closed" };
67
+ return null;
68
+ }
69
+
70
+ /** The venue spot FX pairs trade on. It has no session calendar here; its week is in `fx-market-hours`. */
71
+ const FX_VENUE = "CCY";
72
+ const DAY_MS = 24 * 60 * 60_000;
73
+
74
+ /**
75
+ * The newest observation is its venue's latest close: the venue has not
76
+ * opened since, and the print is from that session's day, after its open (an
77
+ * after-hours print of that day counts). Returns that session's local date.
78
+ */
79
+ function closeDateOf(observation: SessionObservation, session: VenueSession, now: number): string | null {
80
+ const time = observation.time;
81
+ if (!time || time.dateOnly) return null;
82
+ // FX closes once a week, Friday 17:00 New York time, and its trading day turns
83
+ // at 17:00: a print from that last day, or a quiet tick over the weekend, is
84
+ // that Friday's close, not the day it happens to be stamped with.
85
+ const weekClose = canonicalExchange(observation.exchange) === FX_VENUE ? currentFxClosureStart(now) : null;
86
+ if (weekClose != null) return time.time >= weekClose - DAY_MS ? zonedDateKey(weekClose - 1, "America/New_York") : null;
87
+ if (session.last && session.timeZone) {
88
+ if (session.last.open != null && time.time < session.last.open) return null;
89
+ return zonedDateKey(time.time, session.timeZone) === session.last.date ? session.last.date : null;
90
+ }
91
+ // No calendar: the source says the session is over, and the print is dated in UTC.
92
+ return session.state === "closed" && !session.last ? new Date(time.time).toISOString().slice(0, 10) : null;
93
+ }
94
+
95
+ /**
96
+ * Where the markets behind a quote report stand now, and whether the newest
97
+ * observation is a close. Null market when the venues disagree (Tokyo open,
98
+ * New York closed), trade around the clock, or none can be placed; a report
99
+ * then says nothing rather than guess.
100
+ */
101
+ export function reportMarketSession(
102
+ observations: readonly SessionObservation[],
103
+ now: number,
104
+ ): { market: ReportMarket | null; closeDate: string | null } {
105
+ const sessions = new Map<string, VenueSession>();
106
+ let newest: { observation: SessionObservation; session: VenueSession } | null = null;
107
+ for (const observation of observations) {
108
+ const exchange = canonicalExchange(observation.exchange);
109
+ if (!exchange) continue;
110
+ const key = `${exchange}:${observation.marketState ?? ""}`;
111
+ const session = sessions.get(key) ?? venueSession(exchange, observation.marketState, now);
112
+ if (!session) continue;
113
+ sessions.set(key, session);
114
+ if (observation.time && (!newest || observation.time.time > (newest.observation.time?.time ?? -Infinity))) {
115
+ newest = { observation, session };
116
+ }
117
+ }
118
+ const states = new Set([...sessions.values()].map((session) => session.state));
119
+ if (states.size !== 1 || states.has("always")) return { market: null, closeDate: null };
120
+ const state = [...states][0] as ReportMarketState;
121
+ const reopen = state === "closed" || state === "pre-market"
122
+ ? [...sessions.values()].reduce<{ date: string; open: number } | null>((first, session) => (
123
+ session.next && (!first || session.next.open < first.open) ? session.next : first
124
+ ), null)
125
+ : null;
126
+ return {
127
+ market: {
128
+ state,
129
+ ...(reopen ? { reopensAt: new Date(reopen.open).toISOString(), reopensOn: reopen.date } : {}),
130
+ },
131
+ closeDate: (state === "closed" || state === "pre-market") && newest ? closeDateOf(newest.observation, newest.session, now) : null,
132
+ };
133
+ }
@@ -32,6 +32,8 @@ export interface ParsedPaneFunctionArgs {
32
32
  scale: number;
33
33
  /** Small label drawn in the pane title bar, e.g. a domain; null draws nothing. */
34
34
  watermark: string | null;
35
+ /** A screenshot carries its dated status line in the pane footer; `--no-status` sets this false. */
36
+ status?: boolean;
35
37
  requireBotSafe: boolean;
36
38
  /** The listing a one-ticker function's argument named, once `applyListingArgument` has resolved it. */
37
39
  listing?: ListingArg;
@@ -67,7 +69,7 @@ export function parseArgumentsOption(value: string): Record<string, string> {
67
69
  return pairs;
68
70
  }
69
71
 
70
- export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: number } = {}): ParsedPaneFunctionArgs {
72
+ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: number; tail?: number } = {}): ParsedPaneFunctionArgs {
71
73
  const positionals: string[] = [];
72
74
  const options: Record<string, string | true> = {};
73
75
  let outputPath: string | null = null;
@@ -76,6 +78,7 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
76
78
  let theme: string | null = null;
77
79
  let scale = 1;
78
80
  let watermark: string | null = null;
81
+ let status = true;
79
82
  let requireBotSafe = false;
80
83
 
81
84
  for (let index = 0; index < args.length; index += 1) {
@@ -94,6 +97,11 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
94
97
  const equalsIndex = raw.indexOf("=");
95
98
  const key = equalsIndex >= 0 ? raw.slice(0, equalsIndex) : raw;
96
99
  const inlineValue = equalsIndex >= 0 ? raw.slice(equalsIndex + 1) : undefined;
100
+ // A switch: the token after it is the function's argument, never its value.
101
+ if (normalizeOptionKey(key) === "noStatus" && inlineValue === undefined) {
102
+ status = false;
103
+ continue;
104
+ }
97
105
  const next = args[index + 1];
98
106
  const nextValue = inlineValue === undefined && next && !next.startsWith("--") ? args[++index] : true;
99
107
  const value: string | true = inlineValue ?? nextValue ?? true;
@@ -118,6 +126,7 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
118
126
  scale = parseScale(value);
119
127
  } else if (normalizedKey === "watermark" && value !== true) {
120
128
  watermark = value.trim() || null;
129
+
121
130
  } else if (normalizedKey === "arguments" && value !== true) {
122
131
  Object.assign(options, parseArgumentsOption(value));
123
132
  } else {
@@ -127,10 +136,12 @@ export function parsePaneFunctionArgs(args: string[], globalOptions: { limit?: n
127
136
 
128
137
  const target = positionals[0]?.trim() ?? "";
129
138
  const arg = positionals.slice(1).join(" ").trim();
130
- // The outer CLI parser consumes --limit before fn/shot sees its arguments.
131
- // Restore it for the pane's own schema and loader, including bounds checks.
139
+ // The outer CLI parser consumes --limit and --tail before fn/shot sees its arguments.
140
+ // Restore them for the pane's own schema and loader, including bounds checks,
141
+ // so a function without a tail option says so instead of ignoring it.
132
142
  if (globalOptions.limit != null) options.limit = String(globalOptions.limit);
133
- return { target, arg, options, outputPath, width, height, theme, scale, watermark, requireBotSafe };
143
+ if (globalOptions.tail != null) options.tail = String(globalOptions.tail);
144
+ return { target, arg, options, outputPath, width, height, theme, scale, watermark, status, requireBotSafe };
134
145
  }
135
146
 
136
147
  export function parsePaneCatalogArgs(args: string[]): ParsedPaneCatalogArgs {
@@ -0,0 +1,63 @@
1
+ import type { RemoteUiNodeSnapshot } from "../../remote/types";
2
+ import type { HeadlessFreshnessStatus, HeadlessPaneFreshness } from "../../types/headless";
3
+ import { isFiniteNumber, isRecord } from "../../utils/guards";
4
+
5
+ export interface RenderedReportNotices {
6
+ /** Lines a report prints under its title, such as "21 of 145 strikes". */
7
+ notices: string[];
8
+ /** The same facts by key, for `metadata` in JSON: `strikes: { shown, total, window }`. */
9
+ facts: Record<string, unknown>;
10
+ }
11
+
12
+ /**
13
+ * What a rendered pane says about the rows it lists, from the `report-notice`
14
+ * nodes it publishes: `{ text, key?, value? }`. The pane knows what its view
15
+ * leaves out (the option chain's strike window); the report repeats it.
16
+ */
17
+ export function renderedReportNotices(semanticUi: readonly RemoteUiNodeSnapshot[]): RenderedReportNotices {
18
+ const notices: string[] = [];
19
+ const facts: Record<string, unknown> = {};
20
+ for (const node of semanticUi) {
21
+ if (node.role !== "report-notice" || !node.metadata) continue;
22
+ const { text, key, value } = node.metadata as { text?: unknown; key?: unknown; value?: unknown };
23
+ if (typeof text === "string" && text.trim() && !notices.includes(text.trim())) notices.push(text.trim());
24
+ if (typeof key === "string" && key && value !== undefined) facts[key] = value;
25
+ }
26
+ return { notices, facts };
27
+ }
28
+
29
+ const FRESHNESS_STATUSES: readonly HeadlessFreshnessStatus[] = ["live", "delayed", "stale", "not-a-feed"];
30
+
31
+ /**
32
+ * How current a rendered pane says its data is, from the `report-freshness`
33
+ * node it publishes: `{ asOf?, status?, delayMinutes? }`. A capture keeps no
34
+ * footer status and few cells carry a time, so a pane that knows its feed
35
+ * (OMON's chain) dates the report here, as the plain command over the same
36
+ * data does. A pane whose figures come from several dated quotes (FXC's pair
37
+ * rates) publishes them instead, see `renderedReportObservations`.
38
+ */
39
+ export function renderedReportFreshness(
40
+ semanticUi: readonly RemoteUiNodeSnapshot[],
41
+ ): Pick<HeadlessPaneFreshness, "asOf" | "status" | "delayMinutes"> {
42
+ const metadata = semanticUi.find((node) => node.role === "report-freshness")?.metadata;
43
+ if (!metadata) return {};
44
+ const { asOf, status, delayMinutes } = metadata as { asOf?: unknown; status?: unknown; delayMinutes?: unknown };
45
+ return {
46
+ ...(typeof asOf === "string" || isFiniteNumber(asOf) ? { asOf } : {}),
47
+ ...(FRESHNESS_STATUSES.includes(status as HeadlessFreshnessStatus) ? { status: status as HeadlessFreshnessStatus } : {}),
48
+ ...(isFiniteNumber(delayMinutes) && delayMinutes > 0 ? { delayMinutes } : {}),
49
+ };
50
+ }
51
+
52
+ /**
53
+ * The dated observations a rendered pane's figures come from, from the
54
+ * `observations` its `report-freshness` node publishes: one record per quote
55
+ * or rate, in the fields of a headless report's rows (`quoteTime`,
56
+ * `dataSource`, `delayMinutes`, `stale`, `sessionExchange`, `marketState`).
57
+ * The report reads them as it reads those rows, so it says what the newest is,
58
+ * how far the feed is held back, which are stale and where the market stands.
59
+ */
60
+ export function renderedReportObservations(semanticUi: readonly RemoteUiNodeSnapshot[]): Record<string, unknown>[] {
61
+ const observations = semanticUi.find((node) => node.role === "report-freshness")?.metadata?.observations;
62
+ return Array.isArray(observations) ? observations.filter(isRecord) : [];
63
+ }
@@ -2,8 +2,9 @@ import type { MarketContext } from "../types";
2
2
  import type { NormalizedPaneFunctionOptions } from "./capabilities";
3
3
  import type { ResolvedPaneFunction } from "./resolver";
4
4
  import { buildHeadlessFunctionReport } from "./headless";
5
- import { appendDomReportFooter, buildDomFunctionReport } from "./dom";
5
+ import { appendDomReportFooter, buildDomFunctionReport, domReportNote } from "./dom";
6
6
  import type { ReportFreshness } from "./freshness";
7
+ import type { CliReportTables } from "../report-tables";
7
8
 
8
9
  export type PaneFunctionReportSource = "headless" | "dom";
9
10
 
@@ -29,6 +30,8 @@ interface PaneFunctionReportData {
29
30
  export interface PaneFunctionReport {
30
31
  data: PaneFunctionReportData;
31
32
  text: string;
33
+ /** What `--csv` and `--ndjson` write: the text view's tables as flat rows, and the closing `#` lines. */
34
+ tables: CliReportTables;
32
35
  }
33
36
 
34
37
  export function resolvePaneFunctionReportSource(
@@ -46,6 +49,10 @@ export async function buildFunctionReport(
46
49
  const startedAt = performance.now();
47
50
  const source = resolvePaneFunctionReportSource(resolved);
48
51
  if (!source) {
52
+ // HELP is what a newcomer tries first: send them to the command guide and the function list.
53
+ if (resolved.pane.id === "help") {
54
+ throw new Error("HELP is an in-app pane, so it has no report. For the commands run `gloomberb help`; for the functions run `gloomberb catalog`.");
55
+ }
49
56
  throw new Error(`${resolved.token} is an interactive pane and does not expose a data report.`);
50
57
  }
51
58
 
@@ -61,6 +68,10 @@ export async function buildFunctionReport(
61
68
  report.data.truncated === true,
62
69
  report.data.freshness,
63
70
  );
71
+ report.tables = {
72
+ ...report.tables,
73
+ footer: [...report.tables.footer, `note: ${domReportNote(elapsedMs, report.data.truncated === true)}`],
74
+ };
64
75
  }
65
76
  return report;
66
77
  }