gloomberb 0.16.5 → 0.17.0

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 (691) hide show
  1. package/package.json +3 -1
  2. package/src/api-client/central-bank-rates.ts +6 -1
  3. package/src/api-client/chat-pane-link.ts +47 -0
  4. package/src/api-client/chat.ts +35 -1
  5. package/src/api-client/credit-boards.ts +2 -0
  6. package/src/api-client/data.ts +85 -1
  7. package/src/api-client/equity-screener.ts +3 -0
  8. package/src/api-client/europe-energy.ts +197 -0
  9. package/src/api-client/index.ts +15 -0
  10. package/src/api-client/letters.ts +41 -0
  11. package/src/api-client/market-discovery.ts +3 -0
  12. package/src/api-client/normalizers.ts +68 -6
  13. package/src/api-client/organization-tickers.ts +44 -0
  14. package/src/api-client/perps.ts +8 -0
  15. package/src/api-client/request.ts +11 -2
  16. package/src/api-client/socket.ts +29 -0
  17. package/src/api-client/themes.ts +22 -1
  18. package/src/api-client/types.ts +88 -2
  19. package/src/app/global-shortcuts.ts +35 -1
  20. package/src/app/keybindings/actions.ts +12 -1
  21. package/src/app/pane-runtime/plugin-bindings.ts +2 -2
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +9 -3
  23. package/src/app/runtime/desktop-deeplink.ts +58 -20
  24. package/src/app/runtime/startup.ts +1 -1
  25. package/src/app.tsx +6 -0
  26. package/src/architecture/core-builtin-allowlist.json +5 -14
  27. package/src/architecture/dom-cell-metrics-allowlist.json +28 -0
  28. package/src/ask-gloom/pane-context.ts +118 -0
  29. package/src/ask-gloom/pane-request.ts +70 -0
  30. package/src/ask-gloom/pane-text.ts +201 -0
  31. package/src/capabilities/chart-series.ts +8 -1
  32. package/src/capabilities/factories.ts +1 -0
  33. package/src/cli/central-bank-events.ts +23 -0
  34. package/src/cli/command-options.ts +17 -5
  35. package/src/cli/commands/crypto-hints.ts +28 -2
  36. package/src/cli/commands/fred-series.ts +155 -4
  37. package/src/cli/commands/market.ts +175 -35
  38. package/src/cli/commands/overview.ts +62 -20
  39. package/src/cli/commands/search.ts +254 -23
  40. package/src/cli/commands/ticker.ts +137 -24
  41. package/src/cli/desktop-pane-shot-view.ts +114 -0
  42. package/src/cli/desktop-pane-shot.ts +53 -37
  43. package/src/cli/entry.ts +7 -0
  44. package/src/cli/errors.ts +7 -5
  45. package/src/cli/exit-codes.ts +8 -0
  46. package/src/cli/export-source.ts +101 -0
  47. package/src/cli/freshness.ts +18 -3
  48. package/src/cli/glossary.ts +79 -8
  49. package/src/cli/help.ts +10 -0
  50. package/src/cli/helpers.ts +2 -5
  51. package/src/cli/history-rows.ts +244 -13
  52. package/src/cli/index.ts +9 -3
  53. package/src/cli/known-gaps.ts +76 -8
  54. package/src/cli/listing-arg.ts +201 -90
  55. package/src/cli/listing-suggestion.ts +159 -0
  56. package/src/cli/native-smoke.ts +13 -0
  57. package/src/cli/not-a-ticker.ts +24 -4
  58. package/src/cli/pane-functions/access-gate.ts +8 -2
  59. package/src/cli/pane-functions/capabilities.ts +17 -9
  60. package/src/cli/pane-functions/catalog.ts +183 -39
  61. package/src/cli/pane-functions/data.ts +30 -0
  62. package/src/cli/pane-functions/dom.ts +44 -14
  63. package/src/cli/pane-functions/freshness.ts +48 -24
  64. package/src/cli/pane-functions/function-help.ts +66 -12
  65. package/src/cli/pane-functions/headless.ts +81 -22
  66. package/src/cli/pane-functions/index.ts +90 -18
  67. package/src/cli/pane-functions/options.ts +8 -0
  68. package/src/cli/pane-functions/report-notices.ts +6 -3
  69. package/src/cli/pane-functions/report.ts +16 -2
  70. package/src/cli/pane-functions/resolver.ts +32 -11
  71. package/src/cli/pane-functions/screenshot.ts +145 -9
  72. package/src/cli/perpetual-contract.ts +60 -0
  73. package/src/cli/registry.ts +3 -1
  74. package/src/cli/report-tables.ts +53 -13
  75. package/src/cli/result.ts +91 -17
  76. package/src/cli/series-search.ts +111 -0
  77. package/src/cli/staged-tickers.ts +1 -1
  78. package/src/cli/symbol-args.ts +18 -0
  79. package/src/cli/symbol-list.ts +1 -19
  80. package/src/cli/symbol-rows.ts +171 -0
  81. package/src/cli/ticker-resolution.ts +99 -8
  82. package/src/components/chart/composite/axis-overlays.tsx +10 -1
  83. package/src/components/chart/composite/composite-chart.tsx +24 -15
  84. package/src/components/chart/composite/downsample.ts +1 -1
  85. package/src/components/chart/composite/format.ts +29 -0
  86. package/src/components/chart/composite/interactions.ts +9 -2
  87. package/src/components/chart/composite/legend.tsx +57 -33
  88. package/src/components/chart/composite/panel-bitmap.ts +70 -35
  89. package/src/components/chart/composite/panel-labels.ts +18 -17
  90. package/src/components/chart/composite/panel-series.ts +1 -0
  91. package/src/components/chart/composite/panel-surface.tsx +6 -8
  92. package/src/components/chart/composite/price-axis-labels.tsx +1 -0
  93. package/src/components/chart/composite/rasterizer.ts +50 -8
  94. package/src/components/chart/composite/scene.ts +2 -1
  95. package/src/components/chart/composite/time-scale.ts +26 -9
  96. package/src/components/chart/composite/types.ts +18 -0
  97. package/src/components/chart/composite/use-composite-viewport.ts +30 -2
  98. package/src/components/chart/native/raster/crosshair.ts +8 -3
  99. package/src/components/chart/native/raster/primitives.ts +85 -21
  100. package/src/components/chart/native/surface/manager.ts +33 -8
  101. package/src/components/chart/surface3d/chart.tsx +1 -1
  102. package/src/components/command-bar/action-runtime.ts +1 -1
  103. package/src/components/command-bar/commands/collection-list.ts +210 -0
  104. package/src/components/command-bar/commands/collection.ts +40 -1
  105. package/src/components/command-bar/commands/direct/index.ts +4 -0
  106. package/src/components/command-bar/commands/registry.ts +8 -0
  107. package/src/components/command-bar/list/model.ts +2 -0
  108. package/src/components/command-bar/pane-templates/items.ts +1 -1
  109. package/src/components/command-bar/routes/root/plugin-install.ts +4 -4
  110. package/src/components/command-bar/routes/ticker-search/actions.ts +1 -0
  111. package/src/components/command-bar/routes/ticker-search/results.ts +3 -1
  112. package/src/components/command-bar/routing/actions.ts +1 -1
  113. package/src/components/command-bar/workflow/ops.ts +8 -97
  114. package/src/components/command-bar/workflow/pane-reuse.ts +177 -0
  115. package/src/components/command-bar/workflow/tickers.ts +35 -29
  116. package/src/components/data-table/view.tsx +1 -0
  117. package/src/components/feed-data-table/stack-view.tsx +18 -42
  118. package/src/components/layout/detached-pane-shell.tsx +59 -39
  119. package/src/components/layout/floating-pane.tsx +7 -1
  120. package/src/components/layout/header.tsx +5 -0
  121. package/src/components/layout/missing-pane.tsx +5 -3
  122. package/src/components/layout/pane/capture-stamp.ts +41 -0
  123. package/src/components/layout/pane/capture-status.ts +12 -0
  124. package/src/components/layout/pane/footer/keyboard.ts +3 -0
  125. package/src/components/layout/pane/footer/model.ts +7 -1
  126. package/src/components/layout/pane/header.tsx +26 -0
  127. package/src/components/layout/pane/index.tsx +9 -1
  128. package/src/components/layout/pane/sidebar.tsx +10 -11
  129. package/src/components/layout/shell/cursor-occlusion.ts +2 -1
  130. package/src/components/layout/shell/index.tsx +62 -60
  131. package/src/components/layout/shell/menu.ts +9 -1
  132. package/src/components/layout/shell/pane/actions.ts +32 -10
  133. package/src/components/layout/shell/pane/ask.ts +136 -0
  134. package/src/components/layout/shell/pane/capture.ts +136 -0
  135. package/src/components/layout/shell/pane/layers.tsx +26 -5
  136. package/src/components/layout/shell/pane/management-shortcuts.ts +46 -1
  137. package/src/components/layout/shell/shortcuts.ts +21 -4
  138. package/src/components/markdown-text.tsx +37 -6
  139. package/src/components/metric-treemap/heat-surface.tsx +1 -3
  140. package/src/components/reader/article-reader.tsx +467 -0
  141. package/src/components/reader/article-text.ts +527 -0
  142. package/src/components/reader/detail-reader.tsx +210 -0
  143. package/src/components/reader/reader-text.tsx +239 -0
  144. package/src/components/table-view-shared.tsx +8 -4
  145. package/src/components/ticker/badge/list.tsx +15 -4
  146. package/src/components/ticker/list-table-view.tsx +19 -1
  147. package/src/components/ui/choice-dialog.tsx +3 -0
  148. package/src/components/ui/data-table/index.tsx +1 -0
  149. package/src/components/ui/data-table/types.ts +7 -0
  150. package/src/components/ui/detail-scroll-body.tsx +31 -4
  151. package/src/components/ui/display.tsx +46 -1
  152. package/src/components/ui/icon.tsx +14 -1
  153. package/src/components/ui/list-view.tsx +3 -1
  154. package/src/components/ui/query-bar-keys.tsx +2 -0
  155. package/src/components/ui/query-bar.tsx +18 -1
  156. package/src/components/ui/select-button.tsx +3 -0
  157. package/src/data/config/store/fold-panes.ts +202 -11
  158. package/src/data/config/store/migrations.ts +20 -1
  159. package/src/data/config/store/portfolio-as-of-column.ts +57 -0
  160. package/src/docs/snapshot.json +191 -23
  161. package/src/i18n/es.ts +18 -1
  162. package/src/i18n/ja.ts +18 -1
  163. package/src/i18n/ko.ts +18 -1
  164. package/src/i18n/zh-cn.ts +18 -1
  165. package/src/i18n/zh-tw.ts +18 -1
  166. package/src/layout/desk-keys.ts +7 -0
  167. package/src/layout/desks.ts +2 -2
  168. package/src/layout-marketplace/gallery.tsx +1 -1
  169. package/src/market-data/market/freshness.ts +3 -2
  170. package/src/market-data/market/session-close-note.ts +3 -2
  171. package/src/market-data/market/status.ts +67 -6
  172. package/src/market-data/quotes/contributions.ts +17 -0
  173. package/src/market-data/quotes/metadata.ts +12 -0
  174. package/src/market-data/quotes/regular-session-valuation.ts +131 -0
  175. package/src/market-data/quotes/resolution.ts +7 -0
  176. package/src/market-data/snapshot-provider.ts +6 -3
  177. package/src/notifications/app-badge.ts +30 -0
  178. package/src/notifications/app-notifier.ts +17 -1
  179. package/src/notifications/notification-log.ts +180 -0
  180. package/src/plugins/auto-update-node.ts +2 -2
  181. package/src/plugins/auto-update.ts +2 -2
  182. package/src/plugins/builtin/account-management/calendar-tab.tsx +1 -1
  183. package/src/plugins/builtin/account-management/form-components.tsx +53 -1
  184. package/src/plugins/builtin/account-management/index.tsx +1 -0
  185. package/src/plugins/builtin/account-management/keyboard.ts +5 -0
  186. package/src/plugins/builtin/account-management/model.ts +27 -0
  187. package/src/plugins/builtin/account-management/pane.tsx +62 -2
  188. package/src/plugins/builtin/alerts/catalysts.ts +1 -1
  189. package/src/plugins/builtin/alerts/cli.ts +6 -4
  190. package/src/plugins/builtin/alerts/focus.ts +27 -0
  191. package/src/plugins/builtin/alerts/index.tsx +6 -2
  192. package/src/plugins/builtin/alerts/pane.tsx +19 -1
  193. package/src/plugins/builtin/analytics/index.tsx +5 -2
  194. package/src/plugins/builtin/analytics/risk-headless.ts +30 -15
  195. package/src/plugins/builtin/attention/headless.ts +1 -1
  196. package/src/plugins/builtin/awards/headless.ts +88 -18
  197. package/src/plugins/builtin/awards/model.ts +41 -1
  198. package/src/plugins/builtin/awards/pane.tsx +48 -30
  199. package/src/plugins/builtin/backtest/headless.ts +2 -2
  200. package/src/plugins/builtin/backtest/pane.tsx +2 -1
  201. package/src/plugins/builtin/bond-calculator/index.tsx +3 -5
  202. package/src/plugins/builtin/broker-manager/index.tsx +1 -0
  203. package/src/plugins/builtin/buildout/detail/company.tsx +7 -4
  204. package/src/plugins/builtin/builtin-plugin-meta.ts +5 -4
  205. package/src/plugins/builtin/catalysts/headless.ts +1 -1
  206. package/src/plugins/builtin/catalysts/model.ts +92 -10
  207. package/src/plugins/builtin/catalysts/pane.tsx +6 -4
  208. package/src/plugins/builtin/cds/client.ts +11 -31
  209. package/src/plugins/builtin/cds/headless.ts +5 -2
  210. package/src/plugins/builtin/cds/index.tsx +21 -30
  211. package/src/plugins/builtin/cds/model.ts +21 -1
  212. package/src/plugins/builtin/cds/pane.tsx +6 -4
  213. package/src/plugins/builtin/central-bank-rates/client.ts +4 -0
  214. package/src/plugins/builtin/central-bank-rates/headless.ts +46 -16
  215. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -1
  216. package/src/plugins/builtin/central-bank-rates/model.ts +99 -0
  217. package/src/plugins/builtin/central-bank-rates/pane.tsx +1 -1
  218. package/src/plugins/builtin/central-bank-rates/series.ts +19 -25
  219. package/src/plugins/builtin/changelog/index.tsx +11 -12
  220. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
  221. package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -1
  222. package/src/plugins/builtin/chart-composer/headless.ts +151 -9
  223. package/src/plugins/builtin/chart-composer/index.tsx +6 -1
  224. package/src/plugins/builtin/chart-composer/pane.tsx +84 -40
  225. package/src/plugins/builtin/chart-composer/session-history.ts +20 -5
  226. package/src/plugins/builtin/chat/command-bar.ts +6 -8
  227. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  228. package/src/plugins/builtin/chat/content/composer.tsx +15 -2
  229. package/src/plugins/builtin/chat/content/footer.tsx +27 -5
  230. package/src/plugins/builtin/chat/content/handle-prompt.tsx +326 -0
  231. package/src/plugins/builtin/chat/content/index.tsx +201 -13
  232. package/src/plugins/builtin/chat/content/layout-metrics.ts +4 -0
  233. package/src/plugins/builtin/chat/content/new-messages.tsx +89 -0
  234. package/src/plugins/builtin/chat/content/open-targets.ts +20 -4
  235. package/src/plugins/builtin/chat/content/row-activity.ts +132 -0
  236. package/src/plugins/builtin/chat/content/scroll.ts +133 -28
  237. package/src/plugins/builtin/chat/content/shortcuts.ts +2 -2
  238. package/src/plugins/builtin/chat/content/transcript.tsx +155 -79
  239. package/src/plugins/builtin/chat/content/types.ts +1 -0
  240. package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
  241. package/src/plugins/builtin/chat/controller/index.ts +121 -1
  242. package/src/plugins/builtin/chat/controller/message-loading.ts +3 -0
  243. package/src/plugins/builtin/chat/controller/messages.ts +7 -2
  244. package/src/plugins/builtin/chat/controller/realtime.ts +10 -1
  245. package/src/plugins/builtin/chat/controller/send.ts +61 -1
  246. package/src/plugins/builtin/chat/controller/state.ts +8 -0
  247. package/src/plugins/builtin/chat/ghost-user.ts +20 -0
  248. package/src/plugins/builtin/chat/handle-prompt.ts +128 -0
  249. package/src/plugins/builtin/chat/index.tsx +66 -25
  250. package/src/plugins/builtin/chat/layout.ts +84 -18
  251. package/src/plugins/builtin/chat/message/desktop.tsx +111 -14
  252. package/src/plugins/builtin/chat/message/header.tsx +81 -21
  253. package/src/plugins/builtin/chat/message/pane-chip.tsx +223 -0
  254. package/src/plugins/builtin/chat/message/person-icons.tsx +53 -0
  255. package/src/plugins/builtin/chat/message/profile-popover.tsx +77 -9
  256. package/src/plugins/builtin/chat/message/reactions.tsx +210 -0
  257. package/src/plugins/builtin/chat/message/render-state.ts +33 -4
  258. package/src/plugins/builtin/chat/message/terminal.tsx +72 -8
  259. package/src/plugins/builtin/chat/message/types.ts +15 -0
  260. package/src/plugins/builtin/chat/pane-links/model.ts +149 -0
  261. package/src/plugins/builtin/chat/pane-links/open.ts +29 -0
  262. package/src/plugins/builtin/chat/pane-links/previews.ts +119 -0
  263. package/src/plugins/builtin/chat/pane-state.ts +5 -8
  264. package/src/plugins/builtin/chat/people.ts +202 -0
  265. package/src/plugins/builtin/chat/profile-popover.ts +18 -2
  266. package/src/plugins/builtin/chat/reactions.ts +197 -0
  267. package/src/plugins/builtin/chat/share/flow.tsx +87 -0
  268. package/src/plugins/builtin/chat/share/targets.ts +27 -0
  269. package/src/plugins/builtin/chat/status-widget.tsx +7 -9
  270. package/src/plugins/builtin/chat/unread-badge.ts +65 -0
  271. package/src/plugins/builtin/chat/unread-inbox-pane.tsx +151 -27
  272. package/src/plugins/builtin/chat/unread-inbox.ts +22 -0
  273. package/src/plugins/builtin/clinical-trials/client.ts +26 -1
  274. package/src/plugins/builtin/clinical-trials/index.tsx +10 -2
  275. package/src/plugins/builtin/clinical-trials/model.ts +269 -0
  276. package/src/plugins/builtin/clinical-trials/pane.tsx +247 -62
  277. package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +156 -0
  278. package/src/plugins/builtin/cloud/askg/attached-pane.tsx +69 -0
  279. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +94 -0
  280. package/src/plugins/builtin/cloud/askg/pane.tsx +141 -12
  281. package/src/plugins/builtin/cloud/askg/pending-question.ts +11 -0
  282. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +1 -0
  283. package/src/plugins/builtin/cloud/plugin.tsx +19 -2
  284. package/src/plugins/builtin/cloud/team/model.ts +5 -0
  285. package/src/plugins/builtin/cloud/team/module.tsx +1 -0
  286. package/src/plugins/builtin/cloud/team/store.ts +8 -3
  287. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
  288. package/src/plugins/builtin/comment-letters/pane.tsx +30 -11
  289. package/src/plugins/builtin/commodities-energy/index.tsx +179 -0
  290. package/src/plugins/builtin/commodity-balances/client.ts +208 -0
  291. package/src/plugins/builtin/commodity-balances/headless.ts +66 -0
  292. package/src/plugins/builtin/commodity-balances/index.tsx +32 -0
  293. package/src/plugins/builtin/commodity-balances/model.ts +261 -0
  294. package/src/plugins/builtin/commodity-balances/pane.tsx +227 -0
  295. package/src/plugins/builtin/company-attention/headless.ts +2 -2
  296. package/src/plugins/builtin/company-kpis/headless.ts +97 -10
  297. package/src/plugins/builtin/company-kpis/model.ts +24 -2
  298. package/src/plugins/builtin/company-kpis/pane.tsx +13 -14
  299. package/src/plugins/builtin/composite-plugins.ts +26 -1
  300. package/src/plugins/builtin/connections/index.ts +1 -0
  301. package/src/plugins/builtin/cot/headless.ts +1 -1
  302. package/src/plugins/builtin/cpi/headless.ts +3 -3
  303. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +10 -1
  304. package/src/plugins/builtin/credit-boards/client.ts +6 -2
  305. package/src/plugins/builtin/credit-boards/headless.ts +108 -31
  306. package/src/plugins/builtin/credit-boards/index.tsx +21 -42
  307. package/src/plugins/builtin/credit-boards/model.ts +11 -3
  308. package/src/plugins/builtin/credit-conditions/client.ts +12 -9
  309. package/src/plugins/builtin/credit-conditions/headless.ts +13 -2
  310. package/src/plugins/builtin/credit-conditions/index.tsx +20 -22
  311. package/src/plugins/builtin/credit-conditions/model.ts +19 -11
  312. package/src/plugins/builtin/credit-conditions/series.ts +8 -18
  313. package/src/plugins/builtin/credit-documents/headless.ts +2 -2
  314. package/src/plugins/builtin/credit-documents/index.tsx +27 -10
  315. package/src/plugins/builtin/debt-maturities/headless.ts +17 -11
  316. package/src/plugins/builtin/debt-maturities/index.tsx +3 -17
  317. package/src/plugins/builtin/debug/index.tsx +2 -2
  318. package/src/plugins/builtin/dividend-yield/client.ts +6 -1
  319. package/src/plugins/builtin/dividend-yield/headless.ts +8 -3
  320. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  321. package/src/plugins/builtin/dividend-yield/pane.tsx +6 -2
  322. package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
  323. package/src/plugins/builtin/dividend-yield/types.ts +2 -0
  324. package/src/plugins/builtin/docs/blocks.tsx +4 -3
  325. package/src/plugins/builtin/docs/pane.tsx +16 -8
  326. package/src/plugins/builtin/doe/headless.ts +12 -8
  327. package/src/plugins/builtin/earnings/index.tsx +11 -0
  328. package/src/plugins/builtin/earnings-calls/headless.ts +28 -2
  329. package/src/plugins/builtin/earnings-calls/index.tsx +1 -1
  330. package/src/plugins/builtin/earnings-calls/pane.tsx +2 -12
  331. package/src/plugins/builtin/earnings-ripple/index.tsx +2 -0
  332. package/src/plugins/builtin/econ/fred-catalog.ts +9 -0
  333. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  334. package/src/plugins/builtin/equity-screener/headless.ts +39 -5
  335. package/src/plugins/builtin/equity-screener/model.ts +121 -68
  336. package/src/plugins/builtin/estimate-revisions/headless.ts +3 -3
  337. package/src/plugins/builtin/executives/pane.tsx +3 -1
  338. package/src/plugins/builtin/exposure/drivers.ts +13 -0
  339. package/src/plugins/builtin/exposure/headless.ts +205 -13
  340. package/src/plugins/builtin/exposure/index.tsx +1 -0
  341. package/src/plugins/builtin/exposure/model.ts +23 -3
  342. package/src/plugins/builtin/exposure/pane.tsx +11 -11
  343. package/src/plugins/builtin/fear-greed/index.tsx +14 -10
  344. package/src/plugins/builtin/filing-events/index.tsx +20 -0
  345. package/src/plugins/builtin/fixed-income/index.tsx +46 -0
  346. package/src/plugins/builtin/fixed-income/model.ts +27 -0
  347. package/src/plugins/builtin/fixed-income/pane.tsx +162 -0
  348. package/src/plugins/builtin/fixed-income/shortcut.ts +52 -0
  349. package/src/plugins/builtin/futures/index.tsx +24 -7
  350. package/src/plugins/builtin/futures/series.ts +23 -2
  351. package/src/plugins/builtin/futures/use-front-returns.ts +3 -2
  352. package/src/plugins/builtin/futures-curve/headless.ts +1 -1
  353. package/src/plugins/builtin/futures-curve/index.tsx +1 -1
  354. package/src/plugins/builtin/futures-curve/pane.tsx +6 -4
  355. package/src/plugins/builtin/fx-matrix/client.ts +63 -0
  356. package/src/plugins/builtin/fx-matrix/export.ts +2 -1
  357. package/src/plugins/builtin/fx-matrix/headless.ts +154 -0
  358. package/src/plugins/builtin/fx-matrix/index.tsx +3 -3
  359. package/src/plugins/builtin/gpu/headless.ts +4 -4
  360. package/src/plugins/builtin/help/function-index.ts +2 -0
  361. package/src/plugins/builtin/help/index.tsx +14 -1
  362. package/src/plugins/builtin/help/shortcut-table.tsx +4 -2
  363. package/src/plugins/builtin/insider/detail.tsx +20 -12
  364. package/src/plugins/builtin/insider/index.tsx +2 -2
  365. package/src/plugins/builtin/ipo-calendar/headless.ts +1 -1
  366. package/src/plugins/builtin/iv-history/client.ts +6 -2
  367. package/src/plugins/builtin/iv-history/format.ts +9 -0
  368. package/src/plugins/builtin/iv-history/headless.ts +50 -18
  369. package/src/plugins/builtin/iv-history/index.tsx +3 -0
  370. package/src/plugins/builtin/iv-history/model.ts +35 -4
  371. package/src/plugins/builtin/iv-history/screen-pane.tsx +13 -5
  372. package/src/plugins/builtin/jobs/headless.ts +6 -8
  373. package/src/plugins/builtin/jodi/client.ts +220 -0
  374. package/src/plugins/builtin/jodi/headless.ts +63 -0
  375. package/src/plugins/builtin/jodi/index.tsx +32 -0
  376. package/src/plugins/builtin/jodi/model.ts +75 -0
  377. package/src/plugins/builtin/jodi/pane.tsx +141 -0
  378. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  379. package/src/plugins/builtin/market-heatmap/evidence.ts +13 -4
  380. package/src/plugins/builtin/market-heatmap/headless.ts +22 -7
  381. package/src/plugins/builtin/market-heatmap/index.tsx +47 -23
  382. package/src/plugins/builtin/market-heatmap/live.ts +2 -1
  383. package/src/plugins/builtin/market-heatmap/portfolio.ts +67 -21
  384. package/src/plugins/builtin/market-movers/table.ts +2 -2
  385. package/src/plugins/builtin/market-valuation/align.ts +10 -9
  386. package/src/plugins/builtin/members/headless.ts +1 -1
  387. package/src/plugins/builtin/members/pane.tsx +3 -2
  388. package/src/plugins/builtin/mna/distress/headless.ts +4 -4
  389. package/src/plugins/builtin/mna/distress/model.ts +52 -1
  390. package/src/plugins/builtin/mna/distress/pane.tsx +17 -12
  391. package/src/plugins/builtin/mna/headless.ts +3 -3
  392. package/src/plugins/builtin/mna/index.tsx +2 -0
  393. package/src/plugins/builtin/money-markets/headless.ts +5 -4
  394. package/src/plugins/builtin/news/index.tsx +0 -2
  395. package/src/plugins/builtin/news/wire/index.tsx +2 -2
  396. package/src/plugins/builtin/news/wire/industry-pane.tsx +28 -14
  397. package/src/plugins/builtin/news/wire/letters/client.ts +108 -0
  398. package/src/plugins/builtin/news/wire/letters/model.ts +69 -0
  399. package/src/plugins/builtin/news/wire/letters/pane.tsx +155 -0
  400. package/src/plugins/builtin/news/wire/mutes.ts +2 -1
  401. package/src/plugins/builtin/news/wire/news/detail-view.tsx +53 -104
  402. package/src/plugins/builtin/news/wire/news/footer.ts +2 -25
  403. package/src/plugins/builtin/news/wire/news/preset-pane.tsx +0 -2
  404. package/src/plugins/builtin/news/wire/news/query-presets.ts +4 -1
  405. package/src/plugins/builtin/notes/index.tsx +1 -0
  406. package/src/plugins/builtin/openfda/client.ts +123 -15
  407. package/src/plugins/builtin/openfda/model.ts +163 -0
  408. package/src/plugins/builtin/openfda/pane.tsx +109 -65
  409. package/src/plugins/builtin/openfda/types.ts +25 -0
  410. package/src/plugins/builtin/options/index.tsx +3 -0
  411. package/src/plugins/builtin/options/view.tsx +1 -1
  412. package/src/plugins/builtin/options-positioning/headless.ts +3 -3
  413. package/src/plugins/builtin/pe-band/model.ts +3 -2
  414. package/src/plugins/builtin/perps/client.ts +50 -10
  415. package/src/plugins/builtin/perps/equity-row.tsx +29 -16
  416. package/src/plugins/builtin/perps/headless.ts +2 -2
  417. package/src/plugins/builtin/perps/index.tsx +5 -3
  418. package/src/plugins/builtin/perps/list-perpetuals.ts +118 -0
  419. package/src/plugins/builtin/perps/open-market.ts +15 -0
  420. package/src/plugins/builtin/plugin-marketplace/index.tsx +1 -1
  421. package/src/plugins/builtin/plugin-marketplace/pane.tsx +7 -7
  422. package/src/plugins/builtin/plugin-module.ts +3 -1
  423. package/src/plugins/builtin/portfolio-list/allocation.ts +5 -4
  424. package/src/plugins/builtin/portfolio-list/cell-version.ts +8 -1
  425. package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +24 -136
  426. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +78 -19
  427. package/src/plugins/builtin/portfolio-list/cli/render.ts +249 -52
  428. package/src/plugins/builtin/portfolio-list/cli/shared.ts +44 -12
  429. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +21 -7
  430. package/src/plugins/builtin/portfolio-list/column-values.ts +103 -25
  431. package/src/plugins/builtin/portfolio-list/headless.ts +51 -15
  432. package/src/plugins/builtin/portfolio-list/index.tsx +5 -1
  433. package/src/plugins/builtin/portfolio-list/live-valuation.ts +25 -13
  434. package/src/plugins/builtin/portfolio-list/mutations.ts +2 -9
  435. package/src/plugins/builtin/portfolio-list/pane/data.ts +5 -1
  436. package/src/plugins/builtin/portfolio-list/pane/index.tsx +46 -8
  437. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +16 -10
  438. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +21 -1
  439. package/src/plugins/builtin/portfolio-list/settings.ts +17 -0
  440. package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
  441. package/src/plugins/builtin/portfolio-list/summary/totals.ts +8 -3
  442. package/src/plugins/builtin/portfolio-list/table.tsx +30 -6
  443. package/src/plugins/builtin/power/evidence.ts +86 -8
  444. package/src/plugins/builtin/power/gas-client.ts +110 -0
  445. package/src/plugins/builtin/power/gas-model.ts +194 -0
  446. package/src/plugins/builtin/power/gas.tsx +145 -0
  447. package/src/plugins/builtin/power/headless.ts +9 -2
  448. package/src/plugins/builtin/power/index.tsx +35 -3
  449. package/src/plugins/builtin/power/model.ts +2 -2
  450. package/src/plugins/builtin/power/pane.tsx +7 -3
  451. package/src/plugins/builtin/power/prices-client.ts +139 -0
  452. package/src/plugins/builtin/power/prices-headless.ts +205 -0
  453. package/src/plugins/builtin/power/prices-model.ts +103 -0
  454. package/src/plugins/builtin/power/prices.tsx +202 -0
  455. package/src/plugins/builtin/primary-dealers/client.ts +108 -0
  456. package/src/plugins/builtin/primary-dealers/headless.ts +53 -0
  457. package/src/plugins/builtin/primary-dealers/index.tsx +17 -0
  458. package/src/plugins/builtin/primary-dealers/model.ts +206 -0
  459. package/src/plugins/builtin/primary-dealers/pane.tsx +149 -0
  460. package/src/plugins/builtin/rate-path/headless.ts +38 -18
  461. package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
  462. package/src/plugins/builtin/relative-rotation/index.tsx +3 -0
  463. package/src/plugins/builtin/research/analyst-model.ts +9 -5
  464. package/src/plugins/builtin/research/headless-schema.ts +2 -1
  465. package/src/plugins/builtin/research/index.tsx +2 -0
  466. package/src/plugins/builtin/research/relative-valuation-headless.ts +68 -32
  467. package/src/plugins/builtin/research/relative-valuation-model.ts +45 -24
  468. package/src/plugins/builtin/research/relative-valuation-pane.tsx +39 -4
  469. package/src/plugins/builtin/research-search/article.ts +65 -0
  470. package/src/plugins/builtin/research-search/document-view.tsx +33 -215
  471. package/src/plugins/builtin/research-search/model.ts +0 -9
  472. package/src/plugins/builtin/research-search/pane.tsx +41 -30
  473. package/src/plugins/builtin/research-search/snippet.ts +0 -29
  474. package/src/plugins/builtin/revenue-breakdown/headless.ts +18 -6
  475. package/src/plugins/builtin/scanner/flow-pane.tsx +8 -1
  476. package/src/plugins/builtin/sec/filing-display.tsx +3 -36
  477. package/src/plugins/builtin/sec/headless.ts +25 -3
  478. package/src/plugins/builtin/sec/index.tsx +133 -44
  479. package/src/plugins/builtin/sec/model.ts +148 -2
  480. package/src/plugins/builtin/sec/row-summaries.ts +105 -0
  481. package/src/plugins/builtin/sectors/index.tsx +2 -0
  482. package/src/plugins/builtin/sectors/sector-model.ts +2 -2
  483. package/src/plugins/builtin/series-inventory.ts +3 -0
  484. package/src/plugins/builtin/shared/cloud-denied.ts +15 -0
  485. package/src/plugins/builtin/shared/free-preview.ts +47 -0
  486. package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
  487. package/src/plugins/builtin/shared/report-freshness.ts +3 -1
  488. package/src/plugins/builtin/shared/use-quote-board.ts +179 -43
  489. package/src/plugins/builtin/shared/volatility/options.ts +4 -1
  490. package/src/plugins/builtin/shared/wall-teaser.tsx +2 -2
  491. package/src/plugins/builtin/shared/zip-text.ts +46 -0
  492. package/src/plugins/builtin/short-interest/watch-headless.ts +2 -2
  493. package/src/plugins/builtin/short-interest/watch-model.ts +11 -2
  494. package/src/plugins/builtin/short-volume/headless.ts +1 -1
  495. package/src/plugins/builtin/social-mentions/headless.ts +1 -1
  496. package/src/plugins/builtin/supply-chain/headless.ts +43 -11
  497. package/src/plugins/builtin/supply-chain/model.ts +13 -0
  498. package/src/plugins/builtin/supply-chain/pane.tsx +5 -5
  499. package/src/plugins/builtin/themes/headless.ts +74 -10
  500. package/src/plugins/builtin/themes/model.ts +71 -4
  501. package/src/plugins/builtin/themes/pane.tsx +22 -5
  502. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +114 -0
  503. package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +57 -2
  504. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +19 -55
  505. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +11 -0
  506. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +81 -19
  507. package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
  508. package/src/plugins/builtin/ticker-detail/headless-schema.ts +30 -0
  509. package/src/plugins/builtin/ticker-detail/headless.ts +88 -2
  510. package/src/plugins/builtin/ticker-detail/index.tsx +23 -11
  511. package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -1
  512. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +8 -6
  513. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +8 -6
  514. package/src/plugins/builtin/time-sales/headless.ts +23 -1
  515. package/src/plugins/builtin/time-sales/index.tsx +2 -1
  516. package/src/plugins/builtin/trace-bonds/client.ts +196 -0
  517. package/src/plugins/builtin/trace-bonds/headless.ts +69 -0
  518. package/src/plugins/builtin/trace-bonds/index.tsx +33 -0
  519. package/src/plugins/builtin/trace-bonds/model.ts +115 -0
  520. package/src/plugins/builtin/trace-bonds/pane.tsx +191 -0
  521. package/src/plugins/builtin/treasury-auctions/index.tsx +33 -47
  522. package/src/plugins/builtin/treasury-daily/client.ts +96 -0
  523. package/src/plugins/builtin/treasury-daily/headless.ts +69 -0
  524. package/src/plugins/builtin/treasury-daily/index.tsx +17 -0
  525. package/src/plugins/builtin/treasury-daily/model.ts +69 -0
  526. package/src/plugins/builtin/treasury-daily/pane.tsx +140 -0
  527. package/src/plugins/builtin/vol-surface/evidence.ts +7 -1
  528. package/src/plugins/builtin/vol-surface/headless.ts +50 -17
  529. package/src/plugins/builtin/vol-surface/heat-grid.tsx +113 -0
  530. package/src/plugins/builtin/vol-surface/model.ts +39 -6
  531. package/src/plugins/builtin/vol-surface/pane.tsx +14 -13
  532. package/src/plugins/builtin/vol-surface/stored.ts +63 -3
  533. package/src/plugins/builtin/vol-surface/surface.tsx +30 -2
  534. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +6 -5
  535. package/src/plugins/builtin/world-venue-map/basemap.ts +6 -3
  536. package/src/plugins/builtin/world-venue-map/entity-detail.tsx +15 -2
  537. package/src/plugins/builtin/world-venue-map/geo-draw.tsx +2 -1
  538. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +6 -6
  539. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +16 -9
  540. package/src/plugins/builtin/world-venue-map/map.tsx +145 -13
  541. package/src/plugins/builtin/world-venue-map/model.ts +56 -19
  542. package/src/plugins/builtin/world-venue-map/pane.tsx +5 -2
  543. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  544. package/src/plugins/builtin-editorial.ts +1 -1
  545. package/src/plugins/catalog-browser.ts +25 -2
  546. package/src/plugins/installer.ts +2 -2
  547. package/src/plugins/loader.ts +15 -0
  548. package/src/plugins/manager-node.ts +1 -1
  549. package/src/plugins/{builtin/plugin-marketplace → marketplace}/feed.ts +2 -2
  550. package/src/plugins/{builtin/plugin-marketplace → marketplace}/model.ts +63 -27
  551. package/src/plugins/{builtin/plugin-marketplace → marketplace}/store.ts +1 -1
  552. package/src/plugins/registry/host-actions.ts +11 -0
  553. package/src/portfolio/collection-name.ts +31 -0
  554. package/src/portfolio/valuation-basis.ts +79 -0
  555. package/src/public/time-series.ts +1 -1
  556. package/src/react/input.ts +33 -1
  557. package/src/react/key-repeat.ts +86 -0
  558. package/src/remote/controller.ts +21 -1
  559. package/src/remote/schema.ts +3 -2
  560. package/src/renderers/browser/main.tsx +1 -1
  561. package/src/renderers/browser/research-entry.ts +4 -2
  562. package/src/renderers/dom/build-assets.ts +14 -5
  563. package/src/renderers/dom/data-table/index.tsx +5 -3
  564. package/src/renderers/dom/data-table/row.tsx +3 -0
  565. package/src/renderers/dom/desktop/controls.tsx +2 -1
  566. package/src/renderers/dom/desktop/icons.tsx +34 -0
  567. package/src/renderers/dom/desktop/list-view.tsx +1 -1
  568. package/src/renderers/dom/desktop/menu.tsx +1 -1
  569. package/src/renderers/dom/desktop/query-bar.tsx +3 -1
  570. package/src/renderers/dom/host/block-glyph-canvas.tsx +6 -3
  571. package/src/renderers/dom/host/box.tsx +7 -2
  572. package/src/renderers/dom/host/chart-surface.tsx +34 -2
  573. package/src/renderers/dom/host/resize-rows.ts +111 -0
  574. package/src/renderers/dom/host/scroll-box.tsx +32 -0
  575. package/src/renderers/dom/host/size-observer.ts +24 -9
  576. package/src/renderers/dom/host/sticky-scroll.ts +147 -0
  577. package/src/renderers/dom/host/style.ts +15 -1
  578. package/src/renderers/dom/host/surface-3d.tsx +15 -7
  579. package/src/renderers/dom/host/surface-fit.ts +410 -0
  580. package/src/renderers/dom/host/text.tsx +2 -2
  581. package/src/renderers/dom/input-host.tsx +1 -0
  582. package/src/renderers/dom/styles.css +109 -16
  583. package/src/renderers/electrobun/bun/application-menu/click.ts +18 -0
  584. package/src/renderers/electrobun/bun/desktop/app-badge.ts +37 -0
  585. package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
  586. package/src/renderers/electrobun/bun/desktop/host-requests.ts +8 -0
  587. package/src/renderers/electrobun/bun/desktop/macos-dock-badge.ts +37 -0
  588. package/src/renderers/electrobun/bun/desktop/macos-dock-icon.ts +2 -40
  589. package/src/renderers/electrobun/bun/desktop/macos-objc.ts +41 -0
  590. package/src/renderers/electrobun/bun/index.ts +3 -2
  591. package/src/renderers/electrobun/bun/tui-entry.ts +18 -1
  592. package/src/renderers/electrobun/shared/protocol.ts +3 -1
  593. package/src/renderers/electrobun/view/app-badge-host.ts +17 -0
  594. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +20 -3
  595. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +59 -24
  596. package/src/renderers/electrobun/view/main.tsx +5 -3
  597. package/src/renderers/electrobun/view/remote/asset-data-client.ts +7 -1
  598. package/src/renderers/opentui/chart-surface.tsx +1 -0
  599. package/src/renderers/opentui/frame-capture.ts +33 -0
  600. package/src/renderers/opentui/host.tsx +10 -13
  601. package/src/renderers/opentui/pane-image/glyphs-license.txt +107 -0
  602. package/src/renderers/opentui/pane-image/glyphs.json +1 -0
  603. package/src/renderers/opentui/pane-image/index.ts +107 -0
  604. package/src/renderers/opentui/pane-image/raster.ts +428 -0
  605. package/src/renderers/opentui/start.tsx +3 -3
  606. package/src/renderers/opentui/test-utils.tsx +3 -0
  607. package/src/renderers/opentui/ui-host.tsx +29 -1
  608. package/src/series/resolve.ts +3 -2
  609. package/src/series/types.ts +8 -0
  610. package/src/series/validate.ts +2 -2
  611. package/src/shares/api.ts +5 -1
  612. package/src/shares/chat.ts +34 -0
  613. package/src/shares/dialog.tsx +295 -0
  614. package/src/shares/flow.tsx +56 -0
  615. package/src/shares/live.ts +14 -22
  616. package/src/shares/location.ts +31 -0
  617. package/src/shares/open.ts +26 -0
  618. package/src/shares/pane-image.ts +164 -0
  619. package/src/shares/personal-pane.ts +52 -0
  620. package/src/shares/portable-layout.ts +4 -2
  621. package/src/shares/prepare.ts +20 -0
  622. package/src/sources/gloomberb-cloud/index.ts +109 -5
  623. package/src/sources/gloomberb-cloud/normalizers.ts +7 -0
  624. package/src/sources/gloomberb-cloud/perpetuals.ts +249 -0
  625. package/src/sources/history-coverage.ts +22 -4
  626. package/src/sources/history-result.ts +3 -1
  627. package/src/sources/history-retention.ts +11 -0
  628. package/src/sources/listing-target.ts +4 -0
  629. package/src/sources/provider-errors.ts +2 -1
  630. package/src/sources/provider-router/batches.ts +18 -2
  631. package/src/sources/provider-router/index.ts +16 -1
  632. package/src/state/app/context/index.tsx +3 -2
  633. package/src/state/hooks/inline-tickers.ts +49 -11
  634. package/src/test-support/builtin-panes.ts +31 -0
  635. package/src/test-support/shell-pane-keys.tsx +1 -0
  636. package/src/theme/theme-context.tsx +15 -1
  637. package/src/tickers/instrument-kind.ts +9 -1
  638. package/src/tickers/listing-name.ts +34 -0
  639. package/src/tickers/listing-venues.ts +154 -0
  640. package/src/tickers/open-target.ts +11 -3
  641. package/src/tickers/perpetual-listing.ts +151 -0
  642. package/src/tickers/prediction-listing.ts +26 -0
  643. package/src/tickers/search/index.ts +34 -6
  644. package/src/tickers/search/ranking.ts +85 -4
  645. package/src/tickers/ticker-changes.ts +213 -0
  646. package/src/time-series/chart-data.ts +2 -0
  647. package/src/time-series/economic.ts +2 -6
  648. package/src/time-series/ice-credit.ts +39 -0
  649. package/src/time-series/parsed-history-cache.ts +41 -11
  650. package/src/time-series/resolve.ts +163 -28
  651. package/src/time-series/studies.ts +1 -0
  652. package/src/time-series/types.ts +8 -0
  653. package/src/types/config.ts +4 -2
  654. package/src/types/data-provider.ts +19 -1
  655. package/src/types/financials.ts +23 -0
  656. package/src/types/headless.ts +74 -0
  657. package/src/types/instrument.ts +4 -0
  658. package/src/types/plugin.ts +60 -2
  659. package/src/types/price-history.ts +2 -0
  660. package/src/types/ticker-changes.ts +26 -0
  661. package/src/ui/context-menu.tsx +13 -1
  662. package/src/ui/host.tsx +49 -7
  663. package/src/ui/live-resize.ts +10 -0
  664. package/src/ui/live-stretch.ts +51 -19
  665. package/src/ui/sticky-scroll.ts +7 -0
  666. package/src/utils/central-banks.ts +76 -0
  667. package/src/utils/cli-output.ts +126 -16
  668. package/src/utils/crypto-pair.ts +1 -1
  669. package/src/utils/currency-pair.ts +66 -0
  670. package/src/utils/currency-units.ts +11 -0
  671. package/src/utils/datetime-format.ts +27 -6
  672. package/src/utils/decimal-comma.ts +57 -0
  673. package/src/utils/dom-screenshot.ts +3 -1
  674. package/src/utils/double-escape-close.ts +15 -1
  675. package/src/utils/exchanges.ts +36 -0
  676. package/src/utils/format.ts +19 -2
  677. package/src/utils/forward-dividend.ts +14 -0
  678. package/src/utils/fx-status.ts +6 -1
  679. package/src/utils/guards.ts +25 -0
  680. package/src/utils/inline-content-tokenizer.ts +22 -3
  681. package/src/utils/local-utc-offset.ts +37 -0
  682. package/src/utils/market-capitalization.ts +8 -0
  683. package/src/utils/one-shot.ts +73 -0
  684. package/src/utils/options.ts +37 -8
  685. package/src/utils/plugin-proxy-hosts.json +8 -1
  686. package/src/utils/utc-time.ts +12 -0
  687. package/src/plugins/builtin/credit-boards/pane.tsx +0 -30
  688. package/src/ui/pane-fill.ts +0 -11
  689. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/activation.ts +0 -0
  690. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/ids.ts +0 -0
  691. /package/src/plugins/{builtin/plugin-marketplace → marketplace}/packs.ts +0 -0
@@ -0,0 +1,201 @@
1
+ import type { NativeFrameCapture } from "../ui/host";
2
+ import { displayWidth } from "../utils/format";
3
+
4
+ /**
5
+ * At most this much of a pane's text goes with a question: a summary of what
6
+ * it shows, not a dump. It leaves room in the terminal context Gloom reads,
7
+ * which keeps 2,000 characters of it.
8
+ */
9
+ export const PANE_TEXT_MAX_CHARS = 1_500;
10
+ const PANE_TEXT_MAX_LINES = 50;
11
+
12
+ export interface CellRect {
13
+ x: number;
14
+ y: number;
15
+ width: number;
16
+ height: number;
17
+ }
18
+
19
+ /** The frame's sides and rules: lines of them say nothing, and an edge cell is not content. */
20
+ const RULE_LINE = /^[\s│┃║┆┊╎─━═┄┈╌┌┐└┘├┤┬┴┼╭╮╯╰▏▕█▐▌░▒▓]*$/u;
21
+ const LEADING_EDGE = /^[│┃║▏▕]\s?/u;
22
+ const TRAILING_EDGE = /\s?[│┃║▏▕█▐▌░▒▓]+$/u;
23
+
24
+ /**
25
+ * A pane's text, trimmed to what it says: no frame edges, rules or trailing
26
+ * blanks, runs of padding cut to two spaces so columns stay apart, blank
27
+ * lines folded, and the whole bounded. Null when nothing is left.
28
+ */
29
+ export function normalizePaneText(raw: string): string | null {
30
+ const lines: string[] = [];
31
+ let blank = false;
32
+ for (const rawLine of raw.split(/\r?\n/)) {
33
+ if (RULE_LINE.test(rawLine)) {
34
+ blank = lines.length > 0;
35
+ continue;
36
+ }
37
+ const line = rawLine
38
+ .replace(LEADING_EDGE, "")
39
+ .replace(TRAILING_EDGE, "")
40
+ .replace(/\s+$/u, "")
41
+ .replace(/\s{3,}/gu, " ");
42
+ if (!line.trim()) {
43
+ blank = lines.length > 0;
44
+ continue;
45
+ }
46
+ if (blank) lines.push("");
47
+ blank = false;
48
+ lines.push(line);
49
+ }
50
+ if (lines.length === 0) return null;
51
+
52
+ const kept: string[] = [];
53
+ let used = 0;
54
+ for (const line of lines.slice(0, PANE_TEXT_MAX_LINES)) {
55
+ if (used + line.length + 1 > PANE_TEXT_MAX_CHARS) break;
56
+ kept.push(line);
57
+ used += line.length + 1;
58
+ }
59
+ if (kept.length === 0) kept.push(lines[0]!.slice(0, PANE_TEXT_MAX_CHARS - 1));
60
+ const cut = kept.length < lines.length || kept[0] !== lines[0];
61
+ return cut ? `${kept.join("\n")}\n…` : kept.join("\n");
62
+ }
63
+
64
+ function covers(rect: CellRect, x: number, y: number): boolean {
65
+ return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
66
+ }
67
+
68
+ /**
69
+ * A terminal pane's text from the frame on screen: the rows under its header,
70
+ * the columns it spans. Cells another surface covers (a floating pane above
71
+ * it, the menu it was asked from) are left blank, so only this pane's own
72
+ * text goes. `rect` and `covered` are in the pane area's cells, which starts
73
+ * `offsetX` columns and `offsetY` rows into the screen (under the app header).
74
+ */
75
+ export function terminalPaneText(
76
+ frame: NativeFrameCapture,
77
+ rect: CellRect,
78
+ { offsetX = 0, offsetY = 0, covered = [] }: { offsetX?: number; offsetY?: number; covered?: readonly CellRect[] } = {},
79
+ ): string | null {
80
+ const rows: string[] = [];
81
+ const left = Math.max(0, Math.floor(rect.x + offsetX));
82
+ const right = Math.floor(rect.x + offsetX + rect.width);
83
+ const top = Math.floor(rect.y) + 1;
84
+ const bottom = Math.floor(rect.y + rect.height);
85
+ for (let row = top; row < bottom; row += 1) {
86
+ const spans = frame.lines[row + offsetY];
87
+ if (!spans) continue;
88
+ let column = 0;
89
+ let text = "";
90
+ for (const span of spans) {
91
+ // One code point per cell; a wide one is followed by an empty continuation cell.
92
+ for (const char of span.text) {
93
+ const start = column;
94
+ const width = displayWidth(char) >= 2 ? 2 : 1;
95
+ column += width;
96
+ if (start < left || start >= right) continue;
97
+ text += covered.some((other) => covers(other, start - offsetX, row)) ? " ".repeat(width) : char;
98
+ }
99
+ if (column >= right) break;
100
+ }
101
+ rows.push(text);
102
+ }
103
+ return normalizePaneText(rows.join("\n"));
104
+ }
105
+
106
+ function attributeValue(value: string): string {
107
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
108
+ }
109
+
110
+ interface PixelRect {
111
+ left: number;
112
+ top: number;
113
+ right: number;
114
+ bottom: number;
115
+ }
116
+
117
+ function intersect(a: PixelRect, b: PixelRect): PixelRect | null {
118
+ const rect = {
119
+ left: Math.max(a.left, b.left),
120
+ top: Math.max(a.top, b.top),
121
+ right: Math.min(a.right, b.right),
122
+ bottom: Math.min(a.bottom, b.bottom),
123
+ };
124
+ return rect.right > rect.left && rect.bottom > rect.top ? rect : null;
125
+ }
126
+
127
+ /**
128
+ * Where an element's content can be seen: the pane body cut down by every box
129
+ * between them that clips (a scrolled list, a table body), or null when one
130
+ * of them hides it.
131
+ */
132
+ function visibleArea(element: Element, body: HTMLElement, cache: Map<Element, PixelRect | null>): PixelRect | null {
133
+ const cached = cache.get(element);
134
+ if (cached !== undefined) return cached;
135
+ let area: PixelRect | null;
136
+ if (element === body) {
137
+ area = body.getBoundingClientRect();
138
+ } else {
139
+ const parent = element.parentElement;
140
+ area = parent ? visibleArea(parent, body, cache) : null;
141
+ const view = element.ownerDocument.defaultView;
142
+ const style = view?.getComputedStyle(element);
143
+ if (area && style) {
144
+ if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") area = null;
145
+ else if (style.overflowX !== "visible" || style.overflowY !== "visible") area = intersect(area, element.getBoundingClientRect());
146
+ }
147
+ }
148
+ cache.set(element, area);
149
+ return area;
150
+ }
151
+
152
+ /**
153
+ * A desktop or web pane's text as it is on screen: its body, without the
154
+ * header or footer, and only what can be seen, so rows scrolled out of a list
155
+ * and anything clipped stay out. Text on one line is joined in reading order.
156
+ */
157
+ export function domPaneText(paneId: string): string | null {
158
+ const doc = (globalThis as { document?: Document }).document;
159
+ if (!doc) return null;
160
+ const pane = doc.querySelector<HTMLElement>(`[data-gloom-pane-id="${attributeValue(paneId)}"]`);
161
+ const body = pane?.querySelector<HTMLElement>("[data-gloom-role=pane-body]") ?? pane;
162
+ if (!body) return null;
163
+
164
+ const areas = new Map<Element, PixelRect | null>();
165
+ const pieces: Array<{ top: number; bottom: number; left: number; right: number; text: string }> = [];
166
+ const range = doc.createRange();
167
+ const walker = doc.createTreeWalker(body, 4 /* NodeFilter.SHOW_TEXT */);
168
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
169
+ const text = node.textContent?.replace(/\s+/g, " ").trim();
170
+ const parent = node.parentElement;
171
+ if (!text || !parent) continue;
172
+ const area = visibleArea(parent, body, areas);
173
+ if (!area) continue;
174
+ range.selectNodeContents(node);
175
+ const rects = Array.from(range.getClientRects()).filter((rect) => rect.width > 0 && intersect(area, rect));
176
+ const first = rects[0];
177
+ if (!first) continue;
178
+ pieces.push({ top: first.top, bottom: first.bottom, left: first.left, right: first.right, text });
179
+ }
180
+ range.detach?.();
181
+
182
+ // Pieces whose middles share a line read as one, left to right, with a
183
+ // gap where the pane leaves one between them.
184
+ pieces.sort((a, b) => (a.top + a.bottom) / 2 - (b.top + b.bottom) / 2 || a.left - b.left);
185
+ const lines: Array<{ middle: number; height: number; parts: typeof pieces }> = [];
186
+ for (const piece of pieces) {
187
+ const middle = (piece.top + piece.bottom) / 2;
188
+ const line = lines[lines.length - 1];
189
+ if (line && Math.abs(line.middle - middle) < Math.max(4, line.height / 2)) line.parts.push(piece);
190
+ else lines.push({ middle, height: piece.bottom - piece.top, parts: [piece] });
191
+ }
192
+ const text = lines.map((line) => {
193
+ const parts = [...line.parts].sort((a, b) => a.left - b.left);
194
+ return parts.reduce((out, part, index) => {
195
+ if (index === 0) return part.text;
196
+ const gap = part.left - parts[index - 1]!.right;
197
+ return `${out}${gap > 6 ? " " : gap > 0.5 ? " " : ""}${part.text}`;
198
+ }, "");
199
+ }).join("\n");
200
+ return normalizePaneText(text);
201
+ }
@@ -202,7 +202,7 @@ function resolvedOutput(value: unknown): ResolvedSeries {
202
202
  const input = object(value, "resolve output");
203
203
  onlyKeys(input, "resolve output", [
204
204
  "id", "label", "color", "unit", "unitGroup", "nativeFrequency", "timestampMode", "dataShape", "style", "transform",
205
- "axis", "panelId", "interpolation", "timeBasis", "latestChangePercent", "points", "warning", "hidden",
205
+ "axis", "panelId", "interpolation", "timeBasis", "latestChangePercent", "points", "warning", "hidden", "attribution",
206
206
  ]);
207
207
  if (!Array.isArray(input.points)) fail("resolve output points", "expected an array.");
208
208
  if (input.points.length > MAX_CHART_SERIES_POINTS) {
@@ -212,6 +212,9 @@ function resolvedOutput(value: unknown): ResolvedSeries {
212
212
  if (timeBasis) onlyKeys(timeBasis, "resolve output time basis", ["kind", "timeZone", "cadenceMs"]);
213
213
  const kind = timeBasis?.kind === undefined ? undefined : enumValue<"market">(timeBasis.kind, "time basis kind", new Set(["market"]));
214
214
  const warning = optionalString(input.warning, "resolve output warning", 500);
215
+ const attribution = input.attribution === undefined ? undefined : object(input.attribution, "resolve output attribution");
216
+ if (attribution) onlyKeys(attribution, "resolve output attribution", ["source", "publisher"]);
217
+ const publisher = attribution ? optionalString(attribution.publisher, "resolve output publisher", 160) : undefined;
215
218
  return {
216
219
  id: boundedString(input.id, "resolve output ID", 160),
217
220
  label: boundedString(input.label, "resolve output label", 160),
@@ -242,6 +245,10 @@ function resolvedOutput(value: unknown): ResolvedSeries {
242
245
  : {}),
243
246
  points: input.points.map(point),
244
247
  ...(warning !== undefined ? { warning } : {}),
248
+ ...(attribution ? { attribution: {
249
+ source: boundedString(attribution.source, "resolve output source", 160),
250
+ ...(publisher !== undefined ? { publisher } : {}),
251
+ } } : {}),
245
252
  ...(input.hidden !== undefined
246
253
  ? typeof input.hidden === "boolean" ? { hidden: input.hidden } : fail("resolve output hidden", "expected a boolean.")
247
254
  : {}),
@@ -61,6 +61,7 @@ export function assetDataProvider(provider: AssetDataProvider): AssetDataCapabil
61
61
  if (!provider.getCorporateActions) throw new Error(`${provider.name} does not provide corporate actions.`);
62
62
  return provider.getCorporateActions(input.ticker, input.exchange, input.context);
63
63
  }, "query"),
64
+ getTickerChanges: op((input: any) => provider.getTickerChanges?.(input.symbol) ?? Promise.resolve(null), "query"),
64
65
  getEarningsCalendar: op((input: any) => {
65
66
  if (!provider.getEarningsCalendar) throw new Error(`${provider.name} does not provide earnings calendar.`);
66
67
  return provider.getEarningsCalendar(input.symbols ?? [], input.context);
@@ -0,0 +1,23 @@
1
+ import { centralBankNamed } from "../utils/central-banks";
2
+
3
+ /**
4
+ * Central bank acronyms that are also the symbol of a listing people ask
5
+ * about (BOE is a NYSE fund, RBI Raiffeisen Bank in Vienna), so `events BOE`
6
+ * stays that listing's. ECB S.A. in Warsaw is reached as ECB:WSE.
7
+ */
8
+ const LISTED_SYMBOLS = new Set(["BOE", "BOC", "RBA", "PBOC", "RBI", "BOK"]);
9
+
10
+ /**
11
+ * What `events` says for a central bank named on its own ("ECB", "Norges
12
+ * Bank"): its meetings are no company's corporate events, and CBR has its
13
+ * rate and the next decision from the bank's own calendar (China's next LPR
14
+ * fixing; SAMA publishes none). Null for anything else, a symbol with a venue
15
+ * included.
16
+ */
17
+ export function centralBankEventsMessage(typed: string): string | null {
18
+ const bank = centralBankNamed(typed);
19
+ if (!bank || LISTED_SYMBOLS.has(typed.trim().replace(/^\$/, "").toUpperCase())) return null;
20
+ const next = bank.id === "US" ? " and the next FOMC date" : bank.id === "CN" ? " and the next LPR fixing"
21
+ : bank.id === "SA" ? "" : " and the next decision";
22
+ return `${bank.id === "US" ? "Fed" : bank.short} meetings are not listed; CBR shows the rate${next} (gloomberb fn CBR).`;
23
+ }
@@ -4,8 +4,11 @@ import { suggestCliOption } from "./help";
4
4
 
5
5
  /** A built-in command, which may say more about an option it does not take than the list of those it does. */
6
6
  export interface BuiltinCliCommandDef extends CliCommandDef {
7
- /** A sentence for an unknown option people reach for, such as `--from` on `history`; null for the rest. */
8
- unknownOptionHint?(flag: string): string | null;
7
+ /**
8
+ * A sentence for an unknown option people reach for, such as `--from` on
9
+ * `history`; null for the rest. `args` are the command's, to name what it was given.
10
+ */
11
+ unknownOptionHint?(flag: string, args: readonly string[]): string | null;
9
12
  }
10
13
 
11
14
  interface DeclaredOption {
@@ -34,12 +37,21 @@ function declaredOptions(command: CliCommandDef): Map<string, DeclaredOption> |
34
37
  return declared;
35
38
  }
36
39
 
37
- function failUnknownOption(command: BuiltinCliCommandDef, flag: string, declared: Map<string, DeclaredOption>): never {
40
+ /** `--explain` on a command that has no glossary of its own points at the commands that do, and at the glossary. */
41
+ const EXPLAIN_HINT = "--explain follows the reports of fn <function> and portfolio show. "
42
+ + "gloomberb catalog explain <term> prints what one term means; gloomberb catalog glossary lists every term.";
43
+
44
+ function failUnknownOption(
45
+ command: BuiltinCliCommandDef,
46
+ flag: string,
47
+ declared: Map<string, DeclaredOption>,
48
+ args: readonly string[],
49
+ ): never {
38
50
  const flags = [...declared.keys()];
39
51
  const suggestion = suggestCliOption(flag, flags);
40
52
  const help = `See gloomberb help ${command.name}.`;
41
53
  fail(`Unknown option ${flag} for ${command.name}.`, [
42
- command.unknownOptionHint?.(flag) ?? "",
54
+ command.unknownOptionHint?.(flag, args) ?? (flag === "--explain" ? EXPLAIN_HINT : ""),
43
55
  suggestion ? `Did you mean ${suggestion}?` : "",
44
56
  flags.length > 0 ? `Options: ${flags.join(", ")}. ${help}` : `${command.name} takes no options. ${help}`,
45
57
  ].filter(Boolean).join("\n"));
@@ -65,7 +77,7 @@ export function checkCliCommandOptions(
65
77
  const equals = arg.indexOf("=");
66
78
  const flag = equals >= 0 ? arg.slice(0, equals) : arg;
67
79
  const option = declared.get(flag);
68
- if (!option) failUnknownOption(command, flag, declared);
80
+ if (!option) failUnknownOption(command, flag, declared, args);
69
81
  if (!option.takesValue && equals >= 0) {
70
82
  fail(`${flag} takes no value.`, `Write it alone, as ${flag}. See gloomberb help ${command.name}.`);
71
83
  }
@@ -3,6 +3,8 @@ import { isCryptoVenuePerpetual } from "../../tickers/instrument-kind";
3
3
  import type { Quote } from "../../types/financials";
4
4
  import { isCryptoPairSymbol } from "../../utils/crypto-pair";
5
5
  import { isNotATickerMessage } from "../not-a-ticker";
6
+ import { groupByCompany } from "../../tickers/company-name";
7
+ import type { TickerRecord } from "../../types/ticker";
6
8
 
7
9
  /** Where a crypto pair that will not load can still be read. `fn CRYP` takes no argument. */
8
10
  export const CRYPTO_BOARD_HINT = "Crypto prices may be briefly unavailable; the CRYP function (gloomberb fn CRYP) shows the crypto board.";
@@ -27,9 +29,10 @@ const MAJOR_COINS = new Map([
27
29
  /**
28
30
  * A bare coin ticker usually resolves to a listed fund, not the coin. The note applies only when
29
31
  * the listing is not crypto and its own name says the coin (a "Bitcoin" trust), so a company that
30
- * happens to share the letters (Interlink Electronics on LINK) is left alone.
32
+ * happens to share the letters (Interlink Electronics on LINK) is left alone. Every command that
33
+ * reads a bare symbol as a listing says it: quote, ticker, and the portfolio and watchlist adds.
31
34
  */
32
- function notTheCoinNote(symbol: string, quote: Pick<Quote, "name" | "instrumentType"> | null | undefined): string | null {
35
+ export function notTheCoinNote(symbol: string, quote: Pick<Quote, "name" | "instrumentType"> | null | undefined): string | null {
33
36
  const coin = MAJOR_COINS.get(symbol);
34
37
  const type = quote?.instrumentType?.trim();
35
38
  const name = quote?.name?.trim();
@@ -38,6 +41,29 @@ function notTheCoinNote(symbol: string, quote: Pick<Quote, "name" | "instrumentT
38
41
  return `${symbol} is ${name}, not ${coin}. For ${coin} use ${symbol}-USD.`;
39
42
  }
40
43
 
44
+ /** `notTheCoinNote` for a saved listing, by the name and asset category it was saved with. */
45
+ export function savedListingCoinNote(symbol: string, ticker: Pick<TickerRecord, "metadata">): string | null {
46
+ return notTheCoinNote(symbol, { name: ticker.metadata.name, instrumentType: ticker.metadata.assetCategory });
47
+ }
48
+
49
+ /**
50
+ * A bare coin ticker several companies list asks which listing; when none of them is the coin
51
+ * (SOL: Solvay, Washington H. Soul Pattinson), the coin is where the user may have meant:
52
+ * "SOL is Solvay SA or Washington H. Soul Pattinson and Company Limited, not solana. For
53
+ * solana use SOL-USD." Null for any other symbol, or when a listing is a crypto pair.
54
+ */
55
+ export function coinElsewhereNote(symbol: string, listings: ReadonlyArray<{ key: string; name: string }>): string | null {
56
+ const coin = MAJOR_COINS.get(symbol);
57
+ if (!coin || listings.some((listing) => isCryptoPairSymbol(listing.key))) return null;
58
+ const names = groupByCompany(listings.filter((listing) => listing.name.trim()), (listing) => listing.name.trim())
59
+ .map((group) => group[0]!.name.trim());
60
+ if (names.length === 0) return null;
61
+ const named = names.length === 1 ? names[0]!
62
+ : names.length === 2 ? `${names[0]} or ${names[1]}`
63
+ : `${names[0]}, ${names[1]} or another company`;
64
+ return `${symbol} is ${named}, not ${coin}. For ${coin} use ${symbol}-USD.`;
65
+ }
66
+
41
67
  interface QuoteNoteResult {
42
68
  target: { symbol: string };
43
69
  quote: Pick<Quote, "name" | "instrumentType"> | null;
@@ -1,4 +1,6 @@
1
+ import { ICE_SPREAD_SERIES } from "../../time-series/ice-credit";
1
2
  import { toBasisPoints } from "../../utils/basis-points";
3
+ import { parseReportTime } from "../../utils/utc-time";
2
4
 
3
5
  /** Where `fred` starts when `--start` is not given. */
4
6
  export const DEFAULT_FRED_START = "2021-01-01";
@@ -9,16 +11,125 @@ export const DEFAULT_FRED_START = "2021-01-01";
9
11
  */
10
12
  const SPREAD_SERIES: ReadonlySet<string> = new Set([
11
13
  "T10Y2Y", "T10Y3M",
12
- "BAMLC0A0CM", "BAMLC0A1CAAA", "BAMLC0A2CAA", "BAMLC0A3CA", "BAMLC0A4CBBB", "BAMLH0A0HYM2",
14
+ ...ICE_SPREAD_SERIES.map((series) => series.seriesId),
13
15
  ]);
14
16
 
15
17
  export function isSpreadSeries(seriesId: string): boolean {
16
18
  return SPREAD_SERIES.has(seriesId.toUpperCase());
17
19
  }
18
20
 
19
- /** A spread in percentage points as the bp a text table prints: 0.44 is `44`, -0.125 is `-12.5`. */
20
- export function spreadBasisPointsCell(value: unknown): string {
21
- return typeof value === "number" && Number.isFinite(value) ? String(toBasisPoints(value, 1)) : "";
21
+ /**
22
+ * The unit a series' observations export in, for the `currency_or_unit`
23
+ * column beside its bare `value`: `bp` for a spread (exported in basis
24
+ * points), `pct` for a percent, otherwise FRED's own units, with "annual
25
+ * rate" as the text header says it. Null when FRED's metadata did not come
26
+ * back.
27
+ */
28
+ export function fredUnit(seriesId: string, info: { units?: string | null; seasonalAdjustment?: string | null } | null | undefined): string | null {
29
+ if (isSpreadSeries(seriesId)) return "bp";
30
+ const units = info?.units?.trim();
31
+ if (!units) return null;
32
+ if (/^percent$/i.test(units)) return "pct";
33
+ const annualRate = /annual rate/i.test(info?.seasonalAdjustment ?? "") && !/annual rate/i.test(units);
34
+ return `${units}${annualRate ? ", annual rate" : ""}`;
35
+ }
36
+
37
+ /** How often a series observes, in FRED's word lower-cased: `Weekly, Ending Friday` is `weekly`. Null when FRED does not say. */
38
+ export function fredInterval(frequency: string | null | undefined): string | null {
39
+ const word = frequency?.split(",")[0]?.trim().toLowerCase();
40
+ return word || null;
41
+ }
42
+
43
+ /**
44
+ * The observations from the series' first one on: Gloom Cloud answers a
45
+ * `--start` before a series begins with a blank row for every day in between
46
+ * (DEXSFUS from 1975 has five years of them before 1980-01-02). FRED's
47
+ * `observationStart` says where it begins; without it the blank rows before
48
+ * the first value go. Blank rows after it (a holiday's ".") stay.
49
+ */
50
+ export function fredObservationsFromStart<T extends { date: string; value: number | null }>(
51
+ observations: readonly T[],
52
+ seriesStart: string | null | undefined,
53
+ ): T[] {
54
+ const first = seriesStart && /^\d{4}-\d{2}-\d{2}$/.test(seriesStart)
55
+ ? seriesStart
56
+ : observations.reduce<string | null>((earliest, observation) => (
57
+ observation.value != null && Number.isFinite(observation.value) && (!earliest || observation.date < earliest) ? observation.date : earliest
58
+ ), null);
59
+ return first ? observations.filter((observation) => observation.date >= first) : [...observations];
60
+ }
61
+
62
+ /**
63
+ * The value column's header: FRED's units, and "annual rate" when a monthly or
64
+ * quarterly flow is stated at a seasonally adjusted annual rate (GDP, FDEFX),
65
+ * so a quarter's 1,100 billion does not read as spent in that quarter.
66
+ */
67
+ export function fredValueHeader(info: { units?: string | null; seasonalAdjustment?: string | null } | null | undefined): string {
68
+ const units = info?.units?.trim();
69
+ if (!units) return "Value";
70
+ const annualRate = /annual rate/i.test(info?.seasonalAdjustment ?? "") && !/annual rate/i.test(units);
71
+ return `Value (${units}${annualRate ? ", annual rate" : ""})`;
72
+ }
73
+
74
+ /** An observation in the unit it exports in: a spread's percentage points as basis points (3.15 is 315), anything else as FRED gives it. */
75
+ export function fredExportValue(seriesId: string, value: number | null): number | null {
76
+ if (value == null || !Number.isFinite(value)) return null;
77
+ return isSpreadSeries(seriesId) ? toBasisPoints(value, 4) : value;
78
+ }
79
+
80
+ /** Basis points as a text table prints them, to a tenth: 44 is `44`, -12.5 is `-12.5`. */
81
+ export function basisPointsCell(value: unknown): string {
82
+ if (typeof value !== "number" || !Number.isFinite(value)) return "";
83
+ const rounded = Math.round(value * 10) / 10;
84
+ return String(rounded === 0 ? 0 : rounded);
85
+ }
86
+
87
+ /**
88
+ * When FRED last updated a series, its `last_updated` (`2026-10-09 08:08:04-05`),
89
+ * as an ISO instant in UTC: the publication time an export cites. Only when
90
+ * that update covers the newest observation shown (`observationEnd` reaches
91
+ * it): metadata from before it says nothing about when it was published.
92
+ * Null when it does not, or when FRED's metadata does not say.
93
+ */
94
+ export function fredPublicationTime(
95
+ info: { lastUpdated?: string | null; observationEnd?: string | null } | null | undefined,
96
+ newestDate: string | null | undefined,
97
+ ): string | null {
98
+ if (newestDate && !(info?.observationEnd && info.observationEnd >= newestDate)) return null;
99
+ const iso = (info?.lastUpdated ?? "").trim().replace(/^(\d{4}-\d{2}-\d{2}) /, "$1T").replace(/([+-]\d{2})$/, "$1:00");
100
+ const time = parseReportTime(iso);
101
+ return time && !time.dateOnly ? new Date(time.time).toISOString() : null;
102
+ }
103
+
104
+ /** Where a FRED report's figures come from, as its source line names them. */
105
+ export interface FredProvenance {
106
+ source: string;
107
+ publisher?: string;
108
+ publishedAt?: string;
109
+ }
110
+
111
+ /**
112
+ * Where the newest of these observations comes from: FRED, unless Gloom
113
+ * Cloud carried a value FRED does not list yet and named its publisher. Then
114
+ * the source line names that publisher and when it published, so a reader
115
+ * looking for the figure in FRED's own data knows it is not there yet:
116
+ * `U.S. Treasury via FRED, published 2026-10-09`. Null for FRED's own.
117
+ */
118
+ export function fredProvenance(observations: ReadonlyArray<{ date: string; publisher?: string; publishedAt?: string }>): FredProvenance | null {
119
+ const ahead = observations.filter((observation) => observation.publisher?.trim());
120
+ if (ahead.length === 0) return null;
121
+ const publishers = [...new Set(ahead.map((observation) => observation.publisher!.trim()))];
122
+ const times = ahead.flatMap((observation) => {
123
+ const time = parseReportTime(observation.publishedAt);
124
+ return time ? [new Date(time.time).toISOString()] : [];
125
+ }).sort();
126
+ const publishedAt = times.at(-1);
127
+ const publisher = publishers.join(" and ");
128
+ return {
129
+ source: `${publisher} via FRED${publishedAt ? `, published ${publishedAt.slice(0, 10)}` : ""}`,
130
+ publisher,
131
+ ...(publishedAt ? { publishedAt } : {}),
132
+ };
22
133
  }
23
134
 
24
135
  /**
@@ -59,3 +170,43 @@ export function fredEmptyMessage(startDate: string, latest: { date: string; valu
59
170
  ? `Latest observation: ${latest.date}${latest.value == null ? "" : ` (${latest.value})`}. Nothing on or after ${startDate}.`
60
171
  : "No results.";
61
172
  }
173
+
174
+ /** What an export of nothing on or after `--start` says to try instead: a start that reaches the latest observation. */
175
+ export function fredEmptyAdvice(seriesId: string, latest: { date: string } | null): string {
176
+ return latest
177
+ ? `Latest observation: ${latest.date}. Try: gloomberb fred ${seriesId} --start ${latest.date.slice(0, 4)}`
178
+ : `No observations came back for ${seriesId}. Try an earlier --start, or gloomberb fred --list for the series served.`;
179
+ }
180
+
181
+ /** A series as `fred --list` shows it. */
182
+ export interface FredListRow {
183
+ id: string;
184
+ title: string;
185
+ group: string;
186
+ }
187
+
188
+ /** How well a word of the filter matches a series, best first: its id or group name, a word of its group or title, the start of its id, then any text. */
189
+ function matchTier(term: string, row: FredListRow, words: readonly string[]): number {
190
+ if (row.id.toLowerCase() === term || row.group.toLowerCase() === term) return 0;
191
+ if (words.some((word) => word.startsWith(term))) return 1;
192
+ if (row.id.toLowerCase().startsWith(term)) return 2;
193
+ return 3;
194
+ }
195
+
196
+ /**
197
+ * The series that carry every word of the filter in their id, title or group,
198
+ * the ones it names first: a group or a word of the title ahead of the id that
199
+ * only has it inside (`fx` is the FX group, not FDEFX, whose id spells it).
200
+ * Catalog order breaks ties; an empty filter keeps the catalog as it is.
201
+ */
202
+ export function filterFredSeries(rows: readonly FredListRow[], filter: string): FredListRow[] {
203
+ const terms = filter.toLowerCase().split(/\s+/).filter(Boolean);
204
+ const ranked = rows.flatMap((row, index) => {
205
+ const text = `${row.id} ${row.title} ${row.group}`.toLowerCase();
206
+ if (!terms.every((term) => text.includes(term))) return [];
207
+ const words = `${row.title} ${row.group}`.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
208
+ const tiers = terms.map((term) => matchTier(term, row, words));
209
+ return [{ row, index, worst: Math.max(0, ...tiers), total: tiers.reduce((sum, tier) => sum + tier, 0) }];
210
+ });
211
+ return ranked.sort((a, b) => a.worst - b.worst || a.total - b.total || a.index - b.index).map((entry) => entry.row);
212
+ }