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
@@ -1,4 +1,5 @@
1
1
  import { apiClient } from "../../api-client";
2
+ import { ApiRequestError } from "../../api-client/errors";
2
3
  import type { CliCommandDef } from "../../types/plugin";
3
4
  import { withCliServices } from "../context";
4
5
  import { formatCompact } from "../../utils/format";
@@ -10,16 +11,18 @@ import {
10
11
  type ScreenerCategory,
11
12
  } from "../../plugins/builtin/market-movers/screener";
12
13
  import { formatMoverPrice, moverReferencePrice } from "../../plugins/builtin/market-movers/model";
14
+ import { EXTENDED_SESSION_LABELS, getQuoteSessionFields, type ExtendedSession } from "../../market-data/market/status";
13
15
  import {
14
16
  COUNTRY_CYCLE,
15
17
  FILTER_CYCLE,
16
18
  loadCalendar,
17
19
  matchesCountry,
18
20
  matchesImpact,
21
+ shortCalendarEnd,
19
22
  type CountryFilter,
20
23
  type ImpactFilter,
21
24
  } from "../../plugins/builtin/econ/calendar-model";
22
- import { isoDate, rejectExtraArgs, requireArg, requireOneArg, takeOption } from "./command-utils";
25
+ import { isoDate, rejectExtraArgs, requireArg, requireOneArg, takeFlag, takeOption } from "./command-utils";
23
26
  import { buildCorrelationSeries } from "../../plugins/builtin/correlation/matrix/model";
24
27
  import { alignDailyCloses, correlateDailyCloses } from "../../plugins/builtin/correlation/compute";
25
28
  import { mixedSessionCloseNote } from "../../market-data/market/session-close-note";
@@ -39,20 +42,43 @@ const BASKET_NAMES = new Map<string, string>([
39
42
  ...SECTOR_COLLECTIONS.flatMap((collection) => collection.items.map((item) => [item.etf, item.name] as const)),
40
43
  ]);
41
44
  // Priced like the MOST pane. Index and yield levels (^GSPC, ^TNX) carry no currency sign.
42
- const PRICE_COLUMN = { key: "price", header: "Last", align: "right" as const,
43
- format: (value: unknown, row: Record<string, unknown>) => typeof value === "number"
45
+ function formatRowPrice(value: unknown, row: Record<string, unknown>): string {
46
+ return typeof value === "number"
44
47
  ? formatMoverPrice(
45
48
  value,
46
49
  typeof row.currency === "string" && !String(row.symbol ?? "").startsWith("^") ? row.currency : "",
47
50
  moverReferencePrice(row as Parameters<typeof moverReferencePrice>[0]),
48
- ) : "" };
51
+ ) : "";
52
+ }
53
+ const PRICE_COLUMN = { key: "price", header: "Last", align: "right" as const, format: formatRowPrice };
54
+
55
+ /**
56
+ * A pre-market or after-hours print and its move from the regular close, as
57
+ * `quote` gives it, in its own column beside Last and Chg%. Text drops a
58
+ * column no row fills, so a table without an extended print stays as before.
59
+ */
60
+ function extendedColumn(session: ExtendedSession) {
61
+ return {
62
+ key: session === "PRE" ? "preMarket" : "afterHours",
63
+ header: EXTENDED_SESSION_LABELS[session],
64
+ align: "right" as const,
65
+ value: (row: Record<string, unknown>) => row.extendedSession === session ? row.extendedPrice : null,
66
+ format: (value: unknown, row: Record<string, unknown>) => typeof value === "number"
67
+ ? [formatRowPrice(value, row), formatChangePercentCell(row.extendedChangePercent)].join(" ").trim()
68
+ : "",
69
+ };
70
+ }
71
+ const EXTENDED_COLUMNS = [extendedColumn("PRE"), extendedColumn("POST")];
72
+ // A narrow terminal drops these before it cuts the names short.
73
+ const MARKET_CAP_CELL = { key: "marketCap", header: "Mkt Cap", align: "right" as const, optional: true, dropPriority: 1 };
49
74
  const MOVER_COLUMNS = [
50
75
  { key: "symbol", header: "Symbol" },
51
76
  { key: "name", header: "Name" },
52
77
  PRICE_COLUMN,
53
78
  { key: "changePercent", header: "Chg%", align: "right" as const, format: formatChangePercentCell },
79
+ ...EXTENDED_COLUMNS,
54
80
  { key: "volume", header: "Volume", align: "right" as const, format: formatCompactCell },
55
- { key: "marketCap", header: "Mkt Cap", align: "right" as const, format: formatCompactCell },
81
+ { ...MARKET_CAP_CELL, format: formatCompactCell },
56
82
  ];
57
83
  const START_OPTION = { flags: "--start <yyyy-mm-dd>", description: "First observation date (default 2021-01-01)" };
58
84
 
@@ -78,15 +104,27 @@ function rejectArgs(args: readonly string[], usage: string, ctx: Parameters<CliC
78
104
  rejectExtraArgs(args, 0, { usage, takes: "no arguments", advice }, ctx);
79
105
  }
80
106
 
107
+ /** A percent to two decimals, as the table prints it. */
108
+ function roundedPercent(value: number | null): number | null {
109
+ return value == null ? null : Number(value.toFixed(2));
110
+ }
111
+
81
112
  function quoteRows(results: Awaited<ReturnType<NonNullable<import("../../types/data-provider").AssetDataProvider["getQuotesBatch"]>>>) {
82
113
  return results.map((result) => {
83
114
  const quote = result.quote;
115
+ // Last and Chg% are the regular session, as `ticker` reads it; a pre-market or
116
+ // after-hours print is its own column, measured from that session's close.
117
+ const session = getQuoteSessionFields(quote);
84
118
  return {
85
119
  symbol: result.target.symbol,
86
120
  name: quote?.name || BASKET_NAMES.get(result.target.symbol) || "",
87
- price: quote?.price ?? null,
88
- change: quote?.change ?? null,
89
- changePercent: quote?.changePercent == null ? null : Number(quote.changePercent.toFixed(2)),
121
+ price: session.price,
122
+ change: session.change,
123
+ changePercent: roundedPercent(session.changePercent),
124
+ extendedSession: session.extendedSession,
125
+ extendedPrice: session.extendedPrice,
126
+ extendedChange: session.extendedChange,
127
+ extendedChangePercent: roundedPercent(session.extendedChangePercent),
90
128
  currency: quote?.currency ?? "",
91
129
  providerId: quote?.providerId ?? "",
92
130
  marketCap: quote?.marketCap ?? null,
@@ -135,7 +173,8 @@ async function runQuoteBasket(symbols: string[], ctx: Parameters<CliCommandDef["
135
173
  { key: "name", header: "Name" },
136
174
  PRICE_COLUMN,
137
175
  { key: "changePercent", header: "Chg%", align: "right", format: formatChangePercentCell },
138
- { key: "marketCap", header: "Mkt Cap", align: "right", value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
176
+ ...EXTENDED_COLUMNS,
177
+ { ...MARKET_CAP_CELL, value: (row) => row.marketCap == null ? "" : formatCompact(Number(row.marketCap)) },
139
178
  ],
140
179
  });
141
180
  });
@@ -149,7 +188,32 @@ function utcDateTimePart(value: unknown, part: "date" | "time"): string {
149
188
  return part === "date" ? iso.slice(0, 10) : `${iso.slice(11, 16)} UTC`;
150
189
  }
151
190
 
152
- const ECON_USAGE = "econ [--country <region>] [--impact <level>]";
191
+ const ECON_USAGE = "econ [--country <region>] [--impact <level>] [--from <yyyy-mm-dd>]";
192
+ const DAY_MS = 86_400_000;
193
+
194
+ /**
195
+ * What an empty calendar says. A window that starts after the last day listed
196
+ * says where the listing stops, whatever the filters, rather than nothing.
197
+ */
198
+ function econEmptyMessage(from: string | null, listedThrough: string | null, stopsShort: boolean): string {
199
+ if (!listedThrough) return "No events listed.";
200
+ if (from != null && from > listedThrough) return `No events listed after ${listedThrough}.`;
201
+ const range = from ? ` from ${from}` : "";
202
+ return stopsShort
203
+ ? `No matching events${range}; none listed after ${listedThrough}.`
204
+ : `No matching events${range} through ${listedThrough}.`;
205
+ }
206
+
207
+ /** A `yyyy-mm-dd` option, checked here; null when it was not given. */
208
+ function parseDateOption(value: string | undefined, flag: string, ctx: Parameters<CliCommandDef["execute"]>[1]): string | null {
209
+ if (value == null) return null;
210
+ const date = value.trim();
211
+ const time = /^\d{4}-\d{2}-\d{2}$/.test(date) ? Date.parse(`${date}T00:00:00Z`) : Number.NaN;
212
+ if (!Number.isFinite(time) || new Date(time).toISOString().slice(0, 10) !== date) {
213
+ ctx.fail(`${flag} takes a date as yyyy-mm-dd, got "${value}".`);
214
+ }
215
+ return date;
216
+ }
153
217
 
154
218
  /** One of a filter's values, matched without regard to case; anything else fails with the list. */
155
219
  function parseFilter<T extends string>(value: string | undefined, values: readonly T[], label: string, ctx: Parameters<CliCommandDef["execute"]>[1]): T {
@@ -163,13 +227,22 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
163
227
  const rawArgs = [...args];
164
228
  const country: CountryFilter = parseFilter(takeOption(rawArgs, "--country"), COUNTRY_CYCLE, "country", ctx);
165
229
  const impact: ImpactFilter = parseFilter(takeOption(rawArgs, "--impact"), FILTER_CYCLE, "impact", ctx);
230
+ const fromOption = parseDateOption(takeOption(rawArgs, "--from"), "--from", ctx);
166
231
  rejectArgs(rawArgs, ECON_USAGE, ctx, "Filter with --country and --impact.");
167
- await withCliServices(ctx, async (services) => {
168
- const { data: events } = await loadCalendar(ctx.cliOptions.refresh);
232
+ await withCliServices(ctx, async () => {
233
+ const { data: events, fetchedAt, stale } = await loadCalendar(ctx.cliOptions.refresh);
234
+ const todayStart = Math.floor(Date.now() / DAY_MS) * DAY_MS;
235
+ // The calendar lists from today (UTC). --from moves the first day; --tail
236
+ // alone keeps the newest events of the whole calendar, the past week's too.
237
+ const from = fromOption ?? (ctx.cliOptions.tail != null ? null : new Date(todayStart).toISOString().slice(0, 10));
238
+ const fromTime = from == null ? Number.NEGATIVE_INFINITY : Date.parse(`${from}T00:00:00Z`);
239
+ const lastEvent = events.reduce<Date | null>((last, event) => (!last || event.date > last ? event.date : last), null);
240
+ const listedThrough = lastEvent ? lastEvent.toISOString().slice(0, 10) : null;
241
+ // A calendar that stops short of a week ahead says so under the table.
242
+ const listedAfter = shortCalendarEnd(events, Math.max(todayStart, fromTime)) ? `No events listed after ${listedThrough}` : null;
169
243
  const rows = events
170
- .filter((event) => matchesCountry(event, country) && matchesImpact(event, impact))
244
+ .filter((event) => event.date.getTime() >= fromTime && matchesCountry(event, country) && matchesImpact(event, impact))
171
245
  .sort((left, right) => left.date.getTime() - right.date.getTime())
172
- .slice(0, ctx.cliOptions.limit ?? 50)
173
246
  .map((event) => ({
174
247
  date: isoDate(event.date),
175
248
  time: event.time,
@@ -182,9 +255,14 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
182
255
  }));
183
256
  ctx.printResult({
184
257
  data: rows,
185
- metadata: { country, impact },
186
- freshness: rowsFreshness(rows, { status: "not-a-feed", basis: "calendar", observedKey: "date", oldest: null }),
258
+ metadata: { country, impact, from, listedThrough },
259
+ // The calendar as read, not its rows: those are scheduled, mostly ahead.
260
+ freshness: rowsFreshness([], { status: "not-a-feed", basis: "calendar", asOf: fetchedAt }, { stale }),
187
261
  }, {
262
+ dateKey: "date",
263
+ defaultLimit: 50,
264
+ empty: econEmptyMessage(from, listedThrough, listedAfter != null),
265
+ ...(listedAfter ? { footnote: listedAfter } : {}),
188
266
  columns: [
189
267
  // Text shows both halves of the event timestamp in UTC, never the host's zone; exports keep the source values.
190
268
  { key: "date", header: "Date", format: (value) => utcDateTimePart(value, "date") },
@@ -200,18 +278,70 @@ async function runEcon(args: string[], ctx: Parameters<CliCommandDef["execute"]>
200
278
  });
201
279
  }
202
280
 
281
+ const FRED_USAGE = "fred <series-id> [--start <yyyy-mm-dd>] [--sort desc|asc]";
282
+ const FRED_LIST_USAGE = "fred --list [filter]";
283
+
284
+ /** `--start`, checked here: Gloom Cloud cannot read anything but yyyy-mm-dd. */
285
+ function parseStartDate(value: string | undefined, ctx: Parameters<CliCommandDef["execute"]>[1]): string {
286
+ return parseDateOption(value, "--start", ctx) ?? "2021-01-01";
287
+ }
288
+
289
+ /** Gloom Cloud serves a curated set of FRED series and answers 400 for any other id. */
290
+ function isUnsupportedSeries(error: unknown): boolean {
291
+ if (!(error instanceof ApiRequestError) || error.status !== 400) return false;
292
+ const message = typeof error.details?.message === "string" ? error.details.message : error.message;
293
+ return error.details?.code === "unsupported_series" || /^Unsupported FRED series\b/i.test(message);
294
+ }
295
+
296
+ async function runFredList(filter: string, ctx: Parameters<CliCommandDef["execute"]>[1]) {
297
+ let catalog;
298
+ try {
299
+ catalog = await apiClient.getCloudFredSeriesCatalog();
300
+ } catch (error) {
301
+ if (error instanceof ApiRequestError && error.status === 404) ctx.fail("The list of supported FRED series is not available yet.");
302
+ throw error;
303
+ }
304
+ const terms = filter.toLowerCase().split(/\s+/).filter(Boolean);
305
+ const rows = catalog.series
306
+ .map((series) => ({ id: series.id, title: series.title?.trim() ?? "", group: series.group?.trim() ?? "" }))
307
+ .filter((series) => {
308
+ const text = `${series.id} ${series.title} ${series.group}`.toLowerCase();
309
+ return terms.every((term) => text.includes(term));
310
+ });
311
+ ctx.printResult({ data: rows, metadata: { filter: filter || null, total: catalog.series.length } }, {
312
+ columns: [
313
+ { key: "id", header: "ID", shrink: false },
314
+ { key: "title", header: "Title" },
315
+ { key: "group", header: "Group" },
316
+ ],
317
+ empty: filter ? `No supported FRED series match "${filter}". Run gloomberb fred --list to see them all.` : "No FRED series are listed.",
318
+ });
319
+ }
320
+
203
321
  async function runFred(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
204
322
  const args = [...rawArgs];
205
- const startDate = takeOption(args, "--start") ?? "2021-01-01";
206
- const sortOrder = (takeOption(args, "--sort") ?? "desc") as "asc" | "desc";
207
- const seriesId = requireOneArg(args, "fred <series-id> [--start <yyyy-mm-dd>]", "series", ctx).toUpperCase();
208
- const data = await apiClient.getCloudFredSeries(seriesId, { startDate, sortOrder });
209
- const rows = data.observations.slice(0, ctx.cliOptions.limit ?? data.observations.length);
323
+ const list = takeFlag(args, "--list");
324
+ const start = takeOption(args, "--start");
325
+ const sort = takeOption(args, "--sort");
326
+ if (list) {
327
+ if (start != null || sort != null) ctx.fail("--list takes a filter, not --start or --sort.", `Usage: gloomberb ${FRED_LIST_USAGE}`);
328
+ return runFredList(args.join(" ").trim(), ctx);
329
+ }
330
+ const startDate = parseStartDate(start, ctx);
331
+ const sortOrder = (sort ?? "desc").trim().toLowerCase();
332
+ if (sortOrder !== "asc" && sortOrder !== "desc") ctx.fail(`Unknown sort order "${sort}".`, "Use desc (newest first) or asc.");
333
+ const seriesId = requireOneArg(args, FRED_USAGE, "series", ctx).toUpperCase();
334
+ const data = await apiClient.getCloudFredSeries(seriesId, { startDate, sortOrder }).catch((error: unknown) => {
335
+ if (isUnsupportedSeries(error)) ctx.fail(`Unsupported FRED series ${seriesId}. List supported series with: gloomberb fred --list`);
336
+ throw error;
337
+ });
338
+ const rows = data.observations;
210
339
  ctx.printResult({
211
340
  data: rows,
212
341
  metadata: { info: data.info, seriesId, startDate, sortOrder },
213
342
  freshness: rowsFreshness(rows, { source: "FRED", status: "not-a-feed", basis: "published statistics", observedKey: "date", oldest: null }),
214
343
  }, {
344
+ dateKey: "date",
215
345
  textColumns: [
216
346
  { key: "date", header: "Date" },
217
347
  { key: "value", header: data.info?.units ? `Value (${data.info.units})` : "Value", align: "right" },
@@ -223,7 +353,7 @@ const YIELD_TENORS: Record<string, string> = { DGS3MO: "3M", DGS2: "2Y", DGS10:
223
353
 
224
354
  async function runYieldCurve(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
225
355
  const args = [...rawArgs];
226
- const startDate = takeOption(args, "--start") ?? "2021-01-01";
356
+ const startDate = parseStartDate(takeOption(args, "--start"), ctx);
227
357
  rejectArgs(args, "yield-curve [--start <yyyy-mm-dd>]", ctx);
228
358
  const series = Object.keys(YIELD_TENORS);
229
359
  const results = await Promise.all(series.map(async (seriesId) => {
@@ -343,22 +473,30 @@ export const overviewCliCommands: CliCommandDef[] = [
343
473
  options: [
344
474
  { flags: "--country <region>", description: "US, G7, EU, or all (default all)" },
345
475
  { flags: "--impact <level>", description: "high, medium, low, or all (default all)" },
476
+ { flags: "--from <yyyy-mm-dd>", description: "First day listed (default today, UTC); an earlier day reaches past events" },
346
477
  ],
347
- examples: ["econ", "econ --country US --impact high"],
478
+ examples: ["econ", "econ --country US --impact high", "econ --tail 20"],
348
479
  },
349
480
  execute: runEcon,
350
481
  },
351
482
  {
352
483
  name: "fred",
353
- description: "Fetch a FRED economic series (needs a Gloom Cloud sign-in)",
484
+ description: "Fetch a FRED economic series, or list the ones served",
354
485
  help: {
355
486
  group: CLI_COMMAND_GROUPS.markets,
356
- usage: ["fred <series-id> [--start <yyyy-mm-dd>]"],
487
+ usage: [FRED_USAGE, FRED_LIST_USAGE],
357
488
  options: [
358
489
  START_OPTION,
359
490
  { flags: "--sort <order>", description: "desc for newest first (default) or asc" },
491
+ { flags: "--list", description: "List the series Gloom Cloud serves (ID, title, group); words after it filter the list" },
360
492
  ],
361
- examples: ["fred CPIAUCSL", "fred UNRATE --start 2020-01-01 --csv"],
493
+ sections: [{
494
+ title: "Series",
495
+ lines: [
496
+ "Gloom Cloud serves a curated set of FRED series and needs no sign-in for them. gloomberb fred --list prints them; any other ID fails with that pointer.",
497
+ ],
498
+ }],
499
+ examples: ["fred CPIAUCSL", "fred DGS10 --tail 5", "fred UNRATE --start 2020-01-01 --csv", "fred --list", "fred --list fx"],
362
500
  },
363
501
  execute: runFred,
364
502
  },
@@ -13,6 +13,7 @@ import { initMarketData, withMarketData } from "../context";
13
13
  import { fail } from "../errors";
14
14
  import type { MarketContext } from "../types";
15
15
  import type { CliCommandContext } from "../../types/plugin";
16
+ import { indexRootFor, indexRootTryLine } from "../index-roots";
16
17
 
17
18
  interface SearchCommandDependencies {
18
19
  initMarketData?: () => Promise<MarketContext>;
@@ -52,6 +53,11 @@ export async function searchCandidatesForCli({
52
53
  }
53
54
  }
54
55
 
56
+ /** "Saved", or the broker a row came from; never the data service's internal id. */
57
+ function searchSourceLabel(candidate: TickerSearchCandidate): string {
58
+ return candidate.kind === "ticker" ? "Saved" : candidate.result?.brokerLabel || "";
59
+ }
60
+
55
61
  function resolveSearchName(candidate: TickerSearchCandidate): string {
56
62
  return candidate.detail.split(" | ")[0] || candidate.detail || "—";
57
63
  }
@@ -70,14 +76,22 @@ function searchCandidateRows(candidates: TickerSearchCandidate[]) {
70
76
  || candidate.result?.brokerContract?.secType
71
77
  || candidate.ticker?.metadata.assetCategory
72
78
  || "",
73
- source: candidate.kind === "ticker"
74
- ? "Saved"
75
- : candidate.result?.brokerLabel || candidate.result?.providerId || "Provider",
79
+ source: searchSourceLabel(candidate),
76
80
  providerId: candidate.result?.providerId ?? "",
77
81
  saved: candidate.kind === "ticker",
78
82
  }));
79
83
  }
80
84
 
85
+ /**
86
+ * "^VIX Cboe Volatility Index (try: gloomberb quote ^VIX)" for a bare index root, whose
87
+ * search finds funds named after the index but not the index, unless it does list it.
88
+ */
89
+ function indexRootNote(query: string, candidates: TickerSearchCandidate[]): string | null {
90
+ const root = indexRootFor(query);
91
+ if (!root || candidates.some((candidate) => candidate.label.toUpperCase() === root.symbol)) return null;
92
+ return indexRootTryLine(root);
93
+ }
94
+
81
95
  export function buildSearchReport({
82
96
  query,
83
97
  candidates,
@@ -86,6 +100,8 @@ export function buildSearchReport({
86
100
  candidates: TickerSearchCandidate[];
87
101
  }): string {
88
102
  const lines = [renderSection(`Search: ${query}`)];
103
+ const indexNote = indexRootNote(query, candidates);
104
+ if (indexNote) lines.push("", indexNote);
89
105
 
90
106
  if (candidates.length === 0) {
91
107
  lines.push(cliStyles.muted("No matches found."));
@@ -93,6 +109,8 @@ export function buildSearchReport({
93
109
  }
94
110
 
95
111
  const categories = Array.from(new Set(candidates.map((candidate) => candidate.category)));
112
+ // Where a row came from, only when one is saved or from a broker.
113
+ const showSource = candidates.some((candidate) => searchSourceLabel(candidate));
96
114
  for (const category of categories) {
97
115
  const categoryRows = candidates.filter((candidate) => candidate.category === category);
98
116
  lines.push("");
@@ -103,7 +121,7 @@ export function buildSearchReport({
103
121
  { header: "Name" },
104
122
  { header: "Exchange" },
105
123
  { header: "Type" },
106
- { header: "Source" },
124
+ ...(showSource ? [{ header: "Source" }] : []),
107
125
  ],
108
126
  categoryRows.map((candidate) => [
109
127
  candidate.label,
@@ -117,9 +135,7 @@ export function buildSearchReport({
117
135
  || candidate.result?.brokerContract?.secType
118
136
  || candidate.ticker?.metadata.assetCategory
119
137
  || "—",
120
- candidate.kind === "ticker"
121
- ? "Saved"
122
- : candidate.result?.brokerLabel || candidate.result?.providerId || "Provider",
138
+ ...(showSource ? [searchSourceLabel(candidate) || "—"] : []),
123
139
  ]),
124
140
  ));
125
141
  }
@@ -144,7 +160,12 @@ export async function search(query: string, dependencies: SearchCommandDependenc
144
160
  });
145
161
 
146
162
  if (dependencies.printResult) {
147
- dependencies.printResult({ data: searchCandidateRows(candidates), metadata: { query: trimmedQuery } }, {
163
+ const indexNote = indexRootNote(trimmedQuery, candidates);
164
+ dependencies.printResult({
165
+ data: searchCandidateRows(candidates),
166
+ metadata: { query: trimmedQuery },
167
+ warnings: indexNote ? [indexNote] : undefined,
168
+ }, {
148
169
  columns: [
149
170
  { key: "category", header: "Category" },
150
171
  { key: "symbol", header: "Symbol" },
@@ -1,13 +1,16 @@
1
1
  import { join } from "path";
2
2
  import { VERSION } from "../../version";
3
3
  import { saveConfig } from "../../data/config/store";
4
+ import { builtinPluginGroupMembers } from "../../plugins/ownership";
4
5
  import type { TelemetryConfig } from "../../types/config";
5
6
  import type { CliCommandDef } from "../../types/plugin";
6
7
  import { withCliServices, withConfigData } from "../context";
7
8
  import { CLI_COMMAND_GROUPS } from "../help";
8
9
  import { dryRunNote, formatBytes, formatStatusCell } from "../helpers";
9
10
  import { cliStyles, cliTerminalWidth, renderSection, renderTable, wrapText } from "../../utils/cli-output";
11
+ import { getThemeIds } from "../../theme/themes";
10
12
  import { requireArg } from "./command-utils";
13
+ import { describeThemeId, requireThemeId, themeListNote, themeListRows, themeName } from "./themes";
11
14
  import {
12
15
  applyKeybindingCliSet,
13
16
  describeKeybindingsForCli,
@@ -22,12 +25,25 @@ const TELEMETRY_CONFIG_KEYS = {
22
25
  "telemetry.attention": "attention",
23
26
  } as const satisfies Record<string, keyof TelemetryConfig>;
24
27
  type TelemetryConfigKey = keyof typeof TELEMETRY_CONFIG_KEYS;
25
- const EDITABLE_CONFIG_KEYS = ["baseCurrency", "refreshIntervalMinutes", "theme", "valueFlashingEnabled", ...Object.keys(TELEMETRY_CONFIG_KEYS)];
28
+ /** The one-time GitHub star line in the terminal's status bar. */
29
+ const STAR_PROMPT_CONFIG_KEY = "starPrompt.enabled";
30
+ const EDITABLE_CONFIG_KEYS = [
31
+ "baseCurrency",
32
+ "refreshIntervalMinutes",
33
+ "theme",
34
+ "valueFlashingEnabled",
35
+ ...Object.keys(TELEMETRY_CONFIG_KEYS),
36
+ STAR_PROMPT_CONFIG_KEY,
37
+ ];
26
38
 
27
39
  function isTelemetryConfigKey(key: string): key is TelemetryConfigKey {
28
40
  return Object.prototype.hasOwnProperty.call(TELEMETRY_CONFIG_KEYS, key);
29
41
  }
30
42
 
43
+ function isBooleanConfigKey(key: string): boolean {
44
+ return key === "valueFlashingEnabled" || key === STAR_PROMPT_CONFIG_KEY || isTelemetryConfigKey(key);
45
+ }
46
+
31
47
  function describeConfigValue(value: unknown): string {
32
48
  if (value == null) return "nothing";
33
49
  if (Array.isArray(value)) return value.length > 0 ? value.join(", ") : "nothing";
@@ -136,6 +152,12 @@ export function createSystemCliCommands(): CliCommandDef[] {
136
152
  checks.push({ check: "database", status: "ok", detail: join(services.dataDir, ".gloomberb-cache.db") });
137
153
  checks.push({ check: "plugins", status: "ok", detail: String(services.services.pluginRegistry.allPlugins.size) });
138
154
  checks.push({ check: "capabilities", status: "ok", detail: String(services.services.pluginRegistry.capabilities.manifests().length) });
155
+ const theme = services.config.theme;
156
+ checks.push({
157
+ check: "theme",
158
+ status: themeName(theme) ? "ok" : "warn",
159
+ detail: themeName(theme) ? describeThemeId(theme) : `${describeThemeId(theme)}. Pick one with gloomberb config set theme <id>.`,
160
+ });
139
161
  });
140
162
  } catch (error) {
141
163
  checks.push({ check: "runtime", status: "error", detail: error instanceof Error ? error.message : String(error) });
@@ -160,8 +182,11 @@ export function createSystemCliCommands(): CliCommandDef[] {
160
182
  "config list",
161
183
  "config get <key>",
162
184
  "config set <key> <value>",
185
+ "config themes",
186
+ "config set theme <id>",
163
187
  "config set telemetry.crashReports false",
164
188
  "config set telemetry.usage false",
189
+ "config set starPrompt.enabled false",
165
190
  "config get keybindings",
166
191
  "config set keybindings.actions.<action> <keys>|null|default",
167
192
  "config set keybindings.commands.<keys> <command>|null",
@@ -169,9 +194,16 @@ export function createSystemCliCommands(): CliCommandDef[] {
169
194
  sections: [{
170
195
  title: "Editable keys",
171
196
  lines: [`${EDITABLE_CONFIG_KEYS.join(", ")}, and keybindings.*`],
197
+ }, {
198
+ title: "Themes",
199
+ lines: [
200
+ `theme takes one of these ids, or its name as config themes lists it: ${getThemeIds().join(", ")}.`,
201
+ ],
172
202
  }],
173
203
  examples: [
174
204
  "config",
205
+ "config themes",
206
+ "config set theme colorblind",
175
207
  "config set baseCurrency EUR",
176
208
  "config get keybindings",
177
209
  "config set keybindings.actions.ticker-search \"Ctrl+T\"",
@@ -180,6 +212,24 @@ export function createSystemCliCommands(): CliCommandDef[] {
180
212
  },
181
213
  execute: async (args, ctx) => {
182
214
  const action = args[0] ?? "list";
215
+ // Listing the themes needs no data folder, so it works before the first run.
216
+ if (action === "themes") {
217
+ let current: string | null = null;
218
+ try {
219
+ current = await withConfigData(ctx, ({ config }) => config.theme);
220
+ } catch {
221
+ current = null;
222
+ }
223
+ ctx.printResult({ data: themeListRows(current) }, {
224
+ textColumns: [
225
+ { key: "id", header: "ID", shrink: false },
226
+ { key: "name", header: "Name", shrink: false },
227
+ { key: "appearance", header: "Look" },
228
+ { key: "note", header: "Note", format: (_value, row) => themeListNote(row as ReturnType<typeof themeListRows>[number]) },
229
+ ],
230
+ });
231
+ return;
232
+ }
183
233
  await withConfigData(ctx, async (context) => {
184
234
  const safeConfig: Record<string, unknown> = {
185
235
  dataDir: context.config.dataDir,
@@ -197,6 +247,7 @@ export function createSystemCliCommands(): CliCommandDef[] {
197
247
  "telemetry.crashReports": context.config.telemetry?.crashReports !== false,
198
248
  "telemetry.usage": context.config.telemetry?.usage !== false,
199
249
  "telemetry.attention": context.config.telemetry?.attention === true,
250
+ [STAR_PROMPT_CONFIG_KEY]: context.config.starPrompt?.enabled !== false,
200
251
  };
201
252
 
202
253
  if (action === "list") {
@@ -205,6 +256,7 @@ export function createSystemCliCommands(): CliCommandDef[] {
205
256
  key,
206
257
  header: key,
207
258
  ...(key === KEYBINDINGS_CONFIG_KEY ? { format: summarizeKeybindings } : {}),
259
+ ...(key === "theme" ? { format: (value: unknown) => describeThemeId(String(value)) } : {}),
208
260
  })),
209
261
  });
210
262
  return;
@@ -214,6 +266,13 @@ export function createSystemCliCommands(): CliCommandDef[] {
214
266
  if (!Object.prototype.hasOwnProperty.call(safeConfig, key)) {
215
267
  ctx.fail(`Unknown config key "${key}".`, `Keys: ${Object.keys(safeConfig).join(", ")}`);
216
268
  }
269
+ if (key === "theme") {
270
+ const id = String(safeConfig.theme);
271
+ ctx.printResult({ data: { key, value: id, name: themeName(id) } }, {
272
+ text: () => describeThemeId(id),
273
+ });
274
+ return;
275
+ }
217
276
  ctx.printResult({ data: { key, value: safeConfig[key] } }, {
218
277
  text: (data) => key === KEYBINDINGS_CONFIG_KEY ? renderKeybindings(data.value) : describeConfigValue(data.value),
219
278
  });
@@ -244,24 +303,37 @@ export function createSystemCliCommands(): CliCommandDef[] {
244
303
  if (!EDITABLE_CONFIG_KEYS.includes(key)) {
245
304
  ctx.fail(`Config key "${key}" is not editable from the CLI.`, `Editable keys: ${EDITABLE_CONFIG_KEYS.join(", ")}, keybindings.*`);
246
305
  }
247
- if (isTelemetryConfigKey(key) && value !== "true" && value !== "false") {
306
+ if ((isTelemetryConfigKey(key) || key === STAR_PROMPT_CONFIG_KEY) && value !== "true" && value !== "false") {
248
307
  ctx.fail(`Usage: gloomberb config set ${key} true|false`);
249
308
  }
250
- const parsedValue = key === "refreshIntervalMinutes"
251
- ? Number(value)
252
- : key === "valueFlashingEnabled" || isTelemetryConfigKey(key)
253
- ? value === "true"
254
- : value;
309
+ // A display name may come unquoted: config set theme White Phosphor.
310
+ const parsedValue = key === "theme"
311
+ ? requireThemeId(args.slice(2).join(" "))
312
+ : key === "refreshIntervalMinutes"
313
+ ? Number(value)
314
+ : isBooleanConfigKey(key)
315
+ ? value === "true"
316
+ : value;
255
317
  const nextConfig = isTelemetryConfigKey(key)
256
318
  ? { ...context.config, telemetry: { ...context.config.telemetry, [TELEMETRY_CONFIG_KEYS[key]]: parsedValue as boolean } }
257
- : { ...context.config, [key]: parsedValue };
319
+ : key === STAR_PROMPT_CONFIG_KEY
320
+ ? { ...context.config, starPrompt: { ...context.config.starPrompt, enabled: parsedValue as boolean } }
321
+ : { ...context.config, [key]: parsedValue };
258
322
  if (!ctx.cliOptions.dryRun) await saveConfig(nextConfig);
259
- ctx.printResult({ data: { changed: !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, key, value: parsedValue } }, {
260
- text: (data) => `Set ${key} to ${describeConfigValue(data.value)}.${dryRunNote(data.dryRun)}`,
323
+ ctx.printResult({
324
+ data: {
325
+ changed: !ctx.cliOptions.dryRun,
326
+ dryRun: ctx.cliOptions.dryRun,
327
+ key,
328
+ value: parsedValue,
329
+ ...(key === "theme" ? { name: themeName(String(parsedValue)) } : {}),
330
+ },
331
+ }, {
332
+ text: (data) => `Set ${key} to ${key === "theme" ? describeThemeId(String(data.value)) : describeConfigValue(data.value)}.${dryRunNote(data.dryRun)}`,
261
333
  });
262
334
  return;
263
335
  }
264
- ctx.fail("Usage: gloomberb config list|get|set");
336
+ ctx.fail("Usage: gloomberb config list|get|set|themes");
265
337
  });
266
338
  },
267
339
  };
@@ -422,19 +494,27 @@ export function createSystemCliCommands(): CliCommandDef[] {
422
494
  }
423
495
  if (action === "enable" || action === "disable") {
424
496
  const id = requireArg(args[1], `Usage: gloomberb plugin ${action} <id>`, ctx);
425
- const plugin = services.services.pluginRegistry.allPlugins.get(id);
426
- if (!plugin) ctx.fail(`Plugin "${id}" is not available.`);
427
- if (plugin?.toggleable !== true) ctx.fail(`Plugin "${id}" is part of the application and cannot be disabled.`);
497
+ // A retired id that now names a group of built-ins switches all of them.
498
+ const targetIds = builtinPluginGroupMembers(id) ?? [id];
499
+ for (const targetId of targetIds) {
500
+ const plugin = services.services.pluginRegistry.allPlugins.get(targetId);
501
+ if (!plugin) ctx.fail(`Plugin "${targetId}" is not available.`);
502
+ if (plugin?.toggleable !== true) ctx.fail(`Plugin "${targetId}" is part of the application and cannot be disabled.`);
503
+ }
428
504
  const disabled = new Set(services.config.disabledPlugins ?? []);
429
- const before = disabled.has(id);
430
- if (action === "enable") disabled.delete(id);
431
- else disabled.add(id);
505
+ const isOff = () => targetIds.every((targetId) => disabled.has(targetId));
506
+ const isOn = () => targetIds.every((targetId) => !disabled.has(targetId));
507
+ const unchanged = action === "enable" ? isOn() : isOff();
508
+ for (const targetId of targetIds) {
509
+ if (action === "enable") disabled.delete(targetId);
510
+ else disabled.add(targetId);
511
+ }
432
512
  const nextConfig = { ...services.config, disabledPlugins: [...disabled] };
433
513
  if (!ctx.cliOptions.dryRun) await saveConfig(nextConfig);
434
- ctx.printResult({ data: { changed: before !== disabled.has(id) && !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, id, enabled: !disabled.has(id) } }, {
514
+ ctx.printResult({ data: { changed: !unchanged && !ctx.cliOptions.dryRun, dryRun: ctx.cliOptions.dryRun, id, enabled: isOn() } }, {
435
515
  text: (data) => {
436
516
  const state = data.enabled ? "on" : "off";
437
- if (before === disabled.has(id)) return `${id} is already ${state}.`;
517
+ if (unchanged) return `${id} is already ${state}.`;
438
518
  return `Turned ${id} ${state}.${dryRunNote(data.dryRun)}`;
439
519
  },
440
520
  });