gloomberb 0.16.2 → 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 (527) 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/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -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,6 +108,16 @@ export interface SyntaxStyleLike {
108
108
  }): number;
109
109
  }
110
110
 
111
+ /** Where a drag has a box this frame, in cells. */
112
+ export interface LiveBoxFrame {
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;
119
+ }
120
+
111
121
  export interface BoxRenderable {
112
122
  x?: number;
113
123
  y?: number;
@@ -117,6 +127,12 @@ export interface BoxRenderable {
117
127
  absoluteY?: number;
118
128
  absoluteBounds?: { x: number; y: number; width: number; height: number };
119
129
  getBoundingClientRect?: () => { x: number; y: number; width: number; height: number };
130
+ /**
131
+ * Desktop only: draws the box at `frame` by restyling its own element, with
132
+ * no render, until it is called with null. The offset rides the compositor,
133
+ * so it costs no layout or repaint; a size lays out the box's own subtree.
134
+ */
135
+ setLiveFrame?: (frame: LiveBoxFrame | null) => void;
120
136
  [key: string]: unknown;
121
137
  }
122
138
 
package/src/ui/index.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createElement, forwardRef, useCallback, useRef, type ComponentProps } from "react";
2
2
  import { useForwardedScrollBoxRef, useRegisterPaneScrollBox } from "../state/pane-scroll-registry";
3
3
  import { useUiHost, type UiHost } from "./host";
4
+ import { scrollBoxScrollsVertically } from "./scroll-box-axis";
4
5
  import { useRemoteUiNode } from "../remote/semantic-tree";
5
6
  import { assignRef } from "../react/assign-ref";
6
7
  import {
@@ -48,6 +49,7 @@ export type {
48
49
  MediaSurfaceHandle,
49
50
  MediaSurfaceProps,
50
51
  InputRenderable,
52
+ LiveBoxFrame,
51
53
  NativeCursorState,
52
54
  NativeRendererHost,
53
55
  NativePostProcessFn,
@@ -128,8 +130,11 @@ export const ScrollBox = forwardRef<any, ComponentProps<UiHost["ScrollBox"]>>((p
128
130
  const { ScrollBox: HostScrollBox } = useUiHost();
129
131
  const localRef = useForwardedScrollBoxRef<any>(ref);
130
132
  const rawProps = props as Record<string, unknown>;
133
+ // The same rule the hosts scroll by: a ScrollBox that omits scrollY scrolls,
134
+ // so it is a scrollbox to the remote tree and to the pane scroll keys.
135
+ const scrollsVertically = scrollBoxScrollsVertically(props);
131
136
  const remoteNodeId = useRemoteUiNode(
132
- props.scrollY === true
137
+ scrollsVertically
133
138
  ? {
134
139
  role: remotePropRole(rawProps, "scrollbox"),
135
140
  label: remotePropLabel(rawProps),
@@ -150,7 +155,7 @@ export const ScrollBox = forwardRef<any, ComponentProps<UiHost["ScrollBox"]>>((p
150
155
  ...remoteMetadataFromProps(rawProps),
151
156
  width: rawProps.width,
152
157
  height: rawProps.height,
153
- scrollY: props.scrollY === true,
158
+ scrollY: true,
154
159
  scrollTop: localRef.current?.scrollTop,
155
160
  viewportHeight: localRef.current?.viewport?.height,
156
161
  },
@@ -158,7 +163,7 @@ export const ScrollBox = forwardRef<any, ComponentProps<UiHost["ScrollBox"]>>((p
158
163
  : null,
159
164
  );
160
165
  useRegisterPaneScrollBox(localRef, {
161
- enabled: props.scrollY === true,
166
+ enabled: scrollsVertically,
162
167
  onScrollActivity: typeof props.onMouseScroll === "function"
163
168
  ? props.onMouseScroll as (event: { scroll: { direction: "up" | "down"; delta: number } }) => void
164
169
  : undefined,
@@ -191,6 +196,8 @@ export const Input = forwardRef<any, ComponentProps<UiHost["Input"]>>((props, re
191
196
  },
192
197
  metadata: {
193
198
  ...remoteMetadataFromProps(rawProps),
199
+ // Lets a reader that must not see typed passwords (the Cloud relay) recognize one.
200
+ ...(rawProps.type === "password" ? { inputType: "password" } : {}),
194
201
  value: rawProps.value,
195
202
  placeholder: rawProps.placeholder,
196
203
  focused: rawProps.focused,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Which axes a ScrollBox scrolls on, decided from its props. The terminal and
3
+ * DOM hosts and the pane keyboard scroll registration all read it here, so a
4
+ * ScrollBox that omits `scrollY` behaves the same everywhere: it scrolls
5
+ * vertically, with the wheel and with the arrows, PageUp, PageDown, Home and
6
+ * End. Pass `scrollY={false}` to turn that off.
7
+ */
8
+ interface ScrollBoxAxisProps {
9
+ scrollX?: unknown;
10
+ scrollY?: unknown;
11
+ height?: unknown;
12
+ }
13
+
14
+ /**
15
+ * A one-row strip that only scrolls sideways: tab bars, table headers and
16
+ * chart legends. It is not a vertical scroller even though `scrollY` is
17
+ * omitted.
18
+ */
19
+ export function isHeaderStripScrollBox(props: ScrollBoxAxisProps): boolean {
20
+ return props.scrollX === true && props.scrollY !== true && props.height === 1;
21
+ }
22
+
23
+ /** Whether the ScrollBox scrolls vertically: unless `scrollY` is false, and never a header strip. */
24
+ export function scrollBoxScrollsVertically(props: ScrollBoxAxisProps): boolean {
25
+ return props.scrollY !== false && !isHeaderStripScrollBox(props);
26
+ }
@@ -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"),
@@ -13,11 +13,16 @@ const SUB_UNIT_NAMES: Record<string, string> = {
13
13
  ZAc: "cents",
14
14
  };
15
15
 
16
- /** A currency code as a reader should see it; a sub-unit says which one: "GBp (pence)". */
17
- export function currencyUnitLabel(code?: string | null): string {
16
+ /**
17
+ * A currency code as a reader should see it; a sub-unit says which one,
18
+ * "GBp (pence)", and with `ofMajor` what it divides: "ILA (agorot, 1/100 ILS)".
19
+ */
20
+ export function currencyUnitLabel(code?: string | null, { ofMajor = false }: { ofMajor?: boolean } = {}): string {
18
21
  const raw = (code ?? "").trim();
19
22
  const unit = SUB_UNIT_NAMES[raw];
20
- return unit ? `${raw} (${unit})` : raw;
23
+ const major = SUB_UNIT_CURRENCIES[raw];
24
+ if (!unit) return raw;
25
+ return ofMajor && major ? `${raw} (${unit}, 1/${major.divisor} ${major.currency})` : `${raw} (${unit})`;
21
26
  }
22
27
 
23
28
  const LIKELY_UNIT_MISMATCH_RATIOS: Record<string, number[]> = {
@@ -1,26 +1,70 @@
1
+ import type { Fundamentals, Quote, ShareBasis } from "../types/financials";
2
+
1
3
  // A word of its own, so a company called "ADS-TEC" is not one.
2
4
  const DEPOSITARY_NAME = /(?:^|\s)(?:ADRs?|ADSs?|American depositary (?:receipts?|shares?))(?=$|[\s,)])/i;
3
5
  const DEPOSITARY_TYPES = new Set(["ADR", "ADS", "DEPOSITARYRECEIPT"]);
4
6
  /** A profile that opens by naming the security: "Toyota Motor Corporation Sponsored ADR is ...". */
5
7
  const DEPOSITARY_SUBJECT = /^.{0,160}?\s(?:ADRs?|ADSs?|American depositary (?:receipts?|shares?)) is\s/i;
6
8
 
7
- /**
8
- * Whether a US listing is a depositary receipt, as far as its data says. A
9
- * quote carries no ADR flag or ratio, so this reads the instrument type, the
10
- * name, and the security name a profile description opens with. Its share
11
- * count and market cap are then counted in receipts, not ordinary shares. A
12
- * listing in another currency is the ordinary line even when its profile
13
- * mentions the receipts.
14
- */
15
- export function isDepositaryReceipt(input: {
9
+ interface ListingEvidence {
10
+ /** Gloom Cloud's verdict; absent when it has no evidence either way. */
11
+ isDepositaryReceipt?: boolean | null;
16
12
  instrumentType?: string | null;
17
13
  name?: string | null;
18
14
  currency?: string | null;
19
15
  description?: string | null;
20
- }): boolean {
16
+ }
17
+
18
+ /**
19
+ * Whether a listing is a depositary receipt. Gloom Cloud's flag decides when
20
+ * it is set. Without it (an older service, or a listing it has no evidence
21
+ * for) this reads the instrument type, the name, and the security name a
22
+ * profile description opens with. A listing in another currency is the
23
+ * ordinary line even when its profile mentions the receipts.
24
+ */
25
+ export function isDepositaryReceipt(input: ListingEvidence): boolean {
26
+ if (typeof input.isDepositaryReceipt === "boolean") return input.isDepositaryReceipt;
21
27
  const currency = input.currency?.trim().toUpperCase();
22
28
  if (currency && currency !== "USD") return false;
23
29
  if (DEPOSITARY_TYPES.has((input.instrumentType ?? "").replace(/[\s_-]/g, "").toUpperCase())) return true;
24
30
  if (input.name && DEPOSITARY_NAME.test(input.name)) return true;
25
31
  return DEPOSITARY_SUBJECT.test(input.description?.trim() ?? "");
26
32
  }
33
+
34
+ /**
35
+ * Whether a record's share counts are in receipts. Gloom Cloud says so with
36
+ * `shareBasis`; without it, a receipt's counts are read as receipts, as its
37
+ * market cap is.
38
+ */
39
+ function sharesInReceipts(input: ListingEvidence & { shareBasis?: ShareBasis | null }): boolean {
40
+ if (input.shareBasis === "depositary_receipt") return true;
41
+ if (input.shareBasis === "ordinary") return false;
42
+ return isDepositaryReceipt(input);
43
+ }
44
+
45
+ /**
46
+ * Whether a listing's shares outstanding are counted in receipts: the
47
+ * fundamentals' basis, else the listing's flag (the fundamentals' or the
48
+ * quote's), else what the quote and profile say.
49
+ */
50
+ export function sharesOutstandingInReceipts(
51
+ quote: Pick<Quote, "isDepositaryReceipt" | "instrumentType" | "name" | "currency"> | null | undefined,
52
+ fundamentals: Pick<Fundamentals, "isDepositaryReceipt" | "shareBasis"> | null | undefined,
53
+ description?: string | null,
54
+ ): boolean {
55
+ return sharesInReceipts({
56
+ instrumentType: quote?.instrumentType,
57
+ name: quote?.name,
58
+ currency: quote?.currency,
59
+ description,
60
+ isDepositaryReceipt: fundamentals?.isDepositaryReceipt ?? quote?.isDepositaryReceipt,
61
+ shareBasis: fundamentals?.shareBasis,
62
+ });
63
+ }
64
+
65
+ /** "1 ADR = 2 ordinary shares", or null without a usable ratio. */
66
+ export function adrRatioText(ratio: number | null | undefined): string | null {
67
+ if (typeof ratio !== "number" || !Number.isFinite(ratio) || ratio <= 0) return null;
68
+ const count = Number.isInteger(ratio) ? String(ratio) : String(Number(ratio.toPrecision(6)));
69
+ return `1 ADR = ${count} ordinary share${ratio === 1 ? "" : "s"}`;
70
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Reading back a number a table or report drew ("+1.25%", "1.20B", "$1,234.50"),
3
+ * so a CSV export can write the number a spreadsheet uses and name the unit
4
+ * in its header instead of in every cell.
5
+ */
6
+
7
+ /** What a cell draws for a missing value: a dash, a run of dashes, or the loading ellipsis. */
8
+ export const DISPLAY_PLACEHOLDER = /^(?:[-\u2013\u2014]{1,3}|\u2026|\.\.\.)$/;
9
+
10
+ /**
11
+ * A number as a table draws it: sign, currency symbol, thousands commas,
12
+ * decimals, a compact scale (12.3k, 1.20B) and a unit (%, x, bp, USD, or c
13
+ * for the cents grain futures quote in).
14
+ */
15
+ const DISPLAY_NUMBER = /^([+-]?)([A-Z]{0,3}[$\u20ac\u00a3\u00a5\u20b9\u20a9\u20bd\u20ba\u20aa\u20ab\u0e3f])?(?=\.?\d)(\d{1,3}(?:,\d{3})+|\d*)(?:\.(\d+))?([kKMBT])?(%|x|c|\u00a2| ?bps?| [A-Z]{3})?$/;
16
+
17
+ const SCALE_DIGITS: Record<string, number> = { k: 3, K: 3, M: 6, B: 9, T: 12 };
18
+
19
+ export interface DisplayNumber {
20
+ /** Plain decimal text: no sign for positives, no commas, no scale letter. */
21
+ number: string;
22
+ /** What the column header has to name once the cells are bare numbers. */
23
+ unit: string;
24
+ /** The place value of the last digit drawn: 0.01 for "1.25%", 10,000,000 for "1.20B". */
25
+ step: number;
26
+ }
27
+
28
+ export const BLANK_DISPLAY = "blank";
29
+
30
+ /** Moves the decimal point right without going through a float, so 1.23B is exactly 1230000000. */
31
+ function scaleDecimal(integer: string, fraction: string, digits: number): string {
32
+ const whole = `${integer}${fraction.padEnd(digits, "0").slice(0, digits)}`.replace(/^0+(?=\d)/, "") || "0";
33
+ const rest = fraction.slice(digits);
34
+ return rest ? `${whole}.${rest}` : whole;
35
+ }
36
+
37
+ export function parseDisplayNumber(text: string): DisplayNumber | typeof BLANK_DISPLAY | null {
38
+ // A direction arrow beside a signed move repeats the sign.
39
+ const trimmed = text.trim().replace(/\u2212/g, "-").replace(/\s*[\u2191\u2193]$/, "");
40
+ if (!trimmed || DISPLAY_PLACEHOLDER.test(trimmed)) return BLANK_DISPLAY;
41
+ const match = DISPLAY_NUMBER.exec(trimmed);
42
+ if (!match) return null;
43
+ const [, sign, currency = "", grouped, fraction = "", scale, unitText = ""] = match;
44
+ const integer = grouped!.replace(/,/g, "") || "0";
45
+ const digits = scale ? SCALE_DIGITS[scale]! : 0;
46
+ const magnitude = digits ? scaleDecimal(integer, fraction, digits) : fraction ? `${integer}.${fraction}` : integer;
47
+ const unit = unitText.trim().replace(/^bps$/, "bp").replace(/^\u00a2$/, "c");
48
+ return {
49
+ number: `${sign === "-" ? "-" : ""}${magnitude}`,
50
+ unit: [currency, unit].filter(Boolean).join(" "),
51
+ step: 10 ** (digits - fraction.length),
52
+ };
53
+ }
54
+
55
+ /** Adds the unit the cells dropped, unless the label already names it. */
56
+ export function labelWithUnit(label: string, unit: string): string {
57
+ if (!unit) return label;
58
+ // A word unit has to stand alone: "Max" does not name x.
59
+ const named = unit === "bp" ? /\bbps?\b/i.test(label)
60
+ : /^[A-Za-z]+$/.test(unit) ? new RegExp(`\\b${unit}\\b`, "i").test(label)
61
+ : label.includes(unit);
62
+ return named ? label : `${label} (${unit})`;
63
+ }
@@ -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,4 @@
1
- import type { Fundamentals } from "../types/financials";
1
+ import type { Fundamentals, TickerFinancials } from "../types/financials";
2
2
 
3
3
  export const RETRACTABLE_VALUATION_FIELDS = ["enterpriseValue", "enterpriseToRevenue"] as const;
4
4
 
@@ -13,3 +13,19 @@ export function redactUnavailableFundamentals(value: Fundamentals | undefined):
13
13
  for (const field of unavailableFields) delete result[field];
14
14
  return result;
15
15
  }
16
+
17
+ /** A source's enterprise value, or undefined when it has none: banks and insurers come back as 0, which is a gap, not a value. */
18
+ export function reportedEnterpriseValue(fundamentals: Pick<Fundamentals, "enterpriseValue"> | undefined): number | undefined {
19
+ const value = fundamentals?.enterpriseValue;
20
+ return typeof value === "number" && Number.isFinite(value) && value !== 0 ? value : undefined;
21
+ }
22
+
23
+ /**
24
+ * The currency of the trailing EPS, revenue, income and cash flows: the one the fundamentals declare, else the
25
+ * one the statements report in. Never the quote's, which a depositary receipt can differ from (USD quote, TWD books).
26
+ */
27
+ export function fundamentalsCurrency(
28
+ financials: Pick<TickerFinancials, "fundamentals" | "financialCurrency"> | null | undefined,
29
+ ): string | undefined {
30
+ return financials?.fundamentals?.financialCurrency?.trim() || financials?.financialCurrency?.trim() || undefined;
31
+ }
@@ -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
+ }