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
@@ -216,7 +216,12 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
216
216
  const aliases = getItemSearchAliases(item);
217
217
  const aliasScore = (exactListingSymbol ? [...aliases, exactListingSymbol] : aliases)
218
218
  .reduce((best, alias) => Math.max(best, scoreSearchAlias(intent, alias)), 0);
219
- const textScore = labelScore + detailScore + aliasScore
219
+ // Several words match on all of them, in any order, across the name, type,
220
+ // venue and the themes the row answers to; a theme alone ranks after a name.
221
+ const keywordScore = scoreAllWords(intent, [...(item.searchKeywords ?? [])]) > 0 ? KEYWORD_MATCH_SCORE : 0;
222
+ const wordsScore = labelScore + detailScore + aliasScore > 0 ? 0
223
+ : scoreAllWords(intent, [item.label, item.detail, item.right ?? "", ...(item.searchKeywords ?? [])]);
224
+ const textScore = labelScore + detailScore + aliasScore + Math.max(keywordScore, wordsScore)
220
225
  + (isQualifiedTickerQuery(query) && matchesQualifiedTicker(item, query) ? 100_000 : 0);
221
226
  const saved = isSavedSearchItem(item);
222
227
  const explicitIntentScore = scoreAssetPreference(intent, item.instrumentClass)
@@ -285,6 +290,13 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
285
290
  const aSaved = isSavedSearchItem(a.item);
286
291
  const bSaved = isSavedSearchItem(b.item);
287
292
  if (aSaved !== bSaved) return aSaved ? -1 : 1;
293
+ // Rows that answer to the same typed name (GOOGL and GOOG for "Google")
294
+ // keep the order the server gave them rather than the shorter symbol first.
295
+ if (a.symbolMatchRank === 2) {
296
+ const aProviderRank = a.item.providerRank ?? Number.POSITIVE_INFINITY;
297
+ const bProviderRank = b.item.providerRank ?? Number.POSITIVE_INFINITY;
298
+ if (aProviderRank !== bProviderRank) return aProviderRank - bProviderRank;
299
+ }
288
300
  if (a.item.label.length !== b.item.label.length) return a.item.label.length - b.item.label.length;
289
301
  return a.index - b.index;
290
302
  };
@@ -487,6 +499,38 @@ function getItemSearchAliases(item: Pick<TickerSearchRankableItem, "label"> & Pa
487
499
  return aliases.length > 0 ? aliases : [item.label];
488
500
  }
489
501
 
502
+ /** A row that answers only to a theme it was sent with (`searchKeywords`), below any name match. */
503
+ const KEYWORD_MATCH_SCORE = 500;
504
+ /** Every query word found somewhere in the row, in any order. */
505
+ const ALL_WORDS_SCORE = 2_200;
506
+
507
+ /**
508
+ * ALL_WORDS_SCORE when every word of the query (exchange and asset words
509
+ * aside) starts a word of some field: "miners copper" finds "Copper Miners
510
+ * ETF", "lithium" finds a row sent with the lithium theme. Zero otherwise.
511
+ */
512
+ function scoreAllWords(intent: SearchQueryIntent, fields: readonly string[]): number {
513
+ const terms = intent.companyQuery.split(" ").filter(Boolean);
514
+ if (terms.length === 0) return 0;
515
+ const words = fields.flatMap((field) => normalizeSearchText(field).split(" ").filter(Boolean));
516
+ return terms.every((term) => words.some((word) => word.startsWith(term))) ? ALL_WORDS_SCORE : 0;
517
+ }
518
+
519
+ /**
520
+ * Whether a row is in an answer for a theme it was sent with, not for its
521
+ * name: the name lacks some of the query's words (Albemarle for "lithium",
522
+ * Southern Copper for "copper miners").
523
+ */
524
+ export function answersOnlyByKeyword(
525
+ item: Pick<TickerSearchRankableItem, "label" | "detail"> & Partial<TickerSearchRankableItem>,
526
+ query: string,
527
+ ): boolean {
528
+ if (!item.searchKeywords?.length) return false;
529
+ const terms = analyzeSearchQuery(query).companyQuery.split(" ").filter(Boolean);
530
+ const words = [item.label, item.detail].flatMap((field) => normalizeSearchText(field).split(" ").filter(Boolean));
531
+ return terms.length > 0 && !terms.every((term) => words.some((word) => word.startsWith(term)));
532
+ }
533
+
490
534
  function maxScoreForQueries(
491
535
  queries: string[],
492
536
  value: string,
@@ -2,7 +2,7 @@ import type { BrokerContractRef, InstrumentSearchResult, TickerListingRef } from
2
2
  import type { TickerRecord } from "../../types/ticker";
3
3
 
4
4
  export type TickerSearchInstrumentClass = "equity" | "fund" | "derivative" | "other";
5
- type TickerSearchCategory = "Saved" | "Primary Listing" | "Other Listings" | "Funds & Derivatives";
5
+ type TickerSearchCategory = "Saved" | "Primary Listing" | "Other Listings" | "Funds & Derivatives" | "Related";
6
6
 
7
7
  export interface TickerSearchRankableItem {
8
8
  id: string;
@@ -21,6 +21,8 @@ export interface TickerSearchRankableItem {
21
21
  providerRank?: number;
22
22
  popularity?: number;
23
23
  searchAliases?: string[];
24
+ /** Themes the row answers to that its name does not say (`InstrumentSearchResult.searchKeywords`). */
25
+ searchKeywords?: string[];
24
26
  }
25
27
 
26
28
  export interface TickerSearchCandidate extends TickerSearchRankableItem {
@@ -80,6 +80,7 @@ import { getPricePointTimestamp, isPriceHistoryStaleForCurrentWindow } from "../
80
80
  import { latestSessionStart, latestTradingSessionOpen, listingTimeZone, priorSessionClose } from "../market-data/market/trading-sessions";
81
81
  import { futuresGenericCaption, futuresGenericListing, futuresGenericPriceBasis } from "../utils/futures-generic";
82
82
  import { isOhlcSeriesStyle } from "./spec";
83
+ import { SERIES_COLORS } from "../theme/series-colors";
83
84
  import type {
84
85
  ChartResolutionResult,
85
86
  ChartSeriesSpec,
@@ -88,20 +89,6 @@ import type {
88
89
  TimeSeriesPoint,
89
90
  } from "./types";
90
91
 
91
- /** Colours for series told apart by name (compared tickers, markets). */
92
- export const SERIES_COLORS = [
93
- "#4dabf7",
94
- "#63e6be",
95
- "#f6c85f",
96
- "#b197fc",
97
- "#ff8787",
98
- "#ffa94d",
99
- "#74c0fc",
100
- "#e599f7",
101
- "#8ce99a",
102
- "#ffd43b",
103
- ] as const;
104
-
105
92
  export interface ChartResolveSources {
106
93
  dataProvider: DataProvider | null;
107
94
  loadFredSeries: (request: FredSeriesRequest) => Promise<FredSeriesLoadResult>;
@@ -2,7 +2,7 @@ import type { Portfolio, Watchlist } from "./ticker";
2
2
  import type { BrokerContractRef, TickerListingRef } from "./instrument";
3
3
  import type { LanguagePreference } from "../i18n/languages";
4
4
 
5
- export const CURRENT_CONFIG_VERSION = 24;
5
+ export const CURRENT_CONFIG_VERSION = 25;
6
6
 
7
7
  type ChartRendererPreference = "auto" | "kitty" | "braille";
8
8
 
@@ -59,6 +59,30 @@ export interface PanePlacementMemory {
59
59
  detached?: DetachedPlacementMemory;
60
60
  }
61
61
 
62
+ /**
63
+ * A ticker link Move to New Layout had to cut: the pane followed another one
64
+ * that stayed behind (or left with the moved pane) and was pinned on `symbol`.
65
+ * Move Back links it again while it is still pinned there.
66
+ */
67
+ export interface PaneMovedLink {
68
+ instanceId: string;
69
+ sourceInstanceId: string;
70
+ symbol: string;
71
+ /** The title before the pin renamed it ("OPX" for "OPX NVDA"), when it did. */
72
+ title?: string;
73
+ }
74
+
75
+ /** Where Move to New Layout took a pane from, so Move Back can return it. */
76
+ export interface PaneMovedFrom {
77
+ /** `SavedLayout.id` of the layout it left. */
78
+ layoutId: string;
79
+ /** Set when it was docked there: its place in the tree and its share of the split. */
80
+ docked?: DockedPlacementMemory & { ratio?: number };
81
+ /** Set when it was floating there. */
82
+ floating?: FloatingPlacementMemory;
83
+ links?: PaneMovedLink[];
84
+ }
85
+
62
86
  export interface PaneInstanceConfig {
63
87
  instanceId: string;
64
88
  paneId: string;
@@ -69,6 +93,8 @@ export interface PaneInstanceConfig {
69
93
  placementMemory?: PanePlacementMemory;
70
94
  /** Pinned by the user: the keyboard close shortcuts leave this pane alone. */
71
95
  locked?: boolean;
96
+ /** Set by Move to New Layout and cleared by Move Back; never published. */
97
+ movedFrom?: PaneMovedFrom;
72
98
  }
73
99
 
74
100
  interface DockPaneNode {
@@ -203,6 +229,21 @@ export interface TelemetryConfig {
203
229
  attention?: boolean;
204
230
  }
205
231
 
232
+ /**
233
+ * The one-time line in the terminal's status bar asking to star Gloomberb on
234
+ * GitHub. Absent means on and not shown yet. Kept on this machine only.
235
+ */
236
+ export interface StarPromptConfig {
237
+ /** `false` turns the line off; absent means on. */
238
+ enabled?: boolean;
239
+ /** Local days (`YYYY-MM-DD`) the terminal app was opened on, oldest first, until the line shows. */
240
+ days?: string[];
241
+ /** When the line showed. Once set it never shows again. */
242
+ shownAt?: string;
243
+ /** How it ended: the repository was opened, the line was dismissed, or it timed out. */
244
+ outcome?: "opened" | "dismissed" | "expired";
245
+ }
246
+
206
247
  /**
207
248
  * One recent command-bar run: `id` is `pane-template:<templateId>`, `label`
208
249
  * its name when it ran, and `arg` a ticker it ran with (never free text).
@@ -255,6 +296,12 @@ export interface AppConfig {
255
296
  /** Key overrides for this machine. Absent means every default applies. */
256
297
  keybindings?: KeybindingsConfig;
257
298
  telemetry?: TelemetryConfig;
299
+ starPrompt?: StarPromptConfig;
300
+ /**
301
+ * Presentation mode: the panes fill the window, without the header or the
302
+ * status bar, for a screen that is shown rather than worked at. Absent is off.
303
+ */
304
+ presentationMode?: boolean;
258
305
  }
259
306
 
260
307
  export const TICKER_RESEARCH_PANE_ID = "ticker-research";
@@ -582,6 +629,17 @@ export function clonePlacementMemory(memory: PanePlacementMemory | undefined): P
582
629
  };
583
630
  }
584
631
 
632
+ function clonePaneMovedFrom(movedFrom: PaneMovedFrom): PaneMovedFrom {
633
+ return {
634
+ layoutId: movedFrom.layoutId,
635
+ ...(movedFrom.docked ? {
636
+ docked: { ...movedFrom.docked, ...(movedFrom.docked.path ? { path: [...movedFrom.docked.path] } : {}) },
637
+ } : {}),
638
+ ...(movedFrom.floating ? { floating: { ...movedFrom.floating } } : {}),
639
+ ...(movedFrom.links ? { links: movedFrom.links.map((link) => ({ ...link })) } : {}),
640
+ };
641
+ }
642
+
585
643
  function cloneUnknownValue<T>(value: T): T {
586
644
  if (Array.isArray(value)) {
587
645
  return value.map((entry) => cloneUnknownValue(entry)) as T;
@@ -816,6 +874,7 @@ export function normalizePaneLayout(
816
874
  params: instance.params ? { ...instance.params } : undefined,
817
875
  settings: clonePaneSettings(instance.settings),
818
876
  placementMemory: clonePlacementMemory(instance.placementMemory),
877
+ ...(instance.movedFrom ? { movedFrom: clonePaneMovedFrom(instance.movedFrom) } : {}),
819
878
  })),
820
879
  floating: nextLayout.floating
821
880
  .filter((entry) => (
@@ -838,6 +897,7 @@ export function cloneLayout(layout: LayoutConfig): LayoutConfig {
838
897
  params: instance.params ? { ...instance.params } : undefined,
839
898
  settings: clonePaneSettings(instance.settings),
840
899
  placementMemory: clonePlacementMemory(instance.placementMemory),
900
+ ...(instance.movedFrom ? { movedFrom: clonePaneMovedFrom(instance.movedFrom) } : {}),
841
901
  })),
842
902
  floating: layout.floating.map((entry) => ({ ...entry })),
843
903
  detached: detached.map((entry) => ({ ...entry })),
@@ -51,6 +51,15 @@ export interface HeadlessPaneOptionDef {
51
51
  pluginId: string;
52
52
  key?: string;
53
53
  };
54
+ /** The value's name in the catalog's flag, `--expiration <YYYY-MM-DD|unix>`; the type when absent. */
55
+ placeholder?: string;
56
+ /** The option in use as typed after the function, such as `--expiration 2028-01-21`; the catalog shows it as an example. */
57
+ example?: string;
58
+ /**
59
+ * Checks a typed value and returns it as the pane reads it, in place of the
60
+ * type's own check; throws an Error that names what is wrong.
61
+ */
62
+ normalize?(value: string): string | number | boolean;
54
63
  }
55
64
 
56
65
  export interface HeadlessPaneLoadArgs {
@@ -90,6 +99,8 @@ export interface HeadlessPaneColumn {
90
99
  header: string;
91
100
  align?: "left" | "right" | "center";
92
101
  width?: number;
102
+ /** False keeps the column whole when the text table is fitted to a narrow width, so a time or an id is never cut. */
103
+ shrink?: boolean;
93
104
  description?: string;
94
105
  format?: (value: unknown, row: HeadlessPaneRow) => string;
95
106
  }
@@ -59,4 +59,12 @@ export interface InstrumentSearchResult {
59
59
  /** How often the listing is searched, higher is more (the search service score,
60
60
  * sent by newer cloud servers). Only comparable within one response. */
61
61
  popularity?: number;
62
+ /** Trade names the row answers to ("Aramco" for Saudi Arabian Oil Co.), sent by
63
+ * newer cloud servers on a row a trade-name query added. The legal name does not
64
+ * contain them, so search matches the query against them too. */
65
+ searchAliases?: string[];
66
+ /** A theme the row answers to without its name saying so ("lithium" on
67
+ * Albemarle, which the lithium funds hold), sent by newer cloud servers on a
68
+ * row a theme query added. Matched like the name, ranked after name matches. */
69
+ searchKeywords?: string[];
62
70
  }
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { AppTickerRepositoryPort } from "../core/app-service-ports";
3
- import type { HeadlessPaneDefinition, HeadlessPaneFreshness } from "./headless";
3
+ import type { HeadlessPaneDefinition, HeadlessPaneFreshness, HeadlessPaneOptionDef } from "./headless";
4
4
 
5
5
  export type {
6
6
  HeadlessBundleResult,
@@ -132,6 +132,18 @@ export interface PaneDef {
132
132
  * shows would be wrong: local data, a calculator, filed records.
133
133
  */
134
134
  reportFreshness?: Pick<HeadlessPaneFreshness, "source" | "status" | "basis">;
135
+ /**
136
+ * Options a rendered-view report takes (`fn FXC --currencies USD,ZAR`):
137
+ * each sets the pane setting of its key, is checked by its `normalize`, and
138
+ * is listed with its example by `gloomberb catalog`.
139
+ */
140
+ reportOptions?: readonly PaneReportOptionDef[];
141
+ /**
142
+ * Lines a rendered-view report prints above its table, from the settings it
143
+ * ran with: what the default view leaves out and how to see more. `--json`
144
+ * carries them in `data.metadata.notices`.
145
+ */
146
+ reportNotices?(settings: Readonly<Record<string, unknown>>): string[];
135
147
  /** Add an Excel-compatible CSV action for the pane's single active DataTable. */
136
148
  tableExport?: true;
137
149
  settings?: PaneSettingsDef | ((context: PaneSettingsContext) => PaneSettingsDef | null);
@@ -144,6 +156,20 @@ export interface PaneDef {
144
156
  quickSettings?: readonly PaneQuickSettingDef[];
145
157
  }
146
158
 
159
+ /**
160
+ * An option of a rendered-view report, which sets the pane setting of its key,
161
+ * or of `settingKey` when the flag reads better than the setting's name
162
+ * (`--columns` for `optionColumnIds`).
163
+ */
164
+ export interface PaneReportOptionDef extends Omit<HeadlessPaneOptionDef, "pluginState"> {
165
+ /** The value's name in the catalog's flag, `--currencies <codes>`; the type when absent. */
166
+ placeholder?: string;
167
+ /** The option in use as typed after the function, such as `--currencies USD,ZAR,NGN`; the catalog shows it as an example. */
168
+ example?: string;
169
+ /** Checks a typed value and returns it as the pane reads it; throws an Error that names what is wrong. */
170
+ normalize?(value: string): string;
171
+ }
172
+
147
173
  export interface PaneQuickSettingDef {
148
174
  type: "toggle";
149
175
  key: string;
@@ -389,6 +415,10 @@ export interface CommandResultDef {
389
415
  label: string;
390
416
  detail?: string;
391
417
  category?: string;
418
+ /** Short tag drawn left of the label, as on a search provider's row. Six characters at most. */
419
+ badge?: string;
420
+ /** Drawn muted after the label, e.g. a person's full name after their @username. */
421
+ name?: string;
392
422
  right?: string;
393
423
  keywords?: string[];
394
424
  current?: boolean;
@@ -414,6 +444,8 @@ export interface CommandBarResultDef {
414
444
  category?: string;
415
445
  /** Short tag drawn left of the label, e.g. a document type. Six characters at most. */
416
446
  badge?: string;
447
+ /** Drawn muted after the label, e.g. a person's full name after their @username. */
448
+ name?: string;
417
449
  right?: string;
418
450
  keywords?: string[];
419
451
  disabled?: boolean;
@@ -431,15 +463,29 @@ export interface CommandBarSearchProvider {
431
463
  category: string;
432
464
  /** Sort position of the section. Higher sinks. Navigation sections are negative; use a positive value to sit below them. */
433
465
  priority?: number;
434
- /** Skip provide() below this length. Default 3. */
466
+ /** Skip the provider below this length. Default 3. */
435
467
  minQueryLength?: number;
436
- /** Default 300. */
468
+ /** Default 300. Ignored by `match`. */
437
469
  debounceMs?: number;
470
+ /**
471
+ * Codes (`CHAT`) after which the provider is still asked, typed alone or
472
+ * with text after them, with the whole query. Every other provider stays
473
+ * quiet once a code claims the text.
474
+ */
475
+ shortcuts?: readonly string[];
438
476
  provide(
439
477
  query: string,
440
478
  context: CommandBarSearchContext,
441
479
  signal: AbortSignal,
442
480
  ): Promise<CommandBarResultDef[]>;
481
+ /**
482
+ * Rows from what the app already holds in memory. When set, the bar calls
483
+ * this instead of `provide`, as it renders and on every keystroke, so it
484
+ * must be cheap: no request, no waiting. Its rows are ranked with the bar's
485
+ * own matches as they are typed and sit under Suggested in the empty bar.
486
+ * Keep `provide` answering the same rows for older versions of the app.
487
+ */
488
+ match?(query: string, context: CommandBarSearchContext): CommandBarResultDef[];
443
489
  }
444
490
 
445
491
  interface CliHelpColumn {
package/src/ui/host.tsx CHANGED
@@ -108,10 +108,14 @@ export interface SyntaxStyleLike {
108
108
  }): number;
109
109
  }
110
110
 
111
- /** Where a drag has a box this frame: an offset in cells from where it is laid out. */
111
+ /** Where a drag has a box this frame, in cells. */
112
112
  export interface LiveBoxFrame {
113
- dx: number;
114
- dy: number;
113
+ /** Offset from where the box is laid out, carried on the box's own layer. */
114
+ dx?: number;
115
+ dy?: number;
116
+ /** Size in place of the laid-out one: the box and what stretches inside it lay out again, nothing renders. */
117
+ width?: number;
118
+ height?: number;
115
119
  }
116
120
 
117
121
  export interface BoxRenderable {
@@ -126,7 +130,7 @@ export interface BoxRenderable {
126
130
  /**
127
131
  * Desktop only: draws the box at `frame` by restyling its own element, with
128
132
  * no render, until it is called with null. The offset rides the compositor,
129
- * so it costs no layout or repaint.
133
+ * so it costs no layout or repaint; a size lays out the box's own subtree.
130
134
  */
131
135
  setLiveFrame?: (frame: LiveBoxFrame | null) => void;
132
136
  [key: string]: unknown;
package/src/ui/index.tsx CHANGED
@@ -196,6 +196,8 @@ export const Input = forwardRef<any, ComponentProps<UiHost["Input"]>>((props, re
196
196
  },
197
197
  metadata: {
198
198
  ...remoteMetadataFromProps(rawProps),
199
+ // Lets a reader that must not see typed passwords (the Cloud relay) recognize one.
200
+ ...(rawProps.type === "password" ? { inputType: "password" } : {}),
199
201
  value: rawProps.value,
200
202
  placeholder: rawProps.placeholder,
201
203
  focused: rawProps.focused,
@@ -32,6 +32,7 @@ const MIN_SHRUNK_COLUMN_WIDTH = 8;
32
32
  // Text columns first shrink right to left down to this width, then all shrink together.
33
33
  const SOFT_MIN_COLUMN_WIDTH = 20;
34
34
  let colorEnabledOverride: boolean | null = null;
35
+ let widthOverride: number | null = null;
35
36
 
36
37
  function colorEnabled(): boolean {
37
38
  if (colorEnabledOverride != null) return colorEnabledOverride;
@@ -56,8 +57,14 @@ export function visibleLength(text: string): number {
56
57
  return typeof Bun !== "undefined" ? Bun.stringWidth(text) : stripAnsi(text).length;
57
58
  }
58
59
 
59
- /** Columns of the terminal stdout writes to, or null when output is piped or redirected. */
60
+ /** `--width n`: tables, help and wrapped text fit n columns, whether or not stdout is a terminal. */
61
+ export function setCliWidthOverride(value: number | null): void {
62
+ widthOverride = value;
63
+ }
64
+
65
+ /** Columns the output is fitted to: `--width`, else the terminal's, or null when output is piped or redirected. */
60
66
  export function cliTerminalWidth(): number | null {
67
+ if (widthOverride != null) return widthOverride;
61
68
  if (!process.stdout.isTTY) return null;
62
69
  const columns = process.stdout.columns;
63
70
  return typeof columns === "number" && columns > 0 ? columns : null;
@@ -139,6 +146,12 @@ export function wrapText(text: string, width: number): string[] {
139
146
  return lines;
140
147
  }
141
148
 
149
+ /** One line of text under a table, wrapped to the width the table was fitted to; whole when output is not fitted. */
150
+ export function wrapToTerminal(text: string): string {
151
+ const width = cliTerminalWidth();
152
+ return width == null ? text : wrapText(text, width).join("\n");
153
+ }
154
+
142
155
  export const cliStyles = {
143
156
  bold: (text: string) => applyAnsi(text, "1"),
144
157
  dim: (text: string) => applyAnsi(text, "2"),
@@ -133,6 +133,13 @@ export const CANONICAL_EXCHANGE_ALIASES: Record<string, string> = {
133
133
  XJSE: "JSE",
134
134
  TLV: "TASE",
135
135
  XTAE: "TASE",
136
+ TADAWUL: "TADAWUL",
137
+ XSAU: "TADAWUL",
138
+ QE: "QE",
139
+ QSE: "QE",
140
+ DSMD: "QE",
141
+ DFM: "DFM",
142
+ XDFM: "DFM",
136
143
  CCC: "CCC",
137
144
  };
138
145
 
@@ -222,6 +229,9 @@ export const EXCHANGE_TIME_ZONES: Record<string, string> = {
222
229
  BYMA: "America/Argentina/Buenos_Aires",
223
230
  JSE: "Africa/Johannesburg",
224
231
  TASE: "Asia/Jerusalem",
232
+ TADAWUL: "Asia/Riyadh",
233
+ QE: "Asia/Qatar",
234
+ DFM: "Asia/Dubai",
225
235
  CCC: "UTC",
226
236
  };
227
237
 
@@ -275,6 +285,9 @@ const EXCHANGE_NAMES: Record<string, string> = {
275
285
  BYMA: "Bolsas y Mercados Argentinos",
276
286
  JSE: "Johannesburg Stock Exchange",
277
287
  TASE: "Tel Aviv Stock Exchange",
288
+ TADAWUL: "Saudi Exchange",
289
+ QE: "Qatar Stock Exchange",
290
+ DFM: "Dubai Financial Market",
278
291
  };
279
292
 
280
293
  const KNOWN_EXCHANGE_CODES: ReadonlySet<string> = new Set([
@@ -8,7 +8,8 @@ function getCurrencyFormatter(currency: string, maximumFractionDigits = 2): Intl
8
8
  formatter = new Intl.NumberFormat("en-US", {
9
9
  style: "currency",
10
10
  currency,
11
- minimumFractionDigits: 2,
11
+ // Whole units (0) drop the cents rather than throw.
12
+ minimumFractionDigits: Math.min(2, maximumFractionDigits),
12
13
  maximumFractionDigits,
13
14
  });
14
15
  currencyFormatters.set(key, formatter);
@@ -1,4 +1,14 @@
1
+ import { canonicalExchange, parsePublicTickerKey } from "./exchanges";
2
+ import { zonedDateTimeParts, zonedWallClockToUtcMs } from "./zoned-date-time";
3
+
1
4
  const HOUR_MS = 60 * 60_000;
5
+ const DAY_MS = 24 * HOUR_MS;
6
+ const NEW_YORK = "America/New_York";
7
+
8
+ /** A spot currency pair: `=X` listing syntax (EURUSD=X, JPY=X) or the CCY venue. */
9
+ export function isCurrencyPairListing(symbol: string | undefined, exchange: string | undefined): boolean {
10
+ return canonicalExchange(exchange) === "CCY" || (!!symbol && parsePublicTickerKey(symbol).symbol.endsWith("=X"));
11
+ }
2
12
 
3
13
  const newYorkClock = new Intl.DateTimeFormat("en-US", {
4
14
  timeZone: "America/New_York",
@@ -19,6 +29,30 @@ export function isFxMarketOpen(time: number): boolean {
19
29
  return true;
20
30
  }
21
31
 
32
+ /** The latest close of the FX week, Friday 17:00 New York, at or before `time`. */
33
+ export function latestFxWeekClose(time: number): number | null {
34
+ if (!Number.isFinite(time)) return null;
35
+ const { year, month, day } = zonedDateTimeParts(time, NEW_YORK);
36
+ const today = Date.UTC(year, month - 1, day);
37
+ for (let offset = 0; offset <= 7; offset += 1) {
38
+ const date = new Date(today - offset * DAY_MS);
39
+ if (date.getUTCDay() !== 5) continue;
40
+ const close = zonedWallClockToUtcMs(NEW_YORK, date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(), 17, 0, 0);
41
+ if (close <= time) return close;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ /**
47
+ * When the closure the market is in began (Friday 17:00 New York time); null
48
+ * while it trades. That is `latestFxWeekClose` for every moment the week is
49
+ * shut, since a closure never lasts to the next Friday, daylight saving weeks
50
+ * included.
51
+ */
52
+ export function currentFxClosureStart(time: number): number | null {
53
+ return isFxMarketOpen(time) ? null : latestFxWeekClose(time);
54
+ }
55
+
22
56
  /**
23
57
  * When an FX observation has aged `windowMs`, counting only time the market
24
58
  * is open. Friday's last print stays current over the weekend, while a rate
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Option expiries as people type and read them. A chain stamps each expiry as
3
+ * Unix seconds at that calendar date's UTC midnight, so a date and its stamp
4
+ * convert both ways, and two stamps on the same UTC date are the same expiry.
5
+ */
6
+
7
+ // 9999-12-31 in Unix seconds; a longer number is milliseconds or a typo.
8
+ const MAX_UNIX_SECONDS = 253_402_300_799;
9
+ const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
10
+ const UNIX_SECONDS = /^\d{1,12}$/;
11
+ const DAY_MS = 86_400_000;
12
+
13
+ /** How `--expiration` is written, for usage lines and errors. */
14
+ export const OPTION_EXPIRATION_FORMAT = "YYYY-MM-DD (2028-01-21) or Unix seconds (1832025600)";
15
+ /** The value's name in a catalog flag, `--expiration <YYYY-MM-DD|unix>`. */
16
+ export const OPTION_EXPIRATION_PLACEHOLDER = "YYYY-MM-DD|unix";
17
+
18
+ /** The expiry's calendar date, `2028-01-21`; empty for a stamp that is not a time. */
19
+ export function expiryIsoDate(seconds: number): string {
20
+ const date = new Date(seconds * 1000);
21
+ return Number.isNaN(date.getTime()) ? "" : date.toISOString().slice(0, 10);
22
+ }
23
+
24
+ /**
25
+ * `--expiration` as the Unix seconds a chain request takes: `2028-01-21` is that
26
+ * date at 00:00 UTC, a bare integer is already Unix seconds. Anything else,
27
+ * including a date the calendar does not have, is null.
28
+ */
29
+ export function parseOptionExpiration(raw: string): number | null {
30
+ const text = raw.trim();
31
+ const date = ISO_DATE.exec(text);
32
+ if (date) {
33
+ const [year, month, day] = [Number(date[1]), Number(date[2]), Number(date[3])];
34
+ const at = new Date(Date.UTC(year, month - 1, day));
35
+ const real = at.getUTCFullYear() === year && at.getUTCMonth() === month - 1 && at.getUTCDate() === day;
36
+ return real && at.getTime() > 0 ? at.getTime() / 1000 : null;
37
+ }
38
+ if (!UNIX_SECONDS.test(text)) return null;
39
+ const seconds = Number(text);
40
+ return seconds > 0 && seconds <= MAX_UNIX_SECONDS ? seconds : null;
41
+ }
42
+
43
+ /**
44
+ * A pane function's typed expiry flag as the Unix seconds `parseOptionExpiration`
45
+ * reads; throws an Error naming both accepted forms.
46
+ */
47
+ export function expirationOptionSeconds(value: string, flag = "expiration"): number {
48
+ const expiration = parseOptionExpiration(value);
49
+ if (expiration == null) throw new Error(`Invalid --${flag} "${value}". Use ${OPTION_EXPIRATION_FORMAT}.`);
50
+ return expiration;
51
+ }
52
+
53
+ /** A stored or typed expiry setting (seconds, or text `parseOptionExpiration` reads) as Unix seconds; null when it is neither. */
54
+ export function readOptionExpiration(value: unknown): number | null {
55
+ if (typeof value === "number") return Number.isFinite(value) && value > 0 && value <= MAX_UNIX_SECONDS ? value : null;
56
+ return typeof value === "string" ? parseOptionExpiration(value) : null;
57
+ }
58
+
59
+ /** The listed stamp for the requested expiry's date, which may differ from the requested stamp in time of day. */
60
+ export function findListedExpiry(requested: number, expirations: readonly number[]): number | undefined {
61
+ if (expirations.includes(requested)) return requested;
62
+ const date = expiryIsoDate(requested);
63
+ return date ? expirations.find((expiration) => expiryIsoDate(expiration) === date) : undefined;
64
+ }
65
+
66
+ /** Whole calendar days from the as-of date to the expiry, both read as UTC dates; negative once it has passed. */
67
+ export function daysToExpiry(expiration: number, asOfMs: number): number {
68
+ const expiry = Date.parse(expiryIsoDate(expiration));
69
+ const asOf = Date.parse(new Date(asOfMs).toISOString().slice(0, 10));
70
+ return Math.round((expiry - asOf) / DAY_MS);
71
+ }
72
+
73
+ /** `469d` to expiry from the as-of date; `expired` once it has passed. */
74
+ export function formatDaysToExpiry(expiration: number, asOfMs: number): string {
75
+ const days = daysToExpiry(expiration, asOfMs);
76
+ return days < 0 ? "expired" : `${days}d`;
77
+ }
78
+
79
+ const NEAREST_EXPIRIES = 4;
80
+
81
+ /**
82
+ * The listed expiries nearest a requested date, in date order, so a typo or an
83
+ * unlisted LEAPS date points at the neighbours to pick. `listed` says the date
84
+ * itself is listed (its chain came back empty); `more` counts the rest.
85
+ */
86
+ function expiryChoices(requested: number, expirations: readonly number[]) {
87
+ const { nearest, more, ...rest } = nearestListedExpiries(requested, expirations);
88
+ return { ...rest, choices: `${nearest.join(", ")}${more > 0 ? ` (+${more} more)` : ""}` };
89
+ }
90
+
91
+ /**
92
+ * Why a requested expiry is not in a catalogue, for a line that already names
93
+ * the date: `not listed; nearest: 2027-12-17, 2028-01-21, … (+20 more)`.
94
+ */
95
+ export function unlistedExpiryText(requested: number, expirations: readonly number[]): string {
96
+ const { total, choices } = expiryChoices(requested, expirations);
97
+ return total === 0 ? "not listed; the chain lists no expiries" : `not listed; nearest: ${choices}`;
98
+ }
99
+
100
+ function nearestListedExpiries(
101
+ requested: number,
102
+ expirations: readonly number[],
103
+ count = NEAREST_EXPIRIES,
104
+ ): { date: string; listed: boolean; nearest: string[]; more: number; total: number } {
105
+ const date = expiryIsoDate(requested);
106
+ const dates = [...new Set(expirations.map(expiryIsoDate).filter(Boolean))].sort();
107
+ const others = dates.filter((listed) => listed !== date);
108
+ const distance = (listed: string) => Math.abs(Date.parse(listed) - Date.parse(date));
109
+ const nearest = [...others].sort((left, right) => distance(left) - distance(right) || left.localeCompare(right))
110
+ .slice(0, count).sort();
111
+ return { date, listed: dates.includes(date), nearest, more: others.length - nearest.length, total: dates.length };
112
+ }
113
+
114
+ /**
115
+ * Why an expiry has no contracts, naming the nearest listed ones:
116
+ * `no expiry 2028-01-22; available: 2027-12-17, 2028-01-21, … (+20 more)`.
117
+ * `subject` names the underlying where nothing else on screen does.
118
+ */
119
+ export function missingExpiryText(requested: number, expirations: readonly number[], subject?: string): string {
120
+ const { date, listed, total, choices } = expiryChoices(requested, expirations);
121
+ const of = subject ? ` for ${subject}` : "";
122
+ if (total === 0) return `no expiry ${date}${of}; the chain lists no expiries`;
123
+ return listed
124
+ ? `no option contracts returned for${subject ? ` ${subject}` : ""} expiry ${date}; nearest others: ${choices}`
125
+ : `no expiry ${date}${of}; available: ${choices}`;
126
+ }