gloomberb 0.11.2 → 0.12.0

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 (531) hide show
  1. package/README.md +7 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +108 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +134 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +220 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/capabilities/chart-series.ts +3 -2
  20. package/src/cli/commands/market.ts +2 -2
  21. package/src/cli/commands/plugins.ts +39 -11
  22. package/src/cli/desktop-pane-shot.ts +285 -16
  23. package/src/cli/index.ts +1 -1
  24. package/src/cli/pane-functions/capabilities.ts +61 -31
  25. package/src/cli/pane-functions/catalog.ts +16 -4
  26. package/src/cli/pane-functions/discovery.ts +1 -0
  27. package/src/cli/pane-functions/headless.test.ts +247 -0
  28. package/src/cli/pane-functions/headless.ts +423 -0
  29. package/src/cli/pane-functions/index.test.ts +9 -0
  30. package/src/cli/pane-functions/index.ts +6 -2
  31. package/src/cli/pane-functions/options.ts +27 -2
  32. package/src/cli/pane-functions/report.ts +4 -0
  33. package/src/cli/pane-functions/resolver.ts +26 -8
  34. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  35. package/src/cli/pane-functions/screenshot.ts +236 -20
  36. package/src/cli/registry.ts +0 -22
  37. package/src/cli/restore-plugins.ts +34 -0
  38. package/src/cli/result.ts +7 -10
  39. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  40. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  41. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  42. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  43. package/src/components/chart/composite/composite-chart.tsx +301 -204
  44. package/src/components/chart/composite/format.ts +8 -2
  45. package/src/components/chart/composite/interactions.test.ts +68 -102
  46. package/src/components/chart/composite/interactions.ts +214 -264
  47. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  48. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  49. package/src/components/chart/composite/scene.ts +57 -3
  50. package/src/components/chart/composite/text-renderer.ts +3 -2
  51. package/src/components/chart/composite/time-scale.ts +138 -57
  52. package/src/components/chart/composite/types.ts +25 -1
  53. package/src/components/chart/core/data.ts +9 -13
  54. package/src/components/chart/core/date-window.ts +7 -1
  55. package/src/components/chart/core/pointer.ts +3 -0
  56. package/src/components/chart/core/types.ts +0 -45
  57. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  58. package/src/components/chart/native/kitty/index.test.ts +0 -188
  59. package/src/components/chart/native/raster/crosshair.ts +6 -5
  60. package/src/components/chart/native/raster/primitives.ts +0 -23
  61. package/src/components/chart/native/renderer-selection.ts +81 -31
  62. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  63. package/src/components/chart/static/chart-surface.tsx +150 -0
  64. package/src/components/chart/static/index.ts +8 -0
  65. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  66. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  67. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  68. package/src/components/chart/static/series.ts +49 -0
  69. package/src/components/command-bar/action-runtime.ts +1 -3
  70. package/src/components/command-bar/assist/model.test.ts +17 -2
  71. package/src/components/command-bar/assist/model.ts +40 -9
  72. package/src/components/command-bar/commands/direct/index.ts +1 -1
  73. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  74. package/src/components/command-bar/commands/registry.ts +0 -11
  75. package/src/components/command-bar/helpers.test.ts +2 -2
  76. package/src/components/command-bar/helpers.ts +2 -6
  77. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  78. package/src/components/command-bar/list/badge.test.ts +54 -0
  79. package/src/components/command-bar/list/badge.ts +62 -0
  80. package/src/components/command-bar/list/model.test.ts +103 -0
  81. package/src/components/command-bar/list/model.ts +61 -4
  82. package/src/components/command-bar/list/view.test.tsx +113 -0
  83. package/src/components/command-bar/list/view.tsx +147 -131
  84. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  85. package/src/components/command-bar/panel/body.tsx +51 -65
  86. package/src/components/command-bar/panel/index.tsx +48 -13
  87. package/src/components/command-bar/panel/layout.test.ts +139 -0
  88. package/src/components/command-bar/panel/layout.ts +65 -28
  89. package/src/components/command-bar/panel/native-surface.ts +24 -0
  90. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  91. package/src/components/command-bar/panel/runtime.ts +6 -3
  92. package/src/components/command-bar/panel/state.ts +41 -7
  93. package/src/components/command-bar/panel/types.ts +7 -1
  94. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  95. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  96. package/src/components/command-bar/routes/root/results.ts +30 -28
  97. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  98. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  99. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  100. package/src/components/command-bar/routes/root/selection.ts +1 -12
  101. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  102. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  103. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  104. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  105. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  106. package/src/components/command-bar/routing/actions.ts +1 -81
  107. package/src/components/command-bar/routing/launch-request.ts +1 -1
  108. package/src/components/command-bar/routing/list-state.ts +6 -21
  109. package/src/components/command-bar/selection-runtime.ts +1 -1
  110. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  111. package/src/components/command-bar/surface/environment.ts +8 -1
  112. package/src/components/command-bar/surface/index.test.tsx +84 -68
  113. package/src/components/command-bar/surface/index.tsx +33 -36
  114. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  115. package/src/components/command-bar/theme-picker.tsx +43 -15
  116. package/src/components/command-bar/view-model.test.ts +53 -8
  117. package/src/components/command-bar/view-model.ts +43 -14
  118. package/src/components/command-bar/workflow/tickers.ts +1 -0
  119. package/src/components/command-bar/workflow/types.ts +1 -1
  120. package/src/components/data-table/export.test.ts +21 -0
  121. package/src/components/data-table/export.ts +21 -0
  122. package/src/components/data-table/view.test.tsx +45 -6
  123. package/src/components/data-table/view.tsx +55 -9
  124. package/src/components/index.ts +10 -2
  125. package/src/components/input-search-bar.test.tsx +36 -2
  126. package/src/components/input-search-bar.tsx +18 -1
  127. package/src/components/layout/header.test.tsx +57 -10
  128. package/src/components/layout/header.tsx +292 -149
  129. package/src/components/layout/market-summary.test.ts +81 -0
  130. package/src/components/layout/market-summary.ts +109 -0
  131. package/src/components/layout/shell/chrome.ts +96 -1
  132. package/src/components/layout/shell/index.tsx +11 -3
  133. package/src/components/layout/shell/menu.ts +9 -0
  134. package/src/components/layout/shell/pane/actions.ts +29 -0
  135. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  136. package/src/components/layout/shell/shortcuts.ts +3 -0
  137. package/src/components/layout/status-bar.test.tsx +9 -8
  138. package/src/components/layout/status-bar.tsx +85 -54
  139. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  140. package/src/components/price-sparkline/index.ts +61 -34
  141. package/src/components/price-sparkline/view.tsx +1 -1
  142. package/src/components/sign-in-gate.test.tsx +128 -0
  143. package/src/components/sign-in-gate.tsx +149 -0
  144. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  145. package/src/components/ticker/badge/list.tsx +17 -3
  146. package/src/components/ticker/list-table-view.tsx +6 -3
  147. package/src/components/ui/data-table/index.tsx +22 -4
  148. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  149. package/src/components/ui/frame.tsx +28 -0
  150. package/src/components/ui/index.ts +3 -0
  151. package/src/components/ui/native-select.tsx +20 -10
  152. package/src/components/ui/select-button.tsx +138 -0
  153. package/src/components/ui/status.tsx +2 -2
  154. package/src/components/ui/table-layout.ts +1 -1
  155. package/src/components/use-double-click-activation.ts +7 -3
  156. package/src/core/app-services.ts +3 -3
  157. package/src/data/config/chart-settings.ts +4 -4
  158. package/src/data/config/layout.ts +12 -6
  159. package/src/data/config/store/index.test.ts +97 -0
  160. package/src/data/config/store/migrations.ts +41 -0
  161. package/src/data/config/store/normalize.ts +5 -3
  162. package/src/i18n/es.ts +6 -2
  163. package/src/i18n/ja.ts +6 -2
  164. package/src/i18n/ko.ts +6 -2
  165. package/src/i18n/zh-cn.ts +6 -2
  166. package/src/i18n/zh-tw.ts +6 -2
  167. package/src/layout-marketplace/gallery-terminal.tsx +126 -96
  168. package/src/layout-marketplace/gallery.test.tsx +65 -1
  169. package/src/layout-marketplace/pane-imagery.ts +1 -0
  170. package/src/layout-marketplace/payload.test.ts +17 -0
  171. package/src/layout-marketplace/payload.ts +40 -5
  172. package/src/market-data/coordinator/index.ts +3 -2
  173. package/src/market-data/coordinator/quotes.ts +173 -66
  174. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  175. package/src/pane-settings.ts +1 -1
  176. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  177. package/src/plugins/builtin/analytics/view.tsx +1 -1
  178. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  179. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  180. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  181. package/src/plugins/builtin/chart-composer/cli-options.ts +12 -9
  182. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  183. package/src/plugins/builtin/chart-composer/settings.ts +2 -13
  184. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  185. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  186. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  187. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  188. package/src/plugins/builtin/composite-plugins.ts +16 -1
  189. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  190. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  191. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  192. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  193. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  194. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  195. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  196. package/src/plugins/builtin/earnings/index.tsx +1 -0
  197. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  198. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  199. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  200. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  201. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  202. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  203. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  204. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  205. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  206. package/src/plugins/builtin/econ/index.tsx +3 -1
  207. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  208. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  209. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  210. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  211. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  212. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  213. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  214. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  215. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  216. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  217. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  218. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  219. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  220. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  221. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  222. package/src/plugins/builtin/futures/index.tsx +1 -0
  223. package/src/plugins/builtin/fx-matrix/index.tsx +1 -0
  224. package/src/plugins/builtin/help/index.tsx +4 -0
  225. package/src/plugins/builtin/holders/index.tsx +1 -0
  226. package/src/plugins/builtin/insider/index.tsx +1 -0
  227. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -0
  228. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  229. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  230. package/src/plugins/builtin/market-halts/index.tsx +1 -0
  231. package/src/plugins/builtin/market-movers/index.tsx +1 -0
  232. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  233. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  234. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  235. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  236. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  237. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  238. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  239. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  240. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  241. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  242. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  243. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  244. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  245. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  246. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  247. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  248. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  249. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  250. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  251. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  252. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  253. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  254. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  255. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  256. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  257. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  258. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  259. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  260. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  261. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  262. package/src/plugins/builtin/options/analytics.test.ts +83 -0
  263. package/src/plugins/builtin/options/analytics.ts +110 -0
  264. package/src/plugins/builtin/options/index.tsx +18 -2
  265. package/src/plugins/builtin/options/table.ts +121 -51
  266. package/src/plugins/builtin/options/types.ts +22 -15
  267. package/src/plugins/builtin/options/view.test.tsx +53 -1
  268. package/src/plugins/builtin/options/view.tsx +92 -41
  269. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  270. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  271. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  272. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  273. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  274. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  275. package/src/plugins/builtin/plugin-module.test.ts +2 -4
  276. package/src/plugins/builtin/plugin-module.ts +9 -8
  277. package/src/plugins/builtin/portfolio-list/index.tsx +1 -0
  278. package/src/plugins/builtin/research/index.tsx +4 -0
  279. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  280. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  281. package/src/plugins/builtin/research-search/data.ts +109 -0
  282. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  283. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  284. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  285. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  286. package/src/plugins/builtin/research-search/index.tsx +85 -0
  287. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  288. package/src/plugins/builtin/research-search/model.ts +291 -0
  289. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  290. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  291. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  292. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  293. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  294. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  295. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  296. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  297. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  298. package/src/plugins/builtin/scanner/index.ts +2 -0
  299. package/src/plugins/builtin/sec/index.tsx +1 -0
  300. package/src/plugins/builtin/sectors/index.tsx +1 -0
  301. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  302. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  303. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  304. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  305. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +3 -5
  306. package/src/plugins/builtin/ticker-detail/index.tsx +2 -14
  307. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  308. package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
  309. package/src/plugins/builtin/volatility/index.tsx +1 -1
  310. package/src/plugins/builtin/world-indices/index.tsx +1 -0
  311. package/src/plugins/builtin/world-venue-map/index.ts +27 -0
  312. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  313. package/src/plugins/builtin/world-venue-map/map.tsx +481 -0
  314. package/src/plugins/builtin/world-venue-map/model.test.ts +85 -0
  315. package/src/plugins/builtin/world-venue-map/model.ts +268 -0
  316. package/src/plugins/builtin/world-venue-map/pane.tsx +300 -0
  317. package/src/plugins/builtin/world-venue-map/world-outlines.ts +2 -0
  318. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  319. package/src/plugins/bundle.test.ts +109 -0
  320. package/src/plugins/bundle.ts +139 -0
  321. package/src/plugins/catalog-backend.ts +6 -44
  322. package/src/plugins/catalog-browser.test.ts +1 -0
  323. package/src/plugins/catalog-browser.ts +12 -0
  324. package/src/plugins/catalog-manifest.test.ts +43 -0
  325. package/src/plugins/catalog-manifest.ts +92 -0
  326. package/src/plugins/catalog-ui.ts +20 -4
  327. package/src/plugins/catalog.ts +9 -2
  328. package/src/plugins/current-target.ts +28 -0
  329. package/src/plugins/host-contract.ts +37 -0
  330. package/src/plugins/host-link.ts +158 -0
  331. package/src/plugins/host-modules.ts +61 -0
  332. package/src/plugins/loader.ts +41 -22
  333. package/src/plugins/ownership.ts +1 -0
  334. package/src/plugins/prediction-markets/index.tsx +1 -0
  335. package/src/plugins/registry/context.ts +3 -0
  336. package/src/plugins/registry/contributions.ts +27 -0
  337. package/src/plugins/registry/index.test.ts +25 -0
  338. package/src/plugins/registry/index.ts +8 -0
  339. package/src/plugins/registry/pane-settings.test.ts +53 -0
  340. package/src/plugins/registry/pane-settings.ts +29 -3
  341. package/src/plugins/seed.test.ts +83 -0
  342. package/src/plugins/seed.ts +80 -0
  343. package/src/public/broker.ts +31 -0
  344. package/src/public/market-data.ts +9 -0
  345. package/src/public/pane-hooks.ts +57 -0
  346. package/src/public/public-api.test.ts +285 -0
  347. package/src/public/react.ts +62 -0
  348. package/src/public/test-support.ts +23 -0
  349. package/src/public/time-series.ts +8 -0
  350. package/src/public/utils.ts +77 -0
  351. package/src/remote/layout-helpers.ts +6 -8
  352. package/src/remote/resources.ts +2 -0
  353. package/src/remote/schema.ts +1 -0
  354. package/src/remote/semantic-tree.test.tsx +105 -0
  355. package/src/remote/semantic-tree.tsx +53 -35
  356. package/src/renderers/browser/main.tsx +6 -0
  357. package/src/renderers/browser/ui-host.tsx +11 -0
  358. package/src/renderers/cloudflare/worker.test.ts +19 -0
  359. package/src/renderers/cloudflare/worker.ts +1 -1
  360. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -2
  361. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  362. package/src/renderers/electrobun/bun/index.ts +6 -2
  363. package/src/renderers/electrobun/shared/protocol.ts +25 -0
  364. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  365. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  366. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  367. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  368. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  369. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  370. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  371. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  372. package/src/renderers/electrobun/view/main.tsx +27 -2
  373. package/src/renderers/electrobun/view/styles.css +5 -0
  374. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  375. package/src/renderers/electrobun/view/ui-host.tsx +3 -0
  376. package/src/renderers/opentui/chart-surface.tsx +5 -1
  377. package/src/renderers/opentui/host.tsx +20 -2
  378. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  379. package/src/renderers/opentui/interaction-performance.ts +188 -0
  380. package/src/renderers/opentui/start.tsx +26 -1
  381. package/src/renderers/opentui/test-utils.tsx +13 -0
  382. package/src/renderers/opentui/toast-host.tsx +26 -2
  383. package/src/renderers/opentui/ui-host.tsx +1 -0
  384. package/src/sources/provider-router/search.test.ts +191 -0
  385. package/src/sources/provider-router/search.ts +92 -39
  386. package/src/state/app/context/index.tsx +2 -0
  387. package/src/state/app/context/selector.test.tsx +33 -1
  388. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  389. package/src/state/hooks/quote-streaming.ts +106 -30
  390. package/src/state/pane-table-export-registry.ts +50 -0
  391. package/src/theme/color-utils.ts +1 -1
  392. package/src/theme/colors.ts +50 -0
  393. package/src/theme/themes.test.ts +15 -2
  394. package/src/theme/themes.ts +19 -1
  395. package/src/tickers/search/index.test.ts +26 -0
  396. package/src/tickers/search/index.ts +42 -17
  397. package/src/tickers/search/ranking.ts +14 -0
  398. package/src/time-series/chart-data.test.ts +216 -0
  399. package/src/time-series/range.ts +12 -0
  400. package/src/time-series/resolution.test.ts +25 -26
  401. package/src/time-series/resolution.ts +44 -25
  402. package/src/time-series/resolve.ts +7 -0
  403. package/src/time-series/spec.ts +5 -5
  404. package/src/time-series/types.ts +3 -1
  405. package/src/time-series/use-chart-resolution.ts +5 -0
  406. package/src/types/config.ts +123 -3
  407. package/src/types/data-provider.ts +11 -0
  408. package/src/types/headless.ts +161 -0
  409. package/src/types/plugin.ts +104 -20
  410. package/src/ui/host.tsx +19 -1
  411. package/src/ui/index.tsx +1 -0
  412. package/src/ui/toast.tsx +8 -4
  413. package/src/utils/csv.test.ts +13 -0
  414. package/src/utils/csv.ts +37 -0
  415. package/src/utils/format.ts +35 -0
  416. package/src/utils/fuzzy-search.test.ts +94 -0
  417. package/src/utils/fuzzy-search.ts +81 -22
  418. package/src/utils/save-text-file.test.ts +24 -0
  419. package/src/utils/save-text-file.ts +36 -0
  420. package/src/utils/shortcut-labels.ts +14 -0
  421. package/src/components/chart/core/axis-format.ts +0 -157
  422. package/src/components/chart/core/index.test.ts +0 -1075
  423. package/src/components/chart/core/projection.ts +0 -357
  424. package/src/components/chart/core/render-utils.ts +0 -4
  425. package/src/components/chart/core/renderer.ts +0 -244
  426. package/src/components/chart/core/scene.ts +0 -180
  427. package/src/components/chart/core/time-axis.ts +0 -330
  428. package/src/components/chart/cursor-motion.ts +0 -47
  429. package/src/components/chart/indicators/types.ts +0 -26
  430. package/src/components/chart/market-session.ts +0 -176
  431. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  432. package/src/components/chart/multi-line/renderer.ts +0 -161
  433. package/src/components/chart/multi-line/scene.ts +0 -188
  434. package/src/components/chart/native/raster/layout.ts +0 -129
  435. package/src/components/chart/native/raster/price-chart.ts +0 -244
  436. package/src/components/chart/rendering/chart-draw.ts +0 -270
  437. package/src/components/chart/rendering/geometry.ts +0 -95
  438. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  439. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  440. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  441. package/src/components/chart/static/chart/surface.tsx +0 -358
  442. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  443. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  444. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  445. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  446. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  447. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  448. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  449. package/src/plugins/builtin/substack/api/store.ts +0 -186
  450. package/src/plugins/builtin/substack/api/types.ts +0 -42
  451. package/src/plugins/builtin/substack/api.test.ts +0 -298
  452. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  453. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  454. package/src/plugins/builtin/substack/content.ts +0 -478
  455. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  456. package/src/plugins/builtin/substack/index.tsx +0 -49
  457. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  458. package/src/plugins/builtin/substack/model.test.ts +0 -155
  459. package/src/plugins/builtin/substack/normalize.ts +0 -343
  460. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  461. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  462. package/src/plugins/builtin/substack/pane.tsx +0 -547
  463. package/src/plugins/builtin/substack/read-state.ts +0 -30
  464. package/src/plugins/builtin/substack/table.ts +0 -111
  465. package/src/plugins/builtin/substack/types.ts +0 -71
  466. package/src/plugins/builtin/substack/utils.ts +0 -131
  467. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  468. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  469. package/src/plugins/ibkr/account-cache.ts +0 -24
  470. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  471. package/src/plugins/ibkr/config.test.ts +0 -79
  472. package/src/plugins/ibkr/config.ts +0 -250
  473. package/src/plugins/ibkr/dialogs.tsx +0 -40
  474. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  475. package/src/plugins/ibkr/flex/index.ts +0 -367
  476. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  477. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  478. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  479. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  480. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  481. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  482. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  483. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  484. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  485. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  486. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  487. package/src/plugins/ibkr/gateway/history.ts +0 -120
  488. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  489. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  490. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  491. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  492. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  493. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  494. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  495. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  496. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  497. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  498. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  499. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  500. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  501. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  502. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  503. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  504. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  505. package/src/plugins/ibkr/gateway/types.ts +0 -27
  506. package/src/plugins/ibkr/index.test.tsx +0 -162
  507. package/src/plugins/ibkr/index.tsx +0 -159
  508. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  509. package/src/plugins/ibkr/instance-selection.ts +0 -36
  510. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  511. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  512. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  513. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  514. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  515. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  516. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  517. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  518. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  519. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  520. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  521. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  522. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  523. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  524. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  525. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  526. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  527. package/src/plugins/ibkr/trade/utils.ts +0 -144
  528. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  529. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  530. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  531. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -0,0 +1,68 @@
1
+ import type { BrokerAdapter } from "../types/broker";
2
+
3
+ /**
4
+ * A stand-in broker for tests of the generic broker machinery.
5
+ *
6
+ * These tests used to reach for the real IBKR adapter, which now lives in its
7
+ * own repository. Depending on a plugin to exercise host code is backwards
8
+ * anyway: it made a core test fail whenever that plugin's schema changed, and
9
+ * it hid which behaviour was actually under test.
10
+ *
11
+ * The shape mirrors what the machinery has to handle — a mode switch, a nested
12
+ * config, and a password field that must survive an edit left blank.
13
+ */
14
+ export const testBroker: BrokerAdapter = {
15
+ id: "test-broker",
16
+ name: "Test Broker",
17
+ configSchema: [
18
+ {
19
+ key: "connectionMode",
20
+ label: "Connection",
21
+ type: "select",
22
+ options: [
23
+ { value: "token", label: "Token" },
24
+ { value: "local", label: "Local" },
25
+ ],
26
+ defaultValue: "token",
27
+ },
28
+ { key: "token", label: "API Token", type: "password", required: true, visibleWhen: { connectionMode: "token" } },
29
+ { key: "accountId", label: "Account", type: "text", required: true, visibleWhen: { connectionMode: "token" } },
30
+ { key: "host", label: "Host", type: "text", defaultValue: "127.0.0.1", visibleWhen: { connectionMode: "local" } },
31
+ ],
32
+
33
+ async validate() {
34
+ return true;
35
+ },
36
+
37
+ async importPositions() {
38
+ return [];
39
+ },
40
+
41
+ getStatus() {
42
+ return { state: "disconnected", message: "", updatedAt: 0 };
43
+ },
44
+
45
+ getProfileActions() {
46
+ // The pane renders and invokes profile actions; one is enough to cover that.
47
+ return [{ id: "test-console", label: "Console", paneId: "ibkr-trading" }];
48
+ },
49
+
50
+ toConfigValues(instance) {
51
+ const config = instance.config as Record<string, unknown>;
52
+ const credentials = (config.credentials ?? {}) as Record<string, unknown>;
53
+ return {
54
+ connectionMode: String(config.connectionMode ?? "token"),
55
+ token: String(credentials.token ?? ""),
56
+ accountId: String(credentials.accountId ?? ""),
57
+ host: String(config.host ?? "127.0.0.1"),
58
+ };
59
+ },
60
+
61
+ fromConfigValues(values) {
62
+ return {
63
+ connectionMode: values.connectionMode,
64
+ credentials: { token: values.token, accountId: values.accountId },
65
+ host: values.host,
66
+ };
67
+ },
68
+ };
@@ -9,6 +9,7 @@ import type {
9
9
  SeriesTransform,
10
10
  TimeSeriesPoint,
11
11
  } from "../time-series/types";
12
+ import { CHART_RESOLUTIONS, TIME_RANGES } from "../time-series/range";
12
13
  import type {
13
14
  CapabilityInvoker,
14
15
  CapabilitySchema,
@@ -24,8 +25,8 @@ export const MAX_CHART_SERIES_POINTS = 20_000;
24
25
  const CAPABILITY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
25
26
  const SERIES_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~%:/-]{0,239}$/;
26
27
  const chartSeriesLog = debugLog.createLogger("chart-series");
27
- const ranges = new Set(["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"]);
28
- const resolutions = new Set(["auto", "1m", "5m", "15m", "30m", "45m", "1h", "1d", "1wk", "1mo"]);
28
+ const ranges = new Set<string>(TIME_RANGES);
29
+ const resolutions = new Set<string>(CHART_RESOLUTIONS);
29
30
  const periods = new Set(["auto", "daily", "weekly", "monthly", "quarterly", "annual", "ttm"]);
30
31
  const styles = new Set(["line", "area", "step", "columns", "points", "candles", "ohlc", "hlc"]);
31
32
  const transforms = new Set(["raw", "percent", "index100", "yoy", "qoq", "log"]);
@@ -1,5 +1,5 @@
1
1
  import type { CliCommandDef } from "../../types/plugin";
2
- import type { TimeRange } from "../../time-series/range";
2
+ import { TIME_RANGES, type TimeRange } from "../../time-series/range";
3
3
  import type { EarningsEvent, QuoteBatchResult, SecFilingItem } from "../../types/data-provider";
4
4
  import type { NewsArticle, NewsFeed, NewsQuery } from "../../news/types";
5
5
  import type {
@@ -15,7 +15,7 @@ import { formatCompact } from "../../utils/format";
15
15
  import { withCliServices, withMarketData } from "../context";
16
16
  import { isoDate, parsePositiveInt, requireArg, takeOption } from "./command-utils";
17
17
 
18
- const VALID_RANGES = new Set<TimeRange>(["1D", "1W", "1M", "3M", "6M", "1Y", "5Y", "ALL"]);
18
+ const VALID_RANGES = new Set<TimeRange>(TIME_RANGES);
19
19
  const VALID_NEWS_FEEDS = new Set<NewsFeed>(["latest", "top", "breaking", "ticker", "sector", "topic"]);
20
20
 
21
21
  type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & { error: string | null };
@@ -2,6 +2,7 @@ import { join } from "path";
2
2
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "fs";
3
3
  import { execFileSync } from "child_process";
4
4
  import { getPluginsDir } from "../../plugins/loader";
5
+ import { linkHostPackages } from "../../plugins/host-link";
5
6
  import {
6
7
  cliStyles,
7
8
  renderSection,
@@ -50,7 +51,20 @@ function parseGitHubRef(rawRef: string): { url: string; name: string } {
50
51
  throw new Error(`Invalid plugin reference: ${ref}. Use user/repo or a GitHub URL.`);
51
52
  }
52
53
 
53
- export async function installPlugin(ref: string) {
54
+ export interface InstallPluginOptions {
55
+ /**
56
+ * Suppress child-process output and progress logging. The marketplace pane
57
+ * installs while the terminal UI owns the screen, and git and bun writing to
58
+ * stdout corrupts the rendered frame.
59
+ */
60
+ quiet?: boolean;
61
+ }
62
+
63
+ export async function installPlugin(ref: string, options: InstallPluginOptions = {}) {
64
+ const stdio = options.quiet ? "pipe" : "inherit";
65
+ const say = (message: string) => {
66
+ if (!options.quiet) console.log(message);
67
+ };
54
68
  ensurePluginsDir();
55
69
  const { url, name } = parseGitHubRef(ref);
56
70
  const targetDir = join(PLUGINS_DIR, name);
@@ -59,11 +73,11 @@ export async function installPlugin(ref: string) {
59
73
  fail(`Plugin "${name}" already exists.`, `Use "gloomberb update ${name}" to refresh it.`);
60
74
  }
61
75
 
62
- console.log(cliStyles.accent(`Installing ${name}`));
63
- console.log(cliStyles.muted(url));
76
+ say(cliStyles.accent(`Installing ${name}`));
77
+ say(cliStyles.muted(url));
64
78
 
65
79
  try {
66
- execFileSync("git", ["clone", "--depth", "1", url, targetDir], { stdio: "inherit" });
80
+ execFileSync("git", ["clone", "--depth", "1", url, targetDir], { stdio });
67
81
  } catch {
68
82
  rmSync(targetDir, { recursive: true, force: true });
69
83
  fail(`Failed to clone ${url}.`);
@@ -71,11 +85,24 @@ export async function installPlugin(ref: string) {
71
85
 
72
86
  const pkgPath = join(targetDir, "package.json");
73
87
  if (existsSync(pkgPath)) {
74
- console.log(cliStyles.muted("Installing plugin dependencies..."));
88
+ say(cliStyles.muted("Installing plugin dependencies..."));
75
89
  try {
76
- execFileSync("bun", ["install"], { cwd: targetDir, stdio: "inherit" });
90
+ // --production: plugin repos depend on `gloomberb` as a devDependency so
91
+ // their own CI can typecheck against the real API. At runtime the host is
92
+ // symlinked in instead, and pulling a second full copy here would both
93
+ // waste a lot of disk and risk a duplicate React.
94
+ execFileSync("bun", ["install", "--production"], { cwd: targetDir, stdio });
77
95
  } catch {
78
- console.error(cliStyles.warning("Warning: failed to install plugin dependencies."));
96
+ if (!options.quiet) console.error(cliStyles.warning("Warning: failed to install plugin dependencies."));
97
+ }
98
+ }
99
+
100
+ // After `bun install`, which prunes links it does not know about.
101
+ const link = linkHostPackages(targetDir);
102
+ if (link.error) {
103
+ if (!options.quiet) {
104
+ console.error(cliStyles.warning(`Warning: could not link the Gloomberb runtime (${link.error}).`));
105
+ console.error(cliStyles.muted("The plugin's \"gloomberb/*\" imports will not resolve."));
79
106
  }
80
107
  }
81
108
 
@@ -98,13 +125,13 @@ export async function installPlugin(ref: string) {
98
125
  const mod = await import(entryFile);
99
126
  const plugin = mod.default ?? mod.plugin;
100
127
  if (plugin?.id && plugin?.name) {
101
- console.log(cliStyles.success(`Installed ${plugin.name} v${plugin.version || "0.0.0"}`));
128
+ say(cliStyles.success(`Installed ${plugin.name} v${plugin.version || "0.0.0"}`));
102
129
  return;
103
130
  }
104
131
  }
105
- console.log(cliStyles.warning("Installed files, but no valid GloomPlugin export was found."));
132
+ say(cliStyles.warning("Installed files, but no valid GloomPlugin export was found."));
106
133
  } catch (err) {
107
- console.log(cliStyles.warning(`Plugin validation failed: ${err}`));
134
+ say(cliStyles.warning(`Plugin validation failed: ${err}`));
108
135
  }
109
136
  }
110
137
 
@@ -141,8 +168,9 @@ export async function updatePlugins(name?: string) {
141
168
  execFileSync("git", ["pull", "--ff-only"], { cwd: targetDir, stdio: "inherit" });
142
169
  const pkgPath = join(targetDir, "package.json");
143
170
  if (existsSync(pkgPath)) {
144
- execFileSync("bun", ["install"], { cwd: targetDir, stdio: "inherit" });
171
+ execFileSync("bun", ["install", "--production"], { cwd: targetDir, stdio: "inherit" });
145
172
  }
173
+ linkHostPackages(targetDir);
146
174
  } catch {
147
175
  console.error(cliStyles.danger(`Failed to update ${dir}.`));
148
176
  }
@@ -1,7 +1,6 @@
1
1
  import { mkdir, mkdtemp, rm, writeFile } from "fs/promises";
2
2
  import { tmpdir } from "os";
3
- import { join } from "path";
4
- import { pathToFileURL } from "url";
3
+ import { join, resolve, sep } from "path";
5
4
  import type { AppConfig } from "../types/config";
6
5
  import type { ResolvedSeries } from "../time-series/types";
7
6
  import type { OptionsChain, TickerFinancials } from "../types/financials";
@@ -9,6 +8,7 @@ import type { TickerRecord } from "../types/ticker";
9
8
  import type { PaneRuntimeState } from "../core/state/app/state";
10
9
  import type { RemoteUiNodeSnapshot } from "../remote/types";
11
10
  import type { FredSeriesCacheEntry } from "../data/fred-series";
11
+ import type { DatedObservation } from "../plugins/builtin/market-valuation/series";
12
12
  import {
13
13
  electrobunViewPath,
14
14
  writeElectrobunViewPage,
@@ -21,16 +21,46 @@ export interface DesktopPaneShotPayload {
21
21
  heightCells: number;
22
22
  widthPx: number;
23
23
  heightPx: number;
24
+ /** Chrome device scale factor; 2 keeps text crisp, higher values zoom the layout. */
25
+ deviceScaleFactor?: number;
26
+ /** Label drawn at the right edge of the pane title bar; null draws nothing. */
27
+ watermark?: string | null;
24
28
  tickers: TickerRecord[];
25
29
  financials: Array<[string, TickerFinancials]>;
26
30
  optionsChains: Array<[string, OptionsChain]>;
27
31
  fredSeries: Array<[string, FredSeriesCacheEntry]>;
32
+ valuationSeries: Array<[string, DatedObservation[]]>;
33
+ statSeries: Array<[string, DatedObservation[]]>;
28
34
  capabilitySeries: Array<[string, ResolvedSeries]>;
29
35
  paneState: Record<string, PaneRuntimeState>;
30
36
  }
31
37
 
38
+ /** Kept in Bun memory and never serialized into the browser page or CLI result. */
39
+ export interface DesktopPaneShotApiProxy {
40
+ baseUrl: string;
41
+ sessionToken: string | null;
42
+ }
43
+
44
+ export interface DesktopPaneShotRenderedCell {
45
+ columnId?: string;
46
+ columnLabel: string;
47
+ text: string;
48
+ }
49
+
50
+ export interface DesktopPaneShotRenderedRow {
51
+ tableIndex: number;
52
+ rowIndex: number;
53
+ key?: string;
54
+ selected: boolean;
55
+ cells: DesktopPaneShotRenderedCell[];
56
+ }
57
+
32
58
  export interface DesktopPaneShotRenderResult {
33
59
  visibleText: string;
60
+ rows: DesktopPaneShotRenderedRow[];
61
+ loadingStateDetected: boolean;
62
+ errorStateDetected: boolean;
63
+ errorStateMarkers: string[];
34
64
  emptyStateDetected: boolean;
35
65
  emptyStateMarkers: string[];
36
66
  semanticUi: RemoteUiNodeSnapshot[];
@@ -47,30 +77,47 @@ type PendingCdpCall = {
47
77
  reject: (error: Error) => void;
48
78
  };
49
79
 
80
+ const SHOT_MODE_CSS = [
81
+ "[data-gloom-role='composite-chart-toolbar']",
82
+ "[data-gloom-role='chart-series-quick-add']",
83
+ "[data-gloom-role='pane-close']",
84
+ ].join(", ") + " { display: none !important; }\n"
85
+ // Sits where the hidden close button was: one cell high, right-aligned in the title bar.
86
+ + "[data-gloom-role='shot-watermark'] { position: fixed; top: 1px; right: 10px; height: var(--cell-h);"
87
+ + " line-height: var(--cell-h); font-size: 12px; letter-spacing: 0.02em; color: var(--gloom-text-dim, #888);"
88
+ + " pointer-events: none; z-index: 1000; }";
89
+
50
90
  const CHROME_POLL_ATTEMPTS = 80;
51
- const SHOT_READY_TIMEOUT_MS = 10_000;
91
+ const SHOT_READY_TIMEOUT_MS = 45_000;
52
92
  const CDP_CALL_TIMEOUT_MS = 10_000;
53
- const SHOT_DEVICE_SCALE_FACTOR = 2;
93
+ const DEFAULT_DEVICE_SCALE_FACTOR = 2;
94
+ const SHOT_API_PROXY_PREFIX = "/__gloom_cli_api__";
95
+ const SESSION_COOKIE_NAMES = ["__Secure-gloomberb.session_token", "gloomberb.session_token"] as const;
54
96
 
55
97
  export async function renderDesktopPaneScreenshot(
56
98
  payload: DesktopPaneShotPayload,
57
99
  outputPath: string,
100
+ apiProxy: DesktopPaneShotApiProxy,
58
101
  ): Promise<DesktopPaneShotRenderResult> {
59
102
  const tempDir = await mkdtemp(join(tmpdir(), "gloom-pane-shot-"));
103
+ let server: ReturnType<typeof Bun.serve> | null = null;
60
104
  try {
61
105
  const outdir = join(tempDir, "assets");
62
106
  await mkdir(outdir, { recursive: true });
63
- const htmlPath = await buildShotPage(outdir, payload);
107
+ await buildShotPage(outdir, payload);
108
+ server = serveShotPage(outdir, apiProxy);
64
109
  const chrome = await findChromeExecutable();
65
110
  return await capturePageScreenshot({
66
111
  chrome,
67
- url: pathToFileURL(htmlPath).href,
112
+ url: new URL("/", server.url).href,
68
113
  outputPath,
69
114
  widthPx: payload.widthPx,
70
115
  heightPx: payload.heightPx,
116
+ deviceScaleFactor: payload.deviceScaleFactor ?? DEFAULT_DEVICE_SCALE_FACTOR,
71
117
  userDataDir: join(tempDir, "chrome-profile"),
72
118
  });
73
119
  } finally {
120
+ server?.stop(true);
74
121
  await rm(tempDir, { recursive: true, force: true });
75
122
  }
76
123
  }
@@ -90,6 +137,20 @@ async function buildShotPage(outdir: string, payload: DesktopPaneShotPayload): P
90
137
  loadingText: "Rendering pane...",
91
138
  bootstrapScript: `
92
139
  window.__GLOOM_CLI_SHOT_PAYLOAD__ = ${payloadJson};
140
+ (() => {
141
+ // A screenshot cannot be interacted with, so drawing tools, the
142
+ // quick-add input and the close button only add noise to the image.
143
+ const style = document.createElement("style");
144
+ style.textContent = ${JSON.stringify(SHOT_MODE_CSS)};
145
+ document.head.appendChild(style);
146
+ const watermark = ${JSON.stringify(payload.watermark ?? null)};
147
+ if (watermark) {
148
+ const mark = document.createElement("div");
149
+ mark.setAttribute("data-gloom-role", "shot-watermark");
150
+ mark.textContent = watermark;
151
+ document.addEventListener("DOMContentLoaded", () => document.body.appendChild(mark));
152
+ }
153
+ })();
93
154
  window.addEventListener("error", (event) => {
94
155
  window.__GLOOM_CLI_SHOT_ERROR__ = event.error && event.error.stack ? event.error.stack : String(event.error || event.message);
95
156
  });
@@ -100,6 +161,116 @@ async function buildShotPage(outdir: string, payload: DesktopPaneShotPayload): P
100
161
  });
101
162
  }
102
163
 
164
+ function serveShotPage(
165
+ outdir: string,
166
+ apiProxy: DesktopPaneShotApiProxy,
167
+ ): ReturnType<typeof Bun.serve> {
168
+ const staticRoot = resolve(outdir);
169
+ return Bun.serve({
170
+ hostname: "127.0.0.1",
171
+ port: 0,
172
+ idleTimeout: 60,
173
+ fetch(request) {
174
+ const url = new URL(request.url);
175
+ if (url.pathname.startsWith(`${SHOT_API_PROXY_PREFIX}/`)) {
176
+ return proxyShotApiRequest(request, url, apiProxy);
177
+ }
178
+ return serveShotAsset(url, staticRoot);
179
+ },
180
+ });
181
+ }
182
+
183
+ async function serveShotAsset(url: URL, staticRoot: string): Promise<Response> {
184
+ let pathname: string;
185
+ try {
186
+ pathname = decodeURIComponent(url.pathname);
187
+ } catch {
188
+ return new Response("Bad request", { status: 400 });
189
+ }
190
+ const relativePath = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
191
+ const filePath = resolve(staticRoot, relativePath);
192
+ if (filePath !== staticRoot && !filePath.startsWith(`${staticRoot}${sep}`)) {
193
+ return new Response("Not found", { status: 404 });
194
+ }
195
+ const file = Bun.file(filePath);
196
+ if (!(await file.exists())) return new Response("Not found", { status: 404 });
197
+ return new Response(file);
198
+ }
199
+
200
+ async function proxyShotApiRequest(
201
+ request: Request,
202
+ requestUrl: URL,
203
+ apiProxy: DesktopPaneShotApiProxy,
204
+ ): Promise<Response> {
205
+ const baseUrl = new URL(apiProxy.baseUrl);
206
+ const proxiedPath = requestUrl.pathname.slice(SHOT_API_PROXY_PREFIX.length) || "/";
207
+ const target = new URL(baseUrl);
208
+ const basePath = baseUrl.pathname.replace(/\/+$/, "");
209
+ target.pathname = `${basePath}${proxiedPath.startsWith("/") ? proxiedPath : `/${proxiedPath}`}`;
210
+ target.search = requestUrl.search;
211
+ target.hash = "";
212
+ const headers = new Headers(request.headers);
213
+ for (const name of [
214
+ "authorization",
215
+ "connection",
216
+ "content-length",
217
+ "cookie",
218
+ "host",
219
+ "origin",
220
+ "proxy-authorization",
221
+ "referer",
222
+ "te",
223
+ "trailer",
224
+ "transfer-encoding",
225
+ "upgrade",
226
+ "x-forwarded-for",
227
+ "x-forwarded-host",
228
+ "x-forwarded-proto",
229
+ ]) {
230
+ headers.delete(name);
231
+ }
232
+ const browserOwnedHeaders: string[] = [];
233
+ headers.forEach((_value, name) => {
234
+ if (name.startsWith("sec-") || name === "accept-encoding") browserOwnedHeaders.push(name);
235
+ });
236
+ for (const name of browserOwnedHeaders) headers.delete(name);
237
+ headers.set("Origin", baseUrl.origin);
238
+ if (apiProxy.sessionToken) {
239
+ headers.set(
240
+ "Cookie",
241
+ SESSION_COOKIE_NAMES.map((name) => `${name}=${apiProxy.sessionToken}`).join("; "),
242
+ );
243
+ }
244
+
245
+ try {
246
+ const response = await fetch(target, {
247
+ method: request.method,
248
+ headers,
249
+ body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,
250
+ redirect: "follow",
251
+ });
252
+ const responseHeaders = new Headers(response.headers);
253
+ for (const name of [
254
+ "access-control-allow-credentials",
255
+ "access-control-allow-origin",
256
+ "content-encoding",
257
+ "content-length",
258
+ "set-cookie",
259
+ "transfer-encoding",
260
+ ]) {
261
+ responseHeaders.delete(name);
262
+ }
263
+ responseHeaders.set("Cache-Control", "no-store");
264
+ return new Response(response.body, {
265
+ status: response.status,
266
+ statusText: response.statusText,
267
+ headers: responseHeaders,
268
+ });
269
+ } catch {
270
+ return Response.json({ error: "Cloud API request failed." }, { status: 502 });
271
+ }
272
+ }
273
+
103
274
  async function findChromeExecutable(): Promise<string> {
104
275
  const candidates = [
105
276
  process.env.CHROME_PATH,
@@ -123,6 +294,7 @@ async function capturePageScreenshot({
123
294
  outputPath,
124
295
  widthPx,
125
296
  heightPx,
297
+ deviceScaleFactor,
126
298
  userDataDir,
127
299
  }: {
128
300
  chrome: string;
@@ -130,6 +302,7 @@ async function capturePageScreenshot({
130
302
  outputPath: string;
131
303
  widthPx: number;
132
304
  heightPx: number;
305
+ deviceScaleFactor: number;
133
306
  userDataDir: string;
134
307
  }): Promise<DesktopPaneShotRenderResult> {
135
308
  await mkdir(userDataDir, { recursive: true });
@@ -166,7 +339,7 @@ async function capturePageScreenshot({
166
339
  await session.send("Emulation.setDeviceMetricsOverride", {
167
340
  width: widthPx,
168
341
  height: heightPx,
169
- deviceScaleFactor: SHOT_DEVICE_SCALE_FACTOR,
342
+ deviceScaleFactor,
170
343
  mobile: false,
171
344
  });
172
345
  await waitForShotReady(session);
@@ -189,6 +362,21 @@ async function capturePageScreenshot({
189
362
  }
190
363
  }
191
364
 
365
+ const LOADING_STATE_PATTERNS = [
366
+ /\bLoading(?: [^.]{0,80})?\.{3}/gi,
367
+ /\bRendering pane\.{3}/gi,
368
+ ];
369
+
370
+ const ERROR_STATE_PATTERNS = [
371
+ /\b[^.]{1,100} unavailable\./gi,
372
+ /\bFailed to fetch\b/gi,
373
+ /\bCould not load\b/gi,
374
+ /\bSign in to\b/gi,
375
+ /\bVerify your email\b/gi,
376
+ /\bpart of Gloom Cloud Pro\b/gi,
377
+ /\bCloud API request failed\b/gi,
378
+ ];
379
+
192
380
  const EMPTY_STATE_PATTERNS = [
193
381
  /\bNo chart data\b/gi,
194
382
  /\bNo graph data\b/gi,
@@ -197,21 +385,94 @@ const EMPTY_STATE_PATTERNS = [
197
385
  /\bNo comparison tickers configured\b/gi,
198
386
  /\bNo relationship tickers configured\b/gi,
199
387
  /\bNo overlapping price history\b/gi,
200
- /\bLoading chart\.{0,3}\b/gi,
201
- /\bRendering pane\.{0,3}\b/gi,
202
- /\bNo tickers selected\b/gi,
203
- /\bMarket data unavailable\b/gi,
388
+ /\bNo tickers? selected\b/gi,
204
389
  /\bNo data(?: available)?\b/gi,
390
+ /\bNothing to show yet\b/gi,
391
+ /\bNo transcribed calls(?: for [^.]+)? yet\b/gi,
392
+ /\bNo short interest (?:data|found)\b/gi,
393
+ /\bNo (?:House PTR |insider )?(?:trades|members|transactions|filings)\b/gi,
205
394
  ];
206
395
 
396
+ function stateMarkers(text: string, patterns: RegExp[]): string[] {
397
+ return [...new Set(patterns.flatMap((pattern) => (
398
+ [...text.matchAll(pattern)].map((match) => match[0])
399
+ )))];
400
+ }
401
+
207
402
  async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneShotRenderResult> {
208
403
  const result = await session.send("Runtime.evaluate", {
209
404
  expression: `(() => {
210
405
  const root = document.getElementById("root") || document.body;
406
+ const semanticUi = window.__GLOOM_CLI_SHOT_SEMANTIC_UI__ || [];
407
+ const normalize = (value) => String(value || "").replace(/\\s+/g, " ").trim();
408
+ const isVisible = (element) => {
409
+ if (!(element instanceof HTMLElement)) return false;
410
+ const style = getComputedStyle(element);
411
+ if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false;
412
+ const rect = element.getBoundingClientRect();
413
+ return rect.width > 0 && rect.height > 0
414
+ && rect.right > 0 && rect.bottom > 0
415
+ && rect.left < window.innerWidth && rect.top < window.innerHeight;
416
+ };
417
+ const semanticTables = semanticUi.filter((node) => node && node.role === "table");
418
+ const rows = [];
419
+ [...root.querySelectorAll('[data-gloom-role="data-table"]')]
420
+ .filter(isVisible)
421
+ .forEach((table, tableIndex) => {
422
+ const tableMetadata = semanticTables[tableIndex] && semanticTables[tableIndex].metadata || {};
423
+ const semanticColumns = Array.isArray(tableMetadata.columns) ? tableMetadata.columns : [];
424
+ const semanticRows = Array.isArray(tableMetadata.rows) ? tableMetadata.rows : [];
425
+ const headers = [...table.querySelectorAll('[data-gloom-role="data-table-header-cell"]')]
426
+ .map((cell) => normalize(cell.innerText || cell.textContent));
427
+ const rowElements = [...table.querySelectorAll(
428
+ '[data-gloom-role="data-table-row"], [data-gloom-role="data-table-section-header"]',
429
+ )].filter(isVisible);
430
+ rowElements.forEach((row, rowIndex) => {
431
+ const cellElements = [...row.querySelectorAll('[data-gloom-role="data-table-cell"]')];
432
+ const values = cellElements.length > 0 ? cellElements : [row];
433
+ const semanticRow = semanticRows[rowIndex] || {};
434
+ const cells = values.map((cell, cellIndex) => {
435
+ const semanticColumn = semanticColumns[cellIndex] || {};
436
+ return {
437
+ ...(typeof semanticColumn.id === "string" ? { columnId: semanticColumn.id } : {}),
438
+ columnLabel: typeof semanticColumn.label === "string"
439
+ ? semanticColumn.label
440
+ : headers[cellIndex] || (values.length === 1 ? "Row" : String(cellIndex + 1)),
441
+ text: normalize(cell.innerText || cell.textContent),
442
+ };
443
+ }).filter((cell) => cell.text.length > 0);
444
+ if (cells.length === 0) return;
445
+ rows.push({
446
+ tableIndex,
447
+ rowIndex,
448
+ ...(typeof semanticRow.key === "string" ? { key: semanticRow.key } : {}),
449
+ selected: row.getAttribute("data-selected") === "true" || semanticRow.selected === true,
450
+ cells,
451
+ });
452
+ });
453
+ });
454
+ if (rows.length === 0) {
455
+ [...root.querySelectorAll('[data-gloom-role="desktop-list-row"]')]
456
+ .filter(isVisible)
457
+ .forEach((row, rowIndex) => {
458
+ const text = normalize(row.innerText || row.textContent);
459
+ if (!text) return;
460
+ rows.push({
461
+ tableIndex: 0,
462
+ rowIndex,
463
+ selected: row.getAttribute("data-selected") === "true",
464
+ cells: [{ columnLabel: "Row", text }],
465
+ });
466
+ });
467
+ }
211
468
  return {
212
469
  visibleText: root.innerText || root.textContent || "",
213
470
  error: window.__GLOOM_CLI_SHOT_ERROR__ || "",
214
- semanticUi: window.__GLOOM_CLI_SHOT_SEMANTIC_UI__ || [],
471
+ loadingStateDetected: root.querySelector('[data-gloom-status="loading"]') !== null,
472
+ errorStateDetected: root.querySelector('[data-gloom-status="error"]') !== null,
473
+ emptyStateDetected: root.querySelector('[data-gloom-status="empty"]') !== null,
474
+ rows,
475
+ semanticUi,
215
476
  };
216
477
  })()`,
217
478
  returnByValue: true,
@@ -220,6 +481,10 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
220
481
  value?: {
221
482
  visibleText?: string;
222
483
  error?: string;
484
+ loadingStateDetected?: boolean;
485
+ errorStateDetected?: boolean;
486
+ emptyStateDetected?: boolean;
487
+ rows?: DesktopPaneShotRenderedRow[];
223
488
  semanticUi?: RemoteUiNodeSnapshot[];
224
489
  };
225
490
  };
@@ -227,12 +492,16 @@ async function readRenderedPaneState(session: CdpSession): Promise<DesktopPaneSh
227
492
  const value = result.result?.value;
228
493
  if (value?.error) throw new Error(value.error);
229
494
  const visibleText = (value?.visibleText ?? "").replace(/\s+/g, " ").trim();
230
- const emptyStateMarkers = [...new Set(EMPTY_STATE_PATTERNS.flatMap((pattern) => (
231
- [...visibleText.matchAll(pattern)].map((match) => match[0])
232
- )))];
495
+ const loadingStateMarkers = stateMarkers(visibleText, LOADING_STATE_PATTERNS);
496
+ const errorStateMarkers = stateMarkers(visibleText, ERROR_STATE_PATTERNS);
497
+ const emptyStateMarkers = stateMarkers(visibleText, EMPTY_STATE_PATTERNS);
233
498
  return {
234
499
  visibleText,
235
- emptyStateDetected: emptyStateMarkers.length > 0,
500
+ rows: Array.isArray(value?.rows) ? value.rows : [],
501
+ loadingStateDetected: value?.loadingStateDetected === true || loadingStateMarkers.length > 0,
502
+ errorStateDetected: value?.errorStateDetected === true || errorStateMarkers.length > 0,
503
+ errorStateMarkers,
504
+ emptyStateDetected: value?.emptyStateDetected === true || emptyStateMarkers.length > 0,
236
505
  emptyStateMarkers,
237
506
  semanticUi: Array.isArray(value?.semanticUi) ? value.semanticUi : [],
238
507
  };
package/src/cli/index.ts CHANGED
@@ -121,7 +121,7 @@ function createCoreCliCommands(
121
121
  aliases: ["screenshot"],
122
122
  description: "Render a desktop-style screenshot for a pane-backed market function",
123
123
  help: {
124
- usage: ["shot <function-or-pane> [argument] [--output path] [--width px] [--height px] [--key value]"],
124
+ usage: ["shot <function-or-pane> [argument] [--output path] [--width px] [--height px] [--theme id] [--scale n] [--watermark text] [--key value]"],
125
125
  },
126
126
  execute: async (args, ctx) => {
127
127
  await runPaneScreenshot(args, ctx);