@gloom-sh/terminal 0.19.1 → 0.19.3

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 (554) hide show
  1. package/README.md +7 -1
  2. package/package.json +2 -1
  3. package/patches/electrobun@1.18.1.patch +108 -1
  4. package/src/account/upgrade-dialog.tsx +4 -23
  5. package/src/api-client/coalesce.ts +118 -0
  6. package/src/api-client/credit-boards.ts +13 -0
  7. package/src/api-client/crypto-etf-flows.ts +6 -0
  8. package/src/api-client/crypto-markets.ts +40 -0
  9. package/src/api-client/data.ts +201 -14
  10. package/src/api-client/equity-screener.ts +10 -0
  11. package/src/api-client/errors.ts +58 -0
  12. package/src/api-client/gpu.ts +29 -0
  13. package/src/api-client/index.ts +5 -2
  14. package/src/api-client/plan-access.ts +10 -1
  15. package/src/api-client/plan-rules.ts +35 -0
  16. package/src/api-client/research-activity.ts +1 -32
  17. package/src/api-client/types.ts +21 -1
  18. package/src/app/attention-notice.tsx +94 -0
  19. package/src/app/runtime/startup.ts +3 -1
  20. package/src/app.tsx +23 -1
  21. package/src/architecture/core-builtin-allowlist.json +2 -1
  22. package/src/capabilities/chart-series.ts +2 -1
  23. package/src/capabilities/factories.ts +1 -0
  24. package/src/cli/ascii-output.ts +42 -0
  25. package/src/cli/base-converter.ts +10 -2
  26. package/src/cli/command-options.ts +16 -1
  27. package/src/cli/commands/crypto-hints.ts +23 -4
  28. package/src/cli/commands/market.ts +101 -27
  29. package/src/cli/commands/news-search.ts +24 -5
  30. package/src/cli/commands/overview.ts +14 -10
  31. package/src/cli/commands/search.ts +84 -25
  32. package/src/cli/commands/system.ts +22 -30
  33. package/src/cli/commands/ticker.ts +49 -9
  34. package/src/cli/commands/version.ts +33 -0
  35. package/src/cli/context.ts +6 -9
  36. package/src/cli/data-dir.ts +7 -0
  37. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  38. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  39. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  40. package/src/cli/desktop-pane-shot.ts +196 -256
  41. package/src/cli/early-command.ts +42 -0
  42. package/src/cli/earnings-calendar.ts +9 -0
  43. package/src/cli/entry.ts +19 -13
  44. package/src/cli/errors.ts +12 -2
  45. package/src/cli/glossary.ts +2 -1
  46. package/src/cli/help.ts +1 -1
  47. package/src/cli/history-export.ts +22 -2
  48. package/src/cli/history-rows.ts +189 -11
  49. package/src/cli/index.ts +18 -1
  50. package/src/cli/known-gaps.ts +51 -5
  51. package/src/cli/listing-arg.ts +63 -10
  52. package/src/cli/options.ts +1 -1
  53. package/src/cli/output-file.ts +60 -13
  54. package/src/cli/pane-functions/catalog.ts +14 -3
  55. package/src/cli/pane-functions/data.ts +1 -1
  56. package/src/cli/pane-functions/dom.ts +45 -0
  57. package/src/cli/pane-functions/freshness.ts +46 -9
  58. package/src/cli/pane-functions/function-help.ts +17 -9
  59. package/src/cli/pane-functions/headless.ts +22 -14
  60. package/src/cli/pane-functions/index.ts +6 -2
  61. package/src/cli/pane-functions/options.ts +6 -2
  62. package/src/cli/pane-functions/resolver.ts +27 -5
  63. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  64. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  65. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  66. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  67. package/src/cli/quote-answer.ts +49 -6
  68. package/src/cli/registry.ts +41 -13
  69. package/src/cli/result.ts +12 -6
  70. package/src/cli/ticker-resolution.ts +2 -1
  71. package/src/cli/types.ts +2 -0
  72. package/src/components/chart/composite/format.ts +3 -0
  73. package/src/components/chart-table/header.tsx +1 -1
  74. package/src/components/chart-table/layout.ts +28 -11
  75. package/src/components/command-bar/action-runtime.ts +4 -0
  76. package/src/components/command-bar/commands/direct/index.ts +18 -33
  77. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  78. package/src/components/command-bar/commands/registry.ts +10 -1
  79. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  80. package/src/components/command-bar/routes/root/runtime.ts +51 -6
  81. package/src/components/command-bar/selection-runtime.ts +12 -29
  82. package/src/components/command-bar/surface/index.tsx +9 -3
  83. package/src/components/company-logo.tsx +2 -1
  84. package/src/components/layout/detached-pane-shell.tsx +2 -2
  85. package/src/components/layout/header.tsx +10 -5
  86. package/src/components/layout/market-summary.ts +25 -7
  87. package/src/components/layout/pane/footer/index.tsx +8 -2
  88. package/src/components/layout/pane/footer/model.ts +73 -6
  89. package/src/components/layout/pane/header.tsx +2 -2
  90. package/src/components/layout/pane/status-footer.ts +2 -1
  91. package/src/components/market-board/index.tsx +3 -2
  92. package/src/components/onboarding/desks-step.tsx +12 -4
  93. package/src/components/onboarding/first-run-workspace.ts +81 -4
  94. package/src/components/onboarding/onboarding-frame.tsx +4 -2
  95. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  96. package/src/components/onboarding/onboarding-wizard.tsx +60 -26
  97. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  98. package/src/components/onboarding/run-setup-again.ts +24 -0
  99. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  100. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  101. package/src/components/onboarding/wizard-model.ts +57 -3
  102. package/src/components/onboarding/wizard-positions.ts +1 -1
  103. package/src/components/ui/delayed-mark.tsx +56 -0
  104. package/src/components/ui/display.tsx +16 -5
  105. package/src/components/ui/external-link.tsx +3 -6
  106. package/src/components/ui/icon.tsx +16 -3
  107. package/src/components/ui/logo-mark.tsx +8 -5
  108. package/src/components/ui/stat-grid.tsx +263 -19
  109. package/src/core/app-services.ts +11 -2
  110. package/src/data/app-persistence.ts +9 -7
  111. package/src/data/config/store/node.ts +11 -5
  112. package/src/data/config/store/normalize.ts +16 -3
  113. package/src/data/plugin-cache.ts +15 -0
  114. package/src/data/plugin-state-store.ts +81 -24
  115. package/src/data/profile-permissions.ts +47 -0
  116. package/src/data/resource-store.ts +165 -90
  117. package/src/data/session-store.ts +31 -8
  118. package/src/data/sqlite/access.ts +324 -0
  119. package/src/data/sqlite/checkpointer.ts +285 -0
  120. package/src/data/sqlite/database.ts +80 -4
  121. package/src/data/sqlite/retry.ts +50 -9
  122. package/src/data/ticker-store.ts +57 -23
  123. package/src/docs/snapshot.json +191 -78
  124. package/src/i18n/es.ts +18 -2
  125. package/src/i18n/ja.ts +18 -2
  126. package/src/i18n/ko.ts +18 -2
  127. package/src/i18n/zh-cn.ts +18 -2
  128. package/src/i18n/zh-tw.ts +18 -2
  129. package/src/layout/desk-keys.ts +4 -1
  130. package/src/layout/desks.ts +38 -5
  131. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  132. package/src/market-data/batched-reads.ts +16 -0
  133. package/src/market-data/coordinator/auxiliary.ts +4 -2
  134. package/src/market-data/coordinator/index.ts +11 -2
  135. package/src/market-data/futures-board.ts +1 -1
  136. package/src/market-data/futures-contract.ts +3 -1
  137. package/src/market-data/market/index-level.ts +6 -4
  138. package/src/market-data/published-apac-sessions.ts +6 -0
  139. package/src/market-data/realized-volatility.ts +4 -2
  140. package/src/news/aggregator.ts +7 -4
  141. package/src/news/news-model.ts +11 -0
  142. package/src/news/story-id.ts +22 -0
  143. package/src/news/ticker-symbols.ts +17 -1
  144. package/src/notifications/app-notifier.ts +35 -14
  145. package/src/plugins/auto-update-node.ts +10 -6
  146. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  147. package/src/plugins/builtin/account-management/index.tsx +2 -3
  148. package/src/plugins/builtin/account-management/model.ts +1 -1
  149. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  150. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  151. package/src/plugins/builtin/alerts/events.ts +10 -2
  152. package/src/plugins/builtin/alerts/index.tsx +5 -5
  153. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  154. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  155. package/src/plugins/builtin/alerts/targets.ts +4 -1
  156. package/src/plugins/builtin/attention/headless.ts +1 -1
  157. package/src/plugins/builtin/attention/index.tsx +2 -4
  158. package/src/plugins/builtin/attention/listing.ts +1 -1
  159. package/src/plugins/builtin/attention/series.ts +1 -1
  160. package/src/plugins/builtin/awards/index.tsx +1 -2
  161. package/src/plugins/builtin/backtest/client.ts +1 -0
  162. package/src/plugins/builtin/backtest/index.tsx +1 -2
  163. package/src/plugins/builtin/backtest/rules.ts +3 -1
  164. package/src/plugins/builtin/backtest/window.ts +8 -7
  165. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  166. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  167. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  168. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  169. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  170. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  171. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  172. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  173. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  174. package/src/plugins/builtin/buildout/format.ts +9 -0
  175. package/src/plugins/builtin/buildout/index.tsx +51 -5
  176. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  177. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  178. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  179. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  180. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  181. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  182. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  183. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  184. package/src/plugins/builtin/cds/headless.ts +10 -2
  185. package/src/plugins/builtin/cds/model.ts +7 -3
  186. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  187. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  188. package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
  189. package/src/plugins/builtin/changelog/index.tsx +3 -2
  190. package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
  191. package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
  192. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  193. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  194. package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
  195. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  196. package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
  197. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  198. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  199. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  200. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  201. package/src/plugins/builtin/chat/index.tsx +5 -5
  202. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  203. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  204. package/src/plugins/builtin/cloud/index.tsx +1 -2
  205. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  206. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  207. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  208. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  209. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  210. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  211. package/src/plugins/builtin/commodity-balances/client.ts +4 -1
  212. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  213. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  214. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  215. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  216. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  217. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  218. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  219. package/src/plugins/builtin/connections/index.ts +2 -3
  220. package/src/plugins/builtin/cot/client.ts +28 -2
  221. package/src/plugins/builtin/cot/headless.ts +4 -1
  222. package/src/plugins/builtin/cot/index.tsx +5 -4
  223. package/src/plugins/builtin/cot/model.ts +58 -7
  224. package/src/plugins/builtin/cot/pane.tsx +6 -2
  225. package/src/plugins/builtin/cpi/client.ts +6 -5
  226. package/src/plugins/builtin/cpi/index.tsx +1 -2
  227. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  228. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  229. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  230. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  231. package/src/plugins/builtin/credit-boards/series.ts +3 -4
  232. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  233. package/src/plugins/builtin/credit-conditions/client.ts +22 -14
  234. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  235. package/src/plugins/builtin/crypto-board/client.ts +5 -5
  236. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  237. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  238. package/src/plugins/builtin/crypto-board/series.ts +6 -4
  239. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  240. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  241. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  242. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  243. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  244. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  245. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  246. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  247. package/src/plugins/builtin/debt-maturities/series.ts +3 -3
  248. package/src/plugins/builtin/debug/index.tsx +1 -1
  249. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  250. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  251. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  252. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  253. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  254. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  255. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  256. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  257. package/src/plugins/builtin/docs/index.tsx +2 -3
  258. package/src/plugins/builtin/doe/client.ts +6 -5
  259. package/src/plugins/builtin/doe/headless.ts +16 -3
  260. package/src/plugins/builtin/doe/index.tsx +5 -4
  261. package/src/plugins/builtin/doe/prices.ts +2 -3
  262. package/src/plugins/builtin/doe/series.ts +4 -4
  263. package/src/plugins/builtin/earnings/board.tsx +2 -1
  264. package/src/plugins/builtin/earnings/history.tsx +2 -1
  265. package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
  266. package/src/plugins/builtin/earnings-calls/data.ts +19 -11
  267. package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
  268. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  269. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  270. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  271. package/src/plugins/builtin/econ/detail-view.tsx +16 -2
  272. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  273. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  274. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  275. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  276. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  277. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  278. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  279. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  280. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  281. package/src/plugins/builtin/executives/data.ts +6 -1
  282. package/src/plugins/builtin/executives/pane.tsx +9 -21
  283. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  284. package/src/plugins/builtin/exposure/index.tsx +1 -2
  285. package/src/plugins/builtin/fear-greed/cache.ts +4 -0
  286. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  287. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  288. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  289. package/src/plugins/builtin/fear-greed/series.ts +6 -4
  290. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  291. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  292. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  293. package/src/plugins/builtin/futures/index.tsx +15 -0
  294. package/src/plugins/builtin/futures/model.ts +14 -0
  295. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  296. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  297. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  298. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  299. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  300. package/src/plugins/builtin/gpu/board.tsx +51 -12
  301. package/src/plugins/builtin/gpu/client.ts +19 -3
  302. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  303. package/src/plugins/builtin/gpu/headless.ts +14 -2
  304. package/src/plugins/builtin/gpu/index.tsx +1 -2
  305. package/src/plugins/builtin/gpu/model.ts +20 -2
  306. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  307. package/src/plugins/builtin/gpu/series.ts +2 -3
  308. package/src/plugins/builtin/help/index.tsx +1 -1
  309. package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
  310. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  311. package/src/plugins/builtin/holders/index.tsx +2 -3
  312. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  313. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  314. package/src/plugins/builtin/jodi/client.ts +4 -1
  315. package/src/plugins/builtin/jodi/index.tsx +1 -2
  316. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  317. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  318. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  319. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  320. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  321. package/src/plugins/builtin/market-movers/client.ts +1 -0
  322. package/src/plugins/builtin/market-movers/index.tsx +16 -5
  323. package/src/plugins/builtin/market-movers/screener.ts +23 -6
  324. package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
  325. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  326. package/src/plugins/builtin/members/index.tsx +1 -2
  327. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  328. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  329. package/src/plugins/builtin/money-markets/series.ts +3 -5
  330. package/src/plugins/builtin/news/headless.ts +7 -0
  331. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  332. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  333. package/src/plugins/builtin/notes/index.tsx +3 -6
  334. package/src/plugins/builtin/openfda/index.tsx +1 -2
  335. package/src/plugins/builtin/options/view.tsx +6 -5
  336. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  337. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  338. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  339. package/src/plugins/builtin/perps/index.tsx +1 -2
  340. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  341. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  342. package/src/plugins/builtin/plugin-module.ts +16 -2
  343. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  344. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  345. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  346. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  347. package/src/plugins/builtin/power/gas-client.ts +2 -1
  348. package/src/plugins/builtin/power/index.tsx +1 -2
  349. package/src/plugins/builtin/power/prices-client.ts +3 -2
  350. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  351. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  352. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  353. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  354. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  355. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  356. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  357. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  358. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  359. package/src/plugins/builtin/research/index.tsx +5 -8
  360. package/src/plugins/builtin/research-search/index.tsx +1 -2
  361. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  362. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  363. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  364. package/src/plugins/builtin/risk-factors/data.ts +8 -4
  365. package/src/plugins/builtin/risk-factors/index.tsx +3 -1
  366. package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
  367. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  368. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  369. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  370. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  371. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  372. package/src/plugins/builtin/scanner/lock-note.ts +26 -3
  373. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  374. package/src/plugins/builtin/sec/headless.ts +5 -1
  375. package/src/plugins/builtin/sec/index.tsx +4 -2
  376. package/src/plugins/builtin/sec/model.ts +2 -0
  377. package/src/plugins/builtin/sectors/index.tsx +11 -5
  378. package/src/plugins/builtin/series-inventory.ts +2 -0
  379. package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
  380. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  381. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  382. package/src/plugins/builtin/shared/fred-points.ts +6 -1
  383. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  384. package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
  385. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  386. package/src/plugins/builtin/shared/series-warning.ts +13 -4
  387. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  388. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  389. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  390. package/src/plugins/builtin/short-volume/series.ts +2 -3
  391. package/src/plugins/builtin/social-mentions/client.ts +15 -10
  392. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  393. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  394. package/src/plugins/builtin/social-mentions/series.ts +2 -3
  395. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  396. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  397. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  398. package/src/plugins/builtin/thirteenf/api.ts +127 -29
  399. package/src/plugins/builtin/thirteenf/data.ts +165 -35
  400. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  401. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  402. package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
  403. package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
  404. package/src/plugins/builtin/thirteenf/types.ts +14 -0
  405. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
  406. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  407. package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
  408. package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
  409. package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
  410. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  411. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  412. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  413. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  414. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  415. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  416. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  417. package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
  418. package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
  419. package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
  420. package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
  421. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  422. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  423. package/src/plugins/builtin/volatility/client.ts +43 -16
  424. package/src/plugins/builtin/volatility/headless.ts +5 -2
  425. package/src/plugins/builtin/volatility/index.tsx +1 -2
  426. package/src/plugins/builtin/volatility/model.ts +17 -6
  427. package/src/plugins/builtin/volatility/pane.tsx +6 -2
  428. package/src/plugins/builtin/volatility/series.ts +2 -1
  429. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  430. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  431. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  432. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  433. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  434. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  435. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  436. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  437. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  438. package/src/plugins/builtin/yield-curve/client.ts +4 -6
  439. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  440. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  441. package/src/plugins/bundle.ts +52 -2
  442. package/src/plugins/catalog-browser.ts +2 -2
  443. package/src/plugins/catalog-cli.ts +20 -0
  444. package/src/plugins/host-link.ts +13 -1
  445. package/src/plugins/installer.ts +64 -42
  446. package/src/plugins/loader.ts +43 -11
  447. package/src/plugins/peer-imports.ts +201 -0
  448. package/src/plugins/peer-retry.ts +26 -29
  449. package/src/plugins/plugin-process.ts +128 -0
  450. package/src/plugins/redirection-guard.ts +45 -0
  451. package/src/remote/endpoint.ts +16 -3
  452. package/src/renderers/browser/main.tsx +3 -2
  453. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  454. package/src/renderers/dom/desktop/icons.tsx +5 -2
  455. package/src/renderers/dom/host/image-surface.tsx +46 -21
  456. package/src/renderers/dom/styles.css +7 -0
  457. package/src/renderers/dom/toast-host.tsx +2 -0
  458. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  459. package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
  460. package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
  461. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  462. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  463. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  464. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  465. package/src/renderers/electrobun/bun/window/attention.ts +54 -0
  466. package/src/renderers/electrobun/bun/window/create.ts +6 -2
  467. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  468. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  469. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  470. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  471. package/src/renderers/electrobun/view/main.tsx +7 -2
  472. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  473. package/src/renderers/electrobun/view/ui-host.tsx +9 -2
  474. package/src/renderers/opentui/browser-open.ts +10 -4
  475. package/src/renderers/opentui/image/surface.tsx +1 -1
  476. package/src/renderers/opentui/start.tsx +12 -6
  477. package/src/renderers/opentui/toast-host.tsx +2 -1
  478. package/src/series/registry.ts +18 -0
  479. package/src/series/resolve.ts +2 -1
  480. package/src/series/standalone.ts +10 -1
  481. package/src/series/types.ts +12 -0
  482. package/src/series/validate.ts +11 -2
  483. package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
  484. package/src/sources/gloomberb-cloud/index.ts +28 -5
  485. package/src/sources/gloomberb-cloud/news.ts +24 -3
  486. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  487. package/src/sources/provider-errors.ts +26 -3
  488. package/src/sources/provider-router/history.ts +5 -0
  489. package/src/sources/provider-router/index.ts +13 -0
  490. package/src/sync/core-contributors.ts +32 -0
  491. package/src/telemetry/attention-counts.ts +64 -6
  492. package/src/telemetry/attention-setting.ts +33 -0
  493. package/src/telemetry/crash-reports-node.ts +3 -1
  494. package/src/telemetry/stall-clocks.ts +93 -0
  495. package/src/telemetry/stall-reports-node.ts +25 -9
  496. package/src/telemetry/stall-reports.ts +218 -13
  497. package/src/test-support/outage.ts +8 -0
  498. package/src/test-support/preload.ts +9 -0
  499. package/src/tickers/company-name-listing.ts +59 -29
  500. package/src/tickers/company-name.ts +84 -1
  501. package/src/tickers/listing-fate.ts +14 -3
  502. package/src/tickers/saved-instrument.ts +17 -0
  503. package/src/tickers/search/home-listing.ts +104 -8
  504. package/src/tickers/search/index.ts +5 -2
  505. package/src/tickers/search/types.ts +6 -1
  506. package/src/time-series/bar-aggregation.ts +7 -1
  507. package/src/time-series/base-series.ts +3 -3
  508. package/src/time-series/chart-bounds.ts +1 -1
  509. package/src/time-series/chart-data.ts +5 -2
  510. package/src/time-series/chart-seed.ts +1 -1
  511. package/src/time-series/coin-session-close.ts +211 -0
  512. package/src/time-series/live-bars.ts +13 -2
  513. package/src/time-series/live-quotes.ts +1 -1
  514. package/src/time-series/market.ts +5 -3
  515. package/src/time-series/official-close.ts +159 -0
  516. package/src/time-series/price-comparison.ts +75 -8
  517. package/src/time-series/resolve.ts +32 -4
  518. package/src/time-series/series-presentation.ts +1 -1
  519. package/src/time-series/studies/formula.ts +205 -0
  520. package/src/time-series/studies/indicators.ts +299 -0
  521. package/src/time-series/studies/leverage.ts +75 -0
  522. package/src/time-series/studies/pair.ts +138 -0
  523. package/src/time-series/studies/resolve.ts +333 -0
  524. package/src/time-series/studies/shared.ts +141 -0
  525. package/src/time-series/studies/units.ts +243 -0
  526. package/src/time-series/studies/vwap.ts +274 -0
  527. package/src/time-series/types.ts +7 -0
  528. package/src/types/config.ts +18 -2
  529. package/src/types/data-provider.ts +23 -0
  530. package/src/types/financials.ts +40 -3
  531. package/src/types/headless.ts +42 -3
  532. package/src/types/plugin.ts +32 -3
  533. package/src/ui/host.tsx +17 -1
  534. package/src/ui/toast.tsx +26 -1
  535. package/src/utils/ascii-text.ts +140 -0
  536. package/src/utils/cli-output.ts +91 -18
  537. package/src/utils/concurrency.ts +24 -0
  538. package/src/utils/exchanges.ts +6 -0
  539. package/src/utils/external-url.ts +20 -7
  540. package/src/utils/financial-statements.ts +73 -16
  541. package/src/utils/fundamentals.ts +17 -5
  542. package/src/utils/futures-generic.ts +4 -4
  543. package/src/utils/http-proxy-upstream.ts +124 -0
  544. package/src/utils/lease-liabilities.ts +54 -0
  545. package/src/utils/main-thread-stall.ts +122 -5
  546. package/src/utils/perf-marks.ts +58 -12
  547. package/src/utils/price-history-integrity.ts +47 -1
  548. package/src/utils/private-files.ts +141 -0
  549. package/src/utils/public-address.ts +99 -0
  550. package/src/account/upgrade-personalization.ts +0 -57
  551. package/src/account/upgrade-tickers.ts +0 -124
  552. package/src/api-client/upgrade-experiment.ts +0 -91
  553. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  554. package/src/time-series/studies.ts +0 -1628
@@ -15,9 +15,9 @@ import {
15
15
  type ListingVenue,
16
16
  } from "../tickers/listing-venues";
17
17
  import { findUsListingOfCompany, type UsListing } from "../tickers/us-listing";
18
- import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg, type CompanyNameListing } from "../tickers/company-name-listing";
18
+ import { findListingOfCompanyName, isCompanyNameInput, marksAnotherListing, readTerminalCodeArg, type CompanyNameListing } from "../tickers/company-name-listing";
19
19
  import { movedAwayChange, startTickerChangeLookup, type MovedTickerChange } from "../tickers/ticker-changes";
20
- import { lineMayHaveMoved, loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
20
+ import { lineMayHaveMoved, listingChangeLine, loadListingFate, MovedSymbolError, SUCCESSOR_CHANGE_WAIT_MS, type ListingFate } from "../tickers/listing-fate";
21
21
  import { quotedListingName } from "../tickers/listing-name";
22
22
  import { cliStyles } from "../utils/cli-output";
23
23
  import { groupByCompany, namesDepositaryReceipt, sameCompany, sameListedName, shortCompanyName } from "../tickers/company-name";
@@ -25,6 +25,7 @@ import {
25
25
  canonicalExchange,
26
26
  exchangeCodeVenues,
27
27
  exchangeLabel,
28
+ exchangeName,
28
29
  isKnownExchangeCode,
29
30
  isSharedExchangeCode,
30
31
  isUsListingExchange,
@@ -36,7 +37,7 @@ import { withMarketData } from "./scoped-context";
36
37
  import { failWithFields } from "./errors";
37
38
  import { stripStrayEdges } from "./symbol-args";
38
39
  import { isNoProviderMessage, isNoSourceAnswer, isProviderBusy } from "../sources/provider-errors";
39
- import { dashedCoinPair, exchangeLineCoinListing, isSpotCommodityOrCurrencyPair, MAJOR_COIN_NAMES } from "../utils/crypto-pair";
40
+ import { dashedCoinPair, exchangeLineCoinListing, isSpotCommodityOrCurrencyPair, MAJOR_COIN_NAMES, TOP_COIN_NAMES } from "../utils/crypto-pair";
40
41
  import { boardCoinFor, primeBoardCoins } from "./board-coins";
41
42
  import { knownGapMessage } from "./known-gaps";
42
43
  import { currencyPairSpelling } from "../utils/currency-pair";
@@ -60,7 +61,7 @@ import {
60
61
  import { listingAbroadVenue } from "../utils/sec";
61
62
  import { savedAbroadForBareSymbol, savedUsListingOf } from "../tickers/search/bare-symbol";
62
63
  import { listingSuffixCandidate, listingSuffixVenue, tickerHasListingSuffix } from "../sources/listing-symbols";
63
- import { notTheCoinNote } from "./commands/crypto-hints";
64
+ import { coinElsewhereNote, notTheCoinNote } from "./commands/crypto-hints";
64
65
 
65
66
  /** The `--exchange` option of every command that takes a ticker. */
66
67
  export const EXCHANGE_OPTION = {
@@ -414,9 +415,15 @@ export function listingChoiceNote(
414
415
  .filter((exchange) => exchange !== chosen.exchange);
415
416
  if (others.length === 0) return null;
416
417
  const more = others.length - MAX_OTHER_LISTINGS;
417
- const named = `${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloom search ${symbol}` : ""}`;
418
+ const named = `${others.slice(0, MAX_OTHER_LISTINGS).map(venueInProse).join(", ")}${more > 0 ? ` and ${more} more: gloom search ${symbol}` : ""}`;
418
419
  const which = others.length === 1 ? "a different company" : "different companies";
419
- return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${chosen.exchange} (${which} on ${named})`;
420
+ return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${exchangeName(chosen.exchange)} (${which} on ${named})`;
421
+ }
422
+
423
+ /** A venue as a sentence names it, in full: "the Warsaw Stock Exchange", "Xetra", "NASDAQ" (a name that is its code). */
424
+ function venueInProse(code: string): string {
425
+ const name = exchangeName(code);
426
+ return /\b(?:Exchange|Bourse|Market)$/.test(name) ? `the ${name}` : name;
420
427
  }
421
428
 
422
429
  // Quotes of these never name a company's listing, so they are never a choice between companies.
@@ -454,6 +461,19 @@ export function coinListingNotes(
454
461
  return note ? [note] : [];
455
462
  }
456
463
 
464
+ /**
465
+ * `coinListingNotes` for each listing of a list (CORR RIOT,BTC), asked only
466
+ * of a bare top coin code, whose quote alone says whether it is the coin.
467
+ */
468
+ export async function listCoinNotes(
469
+ listings: ReadonlyArray<Pick<CliListing, "symbol" | "exchange" | "request">>,
470
+ deps: Pick<ListingDeps, "dataProvider">,
471
+ ): Promise<string[]> {
472
+ const bare = listings.filter((listing) => !listing.exchange && TOP_COIN_NAMES.has(listing.symbol));
473
+ const quotes = await Promise.all(bare.map((listing) => loadListingQuote(deps.dataProvider, listing)));
474
+ return bare.flatMap((listing, index) => coinListingNotes(listing, quotes[index]));
475
+ }
476
+
457
477
  /**
458
478
  * `listingChoiceNote` for a listing a command resolved, from the quote it was
459
479
  * read on: only a bare symbol went where the data service chose. A named
@@ -636,8 +656,12 @@ function describeOtherListings(
636
656
  // Every other listing was asked and had nothing: they are named as such, with nothing to try.
637
657
  const heading = choose ? "Listings" : lines.length === 0 ? `No ${request.noun} on its other listings either` : "Other listings";
638
658
  const named = lines.length === 0 ? without : offered;
639
- const details = lines.length === 0 ? `${heading}: ${without.map((venue) => venueKey(listing.symbol, venue)).join(", ")}.`
659
+ const listed = lines.length === 0 ? `${heading}: ${without.map((venue) => venueKey(listing.symbol, venue)).join(", ")}.`
640
660
  : lines.length === 1 ? `${heading}: ${lines[0]}` : [`${heading}:`, ...lines.map((line) => ` ${line}`)].join("\n");
661
+ // A top coin's code (HYPE, SOL) none of the listings is: the coin may be what was meant.
662
+ const coin = coinElsewhereNote(listing.symbol, [...resolved ? [resolved] : [], ...others]
663
+ .map((venue) => ({ key: venueKey(listing.symbol, venue), name: venue.name })));
664
+ const details = coin ? `${listed}\n${coin}` : listed;
641
665
  return {
642
666
  // The symbol went to no venue, so none of them is said to lack the data.
643
667
  message: choose
@@ -786,6 +810,11 @@ export interface ReadListing {
786
810
  name: string | null;
787
811
  /** A bare coin ticker read as a listing that is not the coin (`coinListingNotes`); null otherwise. */
788
812
  coinNote: string | null;
813
+ /**
814
+ * What became of the company that had the symbol, as `quote` says it ("GOGL was Golden Ocean Group until
815
+ * 2025, now Cmb.Tech: CMBT"), when asked for (`successor`); null otherwise.
816
+ */
817
+ successorNote?: string | null;
789
818
  }
790
819
 
791
820
  /**
@@ -801,13 +830,19 @@ export async function readBareListing(
801
830
  symbol: string,
802
831
  deps: ListingDeps,
803
832
  request: ListingDataRequest,
804
- { refresh = false }: { refresh?: boolean } = {},
833
+ /** `successor`: also say what became of the company that had the symbol, looked up beside its quote. */
834
+ { refresh = false, successor = false }: { refresh?: boolean; successor?: boolean } = {},
805
835
  ): Promise<ReadListing | null> {
806
836
  const listing = await resolveCliListing(symbol, null, deps);
807
837
  if (listing.exchange) return null;
838
+ const changes = successor ? startTickerChangeLookup(deps.dataProvider, listing.symbol, listing.exchange) : null;
808
839
  const answer = await loadListingQuoteAnswer(deps.dataProvider, listing);
809
840
  if (answer.quote) {
810
- return { symbol: listing.symbol, exchange: listing.request.exchange, name: answer.quote.name?.trim() || null, coinNote: coinListingNotes(listing, answer.quote)[0] ?? null };
841
+ const successorNote = changes
842
+ ? listingChangeLine(await loadListingFate(listing, deps.dataProvider, { quote: answer.quote, changes, graceMs: SUCCESSOR_CHANGE_WAIT_MS }))
843
+ : null;
844
+ return { symbol: listing.symbol, exchange: listing.request.exchange, name: answer.quote.name?.trim() || null, coinNote: coinListingNotes(listing, answer.quote)[0] ?? null,
845
+ ...successorNote ? { successorNote } : {} };
811
846
  }
812
847
  if (!isNoProviderError(answer.error)) return null;
813
848
  const other = await quoteBareListing(listing, deps, request, { refresh });
@@ -861,10 +896,12 @@ export function listingFateLoader(
861
896
  listing: Pick<CliListing, "symbol" | "exchange" | "request">,
862
897
  deps: Pick<ListingDeps, "dataProvider">,
863
898
  quote: Promise<Quote | null>,
899
+ /** `successor`: the command says what became of the company too (GOGL's merger), so the lookup is waited for. */
900
+ { successor = false }: { successor?: boolean } = {},
864
901
  ): () => Promise<ListingFate> {
865
902
  const changes = startTickerChangeLookup(deps.dataProvider, listing.symbol, listing.exchange);
866
903
  let fate: Promise<ListingFate> | null = null;
867
- return () => fate ??= loadListingFate(listing, deps.dataProvider, { quote, changes });
904
+ return () => fate ??= loadListingFate(listing, deps.dataProvider, { quote, changes, ...successor ? { graceMs: SUCCESSOR_CHANGE_WAIT_MS } : {} });
868
905
  }
869
906
 
870
907
  /**
@@ -1338,6 +1375,22 @@ export async function oneWordCompanyName(typed: string, deps: Pick<ListingDeps,
1338
1375
  return found ? companyListingArg(typed, found) : null;
1339
1376
  }
1340
1377
 
1378
+ /**
1379
+ * A word the service marks as a company's name (`searchAliases`), read as
1380
+ * that company's listing as `oneWordCompanyName` reads a word, for a bare
1381
+ * symbol no source quotes, before it is read on the symbol's listings abroad:
1382
+ * "siemens" is SIE on XETRA (Siemens AG), not SIEMENS on NSE (Siemens Ltd.).
1383
+ * Null when the service marks no listing with the word, or marks the word's
1384
+ * own symbol, and when search names no company or is too busy to say.
1385
+ */
1386
+ export async function serviceNamedCompany(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<ListingArg | null> {
1387
+ // Most symbols are no company's name: one search says so before any listing is weighed or quoted.
1388
+ if (!ONE_WORD_NAME.test(typed.trim()) || !await marksAnotherListing(deps.dataProvider, typed)) return null;
1389
+ const found = await findOneWordCompany(typed, deps);
1390
+ if (!found?.named || found.symbol.toUpperCase() === typed.trim().toUpperCase()) return null;
1391
+ return companyListingArg(typed, found);
1392
+ }
1393
+
1341
1394
  /** The company `oneWordCompanyName` reads a word as, before it is read as a listing. */
1342
1395
  async function findOneWordCompany(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<CompanyNameListing | null> {
1343
1396
  const word = typed.trim();
@@ -14,7 +14,7 @@ export interface CliGlobalOptions {
14
14
  limit?: number;
15
15
  /** `--tail <n>`: the newest n rows of a dated series, in their printed order. Never set with `limit`. */
16
16
  tail?: number;
17
- /** `--width <n>`: fit tables to n columns. Unset, a terminal's own width fits them and piped output is not fitted. */
17
+ /** `--width <n>`: fit tables to n columns. Unset, a terminal's own width fits them, and piped output fits `COLUMNS` when set, else is not fitted. */
18
18
  width?: number;
19
19
  refresh: boolean;
20
20
  dryRun: boolean;
@@ -1,6 +1,7 @@
1
- import { mkdir, stat, writeFile } from "fs/promises";
1
+ import { lstat, mkdir } from "fs/promises";
2
2
  import { homedir } from "os";
3
3
  import { posix, win32 } from "path";
4
+ import { NOT_A_PLAIN_FILE, writePrivateFile } from "../utils/private-files";
4
5
 
5
6
  interface OutputPathContext {
6
7
  cwd?: string;
@@ -40,29 +41,75 @@ function isFolder(path: string, paths: typeof posix): Error {
40
41
  return new Error(`Cannot write ${path}: it is a folder. Name a file in it, such as ${paths.join(path, "report.csv")}.`);
41
42
  }
42
43
 
43
- /** Fails when `path` names a folder: checked before a report loads, so it stops at once. */
44
+ function isLink(path: string): Error {
45
+ return new Error(`Cannot write ${path}: it is a link. Name the file it points to, or a new file.`);
46
+ }
47
+
48
+ function isNotAFile(path: string): Error {
49
+ return new Error(`Cannot write ${path}: it is not a plain file. Name a new file, or a file to replace.`);
50
+ }
51
+
52
+ /**
53
+ * Fails when `path` names a folder, a link or anything else that is not a
54
+ * plain file (a device, a pipe): checked before a report loads, so it stops
55
+ * at once, and again as the file is opened.
56
+ */
44
57
  export async function checkOutputPath(path: string, platform: NodeJS.Platform = process.platform): Promise<void> {
45
- const existing = await stat(path).catch(() => {
58
+ const existing = await lstat(path).catch(() => {
46
59
  // Intentional ignore: a path that does not exist yet is the usual case, and writing reports any real failure.
47
60
  return null;
48
61
  });
49
- if (existing?.isDirectory()) throw isFolder(path, platform === "win32" ? win32 : posix);
62
+ if (!existing) return;
63
+ if (existing.isDirectory()) throw isFolder(path, platform === "win32" ? win32 : posix);
64
+ if (existing.isSymbolicLink()) throw isLink(path);
65
+ if (!existing.isFile()) throw isNotAFile(path);
66
+ }
67
+
68
+ /**
69
+ * Creates the folder `path` goes in and the folders above it, failing with
70
+ * that folder named when it cannot: `--output /nonexistent/x.png` cannot
71
+ * create /nonexistent, which says more than the `mkdir` error behind it.
72
+ */
73
+ export async function createOutputFolder(path: string, platform: NodeJS.Platform = process.platform): Promise<void> {
74
+ const folder = (platform === "win32" ? win32 : posix).dirname(path);
75
+ try {
76
+ await mkdir(folder, { recursive: true });
77
+ } catch (error) {
78
+ const code = errorCode(error);
79
+ const lead = `Cannot create the folder ${folder}`;
80
+ if (code === "EACCES" || code === "EPERM") throw new Error(`${lead}: permission denied.`);
81
+ if (code === "EROFS") throw new Error(`${lead}: the disk is read-only.`);
82
+ if (code === "ENOTDIR" || code === "EEXIST") throw new Error(`${lead}: a file stands where one of its folders would be.`);
83
+ throw new Error(`${lead}: ${error instanceof Error ? error.message : String(error)}`);
84
+ }
50
85
  }
51
86
 
52
87
  /**
53
88
  * Writes a command's output to `path`, creating the folders it names as
54
- * `shot --output` does, and failing with what to do when it cannot.
89
+ * `shot --output` does, and failing with what to do when it cannot. The file
90
+ * is readable by the user alone, as one it replaces is made, and only a plain
91
+ * file is written: a link or a device in its place is refused, checked first
92
+ * and again as the file is opened.
55
93
  */
56
- export async function writeOutputFile(path: string, contents: string, platform: NodeJS.Platform = process.platform): Promise<void> {
94
+ export async function writeOutputFile(path: string, contents: string | Uint8Array, platform: NodeJS.Platform = process.platform): Promise<void> {
57
95
  const paths = platform === "win32" ? win32 : posix;
96
+ await createOutputFolder(path, platform);
97
+ await checkOutputPath(path, platform);
58
98
  try {
59
- await mkdir(paths.dirname(path), { recursive: true });
60
- await writeFile(path, contents);
99
+ await writePrivateFile(path, contents);
61
100
  } catch (error) {
62
- const code = errorCode(error);
63
- if (code === "EISDIR") throw isFolder(path, paths);
64
- if (code === "EACCES" || code === "EPERM") throw new Error(`Cannot write ${path}: permission denied.`);
65
- if (code === "ENOTDIR" || code === "EEXIST") throw new Error(`Cannot write ${path}: a file stands where one of its folders would be.`);
66
- throw new Error(`Cannot write ${path}: ${error instanceof Error ? error.message : String(error)}`);
101
+ throw writeFailure(path, paths, error);
67
102
  }
68
103
  }
104
+
105
+ function writeFailure(path: string, paths: typeof posix, error: unknown): Error {
106
+ const code = errorCode(error);
107
+ if (code === "EISDIR") return isFolder(path, paths);
108
+ // A link opened without following it: ELOOP on Linux and macOS, EMLINK on FreeBSD.
109
+ if (code === "ELOOP" || code === "EMLINK") return isLink(path);
110
+ // A device, or a pipe no one reads.
111
+ if (code === NOT_A_PLAIN_FILE || code === "ENXIO") return isNotAFile(path);
112
+ if (code === "EACCES" || code === "EPERM") return new Error(`Cannot write ${path}: permission denied.`);
113
+ if (code === "ENOTDIR" || code === "EEXIST") return new Error(`Cannot write ${path}: a file stands where one of its folders would be.`);
114
+ return new Error(`Cannot write ${path}: ${error instanceof Error ? error.message : String(error)}`);
115
+ }
@@ -32,6 +32,17 @@ import {
32
32
  import { renderGlossaryEntries, type GlossaryLookup } from "../glossary";
33
33
  import { renderSeriesMatches, type SeriesMatch } from "../series-search";
34
34
  import { typoDistance } from "../../utils/typo-distance";
35
+ import { quotedSearchCommand } from "../commands/news-search";
36
+
37
+ /**
38
+ * The `news` search for words no function matches (`catalog hurricane`):
39
+ * one word quoted so `news` searches the stories rather than a ticker, several
40
+ * as typed in one pair of quotes.
41
+ */
42
+ function catalogNewsCommand(query: string): string {
43
+ const words = query.trim().replace(/\s+/g, " ").replace(/["']/g, "");
44
+ return /\s/.test(words) ? `gloom news "${words}"` : quotedSearchCommand(words.toLowerCase());
45
+ }
35
46
 
36
47
  export interface PaneFunctionCatalog {
37
48
  panes: ReadonlyMap<string, PaneDef>;
@@ -253,9 +264,9 @@ async function buildTemplateCatalogEntry(
253
264
  const sampleArg = sampleArgForTemplate(template);
254
265
  const createOptions: PaneTemplateCreateOptions | undefined = buildCreateOptions(template, sampleArg);
255
266
  const primarySymbol = createOptions?.symbol ?? createOptions?.symbols?.[0] ?? null;
256
- let defaultSettings: Record<string, unknown> = {};
267
+ let defaultSettings: Record<string, unknown> = template.catalogSettings ?? {};
257
268
 
258
- if (template.createInstance) {
269
+ if (template.catalogSettings === undefined && template.createInstance) {
259
270
  try {
260
271
  const spec = await template.createInstance(buildTemplateContext(context, primarySymbol), createOptions);
261
272
  defaultSettings = spec?.settings ?? {};
@@ -810,7 +821,7 @@ export function renderPaneCatalogReport(
810
821
  if (charted) return withGap([charted, terms].filter(Boolean).join("\n\n"));
811
822
  if (glossary.exact.length > 0 || gap || setting) return withGap(terms);
812
823
  return withTerms(note(args.query
813
- ? `No functions match "${args.query}". Run gloom catalog to browse them all.`
824
+ ? `No functions match "${args.query}". For stories that mention it: ${catalogNewsCommand(args.query)}. Run gloom catalog to browse them all.`
814
825
  : "No functions are available."));
815
826
  }
816
827
 
@@ -238,7 +238,7 @@ export function collectShotSymbols(resolved: ResolvedPaneFunction, rawArg: strin
238
238
  }
239
239
  }
240
240
  // A text argument is a phrase, not a symbol, so it must never be looked up as one.
241
- const argIsTicker = argumentNamesTicker(resolved.template, resolved.headless);
241
+ const argIsTicker = argumentNamesTicker(resolved.template, resolved.headless, resolved.pane);
242
242
  let symbols = resolved.createOptions?.symbols?.length
243
243
  ? resolved.createOptions.symbols
244
244
  : [
@@ -10,6 +10,7 @@ import { createHeadlessPaneContext } from "./headless";
10
10
  import type { HeadlessPaneContext } from "../../types/headless";
11
11
  import { lockNoteReason } from "../../plugins/builtin/shared/pro-lock-note";
12
12
  import { renderDesktopShot } from "./screenshot-render";
13
+ import { resolvePersistedCloudSessionToken } from "./cloud-session";
13
14
  import type { PaneScreenshotResult } from "./screenshot-types";
14
15
  import { deriveRenderedFreshness, formatFreshnessLine, type ReportFreshness } from "./freshness";
15
16
  import { exportTextTable, reportFooterLines, type CliReportTables } from "../report-tables";
@@ -389,11 +390,55 @@ class RenderedReportFailure extends Error {
389
390
  }
390
391
  }
391
392
 
393
+ /**
394
+ * The report of a pane the sign-in wall covers whole (`PaneDef.signInWall`)
395
+ * when there is no session: its lock note, as the rendered wall reports it,
396
+ * without waiting for the wall to render. Null with a session, or for any
397
+ * other pane.
398
+ */
399
+ async function signedOutDomReport(
400
+ resolved: ResolvedPaneFunction,
401
+ context: MarketContext,
402
+ rawArg: string,
403
+ ): Promise<PaneFunctionReport | null> {
404
+ if (!resolved.pane.signInWall || !resolved.pane.reportLockNote || resolvePersistedCloudSessionToken(context)) return null;
405
+ const symbols = collectShotSymbols(resolved, rawArg);
406
+ const note = await resolved.pane.reportLockNote({
407
+ gate: "sign-in", symbol: symbols[0] ?? null, token: resolved.token,
408
+ context: createHeadlessPaneContext(resolved, context, new AbortController().signal),
409
+ });
410
+ const reason = lockNoteReason(note);
411
+ return {
412
+ data: {
413
+ kind: "rendered-view",
414
+ target: resolved.token,
415
+ capabilityId: resolved.capability.id,
416
+ symbols,
417
+ options: resolved.options,
418
+ rowCount: 0,
419
+ empty: true,
420
+ complete: false,
421
+ unavailableSymbols: [],
422
+ rows: [],
423
+ visibleText: "",
424
+ truncated: false,
425
+ truncationReasons: [],
426
+ limitation: DOM_LIMITATION,
427
+ reason,
428
+ locked: "sign-in",
429
+ },
430
+ text: [resolved.label, "", ...lockNoteLines(note)].join("\n").trimEnd(),
431
+ tables: domReportTables([], resolved.label, null, reason, []),
432
+ };
433
+ }
434
+
392
435
  export async function buildDomFunctionReport(
393
436
  resolved: ResolvedPaneFunction,
394
437
  context: MarketContext,
395
438
  rawArg: string,
396
439
  ): Promise<PaneFunctionReport> {
440
+ const signedOut = await signedOutDomReport(resolved, context, rawArg);
441
+ if (signedOut) return signedOut;
397
442
  let result: PaneScreenshotResult;
398
443
  try {
399
444
  result = await renderDesktopShot({
@@ -123,6 +123,8 @@ export interface ReportFreshness {
123
123
  ageOnly?: true;
124
124
  /** What the newest observation is, said before its time: `newest story Tue 29 Sep 2026 20:17 UTC`. */
125
125
  asOfLabel?: string;
126
+ /** The newest observation is a bar of this many minutes opening at `asOf`: the line names its span (`last bar 15:00–16:00 UTC`). */
127
+ barMinutes?: number;
126
128
  /** The line says when the report was retrieved after the newest observation's date (`retrieved Wed 28 Oct 2026 19:07 UTC`). */
127
129
  retrievedToo?: true;
128
130
  /** What the figures are, said after the status: `funding = current estimate, pays 08:00 UTC`. */
@@ -572,6 +574,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
572
574
  const note = declared.note?.trim();
573
575
  const noteVariants = note ? (declared.noteVariants ?? []).map((variant) => variant.trim()).filter((variant) => variant && variant !== note) : [];
574
576
  const asOfLabel = asOf ? declared.asOfLabel?.trim() : undefined;
577
+ const barMinutes = asOf && !asOf.dateOnly && !declaredAsOf && declared.barMinutes != null && declared.barMinutes > 0 ? declared.barMinutes : undefined;
575
578
  const schedule = declared.schedule?.trim();
576
579
  const scheduleVariants = schedule ? (declared.scheduleVariants ?? []).map((variant) => variant.trim()).filter((variant) => variant && variant !== schedule) : [];
577
580
  const publisher = declared.publisher?.trim();
@@ -612,6 +615,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
612
615
  ...(oldestAgeMinutes != null ? { oldestAgeMinutes } : {}),
613
616
  ...(status === "unreported" && declared.ageOnly ? { ageOnly: true as const } : {}),
614
617
  ...(asOfLabel ? { asOfLabel } : {}),
618
+ ...(barMinutes ? { barMinutes } : {}),
615
619
  ...(asOf && declared.retrievedToo ? { retrievedToo: true as const } : {}),
616
620
  ...(note ? { note } : {}),
617
621
  ...(noteVariants.length > 0 ? { noteVariants } : {}),
@@ -619,10 +623,16 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
619
623
  ...(scheduleVariants.length > 0 ? { scheduleVariants } : {}),
620
624
  ...(publisher ? { publisher } : {}),
621
625
  ...(publishedAt ? { publishedAt: isoTime(publishedAt) } : {}),
622
- retrievedAt: new Date(now).toISOString(),
626
+ retrievedAt: declaredRetrieval(declared.retrievedAt, now) ?? new Date(now).toISOString(),
623
627
  };
624
628
  }
625
629
 
630
+ /** A declared retrieval (`retrievedAt`): when the source last read the data, not in the future. */
631
+ function declaredRetrieval(value: string | undefined, now: number): string | null {
632
+ const time = value ? Date.parse(value) : Number.NaN;
633
+ return Number.isFinite(time) && time <= now + FUTURE_TOLERANCE_MS ? new Date(time).toISOString() : null;
634
+ }
635
+
626
636
  /**
627
637
  * Source, as-of and status for a headless report: what the loader declared
628
638
  * (the result's `freshness` over the definition's), the rest read from the
@@ -914,6 +924,8 @@ function asOfText(freshness: ReportFreshness, short: boolean, local: boolean, un
914
924
  : period ? `${periodText(freshness.asOf, period)} (${period})`
915
925
  : freshness.asOfClose
916
926
  ? `${tradingDayText(freshness.asOfClose, market)} close`
927
+ : freshness.barMinutes && time && !time.dateOnly
928
+ ? barSpanText(time.time, freshness.barMinutes, undated && onRetrievalDay(new Date(time.time).toISOString(), freshness.retrievedAt))
917
929
  : time?.dateOnly ? tradingDayText(freshness.asOf, market)
918
930
  : undated && time && onRetrievalDay(new Date(time.time).toISOString(), freshness.retrievedAt)
919
931
  ? `${new Date(time.time).toISOString().slice(11, 16)} UTC` : timeText(freshness.asOf, local);
@@ -924,6 +936,14 @@ function asOfText(freshness: ReportFreshness, short: boolean, local: boolean, un
924
936
  return oldest && !short && freshness.oldestAgeMinutes == null ? `${dated} (oldest ${oldest})` : dated;
925
937
  }
926
938
 
939
+ /** `Fri 30 Oct 2026, last bar 15:00–16:00 UTC`: the newest bar's span; `undated` leaves the day off. */
940
+ function barSpanText(open: number, minutes: number, undated: boolean): string {
941
+ const start = new Date(open).toISOString();
942
+ const end = new Date(open + minutes * 60_000).toISOString();
943
+ const span = `last bar ${start.slice(11, 16)}–${end.slice(11, 16)} UTC`;
944
+ return undated ? span : `${dayText(start)}, ${span}`;
945
+ }
946
+
927
947
  /**
928
948
  * Each source's own as-of, the zone named once at the end:
929
949
  * `Sun 11 Oct 2026 · Binance 02:15 · Bybit 02:24 UTC`. The day leads, the newest source's,
@@ -986,6 +1006,15 @@ function marketText(market: ReportMarket, now: number, short: boolean, local: bo
986
1006
  }
987
1007
  }
988
1008
 
1009
+ /**
1010
+ * Where one venue's market stands now and the status line's words for it (`markets closed until Mon`), for a pane with
1011
+ * no dated rows to read that from, such as a scanner. Null when no calendar places the venue.
1012
+ */
1013
+ export function venueMarketLine(exchange: string, now: number): { state: ReportMarket["state"]; text: string } | null {
1014
+ const { market } = reportMarketSession([{ exchange, time: null }], now);
1015
+ return market ? { state: market.state, text: marketText(market, now, false, false) } : null;
1016
+ }
1017
+
989
1018
  /** Whether every market of a mixed report reads the same (`markets closed until Mon`): one line then says it for all. */
990
1019
  function sameMarketText(markets: readonly VenueMarket[], now: number): boolean {
991
1020
  return new Set(markets.map((market) => marketText(market, now, false, false))).size === 1;
@@ -1092,8 +1121,10 @@ function observedDayText(date: string, retrievedAt: string): string {
1092
1121
  }
1093
1122
 
1094
1123
  interface DatasetLineDetail {
1095
- /** Leave out the source. */
1096
- source?: "none";
1124
+ /** The source without its `Source:` label, or left out. */
1125
+ source?: "bare" | "none";
1126
+ /** Leave out how many days the data lags, which a narrow line gives up before its source and date. */
1127
+ lag?: "none";
1097
1128
  /** Leave out the oldest observation. */
1098
1129
  oldest?: "none";
1099
1130
  /** Count stale rows without their total. */
@@ -1113,11 +1144,12 @@ function lagText(days: number): string {
1113
1144
  * `Source: DTCC · last trade 2026-10-09 · lag 2 days`.
1114
1145
  */
1115
1146
  function datasetLineParts(freshness: ReportFreshness, asOf: string, lagDays: number, detail: DatasetLineDetail = {}): string[] {
1147
+ const source = detail.source === "none" ? [] : [detail.source === "bare" ? freshness.source : `Source: ${freshness.source}`];
1116
1148
  if (freshness.observation) {
1117
1149
  return [
1118
- ...(detail.source === "none" ? [] : [`Source: ${freshness.source}`]),
1150
+ ...source,
1119
1151
  `${freshness.observation} ${asOf.slice(0, 10)}`,
1120
- lagText(lagDays),
1152
+ ...(detail.lag === "none" ? [] : [lagText(lagDays)]),
1121
1153
  ...(freshness.savedAt ? [savedCopyText(freshness.savedAt, freshness.retrievedAt, false)]
1122
1154
  : freshness.status === "stale" ? ["stale"] : []),
1123
1155
  ];
@@ -1127,9 +1159,9 @@ function datasetLineParts(freshness: ReportFreshness, asOf: string, lagDays: num
1127
1159
  const oldest = freshness.oldest && detail.oldest !== "none" ? ` (oldest ${dated(freshness.oldest)})` : "";
1128
1160
  const stale = freshness.status !== "stale" ? null : staleRowsText(freshness, detail.status === "short") ?? "stale";
1129
1161
  return [
1130
- ...(detail.source === "none" ? [] : [`Source: ${freshness.source}`]),
1162
+ ...source,
1131
1163
  `observed ${dated(asOf)}${period ? ` (${period})` : ""}${oldest}`,
1132
- `lag ${lagDays}d`,
1164
+ ...(detail.lag === "none" ? [] : [`lag ${lagDays}d`]),
1133
1165
  ...(freshness.savedAt ? [savedCopyText(freshness.savedAt, freshness.retrievedAt, false)] : stale ? [stale] : []),
1134
1166
  ];
1135
1167
  }
@@ -1190,16 +1222,21 @@ export function paneStatusLineVariants(freshness: ReportFreshness): string[] {
1190
1222
  * `Source: CFTC · Tue 6 Oct 2026 · not a live feed (weekly report)`, shortens
1191
1223
  * the same way and gives up its source last. A dataset reads
1192
1224
  * `Source: Gloom Cloud · observed Sun 4 Oct · lag 6d` and gives up its oldest
1193
- * observation, the stale total, then its source.
1225
+ * observation, the stale total, then its lag; its source loses its label
1226
+ * (`Gloom Cloud · observed Sun 4 Oct`), and the narrowest line is the date
1227
+ * alone (`Observed Sun 4 Oct`): the source goes before the date.
1194
1228
  */
1195
1229
  export function statusLineVariants(freshness: ReportFreshness, { source = false }: { source?: boolean } = {}): string[] {
1196
1230
  if (datesObservation(freshness)) {
1197
1231
  const { asOf, lagDays } = freshness;
1232
+ // The lag, relative to the reading, goes before the source and the date; the source before the date.
1198
1233
  const datasetDetails: DatasetLineDetail[] = [
1199
1234
  {},
1200
1235
  { oldest: "none" },
1201
1236
  { oldest: "none", status: "short" },
1202
- { source: "none", oldest: "none", status: "short" },
1237
+ { oldest: "none", status: "short", lag: "none" },
1238
+ { oldest: "none", status: "short", lag: "none", source: "bare" },
1239
+ { oldest: "none", status: "short", lag: "none", source: "none" },
1203
1240
  ];
1204
1241
  return [...new Set(datasetDetails.map((detail) => capitalize(datasetLineParts(freshness, asOf, lagDays, detail).join(" · "))))];
1205
1242
  }
@@ -234,10 +234,11 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
234
234
  bloomberg: ["RV"],
235
235
  },
236
236
  EQS: {
237
- summary: "Screen covered stocks on valuation, growth, margins, liquidity, volatility, technicals, float and ownership, short interest, insider activity and 13F holders, then rank the matches by any metric. "
237
+ summary: "Screen covered stocks on valuation, growth, margins, returns on capital, leverage, liquidity, volatility, technicals, float and ownership, short interest, insider activity and 13F holders, then rank the matches by any metric. "
238
238
  + "ATR% is the 14-session average true range as a percent of the last close. IV rank is a rank, not an IV percentile: 0 is the lowest 30-day implied volatility of the past 52 weeks and 100 the highest. IV percentile, IV30 change in vol points, HV20 and HV rank screen premium to sell. "
239
- + "Technical fields read the last completed session: RSI 14, price vs SMA 20, 50 and 200, trading days since a MACD or golden cross and since a new 20-day or 52-week high or low, and Bollinger width percentile (0 is a six-month squeeze). Ideas holds ready technical screens.",
240
- usage: ["EQS", "gloom fn EQS --preset golden-cross"],
239
+ + "Technical fields read the last completed session: RSI 14, price vs SMA 20, 50 and 200, trading days since a MACD or golden cross and since a new 20-day or 52-week high or low, and Bollinger width percentile (0 is a six-month squeeze). "
240
+ + "ROIC, FCF yield, net debt / EBITDA, debt / equity and the revenue and EPS CAGRs read FA's bases, and forward growth EE's consensus; N/M where FA prints it. Ideas holds ready technical screens and Value, Quality and GARP.",
241
+ usage: ["EQS", "gloom fn EQS --preset golden-cross", "gloom fn EQS --preset quality"],
241
242
  keys: [key("a", "dd criterion"), key("m", "etric"), key("t", "check ticker"), key("s", "ave screen"), key("o", "pen research")],
242
243
  data: same("Daily snapshot, statements as filed"),
243
244
  bloomberg: ["EQS"],
@@ -533,7 +534,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
533
534
  bloomberg: ["CAST", "DDIS"],
534
535
  },
535
536
  ATTN: {
536
- summary: "See which tickers opted-in Gloom users are researching, with delayed privacy-qualified hourly counts, abnormal attention, sectors, countries and market context. Suppressed activity is unavailable, never zero.",
537
+ summary: "See which tickers Gloom users are researching, with delayed privacy-qualified hourly counts, abnormal attention, sectors, countries and market context. Suppressed activity is unavailable, never zero.",
537
538
  usage: ["ATTN", "ATTN 6758:JPX"],
538
539
  keys: [OPEN, key("e", "vidence"), key("d", "es"), key("g", "raph"), key("n", "ews")],
539
540
  data: { free: "Three published rows per section and the latest history point", pro: "All published rows and hourly history; minimum one-hour publication lag" },
@@ -1252,10 +1253,17 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1252
1253
  bloomberg: [],
1253
1254
  },
1254
1255
  TBO: {
1255
- summary: "Data center and semiconductor supply chain intelligence: company lists, sites and intel, with limited free data and full access with a TheBuildout subscription.",
1256
+ summary: "Data center and semiconductor supply chain intelligence: a board of AI buildout themes sized by market cap and coloured by median 1Y return, each theme's companies, sites and intel, with limited free data and full access with Buildout.",
1256
1257
  usage: ["TBO"],
1257
- keys: [TABS, OPEN, key("s", "tar")],
1258
- data: same("Intel 72 hours delayed without a TheBuildout plan"),
1258
+ keys: [TABS, key("←/→", " theme"), OPEN, key("s", "tar")],
1259
+ data: same("Intel 72 hours delayed without Buildout"),
1260
+ bloomberg: [],
1261
+ },
1262
+ TBOC: {
1263
+ summary: "One company in TheBuildout: its power and hosting deals, suppliers and customers as a flow, its sites, hiring by site and the signals that name it. Linked to TBO, it follows the company under the cursor.",
1264
+ usage: ["TBOC MSFT", "TBOC MSFT --tab signals"],
1265
+ keys: [TABS, OPEN],
1266
+ data: same("Signals 72 hours delayed and no hiring by site without Buildout"),
1259
1267
  bloomberg: [],
1260
1268
  },
1261
1269
  ACM: {
@@ -1427,9 +1435,9 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
1427
1435
  bloomberg: [],
1428
1436
  },
1429
1437
  ROADMAP: {
1430
- summary: "What is being built, what is planned and what shipped, with each item's votes. Pro members vote on what is building or planned, once per item, and can take the vote back.",
1438
+ summary: "What is being built, what is planned and what shipped, with each item's votes, and the Changelog in a second tab. Pro members vote on what is building or planned, once per item, and can take the vote back.",
1431
1439
  usage: ["ROADMAP"],
1432
- keys: [OPEN, key("v", "ote")],
1440
+ keys: [TABS, OPEN, key("v", "ote")],
1433
1441
  data: null,
1434
1442
  bloomberg: [],
1435
1443
  },