gloomberb 0.10.5 → 0.11.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 (532) hide show
  1. package/README.md +29 -2
  2. package/package.json +3 -1
  3. package/src/api-client/auth.ts +34 -0
  4. package/src/api-client/data.ts +25 -0
  5. package/src/api-client/index.test.ts +81 -0
  6. package/src/api-client/index.ts +37 -0
  7. package/src/api-client/request.test.ts +30 -0
  8. package/src/api-client/request.ts +44 -20
  9. package/src/api-client/socket-health.test.ts +79 -0
  10. package/src/api-client/socket.ts +108 -5
  11. package/src/api-client/types.ts +139 -0
  12. package/src/app/global-shortcuts.test.tsx +144 -9
  13. package/src/app/global-shortcuts.ts +33 -11
  14. package/src/app/runtime/broker-import.ts +39 -5
  15. package/src/app/runtime/startup.ts +7 -1
  16. package/src/app/runtime/update.ts +26 -1
  17. package/src/app.tsx +36 -19
  18. package/src/brokers/broker-ticker-sync.ts +3 -8
  19. package/src/brokers/sync-broker-instance.test.ts +59 -0
  20. package/src/brokers/sync-broker-instance.ts +37 -13
  21. package/src/capabilities/chart-series.ts +318 -0
  22. package/src/capabilities/factories.ts +46 -0
  23. package/src/capabilities/index.ts +1 -0
  24. package/src/capabilities/registry.test.ts +135 -0
  25. package/src/capabilities/registry.ts +31 -4
  26. package/src/capabilities/types.ts +52 -0
  27. package/src/cli/commands/overview.ts +2 -2
  28. package/src/cli/desktop-pane-shot.ts +2 -0
  29. package/src/cli/pane-functions/catalog.ts +4 -0
  30. package/src/cli/pane-functions/data.ts +6 -1
  31. package/src/cli/pane-functions/discovery.ts +76 -10
  32. package/src/cli/pane-functions/report.test.ts +76 -0
  33. package/src/cli/pane-functions/report.ts +7 -1
  34. package/src/cli/pane-functions/resolver.ts +4 -1
  35. package/src/cli/pane-functions/screenshot.test.ts +56 -3
  36. package/src/cli/pane-functions/screenshot.ts +68 -5
  37. package/src/components/chart/composite/composite-chart.test.tsx +54 -0
  38. package/src/components/chart/composite/composite-chart.tsx +92 -15
  39. package/src/components/chart/composite/downsample.test.ts +136 -0
  40. package/src/components/chart/composite/downsample.ts +208 -0
  41. package/src/components/chart/composite/panel-series.test.ts +66 -0
  42. package/src/components/chart/composite/panel-series.ts +101 -0
  43. package/src/components/chart/multi-line/renderer.ts +1 -0
  44. package/src/components/chart/native/kitty/index.test.ts +25 -3
  45. package/src/components/chart/native/kitty/support.ts +37 -3
  46. package/src/components/chart/native/use-chart-text-fallback.ts +19 -0
  47. package/src/components/chart/static/chart/surface.tsx +43 -12
  48. package/src/components/chart/static/multi-line-chart-surface.tsx +41 -5
  49. package/src/components/command-bar/commands/direct/index.ts +18 -0
  50. package/src/components/command-bar/commands/registry.ts +14 -0
  51. package/src/components/command-bar/routes/root/article-results.test.ts +83 -0
  52. package/src/components/command-bar/routes/root/article-results.ts +55 -0
  53. package/src/components/command-bar/routes/root/command-items.ts +3 -0
  54. package/src/components/command-bar/routes/root/results.ts +38 -11
  55. package/src/components/command-bar/routes/root/runtime.ts +6 -2
  56. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -1
  57. package/src/components/command-bar/surface/assist.test.tsx +13 -6
  58. package/src/components/command-bar/surface/index.tsx +35 -0
  59. package/src/components/command-bar/view-model.test.ts +19 -0
  60. package/src/components/command-bar/view-model.ts +39 -3
  61. package/src/components/command-bar/workflow/collection-actions.test.ts +94 -0
  62. package/src/components/command-bar/workflow/collection-actions.ts +14 -3
  63. package/src/components/command-bar/workflow/ops.test.ts +136 -0
  64. package/src/components/command-bar/workflow/ops.ts +71 -1
  65. package/src/components/index.ts +1 -1
  66. package/src/components/layout/header.test.tsx +35 -0
  67. package/src/components/layout/header.tsx +101 -9
  68. package/src/components/layout/pane/cycle-order.test.ts +0 -1
  69. package/src/components/layout/pane/footer/index.test.tsx +19 -19
  70. package/src/components/layout/pane/header.tsx +2 -13
  71. package/src/components/layout/shell/index.tsx +3 -0
  72. package/src/components/layout/status-bar.test.tsx +36 -1
  73. package/src/components/layout/status-bar.tsx +48 -2
  74. package/src/components/onboarding/account-step/chooser-panel.tsx +27 -50
  75. package/src/components/onboarding/account-step/form-panel.tsx +85 -29
  76. package/src/components/onboarding/account-step/index.tsx +10 -1
  77. package/src/components/onboarding/account-step/model.ts +3 -3
  78. package/src/components/onboarding/account-step/qr-panel.tsx +68 -0
  79. package/src/components/onboarding/account-step/signed-in-panel.tsx +4 -7
  80. package/src/components/onboarding/onboarding-frame.tsx +517 -0
  81. package/src/components/onboarding/onboarding-steps.tsx +0 -264
  82. package/src/components/onboarding/onboarding-wizard.test.tsx +626 -200
  83. package/src/components/onboarding/onboarding-wizard.tsx +848 -266
  84. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +81 -51
  85. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +14 -20
  86. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +8 -11
  87. package/src/components/onboarding/portfolio-step/choose-panel.tsx +18 -22
  88. package/src/components/onboarding/portfolio-step/index.tsx +3 -0
  89. package/src/components/onboarding/portfolio-step/types.ts +3 -0
  90. package/src/components/onboarding/wizard-account.ts +38 -3
  91. package/src/components/onboarding/wizard-broker-sync.ts +45 -67
  92. package/src/components/onboarding/wizard-model.ts +18 -2
  93. package/src/components/ui/data-table/opentui/index.tsx +29 -9
  94. package/src/components/ui/data-table/types.ts +2 -0
  95. package/src/components/ui/external-link.tsx +4 -2
  96. package/src/components/ui/loading.tsx +1 -1
  97. package/src/components/ui/status.tsx +76 -4
  98. package/src/components/ui/table-layout.test.ts +24 -0
  99. package/src/components/ui/table-layout.ts +90 -23
  100. package/src/components/ui/tabs.tsx +86 -4
  101. package/src/core/app-services.ts +14 -3
  102. package/src/core/connection-health.test.ts +143 -0
  103. package/src/core/connection-health.ts +295 -0
  104. package/src/core/state/app/layout-reducer.ts +34 -0
  105. package/src/core/state/app/layout.ts +21 -0
  106. package/src/core/state/app/state.ts +10 -0
  107. package/src/core/state/app/types.ts +7 -1
  108. package/src/data/config/chart-settings.ts +11 -2
  109. package/src/data/config/store/index.test.ts +83 -9
  110. package/src/data/config/store/migrations.ts +16 -0
  111. package/src/data/config/store/normalize.ts +59 -1
  112. package/src/data/fred-series.ts +8 -2
  113. package/src/i18n/es.ts +126 -0
  114. package/src/i18n/ja.ts +126 -0
  115. package/src/i18n/ko.ts +126 -0
  116. package/src/i18n/zh-cn.ts +126 -0
  117. package/src/i18n/zh-tw.ts +126 -0
  118. package/src/market-data/coordinator/chart.ts +3 -2
  119. package/src/market-data/hooks.tsx +3 -2
  120. package/src/market-data/market/status.test.ts +12 -0
  121. package/src/market-data/market/status.ts +34 -17
  122. package/src/news/aggregator.test.ts +39 -0
  123. package/src/news/aggregator.ts +78 -22
  124. package/src/news/hooks.ts +4 -0
  125. package/src/notifications/app-notifier.test.ts +28 -6
  126. package/src/notifications/app-notifier.ts +24 -5
  127. package/src/plugins/broker-sync/broker-sync.test.ts +130 -0
  128. package/src/plugins/broker-sync/native-loader.ts +10 -0
  129. package/src/plugins/broker-sync/normalize.ts +271 -0
  130. package/src/plugins/broker-sync/public.ts +122 -0
  131. package/src/plugins/broker-sync/robinhood-native.ts +367 -0
  132. package/src/plugins/broker-sync/robinhood.ts +114 -0
  133. package/src/plugins/broker-sync/simplefin-native.ts +201 -0
  134. package/src/plugins/broker-sync/simplefin.ts +70 -0
  135. package/src/plugins/builtin/account-management/footer.ts +8 -19
  136. package/src/plugins/builtin/account-management/pane.tsx +13 -8
  137. package/src/plugins/builtin/ai/pi/host.ts +6 -5
  138. package/src/plugins/builtin/ai/providers.ts +1 -1
  139. package/src/plugins/builtin/ai/screener/footer.ts +0 -7
  140. package/src/plugins/builtin/ai/screener/pane.test.tsx +2 -1
  141. package/src/plugins/builtin/ai/screener/pane.tsx +3 -1
  142. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -0
  143. package/src/plugins/builtin/ai/screener/runner.ts +7 -3
  144. package/src/plugins/builtin/ai/workspace/pane.tsx +13 -2
  145. package/src/plugins/builtin/alerts/alert-engine.ts +13 -0
  146. package/src/plugins/builtin/alerts/constants.ts +2 -1
  147. package/src/plugins/builtin/alerts/index.tsx +67 -36
  148. package/src/plugins/builtin/alerts/pane.tsx +17 -65
  149. package/src/plugins/builtin/analytics/index.tsx +24 -11
  150. package/src/plugins/builtin/analytics/pane-model.ts +48 -14
  151. package/src/plugins/builtin/analytics/portfolio-selection.ts +19 -0
  152. package/src/plugins/builtin/analytics/view.tsx +5 -3
  153. package/src/plugins/builtin/broker-manager/detail.tsx +7 -2
  154. package/src/plugins/builtin/broker-manager/footer.ts +4 -9
  155. package/src/plugins/builtin/broker-manager/index.tsx +1 -6
  156. package/src/plugins/builtin/buildout/detail/ui.tsx +15 -4
  157. package/src/plugins/builtin/buildout/pane/header.tsx +1 -0
  158. package/src/plugins/builtin/changelog/index.tsx +60 -45
  159. package/src/plugins/builtin/chart-composer/catalog-inventory.test.ts +97 -0
  160. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +366 -0
  161. package/src/plugins/builtin/chart-composer/chart-spec.ts +21 -1
  162. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +307 -0
  163. package/src/plugins/builtin/chart-composer/editor/controller.ts +1 -1
  164. package/src/plugins/builtin/chart-composer/index.tsx +45 -5
  165. package/src/plugins/builtin/chart-composer/pane.tsx +7 -88
  166. package/src/plugins/builtin/chart-composer/presets.test.ts +48 -0
  167. package/src/plugins/builtin/chart-composer/presets.ts +69 -3
  168. package/src/plugins/builtin/chart-composer/quick-add.tsx +8 -5
  169. package/src/plugins/builtin/chart-composer/semantic.ts +18 -14
  170. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +39 -0
  171. package/src/plugins/builtin/chart-composer/series-catalog.ts +104 -3
  172. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +10 -10
  173. package/src/plugins/builtin/chart-composer/shortcuts.ts +4 -12
  174. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +230 -23
  175. package/src/plugins/builtin/chat/chat.test.tsx +22 -0
  176. package/src/plugins/builtin/chat/content/composer.tsx +6 -4
  177. package/src/plugins/builtin/chat/content/index.tsx +7 -0
  178. package/src/plugins/builtin/chat/content/snapshot.ts +9 -2
  179. package/src/plugins/builtin/chat/content/transcript.tsx +10 -0
  180. package/src/plugins/builtin/chat/controller/channel-actions.ts +11 -1
  181. package/src/plugins/builtin/chat/controller/channels.ts +13 -3
  182. package/src/plugins/builtin/chat/controller/index.ts +61 -7
  183. package/src/plugins/builtin/chat/controller/lifecycle.ts +3 -0
  184. package/src/plugins/builtin/chat/controller/message-loading.ts +5 -1
  185. package/src/plugins/builtin/chat/controller/messages.ts +3 -1
  186. package/src/plugins/builtin/chat/controller/notifications.ts +32 -11
  187. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  188. package/src/plugins/builtin/chat/controller/view.ts +1 -0
  189. package/src/plugins/builtin/chat/controller.test.ts +117 -28
  190. package/src/plugins/builtin/cloud/auth-commands.ts +18 -0
  191. package/src/plugins/builtin/cloud/device-signin-dialog.tsx +243 -0
  192. package/src/plugins/builtin/cloud/device-signin.test.ts +217 -0
  193. package/src/plugins/builtin/cloud/device-signin.ts +240 -0
  194. package/src/plugins/builtin/cloud/plugin.tsx +7 -1
  195. package/src/plugins/builtin/cloud-tweets/table.tsx +57 -10
  196. package/src/plugins/builtin/composite-plugins.ts +39 -5
  197. package/src/plugins/builtin/congress-trades/detail.tsx +2 -4
  198. package/src/plugins/builtin/congress-trades/footer.ts +7 -16
  199. package/src/plugins/builtin/congress-trades/pane.tsx +12 -20
  200. package/src/plugins/builtin/connections/index.ts +53 -0
  201. package/src/plugins/builtin/connections/pane.test.tsx +79 -0
  202. package/src/plugins/builtin/connections/pane.tsx +258 -0
  203. package/src/plugins/builtin/correlation/index.test.tsx +2 -1
  204. package/src/plugins/builtin/correlation/index.tsx +58 -19
  205. package/src/plugins/builtin/correlation/matrix/model.ts +18 -7
  206. package/src/plugins/builtin/correlation/relationship/pane.tsx +43 -19
  207. package/src/plugins/builtin/correlation/settings.ts +1 -1
  208. package/src/plugins/builtin/credit-conditions/client.test.ts +80 -0
  209. package/src/plugins/builtin/credit-conditions/client.ts +105 -0
  210. package/src/plugins/builtin/credit-conditions/index.test.tsx +111 -0
  211. package/src/plugins/builtin/credit-conditions/index.tsx +227 -0
  212. package/src/plugins/builtin/credit-conditions/model.test.ts +43 -0
  213. package/src/plugins/builtin/credit-conditions/model.ts +64 -0
  214. package/src/plugins/builtin/debug/index.tsx +11 -6
  215. package/src/plugins/builtin/dividend-yield/client.test.ts +58 -0
  216. package/src/plugins/builtin/dividend-yield/client.ts +252 -0
  217. package/src/plugins/builtin/dividend-yield/index.tsx +61 -0
  218. package/src/plugins/builtin/dividend-yield/model.ts +83 -0
  219. package/src/plugins/builtin/dividend-yield/pane.tsx +305 -0
  220. package/src/plugins/builtin/dividend-yield/types.ts +22 -0
  221. package/src/plugins/builtin/earnings/data/cache.test.ts +10 -7
  222. package/src/plugins/builtin/earnings/data/cache.ts +23 -9
  223. package/src/plugins/builtin/earnings/index.tsx +57 -6
  224. package/src/plugins/builtin/econ/calendar-model.test.ts +37 -3
  225. package/src/plugins/builtin/econ/calendar-model.ts +32 -26
  226. package/src/plugins/builtin/econ/fred-series-map.ts +18 -0
  227. package/src/plugins/builtin/econ/index.test.tsx +105 -0
  228. package/src/plugins/builtin/econ/index.tsx +95 -52
  229. package/src/plugins/builtin/fear-greed/cache.test.ts +18 -3
  230. package/src/plugins/builtin/fear-greed/cache.ts +18 -6
  231. package/src/plugins/builtin/fear-greed/charts.tsx +9 -2
  232. package/src/plugins/builtin/fear-greed/format.ts +0 -9
  233. package/src/plugins/builtin/fear-greed/pane.tsx +21 -21
  234. package/src/plugins/builtin/futures/contracts.ts +100 -0
  235. package/src/plugins/builtin/futures/index.tsx +298 -0
  236. package/src/plugins/builtin/futures/model.test.ts +129 -0
  237. package/src/plugins/builtin/futures/model.ts +136 -0
  238. package/src/plugins/builtin/futures/pane.test.tsx +239 -0
  239. package/src/plugins/builtin/futures/table.test.ts +155 -0
  240. package/src/plugins/builtin/futures/table.tsx +241 -0
  241. package/src/plugins/builtin/fx-matrix/index.tsx +160 -134
  242. package/src/plugins/builtin/fx-matrix/pairs.ts +12 -1
  243. package/src/plugins/builtin/fx-matrix/pane.test.tsx +109 -0
  244. package/src/plugins/builtin/help/components.tsx +1 -30
  245. package/src/plugins/builtin/help/index.tsx +4 -24
  246. package/src/plugins/builtin/holders/pane.tsx +17 -12
  247. package/src/plugins/builtin/holders/treemap.tsx +4 -2
  248. package/src/plugins/builtin/insider/index.tsx +20 -10
  249. package/src/plugins/builtin/ipo-calendar/cache.ts +101 -0
  250. package/src/plugins/builtin/ipo-calendar/client.test.ts +31 -0
  251. package/src/plugins/builtin/ipo-calendar/client.ts +296 -0
  252. package/src/plugins/builtin/ipo-calendar/index.tsx +65 -0
  253. package/src/plugins/builtin/ipo-calendar/model.ts +145 -0
  254. package/src/plugins/builtin/ipo-calendar/pane.tsx +328 -0
  255. package/src/plugins/builtin/ipo-calendar/types.ts +19 -0
  256. package/src/plugins/builtin/kelly-sizer/index.test.tsx +3 -3
  257. package/src/plugins/builtin/kelly-sizer/model.ts +8 -1
  258. package/src/plugins/builtin/kelly-sizer/pane.tsx +33 -15
  259. package/src/plugins/builtin/kelly-sizer/view.tsx +12 -5
  260. package/src/plugins/builtin/market-halts/client.ts +114 -0
  261. package/src/plugins/builtin/market-halts/index.tsx +29 -0
  262. package/src/plugins/builtin/market-halts/model.test.ts +187 -0
  263. package/src/plugins/builtin/market-halts/model.ts +291 -0
  264. package/src/plugins/builtin/market-halts/pane.tsx +256 -0
  265. package/src/plugins/builtin/market-heatmap/data.test.ts +1 -0
  266. package/src/plugins/builtin/market-heatmap/data.ts +12 -3
  267. package/src/plugins/builtin/market-heatmap/index.tsx +131 -46
  268. package/src/plugins/builtin/market-movers/index.tsx +145 -63
  269. package/src/plugins/builtin/market-movers/model.ts +19 -3
  270. package/src/plugins/builtin/market-movers/screener.test.ts +108 -0
  271. package/src/plugins/builtin/market-movers/screener.ts +168 -10
  272. package/src/plugins/builtin/news/index.tsx +78 -99
  273. package/src/plugins/builtin/news/wire/article-search.test.ts +72 -0
  274. package/src/plugins/builtin/news/wire/article-search.ts +140 -0
  275. package/src/plugins/builtin/news/wire/breaking/pane.tsx +18 -8
  276. package/src/plugins/builtin/news/wire/categories.ts +35 -0
  277. package/src/plugins/builtin/news/wire/default-feeds.ts +47 -1
  278. package/src/plugins/builtin/news/wire/feed-config.test.ts +10 -5
  279. package/src/plugins/builtin/news/wire/index.tsx +41 -4
  280. package/src/plugins/builtin/news/wire/industry-pane.tsx +38 -19
  281. package/src/plugins/builtin/news/wire/news/detail-view.tsx +10 -5
  282. package/src/plugins/builtin/news/wire/news/footer.ts +1 -0
  283. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +15 -6
  284. package/src/plugins/builtin/news/wire/news/query-presets.ts +16 -2
  285. package/src/plugins/builtin/news/wire/news/table.test.tsx +54 -0
  286. package/src/plugins/builtin/news/wire/news/table.tsx +111 -17
  287. package/src/plugins/builtin/notes/files.test.ts +18 -0
  288. package/src/plugins/builtin/notes/files.ts +10 -4
  289. package/src/plugins/builtin/notes/index.tsx +4 -1
  290. package/src/plugins/builtin/notes/quick-notes-pane.test.tsx +182 -0
  291. package/src/plugins/builtin/notes/quick-notes-pane.tsx +74 -31
  292. package/src/plugins/builtin/notes/ticker-notes-tab.test.tsx +380 -0
  293. package/src/plugins/builtin/notes/ticker-notes-tab.tsx +97 -40
  294. package/src/plugins/builtin/options/calc-seed.test.ts +118 -0
  295. package/src/plugins/builtin/options/calc-seed.ts +52 -0
  296. package/src/plugins/builtin/options/footer.ts +4 -1
  297. package/src/plugins/builtin/options/index.tsx +22 -1
  298. package/src/plugins/builtin/options/live-quotes.ts +8 -0
  299. package/src/plugins/builtin/options/table.ts +3 -1
  300. package/src/plugins/builtin/options/view.test.tsx +5 -1
  301. package/src/plugins/builtin/options/view.tsx +112 -17
  302. package/src/plugins/builtin/options-calculator/index.tsx +35 -0
  303. package/src/plugins/builtin/options-calculator/model.test.ts +172 -0
  304. package/src/plugins/builtin/options-calculator/model.ts +273 -0
  305. package/src/plugins/builtin/options-calculator/pane.test.tsx +138 -0
  306. package/src/plugins/builtin/options-calculator/pane.tsx +218 -0
  307. package/src/plugins/builtin/plugin-module.test.ts +16 -2
  308. package/src/plugins/builtin/plugin-module.ts +5 -1
  309. package/src/plugins/builtin/polls/client.ts +57 -0
  310. package/src/plugins/builtin/polls/hooks.ts +20 -0
  311. package/src/plugins/builtin/polls/index.tsx +35 -0
  312. package/src/plugins/builtin/polls/normalize.test.ts +230 -0
  313. package/src/plugins/builtin/polls/normalize.ts +319 -0
  314. package/src/plugins/builtin/polls/pane.tsx +722 -0
  315. package/src/plugins/builtin/polls/types.ts +77 -0
  316. package/src/plugins/builtin/portfolio-list/header.tsx +15 -4
  317. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -3
  318. package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +1 -1
  319. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -0
  320. package/src/plugins/builtin/portfolio-list/summary/index.tsx +2 -1
  321. package/src/plugins/builtin/research/analyst-pane.tsx +40 -67
  322. package/src/plugins/builtin/research/corporate-actions-pane.tsx +25 -19
  323. package/src/plugins/builtin/research/index.test.ts +6 -25
  324. package/src/plugins/builtin/research/index.tsx +11 -5
  325. package/src/plugins/builtin/research/relative-valuation-pane.tsx +86 -26
  326. package/src/plugins/builtin/scanner/denied.tsx +31 -0
  327. package/src/plugins/builtin/scanner/feed.ts +94 -0
  328. package/src/plugins/builtin/scanner/flow-model.test.ts +85 -0
  329. package/src/plugins/builtin/scanner/flow-model.ts +133 -0
  330. package/src/plugins/builtin/scanner/flow-pane.tsx +197 -0
  331. package/src/plugins/builtin/scanner/hilo-bars.tsx +121 -0
  332. package/src/plugins/builtin/scanner/hilo-model.test.ts +44 -0
  333. package/src/plugins/builtin/scanner/hilo-model.ts +75 -0
  334. package/src/plugins/builtin/scanner/hilo-pane.tsx +165 -0
  335. package/src/plugins/builtin/scanner/index.ts +109 -0
  336. package/src/plugins/builtin/scanner/waiting.tsx +11 -0
  337. package/src/plugins/builtin/sec/index.tsx +11 -7
  338. package/src/plugins/builtin/sectors/index.tsx +105 -81
  339. package/src/plugins/builtin/sectors/move-bar.tsx +43 -0
  340. package/src/plugins/builtin/sectors/sector-data.ts +18 -0
  341. package/src/plugins/builtin/sectors/sector-model.ts +13 -15
  342. package/src/plugins/builtin/shared/auto-refresh.ts +51 -0
  343. package/src/plugins/builtin/shared/cloud-upgrade.ts +29 -5
  344. package/src/plugins/builtin/shared/pane-footer.ts +16 -3
  345. package/src/plugins/builtin/shared/screener-live-quotes.test.ts +91 -0
  346. package/src/plugins/builtin/shared/screener-live-quotes.ts +134 -0
  347. package/src/plugins/builtin/shared/table-pane.ts +1 -5
  348. package/src/plugins/builtin/shared/use-quote-board.test.tsx +211 -0
  349. package/src/plugins/builtin/shared/use-quote-board.ts +195 -0
  350. package/src/plugins/builtin/short-interest/client.ts +124 -0
  351. package/src/plugins/builtin/short-interest/index.tsx +62 -0
  352. package/src/plugins/builtin/short-interest/model.ts +113 -0
  353. package/src/plugins/builtin/short-interest/pane.tsx +252 -0
  354. package/src/plugins/builtin/short-interest/types.ts +9 -0
  355. package/src/plugins/builtin/substack/article-detail.tsx +15 -1
  356. package/src/plugins/builtin/substack/pane-footer.ts +3 -22
  357. package/src/plugins/builtin/substack/pane.tsx +1 -2
  358. package/src/plugins/builtin/thirteenf/api.ts +4 -1
  359. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  360. package/src/plugins/builtin/thirteenf/pane.tsx +6 -2
  361. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +10 -4
  362. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +67 -11
  363. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +15 -8
  364. package/src/plugins/builtin/ticker-detail/index.tsx +5 -21
  365. package/src/plugins/builtin/ticker-detail/overview/components.tsx +72 -32
  366. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +0 -10
  367. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -1
  368. package/src/plugins/builtin/ticker-detail/quote-monitor/card.test.tsx +70 -0
  369. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +34 -26
  370. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +96 -49
  371. package/src/plugins/builtin/ticker-detail/research-tabs.ts +32 -0
  372. package/src/plugins/builtin/ticker-research-backend-plugin.ts +2 -9
  373. package/src/plugins/builtin/ticker-research-plugin.ts +6 -0
  374. package/src/plugins/builtin/treasury-auctions/cache.test.ts +173 -0
  375. package/src/plugins/builtin/treasury-auctions/cache.ts +104 -0
  376. package/src/plugins/builtin/treasury-auctions/client.test.ts +181 -0
  377. package/src/plugins/builtin/treasury-auctions/client.ts +151 -0
  378. package/src/plugins/builtin/treasury-auctions/index.tsx +90 -0
  379. package/src/plugins/builtin/treasury-auctions/model.test.ts +147 -0
  380. package/src/plugins/builtin/treasury-auctions/model.ts +188 -0
  381. package/src/plugins/builtin/treasury-auctions/pane.test.tsx +170 -0
  382. package/src/plugins/builtin/treasury-auctions/pane.tsx +417 -0
  383. package/src/plugins/builtin/treasury-auctions/types.ts +59 -0
  384. package/src/plugins/builtin/tv/pane.tsx +67 -7
  385. package/src/plugins/builtin/tv/youtube-embed.test.ts +84 -0
  386. package/src/plugins/builtin/tv/youtube-embed.ts +173 -0
  387. package/src/plugins/builtin/tv/youtube-stream.ts +27 -2
  388. package/src/plugins/builtin/volatility/client.ts +87 -0
  389. package/src/plugins/builtin/volatility/index.test.tsx +107 -0
  390. package/src/plugins/builtin/volatility/index.tsx +193 -0
  391. package/src/plugins/builtin/volatility/model.test.ts +66 -0
  392. package/src/plugins/builtin/volatility/model.ts +113 -0
  393. package/src/plugins/builtin/world-indices/footer.ts +43 -23
  394. package/src/plugins/builtin/world-indices/index.tsx +44 -93
  395. package/src/plugins/builtin/world-indices/indices.ts +11 -2
  396. package/src/plugins/builtin/world-indices/model.ts +17 -24
  397. package/src/plugins/builtin/world-indices/pane.test.tsx +116 -0
  398. package/src/plugins/builtin/world-indices/table.tsx +82 -37
  399. package/src/plugins/builtin/yield-curve/index.tsx +16 -7
  400. package/src/plugins/builtin/yield-curve/treasury-data.test.ts +25 -0
  401. package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -0
  402. package/src/plugins/catalog-backend.ts +8 -0
  403. package/src/plugins/catalog-ui.ts +8 -0
  404. package/src/plugins/event-bus.ts +1 -0
  405. package/src/plugins/ibkr/trade/tab/footer.ts +9 -23
  406. package/src/plugins/ibkr/trade/tab/index.tsx +1 -5
  407. package/src/plugins/ibkr/trading/pane-view.tsx +149 -87
  408. package/src/plugins/ibkr/trading/pane.tsx +2 -2
  409. package/src/plugins/ownership.ts +3 -0
  410. package/src/plugins/pane-manager/floating.ts +8 -2
  411. package/src/plugins/pane-manager.test.ts +13 -0
  412. package/src/plugins/prediction-markets/backend-plugin.ts +2 -0
  413. package/src/plugins/prediction-markets/capability.test.ts +281 -0
  414. package/src/plugins/prediction-markets/capability.ts +171 -0
  415. package/src/plugins/prediction-markets/chart.tsx +18 -26
  416. package/src/plugins/prediction-markets/columns.ts +3 -2
  417. package/src/plugins/prediction-markets/controller/catalog.ts +15 -12
  418. package/src/plugins/prediction-markets/controller/detail.ts +4 -0
  419. package/src/plugins/prediction-markets/controller/index.ts +0 -15
  420. package/src/plugins/prediction-markets/detail/book.tsx +3 -23
  421. package/src/plugins/prediction-markets/detail/overview.tsx +0 -18
  422. package/src/plugins/prediction-markets/detail/pane.tsx +36 -12
  423. package/src/plugins/prediction-markets/detail.test.tsx +1 -1
  424. package/src/plugins/prediction-markets/index.tsx +2 -0
  425. package/src/plugins/prediction-markets/metrics.ts +28 -18
  426. package/src/plugins/prediction-markets/navigation.ts +1 -0
  427. package/src/plugins/prediction-markets/pane.test.tsx +1 -1
  428. package/src/plugins/prediction-markets/pane.tsx +0 -1
  429. package/src/plugins/prediction-markets/services/fetch.ts +3 -2
  430. package/src/plugins/prediction-markets/services/kalshi/adapter.ts +82 -23
  431. package/src/plugins/prediction-markets/services/kalshi/normalize.ts +3 -1
  432. package/src/plugins/prediction-markets/services/kalshi/types.ts +14 -0
  433. package/src/plugins/prediction-markets/services/polymarket/adapter.ts +26 -14
  434. package/src/plugins/prediction-markets/services/polymarket/detail.ts +67 -18
  435. package/src/plugins/prediction-markets/test-helpers.tsx +0 -1
  436. package/src/plugins/prediction-markets/types.ts +3 -9
  437. package/src/plugins/registry/context.ts +6 -1
  438. package/src/plugins/registry/index.test.ts +2 -0
  439. package/src/plugins/registry/index.ts +53 -3
  440. package/src/plugins/runtime/context.tsx +4 -2
  441. package/src/plugins/runtime/index.tsx +9 -0
  442. package/src/remote/controller.test.ts +17 -4
  443. package/src/renderers/electrobun/bun/core-capabilities.ts +2 -2
  444. package/src/renderers/electrobun/bun/desktop/capability-bridge.test.ts +64 -0
  445. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +41 -7
  446. package/src/renderers/electrobun/bun/desktop/host-requests.ts +5 -2
  447. package/src/renderers/electrobun/bun/desktop/http-fetch.test.ts +33 -0
  448. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +53 -6
  449. package/src/renderers/electrobun/bun/desktop/initialization.test.ts +27 -0
  450. package/src/renderers/electrobun/bun/desktop/initialization.ts +6 -1
  451. package/src/renderers/electrobun/bun/index.ts +1 -0
  452. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  453. package/src/renderers/electrobun/view/app-services.ts +25 -2
  454. package/src/renderers/electrobun/view/build-assets.ts +5 -0
  455. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +186 -65
  456. package/src/renderers/electrobun/view/cli-pane-shot-health.test.ts +25 -0
  457. package/src/renderers/electrobun/view/cli-pane-shot-health.ts +31 -0
  458. package/src/renderers/electrobun/view/data-table/index.tsx +10 -11
  459. package/src/renderers/electrobun/view/data-table/row.tsx +2 -0
  460. package/src/renderers/electrobun/view/desktop/controls.test.tsx +68 -0
  461. package/src/renderers/electrobun/view/desktop/controls.tsx +25 -1
  462. package/src/renderers/electrobun/view/host/box.test.tsx +80 -0
  463. package/src/renderers/electrobun/view/host/media-surface.tsx +70 -3
  464. package/src/renderers/electrobun/view/host/tabs.tsx +97 -5
  465. package/src/renderers/electrobun/view/host/text.tsx +5 -2
  466. package/src/renderers/electrobun/view/input-host.test.tsx +140 -0
  467. package/src/renderers/electrobun/view/input-host.tsx +4 -2
  468. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +19 -1
  469. package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +7 -0
  470. package/src/renderers/electrobun/view/native-stubs/chart/kitty-support.ts +10 -0
  471. package/src/renderers/electrobun/view/remote/capability-invoker.test.ts +46 -0
  472. package/src/renderers/electrobun/view/remote/capability-invoker.ts +34 -13
  473. package/src/renderers/electrobun/view/remote/connection-health-backend.ts +19 -0
  474. package/src/renderers/electrobun/view/remote/connection-health.test.ts +44 -0
  475. package/src/renderers/electrobun/view/remote/connection-health.ts +49 -0
  476. package/src/renderers/electrobun/view/styles.css +5 -0
  477. package/src/renderers/opentui/host.tsx +15 -1
  478. package/src/renderers/opentui/terminal-media.test.ts +102 -0
  479. package/src/renderers/opentui/terminal-media.ts +142 -0
  480. package/src/sources/provider-router/connection-health.test.ts +28 -0
  481. package/src/sources/provider-router/connection-health.ts +47 -0
  482. package/src/sources/provider-router/history.test.ts +53 -4
  483. package/src/sources/provider-router/history.ts +119 -14
  484. package/src/sources/provider-router/index.ts +12 -1
  485. package/src/sources/yahoo-finance/history.ts +2 -9
  486. package/src/sources/yahoo-finance/http.ts +1 -1
  487. package/src/sources/yahoo-finance/symbols.ts +1 -1
  488. package/src/sources/yahoo-finance/types.ts +19 -0
  489. package/src/state/app/context/index.test.tsx +101 -0
  490. package/src/state/app/context/index.tsx +10 -0
  491. package/src/state/config-save-scheduler.ts +1 -6
  492. package/src/state/persist-scheduler.test.ts +31 -0
  493. package/src/state/persist-scheduler.ts +21 -8
  494. package/src/sync/auth-transition.test.ts +36 -0
  495. package/src/sync/auth-transition.ts +22 -0
  496. package/src/sync/core-contributors.test.ts +58 -1
  497. package/src/sync/core-contributors.ts +39 -2
  498. package/src/sync/react.ts +9 -0
  499. package/src/test-support/plugin-runtime.ts +3 -0
  500. package/src/theme/font-scale.test.ts +14 -0
  501. package/src/theme/font-scale.ts +69 -0
  502. package/src/time-series/alignment.ts +15 -11
  503. package/src/time-series/field-catalog.ts +10 -0
  504. package/src/time-series/fundamentals.ts +19 -11
  505. package/src/time-series/hooks.ts +8 -2
  506. package/src/time-series/parsed-history-cache.ts +35 -0
  507. package/src/time-series/resolution.ts +10 -0
  508. package/src/time-series/resolve.test.ts +332 -3
  509. package/src/time-series/resolve.ts +225 -25
  510. package/src/time-series/spec-studies.test.ts +23 -0
  511. package/src/time-series/spec.ts +21 -2
  512. package/src/time-series/transforms.test.ts +76 -1
  513. package/src/time-series/transforms.ts +39 -8
  514. package/src/time-series/types.ts +9 -2
  515. package/src/time-series/use-chart-resolution.test.tsx +105 -1
  516. package/src/time-series/use-chart-resolution.ts +67 -13
  517. package/src/types/config.ts +30 -27
  518. package/src/types/media.ts +2 -0
  519. package/src/types/plugin.ts +8 -1
  520. package/src/ui/host.tsx +4 -0
  521. package/src/ui/pointer-drag.ts +12 -0
  522. package/src/ui/qr.test.ts +79 -0
  523. package/src/ui/qr.ts +82 -0
  524. package/src/updater.test.ts +117 -16
  525. package/src/updater.ts +32 -5
  526. package/src/utils/exchanges.ts +1 -0
  527. package/src/utils/external-url.test.ts +32 -0
  528. package/src/utils/external-url.ts +23 -0
  529. package/src/utils/sort-values.ts +31 -0
  530. package/src/utils/zoned-date-time.ts +67 -0
  531. package/src/components/onboarding/wizard-keyboard.ts +0 -306
  532. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +0 -120
package/README.md CHANGED
@@ -106,6 +106,7 @@ Open command mode with `Ctrl+P`, then type a command. Press `` ` `` to open tick
106
106
  | `TOP` | Ranked market stories |
107
107
  | `HM` | Market heatmap |
108
108
  | `MOST` | Market movers |
109
+ | `HILO` | New highs and new lows |
109
110
  | `PF` | Portfolios and watchlists |
110
111
  | `KELLY AAPL` | Position sizing |
111
112
  | `HELP` | Full in-app shortcut list |
@@ -113,9 +114,19 @@ Open command mode with `Ctrl+P`, then type a command. Press `` ` `` to open tick
113
114
  ## What It Does
114
115
 
115
116
  - Research companies with quotes, charts, financials, filings, holders, insiders, options, analyst ratings, events, and relative valuation.
116
- - Follow markets with top stories, breaking news, sector feeds, Substack subscriptions, global indices, FX, macro events, yield curves, market movers, and fear/greed.
117
+ - Follow markets with top stories, breaking news, sector feeds, Substack subscriptions, global indices, futures, FX, macro events, yield curves, Treasury auctions, market movers, new-high/new-low and options-flow scanners, and fear/greed.
117
118
  - Track portfolios and watchlists, connect brokers, set alerts, keep notes, run AI screens, browse prediction markets, and use Gloom Cloud chat.
118
119
 
120
+ ### Broker position sync
121
+
122
+ Use **New Portfolio** or **Add Broker Account** to connect a broker. Gloomberb can import positions from Interactive Brokers, Public, Robinhood, and SimpleFIN.
123
+
124
+ - Robinhood opens a browser sign-in page. Gloomberb uses only the read-only account and equity-position tools from the Robinhood Trading MCP server.
125
+ - Public needs an API secret from Public API settings. Gloomberb creates a short-lived access token and uses only the account and portfolio endpoints.
126
+ - SimpleFIN needs a one-time setup token from SimpleFIN Bridge. Gloomberb exchanges the token and imports only accounts that contain holdings.
127
+
128
+ Gloomberb saves the connection data on the local device. It does not include this data in Gloom Cloud synchronization. A later position sync updates the managed portfolios and removes positions that the broker no longer reports.
129
+
119
130
  ## CLI
120
131
 
121
132
  Running `gloomberb` with no arguments launches the terminal UI. Normal commands run through a headless CLI path; use `gloomberb launch-ui` when a script should explicitly open the UI.
@@ -199,6 +210,7 @@ Use `HELP` inside Gloomberb for the live shortcut list. The common command-bar p
199
210
  | `DES <ticker>` / `T <ticker>` | Security details for a ticker |
200
211
  | `FA <ticker>` | Financial statement view |
201
212
  | `G <series>` | Custom chart composer |
213
+ | `CAT [query]` | Browse and search chartable series |
202
214
  | `GP <ticker>` | Price chart |
203
215
  | `GIP <ticker>` | Intraday price chart |
204
216
  | `HP <ticker>` | Historical OHLCV prices |
@@ -214,7 +226,10 @@ Use `HELP` inside Gloomberb for the live shortcut list. The common command-bar p
214
226
  | `ANR <ticker>` | Analyst targets and ratings |
215
227
  | `SEC <ticker>` | SEC filings and company disclosures |
216
228
  | `OMON <ticker>` | Options monitor |
229
+ | `OVME` | Black-Scholes option calculator with Greeks and implied volatility |
217
230
  | `HDS <ticker>` | Institutional holders |
231
+ | `DVD <ticker>` | Dividend yield and history |
232
+ | `SI <ticker>` | Short interest |
218
233
  | `13F [fund/ticker/CIK]` | 13F fund filings and holdings |
219
234
  | `INS <ticker>` | Insider activity |
220
235
  | `EVT <ticker>` | Corporate actions, earnings, and estimates |
@@ -222,7 +237,7 @@ Use `HELP` inside Gloomberb for the live shortcut list. The common command-bar p
222
237
 
223
238
  ### Chart Composer
224
239
 
225
- `G`, `GP`, `GIP`, `CMP`, `GF`, and `GE` all open the same chart composer with different starting presets. A custom expression can mix unrelated data sources on one synchronized timeline:
240
+ `G`, `GP`, `GIP`, `CMP`, `GF`, and `GE` all open the same chart composer with different starting presets. `CAT` opens a searchable catalog of those chartable series so you can graph one without typing the expression. A custom expression can mix unrelated data sources on one synchronized timeline:
226
241
 
227
242
  ```text
228
243
  G AAPL:price, MSFT:revenue, FRED:CPIAUCSL
@@ -239,6 +254,8 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
239
254
  | `TOP` | Ranked market stories |
240
255
  | `HM` | Market heatmap for large US stocks and ETFs |
241
256
  | `MOST` | Top gainers, losers, most active, and trending tickers |
257
+ | `HILO` | Session new highs and new lows with 30s/1m/5m momentum |
258
+ | `FLOW` | Unusual options activity: sweeps, blocks, and large premium |
242
259
  | `PM <query>` | Polymarket and Kalshi prediction data |
243
260
  | `N` | News feed |
244
261
  | `CN <ticker>` | Ticker news |
@@ -249,9 +266,15 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
249
266
  | `TBO` | TheBuildout infrastructure intelligence |
250
267
  | `CG` | Congress trading disclosures |
251
268
  | `WEI` | Global equity indices |
269
+ | `FUT` | Front-month futures across index, rates, energy, metals, grains, and FX |
252
270
  | `ECON` | Economic events and releases |
253
271
  | `GC` | Yield curve |
272
+ | `AUCT` | Treasury auction results: high rate, bid-to-cover, indirect share, and size |
273
+ | `VIX` | VIX 30-day/3-month implied-volatility curve |
274
+ | `CRD` | Credit spreads |
254
275
  | `ERN` | Earnings calendar |
276
+ | `IPO` | Upcoming and recent IPOs |
277
+ | `HALT` | US trading halts with reason and resumption times |
255
278
  | `TV` | Live Bloomberg, CNBC, and Yahoo Finance television |
256
279
  | `BI` / `SP` | S&P 500 sector performance |
257
280
  | `FXC` | Major FX cross rates |
@@ -288,6 +311,10 @@ The toolbar controls preset or exact date ranges, intervals from one minute thro
288
311
  | `LANG <locale>` | Change interface language (`auto`, `en`, `es`, `zh-CN`, `zh-TW`, `ja`, or `ko`) |
289
312
  | `PL <plugin>` | Manage plugins |
290
313
 
314
+ ## Gloom Cloud sign-in
315
+
316
+ Sign in with email and password, or pick `Log In with QR Code` from the command bar and scan the code with the Gloomberb mobile companion app to sign the terminal in without typing. The onboarding wizard offers the same QR option as the recommended path, with email and password as the alternative.
317
+
291
318
  ## Localized interface
292
319
 
293
320
  Gloomberb includes English, Spanish, Simplified Chinese, Traditional Chinese, Japanese, and Korean UI support. English remains the default fallback language.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gloomberb",
3
- "version": "0.10.5",
3
+ "version": "0.11.0",
4
4
  "packageManager": "bun@1.3.11",
5
5
  "description": "Market research and portfolio tracker for the terminal",
6
6
  "module": "src/index.tsx",
@@ -60,12 +60,14 @@
60
60
  "dependencies": {
61
61
  "@earendil-works/pi-agent-core": "0.81.1",
62
62
  "@earendil-works/pi-ai": "0.81.1",
63
+ "@modelcontextprotocol/sdk": "^1.30.0",
63
64
  "@opentui/core": "^0.3.2",
64
65
  "@opentui/react": "^0.3.2",
65
66
  "@stoqey/ib": "^1.5.6",
66
67
  "@tanstack/react-virtual": "^3.14.2",
67
68
  "hls.js": "1.6.13",
68
69
  "jimp": "1.6.1",
70
+ "qrcode-generator": "^2.0.4",
69
71
  "react": "19.2.7",
70
72
  "react-dom": "19.2.7",
71
73
  "rxjs": "^7.8.2",
@@ -5,8 +5,11 @@ import type {
5
5
  AuthUser,
6
6
  BuildoutAccountResponse,
7
7
  BuildoutTokenResponse,
8
+ CloudBrowserHandoffResponse,
8
9
  CloudPricing,
9
10
  CloudVerificationResponse,
11
+ DeviceAuthStartResponse,
12
+ DeviceAuthTokenResponse,
10
13
  PersistedAuthUser,
11
14
  } from "./types";
12
15
 
@@ -86,6 +89,26 @@ export class CloudAuthApi {
86
89
  return result.user;
87
90
  }
88
91
 
92
+ /** Starts a QR / device sign-in; the mobile app approves the returned user code. */
93
+ async startDeviceSignIn(body: { clientName?: string; clientPlatform?: string }): Promise<DeviceAuthStartResponse> {
94
+ return this.options.request<DeviceAuthStartResponse>("/auth/device/start", {
95
+ method: "POST",
96
+ body: JSON.stringify(body),
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Polls a pending device sign-in. Unlike the email flow, approval hands back
102
+ * a raw session token in the body instead of a Set-Cookie header; the caller
103
+ * installs it through the same path boot restoration uses.
104
+ */
105
+ async pollDeviceSignIn(deviceCode: string): Promise<DeviceAuthTokenResponse> {
106
+ return this.options.request<DeviceAuthTokenResponse>("/auth/device/token", {
107
+ method: "POST",
108
+ body: JSON.stringify({ deviceCode }),
109
+ });
110
+ }
111
+
89
112
  async signOut(): Promise<void> {
90
113
  try {
91
114
  await this.options.request("/auth/sign-out", { method: "POST" });
@@ -118,6 +141,17 @@ export class CloudAuthApi {
118
141
  });
119
142
  }
120
143
 
144
+ /**
145
+ * Creates a short-lived browser handoff for the captured native session.
146
+ * The server deliberately returns an opaque one-time URL, never the session cookie.
147
+ */
148
+ async createBrowserHandoff(): Promise<CloudBrowserHandoffResponse> {
149
+ return this.options.request<CloudBrowserHandoffResponse>("/cloud/auth/browser-handoff", {
150
+ method: "POST",
151
+ body: JSON.stringify({}),
152
+ });
153
+ }
154
+
121
155
  async getAccountProfile(): Promise<AccountProfile> {
122
156
  const result = await this.options.request<{ profile: AccountProfile }>("/account/profile", {
123
157
  method: "GET",
@@ -22,6 +22,7 @@ import {
22
22
  } from "./paths";
23
23
  import type {
24
24
  CloudAnalystResearchPayload,
25
+ CloudShortInterestPayload,
25
26
  CloudCompanyProfile,
26
27
  CloudCongressHousePayload,
27
28
  CloudCorporateActionsPayload,
@@ -32,6 +33,8 @@ import type {
32
33
  CloudMarketBatchPayload,
33
34
  CloudMarketBatchTarget,
34
35
  CloudMarketResponse,
36
+ CloudMarketScreenerCategory,
37
+ CloudMarketScreenerPayload,
35
38
  CloudNewsListResponse,
36
39
  CloudNewsPayload,
37
40
  CloudOptionsChainPayload,
@@ -77,6 +80,22 @@ export class CloudDataApi {
77
80
  return this.postMarketBatch("/market/quotes/batch", targets, mode);
78
81
  }
79
82
 
83
+ async getCloudMarketScreener(
84
+ category: CloudMarketScreenerCategory,
85
+ count = 25,
86
+ mode: "cache-first" | "refresh" = "cache-first",
87
+ ): Promise<CloudMarketResponse<CloudMarketScreenerPayload>> {
88
+ const requestedCount = Number.isFinite(count) ? Math.round(count) : 25;
89
+ const params = new URLSearchParams({
90
+ category,
91
+ count: String(Math.max(1, Math.min(50, requestedCount))),
92
+ mode,
93
+ });
94
+ return this.request<CloudMarketResponse<CloudMarketScreenerPayload>>(
95
+ `/market/screener?${params.toString()}`,
96
+ );
97
+ }
98
+
80
99
  async getCloudOptionsChain(
81
100
  symbol: string,
82
101
  exchange?: string,
@@ -112,6 +131,12 @@ export class CloudDataApi {
112
131
  return this.requestMarketSymbol("/market/analyst", symbol, exchange);
113
132
  }
114
133
 
134
+ async getCloudShortInterest(symbol: string, years?: number): Promise<CloudMarketResponse<CloudShortInterestPayload>> {
135
+ const params = new URLSearchParams({ symbol: symbol.toUpperCase() });
136
+ if (years != null) params.set("years", String(years));
137
+ return this.request<CloudMarketResponse<CloudShortInterestPayload>>(`/market/short-interest?${params}`);
138
+ }
139
+
115
140
  async getCloudCorporateActions(symbol: string, exchange?: string): Promise<CloudMarketResponse<CloudCorporateActionsPayload>> {
116
141
  return this.requestMarketSymbol("/market/corporate-actions", symbol, exchange);
117
142
  }
@@ -185,6 +185,28 @@ describe("apiClient auth cookies", () => {
185
185
  ]);
186
186
  });
187
187
 
188
+ test("creates a browser handoff with the captured session instead of exposing it in the URL", async () => {
189
+ let requestedUrl = "";
190
+ let requestedCookie: string | null = null;
191
+ apiClient.setSessionToken("desktop-session-token");
192
+ globalThis.fetch = mockFetch(async (input: Request | string | URL, init?: RequestInit) => {
193
+ requestedUrl = String(input);
194
+ requestedCookie = new Headers(init?.headers).get("Cookie");
195
+ return createResponse({
196
+ url: "https://api.gloom.sh/cloud/auth/browser-handoff?token=opaque-one-time-token",
197
+ });
198
+ });
199
+
200
+ const handoff = await apiClient.createBrowserHandoff();
201
+
202
+ expect(new URL(requestedUrl).pathname).toBe("/cloud/auth/browser-handoff");
203
+ expect(requestedCookie).toBe(
204
+ "__Secure-gloomberb.session_token=desktop-session-token; gloomberb.session_token=desktop-session-token",
205
+ );
206
+ expect(handoff.url).toContain("token=opaque-one-time-token");
207
+ expect(handoff.url).not.toContain("desktop-session-token");
208
+ });
209
+
188
210
  test("keeps cached identity when session refresh is rejected without a hard account-missing response", async () => {
189
211
  apiClient.setSessionToken("persisted-token.value");
190
212
  apiClient.restoreCachedUser(verifiedUser);
@@ -583,6 +605,65 @@ describe("apiClient quote socket", () => {
583
605
  });
584
606
  });
585
607
 
608
+ describe("apiClient scanner subscriptions", () => {
609
+ test("subscribes once for many panes, fans out, replays the snapshot, and unsubscribes last", () => {
610
+ const sockets = installTestWebSocket();
611
+ const first: unknown[] = [];
612
+ const second: unknown[] = [];
613
+
614
+ const unsubscribeFirst = apiClient.subscribeScanner("hilo", (event) => first.push(event));
615
+ const socket = sockets[0]!;
616
+ socket.open();
617
+ expect(socket.sent).toContainEqual({ type: "scanner.subscribe", scanner: "hilo" });
618
+
619
+ const payload = {
620
+ status: "live",
621
+ asOf: 1,
622
+ windows: { s30: { highs: 1, lows: 0 }, m1: { highs: 2, lows: 1 }, m5: { highs: 3, lows: 2 } },
623
+ highs: [],
624
+ lows: [],
625
+ };
626
+ socket.receive({ type: "scanner.hilo", ...payload });
627
+
628
+ // A second pane must not open a second upstream subscription, and must not
629
+ // wait a tick for its first frame.
630
+ const subscribeCount = () => socket.sent.filter((message: any) => message.type === "scanner.subscribe").length;
631
+ const before = subscribeCount();
632
+ const unsubscribeSecond = apiClient.subscribeScanner("hilo", (event) => second.push(event));
633
+ expect(subscribeCount()).toBe(before);
634
+ expect(second).toEqual([{ type: "data", payload }]);
635
+
636
+ socket.receive({ type: "scanner.hilo", ...payload, asOf: 2 });
637
+ expect(first).toHaveLength(2);
638
+ expect(second).toHaveLength(2);
639
+
640
+ unsubscribeFirst();
641
+ expect(socket.sent).not.toContainEqual({ type: "scanner.unsubscribe", scanner: "hilo" });
642
+ unsubscribeSecond();
643
+ expect(socket.sent).toContainEqual({ type: "scanner.unsubscribe", scanner: "hilo" });
644
+ });
645
+
646
+ test("replays scanner subscriptions after a reconnect and surfaces denials", () => {
647
+ const sockets = installTestWebSocket();
648
+ const seen: unknown[] = [];
649
+ const unsubscribe = apiClient.subscribeScanner("flow", (event) => seen.push(event));
650
+
651
+ const socket = sockets[0]!;
652
+ socket.open();
653
+ socket.receive({ type: "scanner.denied", scanner: "flow", reason: "pro_required" });
654
+ expect(seen).toEqual([{ type: "denied", reason: "pro_required" }]);
655
+
656
+ jest.useFakeTimers();
657
+ socket.closeWith({ code: 1006, reason: "network" });
658
+ jest.runAllTimers();
659
+ const reconnected = sockets[1]!;
660
+ reconnected.open();
661
+ expect(reconnected.sent).toContainEqual({ type: "scanner.subscribe", scanner: "flow" });
662
+
663
+ unsubscribe();
664
+ });
665
+ });
666
+
586
667
  describe("apiClient chat timestamps", () => {
587
668
  test("sends the before cursor when loading older chat messages", async () => {
588
669
  let requestedUrl = "";
@@ -35,6 +35,8 @@ import type {
35
35
  CloudFundamentals,
36
36
  CloudHoldersPayload,
37
37
  CloudAnalystResearchPayload,
38
+ CloudShortInterestPayload,
39
+ CloudBrowserHandoffResponse,
38
40
  CloudCorporateActionsPayload,
39
41
  CloudPricePointPayload,
40
42
  CloudEconEventPayload,
@@ -47,11 +49,17 @@ import type {
47
49
  CloudMarketResponse,
48
50
  CloudMarketBatchTarget,
49
51
  CloudMarketBatchPayload,
52
+ CloudMarketScreenerCategory,
53
+ CloudMarketScreenerPayload,
50
54
  CloudVerificationResponse,
55
+ DeviceAuthStartResponse,
56
+ DeviceAuthTokenResponse,
51
57
  CloudRoundupPreviewResponse,
52
58
  CloudSyncPushResponse,
53
59
  CloudSyncSnapshotResponse,
54
60
  QuoteStreamTarget,
61
+ ScannerFeedEvent,
62
+ ScannerKind,
55
63
  } from "./types";
56
64
  import type { SyncSettings, SyncSnapshot } from "../sync/types";
57
65
 
@@ -205,6 +213,14 @@ class GloomApiClient {
205
213
  return this.auth.signIn(email, password);
206
214
  }
207
215
 
216
+ async startDeviceSignIn(body: { clientName?: string; clientPlatform?: string }): Promise<DeviceAuthStartResponse> {
217
+ return this.auth.startDeviceSignIn(body);
218
+ }
219
+
220
+ async pollDeviceSignIn(deviceCode: string): Promise<DeviceAuthTokenResponse> {
221
+ return this.auth.pollDeviceSignIn(deviceCode);
222
+ }
223
+
208
224
  async signOut(): Promise<void> {
209
225
  return this.auth.signOut();
210
226
  }
@@ -217,6 +233,10 @@ class GloomApiClient {
217
233
  return this.auth.sendVerification();
218
234
  }
219
235
 
236
+ async createBrowserHandoff(): Promise<CloudBrowserHandoffResponse> {
237
+ return this.auth.createBrowserHandoff();
238
+ }
239
+
220
240
  async getAccountProfile(): Promise<AccountProfile> {
221
241
  return this.auth.getAccountProfile();
222
242
  }
@@ -394,6 +414,11 @@ class GloomApiClient {
394
414
  return this.socket.subscribeQuotes(targets, onQuote);
395
415
  }
396
416
 
417
+ /** Subscribes to a shared scanner feed; all panes of one kind share one upstream subscription. */
418
+ subscribeScanner(scanner: ScannerKind, listener: (event: ScannerFeedEvent) => void): () => void {
419
+ return this.socket.subscribeScanner(scanner, listener);
420
+ }
421
+
397
422
  dispose(): void {
398
423
  this.socket.dispose();
399
424
  }
@@ -413,6 +438,14 @@ class GloomApiClient {
413
438
  return this.data.getCloudQuotesBatch(targets, mode);
414
439
  }
415
440
 
441
+ async getCloudMarketScreener(
442
+ category: CloudMarketScreenerCategory,
443
+ count = 25,
444
+ mode: "cache-first" | "refresh" = "cache-first",
445
+ ): Promise<CloudMarketResponse<CloudMarketScreenerPayload>> {
446
+ return this.data.getCloudMarketScreener(category, count, mode);
447
+ }
448
+
416
449
  async getCloudOptionsChain(
417
450
  symbol: string,
418
451
  exchange?: string,
@@ -444,6 +477,10 @@ class GloomApiClient {
444
477
  return this.data.getCloudHolders(symbol, exchange);
445
478
  }
446
479
 
480
+ async getCloudShortInterest(symbol: string, years?: number): Promise<CloudMarketResponse<CloudShortInterestPayload>> {
481
+ return this.data.getCloudShortInterest(symbol, years);
482
+ }
483
+
447
484
  async getCloudAnalystResearch(symbol: string, exchange?: string): Promise<CloudMarketResponse<CloudAnalystResearchPayload>> {
448
485
  return this.data.getCloudAnalystResearch(symbol, exchange);
449
486
  }
@@ -1,4 +1,8 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import {
3
+ ConnectionHealthRegistry,
4
+ registerGloomCloudConnectionSources,
5
+ } from "../core/connection-health";
2
6
  import { CloudApiRequestTransport } from "./request";
3
7
 
4
8
  function responseWithBody(body: () => Promise<string>): Response {
@@ -10,6 +14,32 @@ function responseWithBody(body: () => Promise<string>): Response {
10
14
  } as Response;
11
15
  }
12
16
 
17
+ describe("CloudApiRequestTransport connection reporting", () => {
18
+ test("reports real FRED requests to both Gloom and FRED sources", async () => {
19
+ const health = new ConnectionHealthRegistry();
20
+ const dispose = registerGloomCloudConnectionSources(health);
21
+ const transport = new CloudApiRequestTransport({
22
+ connectionHealth: health,
23
+ fetchTransport: async () => responseWithBody(async () => JSON.stringify({ observations: [], info: null })),
24
+ });
25
+
26
+ await transport.request("/cloud/econ/series/VIXCLS?limit=120&sortOrder=desc");
27
+
28
+ expect(health.getSnapshot().sources.map((source) => ({
29
+ id: source.id,
30
+ status: source.status,
31
+ operation: source.lastOperation,
32
+ }))).toEqual([
33
+ { id: "gloom-cloud-http", status: "connected", operation: "GET /cloud/econ/series/VIXCLS" },
34
+ { id: "gloom-cloud-socket", status: "idle", operation: null },
35
+ { id: "gloom-cloud-fred", status: "connected", operation: "GET /cloud/econ/series/VIXCLS" },
36
+ ]);
37
+
38
+ dispose();
39
+ expect(health.getSnapshot().sources).toEqual([]);
40
+ });
41
+ });
42
+
13
43
  describe("CloudApiRequestTransport market deadlines", () => {
14
44
  test("aborts when response headers never arrive", async () => {
15
45
  let signal: AbortSignal | null | undefined;
@@ -1,6 +1,12 @@
1
1
  import { httpFetch } from "../utils/http-transport";
2
2
  import { withDeadline } from "../utils/async-deadline";
3
3
  import { ApiRequestError, parseApiErrorMessage } from "./errors";
4
+ import {
5
+ connectionHealth,
6
+ GLOOM_CLOUD_FRED_CONNECTION_ID,
7
+ GLOOM_CLOUD_HTTP_CONNECTION_ID,
8
+ type ConnectionHealthRegistry,
9
+ } from "../core/connection-health";
4
10
 
5
11
  const DEFAULT_API_URL = "https://api.gloom.sh";
6
12
  const DEFAULT_MARKET_REQUEST_TIMEOUT_MS = 10_000;
@@ -16,7 +22,12 @@ export function setCloudApiFetchTransport(transport: CloudApiFetchTransport | nu
16
22
  cloudApiFetchTransport = transport ?? httpFetch;
17
23
  }
18
24
 
25
+ declare const __GLOOMBERB_API_URL__: string | undefined;
26
+
19
27
  function getCloudApiBaseUrl(): string {
28
+ // Browser bundles have no `process`; the build replaces this with a literal.
29
+ const bundled = typeof __GLOOMBERB_API_URL__ === "string" ? __GLOOMBERB_API_URL__ : "";
30
+ if (bundled) return bundled;
20
31
  if (typeof process === "undefined") {
21
32
  return DEFAULT_API_URL;
22
33
  }
@@ -34,15 +45,18 @@ export class CloudApiRequestTransport {
34
45
  private websocketToken: string | null = null;
35
46
  private readonly fetchTransport: CloudApiFetchTransport | null;
36
47
  private readonly marketRequestTimeoutMs: number;
48
+ private readonly connectionHealth: ConnectionHealthRegistry;
37
49
 
38
50
  readonly baseUrl = getCloudApiBaseUrl();
39
51
 
40
52
  constructor(options: {
41
53
  fetchTransport?: CloudApiFetchTransport;
42
54
  marketRequestTimeoutMs?: number;
55
+ connectionHealth?: ConnectionHealthRegistry;
43
56
  } = {}) {
44
57
  this.fetchTransport = options.fetchTransport ?? null;
45
58
  this.marketRequestTimeoutMs = options.marketRequestTimeoutMs ?? DEFAULT_MARKET_REQUEST_TIMEOUT_MS;
59
+ this.connectionHealth = options.connectionHealth ?? connectionHealth;
46
60
  }
47
61
 
48
62
  getSessionToken(): string | null {
@@ -116,27 +130,37 @@ export class CloudApiRequestTransport {
116
130
  this.setSessionCookieHeader(headers);
117
131
  headers.set("Origin", this.baseUrl);
118
132
 
119
- const res = await (this.fetchTransport ?? cloudApiFetchTransport)(`${this.baseUrl}${path}`, {
120
- ...options,
121
- headers,
122
- credentials: "include",
123
- });
124
- throwIfRequestAborted(options?.signal);
125
- this.extractSessionCookie(res);
126
- const text = await res.text();
127
- throwIfRequestAborted(options?.signal);
128
-
129
- if (!res.ok) {
130
- const msg = parseApiErrorMessage(text);
131
- throw new ApiRequestError(msg, res.status);
132
- }
133
+ const operation = `${options?.method ?? "GET"} ${path.split("?")[0]}`;
134
+ const request = async () => {
135
+ const res = await (this.fetchTransport ?? cloudApiFetchTransport)(`${this.baseUrl}${path}`, {
136
+ ...options,
137
+ headers,
138
+ credentials: "include",
139
+ });
140
+ throwIfRequestAborted(options?.signal);
141
+ this.extractSessionCookie(res);
142
+ const text = await res.text();
143
+ throwIfRequestAborted(options?.signal);
144
+
145
+ if (!res.ok) {
146
+ const msg = parseApiErrorMessage(text);
147
+ throw new ApiRequestError(msg, res.status);
148
+ }
133
149
 
134
- if (!text) return undefined as T;
135
- const parsed = JSON.parse(text) as T & { token?: string };
136
- if (typeof parsed?.token === "string" && parsed.token.length > 0) {
137
- this.websocketToken = parsed.token;
138
- }
139
- return parsed as T;
150
+ if (!text) return undefined as T;
151
+ const parsed = JSON.parse(text) as T & { token?: string };
152
+ if (typeof parsed?.token === "string" && parsed.token.length > 0) {
153
+ this.websocketToken = parsed.token;
154
+ }
155
+ return parsed as T;
156
+ };
157
+ return this.connectionHealth.track(
158
+ GLOOM_CLOUD_HTTP_CONNECTION_ID,
159
+ operation,
160
+ () => path.startsWith("/cloud/econ/series/")
161
+ ? this.connectionHealth.track(GLOOM_CLOUD_FRED_CONNECTION_ID, operation, request)
162
+ : request(),
163
+ );
140
164
  }
141
165
 
142
166
  private extractSessionCookie(res: CloudApiResponse): void {
@@ -0,0 +1,79 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ ConnectionHealthRegistry,
4
+ GLOOM_CLOUD_SOCKET_CONNECTION_ID,
5
+ type ConnectionHealthStatus,
6
+ } from "../core/connection-health";
7
+ import { CloudApiSocket } from "./socket";
8
+
9
+ function waitForStatus(
10
+ health: ConnectionHealthRegistry,
11
+ status: ConnectionHealthStatus,
12
+ ): Promise<void> {
13
+ if (health.getSnapshot().sources[0]?.status === status) return Promise.resolve();
14
+ return new Promise((resolve, reject) => {
15
+ const timer = setTimeout(() => {
16
+ unsubscribe();
17
+ reject(new Error(`Timed out waiting for ${status}`));
18
+ }, 2_000);
19
+ const unsubscribe = health.subscribe(() => {
20
+ if (health.getSnapshot().sources[0]?.status !== status) return;
21
+ clearTimeout(timer);
22
+ unsubscribe();
23
+ resolve();
24
+ });
25
+ });
26
+ }
27
+
28
+ describe("CloudApiSocket connection health", () => {
29
+ test("reports transitions from a real loopback websocket", async () => {
30
+ let peer: ServerWebSocket<unknown> | null = null;
31
+ const server = Bun.serve({
32
+ hostname: "127.0.0.1",
33
+ port: 0,
34
+ fetch(request, server) {
35
+ return server.upgrade(request) ? undefined : new Response("Upgrade required", { status: 426 });
36
+ },
37
+ websocket: {
38
+ open(socket) {
39
+ peer = socket;
40
+ },
41
+ message() {},
42
+ },
43
+ });
44
+ const health = new ConnectionHealthRegistry();
45
+ health.registerSource({
46
+ id: GLOOM_CLOUD_SOCKET_CONNECTION_ID,
47
+ name: "Gloom Cloud Stream",
48
+ kind: "websocket",
49
+ });
50
+ const socket = new CloudApiSocket({
51
+ getBaseUrl: () => `http://127.0.0.1:${server.port}`,
52
+ getSocketAuthToken: () => null,
53
+ hasVerifiedUser: () => false,
54
+ isUsingWebSocketToken: () => false,
55
+ clearWebSocketTokenForFallback: () => false,
56
+ markCurrentUserUnverified: () => {},
57
+ updateCurrentUserFromSocket: () => {},
58
+ }, health);
59
+
60
+ try {
61
+ socket.subscribeQuotes([{ symbol: "AAPL" }], () => {});
62
+ expect(health.getSnapshot().sources[0]).toMatchObject({
63
+ status: "connecting",
64
+ socketState: "connecting",
65
+ });
66
+
67
+ await waitForStatus(health, "connected");
68
+ peer!.close(1000, "network lost");
69
+ await waitForStatus(health, "disconnected");
70
+ expect(health.getSnapshot().sources[0]).toMatchObject({
71
+ socketState: "closed",
72
+ currentDetail: "network lost",
73
+ });
74
+ } finally {
75
+ socket.dispose();
76
+ void server.stop(true);
77
+ }
78
+ });
79
+ });