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,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
+ }
@@ -49,6 +49,10 @@ export async function buildFunctionReport(
49
49
  const startedAt = performance.now();
50
50
  const source = resolvePaneFunctionReportSource(resolved);
51
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
+ }
52
56
  throw new Error(`${resolved.token} is an interactive pane and does not expose a data report.`);
53
57
  }
54
58
 
@@ -180,12 +180,10 @@ export async function resolvePaneFunction(
180
180
  const normalizedOptions = normalizeCapabilityOptions(capability, args.options, {
181
181
  strict: args.requireBotSafe || (!!headless && resolutionSettings.strictHeadlessOptions === true),
182
182
  });
183
- const settings = capability.botSafe
184
- ? {
185
- ...optionSettings(args.options),
186
- ...capabilityPaneSettings(capability, normalizedOptions),
187
- }
188
- : optionSettings(args.options);
183
+ const settings = {
184
+ ...optionSettings(args.options),
185
+ ...capabilityPaneSettings(capability, normalizedOptions),
186
+ };
189
187
  if (args.requireBotSafe) {
190
188
  validateTickerCardinality(capability, createOptions);
191
189
  }
@@ -18,6 +18,7 @@ import { scenarioScreenshotEvidence } from "../../plugins/builtin/options-scenar
18
18
  import { realizedVolScreenshotEvidence } from "../../plugins/builtin/realized-vol/evidence";
19
19
  import { volSurfaceScreenshotEvidence } from "../../plugins/builtin/vol-surface/evidence";
20
20
  import { volatilityScreenshotEvidence } from "../../plugins/builtin/volatility/evidence";
21
+ import { marketHeatmapScreenshotEvidence } from "../../plugins/builtin/market-heatmap/evidence";
21
22
  import { worldMapScreenshotEvidence } from "../../plugins/builtin/world-venue-map/evidence";
22
23
 
23
24
  /** The envelope every pane's rendered chart-data evidence carries. */
@@ -95,6 +96,7 @@ const PANE_SCREENSHOT_EVIDENCE: readonly PaneScreenshotEvidenceHook[] = [
95
96
  volSurfaceScreenshotEvidence,
96
97
  volatilityScreenshotEvidence,
97
98
  worldMapScreenshotEvidence,
99
+ marketHeatmapScreenshotEvidence,
98
100
  ];
99
101
 
100
102
  export function paneScreenshotEvidenceHook(resolved: ResolvedPaneFunction): PaneScreenshotEvidenceHook | null {
@@ -1,4 +1,5 @@
1
1
  import { fetchTape } from "../../plugins/builtin/time-sales/client";
2
+ import { isOptionsUnavailableError } from "../../market-data/options-alternatives";
2
3
  import type { TapeCapture } from "../../plugins/builtin/time-sales/snapshot-client";
3
4
  import { apiClient } from "../../api-client";
4
5
  import { snapshotInstrumentKey, type SnapshotMarketData } from "../../market-data/snapshot-provider";
@@ -9,7 +10,7 @@ import { dirname, resolve } from "path";
9
10
  import { mkdir } from "fs/promises";
10
11
  import type { PaneRuntimeState } from "../../core/state/app/state";
11
12
  import { CHART_COMPOSER_PANE_ID, type AppConfig } from "../../types/config";
12
- import type { OptionsChain, PricePoint, TickerFinancials } from "../../types/financials";
13
+ import type { OptionsChain, PricePoint, Quote, TickerFinancials } from "../../types/financials";
13
14
  import type { TickerRecord } from "../../types/ticker";
14
15
  import { slugifyName } from "../../utils/slugify";
15
16
  import {
@@ -18,7 +19,7 @@ import {
18
19
  type HttpProxyRequestEnvelope,
19
20
  type HttpProxyResponseEnvelope,
20
21
  } from "../../utils/http-proxy-response";
21
- import { getTheme, getThemeIds } from "../../theme/themes";
22
+ import { requireThemeId } from "../commands/themes";
22
23
  import { isRecord } from "../../utils/guards";
23
24
 
24
25
  const DEFAULT_SHOT_DEVICE_SCALE_FACTOR = 2;
@@ -86,6 +87,7 @@ import {
86
87
  clipPriceHistoryToRange,
87
88
  createFallbackTicker,
88
89
  fetchTickerFinancials,
90
+ loadBrokerMarkedHolding,
89
91
  isFinancialAnalysisFunction,
90
92
  readsDailyReturns,
91
93
  withShotDailyReturns,
@@ -93,6 +95,9 @@ import {
93
95
  withShotPriceHistory,
94
96
  withShotSeasonalityHistory,
95
97
  } from "./data";
98
+ import { renderedReportFreshness, renderedReportNotices, renderedReportObservations } from "./report-notices";
99
+ import { deriveRenderedFreshness, statusLineVariants, type ReportFreshness } from "./freshness";
100
+ import { quotesFreshness } from "../freshness";
96
101
  import {
97
102
  financialRatioRenderMismatches,
98
103
  financialRatioShotEvidence,
@@ -426,6 +431,10 @@ export interface PaneScreenshotResult {
426
431
  usable: boolean;
427
432
  unusableReason: string | null;
428
433
  dataEvidence: PaneScreenshotDataEvidence | null;
434
+ /** What the pane says its view leaves out, such as OMON's "21 of 145 strikes". */
435
+ notices?: string[];
436
+ /** How current the captured data is, as the capture's status line says it; absent with `--no-status`. */
437
+ freshness?: ReportFreshness;
429
438
  outputPath: string;
430
439
  render: DesktopPaneShotRenderResult & {
431
440
  expectedText: string[];
@@ -493,7 +502,7 @@ export async function buildDesktopShotPayload(
493
502
  };
494
503
  const config = stripDesktopShotCredentials<AppConfig>({
495
504
  ...context.config,
496
- ...(theme ? { theme: resolveShotTheme(theme) } : {}),
505
+ ...(theme ? { theme: requireThemeId(theme) } : {}),
497
506
  layout,
498
507
  layouts: [{
499
508
  name: "CLI Shot",
@@ -525,7 +534,7 @@ export async function buildDesktopShotPayload(
525
534
  shotInstance = { ...shotInstance, settings: { ...shotInstance.settings, ...prepared.settings } };
526
535
  for (const [symbol, data] of prepared.financials ?? []) {
527
536
  financials.push([symbol, data]);
528
- tickers.push(createFallbackTicker(symbol, data, context));
537
+ tickers.push(await createFallbackTicker(symbol, data, context));
529
538
  }
530
539
  } else if (resolved.pane.id === "analytics") {
531
540
  const loaded = await loadResolvedHeadlessPaneModel(resolved, context, rawArg);
@@ -551,13 +560,23 @@ export async function buildDesktopShotPayload(
551
560
  if (!target.instrument) financials.push([label, data]);
552
561
  const { symbol } = parsePublicTickerKey(key);
553
562
  const ticker = await context.store.loadTicker(key) ?? await context.store.loadTicker(symbol);
554
- tickers.push(ticker ?? createFallbackTicker(key, data, context));
563
+ tickers.push(ticker ?? await createFallbackTicker(key, data, context));
555
564
  }
556
565
  intradayHistories.push(...chartModel.snapshot.intradayHistories.map((history) => ({
557
566
  ...history, start: history.start?.toISOString() ?? null, end: history.end?.toISOString() ?? null,
558
567
  })));
559
568
  } else for (const symbol of await collectShotSymbolsWithCollections(resolved, context, rawArg)) {
560
- const entry = await fetchTickerFinancials(context, symbol);
569
+ let entry: Awaited<ReturnType<typeof fetchTickerFinancials>>;
570
+ try {
571
+ entry = await fetchTickerFinancials(context, symbol);
572
+ } catch (error) {
573
+ // The pane values a holding no quote covers, such as an option contract,
574
+ // at the broker's mark. It has no financials to capture.
575
+ const holding = COLLECTION_PANE_IDS.has(resolved.pane.id) ? await loadBrokerMarkedHolding(context, symbol) : null;
576
+ if (!holding) throw error;
577
+ tickers.push(holding);
578
+ continue;
579
+ }
561
580
  const requestedRange = shotPriceHistoryRange(resolved);
562
581
  let data = entry.financials;
563
582
  const exchange = entry.instrument.exchange
@@ -565,7 +584,9 @@ export async function buildDesktopShotPayload(
565
584
  ?? data.quote?.listingExchangeName
566
585
  ?? data.quote?.exchangeName
567
586
  ?? "";
568
- const fiveYearsDaily = resolved.pane.id === "realized-vol" || resolved.pane.id === "iv-history" || resolved.pane.id === "macro-day";
587
+ const fiveYearsDaily = resolved.pane.id === "realized-vol" || resolved.pane.id === "iv-history" || resolved.pane.id === "macro-day"
588
+ // KELLY's History tab reads monthly returns off five years of daily closes.
589
+ || resolved.pane.id === "kelly-sizer";
569
590
  if (fiveYearsDaily || resolved.pane.id === "iv-screen" || resolved.pane.id === "short-watch"
570
591
  || resolved.pane.id === "backtest" || resolved.pane.id === OPTIONS_PANE_ID) {
571
592
  // Generic 5Y snapshots can contain weekly bars. The snapshot provider
@@ -600,15 +621,19 @@ export async function buildDesktopShotPayload(
600
621
  // letting the pane fetch them.
601
622
  data = await withShotPriceHistory(context, symbol, entry.tickerFile, data);
602
623
  }
603
- tickers.push(entry.tickerFile ?? createFallbackTicker(symbol, data, context));
624
+ tickers.push(entry.tickerFile ?? await createFallbackTicker(symbol, data, context));
604
625
  financials.push([symbol, data]);
605
626
  if (resolved.pane.id === "time-sales") {
606
627
  tapeSnapshots.push([entry.instrument.symbol, exchange, await fetchTape(entry.instrument.symbol, exchange)]);
607
628
  }
608
629
  if (includeOptionsChains && context.dataProvider.getOptionsChain) {
630
+ // A future with no chain of its own is the pane's to explain (the listed alternative), not a failed shot.
609
631
  const chain = await context.dataProvider.getOptionsChain(entry.instrument.symbol, exchange, undefined,
610
- context.refresh ? { cacheMode: "refresh" } : undefined);
611
- optionsChains.push([symbol, chain]);
632
+ context.refresh ? { cacheMode: "refresh" } : undefined).catch((error: unknown) => {
633
+ if (isOptionsUnavailableError(error)) return null;
634
+ throw error;
635
+ });
636
+ if (chain) optionsChains.push([symbol, chain]);
612
637
  }
613
638
  }
614
639
  layout.instances[0] = shotInstance;
@@ -666,17 +691,6 @@ async function collectShotSymbolsWithCollections(
666
691
  return [...new Set([...symbols, ...members])];
667
692
  }
668
693
 
669
- function resolveShotTheme(requested: string): string {
670
- const normalized = requested.trim().toLowerCase().replace(/[\s_]+/g, "-");
671
- const ids = getThemeIds();
672
- const match = ids.find((id) => id.toLowerCase() === normalized)
673
- ?? ids.find((id) => getTheme(id).name.toLowerCase().replace(/[\s_]+/g, "-") === normalized);
674
- if (!match) {
675
- throw new Error(`Unknown theme "${requested}". Available themes: ${ids.join(", ")}`);
676
- }
677
- return match;
678
- }
679
-
680
694
  function shotPriceHistoryRange(resolved: ResolvedPaneFunction): TimeRange | null {
681
695
  // Captured history is authoritative, and the generic 5Y capture is weekly.
682
696
  if (resolved.pane.id === "quote-monitor") return getQuoteMonitorPaneSettings(resolved.instance.settings).chartPeriod;
@@ -691,6 +705,60 @@ function shotPriceHistoryRange(resolved: ResolvedPaneFunction): TimeRange | null
691
705
  }
692
706
  }
693
707
 
708
+ /** The bridge, keeping every quote it serves the page: what a pane that loads its own quotes (WEI) shows. */
709
+ function recordBridgedQuotes(bridge: DesktopPaneShotBridge): { bridge: DesktopPaneShotBridge; quotes: Quote[] } {
710
+ const quotes: Quote[] = [];
711
+ const keep = (value: unknown) => {
712
+ if (isRecord(value) && typeof value.symbol === "string" && typeof value.price === "number") quotes.push(value as unknown as Quote);
713
+ };
714
+ return {
715
+ quotes,
716
+ bridge: {
717
+ ...bridge,
718
+ async marketData(operation, args) {
719
+ const result = await bridge.marketData(operation, args);
720
+ if (operation === "getQuote") keep(result);
721
+ if (operation === "getQuotesBatch" && Array.isArray(result)) {
722
+ for (const entry of result) keep(isRecord(entry) ? entry.quote : null);
723
+ }
724
+ return result;
725
+ },
726
+ },
727
+ };
728
+ }
729
+
730
+ /**
731
+ * How current a capture's data is. A pane that says what its data is (its
732
+ * declared status, or a feed it publishes, such as OMON's chain or the news
733
+ * wire's delay) is taken at its word; otherwise the quotes it shows date it,
734
+ * with their delay and where their markets stand; otherwise what the
735
+ * rendered view carries, which may be nothing.
736
+ */
737
+ function shotFreshness(
738
+ resolved: Pick<ResolvedPaneFunction, "pane">,
739
+ payload: Pick<DesktopPaneShotPayload, "financials" | "instrumentFinancials">,
740
+ render: Pick<DesktopPaneShotRenderResult, "semanticUi" | "footerText" | "rows">,
741
+ bridgedQuotes: readonly Quote[],
742
+ now = Date.now(),
743
+ ): ReportFreshness {
744
+ const declared = { ...resolved.pane.reportFreshness, ...renderedReportFreshness(render.semanticUi ?? []) };
745
+ const quotes = [
746
+ ...payload.financials.map(([, financials]) => financials.quote),
747
+ ...(payload.instrumentFinancials ?? []).map(({ financials }) => financials.quote),
748
+ ...bridgedQuotes,
749
+ ];
750
+ // A pane that publishes the observations behind its figures (FXC's rates) dates itself.
751
+ const observed = renderedReportObservations(render.semanticUi ?? []);
752
+ const quoted = declared.status || observed.length > 0
753
+ ? undefined
754
+ : quotesFreshness(quotes, declared.source ? { source: declared.source } : undefined, now);
755
+ return quoted ?? deriveRenderedFreshness(declared, {
756
+ footerText: render.footerText ?? "",
757
+ cellTimes: render.rows.flatMap((row) => row.cells.flatMap((cell) => (cell.instant ? [cell.instant] : []))),
758
+ observed,
759
+ }, now);
760
+ }
761
+
694
762
  export async function renderDesktopShot({
695
763
  resolved,
696
764
  context,
@@ -701,6 +769,7 @@ export async function renderDesktopShot({
701
769
  theme,
702
770
  scale,
703
771
  watermark,
772
+ statusLine = false,
704
773
  options,
705
774
  captureImage = true,
706
775
  }: {
@@ -713,6 +782,8 @@ export async function renderDesktopShot({
713
782
  theme?: string | null;
714
783
  scale?: number;
715
784
  watermark?: string | null;
785
+ /** Draw the dated status line in the capture's footer. Reports and `--no-status` leave it out. */
786
+ statusLine?: boolean;
716
787
  options: Record<string, string | true>;
717
788
  captureImage?: boolean;
718
789
  }): Promise<PaneScreenshotResult> {
@@ -721,6 +792,7 @@ export async function renderDesktopShot({
721
792
  const previousSessionToken = apiClient.getSessionToken();
722
793
  let payload: DesktopPaneShotPayload;
723
794
  let render: DesktopPaneShotRenderResult;
795
+ let freshness: ReportFreshness | undefined;
724
796
  apiClient.setSessionToken(apiProxy.sessionToken);
725
797
  try {
726
798
  payload = await buildDesktopShotPayload(
@@ -734,16 +806,24 @@ export async function renderDesktopShot({
734
806
  scale ?? 1,
735
807
  watermark ?? null,
736
808
  );
809
+ if (statusLine && captureImage) payload.statusLine = true;
810
+ const recorded = recordBridgedQuotes(createDesktopShotBridge(context));
811
+ const shotPayload = payload;
737
812
  render = await renderDesktopPaneScreenshot(payload, outputPath, apiProxy, {
738
813
  captureImage,
739
- bridge: createDesktopShotBridge(context),
814
+ bridge: recorded.bridge,
815
+ statusLine: (rendered) => {
816
+ freshness = shotFreshness(resolved, shotPayload, rendered, recorded.quotes);
817
+ return statusLineVariants(freshness);
818
+ },
740
819
  });
741
820
  } finally {
742
821
  apiClient.setSessionToken(previousSessionToken);
743
822
  }
744
823
  const renderedInstance = payload.config.layout.instances.find(({ instanceId }) => instanceId === payload.paneId);
745
824
  if (renderedInstance) resolved = { ...resolved, instance: renderedInstance };
746
- return assessPaneScreenshot(resolved, payload, render, rawArg, outputPath);
825
+ const assessed = assessPaneScreenshot(resolved, payload, render, rawArg, outputPath);
826
+ return freshness ? { ...assessed, freshness } : assessed;
747
827
  }
748
828
 
749
829
  /** What a rendered capture shows, and whether it is fit to use. */
@@ -807,6 +887,7 @@ export function assessPaneScreenshot(
807
887
  const unusableReason = usable
808
888
  ? null
809
889
  : shotUnusableReasonFor(resolved, payload, render, unavailableSymbols, semanticMismatch);
890
+ const { notices } = renderedReportNotices(render.semanticUi ?? []);
810
891
  return {
811
892
  kind: "pane-screenshot",
812
893
  target: resolved.token,
@@ -827,6 +908,7 @@ export function assessPaneScreenshot(
827
908
  usable,
828
909
  unusableReason,
829
910
  dataEvidence,
911
+ ...(notices.length > 0 ? { notices } : {}),
830
912
  outputPath,
831
913
  render: {
832
914
  ...stripDesktopShotCredentials(render),
@@ -149,6 +149,7 @@ export function createCliCommandContext(
149
149
  ownerId: string,
150
150
  registryOrPlugins: Pick<CliCommandRegistry, "plugins" | "externalPlugins"> | GloomPlugin[],
151
151
  cliOptions: CliGlobalOptions = DEFAULT_CLI_OPTIONS,
152
+ args: readonly string[] = [],
152
153
  ): CliCommandContext {
153
154
  const registry = Array.isArray(registryOrPlugins)
154
155
  ? { plugins: registryOrPlugins, externalPlugins: [] }
@@ -169,7 +170,7 @@ export function createCliCommandContext(
169
170
  renderStats,
170
171
  renderTable,
171
172
  },
172
- printResult: (result, options) => printCliResult(result, cliOptions, options),
173
+ printResult: (result, options) => printCliResult(result, cliOptions, options, args),
173
174
  log: debugLog.createLogger(ownerId === "core" ? "cli" : ownerId),
174
175
  };
175
176
  }
package/src/cli/result.ts CHANGED
@@ -7,13 +7,16 @@ import {
7
7
  statValueWidth,
8
8
  visibleLength,
9
9
  wrapText,
10
+ wrapToTerminal,
10
11
  type CliStatEntry,
11
12
  type CliTableColumn,
12
13
  } from "../utils/cli-output";
13
14
  import { serializeCsv } from "../utils/csv";
14
15
  import { formatUtcTime, isEpochMilliseconds, isZonedIsoDateTime } from "../utils/utc-time";
15
16
  import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
17
+ import { withSearchHints } from "./not-a-ticker";
16
18
  import { renderReportCsv, renderReportNdjson, type CliReportTables } from "./report-tables";
19
+ import { windowRows, type RowWindow, type RowWindowOptions } from "./row-window";
17
20
 
18
21
  export interface CliResult<T = unknown> {
19
22
  data: T;
@@ -59,6 +62,17 @@ export interface CliResultRenderOptions<T = unknown, Row = Record<string, unknow
59
62
  summary?: (data: T) => string;
60
63
  /** Text-mode line printed first, naming what the result is about, such as the listing a symbol resolved to. */
61
64
  heading?: string;
65
+ /** Text-mode line printed under the rows, such as where a listing stops. Not printed with no rows: say it in `empty`. */
66
+ footnote?: string;
67
+ /**
68
+ * The row key holding each row's date, which makes the rows a dated series:
69
+ * `--tail` keeps the newest rows whichever way the series runs, and a
70
+ * `--limit` that keeps the oldest says so, under the text table and as
71
+ * `metadata.rows` in JSON.
72
+ */
73
+ dateKey?: string;
74
+ /** How many rows to show when neither `--limit` nor `--tail` is given; every row when absent. */
75
+ defaultLimit?: number;
62
76
  /**
63
77
  * What CSV and NDJSON write instead of the rows: a report's tables as the
64
78
  * text view shows them, CSV with its `# section:` and closing `#` lines.
@@ -89,14 +103,28 @@ const KEY_ACRONYMS: Record<string, string> = {
89
103
  usd: "USD",
90
104
  };
91
105
 
92
- function applyLimit<T>(data: T, limit?: number): T {
93
- if (!Array.isArray(data) || limit == null) return data;
94
- return data.slice(0, limit) as T;
106
+ function rowWindowOptions(options: CliGlobalOptions, renderOptions: Pick<CliResultRenderOptions, "defaultLimit">): RowWindowOptions {
107
+ return options.tail != null ? { tail: options.tail } : { limit: options.limit ?? renderOptions.defaultLimit };
108
+ }
109
+
110
+ function windowed<Row>(
111
+ rows: readonly Row[],
112
+ options: CliGlobalOptions,
113
+ renderOptions: Pick<CliResultRenderOptions, "dateKey" | "defaultLimit">,
114
+ ): RowWindow<Row> {
115
+ const { dateKey } = renderOptions;
116
+ const dateOf = dateKey ? (row: Row) => (isPlainObject(row) ? row[dateKey] : undefined) : undefined;
117
+ return windowRows(rows, rowWindowOptions(options, renderOptions), dateOf);
118
+ }
119
+
120
+ function asRows<T, Row>(data: T, rows?: (data: T) => Row[]): Row[] {
121
+ return rows ? rows(data) : (Array.isArray(data) ? data : [data]) as Row[];
95
122
  }
96
123
 
97
- function asRows<T, Row>(data: T, limit?: number, rows?: (data: T) => Row[]): Row[] {
98
- const resolvedRows = rows ? rows(data) : (Array.isArray(data) ? data : [data]) as Row[];
99
- return limit == null ? resolvedRows : resolvedRows.slice(0, limit);
124
+ /** What JSON says about rows the window cut, under `metadata.rows`. */
125
+ function cutMetadata(window: RowWindow<unknown>) {
126
+ if (window.rows.length >= window.total) return null;
127
+ return { shown: window.rows.length, total: window.total, kept: window.kept, ...(window.note ? { note: window.note } : {}) };
100
128
  }
101
129
 
102
130
  function isPlainObject(value: unknown): value is Record<string, unknown> {
@@ -277,6 +305,7 @@ function renderTextTable<Row extends Record<string, unknown>>(
277
305
  width: column.width,
278
306
  maxWidth: column.maxWidth,
279
307
  optional: column.optional,
308
+ dropPriority: column.dropPriority,
280
309
  shrink: column.shrink,
281
310
  })),
282
311
  cells.map((row) => shown.map(({ index }) => row[index]!)),
@@ -310,7 +339,7 @@ function overflowsTerminal<Row extends Record<string, unknown>>(rows: Row[]): bo
310
339
 
311
340
  /** The closing source, as-of and status line of a text result, for output printed without `printResult`. */
312
341
  export function cliFreshnessFooter(freshness: ReportFreshness): string {
313
- return cliStyles.muted(formatFreshnessLine(freshness));
342
+ return cliStyles.muted(wrapToTerminal(formatFreshnessLine(freshness)));
314
343
  }
315
344
 
316
345
  export function serializeCliResult<T, Row extends Record<string, unknown> = Record<string, unknown>>(
@@ -321,11 +350,13 @@ export function serializeCliResult<T, Row extends Record<string, unknown> = Reco
321
350
  const { freshness, ...rest } = result;
322
351
  if (options.format === "json") {
323
352
  const columns = serializeColumns(renderOptions.columns);
353
+ const window = Array.isArray(result.data) ? windowed(result.data as unknown[], options, renderOptions) : null;
354
+ const cut = window ? cutMetadata(window) : null;
324
355
  const envelope: CliResultJsonEnvelope<T> = {
325
356
  ok: true,
326
357
  ...rest,
327
- ...(freshness ? { metadata: { ...rest.metadata, freshness } } : {}),
328
- data: applyLimit(result.data, options.limit),
358
+ ...(freshness || cut ? { metadata: { ...rest.metadata, ...(freshness ? { freshness } : {}), ...(cut ? { rows: cut } : {}) } } : {}),
359
+ data: window ? window.rows as T : result.data,
329
360
  ...(columns?.length ? { columns } : {}),
330
361
  };
331
362
  return JSON.stringify(envelope, null, 2);
@@ -342,7 +373,8 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
342
373
  ): string {
343
374
  if (renderOptions.tables && options.format === "csv") return renderReportCsv(renderOptions.tables);
344
375
  if (renderOptions.tables && options.format === "ndjson") return renderReportNdjson(renderOptions.tables);
345
- const rows = asRows(result.data, options.limit, renderOptions.rows);
376
+ const window = windowed(asRows(result.data, renderOptions.rows), options, renderOptions);
377
+ const rows = window.rows;
346
378
  if (options.format === "ndjson") {
347
379
  return rows.map((row) => JSON.stringify(row)).join("\n");
348
380
  }
@@ -357,7 +389,9 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
357
389
  if (rows.length === 0) {
358
390
  return withHeading(summary || cliStyles.muted(renderOptions.empty ?? "No results."));
359
391
  }
360
- const body = renderTextRows(rows as Row[], result.data, renderOptions);
392
+ const table = renderTextRows(rows as Row[], result.data, renderOptions);
393
+ const notes = [window.note, renderOptions.footnote].filter((note): note is string => !!note);
394
+ const body = [table, ...notes.map((note) => cliStyles.muted(note))].join("\n");
361
395
  return withHeading(summary ? `${summary}\n\n${body}` : body);
362
396
  }
363
397
 
@@ -382,9 +416,12 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
382
416
  result: CliResult<T>,
383
417
  options: CliGlobalOptions,
384
418
  renderOptions: CliResultRenderOptions<T, Row> = {},
419
+ args: readonly string[] = [],
385
420
  ): void {
386
421
  if (options.quiet && options.format === "text") return;
387
- const output = serializeCliResult(result, options, renderOptions);
422
+ // JSON keeps the router's own words; everything else is for a person, so a "Not a ticker" gets its hint.
423
+ const hinted = (text: string) => options.format === "json" ? text : withSearchHints(text, args);
424
+ const output = hinted(serializeCliResult(result, options, renderOptions));
388
425
  // Bun's console writer can truncate a large pipe write after stdout has been
389
426
  // initialized. The stream queues the remaining bytes until the reader drains.
390
427
  if (output) process.stdout.write(`${output}\n`);
@@ -392,7 +429,7 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
392
429
  // rows, so a warning goes to stderr, out of a file the rows are piped into.
393
430
  if (options.format !== "json" && !options.quiet) {
394
431
  for (const warning of result.warnings ?? []) {
395
- console.error(`${cliStyles.warning("warning:")} ${warning}`);
432
+ console.error(`${cliStyles.warning("warning:")} ${hinted(warning)}`);
396
433
  }
397
434
  }
398
435
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Which rows `--limit` and `--tail` keep, for every command that prints a
3
+ * list. `--limit n` keeps the first n rows as printed. `--tail n` keeps the
4
+ * newest n rows of a dated series, whichever way it runs (the first n of a
5
+ * newest-first series, the last n of an oldest-first one), and the last n of
6
+ * any other list. The kept rows stay in their printed order.
7
+ */
8
+ export interface RowWindowOptions {
9
+ limit?: number;
10
+ tail?: number;
11
+ }
12
+
13
+ export interface RowWindow<Row> {
14
+ rows: Row[];
15
+ /** How many rows there were before the cut. */
16
+ total: number;
17
+ /** The end of a dated series the cut kept; null when nothing was cut or the rows are not a dated series. */
18
+ kept: "oldest" | "newest" | null;
19
+ /**
20
+ * Said when `--limit` cut an oldest-first series, which reads as the latest
21
+ * rows but is the earliest: "showing the oldest 2 of 252 rows; --tail 2 for
22
+ * the latest". Null otherwise.
23
+ */
24
+ note: string | null;
25
+ }
26
+
27
+ function timeOf(value: unknown): number | null {
28
+ if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null;
29
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
30
+ if (typeof value !== "string" || !value.trim()) return null;
31
+ const time = Date.parse(value);
32
+ return Number.isFinite(time) ? time : null;
33
+ }
34
+
35
+ function firstTime<Row>(rows: readonly Row[], dateOf: (row: Row) => unknown, fromEnd: boolean): number | null {
36
+ for (let step = 0; step < rows.length; step += 1) {
37
+ const time = timeOf(dateOf(rows[fromEnd ? rows.length - 1 - step : step]!));
38
+ if (time != null) return time;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ /** Which way a dated series runs, from its first and last dated rows; null when it is undated or spans one instant. */
44
+ function seriesOrder<Row>(rows: readonly Row[], dateOf: (row: Row) => unknown): "ascending" | "descending" | null {
45
+ const first = firstTime(rows, dateOf, false);
46
+ const last = firstTime(rows, dateOf, true);
47
+ if (first == null || last == null || first === last) return null;
48
+ return first < last ? "ascending" : "descending";
49
+ }
50
+
51
+ /** The rows `options` keep. `dateOf` reads a row's date, which makes the rows a dated series. */
52
+ export function windowRows<Row>(
53
+ rows: readonly Row[],
54
+ options: RowWindowOptions,
55
+ dateOf?: (row: Row) => unknown,
56
+ ): RowWindow<Row> {
57
+ const total = rows.length;
58
+ const count = options.tail ?? options.limit;
59
+ if (count == null || count >= total) return { rows: [...rows], total, kept: null, note: null };
60
+ const order = dateOf ? seriesOrder(rows, dateOf) : null;
61
+ if (options.tail != null) {
62
+ return {
63
+ rows: order === "descending" ? rows.slice(0, count) : rows.slice(total - count),
64
+ total,
65
+ kept: order ? "newest" : null,
66
+ note: null,
67
+ };
68
+ }
69
+ const kept = order === "ascending" ? "oldest" : order === "descending" ? "newest" : null;
70
+ return {
71
+ rows: rows.slice(0, count),
72
+ total,
73
+ kept,
74
+ note: kept === "oldest" ? `showing the oldest ${count} of ${total} rows; --tail ${count} for the latest` : null,
75
+ };
76
+ }
@@ -0,0 +1,34 @@
1
+ /** A scrolling box as the page measures it. */
2
+ export interface ScrollBoxMetrics {
3
+ scrollTop: number;
4
+ scrollHeight: number;
5
+ clientHeight: number;
6
+ scrollLeft: number;
7
+ scrollWidth: number;
8
+ clientWidth: number;
9
+ /** The box draws a scrollbar on that axis, which says it overflows even when the measures cannot. */
10
+ scrollbarY: boolean;
11
+ scrollbarX: boolean;
12
+ }
13
+
14
+ /**
15
+ * What a capture leaves out of a scrolling box, naming the side: a chain
16
+ * centred on the money is cut above and below, a list at its top only below.
17
+ * Self-contained so the screenshot runner can evaluate it in the browser.
18
+ */
19
+ export function scrollTruncationReasons(box: ScrollBoxMetrics): string[] {
20
+ const reasons: string[] = [];
21
+ const cut = (offset: number, content: number, viewport: number, flagged: boolean, noun: string, sides: readonly [string, string, string, string]) => {
22
+ if (!flagged && !(content > viewport + 1)) return;
23
+ // One pixel either way is subpixel layout, not a hidden row.
24
+ const before = offset > 1;
25
+ const after = offset + viewport < content - 1;
26
+ // A drawn scrollbar over content that measures as fitting (a list that loads more) cannot say which side.
27
+ reasons.push(before || after
28
+ ? `${noun} ${before && after ? sides[2] : before ? sides[0] : sides[1]} the rendered viewport are cut`
29
+ : `${noun} ${sides[3]} the rendered viewport may be cut`);
30
+ };
31
+ cut(box.scrollTop, box.scrollHeight, box.clientHeight, box.scrollbarY, "rows", ["above", "below", "above and below", "above or below"]);
32
+ cut(box.scrollLeft, box.scrollWidth, box.clientWidth, box.scrollbarX, "columns", ["left of", "right of", "left and right of", "left or right of"]);
33
+ return reasons;
34
+ }