gloomberb 0.16.3 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/signed-in/adapter.ts +33 -2
  30. package/src/brokers/signed-in/client.ts +10 -0
  31. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  32. package/src/brokers/signed-in/sign-in.ts +62 -5
  33. package/src/capabilities/registry.ts +8 -1
  34. package/src/capabilities/types.ts +2 -0
  35. package/src/cli/commands/crypto-hints.ts +12 -4
  36. package/src/cli/commands/market.ts +359 -99
  37. package/src/cli/commands/options-chain.ts +172 -0
  38. package/src/cli/commands/options-leaps.ts +422 -0
  39. package/src/cli/commands/overview.ts +164 -26
  40. package/src/cli/commands/search.ts +29 -8
  41. package/src/cli/commands/system.ts +99 -19
  42. package/src/cli/commands/themes.ts +64 -0
  43. package/src/cli/commands/ticker.ts +62 -30
  44. package/src/cli/desktop-pane-shot.ts +97 -16
  45. package/src/cli/errors.ts +25 -2
  46. package/src/cli/freshness.ts +7 -4
  47. package/src/cli/fx-pair.ts +57 -0
  48. package/src/cli/help.ts +14 -1
  49. package/src/cli/index-roots.ts +51 -0
  50. package/src/cli/index.ts +9 -6
  51. package/src/cli/listing-arg.ts +211 -3
  52. package/src/cli/not-a-ticker.ts +37 -0
  53. package/src/cli/options.ts +50 -6
  54. package/src/cli/pane-functions/capabilities.ts +8 -2
  55. package/src/cli/pane-functions/catalog.ts +13 -2
  56. package/src/cli/pane-functions/data.ts +36 -5
  57. package/src/cli/pane-functions/dom.ts +21 -13
  58. package/src/cli/pane-functions/freshness.ts +205 -26
  59. package/src/cli/pane-functions/function-help.ts +67 -22
  60. package/src/cli/pane-functions/headless.ts +5 -4
  61. package/src/cli/pane-functions/index.ts +3 -0
  62. package/src/cli/pane-functions/market-session.ts +133 -0
  63. package/src/cli/pane-functions/options.ts +15 -4
  64. package/src/cli/pane-functions/report-notices.ts +63 -0
  65. package/src/cli/pane-functions/report.ts +4 -0
  66. package/src/cli/pane-functions/resolver.ts +4 -6
  67. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  68. package/src/cli/pane-functions/screenshot.ts +105 -23
  69. package/src/cli/registry.ts +2 -1
  70. package/src/cli/result.ts +50 -13
  71. package/src/cli/row-window.ts +76 -0
  72. package/src/cli/scroll-truncation.ts +34 -0
  73. package/src/components/chart/composite/composite-chart.tsx +43 -4
  74. package/src/components/chart/composite/legend.tsx +39 -6
  75. package/src/components/chart/composite/rasterizer.ts +64 -4
  76. package/src/components/chart/composite/series-cues.ts +71 -0
  77. package/src/components/chart/composite/text-renderer.ts +16 -8
  78. package/src/components/chart/composite/types.ts +3 -0
  79. package/src/components/command-bar/commands/direct/index.ts +9 -1
  80. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  81. package/src/components/command-bar/commands/registry.ts +7 -0
  82. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  83. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  84. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  85. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  86. package/src/components/command-bar/routes/root/results.ts +40 -10
  87. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  88. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  89. package/src/components/command-bar/surface/index.tsx +18 -4
  90. package/src/components/command-bar/theme-picker.tsx +48 -13
  91. package/src/components/command-bar/workflow/ops.ts +5 -3
  92. package/src/components/data-table/view.tsx +28 -9
  93. package/src/components/layout/floating-pane.tsx +6 -2
  94. package/src/components/layout/market-summary.ts +23 -9
  95. package/src/components/layout/pane/content.tsx +46 -29
  96. package/src/components/layout/pane/cycle-order.ts +2 -11
  97. package/src/components/layout/pane/header.tsx +2 -1
  98. package/src/components/layout/pane/title.ts +1 -1
  99. package/src/components/layout/presentation.ts +22 -0
  100. package/src/components/layout/shell/active-drag.ts +1 -1
  101. package/src/components/layout/shell/drag/index.ts +4 -0
  102. package/src/components/layout/shell/drag/live.ts +76 -5
  103. package/src/components/layout/shell/drag/runtime.ts +8 -8
  104. package/src/components/layout/shell/index.tsx +47 -6
  105. package/src/components/layout/shell/layout-state.ts +5 -11
  106. package/src/components/layout/shell/menu.ts +28 -1
  107. package/src/components/layout/shell/pane/actions.ts +2 -1
  108. package/src/components/layout/shell/pane/layers.tsx +51 -10
  109. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  110. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  111. package/src/components/layout/shell/shortcuts.ts +5 -2
  112. package/src/components/layout/shell/visible-layout.ts +44 -1
  113. package/src/components/layout/status-bar.tsx +4 -1
  114. package/src/components/markdown-text.tsx +9 -5
  115. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  116. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  117. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  118. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  119. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  120. package/src/components/onboarding/pro-step-layout.ts +86 -0
  121. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  122. package/src/components/ui/button.tsx +10 -0
  123. package/src/components/ui/data-table/index.tsx +38 -0
  124. package/src/components/ui/data-table/types.ts +8 -1
  125. package/src/components/ui/data-table/visible-range.ts +2 -0
  126. package/src/components/ui/icon.tsx +11 -2
  127. package/src/components/ui/index.ts +1 -1
  128. package/src/core/connection-health.ts +1 -1
  129. package/src/core/state/app/layout-reducer.ts +89 -3
  130. package/src/core/state/app/state.ts +8 -5
  131. package/src/core/state/app/types.ts +13 -2
  132. package/src/data/config/layout.ts +55 -21
  133. package/src/data/config/store/fold-panes.ts +149 -0
  134. package/src/data/config/store/migrations.ts +7 -0
  135. package/src/data/config/store/node.ts +27 -6
  136. package/src/data/config/store/normalize.ts +37 -3
  137. package/src/i18n/es.ts +14 -1
  138. package/src/i18n/ja.ts +14 -1
  139. package/src/i18n/ko.ts +14 -1
  140. package/src/i18n/zh-cn.ts +14 -1
  141. package/src/i18n/zh-tw.ts +14 -1
  142. package/src/layout/desks.ts +1 -2
  143. package/src/layout/pane-layout-move.ts +264 -0
  144. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  145. package/src/layout/pane-manager/index.ts +1 -0
  146. package/src/market-data/coordinator/entries.ts +7 -1
  147. package/src/market-data/coordinator/quotes.ts +2 -2
  148. package/src/market-data/market/freshness.ts +118 -18
  149. package/src/market-data/market/status.ts +40 -0
  150. package/src/market-data/options-alternatives.ts +69 -0
  151. package/src/market-data/quotes/contributions.ts +19 -2
  152. package/src/market-data/quotes/metadata.ts +16 -0
  153. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  154. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  155. package/src/plugins/builtin/account-management/model.ts +1 -0
  156. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  157. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  158. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  159. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  160. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  161. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  162. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  163. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  164. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  165. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  166. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  167. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  168. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  169. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  170. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  171. package/src/plugins/builtin/chat/channels.ts +34 -36
  172. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  173. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  174. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  175. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  176. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  177. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  178. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  179. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  180. package/src/plugins/builtin/chat/conversations.ts +256 -0
  181. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  182. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  183. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  184. package/src/plugins/builtin/chat/index.tsx +40 -2
  185. package/src/plugins/builtin/chat/layout.ts +2 -0
  186. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  187. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  188. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  189. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  190. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  191. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  192. package/src/plugins/builtin/chat/pane.tsx +8 -0
  193. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  194. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  195. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  196. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  197. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  198. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  199. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  200. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  201. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  202. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  203. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  204. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  205. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  206. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  207. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  208. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  209. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  210. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  211. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  212. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  213. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  214. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  215. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  216. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  217. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  218. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  219. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  220. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  221. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  222. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  223. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  224. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  225. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  226. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  227. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  228. package/src/plugins/builtin/composite-plugins.ts +107 -64
  229. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  230. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  231. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  232. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  233. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  234. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  235. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  236. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  237. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  238. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  239. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  240. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  241. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  242. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  243. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  244. package/src/plugins/builtin/econ/index.tsx +68 -22
  245. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  246. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  247. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  248. package/src/plugins/builtin/executives/data.ts +17 -6
  249. package/src/plugins/builtin/executives/pane.tsx +16 -7
  250. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  251. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  252. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  253. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  254. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  255. package/src/plugins/builtin/gpu/headless.ts +9 -3
  256. package/src/plugins/builtin/gpu/model.ts +14 -5
  257. package/src/plugins/builtin/help/index.tsx +1 -0
  258. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  259. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  260. package/src/plugins/builtin/holders/headless.ts +6 -1
  261. package/src/plugins/builtin/holders/pane.tsx +3 -0
  262. package/src/plugins/builtin/insider/index.tsx +4 -4
  263. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  264. package/src/plugins/builtin/iv-history/client.ts +12 -2
  265. package/src/plugins/builtin/iv-history/format.ts +33 -2
  266. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  267. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  268. package/src/plugins/builtin/iv-history/model.ts +61 -13
  269. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  270. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  271. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  272. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  273. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  274. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  275. package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
  276. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  277. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  278. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  279. package/src/plugins/builtin/macro-day/client.ts +24 -0
  280. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  281. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  282. package/src/plugins/builtin/macro-day/model.ts +11 -6
  283. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  284. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  285. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  286. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  287. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  288. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  289. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  290. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  291. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  292. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  293. package/src/plugins/builtin/market-movers/model.ts +71 -17
  294. package/src/plugins/builtin/market-movers/table.ts +50 -23
  295. package/src/plugins/builtin/news/headless.ts +9 -2
  296. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  297. package/src/plugins/builtin/openfda/client.ts +1 -1
  298. package/src/plugins/builtin/options/carry.ts +95 -0
  299. package/src/plugins/builtin/options/index.tsx +58 -5
  300. package/src/plugins/builtin/options/strike-window.ts +183 -0
  301. package/src/plugins/builtin/options/table.ts +55 -3
  302. package/src/plugins/builtin/options/types.ts +8 -1
  303. package/src/plugins/builtin/options/view.tsx +161 -12
  304. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  305. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  306. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  307. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  308. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  309. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  310. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  311. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  312. package/src/plugins/builtin/pe-band/model.ts +2 -2
  313. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  314. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  315. package/src/plugins/builtin/perps/model.ts +16 -5
  316. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  317. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  318. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  319. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  320. package/src/plugins/builtin/plugin-module.ts +87 -4
  321. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  322. package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
  323. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  324. package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
  325. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  326. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  327. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  328. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  329. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  330. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  331. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  332. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  333. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  334. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  335. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  336. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  337. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  338. package/src/plugins/builtin/sec/index.tsx +5 -5
  339. package/src/plugins/builtin/sectors/client.ts +10 -2
  340. package/src/plugins/builtin/sectors/headless.ts +35 -6
  341. package/src/plugins/builtin/sectors/index.tsx +18 -1
  342. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  343. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  344. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  345. package/src/plugins/builtin/shared/members-menu.tsx +4 -4
  346. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  347. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  348. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  349. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  350. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  351. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  352. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  353. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  354. package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
  355. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  356. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  357. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  358. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  359. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  360. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  361. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  362. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  363. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  364. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  365. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  366. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  367. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  368. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  369. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  370. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  371. package/src/plugins/builtin/world-indices/client.ts +22 -4
  372. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  373. package/src/plugins/builtin/world-indices/model.ts +4 -2
  374. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  375. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  376. package/src/plugins/builtin/world-venue-map/index.ts +1 -1
  377. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  378. package/src/plugins/builtin-editorial.ts +126 -0
  379. package/src/plugins/catalog-backend.ts +16 -9
  380. package/src/plugins/catalog-browser.ts +127 -63
  381. package/src/plugins/catalog-manifest.ts +73 -40
  382. package/src/plugins/catalog-ui.ts +26 -6
  383. package/src/plugins/extracted-plugins.ts +7 -1
  384. package/src/plugins/ownership.ts +150 -7
  385. package/src/plugins/plugin-functions.ts +54 -0
  386. package/src/plugins/plugin-off.ts +36 -0
  387. package/src/plugins/registry/host-actions.ts +16 -0
  388. package/src/plugins/registry/index.ts +84 -24
  389. package/src/plugins/registry/plugin-state.ts +25 -1
  390. package/src/plugins/runtime/context.tsx +29 -0
  391. package/src/react/input.ts +48 -1
  392. package/src/remote/app-host.tsx +5 -0
  393. package/src/remote/controller.ts +3 -53
  394. package/src/remote/matching.ts +69 -0
  395. package/src/remote/schema.ts +1 -1
  396. package/src/remote/semantic-tree.tsx +14 -0
  397. package/src/remote/types.ts +2 -0
  398. package/src/renderers/dom/desktop/icons.tsx +14 -0
  399. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  400. package/src/renderers/dom/host/box.tsx +1 -1
  401. package/src/renderers/dom/host/drag-shield.ts +17 -6
  402. package/src/renderers/dom/host/live-frame.ts +10 -2
  403. package/src/renderers/dom/input-host.tsx +6 -1
  404. package/src/renderers/dom/styles.css +13 -0
  405. package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
  406. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  407. package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
  408. package/src/renderers/electrobun/shared/protocol.ts +2 -0
  409. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  410. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  411. package/src/renderers/electrobun/view/main.tsx +4 -1
  412. package/src/renderers/opentui/input-host.tsx +6 -1
  413. package/src/renderers/opentui/start.tsx +9 -0
  414. package/src/shares/portable-layout.ts +16 -6
  415. package/src/sources/gloomberb-cloud/index.ts +19 -10
  416. package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
  417. package/src/sources/listing-symbols.ts +11 -5
  418. package/src/sources/provider-errors.ts +88 -3
  419. package/src/sources/provider-router/cached-routes.ts +8 -4
  420. package/src/sources/provider-router/financial-routes.ts +11 -5
  421. package/src/sources/provider-router/financials.ts +42 -15
  422. package/src/sources/provider-router/history.ts +27 -11
  423. package/src/sources/provider-router/index.ts +1 -1
  424. package/src/sources/provider-router/primary.ts +23 -5
  425. package/src/sources/sec-edgar.ts +68 -8
  426. package/src/sync/core-contributors.ts +4 -3
  427. package/src/test-support/cli-context.ts +2 -1
  428. package/src/test-support/data-provider.ts +34 -0
  429. package/src/test-support/shell-pane-keys.tsx +1 -0
  430. package/src/theme/series-colors.ts +46 -0
  431. package/src/theme/themes.ts +149 -0
  432. package/src/tickers/search/index.ts +22 -2
  433. package/src/tickers/search/ranking.ts +45 -1
  434. package/src/tickers/search/types.ts +3 -1
  435. package/src/time-series/resolve.ts +1 -14
  436. package/src/types/config.ts +61 -1
  437. package/src/types/headless.ts +11 -0
  438. package/src/types/instrument.ts +8 -0
  439. package/src/types/plugin.ts +49 -3
  440. package/src/ui/host.tsx +8 -4
  441. package/src/ui/index.tsx +2 -0
  442. package/src/utils/cli-output.ts +14 -1
  443. package/src/utils/exchanges.ts +13 -0
  444. package/src/utils/format.ts +2 -1
  445. package/src/utils/fx-market-hours.ts +34 -0
  446. package/src/utils/option-expiry.ts +126 -0
  447. package/src/utils/price-history.ts +39 -6
  448. package/src/utils/rpc-timeout-error.ts +6 -2
  449. package/src/utils/sec-eps-basis.ts +159 -30
  450. package/src/utils/sec.ts +38 -34
  451. package/src/utils/throttled-fetch.ts +51 -22
@@ -26,9 +26,10 @@ import {
26
26
  useUiCapabilities,
27
27
  } from "../../../ui";
28
28
  import { isPermanentClientError } from "../../../api-client/errors";
29
+ import type { IssuerListingParams } from "../../../api-client/paths";
29
30
  import { useFilingYearReader } from "../shared/filing-year-reader";
30
31
  import { useProFeatureWall, type ProFeatureWallCopy, type ReadGuard } from "../shared/pro-feature-wall";
31
- import { useBoundTicker } from "../shared/ticker-request";
32
+ import { useBoundTicker, useIssuerListing } from "../shared/ticker-request";
32
33
  import { SplitBar } from "../../../components/ui/split-bar";
33
34
  import { loadProxyStatement, loadProxyStatements } from "./data";
34
35
  import {
@@ -249,27 +250,35 @@ export function ExecutivesPane({
249
250
  nested?: boolean;
250
251
  }) {
251
252
  const { wall, guard } = useProFeatureWall(EXEC_WALL, { width, height });
252
- const { symbol } = useBoundTicker();
253
+ const { symbol, ticker: boundTicker } = useBoundTicker();
253
254
  const ticker = symbol ? symbol.toUpperCase() : null;
255
+ const listing = useIssuerListing(boundTicker);
254
256
  if (wall) return wall;
255
- if (!ticker) return <EmptyState title="Pick a ticker to see its executives." />;
256
- return <ExecutiveResearch key={ticker} ticker={ticker} focused={focused} width={width} nested={nested} guard={guard} />;
257
+ if (!ticker) return <EmptyState title="Select a ticker." />;
258
+ return <ExecutiveResearch key={ticker} ticker={ticker} listing={listing} focused={focused} width={width} nested={nested} guard={guard} />;
257
259
  }
258
260
 
259
261
  export function ExecutivesResearchTab(props: { focused: boolean; width: number; height: number }) {
260
262
  return <ExecutivesPane {...props} nested />;
261
263
  }
262
264
 
263
- function ExecutiveResearch({ ticker, focused, width, nested, guard }: { ticker: string; focused: boolean; width: number; nested: boolean; guard: ReadGuard }) {
265
+ function ExecutiveResearch({ ticker, listing, focused, width, nested, guard }: {
266
+ ticker: string;
267
+ listing: IssuerListingParams;
268
+ focused: boolean;
269
+ width: number;
270
+ nested: boolean;
271
+ guard: ReadGuard;
272
+ }) {
264
273
  const nativePaneChrome = useUiCapabilities().nativePaneChrome === true;
265
274
  const rendererHost = useRendererHost();
266
275
  const [selectedYear, setYear] = usePaneStateValue<number | null>("proxyYear", null);
267
- const loadYears = useCallback((force: boolean) => guard(loadProxyStatements(ticker, { force })), [guard, ticker]);
276
+ const loadYears = useCallback((force: boolean) => guard(loadProxyStatements(ticker, { force, listing })), [guard, listing, ticker]);
268
277
  const list = useAsyncResource(loadYears, { clearOnError: isPermanentClientError });
269
278
  const years = useMemo(() => (list.data?.data?.proxies ?? []).map((entry) => entry.proxyYear), [list.data]);
270
279
  const year = selectedYear !== null && years.includes(selectedYear)
271
280
  ? selectedYear : years[0] ?? null;
272
- const loadStatement = useCallback((force: boolean) => guard(loadProxyStatement(ticker, year!, { force })), [guard, ticker, year]);
281
+ const loadStatement = useCallback((force: boolean) => guard(loadProxyStatement(ticker, year!, { force, listing })), [guard, listing, ticker, year]);
273
282
  const detail = useAsyncResource(year === null ? null : loadStatement, { clearOnError: isPermanentClientError });
274
283
  const statement = detail.data?.data ?? null;
275
284
  const loading = list.loading || detail.loading;
@@ -1,5 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { apiClient } from "../../../api-client";
3
+ import { ApiRequestError } from "../../../api-client/errors";
4
+ import type { IssuerListingParams } from "../../../api-client/paths";
3
5
  import {
4
6
  BulletList,
5
7
  EmptyState,
@@ -24,7 +26,7 @@ import {
24
26
  import { truncateToDisplayWidth } from "../../../utils/format";
25
27
  import { isPlainKey } from "../../../utils/keyboard";
26
28
  import { useProFeatureWall, type ProFeatureWallCopy, type ReadGuard } from "../shared/pro-feature-wall";
27
- import { useBoundTicker, useTickerRequest } from "../shared/ticker-request";
29
+ import { useBoundTicker, useIssuerListing, useTickerRequest } from "../shared/ticker-request";
28
30
  import {
29
31
  buildFilingEventsFeed,
30
32
  resolveFeedScrollTop,
@@ -100,6 +102,20 @@ function FilingEntry({
100
102
  );
101
103
  }
102
104
 
105
+ /**
106
+ * The latest 8-Ks of a listing. The service answers 404 when it can tie no
107
+ * filer to the ticker, as for a listing abroad whose company files none:
108
+ * that is no 8-Ks, not a failure.
109
+ */
110
+ async function fetchFilingEvents(ticker: string, listing: IssuerListingParams) {
111
+ try {
112
+ return await apiClient.getFilingEvents(ticker, 100, listing);
113
+ } catch (error) {
114
+ if (error instanceof ApiRequestError && error.status === 404) return { ticker, events: [] };
115
+ throw error;
116
+ }
117
+ }
118
+
103
119
  interface FilingEventsPaneProps {
104
120
  focused: boolean;
105
121
  width: number;
@@ -116,14 +132,15 @@ function FilingEventsReader({
116
132
  width,
117
133
  guard,
118
134
  }: FilingEventsPaneProps & { guard: ReadGuard }) {
119
- const { symbol, exchange } = useBoundTicker();
135
+ const { symbol, exchange, ticker: boundTicker } = useBoundTicker();
120
136
  const ticker = symbol ? symbol.toUpperCase() : null;
137
+ const { name } = useIssuerListing(boundTicker);
121
138
  const nativePaneChrome = useUiCapabilities().nativePaneChrome === true;
122
139
  const rendererHost = useRendererHost();
123
140
 
124
141
  const loadFilingEvents = useCallback(
125
- (listing: string) => guard(apiClient.getFilingEvents(listing, 100)),
126
- [guard],
142
+ (key: string, venue: string) => guard(fetchFilingEvents(key, { exchange: venue, name })),
143
+ [guard, name],
127
144
  );
128
145
  const { data, loading, error, reload } = useTickerRequest(loadFilingEvents, ticker, exchange);
129
146
  const events = data?.events ?? [];
@@ -227,7 +244,7 @@ function FilingEventsReader({
227
244
  return { info, hints };
228
245
  }, [loading, error, ticker, selected, openFiling]);
229
246
 
230
- if (!ticker) return <EmptyState title="Pick a ticker to see its 8-K filings." />;
247
+ if (!ticker) return <EmptyState title="Select a ticker." />;
231
248
  if (loading && !data) {
232
249
  return <PaneStatusBody loading align="center" loadingLabel="Loading 8-Ks..." />;
233
250
  }
@@ -0,0 +1,65 @@
1
+ import type { QueryEntry } from "../../../market-data/result-types";
2
+ import type { Quote } from "../../../types/financials";
3
+ import { isFxMarketOpen } from "../../../utils/fx-market-hours";
4
+ import { quoteFreshnessFields } from "../shared/report-freshness";
5
+
6
+ /** The venue FX pairs trade on, so a report can place the matrix's market. */
7
+ const FX_VENUE = "CCY";
8
+
9
+ /**
10
+ * One rate the matrix draws, in the fields a report reads to date it: when it
11
+ * was observed, whether it is live or delayed (and by how much), whether it is
12
+ * stale, and where its market stands. The pane publishes these for `fn FXC`
13
+ * and `shot FXC`; they are not shown in the pane.
14
+ */
15
+ type FxRateObservation = {
16
+ currency: string;
17
+ /** ISO time of the observation; absent when the rate carries none. */
18
+ quoteTime?: string;
19
+ dataSource?: "live" | "delayed";
20
+ delayMinutes?: number;
21
+ stale: boolean;
22
+ sessionExchange: string;
23
+ marketState: NonNullable<Quote["marketState"]>;
24
+ };
25
+
26
+ function isoTime(time: number | undefined | null): string | undefined {
27
+ return time != null && Number.isFinite(time) ? new Date(time).toISOString() : undefined;
28
+ }
29
+
30
+ /**
31
+ * The observation behind each rate the matrix shows, except USD (the base,
32
+ * observed nowhere) and a currency with no usable rate (drawn as a dash).
33
+ *
34
+ * A rate read from a live pair quote (`legQuote`) says what that quote says:
35
+ * live or delayed, by how much, and its market state. Any other rate is the
36
+ * loaded snapshot: a snapshot refreshed on a schedule, so delayed, dated by
37
+ * the snapshot's own observation time and placed on the FX week, which runs
38
+ * Sunday 17:00 to Friday 17:00 New York time. A rate is stale when its entry
39
+ * says so (a failed refresh, or its own schedule), as the pane's footer counts.
40
+ */
41
+ export function fxRateObservations(
42
+ currencies: readonly string[],
43
+ rates: ReadonlyMap<string, number>,
44
+ read: (currency: string) => { entry?: QueryEntry<number> | null; legQuote?: Quote | null },
45
+ now = Date.now(),
46
+ ): FxRateObservation[] {
47
+ return [...new Set(currencies)].flatMap((currency) => {
48
+ if (currency === "USD") return [];
49
+ const rate = rates.get(currency);
50
+ if (rate == null || !Number.isFinite(rate) || rate <= 0) return [];
51
+ const { entry, legQuote } = read(currency);
52
+ const fields = legQuote ? quoteFreshnessFields(legQuote, now) : null;
53
+ const entryStale = entry?.error != null || (entry?.staleAt != null && entry.staleAt <= now);
54
+ const quoteTime = isoTime(entry?.asOf ?? legQuote?.lastUpdated);
55
+ return [{
56
+ currency,
57
+ ...(quoteTime ? { quoteTime } : {}),
58
+ dataSource: fields?.dataSource ?? "delayed",
59
+ ...(fields?.delayMinutes ? { delayMinutes: fields.delayMinutes } : {}),
60
+ stale: entryStale || fields?.stale === true,
61
+ sessionExchange: fields?.sessionExchange ?? FX_VENUE,
62
+ marketState: fields?.marketState ?? (isFxMarketOpen(now) ? "REGULAR" : "CLOSED"),
63
+ }];
64
+ });
65
+ }
@@ -12,6 +12,7 @@ import { CountryFlag } from "../../../components/ui/country-flag";
12
12
  import { getSharedMarketDataCoordinator } from "../../../market-data/coordinator";
13
13
  import { useFxRatesMap } from "../../../market-data/hooks";
14
14
  import { resolveEntryData } from "../../../market-data/selectors";
15
+ import { useRemoteUiNode } from "../../../remote/semantic-tree";
15
16
  import { usePaneSettingValue } from "../../../state/app/context";
16
17
  import { colors } from "../../../theme/colors";
17
18
  import type { PaneProps } from "../../../types/plugin";
@@ -25,8 +26,9 @@ import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
25
26
  import { fxLegQuoteKey, fxLegReferenceRate, fxLegTargets, fxLegs, fxLegsBehind, liveFxLegEntry } from "./live-legs";
26
27
  import { CURRENCY_FLAG_REGIONS, FX_CURRENCIES, formatRate, resolveCurrencies, type FxCurrency } from "./pairs";
27
28
  import { crossMovePercent, directionTint, nextTintLevel } from "./direction";
28
- import { fxMatrixSettings } from "./settings";
29
+ import { FX_MATRIX_REPORT_OPTIONS, fxMatrixReportNotices, fxMatrixSettings } from "./settings";
29
30
  import { createFxExportMetadata } from "./export";
31
+ import { fxRateObservations } from "./freshness";
30
32
 
31
33
  const FX_MATRIX_PANE_ID = "fx-matrix";
32
34
  /** Stable identity: a fresh literal here would reload the board every render. */
@@ -114,6 +116,23 @@ function FxMatrixPane({ focused, width, height }: PaneProps) {
114
116
  ? { ...entry, error: entry.error ? { ...entry.error } : null }
115
117
  : undefined] as const;
116
118
  }));
119
+ // A report or capture of the board is dated by the rates it draws: when each
120
+ // was observed, whether it is delayed or stale, and where the FX market
121
+ // stands. The pane draws none of this; its footer keeps its own status.
122
+ useRemoteUiNode({
123
+ role: "report-freshness",
124
+ label: "Rate freshness",
125
+ getMetadata: () => {
126
+ const legQuotes = new Map(legs.map((leg) => [leg.currency, resolveEntryData(legEntries.get(fxLegQuoteKey(leg)))] as const));
127
+ return {
128
+ observations: fxRateObservations(currencies, rates, (currency) => ({
129
+ entry: rateEntries.get(currency),
130
+ // Only a rate that is the pair quote's own says what that quote says.
131
+ legQuote: liveEntries.has(currency) ? legQuotes.get(currency) : null,
132
+ })),
133
+ };
134
+ },
135
+ });
117
136
  const status = summarizeFxRates(currencies, rates, (currency) => rateEntries.get(currency));
118
137
  const statusText = fxStatusLabel(status);
119
138
  const snapshotFetchedAt = summarizeFxRates(currencies, snapshotRates, (currency) => (
@@ -253,6 +272,8 @@ export const fxMatrixModule: PluginModule = {
253
272
  defaultFloatingSize: { width: 105, height: 14 },
254
273
  tableExport: true,
255
274
  settings: fxMatrixSettings,
275
+ reportOptions: FX_MATRIX_REPORT_OPTIONS,
276
+ reportNotices: fxMatrixReportNotices,
256
277
  },
257
278
  ],
258
279
 
@@ -60,7 +60,7 @@ export function formatRate(rate: number, referenceRate = rate): string {
60
60
  return rate.toFixed(decimals);
61
61
  }
62
62
 
63
- function isFxCurrency(code: unknown): code is FxCurrency {
63
+ export function isFxCurrency(code: unknown): code is FxCurrency {
64
64
  return typeof code === "string" && FX_CURRENCIES.includes(code as FxCurrency);
65
65
  }
66
66
 
@@ -1,5 +1,5 @@
1
- import type { PaneSettingsContext, PaneSettingsDef } from "../../../types/plugin";
2
- import { CURRENCY_SETS, FX_CURRENCIES, resolveCurrencies } from "./pairs";
1
+ import type { PaneReportOptionDef, PaneSettingsContext, PaneSettingsDef } from "../../../types/plugin";
2
+ import { CURRENCY_SETS, FX_CURRENCIES, MAJOR_CURRENCIES, isFxCurrency, resolveCurrencies } from "./pairs";
3
3
 
4
4
  const CUSTOM_SET = "custom";
5
5
 
@@ -60,3 +60,43 @@ export function fxMatrixSettings(context: PaneSettingsContext): PaneSettingsDef
60
60
  },
61
61
  };
62
62
  }
63
+
64
+ const SET_NAMES = CURRENCY_SETS.map((set) => set.id).join(", ");
65
+
66
+ /**
67
+ * `--currencies` as typed: codes and set names in any case, separated by
68
+ * commas or spaces, returned as the codes in order without repeats. A token
69
+ * that is neither fails, naming it and the codes the board carries, where the
70
+ * pane setting would drop it and quietly show the majors.
71
+ */
72
+ function parseCurrenciesOption(value: string): string {
73
+ const tokens = value.split(/[\s,]+/).filter(Boolean);
74
+ const unknown = tokens.filter((token) => (
75
+ !isFxCurrency(token.toUpperCase()) && !CURRENCY_SETS.some((set) => set.id === token.toLowerCase())
76
+ ));
77
+ if (unknown.length > 0) {
78
+ throw new Error(
79
+ `Unknown ${unknown.length > 1 ? "currencies" : "currency"} ${unknown.join(", ")} for --currencies. `
80
+ + `Use codes from ${FX_CURRENCIES.join(", ")}, or a set: ${SET_NAMES}.`,
81
+ );
82
+ }
83
+ if (tokens.length === 0) throw new Error("--currencies needs at least one code, such as USD,ZAR,NGN.");
84
+ return resolveCurrencies(value).join(",");
85
+ }
86
+
87
+ /** `fn FXC --currencies USD,ZAR,NGN`: the board's currency list, as its settings choose it. */
88
+ export const FX_MATRIX_REPORT_OPTIONS: readonly PaneReportOptionDef[] = [{
89
+ key: "currencies",
90
+ type: "string",
91
+ placeholder: "codes",
92
+ description: `The currencies to cross, in order: codes separated by commas, or a set (${SET_NAMES}). `
93
+ + `Without it, the ${MAJOR_CURRENCIES.length} majors. Codes: ${FX_CURRENCIES.join(", ")}`,
94
+ example: "--currencies USD,ZAR,NGN,SAR,ILS",
95
+ normalize: parseCurrenciesOption,
96
+ }];
97
+
98
+ /** A report of the default board says it is only the majors, and how to choose others. */
99
+ export function fxMatrixReportNotices(settings: Readonly<Record<string, unknown>>): string[] {
100
+ if (settings.currencies != null) return [];
101
+ return [`Showing the ${MAJOR_CURRENCIES.length} majors. Choose others with --currencies USD,ZAR,NGN; gloomberb catalog FXC lists all ${FX_CURRENCIES.length}.`];
102
+ }
@@ -3,6 +3,7 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
3
3
  import { fetchGpuBoard, fetchGpuEvents, fetchGpuHistory, loadGpuEquityHistory } from "./client";
4
4
  import { GPU_TABS, gpuArgument, gpuBasisLabel, gpuChange, gpuEquityRows, gpuEventDate, gpuProvenanceLabel, gpuLabel, gpuRows, gpuSource, gpuTab } from "./model";
5
5
  import { quoteFreshnessFields } from "../shared/report-freshness";
6
+ import { getRegularSessionDisplay } from "../../../market-data/market/status";
6
7
 
7
8
  const observationRow = (row: GpuObservation) => ({ gpu: gpuLabel(row), source: gpuSource(row), basis: gpuBasisLabel(row.basis),
8
9
  price: row.pricePerGpuHr, availability: row.availability, observedAt: row.observedAt, effectiveAt: row.effectiveAt, provenance: row.provenance ?? "live", provenanceLabel: gpuProvenanceLabel(row, true),
@@ -19,14 +20,17 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
19
20
  shape: "bundle", argument: { kind: "free-text", optional: true, placeholder: "GPU", description: "GPU model, such as H100 or B200." },
20
21
  options: [{ key: "tab", type: "enum", values: GPU_TABS.map(({ value }) => ({ value })), defaultValue: "board", description: "Board, sourced history, dated changes or related equities." }],
21
22
  discovery: { aliases: ["GPU"], dataRequirements: ["GPU rental price observations"],
22
- limitations: ["List prices, provider-declared spot and asks are distinct bases", "1D/7D/30D need sufficient observation history; unavailable changes remain null"] },
23
+ limitations: ["List prices, provider-declared spot and asks are distinct bases",
24
+ "Hyperscaler and Neocloud rows are list indexes: the median of the providers' list prices linked over time, so a provider joining or leaving causes no jump; sample.rawMedian is the raw median of today's providers and can differ slightly",
25
+ "1D/7D/30D need sufficient observation history; unavailable changes remain null"] },
23
26
  describe: "GPU rental prices in USD per GPU-hour",
24
27
  async load(args, ctx) {
25
28
  const argument = Array.isArray(args.argument) ? args.argument.join(" ") : args.argument;
26
29
  const model = gpuArgument(argument);
27
30
  const tab = gpuTab(args.options.tab);
28
31
  const board = await fetchGpuBoard(ctx.apiClient);
29
- const metadata = { asOf: board.asOf, stale: board.stale, complete: board.status === "available" && !board.access?.locked, unit: "USD/GPU-hour", access: board.access };
32
+ const metadata = { asOf: board.asOf, stale: board.stale, complete: board.status === "available" && !board.access?.locked, unit: "USD/GPU-hour", access: board.access,
33
+ methodology: "docs/gpu-rental-prices.md#list-indexes" };
30
34
  // Prices observed on providers' pages, not a feed; each board row says whether its source has gone quiet.
31
35
  const freshness = { source: "GPU cloud providers", status: "not-a-feed" as const, basis: "observed prices" };
32
36
  if (tab === "history") {
@@ -52,7 +56,9 @@ export const gpuHeadless: HeadlessPaneDefinition<"bundle"> = {
52
56
  const rows = await Promise.all(related.map(async (row) => {
53
57
  const quote = await ctx.marketData.getQuote(row.symbol, row.exchange).catch(() => null);
54
58
  const history = histories.find((entry) => entry.symbol === row.symbol);
55
- return { symbol: row.symbol, role: row.role, price: quote?.price ?? null, change1d: quote?.changePercent ?? null,
59
+ // The regular session's last and move, as the pane's board shows them.
60
+ const headline = getRegularSessionDisplay(quote);
61
+ return { symbol: row.symbol, role: row.role, price: headline?.price ?? null, change1d: headline?.changePercent ?? null,
56
62
  change5d: history?.value ?? null, fiveDayAsOf: history?.asOf ?? null, quoteAsOf: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString() : null,
57
63
  ...quoteFreshnessFields(quote),
58
64
  gpu: row.reference ? gpuLabel(row.reference) : row.gpuModel, gpuSource: row.reference ? gpuSource(row.reference) : null, gpuChange7d: row.reference?.change7d ?? null };
@@ -1,6 +1,6 @@
1
1
  import type { GpuBasis, GpuBoardRow, GpuEvent, GpuObservation } from "../../../api-client/gpu";
2
2
  import { staticSeries } from "../../../components/chart/static/series";
3
- import { SERIES_COLORS } from "../../../time-series/resolve";
3
+ import { SERIES_COLORS } from "../../../theme/series-colors";
4
4
  import type { ResolvedSeries } from "../../../time-series/types";
5
5
  import type { PricePoint } from "../../../types/financials";
6
6
 
@@ -32,16 +32,25 @@ const PROVIDER_NAMES: Record<string, string> = {
32
32
  scaleway: "Scaleway", horizon: "Horizon", verda: "Verda", latitude: "Latitude", imwt: "IMWT",
33
33
  };
34
34
 
35
+ /**
36
+ * The provider-class rows are chain-linked list indexes, not raw medians. Servers that predate the
37
+ * rename still send "list median"; both strings read as the index.
38
+ */
39
+ const LIST_INDEX_PROVIDER = /^(Hyperscaler|Neocloud) list (?:index|median)$/;
40
+
35
41
  /** Cloud operators are the product being compared. The data collection intermediary stays invisible. */
36
42
  export function gpuSource(row: Pick<GpuObservation, "source" | "provider" | "skuKey">): string {
43
+ const listIndex = LIST_INDEX_PROVIDER.exec(row.provider);
44
+ if (listIndex) return `${listIndex[1]} list index`;
37
45
  if (row.source === "vast" || row.source === "vast-ai") return "Marketplace ask median";
38
46
  if (row.source === "akash") return "Decentralized ask median";
39
47
  if (row.source === "runpod") return /community/i.test(`${row.provider} ${row.skuKey}`) ? "Community cloud asks" : "Secure cloud asks";
40
48
  return PROVIDER_NAMES[row.provider.toLowerCase()] ?? row.provider;
41
49
  }
42
50
 
43
- /** A source inside its basis section: the section already says list or ask, so a median drops the word. */
44
- export const gpuShortSource = (row: Pick<GpuObservation, "source" | "provider" | "skuKey">) => gpuSource(row).replace(/ (?:list|ask) median$/, " median");
51
+ /** A source inside its basis section: the section already says list or ask, so an index or median drops the word. */
52
+ export const gpuShortSource = (row: Pick<GpuObservation, "source" | "provider" | "skuKey">) =>
53
+ gpuSource(row).replace(/ list index$/, " index").replace(/ ask median$/, " median");
45
54
 
46
55
  export function gpuTime(value: string | null | undefined, compact = false): string {
47
56
  if (!value) return "-";
@@ -73,7 +82,7 @@ export function gpuBasisColor(basis: GpuBasis): string {
73
82
  export const gpuVariant = (row: Pick<GpuObservation, "formFactor" | "memoryGb">): string[] =>
74
83
  [row.formFactor, row.memoryGb ? `${row.memoryGb}GB` : null].filter((part): part is string => !!part);
75
84
 
76
- /** Medians lead their section: provider-class medians and the marketplace offer medians. */
85
+ /** Indexes and medians lead their section: the provider-class list indexes and the marketplace offer medians. */
77
86
  export const gpuHeadline = (row: Pick<GpuObservation, "providerClass" | "source">) =>
78
87
  !row.source.startsWith("ref-") && (row.providerClass === "aggregate" || row.source === "vast" || row.source === "vast-ai" || row.source === "akash");
79
88
 
@@ -320,7 +329,7 @@ export function gpuEquityRows(rows: readonly GpuBoardRow[], selectedModel: strin
320
329
  const aggregates = candidates.filter((row) => row.providerClass === "aggregate").sort((a, b) =>
321
330
  Number(b.formFactor === "SXM") - Number(a.formFactor === "SXM") || (b.stats?.n ?? 0) - (a.stats?.n ?? 0) || (b.memoryGb ?? 0) - (a.memoryGb ?? 0));
322
331
  const reference = "provider" in equity ? candidates.find((row) => gpuSource(row) === equity.provider)
323
- : aggregates.find((row) => row.provider === "Neocloud list median") ?? aggregates[0];
332
+ : aggregates.find((row) => row.provider.startsWith("Neocloud")) ?? aggregates[0];
324
333
  return { ...equity, gpuModel: model, reference: reference ?? null };
325
334
  });
326
335
  }
@@ -153,6 +153,7 @@ function HelpPane({ focused, width, height }: PaneProps) {
153
153
  entry("des", ["DES", "<ticker>"], "Open security details for a specific ticker."),
154
154
  entry("help-card", ["HELP", "<function>"], "Open a function's help card: what it shows, its keys, how fresh its data is. HELP HELP reaches support."),
155
155
  entry("upgrade", ["UPGRADE"], tf("Go Pro for real-time data at gloom.sh/cloud, free for {days} days.", { days: trialDays })),
156
+ entry("mcp", ["MCP"], "Connect Claude Code, Codex, Cursor or any MCP client to Gloom's research tools."),
156
157
  entry("move", ["Up/Down", "Ctrl+P/N"], "Move through command bar results."),
157
158
  entry("page", ["PageUp/PageDown", "Ctrl+Home/End"], "Jump a page, or to the first or last result."),
158
159
  entry("run", ["Enter", "Shift+Enter"], "Run the selected result or its secondary action."),
@@ -7,7 +7,9 @@ import type {
7
7
  BeneficialOwnersPayload,
8
8
  } from "../../../api-client/beneficial-owners";
9
9
  import { ApiRequestError, isAccessDenied } from "../../../api-client/errors";
10
+ import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
10
11
  import { createPluginCache } from "../../../data/plugin-cache";
12
+ import { secListingKey } from "../../../utils/sec";
11
13
  import { finiteOrNull, recordOrNull } from "../../../utils/guards";
12
14
  import { cachedCloudResource, loadCloudResource, type CloudResource } from "../shared/cloud-resource";
13
15
  import { CLOUD_SESSION_REQUIRED } from "../shared/research-cloud-session";
@@ -28,6 +30,8 @@ export interface BeneficialOwnersRequest {
28
30
  form?: BeneficialOwnersForm;
29
31
  /** Every report instead of the latest per filer. */
30
32
  history?: boolean;
33
+ /** The listing's venue and company, for a listing outside the US. */
34
+ listing?: IssuerListingParams;
31
35
  }
32
36
 
33
37
  type BeneficialOwnersClient = Pick<typeof apiClient, "getCloudSecBeneficialOwners">;
@@ -142,7 +146,7 @@ function describeFailure(error: unknown, ticker: string): unknown {
142
146
  */
143
147
  export async function fetchBeneficialOwners(
144
148
  ticker: string,
145
- { form = "all", history = false }: BeneficialOwnersRequest = {},
149
+ { form = "all", history = false, listing }: BeneficialOwnersRequest = {},
146
150
  { client = apiClient, signal }: { client?: BeneficialOwnersClient; signal?: AbortSignal } = {},
147
151
  ): Promise<BeneficialOwnersPayload> {
148
152
  const symbol = ticker.trim().toUpperCase();
@@ -154,6 +158,8 @@ export async function fetchBeneficialOwners(
154
158
  for (;;) {
155
159
  const page = normalizeBeneficialOwnersPayload(await client.getCloudSecBeneficialOwners({
156
160
  ticker: symbol,
161
+ exchange: listing?.exchange,
162
+ name: listing?.name,
157
163
  form,
158
164
  history,
159
165
  limit: PAGE_SIZE,
@@ -178,10 +184,20 @@ export async function fetchBeneficialOwners(
178
184
  offset = nextOffset;
179
185
  }
180
186
  } catch (error) {
187
+ // A listing abroad whose company the SEC does not know has no reports.
188
+ const abroad = listingAbroad(symbol, listing?.exchange);
189
+ if (abroad && error instanceof ApiRequestError && error.status === 404) return noReports(symbol, history);
181
190
  throw describeFailure(error, symbol);
182
191
  }
183
192
  }
184
193
 
194
+ function noReports(ticker: string, history: boolean): BeneficialOwnersPayload {
195
+ return {
196
+ ticker, cik: "", companyName: "", asOf: null, owners: [], ...(history ? { filings: [] } : {}),
197
+ hasMore: false, nextOffset: null, coverage: null,
198
+ };
199
+ }
200
+
185
201
  /** Pages can overlap when a report lands between two requests. */
186
202
  function dedupe(rows: BeneficialOwnerFiling[]): BeneficialOwnerFiling[] {
187
203
  const seen = new Set<string>();
@@ -193,17 +209,23 @@ function dedupe(rows: BeneficialOwnerFiling[]): BeneficialOwnerFiling[] {
193
209
  });
194
210
  }
195
211
 
196
- const cacheKey = (ticker: string) => ticker.trim().toUpperCase();
212
+ // SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
213
+ const cacheKey = (ticker: string, listing?: IssuerListingParams) => secListingKey(ticker, listing?.exchange);
197
214
 
198
- export function cachedBeneficialOwners(ticker: string): CloudResource<BeneficialOwnersPayload> | null {
199
- return cachedCloudResource(beneficialOwnersCache, cacheKey(ticker));
215
+ export function cachedBeneficialOwners(ticker: string, listing?: IssuerListingParams): CloudResource<BeneficialOwnersPayload> | null {
216
+ return cachedCloudResource(beneficialOwnersCache, cacheKey(ticker, listing));
200
217
  }
201
218
 
202
219
  /** The pane's list: the latest report per filer, both kinds, cached on disk. */
203
- export async function loadBeneficialOwners(ticker: string, force = false): Promise<CloudResource<BeneficialOwnersPayload>> {
220
+ export async function loadBeneficialOwners(
221
+ ticker: string,
222
+ force = false,
223
+ listing?: IssuerListingParams,
224
+ ): Promise<CloudResource<BeneficialOwnersPayload>> {
225
+ const key = cacheKey(ticker, listing);
204
226
  try {
205
- return await loadCloudResource(beneficialOwnersCache, cacheKey(ticker), () => fetchBeneficialOwners(ticker), { force });
227
+ return await loadCloudResource(beneficialOwnersCache, key, () => fetchBeneficialOwners(ticker, { listing }), { force });
206
228
  } catch (error) {
207
- throw describeFailure(error, cacheKey(ticker));
229
+ throw describeFailure(error, key);
208
230
  }
209
231
  }
@@ -10,6 +10,7 @@ import {
10
10
  type DataTableKeyEvent,
11
11
  } from "../../../components";
12
12
  import { handleRefreshKey, loadingErrorFooterInfo } from "../../../components/data-table/table-pane";
13
+ import type { IssuerListingParams } from "../../../api-client/paths";
13
14
  import { useAsyncResource } from "../../../react/async-resource";
14
15
  import { useAutoRefresh } from "../../../react/auto-refresh";
15
16
  import { colors, priceColor } from "../../../theme/colors";
@@ -55,10 +56,13 @@ export function BeneficialOwnersView({
55
56
  fundMatches,
56
57
  onCycleView,
57
58
  onRefreshHolders,
59
+ listing,
58
60
  }: {
59
61
  focused: boolean;
60
62
  width: number;
61
63
  symbol: string | null;
64
+ /** The listing's venue and company, for a listing outside the US. */
65
+ listing?: IssuerListingParams;
62
66
  holderRows: readonly HolderRow[];
63
67
  fundMatches: ReadonlyMap<string, Holder13FMatch>;
64
68
  onCycleView: () => void;
@@ -75,11 +79,11 @@ export function BeneficialOwnersView({
75
79
 
76
80
  // A session change retries a request the sign-in wall was shown for.
77
81
  const loader = useMemo(
78
- () => symbol ? (force: boolean) => loadBeneficialOwners(symbol, force) : null,
79
- [symbol, session.requestKey],
82
+ () => symbol ? (force: boolean) => loadBeneficialOwners(symbol, force, listing) : null,
83
+ [symbol, listing, session.requestKey],
80
84
  );
81
85
  const resource = useAsyncResource(loader, {
82
- initialData: () => symbol ? cachedBeneficialOwners(symbol) : null,
86
+ initialData: () => symbol ? cachedBeneficialOwners(symbol, listing) : null,
83
87
  clearOnError: (error) => error instanceof Error && isCloudSessionRequired(error.message),
84
88
  });
85
89
  useAutoRefresh(resource.updatedAt, resource.load);
@@ -35,6 +35,7 @@ const CHANGE_COLUMN_KEYS = new Set(["changeShares", "changePercent"]);
35
35
  const BASIS_COLUMN_KEY = "shareBasis";
36
36
  import type { BeneficialOwnersPayload } from "../../../api-client/beneficial-owners";
37
37
  import { fetchBeneficialOwners, type BeneficialOwnersRequest } from "./beneficial-client";
38
+ import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
38
39
  import type { BeneficialColumnId } from "./beneficial-model";
39
40
  import {
40
41
  BENEFICIAL_REPORT_COLUMNS,
@@ -136,7 +137,11 @@ export interface HoldersHeadlessDependencies {
136
137
 
137
138
  const defaultDependencies: HoldersHeadlessDependencies = {
138
139
  loadSnapshot: (symbol, _args, ctx) => loadHolderSnapshot(ctx.marketData, symbol),
139
- loadBeneficialOwners: (symbol, request, ctx) => fetchBeneficialOwners(symbol, request, {
140
+ // A listing abroad names its venue and company, so it never reads a US namesake's owners.
141
+ loadBeneficialOwners: async (symbol, request, ctx) => fetchBeneficialOwners(symbol, {
142
+ ...request,
143
+ listing: await resolveHeadlessIssuerListing(ctx, symbol),
144
+ }, {
140
145
  client: ctx.apiClient,
141
146
  signal: ctx.signal,
142
147
  }),
@@ -43,6 +43,7 @@ import {
43
43
  import { loadHolderData } from "./client";
44
44
  import { HoldersTreemap } from "./treemap";
45
45
  import { BeneficialOwnersView } from "./beneficial-view";
46
+ import { useIssuerListing } from "../shared/ticker-request";
46
47
  import type { HolderColumn, HolderColumnId, HolderRow, SortPreference, ViewMode } from "./types";
47
48
  import { loadHolder13FMatches, type Holder13FMatch } from "./thirteenf-match";
48
49
  import { useSampledValue, useTickerQuoteStream } from "../../../state/hooks/live-ticker-financials";
@@ -65,6 +66,7 @@ export function HoldersView({ focused, width, height }: { focused: boolean; widt
65
66
  const fundMatchAbortRef = useRef<AbortController | null>(null);
66
67
 
67
68
  const exchange = ticker?.metadata.exchange ?? "";
69
+ const issuerListing = useIssuerListing(ticker);
68
70
  const loadHolders = useMemo(() => symbol ? async (forceRefresh: boolean) => {
69
71
  if (!dataProvider) throw new Error("Holder data unavailable");
70
72
  return loadHolderData(dataProvider, symbol, exchange, forceRefresh ? { cacheMode: "refresh" } : undefined);
@@ -337,6 +339,7 @@ export function HoldersView({ focused, width, height }: { focused: boolean; widt
337
339
  focused={focused}
338
340
  width={width}
339
341
  symbol={symbol ?? null}
342
+ listing={issuerListing}
340
343
  holderRows={rows}
341
344
  fundMatches={fundMatches}
342
345
  onCycleView={toggleView}
@@ -22,7 +22,7 @@ import {
22
22
  type StatItem,
23
23
  } from "../../../components";
24
24
  import { useDebouncedPluginPaneState, usePluginPaneState } from "../../runtime";
25
- import { isUsEquityTicker } from "../../../utils/sec";
25
+ import { mayBeUsEquityTicker } from "../../../utils/sec";
26
26
  import { formatCompact } from "../../../utils/format";
27
27
  import { nextHeaderSort } from "../../../utils/sort-values";
28
28
  import { formatShortDate } from "../../../utils/datetime-format";
@@ -130,7 +130,7 @@ function InsiderView({ width, height, focused }: { width: number; height: number
130
130
  [setOpenItemIdState],
131
131
  );
132
132
  const [sort, setSort] = useState<StackSortPreference<InsiderColumnId>>({ columnId: "date", direction: "desc" });
133
- const eligibleTicker = isUsEquityTicker(ticker);
133
+ const eligibleTicker = mayBeUsEquityTicker(ticker);
134
134
  const instrument = instrumentFromTicker(ticker, ticker?.metadata.ticker ?? null);
135
135
 
136
136
  const filingsEntry = useSecFilingsQuery(
@@ -458,7 +458,7 @@ export const insiderModule: PluginModule = {
458
458
  description: "Insider transaction activity for the selected ticker.",
459
459
  keywords: ["insider", "form 4", "ownership", "transactions", "ins"],
460
460
  shortcut: "INS",
461
- canCreate: (_context, options) => !options?.ticker || isUsEquityTicker(options.ticker),
461
+ canCreate: (_context, options) => !options?.ticker || mayBeUsEquityTicker(options.ticker),
462
462
  }),
463
463
  headless: insiderHeadless,
464
464
  },
@@ -471,7 +471,7 @@ export const insiderModule: PluginModule = {
471
471
  order: 47,
472
472
  component: InsiderView,
473
473
  instruments: ["equity"],
474
- isVisible: ({ ticker }) => isUsEquityTicker(ticker),
474
+ isVisible: ({ ticker }) => mayBeUsEquityTicker(ticker),
475
475
  });
476
476
  },
477
477
  };