gloomberb 0.18.2 → 0.18.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 (954) hide show
  1. package/README.md +8 -8
  2. package/README.zh-CN.md +11 -11
  3. package/bin/{gloomberb → gloom} +3 -1
  4. package/package.json +6 -2
  5. package/patches/electrobun@1.18.1.patch +106 -5
  6. package/src/account/checkout-handoff.tsx +225 -0
  7. package/src/account/device-signin-dialog.tsx +2 -2
  8. package/src/account/device-signin.ts +2 -2
  9. package/src/account/upgrade-dialog.tsx +1 -1
  10. package/src/api-client/askg.ts +2 -2
  11. package/src/api-client/auth.ts +2 -2
  12. package/src/api-client/central-bank-rates.ts +6 -0
  13. package/src/api-client/collections.ts +5 -1
  14. package/src/api-client/cot.ts +2 -0
  15. package/src/api-client/crypto-etf-flows.ts +37 -0
  16. package/src/api-client/crypto-markets.ts +60 -0
  17. package/src/api-client/data.ts +99 -5
  18. package/src/api-client/doe.ts +15 -0
  19. package/src/api-client/equity-screener.ts +28 -0
  20. package/src/api-client/exchange-announcements.ts +38 -0
  21. package/src/api-client/fund-facts.ts +39 -0
  22. package/src/api-client/futures-curve.ts +9 -1
  23. package/src/api-client/hiring.ts +301 -0
  24. package/src/api-client/index.ts +24 -5
  25. package/src/api-client/market-discovery.ts +14 -0
  26. package/src/api-client/members.ts +6 -0
  27. package/src/api-client/mna.ts +6 -0
  28. package/src/api-client/paths.ts +18 -1
  29. package/src/api-client/request.ts +26 -7
  30. package/src/api-client/short-side-publications.ts +37 -0
  31. package/src/api-client/socket.ts +64 -4
  32. package/src/api-client/swap-curve.ts +35 -0
  33. package/src/api-client/telemetry.ts +17 -0
  34. package/src/api-client/themes.ts +7 -1
  35. package/src/api-client/types.ts +90 -2
  36. package/src/app/global-shortcuts.ts +1 -16
  37. package/src/app/pane-runtime/plugin-bindings.ts +9 -2
  38. package/src/app/runtime/desktop-deeplink.ts +5 -5
  39. package/src/app/runtime/plugin-bindings.ts +2 -2
  40. package/src/app/runtime/startup.ts +13 -0
  41. package/src/app/runtime/update.ts +28 -8
  42. package/src/app/star-prompt/status-line.tsx +2 -2
  43. package/src/architecture/core-builtin-allowlist.json +5 -2
  44. package/src/architecture/pane-tabs-allowlist.json +65 -0
  45. package/src/architecture/ui-literals-allowlist.json +3 -3
  46. package/src/architecture/ui-tokens-allowlist.json +1 -1
  47. package/src/brokers/broker-portfolio-sync.ts +37 -0
  48. package/src/brokers/directory.ts +11 -0
  49. package/src/brokers/logos.ts +3 -1
  50. package/src/brokers/signed-in/adapter.ts +304 -26
  51. package/src/brokers/signed-in/client.ts +14 -2
  52. package/src/brokers/signed-in/connect.ts +10 -2
  53. package/src/brokers/sync-broker-instance.ts +9 -1
  54. package/src/capabilities/chart-series.ts +22 -6
  55. package/src/cli/bank-section.ts +1 -1
  56. package/src/cli/base-converter.ts +23 -14
  57. package/src/cli/central-bank-events.ts +1 -1
  58. package/src/cli/command-options.ts +59 -4
  59. package/src/cli/commands/api.ts +9 -6
  60. package/src/cli/commands/broker.ts +2 -2
  61. package/src/cli/commands/command-utils.ts +2 -2
  62. package/src/cli/commands/crypto-hints.ts +7 -20
  63. package/src/cli/commands/fred-series.ts +4 -2
  64. package/src/cli/commands/keybindings.ts +2 -2
  65. package/src/cli/commands/market.ts +1189 -296
  66. package/src/cli/commands/news-hints.ts +2 -2
  67. package/src/cli/commands/news-search.ts +59 -6
  68. package/src/cli/commands/options-leaps.ts +41 -26
  69. package/src/cli/commands/overview.ts +121 -48
  70. package/src/cli/commands/plugins.ts +9 -9
  71. package/src/cli/commands/remote.ts +13 -10
  72. package/src/cli/commands/search.ts +64 -44
  73. package/src/cli/commands/system.ts +27 -19
  74. package/src/cli/commands/themes.ts +1 -1
  75. package/src/cli/commands/ticker.ts +272 -70
  76. package/src/cli/context.ts +3 -2
  77. package/src/cli/desktop-pane-shot-view.ts +2 -2
  78. package/src/cli/desktop-pane-shot.ts +1 -1
  79. package/src/cli/earnings-calendar.ts +4 -4
  80. package/src/cli/entry.ts +18 -1
  81. package/src/cli/errors.ts +20 -2
  82. package/src/cli/exit-codes.ts +1 -1
  83. package/src/cli/export-source.ts +10 -4
  84. package/src/cli/freshness.ts +2 -8
  85. package/src/cli/fx-forward.ts +130 -0
  86. package/src/cli/fx-pair.ts +10 -9
  87. package/src/cli/glossary.ts +64 -20
  88. package/src/cli/help.ts +20 -14
  89. package/src/cli/history-export.ts +126 -0
  90. package/src/cli/history-rows.ts +196 -43
  91. package/src/cli/index-roots.ts +5 -45
  92. package/src/cli/index.ts +32 -18
  93. package/src/cli/issuer-membership.ts +103 -0
  94. package/src/cli/known-gaps.ts +118 -25
  95. package/src/cli/listing-arg.ts +315 -43
  96. package/src/cli/name-entries.ts +73 -22
  97. package/src/cli/native-smoke.ts +2 -0
  98. package/src/cli/not-a-ticker.ts +19 -9
  99. package/src/cli/opentui-libc.ts +5 -0
  100. package/src/cli/options.ts +12 -0
  101. package/src/cli/pane-functions/capabilities.ts +77 -20
  102. package/src/cli/pane-functions/catalog.ts +40 -23
  103. package/src/cli/pane-functions/discovery.ts +1 -1
  104. package/src/cli/pane-functions/dom.ts +12 -8
  105. package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
  106. package/src/cli/pane-functions/freshness.ts +174 -23
  107. package/src/cli/pane-functions/function-help.ts +66 -27
  108. package/src/cli/pane-functions/headless.ts +48 -16
  109. package/src/cli/pane-functions/index.ts +82 -21
  110. package/src/cli/pane-functions/options.ts +22 -2
  111. package/src/cli/pane-functions/report-notices.ts +12 -4
  112. package/src/cli/pane-functions/report.ts +5 -5
  113. package/src/cli/pane-functions/resolver.ts +65 -14
  114. package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
  115. package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
  116. package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
  117. package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
  118. package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
  119. package/src/cli/pane-functions/screenshot-payload.ts +453 -0
  120. package/src/cli/pane-functions/screenshot-render.ts +316 -0
  121. package/src/cli/pane-functions/screenshot-types.ts +162 -0
  122. package/src/cli/pane-functions/shot-options.ts +8 -3
  123. package/src/cli/peso-dollar-rates.ts +79 -0
  124. package/src/cli/quote-answer.ts +38 -3
  125. package/src/cli/report-tables.ts +190 -27
  126. package/src/cli/restore-plugins.ts +29 -14
  127. package/src/cli/result.ts +122 -25
  128. package/src/cli/series-search.ts +5 -5
  129. package/src/cli/statement-filings.ts +19 -0
  130. package/src/cli/suffixed-listing-rows.ts +49 -0
  131. package/src/cli/symbol-args.ts +50 -15
  132. package/src/cli/symbol-cells.ts +120 -0
  133. package/src/cli/symbol-list.ts +226 -60
  134. package/src/cli/symbol-rows.ts +131 -25
  135. package/src/cli/ticker-resolution.ts +108 -11
  136. package/src/cli/treasury-yields.ts +5 -5
  137. package/src/cli/types.ts +2 -0
  138. package/src/cli/uncovered-instrument.ts +3 -3
  139. package/src/cli/unknown-command.ts +6 -6
  140. package/src/components/chart/composite/chart-layout.ts +131 -0
  141. package/src/components/chart/composite/composite-chart.tsx +387 -21
  142. package/src/components/chart/composite/drawing-store.ts +26 -6
  143. package/src/components/chart/composite/interactions.ts +9 -3
  144. package/src/components/chart/composite/legend.tsx +75 -3
  145. package/src/components/chart/composite/levels.ts +5 -0
  146. package/src/components/chart/composite/panel-gestures.ts +41 -0
  147. package/src/components/chart/composite/panel-labels.ts +2 -1
  148. package/src/components/chart/composite/panel-surface.tsx +316 -11
  149. package/src/components/chart/composite/price-axis-labels.tsx +27 -3
  150. package/src/components/chart/composite/rasterizer.ts +44 -19
  151. package/src/components/chart/composite/scene.ts +16 -4
  152. package/src/components/chart/composite/text-renderer.ts +48 -21
  153. package/src/components/chart/composite/toolbar.tsx +52 -5
  154. package/src/components/chart/composite/types.ts +19 -0
  155. package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
  156. package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
  157. package/src/components/chart/composite/use-composite-viewport.ts +12 -3
  158. package/src/components/chart/composite/value-scale.ts +149 -0
  159. package/src/components/chart/composite/volume-profile-paint.ts +6 -3
  160. package/src/components/chart/core/pointer.ts +2 -0
  161. package/src/components/chart-table/index.ts +2 -0
  162. package/src/components/chart-table/read.tsx +74 -0
  163. package/src/components/command-bar/commands/collection-list.ts +41 -19
  164. package/src/components/command-bar/commands/collection.ts +9 -3
  165. package/src/components/command-bar/commands/direct/index.ts +1 -1
  166. package/src/components/command-bar/commands/direct/runtime.ts +3 -2
  167. package/src/components/command-bar/pane-templates/items.ts +59 -6
  168. package/src/components/command-bar/pane-templates/workflow.ts +10 -1
  169. package/src/components/command-bar/routes/root/command-items.ts +32 -6
  170. package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
  171. package/src/components/command-bar/routes/root/provider-search.ts +42 -11
  172. package/src/components/command-bar/routes/root/results.ts +18 -4
  173. package/src/components/command-bar/routes/root/runtime.ts +15 -3
  174. package/src/components/command-bar/routes/root/search-providers.ts +25 -2
  175. package/src/components/command-bar/routes/root/selection.ts +6 -1
  176. package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
  177. package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
  178. package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
  179. package/src/components/command-bar/selection-runtime.ts +50 -10
  180. package/src/components/command-bar/surface/index.tsx +24 -2
  181. package/src/components/command-bar/view-model.ts +7 -0
  182. package/src/components/command-bar/workflow/broker.ts +2 -2
  183. package/src/components/command-bar/workflow/collection-actions.ts +46 -7
  184. package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
  185. package/src/components/command-bar/workflow/fields.ts +8 -2
  186. package/src/components/command-bar/workflow/ops.ts +18 -4
  187. package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
  188. package/src/components/command-bar/workflow/submit.ts +3 -2
  189. package/src/components/command-bar/workflow/tickers.ts +5 -1
  190. package/src/components/company-logo.tsx +39 -0
  191. package/src/components/data-table/sections.ts +4 -2
  192. package/src/components/data-table/view.tsx +23 -3
  193. package/src/components/form-modal/confirm.tsx +2 -1
  194. package/src/components/form-modal/request.ts +5 -0
  195. package/src/components/form-modal/routes.ts +4 -1
  196. package/src/components/index.ts +13 -6
  197. package/src/components/layout/detached-pane-shell.tsx +1 -0
  198. package/src/components/layout/floating-pane.tsx +1 -0
  199. package/src/components/layout/header.tsx +234 -60
  200. package/src/components/layout/market-summary.ts +9 -6
  201. package/src/components/layout/pane/capture-status.ts +1 -1
  202. package/src/components/layout/pane/footer/model.ts +11 -5
  203. package/src/components/layout/pane/header.tsx +10 -3
  204. package/src/components/layout/pane/index.tsx +1 -0
  205. package/src/components/layout/pane/pane-tabs.tsx +9 -1
  206. package/src/components/layout/shell/chrome.ts +19 -6
  207. package/src/components/layout/shell/index.tsx +2 -1
  208. package/src/components/layout/shell/pane/layers.tsx +19 -1
  209. package/src/components/layout/status-bar.tsx +2 -2
  210. package/src/components/market-board/index.tsx +11 -3
  211. package/src/components/onboarding/onboarding-wizard.tsx +1 -0
  212. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
  213. package/src/components/onboarding/wizard-broker-sync.ts +4 -1
  214. package/src/components/pane-settings-dialog/desktop.tsx +20 -7
  215. package/src/components/pane-settings-dialog/tui.tsx +17 -9
  216. package/src/components/pane-settings-dialog/value.ts +8 -1
  217. package/src/components/pane-settings-dialog.tsx +44 -13
  218. package/src/components/sign-in-code-panel.tsx +27 -19
  219. package/src/components/ui/column-grid.tsx +48 -4
  220. package/src/components/ui/country-flag-art.tsx +180 -0
  221. package/src/components/ui/country-flag.tsx +118 -12
  222. package/src/components/ui/data-table/opentui/index.tsx +61 -2
  223. package/src/components/ui/data-table/opentui/model.ts +109 -2
  224. package/src/components/ui/data-table/types.ts +1 -1
  225. package/src/components/ui/display.tsx +5 -1
  226. package/src/components/ui/index.ts +3 -1
  227. package/src/components/ui/logo-mark.tsx +11 -2
  228. package/src/components/ui/query-bar.tsx +1 -1
  229. package/src/components/ui/row-notes.ts +79 -0
  230. package/src/components/ui/stat-grid.tsx +28 -3
  231. package/src/components/ui/status.tsx +3 -1
  232. package/src/core/app-services.ts +4 -0
  233. package/src/core/state/app/layout.ts +4 -4
  234. package/src/data/cached-query.ts +8 -1
  235. package/src/data/config/chart-settings.ts +3 -3
  236. package/src/data/config/home.ts +10 -2
  237. package/src/data/config/layout.ts +4 -1
  238. package/src/data/config/store/node.ts +4 -0
  239. package/src/data/config/store/normalize.ts +13 -3
  240. package/src/data/config/store/portfolio-as-of-column.ts +2 -2
  241. package/src/data/json-storage.ts +2 -0
  242. package/src/data/saved-copies.ts +84 -0
  243. package/src/docs/snapshot.json +65 -32
  244. package/src/i18n/es.ts +51 -12
  245. package/src/i18n/ja.ts +51 -12
  246. package/src/i18n/ko.ts +51 -12
  247. package/src/i18n/zh-cn.ts +51 -12
  248. package/src/i18n/zh-tw.ts +51 -12
  249. package/src/layout/desks.ts +1 -1
  250. package/src/layout/launch-layout.ts +1 -1
  251. package/src/layout/pane-manager/floating-actions.ts +5 -3
  252. package/src/layout/pane-manager/floating.ts +43 -1
  253. package/src/layout/pane-manager/index.ts +2 -0
  254. package/src/layout/selected-ticker.ts +74 -0
  255. package/src/market-data/depositary-pair.ts +251 -0
  256. package/src/market-data/futures-board.ts +1 -1
  257. package/src/market-data/futures-contract.ts +124 -40
  258. package/src/market-data/fx-closes.ts +17 -2
  259. package/src/market-data/hooks.tsx +19 -11
  260. package/src/market-data/market/change-basis.ts +49 -7
  261. package/src/market-data/market/freshness.ts +34 -5
  262. package/src/market-data/market/period-return.ts +163 -0
  263. package/src/market-data/market/trading-sessions.ts +13 -0
  264. package/src/market-data/market/year-to-date.ts +8 -13
  265. package/src/market-data/performance.ts +20 -14
  266. package/src/market-data/perpetual-premium.ts +32 -0
  267. package/src/market-data/published-apac-sessions.ts +2 -1
  268. package/src/market-data/published-futures-sessions.ts +69 -0
  269. package/src/market-data/quotes/contributions.ts +5 -1
  270. package/src/market-data/quotes/freshness.ts +42 -0
  271. package/src/market-data/quotes/intraday-bar.ts +37 -0
  272. package/src/market-data/quotes/regular-session-valuation.ts +13 -8
  273. package/src/market-data/quotes/resolution.ts +4 -2
  274. package/src/market-data/tax-equivalent.ts +1 -1
  275. package/src/market-data/ticker-price-returns.ts +3 -1
  276. package/src/news/aggregator.ts +5 -1
  277. package/src/news/news-model.ts +4 -0
  278. package/src/news/types.ts +11 -0
  279. package/src/notifications/app-notifier.ts +1 -1
  280. package/src/plugins/auto-update.ts +2 -2
  281. package/src/plugins/builtin/account-management/index.tsx +1 -0
  282. package/src/plugins/builtin/account-management/pane.tsx +9 -8
  283. package/src/plugins/builtin/alerts/alert-engine.ts +30 -18
  284. package/src/plugins/builtin/alerts/calendar-events.ts +378 -0
  285. package/src/plugins/builtin/alerts/cli.ts +485 -76
  286. package/src/plugins/builtin/alerts/command.ts +5 -10
  287. package/src/plugins/builtin/alerts/event-rows.ts +9 -5
  288. package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
  289. package/src/plugins/builtin/alerts/events.ts +108 -5
  290. package/src/plugins/builtin/alerts/fired-log.ts +78 -0
  291. package/src/plugins/builtin/alerts/format.ts +2 -1
  292. package/src/plugins/builtin/alerts/headless.ts +100 -39
  293. package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
  294. package/src/plugins/builtin/alerts/history-rows.ts +89 -0
  295. package/src/plugins/builtin/alerts/index.tsx +45 -18
  296. package/src/plugins/builtin/alerts/iv-level.ts +365 -0
  297. package/src/plugins/builtin/alerts/levels.ts +3 -3
  298. package/src/plugins/builtin/alerts/pane.tsx +97 -29
  299. package/src/plugins/builtin/alerts/price.ts +35 -0
  300. package/src/plugins/builtin/alerts/quotes.ts +24 -0
  301. package/src/plugins/builtin/alerts/research-builder.ts +84 -11
  302. package/src/plugins/builtin/alerts/research-rules.ts +77 -3
  303. package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
  304. package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
  305. package/src/plugins/builtin/alerts/scheduled.ts +188 -29
  306. package/src/plugins/builtin/alerts/storage.ts +13 -10
  307. package/src/plugins/builtin/alerts/targets.ts +12 -3
  308. package/src/plugins/builtin/analytics/index.tsx +8 -2
  309. package/src/plugins/builtin/analytics/metrics.ts +1 -1
  310. package/src/plugins/builtin/analytics/risk-book.ts +215 -0
  311. package/src/plugins/builtin/analytics/risk-client.ts +13 -6
  312. package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
  313. package/src/plugins/builtin/analytics/risk-math.ts +6 -3
  314. package/src/plugins/builtin/analytics/risk-model.ts +297 -104
  315. package/src/plugins/builtin/analytics/risk-options.ts +17 -2
  316. package/src/plugins/builtin/analytics/risk-pane.tsx +107 -43
  317. package/src/plugins/builtin/attention/pane.tsx +22 -16
  318. package/src/plugins/builtin/awards/headless.ts +7 -3
  319. package/src/plugins/builtin/awards/model.ts +18 -0
  320. package/src/plugins/builtin/awards/pane.tsx +12 -9
  321. package/src/plugins/builtin/backtest/export.ts +1 -1
  322. package/src/plugins/builtin/backtest/headless.ts +3 -3
  323. package/src/plugins/builtin/backtest/rules.ts +41 -5
  324. package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
  325. package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
  326. package/src/plugins/builtin/broker-manager/footer.ts +27 -11
  327. package/src/plugins/builtin/broker-manager/index.tsx +21 -12
  328. package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
  329. package/src/plugins/builtin/broker-manager/model.ts +12 -2
  330. package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
  331. package/src/plugins/builtin/broker-manager/table.ts +28 -12
  332. package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
  333. package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
  334. package/src/plugins/builtin/broker-trading/controller.ts +45 -13
  335. package/src/plugins/builtin/broker-trading/dock.tsx +24 -4
  336. package/src/plugins/builtin/broker-trading/headless.ts +2 -1
  337. package/src/plugins/builtin/broker-trading/index.tsx +7 -1
  338. package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
  339. package/src/plugins/builtin/broker-trading/order-feed.ts +75 -7
  340. package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
  341. package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
  342. package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
  343. package/src/plugins/builtin/broker-trading/pane.tsx +115 -26
  344. package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
  345. package/src/plugins/builtin/broker-trading/ticket.tsx +122 -36
  346. package/src/plugins/builtin/buildout/cells.tsx +4 -0
  347. package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
  348. package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
  349. package/src/plugins/builtin/buildout/pane/index.tsx +34 -18
  350. package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
  351. package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
  352. package/src/plugins/builtin/catalysts/client.ts +2 -2
  353. package/src/plugins/builtin/catalysts/detail.tsx +3 -2
  354. package/src/plugins/builtin/catalysts/index.tsx +1 -1
  355. package/src/plugins/builtin/catalysts/model.ts +49 -4
  356. package/src/plugins/builtin/catalysts/pane.tsx +9 -11
  357. package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
  358. package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
  359. package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
  360. package/src/plugins/builtin/changelog/index.tsx +1 -0
  361. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
  362. package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +2 -1
  363. package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
  364. package/src/plugins/builtin/chart-composer/cli-options.ts +2 -5
  365. package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
  366. package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
  367. package/src/plugins/builtin/chart-composer/editor.tsx +4 -1
  368. package/src/plugins/builtin/chart-composer/headless-schema.ts +28 -26
  369. package/src/plugins/builtin/chart-composer/headless.ts +253 -35
  370. package/src/plugins/builtin/chart-composer/index.tsx +12 -2
  371. package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
  372. package/src/plugins/builtin/chart-composer/pane.tsx +128 -28
  373. package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
  374. package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
  375. package/src/plugins/builtin/chart-composer/session-history.ts +15 -21
  376. package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
  377. package/src/plugins/builtin/chart-composer/settings.ts +31 -19
  378. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +5 -6
  379. package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
  380. package/src/plugins/builtin/chat/attachments/model.ts +3 -1
  381. package/src/plugins/builtin/chat/content/index.tsx +1 -1
  382. package/src/plugins/builtin/chat/pane-links/open.ts +1 -1
  383. package/src/plugins/builtin/chat/people.ts +1 -1
  384. package/src/plugins/builtin/clinical-trials/client.ts +5 -2
  385. package/src/plugins/builtin/clinical-trials/model.ts +13 -4
  386. package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
  387. package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
  388. package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
  389. package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
  390. package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
  391. package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
  392. package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
  393. package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
  394. package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
  395. package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
  396. package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
  397. package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
  398. package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
  399. package/src/plugins/builtin/commodity-balances/pane.tsx +14 -8
  400. package/src/plugins/builtin/company-attention/app-rank.tsx +11 -10
  401. package/src/plugins/builtin/company-attention/building-model.ts +222 -0
  402. package/src/plugins/builtin/company-attention/building.tsx +172 -0
  403. package/src/plugins/builtin/company-attention/client.ts +117 -1
  404. package/src/plugins/builtin/company-attention/evidence.ts +16 -3
  405. package/src/plugins/builtin/company-attention/headless.ts +101 -5
  406. package/src/plugins/builtin/company-attention/index.tsx +6 -3
  407. package/src/plugins/builtin/company-attention/model.ts +33 -7
  408. package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
  409. package/src/plugins/builtin/company-attention/moves.tsx +159 -0
  410. package/src/plugins/builtin/company-attention/pane.tsx +89 -31
  411. package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
  412. package/src/plugins/builtin/company-attention/signals.tsx +160 -0
  413. package/src/plugins/builtin/company-kpis/headless.ts +3 -2
  414. package/src/plugins/builtin/company-kpis/model.ts +2 -2
  415. package/src/plugins/builtin/company-kpis/pane.tsx +5 -4
  416. package/src/plugins/builtin/composite-plugins.ts +2 -1
  417. package/src/plugins/builtin/congress-trades/headless.ts +1 -1
  418. package/src/plugins/builtin/connections/index.ts +1 -0
  419. package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
  420. package/src/plugins/builtin/correlation/headless.ts +7 -4
  421. package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
  422. package/src/plugins/builtin/correlation/settings.ts +1 -0
  423. package/src/plugins/builtin/cot/headless.ts +28 -6
  424. package/src/plugins/builtin/cot/model.ts +12 -0
  425. package/src/plugins/builtin/cot/pane.tsx +7 -5
  426. package/src/plugins/builtin/cpi/headless.ts +5 -1
  427. package/src/plugins/builtin/cpi/pane.tsx +11 -9
  428. package/src/plugins/builtin/credit-boards/model.ts +2 -0
  429. package/src/plugins/builtin/credit-documents/headless.ts +1 -1
  430. package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
  431. package/src/plugins/builtin/crypto-board/client.ts +86 -0
  432. package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
  433. package/src/plugins/builtin/crypto-board/defi.ts +239 -0
  434. package/src/plugins/builtin/crypto-board/headless.ts +117 -6
  435. package/src/plugins/builtin/crypto-board/index.tsx +8 -3
  436. package/src/plugins/builtin/crypto-board/model.ts +40 -8
  437. package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
  438. package/src/plugins/builtin/crypto-board/series.ts +19 -1
  439. package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
  440. package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
  441. package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
  442. package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
  443. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
  444. package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
  445. package/src/plugins/builtin/debug/index.tsx +1 -0
  446. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  447. package/src/plugins/builtin/docs/index.tsx +1 -1
  448. package/src/plugins/builtin/docs/pane.tsx +2 -2
  449. package/src/plugins/builtin/docs/targets.ts +1 -1
  450. package/src/plugins/builtin/doe/client.ts +5 -1
  451. package/src/plugins/builtin/doe/headless.ts +12 -6
  452. package/src/plugins/builtin/doe/model.ts +43 -6
  453. package/src/plugins/builtin/doe/pane.tsx +19 -12
  454. package/src/plugins/builtin/earnings/board-model.ts +13 -6
  455. package/src/plugins/builtin/earnings/board-table.ts +4 -1
  456. package/src/plugins/builtin/earnings/board.tsx +253 -50
  457. package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
  458. package/src/plugins/builtin/earnings/calendar.tsx +338 -0
  459. package/src/plugins/builtin/earnings/client.ts +4 -3
  460. package/src/plugins/builtin/earnings/headless.ts +53 -15
  461. package/src/plugins/builtin/earnings/history-model.ts +53 -36
  462. package/src/plugins/builtin/earnings/index.tsx +15 -22
  463. package/src/plugins/builtin/earnings/model.ts +17 -12
  464. package/src/plugins/builtin/earnings/scope.ts +89 -0
  465. package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
  466. package/src/plugins/builtin/earnings-calls/data.ts +146 -2
  467. package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
  468. package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
  469. package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
  470. package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
  471. package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
  472. package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
  473. package/src/plugins/builtin/econ/calendar-model.ts +132 -20
  474. package/src/plugins/builtin/econ/detail-view.tsx +48 -10
  475. package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
  476. package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
  477. package/src/plugins/builtin/econ/index.tsx +112 -25
  478. package/src/plugins/builtin/econ/print-watch.ts +2 -1
  479. package/src/plugins/builtin/econ/types.ts +32 -1
  480. package/src/plugins/builtin/econ-statistics/client.ts +16 -3
  481. package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
  482. package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
  483. package/src/plugins/builtin/econ-statistics/headless.ts +13 -2
  484. package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
  485. package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
  486. package/src/plugins/builtin/econ-statistics/stats.ts +103 -0
  487. package/src/plugins/builtin/econ-statistics/view.ts +16 -1
  488. package/src/plugins/builtin/equity-screener/client.ts +21 -3
  489. package/src/plugins/builtin/equity-screener/headless.ts +77 -5
  490. package/src/plugins/builtin/equity-screener/live.ts +1 -1
  491. package/src/plugins/builtin/equity-screener/model.ts +34 -0
  492. package/src/plugins/builtin/equity-screener/pane.tsx +148 -106
  493. package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
  494. package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
  495. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
  496. package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
  497. package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
  498. package/src/plugins/builtin/exposure/headless.ts +51 -6
  499. package/src/plugins/builtin/exposure/index.tsx +28 -2
  500. package/src/plugins/builtin/exposure/model.ts +7 -0
  501. package/src/plugins/builtin/exposure/pane.tsx +16 -10
  502. package/src/plugins/builtin/fear-greed/index.tsx +1 -1
  503. package/src/plugins/builtin/filing-events/index.tsx +1 -1
  504. package/src/plugins/builtin/futures/model.ts +10 -9
  505. package/src/plugins/builtin/futures/returns.ts +8 -36
  506. package/src/plugins/builtin/futures/table.tsx +1 -1
  507. package/src/plugins/builtin/futures-curve/client.ts +40 -9
  508. package/src/plugins/builtin/futures-curve/headless.ts +39 -17
  509. package/src/plugins/builtin/futures-curve/model.ts +258 -69
  510. package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
  511. package/src/plugins/builtin/fx-matrix/client.ts +15 -8
  512. package/src/plugins/builtin/fx-matrix/headless.ts +32 -14
  513. package/src/plugins/builtin/fx-matrix/index.tsx +13 -11
  514. package/src/plugins/builtin/fx-matrix/live-legs.ts +22 -0
  515. package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
  516. package/src/plugins/builtin/gpu/board.tsx +4 -1
  517. package/src/plugins/builtin/gpu/changes.tsx +10 -10
  518. package/src/plugins/builtin/gpu/equities.tsx +4 -1
  519. package/src/plugins/builtin/gpu/headless.ts +9 -4
  520. package/src/plugins/builtin/gpu/history.tsx +4 -2
  521. package/src/plugins/builtin/gpu/pane.tsx +5 -3
  522. package/src/plugins/builtin/help/index.tsx +13 -3
  523. package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
  524. package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
  525. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  526. package/src/plugins/builtin/holders/client.ts +15 -2
  527. package/src/plugins/builtin/holders/format.ts +28 -17
  528. package/src/plugins/builtin/holders/headless.ts +48 -8
  529. package/src/plugins/builtin/holders/index-holders.ts +264 -0
  530. package/src/plugins/builtin/holders/pane.tsx +79 -15
  531. package/src/plugins/builtin/holders/report-rows.ts +35 -7
  532. package/src/plugins/builtin/holders/table-model.ts +2 -1
  533. package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
  534. package/src/plugins/builtin/holders/treemap.tsx +2 -2
  535. package/src/plugins/builtin/insider/base-values.ts +11 -7
  536. package/src/plugins/builtin/insider/client.ts +3 -0
  537. package/src/plugins/builtin/insider/headless.ts +20 -7
  538. package/src/plugins/builtin/insider/index.tsx +17 -8
  539. package/src/plugins/builtin/insider/issuer.ts +34 -4
  540. package/src/plugins/builtin/insider/signals-client.ts +3 -3
  541. package/src/plugins/builtin/insider/signals.tsx +1 -1
  542. package/src/plugins/builtin/insider/table-model.ts +35 -13
  543. package/src/plugins/builtin/insider/table.tsx +5 -2
  544. package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
  545. package/src/plugins/builtin/iv-history/client.ts +20 -0
  546. package/src/plugins/builtin/iv-history/events.ts +145 -0
  547. package/src/plugins/builtin/iv-history/format.ts +3 -0
  548. package/src/plugins/builtin/iv-history/headless.ts +91 -7
  549. package/src/plugins/builtin/iv-history/index.tsx +1 -1
  550. package/src/plugins/builtin/iv-history/model.ts +13 -4
  551. package/src/plugins/builtin/iv-history/series.ts +3 -1
  552. package/src/plugins/builtin/jobs/lock-note.ts +1 -1
  553. package/src/plugins/builtin/jobs/model.ts +4 -15
  554. package/src/plugins/builtin/jobs/pane.tsx +7 -5
  555. package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
  556. package/src/plugins/builtin/jodi/pane.tsx +14 -8
  557. package/src/plugins/builtin/layout-manager/index.tsx +1 -0
  558. package/src/plugins/builtin/macro-day/headless.ts +20 -7
  559. package/src/plugins/builtin/macro-day/index.tsx +3 -2
  560. package/src/plugins/builtin/macro-day/model.ts +29 -8
  561. package/src/plugins/builtin/macro-day/pane.tsx +46 -10
  562. package/src/plugins/builtin/macro-day/releases.ts +55 -13
  563. package/src/plugins/builtin/market-heatmap/data.ts +9 -2
  564. package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
  565. package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
  566. package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
  567. package/src/plugins/builtin/market-heatmap/model.ts +10 -7
  568. package/src/plugins/builtin/market-movers/client.ts +36 -21
  569. package/src/plugins/builtin/market-movers/headless.ts +8 -2
  570. package/src/plugins/builtin/market-movers/index.tsx +16 -10
  571. package/src/plugins/builtin/market-movers/screener.ts +33 -11
  572. package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
  573. package/src/plugins/builtin/market-movers/session-table.ts +4 -2
  574. package/src/plugins/builtin/market-movers/table.ts +4 -1
  575. package/src/plugins/builtin/market-valuation/headless.ts +2 -0
  576. package/src/plugins/builtin/market-valuation/view.ts +5 -0
  577. package/src/plugins/builtin/members/headless.ts +6 -5
  578. package/src/plugins/builtin/members/pane.tsx +11 -4
  579. package/src/plugins/builtin/mna/client.ts +1 -0
  580. package/src/plugins/builtin/mna/detail.tsx +20 -32
  581. package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
  582. package/src/plugins/builtin/mna/distress/model.ts +3 -2
  583. package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
  584. package/src/plugins/builtin/mna/headless.ts +6 -13
  585. package/src/plugins/builtin/mna/model.ts +93 -15
  586. package/src/plugins/builtin/mna/pane.tsx +5 -4
  587. package/src/plugins/builtin/news/headless.ts +12 -6
  588. package/src/plugins/builtin/news/index.tsx +10 -1
  589. package/src/plugins/builtin/news/wire/index.tsx +3 -2
  590. package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
  591. package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
  592. package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
  593. package/src/plugins/builtin/notes/cli.ts +50 -15
  594. package/src/plugins/builtin/notes/export.ts +17 -6
  595. package/src/plugins/builtin/notes/files.ts +16 -5
  596. package/src/plugins/builtin/openfda/pane.tsx +1 -1
  597. package/src/plugins/builtin/options/carry.ts +2 -2
  598. package/src/plugins/builtin/options/enrichment-model.ts +10 -9
  599. package/src/plugins/builtin/options/index.tsx +1 -1
  600. package/src/plugins/builtin/options/model-spot.ts +70 -4
  601. package/src/plugins/builtin/options/view.tsx +51 -23
  602. package/src/plugins/builtin/options-calculator/headless.ts +46 -7
  603. package/src/plugins/builtin/options-calculator/pane.tsx +3 -2
  604. package/src/plugins/builtin/options-positioning/headless.ts +11 -3
  605. package/src/plugins/builtin/options-positioning/model.ts +24 -0
  606. package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
  607. package/src/plugins/builtin/options-scenario/client.ts +142 -29
  608. package/src/plugins/builtin/options-scenario/collar.ts +8 -6
  609. package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
  610. package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
  611. package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
  612. package/src/plugins/builtin/options-scenario/csp.ts +101 -21
  613. package/src/plugins/builtin/options-scenario/headless.ts +49 -21
  614. package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
  615. package/src/plugins/builtin/options-scenario/index.tsx +28 -3
  616. package/src/plugins/builtin/options-scenario/model.ts +13 -0
  617. package/src/plugins/builtin/options-scenario/pane.tsx +22 -10
  618. package/src/plugins/builtin/perps/equity-row.tsx +11 -3
  619. package/src/plugins/builtin/perps/freshness.ts +8 -5
  620. package/src/plugins/builtin/perps/headless.ts +3 -1
  621. package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
  622. package/src/plugins/builtin/perps/live-rows.ts +161 -0
  623. package/src/plugins/builtin/perps/markets-section.tsx +154 -0
  624. package/src/plugins/builtin/perps/pane.tsx +36 -17
  625. package/src/plugins/builtin/perps/venue-markets.ts +96 -0
  626. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
  627. package/src/plugins/builtin/plugin-marketplace/pane.tsx +145 -60
  628. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +78 -27
  629. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
  630. package/src/plugins/builtin/portfolio-list/cli/render.ts +49 -20
  631. package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
  632. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
  633. package/src/plugins/builtin/portfolio-list/headless.ts +6 -3
  634. package/src/plugins/builtin/portfolio-list/income.ts +13 -2
  635. package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
  636. package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
  637. package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
  638. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
  639. package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
  640. package/src/plugins/builtin/power/client.ts +6 -3
  641. package/src/plugins/builtin/power/evidence.ts +22 -7
  642. package/src/plugins/builtin/power/gas.tsx +5 -1
  643. package/src/plugins/builtin/power/pane.tsx +44 -32
  644. package/src/plugins/builtin/power/prices.tsx +10 -3
  645. package/src/plugins/builtin/research/analyst-headless.ts +25 -8
  646. package/src/plugins/builtin/research/analyst-model.ts +68 -3
  647. package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
  648. package/src/plugins/builtin/research/event-model.ts +3 -1
  649. package/src/plugins/builtin/research/event-sources.ts +12 -4
  650. package/src/plugins/builtin/research/index.tsx +2 -1
  651. package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
  652. package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
  653. package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
  654. package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
  655. package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
  656. package/src/plugins/builtin/research/relative-valuation-pane.tsx +76 -9
  657. package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
  658. package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
  659. package/src/plugins/builtin/reverse-dcf/model.ts +10 -5
  660. package/src/plugins/builtin/risk-factors/index.tsx +2 -2
  661. package/src/plugins/builtin/scanner/lock-note.ts +1 -1
  662. package/src/plugins/builtin/seasonality/client.ts +2 -1
  663. package/src/plugins/builtin/sec/forms.ts +13 -0
  664. package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
  665. package/src/plugins/builtin/sec/fund-facts.ts +68 -8
  666. package/src/plugins/builtin/sec/headless.ts +85 -8
  667. package/src/plugins/builtin/sec/index.tsx +58 -12
  668. package/src/plugins/builtin/sectors/client.ts +6 -4
  669. package/src/plugins/builtin/sectors/sector-model.ts +11 -15
  670. package/src/plugins/builtin/series-inventory.ts +2 -0
  671. package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
  672. package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
  673. package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
  674. package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
  675. package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
  676. package/src/plugins/builtin/shared/earnings-report.ts +3 -3
  677. package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
  678. package/src/plugins/builtin/shared/job-posting.ts +129 -0
  679. package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
  680. package/src/plugins/builtin/shared/report-freshness.ts +3 -0
  681. package/src/plugins/builtin/shared/research-trading.tsx +23 -0
  682. package/src/plugins/builtin/shared/series-cache.ts +2 -0
  683. package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
  684. package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
  685. package/src/plugins/builtin/shared/volatility/options.ts +2 -1
  686. package/src/plugins/builtin/short-interest/client.ts +2 -0
  687. package/src/plugins/builtin/short-interest/headless.ts +41 -12
  688. package/src/plugins/builtin/short-interest/index.tsx +23 -1
  689. package/src/plugins/builtin/short-interest/model.ts +70 -19
  690. package/src/plugins/builtin/short-interest/pane.tsx +19 -2
  691. package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
  692. package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
  693. package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
  694. package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
  695. package/src/plugins/builtin/short-interest/schedule.ts +35 -2
  696. package/src/plugins/builtin/short-interest/surface.tsx +11 -4
  697. package/src/plugins/builtin/short-interest/types.ts +9 -0
  698. package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
  699. package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
  700. package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
  701. package/src/plugins/builtin/social-mentions/client.ts +2 -1
  702. package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
  703. package/src/plugins/builtin/supply-chain/model.ts +51 -3
  704. package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
  705. package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
  706. package/src/plugins/builtin/themes/headless.ts +8 -5
  707. package/src/plugins/builtin/themes/model.ts +14 -4
  708. package/src/plugins/builtin/themes/pane.tsx +24 -5
  709. package/src/plugins/builtin/thirteenf/headless.ts +5 -5
  710. package/src/plugins/builtin/thirteenf/signals.ts +3 -1
  711. package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
  712. package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
  713. package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
  714. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
  715. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
  716. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
  717. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
  718. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
  719. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
  720. package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
  721. package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
  722. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
  723. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
  724. package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
  725. package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
  726. package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
  727. package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
  728. package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
  729. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
  730. package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
  731. package/src/plugins/builtin/ticker-detail/quote-footer.ts +8 -3
  732. package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
  733. package/src/plugins/builtin/vol-surface/client.ts +28 -7
  734. package/src/plugins/builtin/vol-surface/headless.ts +43 -15
  735. package/src/plugins/builtin/vol-surface/model.ts +34 -12
  736. package/src/plugins/builtin/vol-surface/pane.tsx +33 -13
  737. package/src/plugins/builtin/vol-surface/stored.ts +34 -3
  738. package/src/plugins/builtin/vol-surface/tables.ts +14 -0
  739. package/src/plugins/builtin/volatility/model.ts +5 -10
  740. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
  741. package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
  742. package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
  743. package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
  744. package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
  745. package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
  746. package/src/plugins/builtin/world-venue-map/index.ts +3 -26
  747. package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
  748. package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
  749. package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
  750. package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
  751. package/src/plugins/builtin/yield-curve/client.ts +25 -0
  752. package/src/plugins/builtin/yield-curve/curves.ts +7 -4
  753. package/src/plugins/builtin/yield-curve/headless.ts +38 -4
  754. package/src/plugins/builtin/yield-curve/history.ts +4 -3
  755. package/src/plugins/builtin/yield-curve/index.tsx +12 -6
  756. package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
  757. package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
  758. package/src/plugins/bundle.ts +1 -1
  759. package/src/plugins/catalog-browser.ts +2 -1
  760. package/src/plugins/compat.ts +3 -3
  761. package/src/plugins/dependencies.ts +75 -14
  762. package/src/plugins/extracted-plugins.ts +132 -34
  763. package/src/plugins/host-link.ts +149 -34
  764. package/src/plugins/installer.ts +35 -10
  765. package/src/plugins/loader.ts +206 -38
  766. package/src/plugins/manager-node.ts +2 -2
  767. package/src/plugins/marketplace/deactivation.ts +22 -0
  768. package/src/plugins/marketplace/model.ts +2 -2
  769. package/src/plugins/marketplace/packs.ts +1 -1
  770. package/src/plugins/ownership.ts +1 -1
  771. package/src/plugins/peer-retry.ts +155 -0
  772. package/src/plugins/registry/contributions.ts +3 -1
  773. package/src/plugins/registry/index.ts +4 -3
  774. package/src/plugins/registry/plugin-state.ts +3 -0
  775. package/src/plugins/seed-notice.ts +20 -0
  776. package/src/plugins/shortcut-argument.ts +39 -0
  777. package/src/portfolio/allocation.ts +1 -1
  778. package/src/portfolio/collection-name.ts +31 -0
  779. package/src/portfolio/column-values.ts +10 -8
  780. package/src/portfolio/cost-fx.ts +296 -19
  781. package/src/portfolio/manual-lots.ts +75 -0
  782. package/src/portfolio/mutations.ts +60 -22
  783. package/src/portfolio/suffixed-listings.ts +209 -0
  784. package/src/portfolio/totals-currency.ts +24 -0
  785. package/src/public/__snapshots__/public-api.test.ts.snap +14 -0
  786. package/src/public/time-series.ts +11 -8
  787. package/src/remote/schema.ts +1 -1
  788. package/src/renderers/browser/config-host.ts +3 -0
  789. package/src/renderers/browser/main.tsx +23 -3
  790. package/src/renderers/dom/boot-frame-page.ts +187 -0
  791. package/src/renderers/dom/boot-frame.ts +237 -0
  792. package/src/renderers/dom/build-assets.ts +46 -8
  793. package/src/renderers/dom/desktop/controls.tsx +1 -1
  794. package/src/renderers/dom/desktop/query-bar.tsx +2 -0
  795. package/src/renderers/dom/dom-ui-host.tsx +4 -29
  796. package/src/renderers/dom/host/ascii-text.ts +16 -2
  797. package/src/renderers/dom/host/box.tsx +43 -2
  798. package/src/renderers/dom/host/chart-surface.tsx +2 -0
  799. package/src/renderers/dom/host/hls.chunk.ts +3 -0
  800. package/src/renderers/dom/host/image-surface.tsx +49 -0
  801. package/src/renderers/dom/host/media-surface.tsx +24 -18
  802. package/src/renderers/dom/host/mouse.ts +30 -0
  803. package/src/renderers/dom/host/size-observer.ts +1 -1
  804. package/src/renderers/dom/host/style.ts +1 -1
  805. package/src/renderers/dom/host/tabs.tsx +16 -5
  806. package/src/renderers/dom/host/top-surface.ts +127 -0
  807. package/src/renderers/dom/styles.css +115 -0
  808. package/src/renderers/dom/test-utils.tsx +2 -1
  809. package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
  810. package/src/renderers/electrobun/bun/core-capabilities.ts +9 -1
  811. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
  812. package/src/renderers/electrobun/bun/desktop/backend.ts +56 -3
  813. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
  814. package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
  815. package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
  816. package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
  817. package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
  818. package/src/renderers/electrobun/bun/index.ts +11 -1
  819. package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
  820. package/src/renderers/electrobun/bun/window/create.ts +4 -1
  821. package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
  822. package/src/renderers/electrobun/shared/protocol.ts +6 -1
  823. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
  824. package/src/renderers/electrobun/view/config-host.ts +3 -0
  825. package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
  826. package/src/renderers/electrobun/view/main.tsx +30 -4
  827. package/src/renderers/electrobun/view/news-source.ts +9 -7
  828. package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -2
  829. package/src/renderers/electrobun/view/ui-host.tsx +1 -1
  830. package/src/renderers/opentui/browser-open.ts +88 -0
  831. package/src/renderers/opentui/host.tsx +5 -14
  832. package/src/renderers/opentui/image/loader.ts +3 -2
  833. package/src/renderers/opentui/native-smoke.ts +10 -1
  834. package/src/renderers/opentui/pane-image/index.ts +2 -1
  835. package/src/renderers/opentui/render-lib.ts +253 -0
  836. package/src/renderers/opentui/render-timing-smoke.ts +1 -1
  837. package/src/renderers/opentui/start.tsx +8 -1
  838. package/src/renderers/share/layout-view.tsx +2 -2
  839. package/src/renderers/share/main.tsx +4 -4
  840. package/src/renderers/share/view.tsx +1 -1
  841. package/src/series/resolve.ts +22 -6
  842. package/src/series/standalone.ts +30 -2
  843. package/src/series/types.ts +8 -1
  844. package/src/shares/api.ts +4 -1
  845. package/src/shares/pane.ts +1 -1
  846. package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
  847. package/src/sources/gloomberb-cloud/index.ts +39 -16
  848. package/src/sources/gloomberb-cloud/news.ts +39 -0
  849. package/src/sources/gloomberb-cloud/normalizers.ts +14 -1
  850. package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
  851. package/src/sources/listing-symbols.ts +37 -0
  852. package/src/sources/provider-errors.ts +16 -2
  853. package/src/sources/provider-router/financial-routes.ts +21 -3
  854. package/src/sources/provider-router/financials.ts +15 -2
  855. package/src/sources/provider-router/history.ts +30 -8
  856. package/src/sources/provider-router/index.ts +16 -0
  857. package/src/sources/provider-router/primary.ts +1 -1
  858. package/src/state/app/bootstrap.ts +23 -0
  859. package/src/state/hooks/inline-tickers.ts +30 -5
  860. package/src/sync/controller.ts +1 -1
  861. package/src/sync/core-contributors.ts +46 -14
  862. package/src/team/collections-sync.ts +2 -1
  863. package/src/team/collections.ts +178 -17
  864. package/src/telemetry/crash-reports.ts +21 -4
  865. package/src/telemetry/update-failures-node.ts +59 -0
  866. package/src/telemetry/update-failures.ts +307 -0
  867. package/src/test-support/builtin-panes.ts +1 -1
  868. package/src/theme/colors.ts +3 -30
  869. package/src/theme/ui-tokens.ts +37 -1
  870. package/src/tickers/company-name-listing.ts +215 -36
  871. package/src/tickers/company-name.ts +43 -0
  872. package/src/tickers/index-roots.ts +47 -0
  873. package/src/tickers/instrument-label.ts +5 -1
  874. package/src/tickers/listing-fate.ts +30 -0
  875. package/src/tickers/metadata.ts +10 -0
  876. package/src/tickers/perpetual-listing.ts +5 -2
  877. package/src/tickers/search/home-listing.ts +75 -0
  878. package/src/tickers/search/index.ts +76 -27
  879. package/src/tickers/search/ranking.ts +54 -4
  880. package/src/tickers/search/upsert.ts +29 -0
  881. package/src/tickers/terminal-codes.ts +416 -40
  882. package/src/time-series/bar-aggregation.ts +243 -0
  883. package/src/time-series/base-series.ts +307 -0
  884. package/src/time-series/chart-bounds.ts +349 -0
  885. package/src/time-series/chart-seed.ts +142 -0
  886. package/src/time-series/economic.ts +5 -0
  887. package/src/time-series/fundamentals.ts +18 -3
  888. package/src/time-series/history-acquisition.ts +499 -0
  889. package/src/time-series/leverage.ts +100 -0
  890. package/src/time-series/market-resolution.ts +4 -2
  891. package/src/time-series/market.ts +2 -1
  892. package/src/time-series/price-comparison.ts +175 -30
  893. package/src/time-series/range.ts +9 -1
  894. package/src/time-series/resolution.ts +174 -16
  895. package/src/time-series/resolve-context.ts +157 -0
  896. package/src/time-series/resolve.ts +126 -1577
  897. package/src/time-series/series-name.ts +38 -0
  898. package/src/time-series/series-presentation.ts +208 -0
  899. package/src/time-series/spec.ts +11 -6
  900. package/src/time-series/studies.ts +109 -12
  901. package/src/time-series/tail-reconcile.ts +121 -0
  902. package/src/time-series/types.ts +29 -2
  903. package/src/time-series/use-chart-resolution.ts +4 -5
  904. package/src/types/broker.ts +10 -0
  905. package/src/types/config.ts +14 -0
  906. package/src/types/data-provider.ts +20 -0
  907. package/src/types/exchange-rate.ts +6 -0
  908. package/src/types/financials.ts +104 -7
  909. package/src/types/headless.ts +83 -6
  910. package/src/types/instrument.ts +3 -0
  911. package/src/types/issuer-listings.ts +28 -0
  912. package/src/types/plugin.ts +65 -8
  913. package/src/types/price-history.ts +2 -0
  914. package/src/types/ticker.ts +13 -0
  915. package/src/types/trading.ts +59 -1
  916. package/src/ui/ascii-font.ts +13 -1
  917. package/src/ui/host.tsx +6 -1
  918. package/src/updater/command-names.ts +175 -0
  919. package/src/updater/failure.ts +163 -0
  920. package/src/updater/index.ts +97 -28
  921. package/src/utils/calendar-date.ts +32 -15
  922. package/src/utils/cash-runway.ts +241 -0
  923. package/src/utils/central-banks.ts +42 -0
  924. package/src/utils/cli-output.ts +259 -80
  925. package/src/utils/contract-month.ts +16 -0
  926. package/src/utils/crypto-pair.ts +61 -3
  927. package/src/utils/currency-pair.ts +26 -0
  928. package/src/utils/currency-units.ts +29 -0
  929. package/src/utils/datetime-format.ts +10 -0
  930. package/src/utils/debug-log.ts +1 -1
  931. package/src/utils/decimal-comma.ts +39 -6
  932. package/src/utils/exchange-announcements.ts +71 -0
  933. package/src/utils/exchange-rate-snapshot.ts +10 -0
  934. package/src/utils/financial-statements.ts +83 -4
  935. package/src/utils/format.ts +34 -16
  936. package/src/utils/fundamentals.ts +150 -34
  937. package/src/utils/fx-market-hours.ts +10 -1
  938. package/src/utils/latest-financial-period.ts +8 -0
  939. package/src/utils/linux-libc.ts +54 -0
  940. package/src/utils/market-capitalization.ts +111 -15
  941. package/src/utils/max-drawdown.ts +64 -0
  942. package/src/utils/network-failure.ts +83 -0
  943. package/src/utils/option-expiry.ts +139 -7
  944. package/src/utils/price-earnings.ts +11 -5
  945. package/src/utils/price-history.ts +18 -8
  946. package/src/utils/price-window.ts +131 -0
  947. package/src/utils/release-date.ts +47 -0
  948. package/src/utils/sec.ts +71 -0
  949. package/src/utils/throttled-fetch.ts +11 -4
  950. package/src/utils/trailing-eps.ts +67 -0
  951. package/src/utils/typed-text.ts +21 -0
  952. package/src/utils/utc-time.ts +24 -1
  953. package/src/version.ts +6 -0
  954. package/src/cli/pane-functions/screenshot.ts +0 -2122
@@ -4,24 +4,39 @@ export interface CliTableColumn {
4
4
  header: string;
5
5
  align?: CliAlign;
6
6
  width?: number;
7
- /** Cap for this column when the table is fitted to a terminal. Piped output stays whole. */
7
+ /** Cap for this column when the table is fitted to a terminal: longer cells are cut there. Piped output stays whole. */
8
8
  maxWidth?: number;
9
- /** The narrowest a text column is cut to, so it never shrinks to a stub that reads like another row's; a narrower table moves columns under their rows. */
9
+ /** The narrowest a text column wraps to (MIN_WRAPPED_COLUMN_WIDTH unless set), or a `shrink: true` column is cut to; a narrower table moves columns under their rows. */
10
10
  minWidth?: number;
11
- /** Dropped, rightmost first, before other columns are shortened to fit a terminal. */
11
+ /** Dropped, rightmost first, before any column is cut or moved to fit a terminal. */
12
12
  optional?: boolean;
13
13
  /** Among optional columns, a higher priority is dropped sooner; equal ones go rightmost first. */
14
14
  dropPriority?: number;
15
- /** False keeps the column whole when fitting, for identifiers a user types into the next command. */
15
+ /**
16
+ * By default a fitted table never cuts a cell, so a date, a figure, a
17
+ * symbol or a title stays whole: text wraps in its cell and columns move
18
+ * under their rows. False keeps the column on one line. True is the
19
+ * explicit opt-out: the table cuts the column's cells, down to its header
20
+ * and `minWidth`, before columns move, for free text that names nothing,
21
+ * such as a description.
22
+ */
16
23
  shrink?: boolean;
17
24
  /**
18
- * When the table cannot fit even with its text shortened, columns move to
19
- * lines under their row, each value after its header: a higher priority
20
- * moves sooner, equal ones go rightmost first. The first column stays.
25
+ * When the table does not fit, columns move to lines under their row, each
26
+ * value whole after its header: first the ones too wide to sit beside the
27
+ * first column, then a higher priority sooner, then long text, then
28
+ * rightmost first. The first column stays.
21
29
  */
22
30
  stackPriority?: number;
23
31
  /** Once moved under the rows, only the first `stackRows` rows print the column; 0 leaves it out. */
24
32
  stackRows?: number;
33
+ /**
34
+ * Left out of a table fitted to fewer terminal columns than this, whether
35
+ * or not it would fit: a detail a narrow table has no room for beside what
36
+ * it is about (a filing's acceptance time beside its filed date). Piped
37
+ * output keeps it.
38
+ */
39
+ minTableWidth?: number;
25
40
  }
26
41
 
27
42
  export interface CliTableOptions {
@@ -43,6 +58,8 @@ const COLUMN_GAP = 2;
43
58
  const MIN_SHRUNK_COLUMN_WIDTH = 8;
44
59
  // Text columns first shrink right to left down to this width, then all shrink together.
45
60
  const SOFT_MIN_COLUMN_WIDTH = 20;
61
+ // Text wraps in its cell down to this width, or its longest word, before columns move under the rows.
62
+ const MIN_WRAPPED_COLUMN_WIDTH = 16;
46
63
  // Cells that say "no value": a moved column prints nothing for them.
47
64
  const EMPTY_CELL = /^(?:|-{1,2}|—|–)$/;
48
65
  // A date, or a date and time, as reports print them (`2026-10-09`, `2026-10`, `2026-10-09 20:15 UTC`).
@@ -50,20 +67,93 @@ const DATE_CELL = /^\d{4}-\d{2}(?:-\d{2})?(?:[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)
50
67
  // A number with its sign, currency, unit or range (`-1.0bp`, `$1,200`, `3.75-4.00%`, `481.75c`).
51
68
  const NUMBER_PART = String.raw`[+\-−±]?[$€£¥]?\d[\d,]*(?:\.\d+)?`;
52
69
  const NUMBER_TOKEN = new RegExp(String.raw`^\(?${NUMBER_PART}(?:[-–]${NUMBER_PART})?(?:%|¢|[a-zA-Z]{1,3})?\)?$`);
53
- // What may sit between the numbers of one cell (`3.875% 82% / 4.125% 18%`, `+25bp ↑`).
54
- const NUMBER_JOINER = /^(?:\/|·|→|↑|↓|[-–])$/;
70
+ // What may sit between the numbers of one cell (`3.875% 82% / 4.125% 18%`, `+25bp ↑`, plain `+25bp up`).
71
+ const NUMBER_JOINER = /^(?:\/|·|→|↑|↓|[-–]|to|up|down)$/;
72
+ // What a plain table writes in a cell with no value.
73
+ const PLAIN_EMPTY_CELL = "—";
55
74
  let colorEnabledOverride: boolean | null = null;
56
75
  let widthOverride: number | null = null;
76
+ let plainOverride: boolean | null = null;
57
77
 
58
78
  function colorEnabled(): boolean {
59
79
  if (colorEnabledOverride != null) return colorEnabledOverride;
60
- return !!process.stdout.isTTY && process.env.NO_COLOR !== "1" && process.env.TERM !== "dumb";
80
+ if (plainOverride === true) return false;
81
+ return !!process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
61
82
  }
62
83
 
63
84
  export function setCliColorEnabledOverride(value: boolean | null): void {
64
85
  colorEnabledOverride = value;
65
86
  }
66
87
 
88
+ /**
89
+ * Plain output, for a screen reader, very large text or a pipe: tables have
90
+ * no rule under their header, keep every column they have with "—" in an
91
+ * empty cell, and say their arrows in words (`plainWords`), as do the
92
+ * markers the CLI writes itself (`cliArrow`); a user's own text is never
93
+ * rewritten. Colour is off unless `--color` asks for it. The CLI turns it on
94
+ * for NO_COLOR and output that is not a terminal (`plainByDefault`), and
95
+ * `--plain` / `--no-plain` decide it either way; unset, it is off.
96
+ */
97
+ export function setCliPlainOverride(value: boolean | null): void {
98
+ plainOverride = value;
99
+ }
100
+
101
+ export function cliPlainEnabled(): boolean {
102
+ return plainOverride === true;
103
+ }
104
+
105
+ /**
106
+ * Whether output is plain without `--plain` or `--no-plain`: NO_COLOR is set
107
+ * to a value (an empty one is unset, as the convention says), or stdout is
108
+ * not a terminal.
109
+ */
110
+ export function plainByDefault(env: Record<string, string | undefined> = process.env, stdout: { isTTY?: boolean } = process.stdout): boolean {
111
+ return !!env.NO_COLOR || !stdout.isTTY;
112
+ }
113
+
114
+ const ARROW_WORDS: Record<string, string> = { "→": "to", "←": "from", "↑": "up", "↓": "down", "▲": "up", "▼": "down" };
115
+ const ARROW = /[←→↑↓▲▼]/;
116
+
117
+ /**
118
+ * An arrow the CLI writes itself, beside text it did not (`gold → gold's
119
+ * market news`), in words when output is plain: "to" for `→`. Table cells
120
+ * and headers are worded by `renderTable`; nothing else is rewritten.
121
+ */
122
+ export function cliArrow(arrow: "→" | "←" | "↑" | "↓"): string {
123
+ return cliPlainEnabled() ? ARROW_WORDS[arrow]! : arrow;
124
+ }
125
+
126
+ /**
127
+ * Arrows as the words they stand for, so a screen reader says them: a range
128
+ * `2026-04-03 → 04-06` reads "2026-04-03 to 04-06", `+25bp ↑` "+25bp up",
129
+ * the keys `←/→` "left/right". Text without an arrow is returned as it is.
130
+ * For a plain table's cells and headers, never a whole output.
131
+ */
132
+ export function plainWords(text: string): string {
133
+ if (!ARROW.test(text)) return text;
134
+ return text
135
+ .replace(/←\s*\/\s*→/g, "left/right")
136
+ .replace(/↑\s*\/\s*↓/g, "up/down")
137
+ .replace(/([ \t]*)([←→↑↓▲▼])([ \t]*)/g, (match: string, lead: string, arrow: string, _trail: string, offset: number, whole: string) => {
138
+ // An arrow that starts a line keeps the line's indent; one that ends it leaves no space after its word.
139
+ const atStart = offset === 0 || whole[offset - 1] === "\n";
140
+ const end = offset + match.length;
141
+ const atEnd = end === whole.length || whole[end] === "\n";
142
+ return `${atStart ? lead : " "}${ARROW_WORDS[arrow]}${atEnd ? "" : " "}`;
143
+ });
144
+ }
145
+
146
+ /** A table header's sort mark in words: `MKT VAL ▼` is "MKT VAL (sorted descending)". */
147
+ function plainHeader(header: string): string {
148
+ const sorted = header.replace(/\s*([▲▼])\s*$/, (_match, mark: string) => ` (sorted ${mark === "▲" ? "ascending" : "descending"})`);
149
+ return plainWords(sorted.trimStart());
150
+ }
151
+
152
+ /** A plain table's cell: arrows in words, and "—" for no value. */
153
+ function plainCell(cell: string): string {
154
+ return EMPTY_CELL.test(stripAnsi(cell).trim()) ? PLAIN_EMPTY_CELL : plainWords(cell);
155
+ }
156
+
67
157
  function applyAnsi(text: string, code: string): string {
68
158
  if (!colorEnabled()) return text;
69
159
  return `\x1b[${code}m${text}\x1b[0m`;
@@ -341,95 +431,132 @@ interface TableEntry {
341
431
  column: CliTableColumn;
342
432
  index: number;
343
433
  width: number;
344
- /** The narrowest the column may be cut to. */
434
+ /** The narrowest the column may get. */
345
435
  floor: number;
346
- /** Every filled cell is a date or a figure. */
347
- figures: boolean;
436
+ /** Text that wraps onto more lines of its cell rather than being cut. */
437
+ wraps: boolean;
438
+ /** Words rather than dates, figures or fixed-width values. */
439
+ text: boolean;
440
+ }
441
+
442
+ /**
443
+ * How narrow a column may get. Cells are whole by default: a date, a figure,
444
+ * a symbol, a name or a title cut short reads as another one. Text wraps in
445
+ * its cell down to its longest word or `minWidth` (MIN_WRAPPED_COLUMN_WIDTH
446
+ * unless set); `shrink: false` keeps it on one line. Only free text marked
447
+ * `shrink: true` is cut, to its header and `minWidth`.
448
+ */
449
+ function columnFloor(column: CliTableColumn, width: number, cells: readonly string[], capped: boolean): Pick<TableEntry, "floor" | "wraps" | "text"> {
450
+ if (column.width != null || column.align === "right" || isWholeColumn(cells)) return { floor: width, wraps: false, text: false };
451
+ // Cut at its `maxWidth`, as the column asks.
452
+ if (capped) return { floor: width, wraps: false, text: true };
453
+ if (column.shrink === true) {
454
+ return { floor: Math.min(width, Math.max(MIN_SHRUNK_COLUMN_WIDTH, visibleLength(column.header), column.minWidth ?? 0)), wraps: false, text: true };
455
+ }
456
+ if (column.shrink === false) return { floor: width, wraps: false, text: true };
457
+ const words = cells.flatMap((cell) => stripAnsi(cell).split(/\s+/));
458
+ const longestWord = Math.max(visibleLength(column.header), ...words.map((word) => visibleLength(word)));
459
+ return { floor: Math.min(width, Math.max(longestWord, column.minWidth ?? MIN_WRAPPED_COLUMN_WIDTH)), wraps: true, text: true };
460
+ }
461
+
462
+ /** A cell's text wrapped to `width`, each line closing the colour it opens and the next reopening it, so it never bleeds into the next column. */
463
+ function wrapCell(text: string, width: number): string[] {
464
+ const lines = wrapText(text, width);
465
+ if (lines.length < 2 || !text.includes("\x1b[")) return lines;
466
+ let open = "";
467
+ return lines.map((line) => {
468
+ const carried = open;
469
+ for (const code of line.match(ANSI_PATTERN) ?? []) open = code === "\x1b[0m" ? "" : `${open}${code}`;
470
+ return `${carried}${line}${open ? "\x1b[0m" : ""}`;
471
+ });
472
+ }
473
+
474
+ interface TableLayout {
475
+ kept: TableEntry[];
476
+ stacked: TableEntry[];
348
477
  }
349
478
 
350
- /** The narrowest a column may get: whole for figures and ids; text keeps its header and its `minWidth`. */
351
- function columnFloor(column: CliTableColumn, width: number): number {
352
- if (column.width != null || column.align === "right" || column.shrink === false) return width;
353
- return Math.min(width, Math.max(MIN_SHRUNK_COLUMN_WIDTH, visibleLength(column.header), column.minWidth ?? 0));
479
+ /** Which of two columns moves under the rows sooner: by the first value that differs. */
480
+ function movesSooner(left: readonly number[], right: readonly number[]): boolean {
481
+ const at = left.findIndex((value, index) => value !== right[index]);
482
+ return at >= 0 && left[at]! > right[at]!;
354
483
  }
355
484
 
356
485
  /**
357
- * The columns that stay in the table, at widths that fit `available`, and the
358
- * ones moved under each row. Text shrinks first, as it always has. A table
359
- * still too wide keeps every date and figure whole and moves columns under
360
- * the rows, by `stackPriority`, then rightmost first, until the rest fits.
361
- * Null when the first column alone does not fit.
486
+ * The ways a table too wide can fit `available`, each moving one more column
487
+ * under the rows, where its values print whole. In each, text wraps in its
488
+ * cell and free text marked `shrink: true` is cut, down to their floors.
489
+ * Columns move in this order: the ones too wide to ever sit beside the first
490
+ * column (a long headline, a note), then by `stackPriority`, then long text
491
+ * (a name, a title) before dates and figures, then rightmost first. The
492
+ * first column stays. The list ends at the first layout that keeps every
493
+ * column it shows whole on one line: moving more only adds lines.
362
494
  */
363
- function fitTableLayout(entries: TableEntry[], available: number): { kept: TableEntry[]; stacked: TableEntry[] } | null {
364
- const fit = (kept: TableEntry[], wholeFigures: boolean) => fitColumnWidths(
365
- kept.map((entry) => entry.width),
366
- kept.map((entry) => (wholeFigures && entry.figures ? entry.width : entry.floor)),
367
- available,
368
- );
495
+ function tableLayouts(entries: TableEntry[], available: number): TableLayout[] {
496
+ const fit = (kept: TableEntry[]) => fitColumnWidths(kept.map((entry) => entry.width), kept.map((entry) => entry.floor), available);
497
+ const first = entries[0]!;
498
+ const room = available - first.floor - COLUMN_GAP;
499
+ const order = (entry: TableEntry) => [
500
+ entry.floor > room ? 1 : 0,
501
+ entry.column.stackPriority ?? 0,
502
+ entry.text && entry.width > SOFT_MIN_COLUMN_WIDTH ? 1 : 0,
503
+ entry.index,
504
+ ];
505
+ const layouts: TableLayout[] = [];
369
506
  let kept = entries;
370
507
  const stacked: TableEntry[] = [];
371
- let widths = fit(kept, false);
372
- if (!widths) widths = fit(kept, true);
373
- while (!widths && kept.length > 1) {
374
- const moved = kept.slice(1).reduce((pick, entry) => (
375
- (entry.column.stackPriority ?? 0) >= (pick.column.stackPriority ?? 0) ? entry : pick
376
- ));
508
+ for (;;) {
509
+ const widths = fit(kept);
510
+ if (widths) {
511
+ layouts.push({ kept: kept.map((entry, position) => ({ ...entry, width: widths[position]! })), stacked: [...stacked] });
512
+ if (widths.every((width, position) => width >= kept[position]!.width)) break;
513
+ }
514
+ if (kept.length === 1) break;
515
+ const moved = kept.slice(1).reduce((pick, entry) => (movesSooner(order(entry), order(pick)) ? entry : pick));
377
516
  kept = kept.filter((entry) => entry !== moved);
378
517
  stacked.push(moved);
379
- widths = fit(kept, true);
380
518
  }
381
- if (!widths) return null;
382
- const fitted = widths;
383
- return { kept: kept.map((entry, position) => ({ ...entry, width: fitted[position]! })), stacked };
519
+ // The first column alone is wider than the table may be: text wraps and free text is cut to the
520
+ // whole width; a symbol or a figure stays whole.
521
+ if (layouts.length === 0) layouts.push({ kept: [{ ...first, width: first.wraps || first.column.shrink === true ? available : first.width }], stacked });
522
+ return layouts;
384
523
  }
385
524
 
386
- export function renderTable(columns: CliTableColumn[], rows: string[][], options: CliTableOptions = {}): string {
525
+ function renderTableLayout(
526
+ columns: CliTableColumn[],
527
+ rows: string[][],
528
+ options: CliTableOptions,
529
+ shown: readonly TableEntry[],
530
+ stacked: readonly TableEntry[],
531
+ available: number | null,
532
+ /** Cells wrap or are cut to their column's width: the table was fitted to `available`. */
533
+ fitted: boolean,
534
+ ): string {
387
535
  const indent = " ".repeat(options.indent ?? 0);
388
- const maxWidth = options.maxWidth === undefined ? cliTerminalWidth() : options.maxWidth;
389
- const available = maxWidth == null ? null : maxWidth - indent.length;
390
- let shown = columns.map((column, index) => {
391
- const cellWidths = rows.map((row) => visibleLength(row[index] ?? ""));
392
- return { column, index, width: column.width ?? Math.max(visibleLength(column.header), ...cellWidths) };
393
- });
394
- let stacked: Array<(typeof shown)[number]> = [];
395
- // Cells are only cut when the table was fitted to a terminal; piped output keeps them whole.
396
- let truncate = false;
397
- if (available != null && tableWidth(shown.map((entry) => entry.width)) > available) {
398
- while (tableWidth(shown.map((entry) => entry.width)) > available) {
399
- const dropped = shown.reduce<(typeof shown)[number] | undefined>((pick, entry) => (
400
- entry.column.optional && (!pick || (entry.column.dropPriority ?? 0) >= (pick.column.dropPriority ?? 0)) ? entry : pick
401
- ), undefined);
402
- if (!dropped) break;
403
- shown = shown.filter((entry) => entry !== dropped);
404
- }
405
- const layout = fitTableLayout(shown.map((entry) => {
406
- const width = entry.column.maxWidth == null
407
- ? entry.width
408
- : Math.min(entry.width, Math.max(entry.column.maxWidth, visibleLength(entry.column.header)));
409
- return { ...entry, width, floor: columnFloor(entry.column, width), figures: isWholeColumn(rows.map((row) => row[entry.index] ?? "")) };
410
- }), available);
411
- // A first column that cannot fit even alone keeps every cell whole rather than losing data and overflowing anyway.
412
- if (layout) {
413
- shown = layout.kept;
414
- stacked = layout.stacked.filter((entry) => entry.column.stackRows !== 0).sort((left, right) => left.index - right.index);
415
- truncate = true;
416
- }
417
- }
418
-
419
536
  const renderRow = (cells: string[], style?: (text: string) => string) => {
420
- const rendered = shown.map(({ column, index, width }, position) => {
421
- const cell = truncate ? truncateDisplay(cells[index] ?? "", width) : cells[index] ?? "";
422
- // The last left-aligned column is not padded so lines carry no trailing spaces.
423
- const padded = position === shown.length - 1 && (column.align ?? "left") === "left"
424
- ? cell
425
- : padDisplay(cell, width, column.align ?? "left");
426
- return style ? style(padded) : padded;
537
+ const columnLines = shown.map(({ index, width, wraps }) => {
538
+ const cell = cells[index] ?? "";
539
+ if (!fitted) return [cell];
540
+ return wraps ? wrapCell(cell, width) : [truncateDisplay(cell, width)];
427
541
  });
428
- return `${indent}${rendered.join(" ".repeat(COLUMN_GAP))}`.trimEnd();
542
+ // A cell that wraps makes its row taller; the other cells sit on the row's first line.
543
+ const height = Math.max(1, ...columnLines.map((lines) => lines.length));
544
+ return Array.from({ length: height }, (_, line) => {
545
+ const rendered = shown.map(({ column, width }, position) => {
546
+ const cell = columnLines[position]![line] ?? "";
547
+ // The last left-aligned column is not padded so lines carry no trailing spaces.
548
+ const padded = position === shown.length - 1 && (column.align ?? "left") === "left"
549
+ ? cell
550
+ : padDisplay(cell, width, column.align ?? "left");
551
+ return style && (line === 0 || cell) ? style(padded) : padded;
552
+ });
553
+ return `${indent}${rendered.join(" ".repeat(COLUMN_GAP))}`.trimEnd();
554
+ }).join("\n");
429
555
  };
430
556
 
431
557
  const header = renderRow(columns.map((column) => column.header), cliStyles.bold);
432
- const divider = `${indent}${shown.map(({ width }) => cliStyles.muted("─".repeat(width))).join(" ".repeat(COLUMN_GAP))}`;
558
+ // A plain table has no rule under its header: a screen reader reads it as a row of dashes.
559
+ const divider = cliPlainEnabled() ? [] : [`${indent}${shown.map(({ width }) => cliStyles.muted("─".repeat(width))).join(" ".repeat(COLUMN_GAP))}`];
433
560
  const belowIndent = `${indent}${" ".repeat(COLUMN_GAP)}`;
434
561
  const belowWidth = Math.max(1, (available ?? 0) - COLUMN_GAP);
435
562
  const renderBelow = (index: number) => (options.below?.[index] ?? []).flatMap((line) => (
@@ -463,5 +590,57 @@ export function renderTable(columns: CliTableColumn[], rows: string[][], options
463
590
  if (line) lines.push(line);
464
591
  return lines.map((text) => `${belowIndent}${text}`.trimEnd());
465
592
  };
466
- return [header, divider, ...rows.flatMap((row, index) => [renderRow(row), ...renderStacked(row, index), ...renderBelow(index)])].join("\n");
593
+ return [header, ...divider, ...rows.flatMap((row, index) => [renderRow(row), ...renderStacked(row, index), ...renderBelow(index)])].join("\n");
594
+ }
595
+
596
+ /**
597
+ * A table fitted to the terminal (or `--width`), or whole when output is
598
+ * piped. A table too wide never cuts a cell short, unless its column is free
599
+ * text marked `shrink: true`: text wraps in its cell and columns move under
600
+ * their rows, each value whole after its header. Of the ways that fit, the
601
+ * one with the fewest lines prints, the one that moves fewer columns on a tie.
602
+ * Plain output (`setCliPlainOverride`) has no rule under the header, words
603
+ * for arrows and "—" in every empty cell.
604
+ */
605
+ export function renderTable(columns: CliTableColumn[], rows: string[][], options: CliTableOptions = {}): string {
606
+ if (cliPlainEnabled()) {
607
+ columns = columns.map((column) => ({ ...column, header: plainHeader(column.header) }));
608
+ rows = rows.map((row) => columns.map((_, index) => plainCell(row[index] ?? "")));
609
+ if (options.below) options = { ...options, below: options.below.map((lines) => lines?.map(plainWords)) };
610
+ }
611
+ const maxWidth = options.maxWidth === undefined ? cliTerminalWidth() : options.maxWidth;
612
+ const available = maxWidth == null ? null : maxWidth - (options.indent ?? 0);
613
+ let shown = columns.map((column, index) => {
614
+ const cellWidths = rows.map((row) => visibleLength(row[index] ?? ""));
615
+ return { column, index, width: column.width ?? Math.max(visibleLength(column.header), ...cellWidths), floor: 0, wraps: false, text: false };
616
+ }).filter(({ column }) => maxWidth == null || column.minTableWidth == null || maxWidth >= column.minTableWidth);
617
+ // Cells are only cut or wrapped when the table is fitted to a terminal; piped output keeps them whole.
618
+ if (available == null || tableWidth(shown.map((entry) => entry.width)) <= available) {
619
+ return renderTableLayout(columns, rows, options, shown, [], available, false);
620
+ }
621
+ while (tableWidth(shown.map((entry) => entry.width)) > available) {
622
+ const dropped = shown.reduce<(typeof shown)[number] | undefined>((pick, entry) => (
623
+ entry.column.optional && (!pick || (entry.column.dropPriority ?? 0) >= (pick.column.dropPriority ?? 0)) ? entry : pick
624
+ ), undefined);
625
+ if (!dropped) break;
626
+ shown = shown.filter((entry) => entry !== dropped);
627
+ }
628
+ const entries = shown.map((entry) => {
629
+ const width = entry.column.maxWidth == null
630
+ ? entry.width
631
+ : Math.min(entry.width, Math.max(entry.column.maxWidth, visibleLength(entry.column.header)));
632
+ return { ...entry, width, ...columnFloor(entry.column, width, rows.map((row) => row[entry.index] ?? ""), width < entry.width) };
633
+ });
634
+ let best = "";
635
+ let bestLines = Infinity;
636
+ for (const layout of tableLayouts(entries, available)) {
637
+ const stacked = layout.stacked.filter((entry) => entry.column.stackRows !== 0).sort((left, right) => left.index - right.index);
638
+ const text = renderTableLayout(columns, rows, options, layout.kept, stacked, available, true);
639
+ const lines = text.split("\n").length;
640
+ if (lines < bestLines) {
641
+ best = text;
642
+ bestLines = lines;
643
+ }
644
+ }
645
+ return best;
467
646
  }
@@ -0,0 +1,16 @@
1
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2
+
3
+ /**
4
+ * A futures contract month as every pane and report writes it, "Oct 2026":
5
+ * "Oct 26" reads as the 26th of October. `month` is 0-11; a two-digit year
6
+ * (the 26 of HGZ26) is in this century.
7
+ */
8
+ export function contractMonthLabel(year: number, month: number): string {
9
+ return `${MONTHS[month] ?? String(month + 1).padStart(2, "0")} ${year < 100 ? 2000 + year : year}`;
10
+ }
11
+
12
+ /** A delivery month, "2026-10", "2026-10-01" or a broker's "202610", as contractMonthLabel writes it; null when it is no month. */
13
+ export function deliveryMonthLabel(deliveryMonth: string | null | undefined): string | null {
14
+ const match = /^(\d{4})-?(0[1-9]|1[0-2])(?:$|-)/.exec(deliveryMonth ?? "");
15
+ return match ? contractMonthLabel(Number(match[1]), Number(match[2]) - 1) : null;
16
+ }
@@ -9,6 +9,8 @@ const FIAT_CURRENCY_CODES = new Set([
9
9
  ]);
10
10
 
11
11
  const USD_COIN_PAIR = /^([A-Z0-9]+)[-/]USD$/;
12
+ // The other currencies and coins the crypto venue quotes a coin in (BTC-EUR, ETH-BTC).
13
+ const QUOTED_COIN_PAIR = /^([A-Z0-9]+)[-/](?:USDT|USDC|EUR|GBP|BTC|ETH)$/;
12
14
 
13
15
  /** A coin quoted in dollars (BTC-USD, ETH/USD). EUR-USD is spot FX, not a coin. */
14
16
  function isUsdCoinPair(symbol: string): boolean {
@@ -16,15 +18,24 @@ function isUsdCoinPair(symbol: string): boolean {
16
18
  return base !== undefined && !FIAT_CURRENCY_CODES.has(base);
17
19
  }
18
20
 
21
+ /** A coin quoted in another currency or coin (BTC-EUR, ETH-BTC). EUR-GBP is spot FX, not a coin. */
22
+ function isQuotedCoinPair(symbol: string): boolean {
23
+ const base = QUOTED_COIN_PAIR.exec(symbol.trim().toUpperCase())?.[1];
24
+ return base !== undefined && !isIsoCurrencyCode(base);
25
+ }
26
+
19
27
  /**
20
28
  * A series that trades around the clock: anything on the crypto venue (CCC), or
21
29
  * a coin quoted in dollars whether or not the caller named that venue. A bare
22
- * BTC-USD is the coin, not a US listing; a bare BTC (no quote) is a US fund.
30
+ * BTC-USD is the coin, not a US listing; a bare BTC (no quote) is a US fund. A
31
+ * bare coin quoted in euros or in another coin (BTC-EUR) is the coin too, never
32
+ * a US listing whose session closed at 16:00 New York.
23
33
  */
24
34
  export function isRoundTheClockCoin(symbol?: string, exchange?: string): boolean {
25
35
  const parsed = symbol ? parsePublicTickerKey(symbol) : null;
26
- if (canonicalExchange(parsed?.exchange ?? exchange) === "CCC" || canonicalExchange(exchange) === "CCC") return true;
27
- return !!parsed && isUsdCoinPair(parsed.symbol);
36
+ const venue = canonicalExchange(parsed?.exchange ?? exchange) || canonicalExchange(exchange);
37
+ if (venue === "CCC" || canonicalExchange(exchange) === "CCC") return true;
38
+ return !!parsed && (isUsdCoinPair(parsed.symbol) || (!venue && isQuotedCoinPair(parsed.symbol)));
28
39
  }
29
40
 
30
41
  const SLASH_COIN_PAIR = /^([A-Z0-9]{1,15})\/(USD|EUR|GBP|USDT|USDC|BTC)$/;
@@ -59,3 +70,50 @@ const CRYPTO_PAIR = /^[A-Z0-9]{1,15}-(?:USD|EUR|GBP|USDT|USDC|BTC)$/;
59
70
  export function isCryptoPairSymbol(symbol: string): boolean {
60
71
  return CRYPTO_PAIR.test(symbol);
61
72
  }
73
+
74
+ /**
75
+ * The coins whose bare ticker is most often typed for the coin itself, with
76
+ * how stories and notes name each: the name a listing's own may not say
77
+ * plainly ("Bitcoin USD").
78
+ */
79
+ export const MAJOR_COIN_NAMES: ReadonlyMap<string, string> = new Map([
80
+ ["BTC", "bitcoin"],
81
+ ["ETH", "ethereum"],
82
+ ["SOL", "solana"],
83
+ ["XRP", "XRP"],
84
+ ["DOGE", "dogecoin"],
85
+ ["ADA", "cardano"],
86
+ ["LTC", "litecoin"],
87
+ ["BCH", "bitcoin cash"],
88
+ ["AVAX", "avalanche"],
89
+ ["DOT", "polkadot"],
90
+ ["LINK", "chainlink"],
91
+ ["BNB", "BNB"],
92
+ ]);
93
+
94
+ const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|EUR|GBP|USDT|USDC|BTC)$/;
95
+ // What a pair's listing name adds after the coin's: its quote currency, an id in brackets.
96
+ const PAIR_NAME_SUFFIX = /(?:\s*\([^)]*\))+$|\s+(?:USD|EUR|GBP|USDT|USDC|BTC|US Dollars?)$/i;
97
+
98
+ /**
99
+ * The coin a crypto pair trades, as stories name it: "bitcoin" for BTC-USD
100
+ * (or BTC-USD on CCC), from the major coins' names, else the listing's name
101
+ * without its quote currency ("Hyperliquid USD" is "hyperliquid"). Null for a
102
+ * symbol that is not a coin pair, and for a coin with no name to search for:
103
+ * its code alone (TON, UNI) is too often a word.
104
+ */
105
+ export function coinSearchName(symbol: string | undefined, exchange?: string, listingName?: string | null): string | null {
106
+ if (!symbol || !isRoundTheClockCoin(symbol, exchange)) return null;
107
+ const parsed = parsePublicTickerKey(symbol).symbol.trim().toUpperCase();
108
+ const code = COIN_PAIR.exec(parsed)?.[1] ?? parsed;
109
+ if (isIsoCurrencyCode(code)) return null;
110
+ const major = MAJOR_COIN_NAMES.get(code);
111
+ if (major) return major;
112
+ let name = listingName?.trim() ?? "";
113
+ for (let previous = ""; previous !== name;) {
114
+ previous = name;
115
+ name = name.replace(PAIR_NAME_SUFFIX, "").trim();
116
+ }
117
+ if (name.length < 3 || name.toUpperCase() === code || !/\p{L}/u.test(name)) return null;
118
+ return name === name.toUpperCase() ? name : name.toLowerCase();
119
+ }
@@ -84,3 +84,29 @@ export function looksLikeCurrencyPair(symbol: string): boolean {
84
84
  const halves = /^([A-Z]{3})[^A-Z0-9]?([A-Z]{3})(?:=X)?$/.exec(code)?.slice(1) ?? /^([A-Z]{3})=X$/.exec(code)?.slice(1) ?? [];
85
85
  return halves.some(isCurrencyCode);
86
86
  }
87
+
88
+ /**
89
+ * Which currency of a pair markets quote it per: EUR, GBP, AUD and NZD go
90
+ * before the dollar, the dollar before CAD, CHF and the rest, and the yen goes
91
+ * last (EUR/USD, USD/CAD, USD/JPY, EUR/CAD, CAD/JPY).
92
+ */
93
+ const PAIR_BASE_ORDER = ["EUR", "GBP", "AUD", "NZD", "USD", "CAD", "CHF"];
94
+
95
+ function pairBaseRank(code: string): number {
96
+ const at = PAIR_BASE_ORDER.indexOf(code);
97
+ if (at >= 0) return at;
98
+ return code === "JPY" ? PAIR_BASE_ORDER.length + 1 : PAIR_BASE_ORDER.length;
99
+ }
100
+
101
+ /**
102
+ * A rate of `quote` per unit of `currency`, written the way markets quote
103
+ * the pair, as `fx` reads a pair: 0.0067 USD per JPY is USD/JPY 149.25, and
104
+ * 1.4 CAD per USD stays USD/CAD 1.4. Two codes of the same rank keep their
105
+ * order.
106
+ */
107
+ export function quotedFxPair(currency: string, quote: string, rate: number): { pair: string; rate: number } {
108
+ const from = currency.trim().toUpperCase(), into = quote.trim().toUpperCase();
109
+ return pairBaseRank(into) < pairBaseRank(from)
110
+ ? { pair: `${into}/${from}`, rate: 1 / rate }
111
+ : { pair: `${from}/${into}`, rate };
112
+ }
@@ -104,6 +104,25 @@ export function heldCurrencyUnit(currency?: string | null): CurrencyUnitInfo {
104
104
  return currency?.trim().toUpperCase() === US_CENTS ? { currency: "USD", divisor: 100 } : resolveCurrencyUnit(currency);
105
105
  }
106
106
 
107
+ /**
108
+ * The codes whose USD rates convert money held in `currencies`: each as a
109
+ * rate is asked for (upper-cased), and a minor unit's major currency too,
110
+ * which convertCurrency reads a hundredth of when the unit has no rate of its
111
+ * own. Gloom Cloud serves none for GBX or ILA, and ZAc and GBp are kept in
112
+ * their own case, which no upper-cased rate matches.
113
+ */
114
+ export function fxRateCurrencies(currencies: Iterable<string | null | undefined>): string[] {
115
+ const codes = new Set<string>();
116
+ for (const currency of currencies) {
117
+ const raw = currency?.trim();
118
+ if (!raw) continue;
119
+ codes.add(raw.toUpperCase());
120
+ const unit = heldCurrencyUnit(raw);
121
+ if (unit.divisor !== 1 && unit.currency) codes.add(unit.currency);
122
+ }
123
+ return [...codes].sort((left, right) => left.localeCompare(right));
124
+ }
125
+
107
126
  interface ExchangeSubUnitOptions {
108
127
  allowDefaultCurrency?: boolean;
109
128
  allowMissingCurrency?: boolean;
@@ -114,6 +133,16 @@ function normalizeExchange(value?: string | null): string {
114
133
  return (value ?? "").trim().toUpperCase();
115
134
  }
116
135
 
136
+ /**
137
+ * A currency code as a position saves it: upper-cased, except a minor unit
138
+ * whose code is written in its own case (GBp, ZAc), which stays as typed so
139
+ * pence are not read as pounds. GBX and gbp read as pence and pounds.
140
+ */
141
+ export function normalizeHeldCurrencyCode(code: string | null | undefined): string {
142
+ const raw = (code ?? "").trim();
143
+ return SUB_UNIT_CURRENCIES[raw] && raw !== raw.toUpperCase() ? raw : raw.toUpperCase();
144
+ }
145
+
117
146
  export function resolveCurrencyUnit(currency?: string | null): CurrencyUnitInfo {
118
147
  const raw = (currency ?? "").trim();
119
148
  if (!raw) {
@@ -39,6 +39,16 @@ export function formatRelativeAge(timestamp: number | undefined, now = Date.now(
39
39
  return elapsed ? `${elapsed.count}${elapsed.unit} ago` : "just now";
40
40
  }
41
41
 
42
+ /** Time until something scheduled, on the same ladder: "in <1m", "in 25m", "in 3h", "in 3h 20m". */
43
+ export function formatCountdown(ms: number): string {
44
+ if (ms <= 60_000) return "in <1m";
45
+ const totalMin = Math.floor(ms / 60_000);
46
+ if (totalMin < 60) return `in ${totalMin}m`;
47
+ const h = Math.floor(totalMin / 60);
48
+ const m = totalMin % 60;
49
+ return m > 0 ? `in ${h}h ${m}m` : `in ${h}h`;
50
+ }
51
+
42
52
  // Reuse the locale formatter when a feed contains older items. Constructing
43
53
  // one for every row dominates opening a long transcript, even without edits.
44
54
  const olderRelativeDateFormatter = new Intl.DateTimeFormat("en-US", {
@@ -146,7 +146,7 @@ class DebugLog {
146
146
  exportAsText(filter?: { level?: LogLevel; source?: string }): string {
147
147
  const entries = this.getEntries(filter);
148
148
  const lines: string[] = [
149
- `Gloomberb Debug Log — exported ${new Date().toISOString()}`,
149
+ `Gloom Debug Log — exported ${new Date().toISOString()}`,
150
150
  `Total entries: ${entries.length}`,
151
151
  "=".repeat(80),
152
152
  "",