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
@@ -3,6 +3,8 @@ import type {
3
3
  GloomPlugin,
4
4
  GloomPluginContext,
5
5
  } from "../../types/plugin";
6
+ import { pluginContextInNamespace } from "../registry/plugin-state";
7
+ import { withPluginStateNamespace } from "../runtime/context";
6
8
 
7
9
  type PluginMetadataKey =
8
10
  | "id"
@@ -21,6 +23,22 @@ type PluginMetadata = Pick<GloomPlugin, PluginMetadataKey>;
21
23
  // Brokers and declared hosts belong to external plugins; no built-in module contributes them.
22
24
  export type PluginModule = Omit<GloomPlugin, PluginMetadataKey | "broker" | "hosts">;
23
25
 
26
+ /** A module with a say in how it is composed. A bare module takes the defaults. */
27
+ interface PluginModuleEntry {
28
+ module: PluginModule;
29
+ /**
30
+ * The state namespace the module keeps when it sits in a plugin whose own
31
+ * namespace is different: its panes, tabs and slots read pane, plugin and
32
+ * config state there, and its setup gets persistence and caches there. Set
33
+ * it when a module moves to another plugin so its users keep their state.
34
+ */
35
+ stateId?: string;
36
+ /** Left out of the plugin the desktop backend process runs. */
37
+ rendererOnly?: boolean;
38
+ }
39
+
40
+ type PluginModuleInput = PluginModule | PluginModuleEntry;
41
+
24
42
  const HANDLED_MODULE_KEYS = [
25
43
  "cliCommands",
26
44
  "setup",
@@ -36,7 +54,43 @@ const ALL_MODULE_KEYS_HANDLED: MissingModuleKey extends never ? true : never = t
36
54
  void ALL_MODULE_KEYS_HANDLED;
37
55
 
38
56
  interface CompositePluginOptions extends PluginMetadata {
39
- modules: readonly PluginModule[];
57
+ modules: readonly PluginModuleInput[];
58
+ }
59
+
60
+ function moduleEntry(input: PluginModuleInput): PluginModuleEntry {
61
+ return "module" in input ? input : { module: input };
62
+ }
63
+
64
+ /**
65
+ * The module as it runs under a namespace other than its plugin's: every
66
+ * surface it renders and the context its setup receives are moved there,
67
+ * while what it contributes still belongs to the plugin.
68
+ */
69
+ function moduleInNamespace(module: PluginModule, stateId: string): PluginModule {
70
+ const slots = module.slots && Object.fromEntries(Object.entries(module.slots).map(([slotName, renderer]) => [
71
+ slotName,
72
+ renderer && withPluginStateNamespace(stateId, renderer as (props: unknown) => ReactNode),
73
+ ])) as PluginModule["slots"];
74
+ return {
75
+ ...module,
76
+ ...(module.panes ? {
77
+ panes: module.panes.map((pane) => ({ ...pane, component: withPluginStateNamespace(stateId, pane.component) })),
78
+ } : {}),
79
+ ...(slots ? { slots } : {}),
80
+ ...(module.setup ? {
81
+ setup: (ctx: GloomPluginContext) => {
82
+ const scoped = pluginContextInNamespace(ctx, stateId);
83
+ return module.setup!({
84
+ ...scoped,
85
+ registerPane: (pane) => ctx.registerPane({ ...pane, component: withPluginStateNamespace(stateId, pane.component) }),
86
+ registerTickerResearchTab: (tab) => ctx.registerTickerResearchTab({
87
+ ...tab,
88
+ component: withPluginStateNamespace(stateId, tab.component),
89
+ }),
90
+ });
91
+ },
92
+ } : {}),
93
+ };
40
94
  }
41
95
 
42
96
  function composeSlots(modules: readonly PluginModule[]): GloomPlugin["slots"] {
@@ -65,14 +119,23 @@ function composeSlots(modules: readonly PluginModule[]): GloomPlugin["slots"] {
65
119
  return slots as GloomPlugin["slots"];
66
120
  }
67
121
 
122
+ const compositions = new WeakMap<GloomPlugin, CompositePluginOptions>();
123
+ const backendVariants = new WeakMap<GloomPlugin, GloomPlugin>();
124
+
68
125
  /**
69
126
  * Combines implementation modules under one real plugin identity.
70
127
  *
71
128
  * Modules deliberately have no identity or toggle metadata. Every contribution
72
- * is owned, persisted, enabled, and disposed through the returned parent plugin.
129
+ * is owned, enabled, and disposed through the returned parent plugin, and
130
+ * persisted under its namespace unless the module's entry keeps another.
73
131
  */
74
132
  export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlugin {
75
- const { modules, ...metadata } = options;
133
+ const { modules: inputs, ...metadata } = options;
134
+ const pluginStateId = metadata.stateId ?? metadata.id;
135
+ const modules = inputs.map((input) => {
136
+ const { module, stateId } = moduleEntry(input);
137
+ return stateId && stateId !== pluginStateId ? moduleInNamespace(module, stateId) : module;
138
+ });
76
139
  const cliCommands = modules.flatMap((module) => module.cliCommands ?? []);
77
140
  const panes = modules.flatMap((module) => module.panes ?? []);
78
141
  const paneTemplates = modules.flatMap((module) => module.paneTemplates ?? []);
@@ -80,7 +143,7 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
80
143
  const slots = composeSlots(modules);
81
144
  let startedModules: PluginModule[] = [];
82
145
 
83
- return {
146
+ const plugin: GloomPlugin = {
84
147
  ...metadata,
85
148
  ...(cliCommands.length > 0 ? { cliCommands } : {}),
86
149
  ...(panes.length > 0 ? { panes } : {}),
@@ -114,4 +177,24 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
114
177
  if (firstError) throw firstError;
115
178
  },
116
179
  };
180
+ compositions.set(plugin, options);
181
+ return plugin;
182
+ }
183
+
184
+ /**
185
+ * The plugin as the desktop backend process runs it: the same identity
186
+ * without its renderer-only modules. Anything else comes back unchanged.
187
+ */
188
+ export function withoutRendererOnlyModules(plugin: GloomPlugin): GloomPlugin {
189
+ const options = compositions.get(plugin);
190
+ if (!options?.modules.some((input) => moduleEntry(input).rendererOnly)) return plugin;
191
+ let variant = backendVariants.get(plugin);
192
+ if (!variant) {
193
+ variant = composeBuiltinPlugin({
194
+ ...options,
195
+ modules: options.modules.filter((input) => !moduleEntry(input).rendererOnly),
196
+ });
197
+ backendVariants.set(plugin, variant);
198
+ }
199
+ return variant;
117
200
  }
@@ -11,7 +11,7 @@ import type { AppConfig } from "../../../../types/config";
11
11
  import type { CliCommandContext, CliCommandDef } from "../../../../types/plugin";
12
12
  import type { Portfolio, TickerRecord } from "../../../../types/ticker";
13
13
  import type { TickerRepository } from "../../../../data/ticker-repository";
14
- import { canonicalExchange, parsePublicTickerKey } from "../../../../utils/exchanges";
14
+ import { canonicalTickerKey, parsePublicTickerKey } from "../../../../utils/exchanges";
15
15
  import {
16
16
  addTickerToPortfolio,
17
17
  adoptFirstPositionCurrency,
@@ -38,7 +38,6 @@ import {
38
38
  } from "../allocation";
39
39
  import { findCachedPortfolioAccount } from "../cached-account";
40
40
  import {
41
- describeTicker,
42
41
  failPortfolioCommand,
43
42
  isCashArgument,
44
43
  listingFields,
@@ -204,12 +203,19 @@ async function setPositionCommand(
204
203
  await store.saveTicker(result.ticker);
205
204
  const adopted = firstPosition ? adoptFirstPositionCurrency(config, portfolio.id, currency) : null;
206
205
  if (adopted) await saveConfig(adopted);
207
- console.log(cliStyles.success(`Set position for ${describeTicker(result.ticker)} in "${portfolio.name}".`));
208
- console.log(renderStats([
209
- ["Shares", formatMarketQuantity(shares, { assetCategory: result.ticker.metadata.assetCategory })],
210
- ["Average Cost", formatMarketCostWithCurrency(avgCost, currency, { assetCategory: result.ticker.metadata.assetCategory })],
211
- ["Currency", currency],
212
- ]));
206
+ const saved = savedListingName(result.ticker);
207
+ ctx.printResult({
208
+ data: { changed: true, ...listingFields(saved), portfolio: portfolio.name, shares, avgCost, currency },
209
+ }, {
210
+ text: () => [
211
+ cliStyles.success(`Set position for ${saved.label} in "${portfolio.name}".`),
212
+ renderStats([
213
+ ["Shares", formatMarketQuantity(shares, { assetCategory: result.ticker.metadata.assetCategory })],
214
+ ["Average Cost", formatMarketCostWithCurrency(avgCost, currency, { assetCategory: result.ticker.metadata.assetCategory })],
215
+ ["Currency", currency],
216
+ ]),
217
+ ].join("\n"),
218
+ });
213
219
  } catch (error) {
214
220
  failPortfolioCommand(ctx, error, `Failed to set position for ${symbol} in "${portfolioName}".`);
215
221
  }
@@ -275,7 +281,8 @@ async function clearCashCommand(name: string, ctx: CliCommandContext) {
275
281
  });
276
282
  }
277
283
 
278
- function printTargetSumNote(config: AppConfig, portfolioId: string, tickers: readonly TickerRecord[]) {
284
+ /** The note under a target change when the targets do not add up to the whole portfolio, else null. */
285
+ function targetSumNote(config: AppConfig, portfolioId: string, tickers: readonly TickerRecord[]): string | null {
279
286
  const portfolio = config.portfolios.find((entry) => entry.id === portfolioId);
280
287
  const listed = new Set([CASH_SYMBOL, ...tickers.filter((ticker) => ticker.metadata.portfolios.includes(portfolioId)).map((ticker) => ticker.metadata.ticker)]);
281
288
  let sum: number | null = null;
@@ -283,24 +290,32 @@ function printTargetSumNote(config: AppConfig, portfolioId: string, tickers: rea
283
290
  if (listed.has(symbol)) sum = (sum ?? 0) + weight;
284
291
  }
285
292
  const note = describeTargetSum(sum);
286
- if (note) console.log(cliStyles.muted(note));
293
+ return note ? cliStyles.muted(note) : null;
287
294
  }
288
295
 
289
- /** The portfolio's own ticker for a symbol, without a search when it is already listed. */
296
+ /**
297
+ * The portfolio's own row for a listing, without a search when it is already
298
+ * listed. A named exchange (SAN:EPA, or SAN --exchange EPA) matches only a row
299
+ * on that venue, whatever key the row is saved under: a bare SAN row is one
300
+ * listing, Paris or New York, never both. A bare SAN takes the row saved under
301
+ * that key, else any row of the symbol.
302
+ */
290
303
  async function findListedTicker(store: TickerRepository, portfolio: Portfolio, symbol: string, exchange?: string): Promise<TickerRecord | null> {
291
304
  const listing = parseListingArg(symbol, exchange);
292
- const typed = symbol.trim().toUpperCase();
293
305
  const listed = (await store.loadAllTickers()).filter((ticker) => ticker.metadata.portfolios.includes(portfolio.id));
306
+ const savedAs = (ticker: TickerRecord, key: string) => ticker.metadata.ticker.toUpperCase() === key;
307
+ if (!listing.exchange) {
308
+ const sameSymbol = listed.filter((ticker) => parsePublicTickerKey(ticker.metadata.ticker).symbol === listing.symbol);
309
+ return sameSymbol.find((ticker) => savedAs(ticker, listing.symbol)) ?? sameSymbol[0] ?? null;
310
+ }
311
+ const onVenue = listed.filter((ticker) => canonicalTickerKey(ticker.metadata.ticker, ticker.metadata.exchange) === listing.key);
294
312
  // The key as saved (VTI:XMEX), else the same listing under another spelling of its venue (VTI:BMV).
295
- return listed.find((ticker) => [typed, listing.key].includes(ticker.metadata.ticker.toUpperCase()))
296
- ?? listed.find((ticker) => {
297
- const saved = parsePublicTickerKey(ticker.metadata.ticker);
298
- return saved.symbol === listing.symbol
299
- && (!listing.exchange || canonicalExchange(saved.exchange ?? ticker.metadata.exchange) === listing.exchange);
300
- })
301
- ?? null;
313
+ return onVenue.find((ticker) => savedAs(ticker, listing.key)) ?? onVenue[0] ?? null;
302
314
  }
303
315
 
316
+ /** What a target command reports for the cash line, in the fields a listing has. */
317
+ const CASH_LISTING = { key: CASH_SYMBOL, symbol: CASH_SYMBOL, exchange: "", name: null };
318
+
304
319
  async function setTargetCommand(rest: string[], exchange: string | undefined, ctx: CliCommandContext) {
305
320
  const rawWeight = rest.at(-1);
306
321
  const symbol = rest.at(-2);
@@ -313,8 +328,12 @@ async function setTargetCommand(rest: string[], exchange: string | undefined, ct
313
328
  if (isCashArgument(symbol, exchange)) {
314
329
  const next = setPortfolioTargetWeight(config, portfolio.id, CASH_SYMBOL, weight);
315
330
  await saveConfig(next);
316
- console.log(cliStyles.success(`Set the cash target in "${portfolio.name}" to ${formatAllocationWeight(weight)}.`));
317
- printTargetSumNote(next, portfolio.id, await store.loadAllTickers());
331
+ const note = targetSumNote(next, portfolio.id, await store.loadAllTickers());
332
+ ctx.printResult({
333
+ data: { changed: true, ...listingFields(CASH_LISTING), portfolio: portfolio.name, targetWeight: weight },
334
+ }, {
335
+ text: () => [cliStyles.success(`Set the cash target in "${portfolio.name}" to ${formatAllocationWeight(weight)}.`), note].filter(Boolean).join("\n"),
336
+ });
318
337
  return;
319
338
  }
320
339
  let ticker = await findListedTicker(store, portfolio, symbol, exchange);
@@ -324,7 +343,7 @@ async function setTargetCommand(rest: string[], exchange: string | undefined, ct
324
343
  if (!resolved.metadata.portfolios.includes(portfolio.id)) {
325
344
  if (!isManualPortfolio(portfolio)) {
326
345
  throw new PortfolioCliError(
327
- `${describeTicker(resolved)} is not in "${portfolio.name}".`,
346
+ `${savedListingName(resolved).label} is not in "${portfolio.name}".`,
328
347
  "A broker portfolio lists what the broker holds; set targets on its holdings.",
329
348
  );
330
349
  }
@@ -338,9 +357,17 @@ async function setTargetCommand(rest: string[], exchange: string | undefined, ct
338
357
  }
339
358
  const next = setPortfolioTargetWeight(config, portfolio.id, ticker.metadata.ticker, weight);
340
359
  await saveConfig(next);
341
- if (added) console.log(cliStyles.success(`Added ${describeTicker(ticker)} to "${portfolio.name}".`));
342
- console.log(cliStyles.success(`Set the target for ${describeTicker(ticker)} in "${portfolio.name}" to ${formatAllocationWeight(weight)}.`));
343
- printTargetSumNote(next, portfolio.id, await store.loadAllTickers());
360
+ const saved = savedListingName(ticker);
361
+ const note = targetSumNote(next, portfolio.id, await store.loadAllTickers());
362
+ ctx.printResult({
363
+ data: { changed: true, ...listingFields(saved), portfolio: portfolio.name, targetWeight: weight, added },
364
+ }, {
365
+ text: () => [
366
+ added ? cliStyles.success(`Added ${saved.label} to "${portfolio.name}".`) : null,
367
+ cliStyles.success(`Set the target for ${saved.label} in "${portfolio.name}" to ${formatAllocationWeight(weight)}.`),
368
+ note,
369
+ ].filter(Boolean).join("\n"),
370
+ });
344
371
  } catch (error) {
345
372
  failPortfolioCommand(ctx, error, `Failed to set the target for ${symbol} in "${name}".`);
346
373
  }
@@ -355,21 +382,28 @@ async function clearTargetCommand(rest: string[], exchange: string | undefined,
355
382
  const whole = config.portfolios.some((entry) => [entry.id, entry.name].some((value) => value.toLowerCase() === rest.join(" ").trim().toLowerCase()));
356
383
  const portfolio = requirePortfolio(config, whole ? rest.join(" ") : rest.slice(0, -1).join(" "));
357
384
  const rawSymbol = whole ? undefined : rest.at(-1);
358
- const symbol = rawSymbol == null ? undefined
359
- : isCashArgument(rawSymbol, exchange) ? CASH_SYMBOL
360
- : (await findListedTicker(store, portfolio, rawSymbol, exchange))?.metadata.ticker ?? parseListingArg(rawSymbol, exchange).key;
385
+ const cash = rawSymbol != null && isCashArgument(rawSymbol, exchange);
386
+ const listing = rawSymbol == null || cash ? null : parseListingArg(rawSymbol, exchange);
387
+ // The row the listing is: the portfolio's own, else any saved one; a target is kept under that row's key.
388
+ const ticker = listing ? await findListedTicker(store, portfolio, rawSymbol!, exchange) ?? await loadSavedListing(store, listing) : null;
389
+ const saved = ticker ? savedListingName(ticker) : null;
390
+ const symbol = cash ? CASH_SYMBOL : ticker?.metadata.ticker ?? listing?.key;
361
391
  const { config: next, removed } = clearPortfolioTargetWeights(config, portfolio.id, symbol);
392
+ const fields = cash ? listingFields(CASH_LISTING) : saved ? listingFields(saved) : listing ? listingFields({ ...listing, name: null }) : {};
393
+ const named = saved?.label ?? listing?.key ?? CASH_SYMBOL;
394
+ const data = { changed: removed.length > 0, ...fields, portfolio: portfolio.name, removedTargets: removed.length };
362
395
  if (removed.length === 0) {
363
- console.log(cliStyles.warning(symbol ? `"${portfolio.name}" has no target for ${symbol}.` : `"${portfolio.name}" has no targets.`));
396
+ ctx.printResult({ data }, { text: () => cliStyles.warning(symbol ? `"${portfolio.name}" has no target for ${named}.` : `"${portfolio.name}" has no targets.`) });
364
397
  return;
365
398
  }
366
399
  await saveConfig(next);
367
- if (symbol) {
368
- const ticker = symbol === CASH_SYMBOL ? null : await store.loadTicker(symbol);
369
- console.log(cliStyles.success(`Cleared the ${symbol === CASH_SYMBOL ? "cash target" : `target for ${ticker ? describeTicker(ticker) : symbol}`} in "${portfolio.name}".`));
370
- } else {
371
- console.log(cliStyles.success(`Cleared ${removed.length} target${removed.length === 1 ? "" : "s"} in "${portfolio.name}".`));
372
- }
400
+ ctx.printResult({ data }, {
401
+ text: () => cliStyles.success(
402
+ symbol
403
+ ? `Cleared the ${cash ? "cash target" : `target for ${named}`} in "${portfolio.name}".`
404
+ : `Cleared ${removed.length} target${removed.length === 1 ? "" : "s"} in "${portfolio.name}".`,
405
+ ),
406
+ });
373
407
  } catch (error) {
374
408
  failPortfolioCommand(ctx, error, `Failed to clear targets in "${rest.join(" ")}".`);
375
409
  }
@@ -407,7 +441,8 @@ async function showTargetsCommand(name: string, ctx: CliCommandContext) {
407
441
  [{ header: "Ticker" }, { header: "Name" }, { header: "Target", align: "right" }],
408
442
  rows.map((row) => [row.symbol, row.listed ? row.name ?? "" : cliStyles.muted("not in this portfolio"), formatAllocationWeight(row.targetWeight)]),
409
443
  ));
410
- printTargetSumNote(config, portfolio.id, tickers);
444
+ const note = targetSumNote(config, portfolio.id, tickers);
445
+ if (note) console.log(note);
411
446
  } catch (error) {
412
447
  failPortfolioCommand(ctx, error, `Failed to show targets for "${name}".`);
413
448
  }
@@ -12,7 +12,16 @@ import { currencyMinorDigits, formatMarketCostWithCurrency, formatMarketPriceWit
12
12
  import { resolvePriceBasis } from "../../../../market-data/market/price-basis";
13
13
  import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue, resolvePortfolioPositionPnl } from "../position-metrics";
14
14
  import { resolvePortfolioTotalsCurrency } from "../summary/totals";
15
- import { exchangeShortName, getActiveQuoteDisplay } from "../../../../market-data/market/status";
15
+ import {
16
+ EXTENDED_SESSION_LABELS,
17
+ exchangeShortName,
18
+ getActiveQuoteDisplay,
19
+ getExtendedSessionDisplay,
20
+ type ActiveQuoteDisplay,
21
+ type ExtendedSession,
22
+ type ExtendedSessionDisplay,
23
+ } from "../../../../market-data/market/status";
24
+ import { headlineQuoteDisplay } from "../column-values";
16
25
  import type { AppConfig } from "../../../../types/config";
17
26
  import type { DataProvider } from "../../../../types/data-provider";
18
27
  import type { CliCommandContext } from "../../../../types/plugin";
@@ -47,6 +56,38 @@ function priceFormatOptions(quote: Parameters<typeof quoteFormatOptions>[0] & {
47
56
  : options;
48
57
  }
49
58
 
59
+ /**
60
+ * What a collection's Last and Chg show, as the PF and watchlist panes do: the
61
+ * traded price until the regular close, then that close and its move, with a
62
+ * pre-market or after-hours print beside them, measured from that close.
63
+ * Market value and P&L keep the live price.
64
+ */
65
+ export function collectionQuoteHeadline(quote: CollectionQuote | undefined): {
66
+ headline: ActiveQuoteDisplay | null;
67
+ extended: ExtendedSessionDisplay | null;
68
+ } {
69
+ const traded = getActiveQuoteDisplay(quote);
70
+ const headline = traded && Number.isFinite(traded.price) ? headlineQuoteDisplay(traded, quote) : null;
71
+ const extended = headline ? getExtendedSessionDisplay(quote) : null;
72
+ return { headline, extended: extended && Number.isFinite(extended.price) ? extended : null };
73
+ }
74
+
75
+ /** The extended print as export fields: all null without one. */
76
+ export function extendedQuoteFields(extended: ExtendedSessionDisplay | null) {
77
+ return {
78
+ extendedSession: extended?.session ?? null,
79
+ extendedPrice: extended?.price ?? null,
80
+ extendedChange: extended?.change ?? null,
81
+ extendedChangePercent: extended?.changePercent ?? null,
82
+ };
83
+ }
84
+
85
+ /** The extended sessions among the rows' prints, in the order their columns go. */
86
+ export function extendedSessionsOf(sessions: Iterable<ExtendedSession | null | undefined>): ExtendedSession[] {
87
+ const present = new Set(sessions);
88
+ return (["PRE", "POST"] as const).filter((session) => present.has(session));
89
+ }
90
+
50
91
  export function renderCollectionOverview(config: AppConfig, tickers: TickerRecord[]): string {
51
92
  const blocks: string[] = [];
52
93
 
@@ -143,7 +184,12 @@ export async function loadCollectionQuotes(
143
184
  interface PortfolioPositionValue {
144
185
  ticker: TickerRecord;
145
186
  quote: CollectionQuote | undefined;
187
+ /** The live print, which values the position. */
146
188
  activeQuote: ReturnType<typeof getActiveQuoteDisplay> | null;
189
+ /** Last and Chg as shown: the regular session's (collectionQuoteHeadline). */
190
+ headline: ActiveQuoteDisplay | null;
191
+ /** A pre-market or after-hours print, from the close in `headline`. */
192
+ extended: ExtendedSessionDisplay | null;
147
193
  /** Null for a ticker kept in the portfolio without an open position. */
148
194
  position: TickerPosition | null;
149
195
  metrics: ReturnType<typeof getPortfolioPositionMetrics> | null;
@@ -199,14 +245,15 @@ export async function valuePortfolioPositions({
199
245
  const positions = ticker.metadata.positions.filter((position) => position.portfolio === id && position.shares !== 0);
200
246
  const displayedQuote = getActiveQuoteDisplay(quote);
201
247
  const activeQuote = displayedQuote && Number.isFinite(displayedQuote.price) ? displayedQuote : null;
248
+ const { headline, extended } = collectionQuoteHeadline(quote);
202
249
 
203
250
  if (positions.length === 0) {
204
251
  const quoteCurrency = quote?.currency || ticker.metadata.currency || baseCurrency;
205
252
  const unitPrice = activeQuote ? known(await toBase(activeQuote.price, quoteCurrency)) : null;
206
253
  holdings.push({ symbol: ticker.metadata.ticker, held: false, marketValue: 0, units: 0, unitPrice });
207
- values.push({ ticker, quote, activeQuote, position: null, metrics: null, pnl: null,
254
+ values.push({ ticker, quote, activeQuote, headline, extended, position: null, metrics: null, pnl: null,
208
255
  row: { symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange, shares: null, avgCost: null,
209
- positionCurrency: null, quotePrice: known(activeQuote?.price), quoteCurrency: quote?.currency ?? null,
256
+ positionCurrency: null, quotePrice: known(headline?.price), ...extendedQuoteFields(extended), quoteCurrency: quote?.currency ?? null,
210
257
  costBasis: null, marketValue: null, unrealizedPnl: null, baseCurrency: currency } });
211
258
  continue;
212
259
  }
@@ -238,11 +285,12 @@ export async function valuePortfolioPositions({
238
285
  holding.marketValue = holding.marketValue == null || marketValue == null ? null : holding.marketValue + marketValue;
239
286
  holding.units += metrics.totalShares;
240
287
  holding.unitPrice ??= valuationQuote ? known(await toBase(valuationQuote.price, quoteCurrency)) : null;
241
- values.push({ ticker, quote, activeQuote, position, metrics, pnl,
288
+ values.push({ ticker, quote, activeQuote, headline, extended, position, metrics, pnl,
242
289
  row: { symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange,
243
290
  shares: metrics.totalShares, avgCost: known(position.avgCost), positionCurrency,
244
291
  priceBasis: position.priceBasis ?? null, quantityUnit: metrics.priceBasis === "percent-of-par" ? "face" : null, quotePriceBasis: quote?.priceBasis ?? null,
245
- quotePrice: known(activeQuote?.price), quoteCurrency, quoteAsOf: quote?.lastUpdated ?? null,
292
+ // Last as shown, the regular close after it; market value and P&L are at the live price.
293
+ quotePrice: known(headline?.price), ...extendedQuoteFields(extended), quoteCurrency, quoteAsOf: quote?.lastUpdated ?? null,
246
294
  costBasis: known(direction * costBasisBase), marketValue,
247
295
  unrealizedPnl: known(pnl), baseCurrency: currency, dateAcquired: position.dateAcquired ?? null,
248
296
  pnlBasis: selectedPnl.basis, brokerUnrealizedPnl: known(position.unrealizedPnl), brokerPnlCurrency: positionCurrency, brokerPnlAsOf: null } });
@@ -296,6 +344,18 @@ function signed(text: string, value: number | null | undefined): string {
296
344
 
297
345
  const NOT_AVAILABLE = "n/a";
298
346
 
347
+ /** One cell per extended column: the print and its move from the close, as `quote` prints it, or blank. */
348
+ function extendedCells(
349
+ sessions: readonly ExtendedSession[],
350
+ extended: ExtendedSessionDisplay | null,
351
+ quote: CollectionQuote | undefined,
352
+ assetCategory: string | undefined,
353
+ ): string[] {
354
+ return sessions.map((session) => extended?.session === session && quote
355
+ ? `${formatMarketPriceWithCurrency(extended.price, quote.currency, priceFormatOptions(quote, assetCategory))} ${colorBySign(formatPercentRaw(extended.changePercent), extended.change)}`
356
+ : "");
357
+ }
358
+
299
359
  async function showCollectionWithMarketData(
300
360
  name: string,
301
361
  ctx: CliCommandContext,
@@ -436,18 +496,20 @@ async function showCollectionWithMarketData(
436
496
  return [weight, target, drift, trade, tradeValue];
437
497
  };
438
498
 
439
- const rows: string[][] = valued.map(({ ticker, quote, activeQuote, position, metrics, pnl, row }, index) => {
440
- const priceText = quote && activeQuote
441
- ? colorBySign(formatMarketPriceWithCurrency(activeQuote.price, quote.currency, priceFormatOptions(quote, ticker.metadata.assetCategory)), activeQuote.change)
499
+ const extendedSessions = extendedSessionsOf(valued.map(({ extended }) => extended?.session));
500
+ const rows: string[][] = valued.map(({ ticker, quote, headline, extended, position, metrics, pnl, row }, index) => {
501
+ const priceText = quote && headline
502
+ ? colorBySign(formatMarketPriceWithCurrency(headline.price, quote.currency, priceFormatOptions(quote, ticker.metadata.assetCategory)), headline.change)
442
503
  : "—";
443
- const changeText = activeQuote ? colorBySign(formatPercentRaw(activeQuote.changePercent), activeQuote.change) : "—";
504
+ const changeText = headline ? colorBySign(formatPercentRaw(headline.changePercent), headline.change) : "—";
505
+ const quoteCells = [priceText, changeText, ...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory)];
444
506
  const exported = positionsExport[index]!;
445
507
  const figures = exported.weight != null || exported.targetWeight != null || exported.tradeValue != null
446
508
  ? { weight: exported.weight, targetWeight: exported.targetWeight, drift: exported.drift, tradeValue: exported.tradeValue, tradeUnits: exported.tradeShares }
447
509
  : undefined;
448
510
  const symbolUnits = allocationBySymbol.get(ticker.metadata.ticker)?.units ?? 0;
449
511
  if (!position || !metrics) {
450
- return [ticker.metadata.ticker, priceText, changeText, "—", "—", "—",
512
+ return [ticker.metadata.ticker, ...quoteCells, "—", "—", "—",
451
513
  ...allocationCells(figures, { unpriced: false, units: symbolUnits, assetCategory: ticker.metadata.assetCategory }), "—"];
452
514
  }
453
515
  const positionCurrency = metrics.positionCurrency;
@@ -455,8 +517,7 @@ async function showCollectionWithMarketData(
455
517
  const unpriced = marketValue == null;
456
518
  return [
457
519
  ticker.metadata.ticker,
458
- priceText,
459
- changeText,
520
+ ...quoteCells,
460
521
  formatMarketQuantity(metrics.totalShares, { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, quantityCurrency: positionCurrency }),
461
522
  formatMarketCostWithCurrency(position.avgCost, positionCurrency, { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis }),
462
523
  unpriced ? NOT_AVAILABLE : wholeMoney(marketValue),
@@ -466,12 +527,12 @@ async function showCollectionWithMarketData(
466
527
  });
467
528
  if (cashExport) {
468
529
  const label = cashExport.currency && cashExport.currency !== currency ? `${CASH_SYMBOL} ${cashExport.currency}` : CASH_SYMBOL;
469
- rows.push([label, "", "", "", "", cashExport.value == null ? NOT_AVAILABLE : wholeMoney(cashExport.value),
530
+ rows.push([label, "", "", ...extendedSessions.map(() => ""), "", "", cashExport.value == null ? NOT_AVAILABLE : wholeMoney(cashExport.value),
470
531
  ...allocationCells(cashExport, { unpriced: cashExport.value == null }), ""]);
471
532
  }
472
533
  const total = allocation.total;
473
534
  rows.push([
474
- cliStyles.bold("TOTAL"), "", "", "", "",
535
+ cliStyles.bold("TOTAL"), "", "", ...extendedSessions.map(() => ""), "", "",
475
536
  cliStyles.bold(wholeMoney(total)),
476
537
  total != null && total > 0 ? formatAllocationWeight(100) : "—",
477
538
  ...(showTargets ? [formatAllocationWeight(allocation.targetSum), "", "", ""] : []),
@@ -485,6 +546,8 @@ async function showCollectionWithMarketData(
485
546
  { header: "Ticker" },
486
547
  { header: "Last", align: "right", optional: true, dropPriority: 1 },
487
548
  { header: "Chg", align: "right", optional: true, dropPriority: 2 },
549
+ // Before Chg, after Avg Cost, which is right of it at the same priority.
550
+ ...extendedSessions.map((session) => ({ header: EXTENDED_SESSION_LABELS[session], align: "right" as const, optional: true, dropPriority: 3 })),
488
551
  { header: quantityHeader, align: "right" },
489
552
  { header: "Avg Cost", align: "right", optional: true, dropPriority: 3 },
490
553
  { header: "Value", align: "right" },
@@ -522,12 +585,15 @@ async function showCollectionWithMarketData(
522
585
  for (const note of notes) console.log(note);
523
586
  } else {
524
587
  const rows: string[][] = [];
588
+ const shown = new Map(filtered.map((ticker) => [ticker, collectionQuoteHeadline(quotes.get(ticker.metadata.ticker))]));
589
+ const extendedSessions = extendedSessionsOf([...shown.values()].map(({ extended }) => extended?.session));
525
590
  for (const ticker of filtered) {
526
591
  const quote = quotes.get(ticker.metadata.ticker);
527
- const priceText = quote
528
- ? colorBySign(formatMarketPriceWithCurrency(quote.price, quote.currency, priceFormatOptions(quote, ticker.metadata.assetCategory)), quote.change)
592
+ const { headline, extended } = shown.get(ticker)!;
593
+ const priceText = quote && headline
594
+ ? colorBySign(formatMarketPriceWithCurrency(headline.price, quote.currency, priceFormatOptions(quote, ticker.metadata.assetCategory)), headline.change)
529
595
  : "—";
530
- const changeText = quote ? colorBySign(formatPercentRaw(quote.changePercent), quote.change) : "—";
596
+ const changeText = headline ? colorBySign(formatPercentRaw(headline.changePercent), headline.change) : "—";
531
597
  const marketCapText = quote?.marketCap != null
532
598
  ? `${formatCompact(await toBase(quote.marketCap, quote.currency || ticker.metadata.currency || currency))} ${currency}`
533
599
  : "—";
@@ -537,6 +603,7 @@ async function showCollectionWithMarketData(
537
603
  exchangeShortName(quote?.exchangeName, quote?.fullExchangeName) || ticker.metadata.exchange || "—",
538
604
  priceText,
539
605
  changeText,
606
+ ...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory),
540
607
  marketCapText,
541
608
  ]);
542
609
  }
@@ -547,7 +614,9 @@ async function showCollectionWithMarketData(
547
614
  { header: "Exchange" },
548
615
  { header: "Last", align: "right" },
549
616
  { header: "Chg", align: "right" },
550
- { header: "Mkt Cap", align: "right" },
617
+ ...extendedSessions.map((session) => ({ header: EXTENDED_SESSION_LABELS[session], align: "right" as const, optional: true, dropPriority: 1 })),
618
+ // A narrow terminal drops these first.
619
+ { header: "Mkt Cap", align: "right", optional: true, dropPriority: 2 },
551
620
  ],
552
621
  rows,
553
622
  ));
@@ -1,8 +1,6 @@
1
1
  import type { AppConfig } from "../../../../types/config";
2
2
  import type { CliCommandContext } from "../../../../types/plugin";
3
- import type { TickerRecord } from "../../../../types/ticker";
4
3
  import { parseListingArg, type SavedListingName } from "../../../../cli/listing-arg";
5
- import { isUsListingExchange } from "../../../../utils/exchanges";
6
4
  import { findPortfolio, isManualPortfolio } from "../mutations";
7
5
  import { CASH_SYMBOL } from "../allocation";
8
6
 
@@ -23,8 +21,8 @@ export function failPortfolioCommand(ctx: CliCommandContext, error: unknown, fal
23
21
  return ctx.fail(fallback);
24
22
  }
25
23
 
26
- /** What add and remove report about the listing they changed, as JSON and as the sentence's subject. */
27
- export function listingFields(saved: SavedListingName) {
24
+ /** What every collection command reports about the listing it changed, as JSON and as the sentence's subject. */
25
+ export function listingFields(saved: Pick<SavedListingName, "key" | "symbol" | "exchange" | "name">) {
28
26
  return { listing: saved.key, symbol: saved.symbol, exchange: saved.exchange || null, name: saved.name };
29
27
  }
30
28
 
@@ -72,15 +70,3 @@ export function rejectCashSymbol(symbol: string, exchange: string | undefined, p
72
70
  `Record cash with: gloomberb portfolio cash set "${portfolioName}" <amount> [currency]. For the stock that trades as CASH, name its exchange: CASH:NASDAQ.`,
73
71
  );
74
72
  }
75
-
76
- /** `VTI (Vanguard Total Stock Market ETF)`, with the venue and currency for a listing outside the US. */
77
- export function describeTicker(ticker: TickerRecord): string {
78
- const { ticker: symbol, name, exchange, currency } = ticker.metadata;
79
- const foreign = !!exchange && !isUsListingExchange(exchange);
80
- const details = [
81
- name?.trim(),
82
- foreign ? exchange : undefined,
83
- foreign && currency && currency.toUpperCase() !== "USD" ? currency.toUpperCase() : undefined,
84
- ].filter((part): part is string => !!part);
85
- return details.length > 0 ? `${symbol} (${details.join(", ")})` : symbol;
86
- }