gloomberb 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/package.json +4 -1
  2. package/src/api-client/askg.ts +43 -0
  3. package/src/api-client/auth.ts +15 -0
  4. package/src/api-client/beneficial-owners.ts +4 -0
  5. package/src/api-client/chat.ts +19 -0
  6. package/src/api-client/company-kpis.ts +14 -2
  7. package/src/api-client/data.ts +34 -16
  8. package/src/api-client/index.ts +12 -0
  9. package/src/api-client/normalizers.ts +27 -0
  10. package/src/api-client/paths.ts +88 -23
  11. package/src/api-client/request.ts +2 -1
  12. package/src/api-client/research-activity.ts +31 -0
  13. package/src/api-client/socket.ts +39 -8
  14. package/src/api-client/types.ts +57 -1
  15. package/src/api-client/upgrade-experiment.ts +90 -0
  16. package/src/app/global-shortcuts.ts +11 -1
  17. package/src/app/keybindings/actions.ts +10 -0
  18. package/src/app/pane-runtime/index.tsx +8 -0
  19. package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
  20. package/src/app/pane-runtime/plugin-bindings.ts +65 -25
  21. package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
  22. package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
  23. package/src/app/runtime/desktop-deeplink.ts +5 -0
  24. package/src/app/star-prompt/model.ts +98 -0
  25. package/src/app/star-prompt/runtime.ts +41 -0
  26. package/src/app/star-prompt/status-line.tsx +190 -0
  27. package/src/app.tsx +13 -7
  28. package/src/architecture/core-builtin-allowlist.json +70 -0
  29. package/src/brokers/account-cache.ts +45 -0
  30. package/src/brokers/signed-in/adapter.ts +33 -2
  31. package/src/brokers/signed-in/client.ts +10 -0
  32. package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
  33. package/src/brokers/signed-in/sign-in.ts +62 -5
  34. package/src/capabilities/registry.ts +8 -1
  35. package/src/capabilities/types.ts +2 -0
  36. package/src/cli/command-options.ts +74 -0
  37. package/src/cli/commands/command-utils.ts +29 -0
  38. package/src/cli/commands/crypto-hints.ts +12 -4
  39. package/src/cli/commands/market.ts +524 -148
  40. package/src/cli/commands/options-chain.ts +172 -0
  41. package/src/cli/commands/options-leaps.ts +422 -0
  42. package/src/cli/commands/overview.ts +223 -42
  43. package/src/cli/commands/search.ts +29 -8
  44. package/src/cli/commands/system.ts +99 -19
  45. package/src/cli/commands/themes.ts +64 -0
  46. package/src/cli/commands/ticker.ts +200 -73
  47. package/src/cli/desktop-pane-shot.ts +100 -16
  48. package/src/cli/errors.ts +25 -2
  49. package/src/cli/freshness.ts +7 -4
  50. package/src/cli/fx-pair.ts +57 -0
  51. package/src/cli/help.ts +40 -7
  52. package/src/cli/history-rows.ts +187 -15
  53. package/src/cli/index-roots.ts +51 -0
  54. package/src/cli/index.ts +21 -13
  55. package/src/cli/listing-arg.ts +211 -3
  56. package/src/cli/not-a-ticker.ts +37 -0
  57. package/src/cli/options.ts +55 -7
  58. package/src/cli/pane-functions/capabilities.ts +13 -2
  59. package/src/cli/pane-functions/catalog.ts +13 -2
  60. package/src/cli/pane-functions/data.ts +36 -5
  61. package/src/cli/pane-functions/dom.ts +89 -28
  62. package/src/cli/pane-functions/freshness.ts +205 -26
  63. package/src/cli/pane-functions/function-help.ts +71 -24
  64. package/src/cli/pane-functions/headless.ts +168 -35
  65. package/src/cli/pane-functions/index.ts +12 -2
  66. package/src/cli/pane-functions/market-session.ts +133 -0
  67. package/src/cli/pane-functions/options.ts +15 -4
  68. package/src/cli/pane-functions/report-notices.ts +63 -0
  69. package/src/cli/pane-functions/report.ts +12 -1
  70. package/src/cli/pane-functions/resolver.ts +32 -8
  71. package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
  72. package/src/cli/pane-functions/screenshot.ts +109 -23
  73. package/src/cli/registry.ts +8 -4
  74. package/src/cli/report-tables.ts +460 -0
  75. package/src/cli/result.ts +63 -16
  76. package/src/cli/row-window.ts +76 -0
  77. package/src/cli/scroll-truncation.ts +34 -0
  78. package/src/components/chart/composite/composite-chart.tsx +43 -4
  79. package/src/components/chart/composite/legend.tsx +39 -6
  80. package/src/components/chart/composite/rasterizer.ts +64 -4
  81. package/src/components/chart/composite/series-cues.ts +71 -0
  82. package/src/components/chart/composite/text-renderer.ts +16 -8
  83. package/src/components/chart/composite/types.ts +3 -0
  84. package/src/components/command-bar/commands/direct/index.ts +9 -1
  85. package/src/components/command-bar/commands/plugin/items.ts +3 -1
  86. package/src/components/command-bar/commands/registry.ts +7 -0
  87. package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
  88. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  89. package/src/components/command-bar/routes/root/command-items.ts +2 -0
  90. package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
  91. package/src/components/command-bar/routes/root/results.ts +40 -10
  92. package/src/components/command-bar/routes/root/runtime.ts +8 -0
  93. package/src/components/command-bar/routes/root/search-providers.ts +97 -14
  94. package/src/components/command-bar/surface/index.tsx +18 -4
  95. package/src/components/command-bar/theme-picker.tsx +48 -13
  96. package/src/components/command-bar/workflow/ops.ts +5 -3
  97. package/src/components/data-table/export.ts +6 -51
  98. package/src/components/data-table/view.tsx +31 -10
  99. package/src/components/layout/floating-pane.tsx +6 -2
  100. package/src/components/layout/market-summary.ts +23 -9
  101. package/src/components/layout/pane/content.tsx +46 -29
  102. package/src/components/layout/pane/cycle-order.ts +2 -11
  103. package/src/components/layout/pane/header.tsx +2 -1
  104. package/src/components/layout/pane/title.ts +1 -1
  105. package/src/components/layout/presentation.ts +22 -0
  106. package/src/components/layout/shell/active-drag.ts +67 -35
  107. package/src/components/layout/shell/cursor-occlusion.ts +6 -5
  108. package/src/components/layout/shell/drag/index.ts +16 -7
  109. package/src/components/layout/shell/drag/live.ts +193 -0
  110. package/src/components/layout/shell/drag/overlays.tsx +179 -35
  111. package/src/components/layout/shell/drag/runtime.ts +62 -50
  112. package/src/components/layout/shell/drag/slide.ts +10 -0
  113. package/src/components/layout/shell/index.tsx +84 -57
  114. package/src/components/layout/shell/layout-state.ts +5 -11
  115. package/src/components/layout/shell/menu.ts +28 -1
  116. package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
  117. package/src/components/layout/shell/native/surfaces.ts +30 -14
  118. package/src/components/layout/shell/pane/actions.ts +2 -1
  119. package/src/components/layout/shell/pane/layers.tsx +461 -180
  120. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  121. package/src/components/layout/shell/pane/new-layout.ts +116 -0
  122. package/src/components/layout/shell/shortcuts.ts +5 -2
  123. package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
  124. package/src/components/layout/shell/visible-layout.ts +44 -1
  125. package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
  126. package/src/components/layout/status-bar.tsx +4 -1
  127. package/src/components/markdown-text.tsx +9 -5
  128. package/src/components/metric-treemap/heat-surface.tsx +27 -8
  129. package/src/components/metric-treemap/heat-tiles.ts +48 -17
  130. package/src/components/onboarding/onboarding-frame.tsx +33 -10
  131. package/src/components/onboarding/onboarding-wizard.tsx +49 -40
  132. package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
  133. package/src/components/onboarding/pro-step-layout.ts +86 -0
  134. package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
  135. package/src/components/ui/button.tsx +10 -0
  136. package/src/components/ui/data-table/index.tsx +38 -0
  137. package/src/components/ui/data-table/types.ts +8 -1
  138. package/src/components/ui/data-table/visible-range.ts +2 -0
  139. package/src/components/ui/icon.tsx +11 -2
  140. package/src/components/ui/index.ts +1 -1
  141. package/src/core/connection-health.ts +1 -1
  142. package/src/core/state/app/layout-reducer.ts +89 -3
  143. package/src/core/state/app/state.ts +8 -5
  144. package/src/core/state/app/types.ts +13 -2
  145. package/src/data/config/layout.ts +55 -21
  146. package/src/data/config/store/fold-panes.ts +149 -0
  147. package/src/data/config/store/migrations.ts +7 -0
  148. package/src/data/config/store/node.ts +27 -6
  149. package/src/data/config/store/normalize.ts +37 -3
  150. package/src/data/json-persistence.ts +17 -3
  151. package/src/data/json-storage.ts +2 -0
  152. package/src/data/memory-resource-store.ts +46 -0
  153. package/src/i18n/es.ts +14 -1
  154. package/src/i18n/ja.ts +14 -1
  155. package/src/i18n/ko.ts +14 -1
  156. package/src/i18n/zh-cn.ts +14 -1
  157. package/src/i18n/zh-tw.ts +14 -1
  158. package/src/layout/desks.ts +1 -2
  159. package/src/layout/pane-layout-move.ts +264 -0
  160. package/src/layout/pane-manager/hidden-panes.ts +160 -0
  161. package/src/layout/pane-manager/index.ts +1 -0
  162. package/src/market-data/coordinator/entries.ts +7 -1
  163. package/src/market-data/coordinator/quotes.ts +2 -2
  164. package/src/market-data/market/freshness.ts +156 -11
  165. package/src/market-data/market/status.ts +40 -0
  166. package/src/market-data/options-alternatives.ts +69 -0
  167. package/src/market-data/quotes/contributions.ts +19 -2
  168. package/src/market-data/quotes/freshness.ts +5 -0
  169. package/src/market-data/quotes/metadata.ts +16 -0
  170. package/src/market-data/quotes/screener-live-quotes.ts +56 -5
  171. package/src/plugins/builtin/account-management/keyboard.ts +3 -0
  172. package/src/plugins/builtin/account-management/model.ts +1 -0
  173. package/src/plugins/builtin/account-management/navigation.ts +1 -1
  174. package/src/plugins/builtin/account-management/pane.tsx +31 -4
  175. package/src/plugins/builtin/alerts/quotes.ts +3 -0
  176. package/src/plugins/builtin/analytics/pane-model.ts +10 -6
  177. package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
  178. package/src/plugins/builtin/analytics/risk-model.ts +37 -8
  179. package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
  180. package/src/plugins/builtin/attention/pane.tsx +1 -1
  181. package/src/plugins/builtin/backtest/pane.tsx +1 -1
  182. package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
  183. package/src/plugins/builtin/broker-manager/index.tsx +7 -3
  184. package/src/plugins/builtin/broker-manager/model.ts +8 -0
  185. package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
  186. package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
  187. package/src/plugins/builtin/broker-manager/table.ts +5 -0
  188. package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
  189. package/src/plugins/builtin/catalysts/detail.tsx +1 -1
  190. package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
  191. package/src/plugins/builtin/chat/channels.ts +34 -36
  192. package/src/plugins/builtin/chat/command-bar.ts +138 -0
  193. package/src/plugins/builtin/chat/composer-commands.ts +21 -1
  194. package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
  195. package/src/plugins/builtin/chat/content/footer.tsx +5 -3
  196. package/src/plugins/builtin/chat/content/index.tsx +21 -1
  197. package/src/plugins/builtin/chat/content/mentions.ts +2 -1
  198. package/src/plugins/builtin/chat/content/user-map.ts +3 -1
  199. package/src/plugins/builtin/chat/controller/index.ts +48 -0
  200. package/src/plugins/builtin/chat/conversations.ts +256 -0
  201. package/src/plugins/builtin/chat/discord-command.ts +91 -0
  202. package/src/plugins/builtin/chat/edit-window.ts +12 -6
  203. package/src/plugins/builtin/chat/ghost-user.ts +14 -0
  204. package/src/plugins/builtin/chat/index.tsx +40 -2
  205. package/src/plugins/builtin/chat/layout.ts +2 -0
  206. package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
  207. package/src/plugins/builtin/chat/message/header.tsx +24 -15
  208. package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
  209. package/src/plugins/builtin/chat/message/render-state.ts +3 -0
  210. package/src/plugins/builtin/chat/pane-requests.ts +60 -0
  211. package/src/plugins/builtin/chat/pane-state.ts +32 -2
  212. package/src/plugins/builtin/chat/pane.tsx +8 -0
  213. package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
  214. package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
  215. package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
  216. package/src/plugins/builtin/cloud/askg/model.ts +129 -70
  217. package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
  218. package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
  219. package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
  220. package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
  221. package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
  222. package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
  223. package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
  224. package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
  225. package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
  226. package/src/plugins/builtin/cloud/plugin.tsx +36 -1
  227. package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
  228. package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
  229. package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
  230. package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
  231. package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
  232. package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
  233. package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
  234. package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
  235. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
  236. package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
  237. package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
  238. package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
  239. package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
  240. package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
  241. package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
  242. package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
  243. package/src/plugins/builtin/comment-letters/client.ts +1 -3
  244. package/src/plugins/builtin/company-attention/pane.tsx +1 -1
  245. package/src/plugins/builtin/company-kpis/client.ts +23 -8
  246. package/src/plugins/builtin/company-kpis/headless.ts +3 -1
  247. package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
  248. package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
  249. package/src/plugins/builtin/composite-plugins.ts +107 -64
  250. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
  251. package/src/plugins/builtin/credit-boards/index.tsx +21 -21
  252. package/src/plugins/builtin/credit-boards/model.ts +0 -1
  253. package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
  254. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
  255. package/src/plugins/builtin/debt-maturities/client.ts +37 -9
  256. package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
  257. package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
  258. package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
  259. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  260. package/src/plugins/builtin/earnings-calls/data.ts +28 -7
  261. package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
  262. package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
  263. package/src/plugins/builtin/econ/calendar-model.ts +42 -17
  264. package/src/plugins/builtin/econ/detail-view.tsx +2 -2
  265. package/src/plugins/builtin/econ/index.tsx +68 -22
  266. package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
  267. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  268. package/src/plugins/builtin/econ-statistics/view.ts +17 -0
  269. package/src/plugins/builtin/executives/data.ts +17 -6
  270. package/src/plugins/builtin/executives/pane.tsx +16 -7
  271. package/src/plugins/builtin/filing-events/pane.tsx +22 -5
  272. package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
  273. package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
  274. package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
  275. package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
  276. package/src/plugins/builtin/gpu/headless.ts +9 -3
  277. package/src/plugins/builtin/gpu/model.ts +14 -5
  278. package/src/plugins/builtin/help/index.tsx +1 -0
  279. package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
  280. package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
  281. package/src/plugins/builtin/holders/client.ts +3 -6
  282. package/src/plugins/builtin/holders/format.ts +31 -1
  283. package/src/plugins/builtin/holders/headless.ts +32 -6
  284. package/src/plugins/builtin/holders/pane.tsx +25 -13
  285. package/src/plugins/builtin/holders/report-header.ts +46 -5
  286. package/src/plugins/builtin/insider/index.tsx +4 -4
  287. package/src/plugins/builtin/iv-history/charts.tsx +21 -7
  288. package/src/plugins/builtin/iv-history/client.ts +12 -2
  289. package/src/plugins/builtin/iv-history/format.ts +33 -2
  290. package/src/plugins/builtin/iv-history/headless.ts +22 -10
  291. package/src/plugins/builtin/iv-history/index.tsx +3 -3
  292. package/src/plugins/builtin/iv-history/model.ts +61 -13
  293. package/src/plugins/builtin/iv-history/pane.tsx +11 -3
  294. package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
  295. package/src/plugins/builtin/jobs/pane.tsx +1 -1
  296. package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
  297. package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
  298. package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
  299. package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
  300. package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
  301. package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
  302. package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
  303. package/src/plugins/builtin/layout-manager/index.tsx +9 -1
  304. package/src/plugins/builtin/macro-day/client.ts +24 -0
  305. package/src/plugins/builtin/macro-day/headless.ts +11 -8
  306. package/src/plugins/builtin/macro-day/index.tsx +3 -0
  307. package/src/plugins/builtin/macro-day/model.ts +11 -6
  308. package/src/plugins/builtin/macro-day/pane.tsx +12 -8
  309. package/src/plugins/builtin/macro-day/release-table.ts +40 -0
  310. package/src/plugins/builtin/macro-day/releases.ts +62 -44
  311. package/src/plugins/builtin/market-heatmap/data.ts +25 -5
  312. package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
  313. package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
  314. package/src/plugins/builtin/market-heatmap/model.ts +94 -3
  315. package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
  316. package/src/plugins/builtin/market-movers/headless.ts +31 -4
  317. package/src/plugins/builtin/market-movers/index.tsx +8 -1
  318. package/src/plugins/builtin/market-movers/model.ts +71 -17
  319. package/src/plugins/builtin/market-movers/table.ts +50 -23
  320. package/src/plugins/builtin/members/pane.tsx +10 -0
  321. package/src/plugins/builtin/news/headless.ts +9 -2
  322. package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
  323. package/src/plugins/builtin/openfda/client.ts +1 -1
  324. package/src/plugins/builtin/options/carry.ts +95 -0
  325. package/src/plugins/builtin/options/index.tsx +58 -5
  326. package/src/plugins/builtin/options/strike-window.ts +183 -0
  327. package/src/plugins/builtin/options/table.ts +55 -3
  328. package/src/plugins/builtin/options/types.ts +8 -1
  329. package/src/plugins/builtin/options/view.tsx +161 -12
  330. package/src/plugins/builtin/options-positioning/headless.ts +4 -2
  331. package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
  332. package/src/plugins/builtin/options-scenario/client.ts +73 -17
  333. package/src/plugins/builtin/options-scenario/headless.ts +71 -24
  334. package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
  335. package/src/plugins/builtin/options-scenario/index.tsx +9 -2
  336. package/src/plugins/builtin/options-scenario/model.ts +33 -0
  337. package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
  338. package/src/plugins/builtin/pe-band/model.ts +2 -2
  339. package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
  340. package/src/plugins/builtin/perps/equity-row.tsx +8 -5
  341. package/src/plugins/builtin/perps/model.ts +16 -5
  342. package/src/plugins/builtin/perps/pane.tsx +1 -1
  343. package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
  344. package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
  345. package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
  346. package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
  347. package/src/plugins/builtin/plugin-module.ts +87 -4
  348. package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
  349. package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
  350. package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
  351. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
  352. package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
  353. package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
  354. package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
  355. package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
  356. package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
  357. package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
  358. package/src/plugins/builtin/realized-vol/headless.ts +3 -0
  359. package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
  360. package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
  361. package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
  362. package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
  363. package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
  364. package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
  365. package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
  366. package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
  367. package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
  368. package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
  369. package/src/plugins/builtin/risk-factors/data.ts +23 -9
  370. package/src/plugins/builtin/risk-factors/headless.ts +5 -2
  371. package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
  372. package/src/plugins/builtin/seasonality/pane.tsx +1 -1
  373. package/src/plugins/builtin/sec/index.tsx +5 -5
  374. package/src/plugins/builtin/sectors/client.ts +10 -2
  375. package/src/plugins/builtin/sectors/headless.ts +35 -6
  376. package/src/plugins/builtin/sectors/index.tsx +18 -1
  377. package/src/plugins/builtin/sectors/sector-model.ts +150 -41
  378. package/src/plugins/builtin/shared/command-tab.ts +51 -0
  379. package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
  380. package/src/plugins/builtin/shared/members-menu.tsx +260 -0
  381. package/src/plugins/builtin/shared/report-freshness.ts +50 -2
  382. package/src/plugins/builtin/shared/ticker-request.ts +25 -1
  383. package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
  384. package/src/plugins/builtin/shared/volatility/options.ts +1 -46
  385. package/src/plugins/builtin/short-interest/index.tsx +19 -4
  386. package/src/plugins/builtin/short-interest/surface.tsx +4 -4
  387. package/src/plugins/builtin/short-volume/index.tsx +1 -10
  388. package/src/plugins/builtin/short-volume/pane.tsx +1 -1
  389. package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
  390. package/src/plugins/builtin/supply-chain/model.ts +1 -1
  391. package/src/plugins/builtin/themes/pane.tsx +3 -0
  392. package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
  393. package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
  394. package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
  395. package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
  396. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
  397. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
  398. package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
  399. package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
  400. package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
  401. package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
  402. package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
  403. package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
  404. package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
  405. package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
  406. package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
  407. package/src/plugins/builtin/vol-surface/client.ts +6 -2
  408. package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
  409. package/src/plugins/builtin/vol-surface/headless.ts +124 -20
  410. package/src/plugins/builtin/vol-surface/model.ts +23 -11
  411. package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
  412. package/src/plugins/builtin/vol-surface/tables.ts +124 -0
  413. package/src/plugins/builtin/world-indices/client.ts +22 -4
  414. package/src/plugins/builtin/world-indices/headless.ts +54 -27
  415. package/src/plugins/builtin/world-indices/model.ts +4 -2
  416. package/src/plugins/builtin/world-indices/table.tsx +13 -2
  417. package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
  418. package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
  419. package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
  420. package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
  421. package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
  422. package/src/plugins/builtin/world-venue-map/index.ts +6 -6
  423. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
  424. package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
  425. package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
  426. package/src/plugins/builtin/world-venue-map/model.ts +16 -0
  427. package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
  428. package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
  429. package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
  430. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  431. package/src/plugins/builtin-editorial.ts +126 -0
  432. package/src/plugins/catalog-backend.ts +16 -9
  433. package/src/plugins/catalog-browser.ts +127 -63
  434. package/src/plugins/catalog-manifest.ts +73 -40
  435. package/src/plugins/catalog-ui.ts +26 -6
  436. package/src/plugins/extracted-plugins.ts +7 -1
  437. package/src/plugins/ownership.ts +150 -7
  438. package/src/plugins/plugin-functions.ts +54 -0
  439. package/src/plugins/plugin-off.ts +36 -0
  440. package/src/plugins/registry/host-actions.ts +16 -0
  441. package/src/plugins/registry/index.ts +84 -24
  442. package/src/plugins/registry/plugin-state.ts +25 -1
  443. package/src/plugins/runtime/context.tsx +29 -0
  444. package/src/react/input.ts +48 -1
  445. package/src/remote/app-host.tsx +5 -0
  446. package/src/remote/controller.ts +3 -53
  447. package/src/remote/matching.ts +69 -0
  448. package/src/remote/schema.ts +1 -1
  449. package/src/remote/semantic-tree.tsx +14 -0
  450. package/src/remote/types.ts +2 -0
  451. package/src/renderers/browser/app-services.ts +3 -1
  452. package/src/renderers/dom/data-table/dom.ts +31 -0
  453. package/src/renderers/dom/data-table/index.tsx +5 -0
  454. package/src/renderers/dom/desktop/icons.tsx +14 -0
  455. package/src/renderers/dom/desktop/query-bar.tsx +59 -32
  456. package/src/renderers/dom/host/box.tsx +113 -17
  457. package/src/renderers/dom/host/drag-shield.ts +35 -0
  458. package/src/renderers/dom/host/live-frame.ts +62 -0
  459. package/src/renderers/dom/host/mouse.ts +8 -1
  460. package/src/renderers/dom/host/scroll-box.tsx +6 -5
  461. package/src/renderers/dom/host/tabs.tsx +3 -2
  462. package/src/renderers/dom/input-host.tsx +6 -1
  463. package/src/renderers/dom/styles.css +26 -5
  464. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
  465. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  466. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
  467. package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
  468. package/src/renderers/electrobun/shared/protocol.ts +12 -1
  469. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
  470. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
  471. package/src/renderers/electrobun/view/host/native.ts +0 -10
  472. package/src/renderers/electrobun/view/host/window-move.ts +28 -0
  473. package/src/renderers/electrobun/view/main.tsx +4 -1
  474. package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
  475. package/src/renderers/electrobun/view/ui-host.tsx +2 -5
  476. package/src/renderers/opentui/input-host.tsx +6 -1
  477. package/src/renderers/opentui/start.tsx +9 -0
  478. package/src/renderers/opentui/ui-host.tsx +7 -1
  479. package/src/shares/portable-layout.ts +16 -6
  480. package/src/sources/gloomberb-cloud/index.ts +19 -10
  481. package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
  482. package/src/sources/listing-symbols.ts +11 -5
  483. package/src/sources/provider-errors.ts +88 -3
  484. package/src/sources/provider-router/batches.ts +3 -3
  485. package/src/sources/provider-router/cached-routes.ts +8 -4
  486. package/src/sources/provider-router/financial-routes.ts +13 -7
  487. package/src/sources/provider-router/financials.ts +106 -16
  488. package/src/sources/provider-router/history.ts +27 -11
  489. package/src/sources/provider-router/index.ts +1 -1
  490. package/src/sources/provider-router/primary.ts +24 -6
  491. package/src/sources/sec-edgar.ts +68 -8
  492. package/src/state/pane-scroll-registry.tsx +67 -35
  493. package/src/sync/core-contributors.ts +4 -3
  494. package/src/test-support/cli-context.ts +2 -1
  495. package/src/test-support/data-provider.ts +34 -0
  496. package/src/test-support/margin-account.ts +108 -0
  497. package/src/test-support/shell-pane-keys.tsx +1 -0
  498. package/src/theme/series-colors.ts +46 -0
  499. package/src/theme/themes.ts +149 -0
  500. package/src/tickers/search/index.ts +22 -2
  501. package/src/tickers/search/ranking.ts +45 -1
  502. package/src/tickers/search/types.ts +3 -1
  503. package/src/time-series/resolution.ts +15 -0
  504. package/src/time-series/resolve.ts +1 -14
  505. package/src/types/config.ts +61 -1
  506. package/src/types/financials.ts +32 -3
  507. package/src/types/headless.ts +11 -0
  508. package/src/types/instrument.ts +8 -0
  509. package/src/types/plugin.ts +49 -3
  510. package/src/ui/host.tsx +16 -0
  511. package/src/ui/index.tsx +10 -3
  512. package/src/ui/scroll-box-axis.ts +26 -0
  513. package/src/utils/cli-output.ts +14 -1
  514. package/src/utils/currency-units.ts +8 -3
  515. package/src/utils/depositary-receipt.ts +54 -10
  516. package/src/utils/display-number.ts +63 -0
  517. package/src/utils/exchanges.ts +13 -0
  518. package/src/utils/format.ts +2 -1
  519. package/src/utils/fundamentals.ts +17 -1
  520. package/src/utils/fx-market-hours.ts +34 -0
  521. package/src/utils/option-expiry.ts +126 -0
  522. package/src/utils/price-history-integrity.ts +19 -6
  523. package/src/utils/price-history.ts +39 -6
  524. package/src/utils/rpc-timeout-error.ts +6 -2
  525. package/src/utils/sec-eps-basis.ts +159 -30
  526. package/src/utils/sec.ts +38 -34
  527. package/src/utils/throttled-fetch.ts +51 -22
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Reports as flat tables, for `--csv` and `--ndjson`: the columns the text
3
+ * view shows, one row per row it shows, and values a spreadsheet can use.
4
+ * Numbers drop the thousands commas, compact scales and units the text draws
5
+ * (the unit moves into the header, as table CSV exports do), times are ISO,
6
+ * and nothing is a JSON blob. CSV writes a report with several tables as
7
+ * stacked blocks under `# section:` lines and ends with `#` lines for the
8
+ * source, as-of, notes and errors.
9
+ */
10
+ import { serializeCsv } from "../utils/csv";
11
+ import {
12
+ BLANK_DISPLAY,
13
+ DISPLAY_PLACEHOLDER,
14
+ labelWithUnit,
15
+ parseDisplayNumber,
16
+ type DisplayNumber,
17
+ } from "../utils/display-number";
18
+ import { isEpochMilliseconds, parseReportTime } from "../utils/utc-time";
19
+ import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
20
+
21
+ type CliReportValue = string | number;
22
+
23
+ /** One table of a report, as CSV and NDJSON write it. */
24
+ export interface CliReportTable {
25
+ /** The section title; CSV names it in a `# section:` line when it writes several tables. */
26
+ title: string;
27
+ /** The column names the text view shows, with the unit the cells dropped. */
28
+ columns: string[];
29
+ /** Numbers, ISO times and text; an empty string for a missing value. */
30
+ rows: CliReportValue[][];
31
+ }
32
+
33
+ export interface CliReportTables {
34
+ tables: CliReportTable[];
35
+ /** What CSV writes after the data as `#` lines: source and as-of, completeness, errors, notes. */
36
+ footer: string[];
37
+ }
38
+
39
+ /** A column as a report declares it: the text view draws `format(value, row)`, or the value itself. */
40
+ export interface CliReportColumn<Row = Record<string, unknown>> {
41
+ key: string;
42
+ /** The header as the text view prints it. */
43
+ header: string;
44
+ align?: "left" | "right" | "center";
45
+ format?: (value: unknown, row: Row) => string;
46
+ }
47
+
48
+ /** A label/value line of a report. `unit` names the unit of a numeric `value` when `formatted` cannot say it. */
49
+ export interface CliReportEntry {
50
+ label: string;
51
+ value: unknown;
52
+ formatted?: string;
53
+ key?: string;
54
+ unit?: string;
55
+ }
56
+
57
+ type Cell =
58
+ | { kind: "blank" }
59
+ | { kind: "text"; text: string; time?: boolean }
60
+ | { kind: "number"; value: number; unit: string }
61
+ /** Text that may read as a number, once the whole column is known. */
62
+ | { kind: "textual"; text: string };
63
+
64
+ type ResolvedCell = Exclude<Cell, { kind: "textual" }>;
65
+
66
+ interface ResolvedColumn {
67
+ header: string;
68
+ cells: ResolvedCell[];
69
+ }
70
+
71
+ const BLANK = { kind: "blank" } as const;
72
+ const DATE_ONLY = /^\d{4}-\d{2}-\d{2}$/;
73
+ // Keys whose epoch-millisecond numbers are times rather than counts or prices.
74
+ const TIME_KEY = /(?:At|Time|Timestamp|Updated|AsOf|Date)$|^(?:asOf|date|time|timestamp|updated)$/;
75
+ // A value drawn with a change beside it, "416.2 +6.4%" or "1.20 (3%)": the value is the first figure.
76
+ const CHANGE_NOTE = /^\(?[+\-\u2212]?[\d.,]+%?\)?$/;
77
+ /**
78
+ * How a value relates to the number drawn for it: as is, as a percent of a
79
+ * fraction, or in thousands, millions or billions named by the header.
80
+ */
81
+ const DISPLAY_SCALES = [1, 100, 0.01, 1e-3, 1e-6, 1e-9, 1e-12, 1e3, 1e6, 1e4];
82
+ // "3M" is as often a tenor as three million.
83
+ const BARE_SCALED = /^[+-]?\d+[kKMBT]$/;
84
+ // Beside "1Y" or "6W", "3M" is three months.
85
+ const TENOR = /^\d+\s*[DWQY]$/i;
86
+
87
+ /**
88
+ * Some feeds store prices as float32, so 338.93 arrives as 338.929992676. A value
89
+ * that is exactly a float32 reads as the shortest decimal that rounds back to it,
90
+ * which leaves genuine doubles (never exactly float32 unless short) untouched.
91
+ */
92
+ export function float32Shortest(value: number): number | null {
93
+ if (Math.fround(value) !== value) return null;
94
+ for (let digits = 1; digits <= 9; digits += 1) {
95
+ const candidate = Number(value.toPrecision(digits));
96
+ if (Math.fround(candidate) === value) return candidate;
97
+ }
98
+ return null;
99
+ }
100
+
101
+ /**
102
+ * A number without binary floating-point noise (47.350000000000364, 0.0615 * 100),
103
+ * as the text view drops it: twelve significant digits, whole numbers and
104
+ * amounts of a billion or more as they are.
105
+ */
106
+ export function exportNumber(value: number): number {
107
+ if (Number.isInteger(value) || Math.abs(value) >= 1e9) return Object.is(value, -0) ? 0 : value;
108
+ const rounded = float32Shortest(value) ?? Number(value.toPrecision(12));
109
+ return Object.is(rounded, -0) ? 0 : rounded;
110
+ }
111
+
112
+ function isoInstant(time: number): string {
113
+ return new Date(time).toISOString().replace(".000Z", "Z");
114
+ }
115
+
116
+ function text(value: string): Cell {
117
+ return { kind: "text", text: value };
118
+ }
119
+
120
+ function time(value: string): Cell {
121
+ return { kind: "text", text: value, time: true };
122
+ }
123
+
124
+ /** A drawn number read back. Leading zeros make a code ("0700", "000001"), not a number. */
125
+ function readDisplay(value: string): DisplayNumber | typeof BLANK_DISPLAY | null {
126
+ const trimmed = value.trim();
127
+ if (/^[+-]?0\d/.test(trimmed)) return null;
128
+ return parseDisplayNumber(trimmed);
129
+ }
130
+
131
+ /** The value in the units the text drew, so a fraction drawn as 6.15% exports 6.15, or null when it is not what was drawn. */
132
+ function valueAsDrawn(value: number, drawn: DisplayNumber): number | null {
133
+ const target = Number(drawn.number);
134
+ const tolerance = drawn.step * (1 + 1e-9) + Number.EPSILON;
135
+ for (const scale of DISPLAY_SCALES) {
136
+ const scaled = value * scale;
137
+ if (Math.abs(scaled - target) <= tolerance) return exportNumber(scaled);
138
+ }
139
+ return null;
140
+ }
141
+
142
+ function timeCell(instant: number, dateOnly: boolean, shown: string | null, raw?: string): Cell {
143
+ if (!Number.isFinite(instant)) return shown ? { kind: "textual", text: shown } : BLANK;
144
+ // A date the text drew from an instant (a monthly bar) stays the date it shows.
145
+ if (shown != null && DATE_ONLY.test(shown)) return time(shown);
146
+ if (raw != null && shown == null) return time(raw);
147
+ return time(dateOnly ? new Date(instant).toISOString().slice(0, 10) : isoInstant(instant));
148
+ }
149
+
150
+ function numberCell(value: number, shown: string | null, key: string | undefined): Cell {
151
+ if (!Number.isFinite(value)) return shown ? { kind: "textual", text: shown } : BLANK;
152
+ if (key != null && TIME_KEY.test(key) && isEpochMilliseconds(value)) return timeCell(value, false, shown);
153
+ if (shown == null) return { kind: "number", value: exportNumber(value), unit: "" };
154
+ const drawn = readDisplay(shown);
155
+ if (drawn === BLANK_DISPLAY) return BLANK;
156
+ // "3M" drawn from 3 is a tenor or a code, not three million.
157
+ if (drawn && BARE_SCALED.test(shown) && value === Number(shown.replace(/[kKMBT]$/, ""))) return text(shown);
158
+ if (drawn) return { kind: "number", value: valueAsDrawn(value, drawn) ?? Number(drawn.number), unit: drawn.unit };
159
+ // A time drawn as a date or a clock.
160
+ if (isEpochMilliseconds(value)) return timeCell(value, false, shown);
161
+ const [first, ...rest] = shown.split(/\s+/);
162
+ const lead = rest.length > 0 && CHANGE_NOTE.test(rest.join(" ")) ? readDisplay(first!) : null;
163
+ const scaled = lead && lead !== BLANK_DISPLAY ? valueAsDrawn(value, lead) : null;
164
+ if (lead && lead !== BLANK_DISPLAY && scaled != null) return { kind: "number", value: scaled, unit: lead.unit };
165
+ return text(shown);
166
+ }
167
+
168
+ /** Text for a nested value, never JSON: `a, b` for a list, `Key: value` pairs for a record. */
169
+ function flattenValue(value: unknown, depth = 0): string {
170
+ if (value == null) return "";
171
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : isoInstant(value.getTime());
172
+ if (typeof value === "number") return Number.isFinite(value) ? String(exportNumber(value)) : "";
173
+ if (Array.isArray(value)) {
174
+ return value.map((item) => flattenValue(item, depth + 1)).filter(Boolean).join(depth === 0 ? "; " : ", ");
175
+ }
176
+ if (typeof value === "object") {
177
+ return Object.entries(value)
178
+ .map(([key, entry]) => [key, flattenValue(entry, depth + 1)] as const)
179
+ .filter(([, entry]) => entry !== "")
180
+ .map(([key, entry]) => `${key}: ${entry}`)
181
+ .join(", ");
182
+ }
183
+ return String(value);
184
+ }
185
+
186
+ /** JSON text a row or a format handed over, as the flat text of what it holds; null for any other text. */
187
+ function flattenJsonText(value: string): string | null {
188
+ if (!/^\s*[{[]/.test(value)) return null;
189
+ try {
190
+ const parsed: unknown = JSON.parse(value);
191
+ return parsed != null && typeof parsed === "object" ? flattenValue(parsed) : null;
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ /** A cell from its value and, when the column formats it, the text it draws. */
198
+ function exportCell(raw: unknown, display: string | null, key?: string): Cell {
199
+ const shown = display?.trim() ?? null;
200
+ if (shown != null && (shown === "" || DISPLAY_PLACEHOLDER.test(shown))) return BLANK;
201
+ const json = flattenJsonText(shown ?? (typeof raw === "string" ? raw : ""));
202
+ if (json != null) return json ? text(json) : BLANK;
203
+ if (raw instanceof Date) return timeCell(raw.getTime(), false, shown);
204
+ if (typeof raw === "number") return numberCell(raw, shown, key);
205
+ if (typeof raw === "string") {
206
+ const value = raw.trim();
207
+ const reportTime = parseReportTime(value);
208
+ if (reportTime) return timeCell(reportTime.time, reportTime.dateOnly, shown, value);
209
+ const drawn = shown ?? value;
210
+ return drawn === "" || DISPLAY_PLACEHOLDER.test(drawn) ? BLANK : { kind: "textual", text: drawn };
211
+ }
212
+ if (typeof raw === "boolean") return text(shown ?? String(raw));
213
+ if (raw == null) return shown == null ? BLANK : { kind: "textual", text: shown };
214
+ if (shown != null) return { kind: "textual", text: shown };
215
+ const flat = flattenValue(raw);
216
+ return flat ? text(flat) : BLANK;
217
+ }
218
+
219
+ /**
220
+ * Text that reads as a number becomes one; text that does not, such as a
221
+ * range "3.75-4.00%" or "N/M", stays as drawn. A bare scaled count ("-106B")
222
+ * becomes a number only beside other figures, never in a column of tenors
223
+ * ("3M, 6M, 1Y"). Identifier columns keep their text.
224
+ */
225
+ function resolveColumn(header: string, cells: Cell[], parseText: boolean): ResolvedColumn {
226
+ const textual = cells.flatMap((cell) => cell.kind === "textual" ? [cell] : []);
227
+ const read = parseText ? textual.map((cell) => readDisplay(cell.text)) : [];
228
+ const numeric = read.filter((entry): entry is DisplayNumber => entry != null && entry !== BLANK_DISPLAY);
229
+ const bare = textual.filter((cell, index) => read[index] != null && read[index] !== BLANK_DISPLAY && BARE_SCALED.test(cell.text.trim()));
230
+ const otherFigures = numeric.length > bare.length || cells.some((cell) => cell.kind === "number");
231
+ const convertBare = otherFigures && !textual.some((cell) => TENOR.test(cell.text.trim()));
232
+ let textualIndex = 0;
233
+ return {
234
+ header,
235
+ cells: cells.map((cell): ResolvedCell => {
236
+ if (cell.kind !== "textual") return cell;
237
+ const entry = parseText ? read[textualIndex] : null;
238
+ textualIndex += 1;
239
+ if (entry === BLANK_DISPLAY) return BLANK;
240
+ if (entry == null || (!convertBare && BARE_SCALED.test(cell.text.trim()))) return { kind: "text", text: cell.text };
241
+ return { kind: "number", value: Number(entry.number), unit: entry.unit };
242
+ }),
243
+ };
244
+ }
245
+
246
+ function numberUnits(cells: readonly ResolvedCell[]): Set<string> {
247
+ return new Set(cells.flatMap((cell) => cell.kind === "number" ? [cell.unit] : []));
248
+ }
249
+
250
+ function cellValue(cell: ResolvedCell): CliReportValue {
251
+ return cell.kind === "blank" ? "" : cell.kind === "number" ? cell.value : cell.text;
252
+ }
253
+
254
+ const RANGE = /^[+-]?\d[\d,]*(?:\.\d+)?\s*[-\u2013]\s*[+-]?\d[\d,]*(?:\.\d+)?$/;
255
+
256
+ /** A range drawn in the unit the header now names ("3.75-4.00%" under "Rate (%)") drops it too. */
257
+ function withoutUnit(cell: ResolvedCell, unit: string): CliReportValue {
258
+ if (!unit || cell.kind !== "text") return cellValue(cell);
259
+ const drawn = cell.text.trim();
260
+ const bare = drawn.endsWith(unit) ? drawn.slice(0, -unit.length).trim()
261
+ : drawn.startsWith(unit) ? drawn.slice(unit.length).trim()
262
+ : null;
263
+ return bare != null && RANGE.test(bare) ? bare.replace(/,/g, "") : cell.text;
264
+ }
265
+
266
+ // The first column of a table of metrics, one per row: `Metric, 2025, 2024` or `Indicator, Latest, Previous`.
267
+ const METRIC_LABEL = /^(?:metric|indicator|ratio|measure|statistic|line item)s?$/i;
268
+
269
+ /**
270
+ * In a table of metrics, the columns that mix units keep one per row, and the
271
+ * row's label names it. A symbol or a date in the first column is not a label.
272
+ */
273
+ function rowUnits(columns: readonly ResolvedColumn[], mixed: readonly boolean[], rowCount: number): string[] | null {
274
+ const label = columns[0];
275
+ if (!label || mixed[0] || !METRIC_LABEL.test(label.header.trim())) return null;
276
+ if (!label.cells.some((cell) => cell.kind === "text")) return null;
277
+ if (label.cells.some((cell) => cell.kind === "number" || (cell.kind === "text" && cell.time))) return null;
278
+ const units: string[] = [];
279
+ for (let row = 0; row < rowCount; row += 1) {
280
+ const unitsInRow = numberUnits(columns.flatMap((column, index) => mixed[index] ? [column.cells[row] ?? BLANK] : []));
281
+ if (unitsInRow.size > 1) return null;
282
+ units.push([...unitsInRow][0] ?? "");
283
+ }
284
+ return units;
285
+ }
286
+
287
+ /**
288
+ * Numbers are bare, so their unit goes where a spreadsheet reader looks for
289
+ * it: the header when the column has one unit, the row's label in a table of
290
+ * metrics whose rows each have one, and otherwise a `<header> unit` column
291
+ * beside a column that mixes them (a rate in % and a spread in bp).
292
+ */
293
+ function assembleTable(title: string, columns: readonly ResolvedColumn[], rowCount: number): CliReportTable {
294
+ const units = columns.map((column) => numberUnits(column.cells));
295
+ const mixed = units.map((columnUnits) => columnUnits.size > 1);
296
+ const byRow = mixed.some(Boolean) ? rowUnits(columns, mixed, rowCount) : null;
297
+ const layout = columns.flatMap((column, index) => {
298
+ const columnUnits = units[index]!;
299
+ const cell = (row: number) => cellValue(column.cells[row] ?? BLANK);
300
+ if (index === 0 && byRow) {
301
+ return [{ header: column.header, value: (row: number) => byRow[row] ? labelWithUnit(String(cell(row)), byRow[row]!).trim() : cell(row) }];
302
+ }
303
+ if (!mixed[index]) {
304
+ const unit = [...columnUnits][0] ?? "";
305
+ return [{ header: labelWithUnit(column.header, unit), value: (row: number) => withoutUnit(column.cells[row] ?? BLANK, unit) }];
306
+ }
307
+ if (byRow) return [{ header: column.header, value: cell }];
308
+ return [
309
+ { header: column.header, value: cell },
310
+ {
311
+ header: `${column.header} unit`,
312
+ value: (row: number) => {
313
+ const resolved = column.cells[row];
314
+ return resolved?.kind === "number" ? resolved.unit : "";
315
+ },
316
+ },
317
+ ];
318
+ });
319
+ return {
320
+ title,
321
+ columns: layout.map((column) => column.header),
322
+ rows: Array.from({ length: rowCount }, (_, row) => layout.map((column) => column.value(row))),
323
+ };
324
+ }
325
+
326
+ // Codes a spreadsheet must not turn into numbers, even when they are digits.
327
+ const IDENTIFIER_KEY = /^(?:id|cik|code|cusip|figi|isin|symbol|ticker)$|(?:Id|Cik|Code|Cusip|Figi|Isin|Symbol|Ticker)$/;
328
+
329
+ /** A table of rows under the columns the text view draws. */
330
+ export function exportRowsTable<Row extends Record<string, unknown>>(
331
+ title: string,
332
+ columns: readonly CliReportColumn<Row>[],
333
+ rows: readonly Row[],
334
+ ): CliReportTable {
335
+ return assembleTable(title, columns.map((column) => resolveColumn(
336
+ column.header,
337
+ rows.map((row) => {
338
+ const value = (row as Record<string, unknown> | undefined)?.[column.key];
339
+ return exportCell(value, column.format ? column.format(value, row) : null, column.key);
340
+ }),
341
+ !IDENTIFIER_KEY.test(column.key),
342
+ )), rows.length);
343
+ }
344
+
345
+ /** Label/value lines as `Metric,Value`; a number's unit moves into its label. */
346
+ export function exportEntriesTable(title: string, entries: readonly CliReportEntry[]): CliReportTable {
347
+ return {
348
+ title,
349
+ columns: ["Metric", "Value"],
350
+ rows: entries.map((entry) => {
351
+ if (entry.unit != null && typeof entry.value === "number" && Number.isFinite(entry.value)) {
352
+ return [labelWithUnit(entry.label, entry.unit), exportNumber(entry.value)];
353
+ }
354
+ const [cell] = resolveColumn(entry.label, [exportCell(entry.value, entry.formatted ?? null, entry.key)], true).cells;
355
+ return cell?.kind === "number"
356
+ ? [labelWithUnit(entry.label, cell.unit), cell.value]
357
+ : [entry.label, cellValue(cell ?? BLANK)];
358
+ }),
359
+ };
360
+ }
361
+
362
+ /** A table known only by the text it shows, such as a rendered pane; `instant` is the time behind a shortened one. */
363
+ export function exportTextTable(
364
+ title: string,
365
+ headers: readonly string[],
366
+ rows: ReadonlyArray<ReadonlyArray<{ text: string; instant?: string } | undefined>>,
367
+ ): CliReportTable {
368
+ return assembleTable(title, headers.map((header, index) => resolveColumn(
369
+ header,
370
+ rows.map((row) => {
371
+ const cell = row[index];
372
+ if (!cell) return BLANK;
373
+ return cell.instant ? exportCell(cell.instant, null) : exportCell(null, cell.text);
374
+ }),
375
+ true,
376
+ )), rows.length);
377
+ }
378
+
379
+ /** The closing `#` lines: the text view's source and as-of line, then whatever kept the report from being whole. */
380
+ export function reportFooterLines(input: {
381
+ freshness?: ReportFreshness;
382
+ /** `true`, or what is missing, such as "19 of 20 available". */
383
+ incomplete?: boolean | string;
384
+ errors?: readonly string[];
385
+ notes?: readonly string[];
386
+ }): string[] {
387
+ return [
388
+ ...(input.freshness ? [formatFreshnessLine(input.freshness)] : []),
389
+ ...(input.incomplete ? [typeof input.incomplete === "string" ? `incomplete: ${input.incomplete}` : "incomplete"] : []),
390
+ ...(input.errors ?? []).map((error) => `error: ${error}`),
391
+ ...(input.notes ?? []).map((note) => `note: ${note}`),
392
+ ];
393
+ }
394
+
395
+ function sectionList(tables: readonly CliReportTable[]): string {
396
+ const titles = tables.map((table) => table.title);
397
+ return tables.length === 1
398
+ ? `${titles[0]} (or 1)`
399
+ : `${titles.join(", ")}, or a number from 1 to ${tables.length}`;
400
+ }
401
+
402
+ /**
403
+ * `--section`: one table, by its title (any case) or its 1-based position.
404
+ * Without a selector the report keeps every table.
405
+ */
406
+ export function selectReportTables(report: CliReportTables, selector: string | true | undefined): CliReportTables {
407
+ if (selector === undefined) return report;
408
+ if (selector === true || !selector.trim()) throw new Error("--section needs a section title or number.");
409
+ const wanted = selector.trim();
410
+ if (report.tables.length === 0) throw new Error(`No section "${wanted}": this report has no tables.`);
411
+ const index = /^\d+$/.test(wanted)
412
+ ? Number(wanted) - 1
413
+ : report.tables.findIndex((table) => table.title.trim().toLowerCase() === wanted.toLowerCase());
414
+ const table = report.tables[index];
415
+ if (!table) throw new Error(`No section "${wanted}". Sections: ${sectionList(report.tables)}.`);
416
+ return { ...report, tables: [table] };
417
+ }
418
+
419
+ function commentLine(value: string): string {
420
+ return `# ${value.replace(/\s*[\r\n]+\s*/g, " ").trim()}`;
421
+ }
422
+
423
+ /** A row whose first cell starts with `#` (a rank column) quotes it, so no reader takes the row for a comment. */
424
+ function csvLine(row: readonly unknown[]): string {
425
+ const line = serializeCsv([row]);
426
+ if (!line.startsWith("#")) return line;
427
+ const first = String(row[0]);
428
+ return `"${first}"${line.slice(first.length)}`;
429
+ }
430
+
431
+ /**
432
+ * One table writes as a plain CSV; several write as blocks, each under a
433
+ * `# section: <title>` line, with a blank line between. The footer follows
434
+ * the data as `#` lines after a blank line.
435
+ */
436
+ export function renderReportCsv(report: CliReportTables): string {
437
+ const marked = report.tables.length > 1;
438
+ const blocks = report.tables.map((table) => [
439
+ ...(marked ? [commentLine(`section: ${table.title}`)] : []),
440
+ ...(table.columns.length > 0 ? [[table.columns, ...table.rows].map(csvLine).join("\n")] : []),
441
+ ].join("\n"));
442
+ const footer = report.footer.map(commentLine).join("\n");
443
+ return [blocks.join("\n\n"), footer].filter(Boolean).join("\n\n");
444
+ }
445
+
446
+ /** One JSON object per row, keyed by column; rows of a report with several tables name their section. */
447
+ export function renderReportNdjson(report: CliReportTables): string {
448
+ const tagged = report.tables.length > 1;
449
+ return report.tables.flatMap((table) => {
450
+ const sectionKey = table.columns.some((column) => column.toLowerCase() === "section") ? "_section" : "section";
451
+ return table.rows.map((row) => {
452
+ const object: Record<string, CliReportValue | null> = tagged ? { [sectionKey]: table.title } : {};
453
+ table.columns.forEach((column, index) => {
454
+ const value = row[index];
455
+ object[column] = value === undefined || value === "" ? null : value;
456
+ });
457
+ return JSON.stringify(object);
458
+ });
459
+ }).join("\n");
460
+ }
package/src/cli/result.ts CHANGED
@@ -7,12 +7,16 @@ import {
7
7
  statValueWidth,
8
8
  visibleLength,
9
9
  wrapText,
10
+ wrapToTerminal,
10
11
  type CliStatEntry,
11
12
  type CliTableColumn,
12
13
  } from "../utils/cli-output";
13
14
  import { serializeCsv } from "../utils/csv";
14
15
  import { formatUtcTime, isEpochMilliseconds, isZonedIsoDateTime } from "../utils/utc-time";
15
16
  import { formatFreshnessLine, type ReportFreshness } from "./pane-functions/freshness";
17
+ import { withSearchHints } from "./not-a-ticker";
18
+ import { renderReportCsv, renderReportNdjson, type CliReportTables } from "./report-tables";
19
+ import { windowRows, type RowWindow, type RowWindowOptions } from "./row-window";
16
20
 
17
21
  export interface CliResult<T = unknown> {
18
22
  data: T;
@@ -21,7 +25,8 @@ export interface CliResult<T = unknown> {
21
25
  /**
22
26
  * Source, as-of and status of market data, as every `fn` report states them
23
27
  * (docs/usage.md#how-current-a-report-is): the last line of the text output
24
- * and `metadata.freshness` in JSON. CSV and NDJSON stay rows only.
28
+ * and `metadata.freshness` in JSON. CSV and NDJSON stay rows only, unless
29
+ * the command passes `tables`, whose footer carries it.
25
30
  */
26
31
  freshness?: ReportFreshness;
27
32
  }
@@ -57,6 +62,22 @@ export interface CliResultRenderOptions<T = unknown, Row = Record<string, unknow
57
62
  summary?: (data: T) => string;
58
63
  /** Text-mode line printed first, naming what the result is about, such as the listing a symbol resolved to. */
59
64
  heading?: string;
65
+ /** Text-mode line printed under the rows, such as where a listing stops. Not printed with no rows: say it in `empty`. */
66
+ footnote?: string;
67
+ /**
68
+ * The row key holding each row's date, which makes the rows a dated series:
69
+ * `--tail` keeps the newest rows whichever way the series runs, and a
70
+ * `--limit` that keeps the oldest says so, under the text table and as
71
+ * `metadata.rows` in JSON.
72
+ */
73
+ dateKey?: string;
74
+ /** How many rows to show when neither `--limit` nor `--tail` is given; every row when absent. */
75
+ defaultLimit?: number;
76
+ /**
77
+ * What CSV and NDJSON write instead of the rows: a report's tables as the
78
+ * text view shows them, CSV with its `# section:` and closing `#` lines.
79
+ */
80
+ tables?: CliReportTables;
60
81
  }
61
82
 
62
83
  interface CliResultJsonEnvelope<T> extends Omit<CliResult<T>, "freshness"> {
@@ -82,14 +103,28 @@ const KEY_ACRONYMS: Record<string, string> = {
82
103
  usd: "USD",
83
104
  };
84
105
 
85
- function applyLimit<T>(data: T, limit?: number): T {
86
- if (!Array.isArray(data) || limit == null) return data;
87
- return data.slice(0, limit) as T;
106
+ function rowWindowOptions(options: CliGlobalOptions, renderOptions: Pick<CliResultRenderOptions, "defaultLimit">): RowWindowOptions {
107
+ return options.tail != null ? { tail: options.tail } : { limit: options.limit ?? renderOptions.defaultLimit };
108
+ }
109
+
110
+ function windowed<Row>(
111
+ rows: readonly Row[],
112
+ options: CliGlobalOptions,
113
+ renderOptions: Pick<CliResultRenderOptions, "dateKey" | "defaultLimit">,
114
+ ): RowWindow<Row> {
115
+ const { dateKey } = renderOptions;
116
+ const dateOf = dateKey ? (row: Row) => (isPlainObject(row) ? row[dateKey] : undefined) : undefined;
117
+ return windowRows(rows, rowWindowOptions(options, renderOptions), dateOf);
118
+ }
119
+
120
+ function asRows<T, Row>(data: T, rows?: (data: T) => Row[]): Row[] {
121
+ return rows ? rows(data) : (Array.isArray(data) ? data : [data]) as Row[];
88
122
  }
89
123
 
90
- function asRows<T, Row>(data: T, limit?: number, rows?: (data: T) => Row[]): Row[] {
91
- const resolvedRows = rows ? rows(data) : (Array.isArray(data) ? data : [data]) as Row[];
92
- return limit == null ? resolvedRows : resolvedRows.slice(0, limit);
124
+ /** What JSON says about rows the window cut, under `metadata.rows`. */
125
+ function cutMetadata(window: RowWindow<unknown>) {
126
+ if (window.rows.length >= window.total) return null;
127
+ return { shown: window.rows.length, total: window.total, kept: window.kept, ...(window.note ? { note: window.note } : {}) };
93
128
  }
94
129
 
95
130
  function isPlainObject(value: unknown): value is Record<string, unknown> {
@@ -270,6 +305,7 @@ function renderTextTable<Row extends Record<string, unknown>>(
270
305
  width: column.width,
271
306
  maxWidth: column.maxWidth,
272
307
  optional: column.optional,
308
+ dropPriority: column.dropPriority,
273
309
  shrink: column.shrink,
274
310
  })),
275
311
  cells.map((row) => shown.map(({ index }) => row[index]!)),
@@ -303,7 +339,7 @@ function overflowsTerminal<Row extends Record<string, unknown>>(rows: Row[]): bo
303
339
 
304
340
  /** The closing source, as-of and status line of a text result, for output printed without `printResult`. */
305
341
  export function cliFreshnessFooter(freshness: ReportFreshness): string {
306
- return cliStyles.muted(formatFreshnessLine(freshness));
342
+ return cliStyles.muted(wrapToTerminal(formatFreshnessLine(freshness)));
307
343
  }
308
344
 
309
345
  export function serializeCliResult<T, Row extends Record<string, unknown> = Record<string, unknown>>(
@@ -314,11 +350,13 @@ export function serializeCliResult<T, Row extends Record<string, unknown> = Reco
314
350
  const { freshness, ...rest } = result;
315
351
  if (options.format === "json") {
316
352
  const columns = serializeColumns(renderOptions.columns);
353
+ const window = Array.isArray(result.data) ? windowed(result.data as unknown[], options, renderOptions) : null;
354
+ const cut = window ? cutMetadata(window) : null;
317
355
  const envelope: CliResultJsonEnvelope<T> = {
318
356
  ok: true,
319
357
  ...rest,
320
- ...(freshness ? { metadata: { ...rest.metadata, freshness } } : {}),
321
- data: applyLimit(result.data, options.limit),
358
+ ...(freshness || cut ? { metadata: { ...rest.metadata, ...(freshness ? { freshness } : {}), ...(cut ? { rows: cut } : {}) } } : {}),
359
+ data: window ? window.rows as T : result.data,
322
360
  ...(columns?.length ? { columns } : {}),
323
361
  };
324
362
  return JSON.stringify(envelope, null, 2);
@@ -333,7 +371,10 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
333
371
  options: CliGlobalOptions,
334
372
  renderOptions: CliResultRenderOptions<T, Row>,
335
373
  ): string {
336
- const rows = asRows(result.data, options.limit, renderOptions.rows);
374
+ if (renderOptions.tables && options.format === "csv") return renderReportCsv(renderOptions.tables);
375
+ if (renderOptions.tables && options.format === "ndjson") return renderReportNdjson(renderOptions.tables);
376
+ const window = windowed(asRows(result.data, renderOptions.rows), options, renderOptions);
377
+ const rows = window.rows;
337
378
  if (options.format === "ndjson") {
338
379
  return rows.map((row) => JSON.stringify(row)).join("\n");
339
380
  }
@@ -348,7 +389,9 @@ function serializeCliRows<T, Row extends Record<string, unknown>>(
348
389
  if (rows.length === 0) {
349
390
  return withHeading(summary || cliStyles.muted(renderOptions.empty ?? "No results."));
350
391
  }
351
- const body = renderTextRows(rows as Row[], result.data, renderOptions);
392
+ const table = renderTextRows(rows as Row[], result.data, renderOptions);
393
+ const notes = [window.note, renderOptions.footnote].filter((note): note is string => !!note);
394
+ const body = [table, ...notes.map((note) => cliStyles.muted(note))].join("\n");
352
395
  return withHeading(summary ? `${summary}\n\n${body}` : body);
353
396
  }
354
397
 
@@ -373,16 +416,20 @@ export function printCliResult<T, Row extends Record<string, unknown> = Record<s
373
416
  result: CliResult<T>,
374
417
  options: CliGlobalOptions,
375
418
  renderOptions: CliResultRenderOptions<T, Row> = {},
419
+ args: readonly string[] = [],
376
420
  ): void {
377
421
  if (options.quiet && options.format === "text") return;
378
- const output = serializeCliResult(result, options, renderOptions);
422
+ // JSON keeps the router's own words; everything else is for a person, so a "Not a ticker" gets its hint.
423
+ const hinted = (text: string) => options.format === "json" ? text : withSearchHints(text, args);
424
+ const output = hinted(serializeCliResult(result, options, renderOptions));
379
425
  // Bun's console writer can truncate a large pipe write after stdout has been
380
426
  // initialized. The stream queues the remaining bytes until the reader drains.
381
427
  if (output) process.stdout.write(`${output}\n`);
382
- // Structured formats carry warnings in the envelope; text mode would otherwise lose them.
383
- if (options.format === "text") {
428
+ // JSON carries warnings in its envelope. Text, CSV and NDJSON are only the
429
+ // rows, so a warning goes to stderr, out of a file the rows are piped into.
430
+ if (options.format !== "json" && !options.quiet) {
384
431
  for (const warning of result.warnings ?? []) {
385
- console.error(`${cliStyles.warning("warning:")} ${warning}`);
432
+ console.error(`${cliStyles.warning("warning:")} ${hinted(warning)}`);
386
433
  }
387
434
  }
388
435
  }