gloomberb 0.17.4 → 0.17.6

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 (384) hide show
  1. package/package.json +3 -1
  2. package/patches/electrobun@1.18.1.patch +80 -0
  3. package/src/api-client/chat.ts +9 -0
  4. package/src/api-client/commodity-balances.ts +77 -0
  5. package/src/api-client/data.ts +35 -3
  6. package/src/api-client/earnings.ts +10 -0
  7. package/src/api-client/index.ts +5 -0
  8. package/src/api-client/insider-signals.ts +75 -0
  9. package/src/api-client/market-discovery.ts +1 -1
  10. package/src/api-client/normalizers.ts +1 -0
  11. package/src/api-client/paths.ts +9 -0
  12. package/src/api-client/research-activity.ts +10 -3
  13. package/src/api-client/socket.ts +10 -0
  14. package/src/api-client/types.ts +25 -1
  15. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
  16. package/src/app/runtime/broker-import.ts +23 -21
  17. package/src/app/runtime/plugin-bindings.ts +13 -7
  18. package/src/app/runtime/update.ts +64 -4
  19. package/src/app.tsx +2 -0
  20. package/src/architecture/core-builtin-allowlist.json +2 -10
  21. package/src/ask-gloom/pane-context.ts +11 -1
  22. package/src/brokers/broker-portfolio-sync.ts +66 -0
  23. package/src/brokers/sync-broker-instance.ts +75 -41
  24. package/src/cli/commands/market.ts +101 -34
  25. package/src/cli/commands/options-chain.ts +20 -6
  26. package/src/cli/commands/overview.ts +54 -13
  27. package/src/cli/commands/search.ts +1 -1
  28. package/src/cli/commands/system.ts +19 -1
  29. package/src/cli/commands/ticker.ts +24 -4
  30. package/src/cli/context.ts +13 -0
  31. package/src/cli/errors.ts +15 -2
  32. package/src/cli/fx-pair.ts +77 -0
  33. package/src/cli/glossary.ts +34 -4
  34. package/src/cli/history-rows.ts +25 -11
  35. package/src/cli/index.ts +30 -12
  36. package/src/cli/listing-arg.ts +61 -138
  37. package/src/cli/listing-suggestion.ts +3 -21
  38. package/src/cli/pane-functions/catalog.ts +50 -0
  39. package/src/cli/pane-functions/freshness.ts +44 -9
  40. package/src/cli/pane-functions/function-help.ts +9 -9
  41. package/src/cli/pane-functions/headless.ts +19 -7
  42. package/src/cli/pane-functions/index.ts +63 -5
  43. package/src/cli/pane-functions/market-session.ts +18 -3
  44. package/src/cli/pane-functions/resolver.ts +29 -8
  45. package/src/cli/pane-functions/screenshot.ts +2 -0
  46. package/src/cli/report-tables.ts +11 -4
  47. package/src/cli/result.ts +2 -0
  48. package/src/cli/series-search.ts +6 -1
  49. package/src/cli/ticker-resolution.ts +1 -2
  50. package/src/cli/unknown-command.ts +18 -0
  51. package/src/components/chart/composite/composite-chart.tsx +80 -19
  52. package/src/components/chart/composite/format.ts +17 -0
  53. package/src/components/chart/composite/legend.tsx +26 -15
  54. package/src/components/chart/composite/panel-labels.ts +38 -0
  55. package/src/components/chart/composite/panel-series.ts +1 -0
  56. package/src/components/chart/composite/panel-surface.tsx +25 -7
  57. package/src/components/chart/composite/price-axis-labels.tsx +16 -3
  58. package/src/components/chart/composite/scene.ts +60 -2
  59. package/src/components/chart/composite/series-axis-tags.tsx +67 -0
  60. package/src/components/chart/composite/tools.ts +2 -2
  61. package/src/components/chart/composite/types.ts +28 -0
  62. package/src/components/chart/core/view-reset.ts +46 -0
  63. package/src/components/command-bar/commands/collection.ts +1 -1
  64. package/src/components/command-bar/commands/direct/index.ts +48 -1
  65. package/src/components/command-bar/commands/registry.ts +8 -0
  66. package/src/components/command-bar/list/model.ts +7 -0
  67. package/src/components/command-bar/pane-templates/workflow.ts +5 -0
  68. package/src/components/command-bar/panel/prompt-binding.ts +8 -0
  69. package/src/components/command-bar/routes/root/command-items.ts +17 -2
  70. package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
  71. package/src/components/command-bar/routes/root/provider-search.ts +6 -0
  72. package/src/components/command-bar/routes/root/runtime.ts +3 -0
  73. package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
  74. package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
  75. package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
  76. package/src/components/command-bar/surface/index.tsx +7 -1
  77. package/src/components/command-bar/workflow/collection-actions.ts +6 -5
  78. package/src/components/command-bar/workflow/ops.ts +8 -5
  79. package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
  80. package/src/components/command-bar/workflow/tickers.ts +1 -1
  81. package/src/components/form-modal/confirm.tsx +1 -0
  82. package/src/components/form-modal/request.ts +2 -0
  83. package/src/components/layout/detached-pane-shell.tsx +6 -1
  84. package/src/components/layout/pane/content.tsx +11 -8
  85. package/src/components/layout/pane/sidebar.tsx +24 -11
  86. package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
  87. package/src/components/layout/shell/index.tsx +5 -1
  88. package/src/components/layout/shell/menu.ts +8 -0
  89. package/src/components/layout/shell/pane/capture.ts +29 -12
  90. package/src/components/onboarding/wizard-model.ts +1 -1
  91. package/src/components/onboarding/wizard-positions.ts +18 -10
  92. package/src/components/ticker/badge/format.ts +64 -15
  93. package/src/components/ticker/badge/index.tsx +32 -11
  94. package/src/components/ticker/badge/list.tsx +4 -3
  95. package/src/components/ticker/badge/text.tsx +2 -2
  96. package/src/components/ticker/list-table-view.tsx +8 -1
  97. package/src/components/ui/confirm-dialog.tsx +30 -4
  98. package/src/components/ui/data-table/opentui/index.tsx +61 -20
  99. package/src/components/ui/data-table/opentui/model.ts +104 -0
  100. package/src/components/ui/fields.tsx +5 -0
  101. package/src/components/ui/inline-quick-add.tsx +4 -1
  102. package/src/components/ui/query-bar-keys.tsx +29 -5
  103. package/src/components/ui/query-bar.tsx +29 -4
  104. package/src/components/ui/windowed-list.tsx +27 -0
  105. package/src/data/config/store/normalize.ts +8 -0
  106. package/src/docs/snapshot.json +248 -5
  107. package/src/i18n/es.ts +3 -1
  108. package/src/i18n/ja.ts +3 -1
  109. package/src/i18n/ko.ts +3 -1
  110. package/src/i18n/zh-cn.ts +3 -1
  111. package/src/i18n/zh-tw.ts +3 -1
  112. package/src/layout-marketplace/cloud.ts +16 -5
  113. package/src/layout-marketplace/gallery.tsx +4 -4
  114. package/src/layout-marketplace/linked-sync.ts +9 -3
  115. package/src/layout-marketplace/linked.ts +24 -3
  116. package/src/layout-marketplace/model.ts +39 -4
  117. package/src/market-data/coordinator/events.ts +5 -1
  118. package/src/market-data/coordinator/index.ts +38 -1
  119. package/src/market-data/coordinator/quotes.ts +4 -0
  120. package/src/market-data/futures-contract.ts +53 -0
  121. package/src/market-data/hooks.tsx +225 -30
  122. package/src/market-data/market/bar-period.ts +71 -0
  123. package/src/market-data/market/format.ts +14 -2
  124. package/src/market-data/market/freshness.ts +28 -1
  125. package/src/market-data/market/status.ts +152 -13
  126. package/src/market-data/model-dividend-yield.ts +76 -0
  127. package/src/market-data/quotes/freshness.ts +30 -1
  128. package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
  129. package/src/market-data/quotes/regular-session-valuation.ts +32 -1
  130. package/src/plugins/builtin/analytics/metrics.ts +8 -4
  131. package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
  132. package/src/plugins/builtin/analytics/risk-model.ts +74 -15
  133. package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
  134. package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
  135. package/src/plugins/builtin/broker-manager/model.ts +2 -2
  136. package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
  137. package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
  138. package/src/plugins/builtin/chart-composer/formula.ts +233 -0
  139. package/src/plugins/builtin/chart-composer/headless.ts +27 -2
  140. package/src/plugins/builtin/chart-composer/index.tsx +3 -2
  141. package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
  142. package/src/plugins/builtin/chart-composer/presets.ts +91 -2
  143. package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
  144. package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
  145. package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
  146. package/src/plugins/builtin/chart-composer/settings.ts +16 -4
  147. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  148. package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
  149. package/src/plugins/builtin/chat/content/footer.tsx +5 -2
  150. package/src/plugins/builtin/chat/content/index.tsx +12 -5
  151. package/src/plugins/builtin/chat/content/mentions.ts +50 -21
  152. package/src/plugins/builtin/chat/content/scroll.ts +12 -5
  153. package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
  154. package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
  155. package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
  156. package/src/plugins/builtin/chat/controller/index.ts +88 -17
  157. package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
  158. package/src/plugins/builtin/chat/controller/messages.ts +14 -2
  159. package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
  160. package/src/plugins/builtin/chat/controller/state.ts +25 -0
  161. package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
  162. package/src/plugins/builtin/chat/controller/view.ts +4 -0
  163. package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
  164. package/src/plugins/builtin/chat/conversations.ts +12 -5
  165. package/src/plugins/builtin/chat/index.tsx +1 -1
  166. package/src/plugins/builtin/chat/layout.ts +23 -5
  167. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  168. package/src/plugins/builtin/chat/share/flow.tsx +18 -9
  169. package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
  170. package/src/plugins/builtin/chat/sidebar.tsx +6 -1
  171. package/src/plugins/builtin/chat/unread-badge.ts +38 -7
  172. package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
  173. package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
  174. package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
  175. package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
  176. package/src/plugins/builtin/commodity-balances/client.ts +51 -193
  177. package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
  178. package/src/plugins/builtin/commodity-balances/model.ts +5 -180
  179. package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
  180. package/src/plugins/builtin/custom-view/index.tsx +1 -1
  181. package/src/plugins/builtin/custom-view/loader.ts +57 -7
  182. package/src/plugins/builtin/custom-view/sources.ts +146 -0
  183. package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
  184. package/src/plugins/builtin/earnings/board-model.ts +33 -3
  185. package/src/plugins/builtin/earnings/board-table.ts +59 -14
  186. package/src/plugins/builtin/earnings/board.tsx +36 -3
  187. package/src/plugins/builtin/earnings/headless.ts +24 -5
  188. package/src/plugins/builtin/earnings/history-model.ts +9 -1
  189. package/src/plugins/builtin/earnings/history-table.ts +16 -5
  190. package/src/plugins/builtin/earnings/history.tsx +40 -10
  191. package/src/plugins/builtin/earnings/index.tsx +3 -1
  192. package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
  193. package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
  194. package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
  195. package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
  196. package/src/plugins/builtin/econ-statistics/view.ts +4 -0
  197. package/src/plugins/builtin/futures/model.ts +5 -4
  198. package/src/plugins/builtin/futures/series.ts +38 -1
  199. package/src/plugins/builtin/futures-curve/client.ts +4 -4
  200. package/src/plugins/builtin/futures-curve/headless.ts +33 -8
  201. package/src/plugins/builtin/futures-curve/index.tsx +4 -4
  202. package/src/plugins/builtin/futures-curve/model.ts +109 -6
  203. package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
  204. package/src/plugins/builtin/insider/headless.ts +74 -4
  205. package/src/plugins/builtin/insider/index.tsx +99 -5
  206. package/src/plugins/builtin/insider/signals-client.ts +69 -0
  207. package/src/plugins/builtin/insider/signals-model.ts +146 -0
  208. package/src/plugins/builtin/insider/signals.tsx +176 -0
  209. package/src/plugins/builtin/iv-history/client.ts +41 -2
  210. package/src/plugins/builtin/iv-history/headless.ts +24 -16
  211. package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
  212. package/src/plugins/builtin/jodi/client.ts +42 -205
  213. package/src/plugins/builtin/jodi/headless.ts +13 -4
  214. package/src/plugins/builtin/jodi/model.ts +18 -43
  215. package/src/plugins/builtin/jodi/pane.tsx +5 -0
  216. package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
  217. package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
  218. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  219. package/src/plugins/builtin/macro-day/release-table.ts +3 -3
  220. package/src/plugins/builtin/market-movers/client.ts +32 -6
  221. package/src/plugins/builtin/market-movers/headless.ts +7 -2
  222. package/src/plugins/builtin/market-movers/model.ts +64 -3
  223. package/src/plugins/builtin/market-movers/screener.ts +20 -9
  224. package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
  225. package/src/plugins/builtin/market-valuation/headless.ts +17 -4
  226. package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
  227. package/src/plugins/builtin/market-valuation/view.ts +10 -0
  228. package/src/plugins/builtin/mna/model.ts +2 -1
  229. package/src/plugins/builtin/options-positioning/client.ts +10 -0
  230. package/src/plugins/builtin/options-positioning/headless.ts +88 -13
  231. package/src/plugins/builtin/options-positioning/index.tsx +2 -2
  232. package/src/plugins/builtin/options-positioning/model.ts +47 -0
  233. package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
  234. package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
  235. package/src/plugins/builtin/options-scenario/client.ts +111 -17
  236. package/src/plugins/builtin/options-scenario/collar.ts +141 -0
  237. package/src/plugins/builtin/options-scenario/csp.ts +1 -1
  238. package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
  239. package/src/plugins/builtin/options-scenario/headless.ts +28 -12
  240. package/src/plugins/builtin/options-scenario/index.tsx +1 -1
  241. package/src/plugins/builtin/options-scenario/model.ts +55 -5
  242. package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
  243. package/src/plugins/builtin/perps/client.ts +77 -6
  244. package/src/plugins/builtin/perps/headless.ts +25 -14
  245. package/src/plugins/builtin/perps/index.tsx +1 -1
  246. package/src/plugins/builtin/perps/model.ts +32 -0
  247. package/src/plugins/builtin/perps/pane.tsx +5 -4
  248. package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
  249. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
  250. package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
  251. package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
  252. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
  253. package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
  254. package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
  255. package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
  256. package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
  257. package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
  258. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
  259. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
  260. package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
  261. package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
  262. package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
  263. package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
  264. package/src/plugins/builtin/rate-path/model.ts +1 -1
  265. package/src/plugins/builtin/rate-path/pane.tsx +2 -1
  266. package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
  267. package/src/plugins/builtin/research/event-model.ts +41 -49
  268. package/src/plugins/builtin/research/event-sources.ts +1 -1
  269. package/src/plugins/builtin/research/headless.ts +1 -1
  270. package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
  271. package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
  272. package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
  273. package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
  274. package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
  275. package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
  276. package/src/plugins/builtin/sectors/client.ts +46 -19
  277. package/src/plugins/builtin/sectors/sector-model.ts +22 -10
  278. package/src/plugins/builtin/shared/earnings-report.ts +48 -1
  279. package/src/plugins/builtin/shared/history-span.ts +55 -0
  280. package/src/plugins/builtin/shared/report-freshness.ts +20 -11
  281. package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
  282. package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
  283. package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
  284. package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
  285. package/src/plugins/builtin/themes/headless.ts +15 -2
  286. package/src/plugins/builtin/thirteenf/api.ts +60 -43
  287. package/src/plugins/builtin/thirteenf/data.ts +76 -35
  288. package/src/plugins/builtin/thirteenf/headless.ts +2 -0
  289. package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
  290. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
  291. package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
  292. package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
  293. package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
  294. package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
  295. package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
  296. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  297. package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
  298. package/src/plugins/builtin/yield-curve/curves.ts +15 -1
  299. package/src/plugins/builtin/yield-curve/headless.ts +17 -7
  300. package/src/plugins/builtin/yield-curve/history.ts +2 -0
  301. package/src/plugins/builtin/yield-curve/index.tsx +23 -18
  302. package/src/plugins/builtin/yield-curve/series.ts +19 -9
  303. package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
  304. package/src/plugins/builtin/yield-curve/world.ts +4 -0
  305. package/src/plugins/registry/host-actions.ts +3 -0
  306. package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
  307. package/src/portfolio/valuation-basis.ts +4 -21
  308. package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
  309. package/src/remote/controller.ts +27 -7
  310. package/src/remote/schema.ts +25 -9
  311. package/src/renderers/browser/app-services.ts +7 -1
  312. package/src/renderers/browser/config-host.ts +55 -2
  313. package/src/renderers/browser/first-screen.ts +193 -0
  314. package/src/renderers/browser/main.tsx +59 -7
  315. package/src/renderers/cloudflare/http-proxy.ts +21 -13
  316. package/src/renderers/dom/desktop/controls.tsx +2 -0
  317. package/src/renderers/dom/desktop/query-bar.tsx +1 -1
  318. package/src/renderers/dom/dom-ui-host.tsx +2 -0
  319. package/src/renderers/dom/windowed-list.tsx +201 -0
  320. package/src/renderers/dom/windowed-rows.ts +65 -0
  321. package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
  322. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
  323. package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
  324. package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
  325. package/src/renderers/electrobun/shared/protocol.ts +7 -4
  326. package/src/renderers/electrobun/view/http-fetch.ts +12 -1
  327. package/src/renderers/electrobun/view/update-host.ts +2 -2
  328. package/src/shares/bug-report-dialog.tsx +162 -0
  329. package/src/shares/bug-report-flow.tsx +84 -0
  330. package/src/shares/bug-report.ts +42 -0
  331. package/src/shares/capture-flow.ts +19 -0
  332. package/src/shares/capture-preview.tsx +129 -0
  333. package/src/shares/chat.ts +10 -1
  334. package/src/shares/dialog.tsx +16 -91
  335. package/src/shares/flow.tsx +12 -16
  336. package/src/shares/personal-pane.ts +35 -1
  337. package/src/sources/listing-symbols.ts +8 -4
  338. package/src/state/app/context/index.tsx +13 -0
  339. package/src/state/app/tab-cursor.ts +85 -0
  340. package/src/sync/core-contributors.ts +206 -59
  341. package/src/telemetry/usage-counts.ts +16 -0
  342. package/src/test-support/config-store.ts +31 -0
  343. package/src/test-support/moved-symbols.ts +64 -0
  344. package/src/theme/colors.ts +30 -0
  345. package/src/tickers/listing-fate.ts +242 -0
  346. package/src/tickers/open-target.ts +61 -11
  347. package/src/tickers/search/index.ts +45 -3
  348. package/src/tickers/search/ranking.ts +19 -2
  349. package/src/tickers/search/types.ts +3 -0
  350. package/src/tickers/search/upsert.ts +22 -7
  351. package/src/tickers/ticker-changes.ts +26 -9
  352. package/src/time-series/bar-spacing.ts +19 -0
  353. package/src/time-series/chart-data.ts +37 -13
  354. package/src/time-series/field-catalog.ts +5 -2
  355. package/src/time-series/forward-valuation.ts +10 -7
  356. package/src/time-series/fundamentals.ts +49 -11
  357. package/src/time-series/hooks.ts +2 -0
  358. package/src/time-series/live-bars.ts +3 -1
  359. package/src/time-series/parsed-history-cache.ts +2 -0
  360. package/src/time-series/price-units.ts +69 -0
  361. package/src/time-series/resolve.ts +71 -11
  362. package/src/time-series/session-bar.ts +138 -0
  363. package/src/time-series/spec.ts +20 -3
  364. package/src/time-series/studies.ts +211 -12
  365. package/src/time-series/transforms.ts +2 -0
  366. package/src/time-series/types.ts +11 -1
  367. package/src/time-series/valuation-price.ts +13 -0
  368. package/src/types/config.ts +24 -1
  369. package/src/types/financials.ts +2 -0
  370. package/src/types/headless.ts +27 -0
  371. package/src/types/plugin.ts +7 -0
  372. package/src/ui/host.tsx +5 -0
  373. package/src/updater/channel.ts +51 -0
  374. package/src/updater/github-releases.ts +85 -16
  375. package/src/updater/index.ts +128 -17
  376. package/src/utils/asset-category.ts +20 -0
  377. package/src/utils/datetime-format.ts +7 -1
  378. package/src/utils/decimal-comma.ts +83 -3
  379. package/src/utils/fundamentals.ts +7 -2
  380. package/src/utils/http-proxy-response.ts +66 -9
  381. package/src/utils/plugin-proxy-hosts.json +1 -5
  382. package/src/utils/semver.ts +47 -2
  383. package/src/utils/typo-distance.ts +22 -0
  384. package/src/plugins/builtin/shared/zip-text.ts +0 -46
@@ -27,7 +27,8 @@ import {
27
27
  getExtendedSessionDisplay,
28
28
  getQuoteSessionFields,
29
29
  getRegularSessionDisplay,
30
- marketStateLabel,
30
+ quoteHeadlineTime,
31
+ quoteMarketStateLabel,
31
32
  } from "../../market-data/market/status";
32
33
  import type { FuturesContractTermsPayload } from "../../api-client/futures-curve";
33
34
  import { futuresContractLines, futuresSettleText, isFuturesInstrument, loadFuturesContractTerms } from "../../market-data/futures-contract";
@@ -59,8 +60,10 @@ import { NotesFiles } from "../../plugins/builtin/notes/files";
59
60
  import { isKnownNonUsListing } from "../../utils/sec";
60
61
  import { canonicalExchange, exchangeLabel, isKnownExchangeCode, isUsListingExchange } from "../../utils/exchanges";
61
62
  import {
62
- failIfNotTraded, ListingArgError, listingChoiceNotes, listingIdentity, loadBareVenues, resolveCliListing, type CliListing,
63
+ failIfNotTraded, failMovedSymbol, ListingArgError, listingChoiceNotes, listingFateLoader, listingIdentity, loadBareVenues,
64
+ resolveCliListing, type CliListing, type ListingDataRequest,
63
65
  } from "../listing-arg";
66
+ import { movedAwayChange } from "../../tickers/ticker-changes";
64
67
  import { isNotATickerMessage } from "../not-a-ticker";
65
68
  import { sharesOutstandingInReceipts } from "../../utils/depositary-receipt";
66
69
  import { cliFreshnessFooter } from "../result";
@@ -714,7 +717,7 @@ export async function buildTickerReport({
714
717
  listingVenueLabel(listingExchange, quote, financials, tickerFile) || undefined,
715
718
  (quote?.currency || financials.quoteMetadata?.currency || tickerFile?.metadata.currency)
716
719
  ? `Currency ${quote?.currency || financials.quoteMetadata?.currency || tickerFile?.metadata.currency}` : undefined,
717
- quote?.marketState ? marketStateLabel(quote.marketState) : undefined,
720
+ quoteMarketStateLabel(quote) ?? undefined,
718
721
  quote?.dataSource ? `Source ${quote.dataSource.toUpperCase()}` : undefined,
719
722
  ].filter((part): part is string => !!part);
720
723
  if (summaryParts.length > 0) {
@@ -795,7 +798,7 @@ export async function buildTickerReport({
795
798
  // A perpetual's price comes with no volume; its 0 means not reported, not no trades.
796
799
  ["Volume", quote.volume != null && !(quote.volume === 0 && isCryptoVenuePerpetual(quote.instrumentType, quote.name))
797
800
  ? formatNumber(quote.volume, 0) : "—"],
798
- ["Updated", formatTimestamp(quote.lastUpdated)],
801
+ ["Updated", formatTimestamp(quoteHeadlineTime(quote))],
799
802
  ]);
800
803
 
801
804
  if (futuresContract) appendMetricSection(lines, "Contract", futuresContractMetrics(futuresContract, quote.symbol ?? symbol));
@@ -989,6 +992,17 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
989
992
  const toBase = createBaseConverter(dataProvider, config.baseCurrency);
990
993
  // A bare symbol's venues load beside its research, to name the company it was read as when several list it.
991
994
  const venues = loadBareVenues(listing, { store, dataProvider });
995
+ // So does what became of the symbol, read from the research's quote as `history` reads it: a
996
+ // company that moved (HFC became DINO), with nothing new listed there, has no profile of its own.
997
+ const researchQuote = Promise.withResolvers<Quote | null>();
998
+ const fate = listingFateLoader(listing, { dataProvider }, researchQuote.promise);
999
+ const request: ListingDataRequest = { command: "ticker", noun: "research", fate };
1000
+ /** Nothing to show: a company that moved is under its new symbol. */
1001
+ const failIfMovedAway = async () => {
1002
+ researchQuote.resolve(null);
1003
+ const moved = movedAwayChange((await fate()).change);
1004
+ if (moved) failMovedSymbol(moved, request);
1005
+ };
992
1006
 
993
1007
  let financials: TickerFinancials | null = null;
994
1008
  try {
@@ -996,6 +1010,7 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
996
1010
  } catch (error) {
997
1011
  // A known exchange the symbol is not listed on: say where it is.
998
1012
  await failIfNotTraded(listing, { store, dataProvider }, { fail: failCommand });
1013
+ await failIfMovedAway();
999
1014
  const reason = error instanceof Error ? error.message : String(error);
1000
1015
  // A symbol no listing carries is not a failed fetch, and the message already names it.
1001
1016
  if (isNotATickerMessage(reason)) failCommand(reason);
@@ -1016,10 +1031,15 @@ export async function ticker(symbol: string, dependencies: TickerCommandDependen
1016
1031
  );
1017
1032
  if (!financials || (!hasResearchData && !tickerFile?.metadata.positions.some((position) => position.shares !== 0))) {
1018
1033
  await failIfNotTraded(listing, { store, dataProvider }, { fail: failCommand });
1034
+ await failIfMovedAway();
1019
1035
  failCommand(`No research data available for ${normalized}.`);
1020
1036
  }
1021
1037
  const resolvedFinancials = financials as TickerFinancials;
1022
1038
  const quote = resolvedFinancials.quote;
1039
+ // The quote is the old line's (stopped, or named for the company that left): the profile and figures are another security's.
1040
+ researchQuote.resolve(quote ?? null);
1041
+ const { moved } = await fate();
1042
+ if (moved) failMovedSymbol(moved, request);
1023
1043
 
1024
1044
  const notesFiles = new NotesFiles(dataDir);
1025
1045
  const listingName = listingIdentity(listing, quote).name;
@@ -13,7 +13,9 @@ import { getLoadablePlugins } from "../plugins/catalog";
13
13
  import { standaloneSeriesInvoker } from "../series/standalone";
14
14
  import type { PluginRegistry } from "../plugins/registry";
15
15
  import type { LoadedExternalPlugin } from "../plugins/loader";
16
+ import { apiClient } from "../api-client";
16
17
  import { fail } from "./errors";
18
+ import { resolvePersistedCloudSessionToken } from "./pane-functions/cloud-session";
17
19
  import type { ConfigContext, MarketContext } from "./types";
18
20
 
19
21
  export { withCliServices, withConfigData, withMarketData } from "./scoped-context";
@@ -105,8 +107,18 @@ export async function initConfigData(): Promise<ConfigContext> {
105
107
  return { config, persistence, store, dataDir };
106
108
  }
107
109
 
110
+ /**
111
+ * Market commands (`quote`, `compare`, `history`...) run with the session the
112
+ * app saved after sign-in, as `fn` does: Gloom Cloud answers a Pro account
113
+ * with real-time quotes and anyone else with delayed ones.
114
+ */
115
+ function applyPersistedCloudSession(context: Pick<MarketContext, "persistence">): void {
116
+ apiClient.setSessionToken(resolvePersistedCloudSessionToken(context));
117
+ }
118
+
108
119
  export async function initMarketData(options: CliContextOptions = {}): Promise<MarketContext> {
109
120
  const context = await initConfigData();
121
+ applyPersistedCloudSession(context);
110
122
  const plugins = options.plugins ?? getLoadablePlugins();
111
123
  const capabilities = resolveCliCapabilities(context.config, plugins);
112
124
  const dataProvider = new AssetDataRouter(null, [], context.persistence.resources);
@@ -131,6 +143,7 @@ export async function initCliServices(options: CliServicesOptions = {}) {
131
143
  services.providerRouter.setConfigAccessor(() => config);
132
144
  services.providerRouter.setBackgroundRevalidation(false);
133
145
  await ensureCliServicesReady(services);
146
+ applyPersistedCloudSession(services);
134
147
  return {
135
148
  config,
136
149
  dataDir,
package/src/cli/errors.ts CHANGED
@@ -22,13 +22,16 @@ class CliFailure extends Error {
22
22
  readonly code: string;
23
23
  readonly details?: unknown;
24
24
  readonly retryable?: boolean;
25
+ /** What a structured format carries beside the message; text prints the message alone. */
26
+ readonly fields?: Record<string, unknown>;
25
27
 
26
- constructor(message: string, details?: unknown, code = CLI_ERROR_CODE, retryable?: boolean) {
28
+ constructor(message: string, details?: unknown, code = CLI_ERROR_CODE, retryable?: boolean, fields?: Record<string, unknown>) {
27
29
  super(message);
28
30
  this.name = "CliFailure";
29
31
  this.code = code;
30
32
  this.details = details;
31
33
  this.retryable = retryable;
34
+ this.fields = fields;
32
35
  }
33
36
  }
34
37
 
@@ -36,12 +39,21 @@ export function fail(message: string, details?: unknown, code?: string): never {
36
39
  throw new CliFailure(message, details, code);
37
40
  }
38
41
 
42
+ /**
43
+ * Fails with its own code and the fields `--json` reads beside the message:
44
+ * `{ ok: false, error: { code, message, ...fields } }`. Text prints the
45
+ * message alone, and the exit status is any failure's.
46
+ */
47
+ export function failWithFields(message: string, code: string, fields: Record<string, unknown>): never {
48
+ throw new CliFailure(message, undefined, code, undefined, fields);
49
+ }
50
+
39
51
  export function closeAndFail(persistence: AppPersistence, message: string, details?: string): never {
40
52
  persistence.close();
41
53
  fail(message, details);
42
54
  }
43
55
 
44
- function isCliFailure(error: unknown): error is CliFailure {
56
+ export function isCliFailure(error: unknown): error is CliFailure {
45
57
  return error instanceof CliFailure;
46
58
  }
47
59
 
@@ -57,6 +69,7 @@ function cliErrorObject(error: unknown): CliErrorObject {
57
69
  message: error.message,
58
70
  details: error.details,
59
71
  retryable: error.retryable,
72
+ ...error.fields,
60
73
  };
61
74
  }
62
75
  // Gloom Cloud refused what was asked (an unknown series, a bad date): the input was wrong, not the program.
@@ -1,3 +1,6 @@
1
+ import { currencyMinorDigits } from "../market-data/market/format";
2
+ import { parseTypedNumber, readCurrencyAmount } from "../utils/decimal-comma";
3
+
1
4
  /**
2
5
  * What `gloomberb fx` is asked for. A bare code is one unit of it in the base
3
6
  * currency (`NGN`: USD per 1 NGN). A pair is market convention, BASE/QUOTE:
@@ -42,6 +45,80 @@ export function parseFxRequest(raw: string, baseCurrency: string): FxRequest | {
42
45
  : { currency: parts[0]!, baseCurrency: baseCurrency.trim().toUpperCase() };
43
46
  }
44
47
 
48
+ /** What `gloomberb fx` is asked for, with the amount to convert when one comes first. */
49
+ export interface FxArgs {
50
+ request: FxRequest;
51
+ /** The amount of `currency` to convert into `baseCurrency`; absent for a rate alone. */
52
+ amount?: number;
53
+ /** One currency after an amount names no target, so it converts to USD; the answer says so. */
54
+ toUsdByDefault?: boolean;
55
+ }
56
+
57
+ /** Every form `fx` takes, for its usage line. */
58
+ export const FX_USAGE = "fx <currency> | fx <base>/<quote> | fx <amount> <from>/<to> | fx <amount> <from> <to>";
59
+
60
+ /**
61
+ * `fx` arguments: a currency or a pair (`EUR`, `USD/NGN`), or an amount and
62
+ * what to convert it from and to (`1234.5 EUR/USD`, `1234,5 EUR USD`). The
63
+ * amount is an amount of the first currency and reads as cash does
64
+ * (`readCurrencyAmount`): `1.234,5` and `1,234.5` are 1234.5, and `1,234` or
65
+ * `1.234` are thousands unless that currency has three decimals, where they are
66
+ * refused naming both readings. An amount and one currency converts to USD
67
+ * (`toUsdByDefault`). The error sentence otherwise.
68
+ */
69
+ export function parseFxArgs(args: readonly string[], baseCurrency: string): FxArgs | { error: string } {
70
+ if (args.length === 1) {
71
+ const request = parseFxRequest(args[0]!, baseCurrency);
72
+ if (!("error" in request)) return { request };
73
+ // A lone number is an amount with nothing to convert it from.
74
+ return parseTypedNumber(args[0]).kind === "invalid"
75
+ ? request
76
+ : { error: `fx ${args[0]!.trim()} names no currency; convert an amount as fx ${args[0]!.trim()} EUR/USD.` };
77
+ }
78
+ if (args.length === 0 || args.length > 3) {
79
+ return { error: `fx takes a currency, a pair, or an amount and a pair (fx 1234.5 EUR/USD); got ${args.length} arguments.` };
80
+ }
81
+ const typed = args.map((arg) => arg.trim());
82
+ const [first, ...codes] = typed;
83
+ if (parseTypedNumber(first).kind === "invalid") {
84
+ // The amount typed after the pair (fx EUR/USD 100) belongs in front.
85
+ if (parseTypedNumber(typed.at(-1)).kind === "number") {
86
+ return { error: `Put the amount first: fx ${[typed.at(-1), ...typed.slice(0, -1)].join(" ")}.` };
87
+ }
88
+ // Two codes are one pair (fx NGN ZAR is NGN/ZAR), never two answers.
89
+ const pair = typed.length === 2 && typed.every((arg) => /^[A-Za-z]{3}$/.test(arg)) ? typed.join("/").toUpperCase() : "EUR/USD";
90
+ return { error: `fx takes one currency or pair; got ${typed.join(" ")}. Write a pair as ${pair}, or convert an amount as fx 1234.5 ${pair}.` };
91
+ }
92
+ const toUsdByDefault = codes.length === 1 && !codes[0]!.includes("/");
93
+ const request = parseFxRequest(codes.join("/"), "USD");
94
+ if ("error" in request) return request;
95
+ // The amount is in the currency converted from, so its units say what 1,234 means.
96
+ let amount: number;
97
+ try {
98
+ amount = readCurrencyAmount(first, "The amount", request.currency);
99
+ } catch (error) {
100
+ return { error: error instanceof Error ? error.message : String(error) };
101
+ }
102
+ return { request, amount, ...(toUsdByDefault ? { toUsdByDefault } : {}) };
103
+ }
104
+
105
+ /**
106
+ * An amount in its currency's minor units, grouped (1,234.50 EUR; 1,235 JPY).
107
+ * One too small for them keeps six significant figures instead of reading 0.
108
+ */
109
+ function formatFxAmount(amount: number, currency: string): string {
110
+ const digits = currencyMinorDigits(currency);
111
+ const rounded = Number(amount.toFixed(digits));
112
+ if (rounded === 0 && amount !== 0) return `${formatFxRate(amount)} ${currency}`;
113
+ const text = amount.toLocaleString("en-US", { minimumFractionDigits: digits, maximumFractionDigits: digits });
114
+ return `${text} ${currency}`;
115
+ }
116
+
117
+ /** `1,234.50 EUR = 1,383.47 USD`: the amount and what it converts to at the rate. */
118
+ export function describeFxConversion(request: FxRequest, amount: number, rate: number): string {
119
+ return `${formatFxAmount(amount, request.currency)} = ${formatFxAmount(amount * rate, request.baseCurrency)}`;
120
+ }
121
+
45
122
  /**
46
123
  * The rate of `currency` in `baseCurrency` from each one's USD value (USD per
47
124
  * unit), the way every cross is built. NaN when a leg is missing or unusable.
@@ -2,7 +2,7 @@ import { cliStyles, cliTerminalWidth, renderDefinitions, renderSection, wrapText
2
2
 
3
3
  /**
4
4
  * Plain-words meanings of the rates, auction, energy, perpetual-futures, portfolio,
5
- * fund, short interest, quote-board, futures and economic-calendar terms the functions
5
+ * fund, short interest, quote-board, futures (basis, contango, roll yield) and economic-calendar terms the functions
6
6
  * show in their rows, headers and footers.
7
7
  * `catalog glossary` lists them, `catalog explain <term>` prints one, and
8
8
  * `fn <function> --explain` and `portfolio show --explain` follow a report with
@@ -301,10 +301,11 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
301
301
  + "The funding rate keeps its price close to the asset it tracks.",
302
302
  },
303
303
  {
304
- term: "Funding rate", aliases: ["funding", "funding payment", "funding fee", "funding 8h"], functions: ["PERP"],
305
- shown: ["Funding %/int", "Funding 8h", "Paid %/int"],
304
+ term: "Funding rate", aliases: ["funding", "funding payment", "funding fee", "funding 8h", "funding apr", "apr"], functions: ["PERP"],
305
+ shown: ["Funding %/int", "Funding 8h", "Paid %/int", "APR %"],
306
306
  definition: "A regular payment between the two sides of a perpetual contract, set to pull its price toward the asset's. "
307
- + "Positive means longs pay shorts, negative means shorts pay longs.",
307
+ + "Positive means longs pay shorts, negative means shorts pay longs, so a basis trade (long spot, short the perpetual) collects it when positive. "
308
+ + "APR % is the current 8h rate × 3 × 365: a simple annualisation of the rate now, not funding actually paid.",
308
309
  },
309
310
  {
310
311
  term: "Open interest", short: "OI", aliases: ["open interest usd"], functions: ["PERP"],
@@ -473,6 +474,35 @@ export const GLOSSARY: readonly GlossaryEntry[] = [
473
474
  definition: "A futures contract's official price for the day, set by its exchange from trading near the close. "
474
475
  + "The day's change and margin are measured from it, and it can differ from the last trade. FUT's PREV is the previous one.",
475
476
  },
477
+ // Futures basis and curve shape (CTM, FUT, PERP).
478
+ {
479
+ term: "Futures basis", aliases: ["basis", "basis trade", "cash and carry", "cash-and-carry", "vs spot", "spot spread"],
480
+ functions: ["CTM", "FUT", "PERP"], shown: ["Vs Spot", "VS SPOT"],
481
+ definition: "The gap between a futures price and the spot price of what it tracks, in percent: positive when the future costs more, negative when it costs less. "
482
+ + "It closes by expiry. A basis trade buys spot and sells the future to earn it. A perpetual has no expiry, so its funding rate plays that part.",
483
+ },
484
+ {
485
+ term: "Annualised basis", aliases: ["annualized basis", "ann basis", "ann. basis", "basis annualised"], functions: ["CTM", "PERP"], shown: ["Ann Basis", "ANN BASIS"],
486
+ definition: "The basis restated as a yearly rate, so contracts of different length compare with each other and with a perpetual's funding APR: "
487
+ + "(Last / spot - 1) × 365 / days to expiry, simple rather than compounded, on the latest trade against the spot quote. "
488
+ + "Inside 30 days a few basis points of spot noise move it by whole points: 10 bp is about ±2 points at 18 days to expiry.",
489
+ },
490
+ {
491
+ term: "Contango", aliases: ["contango curve", "upward curve"], functions: ["CTM", "FUT"], shown: ["contango", "Contango"],
492
+ definition: "A futures curve that slopes up: each contract costs more than the one before it, and the future trades above spot. "
493
+ + "A long rolling into the next contract pays that difference, a short earns it, and the basis closes as expiry nears.",
494
+ },
495
+ {
496
+ term: "Backwardation", aliases: ["backwardation curve", "downward curve", "inverted futures curve"], functions: ["CTM", "FUT"], shown: ["backwardation", "Backwardation"],
497
+ definition: "A futures curve that slopes down: later contracts cost less than nearer ones, and the future trades below spot, often when the asset is scarce now. "
498
+ + "A long rolling into the next contract earns that difference, a short pays it.",
499
+ },
500
+ {
501
+ term: "Roll yield", aliases: ["roll", "roll return", "annualised roll yield", "annualized roll yield", "ann roll yield", "ann. roll yield"], functions: ["CTM", "FUT"],
502
+ shown: ["Ann. roll yield", "Annualized Roll Yield"],
503
+ definition: "What a long future earns or loses as it is rolled into the next contract. CTM's annualised roll yield is the front price over the next contract's "
504
+ + "less 1, scaled to a year: (front / next - 1) × 365 / days between the two. It is positive in backwardation and negative in contango.",
505
+ },
476
506
  // The economic calendar (ECO).
477
507
  {
478
508
  term: "Actual", aliases: ["actual release", "actual value"], functions: ["ECO"], shown: ["ACTUAL", "Actual"],
@@ -16,6 +16,8 @@ import { getPricePointTimestamp } from "../utils/price-history";
16
16
  import { pricePointIntegrityFlags, pricePointValues } from "../utils/price-history-integrity";
17
17
  import { formatUtcTime } from "../utils/utc-time";
18
18
  import { historyCoverageNotice } from "../sources/history-coverage";
19
+ import { barPeriodToDate, type BarPeriodToDate } from "../market-data/market/bar-period";
20
+ import { barSizeFromGap, medianBarGap } from "../time-series/bar-spacing";
19
21
 
20
22
  const PRICE_KEYS = ["open", "high", "low", "close"] as const;
21
23
  const DAY_MS = 24 * 60 * 60 * 1000;
@@ -33,7 +35,7 @@ const BREAK_JUMP_RATIO = 100;
33
35
  const BREAK_FLAGS: ReadonlySet<string> = new Set(["gap", "jump"]);
34
36
  const FIRST_MONDAY_MS = Date.UTC(1970, 0, 5);
35
37
 
36
- const BAR_INTERVAL_LABELS: Record<ManualChartResolution, string> = {
38
+ export const BAR_INTERVAL_LABELS: Record<ManualChartResolution, string> = {
37
39
  "1m": "1-minute",
38
40
  "5m": "5-minute",
39
41
  "15m": "15-minute",
@@ -107,32 +109,25 @@ function isUtcMidnight(time: number): boolean {
107
109
  return new Date(time).toISOString().endsWith("T00:00:00.000Z");
108
110
  }
109
111
 
110
- function medianGap(times: readonly number[]): number | null {
111
- const finite = times.filter(Number.isFinite).sort((left, right) => left - right);
112
- const gaps = finite.slice(1).map((time, index) => time - finite[index]!).filter((gap) => gap > 0).sort((left, right) => left - right);
113
- return gaps.length > 0 ? gaps[Math.floor((gaps.length - 1) / 2)]! : null;
114
- }
115
-
116
112
  /** Without a declared cadence, bar spacing decides: daily bars may be stamped at the session open. */
117
113
  function looksIntraday(times: readonly number[]): boolean {
118
114
  const finite = times.filter(Number.isFinite);
119
115
  if (finite.length < 2) return finite.some((time) => !isUtcMidnight(time));
120
- const gap = medianGap(finite);
116
+ const gap = medianBarGap(finite);
121
117
  return gap != null && gap < INTRADAY_MAX_GAP_MS;
122
118
  }
123
119
 
124
120
  /** The bar size served: as the source declared it, otherwise read from the typical spacing of the bars. */
125
121
  function historyInterval(times: readonly number[], resolution: ManualChartResolution | null): ManualChartResolution | null {
126
122
  if (resolution) return resolution;
127
- const gap = medianGap(times);
123
+ const gap = medianBarGap(times);
128
124
  if (gap == null) return null;
129
125
  if (gap < INTRADAY_MAX_GAP_MS) {
130
126
  return INTRADAY_RESOLUTIONS.reduce((best, candidate) => (
131
127
  Math.abs(CHART_RESOLUTION_STEP_MS[candidate] - gap) < Math.abs(CHART_RESOLUTION_STEP_MS[best] - gap) ? candidate : best
132
128
  ));
133
129
  }
134
- const days = gap / DAY_MS;
135
- return days >= 25 ? "1mo" : days >= 6 ? "1wk" : "1d";
130
+ return barSizeFromGap(gap);
136
131
  }
137
132
 
138
133
  /**
@@ -233,6 +228,25 @@ export function historyRows(
233
228
  });
234
229
  }
235
230
 
231
+ /**
232
+ * The newest weekly or monthly bar while its period has not ended at `now`
233
+ * (barPeriodToDate): marked `partial`, as a bar its source marks is, with
234
+ * how far into its period it runs, which the status line names (`month to
235
+ * date, through Thu 15 Oct 2026`). Daily and intraday bars are left as the
236
+ * source marks them. Null when the newest bar is complete.
237
+ */
238
+ export function formingPeriodBar(
239
+ rows: readonly HistoryRow[],
240
+ venue: string | undefined,
241
+ now = Date.now(),
242
+ ): { rows: HistoryRow[]; periodToDate: BarPeriodToDate } | null {
243
+ const last = rows.at(-1);
244
+ const period = last?.interval === "1mo" ? "month" : last?.interval === "1wk" ? "week" : null;
245
+ const forming = last && period ? barPeriodToDate(last.date, period, venue, now) : null;
246
+ if (!forming) return null;
247
+ return { rows: [...rows.slice(0, -1), { ...last!, partial: true }], periodToDate: forming };
248
+ }
249
+
236
250
  /**
237
251
  * The volume of a coin's history: traded value in the quote currency the
238
252
  * prices are in. Null for anything else, whose volume stays unlabelled.
package/src/cli/index.ts CHANGED
@@ -37,8 +37,8 @@ import { createSystemCliCommands } from "./commands/system";
37
37
  import { brokerCliCommand, ibkrCliCommand } from "./commands/broker";
38
38
  import { listPlugins, updatePlugins } from "./commands/plugins";
39
39
  import { installListedPlugin, removePlugin } from "../plugins/installer";
40
- import { catalogCommandHits, functionCodeCommand, runPaneCatalog, runPaneFunction, runPaneScreenshot } from "./pane-functions";
41
- import { catalogPointer, type CatalogCommandHit } from "./unknown-command";
40
+ import { catalogCommandHits, functionCodeCommand, nearFunctionCodeCommand, runPaneCatalog, runPaneFunction, runPaneScreenshot } from "./pane-functions";
41
+ import { catalogPointer, tickerPointer, type CatalogCommandHit } from "./unknown-command";
42
42
  import { catalogFredSeriesIds } from "./series-search";
43
43
 
44
44
  const { width: SHOT_WIDTH, height: SHOT_HEIGHT } = SHOT_SIZE_LIMITS;
@@ -251,6 +251,12 @@ function createCoreCliCommands(
251
251
  help: {
252
252
  group: CLI_COMMAND_GROUPS.plugins,
253
253
  usage: ["update [name]"],
254
+ sections: [{
255
+ title: "Updating Gloomberb itself",
256
+ lines: [
257
+ "The app updates itself from stable releases. config set updates.channel nightly adds the nightly build of main; see gloomberb help config.",
258
+ ],
259
+ }],
254
260
  examples: ["update", "update gloom-tv"],
255
261
  },
256
262
  execute: async (args) => {
@@ -317,6 +323,8 @@ function nearCommand(token: string, commands: CliCommandDef[]): string | null {
317
323
  interface UnknownCommandPointers {
318
324
  /** The word is a function's code: the command that runs it. */
319
325
  fn: { code: string; command: string } | null;
326
+ /** The word is a slip from one function's code (RCDF for RDCF), read only when it is no code, no command is close to it and the catalog finds nothing for it. */
327
+ nearFn: { code: string; label: string; command: string } | null;
320
328
  /** Functions the catalog finds for the word, read only when it is no code and no command is close to it. */
321
329
  hits: CatalogCommandHit[];
322
330
  /** What the reader typed after the word, which a pointer runs the function with. */
@@ -331,24 +339,34 @@ async function unknownCommandPointers(
331
339
  commands: CliCommandDef[],
332
340
  ): Promise<UnknownCommandPointers> {
333
341
  const fn = await functionCodeCommand(ctx, token, args);
334
- const hits = fn || nearCommand(token, commands) ? [] : await catalogCommandHits(ctx, token);
335
- return { fn, hits, args };
342
+ const command = fn || nearCommand(token, commands);
343
+ const hits = command ? [] : await catalogCommandHits(ctx, token);
344
+ // What the catalog finds for a word (`comps`) outranks the code it happens to be a slip from.
345
+ const nearFn = command || hits.length > 0 ? null : await nearFunctionCodeCommand(ctx, token, args);
346
+ return { fn, nearFn, hits, args };
336
347
  }
337
348
 
338
349
  /**
339
350
  * A function's code is pointed at `fn`; a typo of a command at the command;
340
- * a word the catalog knows (`comps`, `peers`) at the functions it finds.
351
+ * a typo of a function's code (`RCDF`) at that function; a word the catalog
352
+ * knows (`comps`, `peers`) at the functions it finds. A word written as a
353
+ * ticker (`PLTR`) is also pointed at `quote` and `search`.
341
354
  */
342
355
  function failUnknownCommand(token: string, commands: CliCommandDef[], pointers: UnknownCommandPointers): never {
343
356
  if (pointers.fn) fail(`${pointers.fn.code} is a function: ${pointers.fn.command}`);
357
+ const ticker = tickerPointer(token);
344
358
  const suggestion = nearCommand(token, commands);
359
+ if (!suggestion && pointers.nearFn) {
360
+ fail(
361
+ `Unknown command "${token}". Did you mean the function ${pointers.nearFn.code} (${pointers.nearFn.label})?`,
362
+ [`Run: ${pointers.nearFn.command}`, ticker].filter(Boolean).join("\n"),
363
+ );
364
+ }
345
365
  const catalog = suggestion ? null : catalogPointer(token, pointers.hits, pointers.args);
346
- fail(
347
- `Unknown command "${token}".`,
348
- suggestion
349
- ? `Did you mean ${suggestion}? Run gloomberb help to see every command.`
350
- : catalog ? `${catalog}\nRun gloomberb help to see every command.` : "Run gloomberb help to see every command.",
351
- );
366
+ const help = suggestion
367
+ ? `Did you mean ${suggestion}? Run gloomberb help to see every command.`
368
+ : catalog ? `${catalog}\nRun gloomberb help to see every command.` : "Run gloomberb help to see every command.";
369
+ fail(`Unknown command "${token}".`, ticker ? `${ticker}\n${help}` : help);
352
370
  }
353
371
 
354
372
  const CLI_DESCRIPTION = "Market research and portfolio tracker for the terminal";
@@ -443,7 +461,7 @@ export async function failUnknownCliCommand(args: string[], options: DispatchCli
443
461
  // functions, with the arguments the reader gave.
444
462
  const pointers = token && parsed
445
463
  ? await unknownCommandPointers(createCliCommandContext("core", registry, parsed.options, parsed.args.slice(1)), token, parsed.args.slice(1), commands)
446
- : { fn: null, hits: [], args: [] };
464
+ : { fn: null, nearFn: null, hits: [], args: [] };
447
465
  return failUnknownCommand(token, commands, pointers);
448
466
  }
449
467