@gloom-sh/terminal 0.19.2 → 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 (472) hide show
  1. package/README.md +7 -1
  2. package/package.json +1 -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 +20 -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.tsx +23 -1
  20. package/src/architecture/core-builtin-allowlist.json +2 -1
  21. package/src/capabilities/factories.ts +1 -0
  22. package/src/cli/ascii-output.ts +42 -0
  23. package/src/cli/base-converter.ts +10 -2
  24. package/src/cli/command-options.ts +16 -1
  25. package/src/cli/commands/crypto-hints.ts +23 -4
  26. package/src/cli/commands/market.ts +92 -23
  27. package/src/cli/commands/news-search.ts +24 -5
  28. package/src/cli/commands/overview.ts +9 -4
  29. package/src/cli/commands/search.ts +71 -12
  30. package/src/cli/commands/system.ts +22 -30
  31. package/src/cli/commands/ticker.ts +49 -9
  32. package/src/cli/commands/version.ts +33 -0
  33. package/src/cli/context.ts +5 -9
  34. package/src/cli/data-dir.ts +7 -0
  35. package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
  36. package/src/cli/desktop-pane-shot-browser.ts +320 -0
  37. package/src/cli/desktop-pane-shot-routes.ts +26 -1
  38. package/src/cli/desktop-pane-shot.ts +196 -256
  39. package/src/cli/early-command.ts +42 -0
  40. package/src/cli/earnings-calendar.ts +9 -0
  41. package/src/cli/entry.ts +19 -13
  42. package/src/cli/errors.ts +12 -2
  43. package/src/cli/help.ts +1 -1
  44. package/src/cli/history-export.ts +22 -2
  45. package/src/cli/history-rows.ts +154 -4
  46. package/src/cli/index.ts +18 -1
  47. package/src/cli/known-gaps.ts +40 -5
  48. package/src/cli/listing-arg.ts +46 -9
  49. package/src/cli/options.ts +1 -1
  50. package/src/cli/output-file.ts +60 -13
  51. package/src/cli/pane-functions/catalog.ts +14 -3
  52. package/src/cli/pane-functions/data.ts +1 -1
  53. package/src/cli/pane-functions/dom.ts +45 -0
  54. package/src/cli/pane-functions/freshness.ts +46 -9
  55. package/src/cli/pane-functions/function-help.ts +17 -9
  56. package/src/cli/pane-functions/headless.ts +22 -14
  57. package/src/cli/pane-functions/index.ts +6 -2
  58. package/src/cli/pane-functions/options.ts +6 -2
  59. package/src/cli/pane-functions/resolver.ts +27 -5
  60. package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
  61. package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
  62. package/src/cli/pane-functions/screenshot-http.ts +70 -0
  63. package/src/cli/pane-functions/screenshot-render.ts +19 -5
  64. package/src/cli/quote-answer.ts +20 -0
  65. package/src/cli/registry.ts +41 -13
  66. package/src/cli/result.ts +12 -6
  67. package/src/cli/ticker-resolution.ts +2 -1
  68. package/src/cli/types.ts +2 -0
  69. package/src/components/chart/composite/format.ts +3 -0
  70. package/src/components/chart-table/header.tsx +1 -1
  71. package/src/components/chart-table/layout.ts +28 -11
  72. package/src/components/command-bar/action-runtime.ts +4 -0
  73. package/src/components/command-bar/commands/direct/index.ts +18 -33
  74. package/src/components/command-bar/commands/direct/runtime.ts +5 -0
  75. package/src/components/command-bar/commands/registry.ts +10 -1
  76. package/src/components/command-bar/routes/root/command-items.ts +9 -4
  77. package/src/components/command-bar/routes/root/runtime.ts +24 -1
  78. package/src/components/command-bar/surface/index.tsx +9 -3
  79. package/src/components/company-logo.tsx +2 -1
  80. package/src/components/layout/header.tsx +8 -3
  81. package/src/components/layout/market-summary.ts +25 -7
  82. package/src/components/layout/pane/footer/index.tsx +7 -2
  83. package/src/components/layout/pane/footer/model.ts +40 -5
  84. package/src/components/market-board/index.tsx +3 -2
  85. package/src/components/onboarding/desks-step.tsx +12 -4
  86. package/src/components/onboarding/first-run-workspace.ts +81 -4
  87. package/src/components/onboarding/onboarding-frame.tsx +2 -1
  88. package/src/components/onboarding/onboarding-keyboard.ts +8 -5
  89. package/src/components/onboarding/onboarding-wizard.tsx +54 -23
  90. package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
  91. package/src/components/onboarding/run-setup-again.ts +24 -0
  92. package/src/components/onboarding/use-onboarding-progress.ts +33 -15
  93. package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
  94. package/src/components/onboarding/wizard-model.ts +57 -3
  95. package/src/components/onboarding/wizard-positions.ts +1 -1
  96. package/src/components/ui/delayed-mark.tsx +56 -0
  97. package/src/components/ui/external-link.tsx +3 -6
  98. package/src/components/ui/icon.tsx +8 -1
  99. package/src/components/ui/logo-mark.tsx +8 -5
  100. package/src/components/ui/stat-grid.tsx +263 -19
  101. package/src/data/config/store/node.ts +11 -5
  102. package/src/data/config/store/normalize.ts +16 -3
  103. package/src/data/profile-permissions.ts +47 -0
  104. package/src/data/resource-store.ts +34 -31
  105. package/src/data/sqlite/access.ts +19 -8
  106. package/src/data/sqlite/checkpointer.ts +95 -8
  107. package/src/data/sqlite/database.ts +40 -7
  108. package/src/docs/snapshot.json +191 -78
  109. package/src/i18n/es.ts +18 -2
  110. package/src/i18n/ja.ts +18 -2
  111. package/src/i18n/ko.ts +18 -2
  112. package/src/i18n/zh-cn.ts +18 -2
  113. package/src/i18n/zh-tw.ts +18 -2
  114. package/src/layout/desk-keys.ts +4 -1
  115. package/src/layout/desks.ts +38 -5
  116. package/src/layout-marketplace/gallery-desktop.tsx +45 -16
  117. package/src/market-data/batched-reads.ts +16 -0
  118. package/src/market-data/coordinator/auxiliary.ts +4 -2
  119. package/src/market-data/coordinator/index.ts +11 -2
  120. package/src/market-data/futures-board.ts +1 -1
  121. package/src/market-data/futures-contract.ts +3 -1
  122. package/src/market-data/market/index-level.ts +6 -4
  123. package/src/market-data/published-apac-sessions.ts +6 -0
  124. package/src/news/aggregator.ts +7 -4
  125. package/src/news/news-model.ts +11 -0
  126. package/src/news/story-id.ts +22 -0
  127. package/src/news/ticker-symbols.ts +17 -1
  128. package/src/notifications/app-notifier.ts +35 -14
  129. package/src/plugins/builtin/account-management/form-components.tsx +1 -1
  130. package/src/plugins/builtin/account-management/index.tsx +2 -3
  131. package/src/plugins/builtin/account-management/model.ts +1 -1
  132. package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
  133. package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
  134. package/src/plugins/builtin/alerts/events.ts +10 -2
  135. package/src/plugins/builtin/alerts/index.tsx +5 -5
  136. package/src/plugins/builtin/alerts/research-builder.ts +5 -1
  137. package/src/plugins/builtin/alerts/research-rules.ts +18 -0
  138. package/src/plugins/builtin/alerts/targets.ts +4 -1
  139. package/src/plugins/builtin/attention/headless.ts +1 -1
  140. package/src/plugins/builtin/attention/index.tsx +2 -4
  141. package/src/plugins/builtin/attention/listing.ts +1 -1
  142. package/src/plugins/builtin/attention/series.ts +1 -1
  143. package/src/plugins/builtin/awards/index.tsx +1 -2
  144. package/src/plugins/builtin/backtest/client.ts +1 -0
  145. package/src/plugins/builtin/backtest/index.tsx +1 -2
  146. package/src/plugins/builtin/backtest/rules.ts +3 -1
  147. package/src/plugins/builtin/backtest/window.ts +8 -7
  148. package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
  149. package/src/plugins/builtin/broker-manager/index.tsx +1 -1
  150. package/src/plugins/builtin/buildout/cells.tsx +5 -0
  151. package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
  152. package/src/plugins/builtin/buildout/company/id.ts +2 -0
  153. package/src/plugins/builtin/buildout/company/model.ts +247 -0
  154. package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
  155. package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
  156. package/src/plugins/builtin/buildout/facilities.ts +309 -0
  157. package/src/plugins/builtin/buildout/format.ts +9 -0
  158. package/src/plugins/builtin/buildout/index.tsx +51 -5
  159. package/src/plugins/builtin/buildout/model/index.ts +62 -10
  160. package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
  161. package/src/plugins/builtin/buildout/model/types.ts +11 -2
  162. package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
  163. package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
  164. package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
  165. package/src/plugins/builtin/buildout/table-model.ts +26 -2
  166. package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
  167. package/src/plugins/builtin/cds/headless.ts +10 -2
  168. package/src/plugins/builtin/cds/model.ts +7 -3
  169. package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
  170. package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
  171. package/src/plugins/builtin/changelog/index.tsx +3 -2
  172. package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
  173. package/src/plugins/builtin/chart-composer/headless.ts +88 -21
  174. package/src/plugins/builtin/chart-composer/index.tsx +2 -3
  175. package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
  176. package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
  177. package/src/plugins/builtin/chart-composer/studies.ts +1 -1
  178. package/src/plugins/builtin/chat/controller/index.ts +1 -1
  179. package/src/plugins/builtin/chat/controller/messages.ts +15 -4
  180. package/src/plugins/builtin/chat/ghost-user.ts +8 -0
  181. package/src/plugins/builtin/chat/index.tsx +5 -5
  182. package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
  183. package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
  184. package/src/plugins/builtin/cloud/index.tsx +1 -2
  185. package/src/plugins/builtin/cloud/plugin.tsx +6 -4
  186. package/src/plugins/builtin/cloud/team/module.tsx +1 -1
  187. package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
  188. package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
  189. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
  190. package/src/plugins/builtin/comment-letters/index.tsx +1 -2
  191. package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
  192. package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
  193. package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
  194. package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
  195. package/src/plugins/builtin/congress-trades/headless.ts +2 -0
  196. package/src/plugins/builtin/congress-trades/model.ts +92 -64
  197. package/src/plugins/builtin/congress-trades/table.tsx +14 -2
  198. package/src/plugins/builtin/connections/index.ts +2 -3
  199. package/src/plugins/builtin/cot/index.tsx +1 -2
  200. package/src/plugins/builtin/cpi/index.tsx +1 -2
  201. package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
  202. package/src/plugins/builtin/credit-boards/client.ts +7 -2
  203. package/src/plugins/builtin/credit-boards/headless.ts +25 -12
  204. package/src/plugins/builtin/credit-boards/model.ts +29 -0
  205. package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
  206. package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
  207. package/src/plugins/builtin/crypto-board/headless.ts +4 -3
  208. package/src/plugins/builtin/crypto-board/index.tsx +4 -2
  209. package/src/plugins/builtin/crypto-board/series.ts +2 -0
  210. package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
  211. package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
  212. package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
  213. package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
  214. package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
  215. package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
  216. package/src/plugins/builtin/debt-maturities/model.ts +17 -1
  217. package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
  218. package/src/plugins/builtin/debug/index.tsx +1 -1
  219. package/src/plugins/builtin/dividend-yield/client.ts +32 -2
  220. package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
  221. package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
  222. package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
  223. package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
  224. package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
  225. package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
  226. package/src/plugins/builtin/dividend-yield/view.ts +21 -0
  227. package/src/plugins/builtin/docs/index.tsx +2 -3
  228. package/src/plugins/builtin/doe/headless.ts +16 -3
  229. package/src/plugins/builtin/doe/index.tsx +5 -4
  230. package/src/plugins/builtin/earnings/board.tsx +2 -1
  231. package/src/plugins/builtin/earnings/history.tsx +2 -1
  232. package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
  233. package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
  234. package/src/plugins/builtin/econ/calendar-model.ts +29 -6
  235. package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
  236. package/src/plugins/builtin/equity-screener/client.ts +1 -1
  237. package/src/plugins/builtin/equity-screener/headless.ts +1 -0
  238. package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
  239. package/src/plugins/builtin/equity-screener/index.tsx +1 -2
  240. package/src/plugins/builtin/equity-screener/model.ts +28 -4
  241. package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
  242. package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
  243. package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
  244. package/src/plugins/builtin/exposure/holdings.ts +35 -13
  245. package/src/plugins/builtin/exposure/index.tsx +1 -2
  246. package/src/plugins/builtin/fear-greed/cli.ts +44 -0
  247. package/src/plugins/builtin/fear-greed/index.tsx +5 -53
  248. package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
  249. package/src/plugins/builtin/fixed-income/index.tsx +1 -2
  250. package/src/plugins/builtin/futures/board-reads.ts +25 -0
  251. package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
  252. package/src/plugins/builtin/futures/index.tsx +15 -0
  253. package/src/plugins/builtin/futures/model.ts +14 -0
  254. package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
  255. package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
  256. package/src/plugins/builtin/futures-curve/headless.ts +6 -1
  257. package/src/plugins/builtin/futures-curve/index.tsx +1 -2
  258. package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
  259. package/src/plugins/builtin/gpu/board.tsx +51 -12
  260. package/src/plugins/builtin/gpu/client.ts +15 -2
  261. package/src/plugins/builtin/gpu/equities.tsx +5 -2
  262. package/src/plugins/builtin/gpu/headless.ts +14 -2
  263. package/src/plugins/builtin/gpu/index.tsx +1 -2
  264. package/src/plugins/builtin/gpu/model.ts +20 -2
  265. package/src/plugins/builtin/gpu/pane.tsx +1 -1
  266. package/src/plugins/builtin/help/index.tsx +1 -1
  267. package/src/plugins/builtin/holders/index-holders.ts +24 -12
  268. package/src/plugins/builtin/holders/index.tsx +2 -3
  269. package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
  270. package/src/plugins/builtin/iv-history/index.tsx +1 -2
  271. package/src/plugins/builtin/jodi/index.tsx +1 -2
  272. package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
  273. package/src/plugins/builtin/layout-manager/index.tsx +9 -8
  274. package/src/plugins/builtin/macro-day/pane.tsx +4 -2
  275. package/src/plugins/builtin/market-halts/index.tsx +1 -2
  276. package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
  277. package/src/plugins/builtin/market-movers/index.tsx +7 -1
  278. package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
  279. package/src/plugins/builtin/market-valuation/index.tsx +1 -2
  280. package/src/plugins/builtin/members/index.tsx +1 -2
  281. package/src/plugins/builtin/money-markets/index.tsx +1 -2
  282. package/src/plugins/builtin/money-markets/pane.tsx +2 -1
  283. package/src/plugins/builtin/news/headless.ts +7 -0
  284. package/src/plugins/builtin/news/wire/index.tsx +1 -1
  285. package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
  286. package/src/plugins/builtin/notes/index.tsx +3 -6
  287. package/src/plugins/builtin/openfda/index.tsx +1 -2
  288. package/src/plugins/builtin/options/view.tsx +6 -5
  289. package/src/plugins/builtin/options-calculator/index.tsx +1 -2
  290. package/src/plugins/builtin/options-positioning/index.tsx +1 -2
  291. package/src/plugins/builtin/options-scenario/index.tsx +2 -4
  292. package/src/plugins/builtin/perps/index.tsx +1 -2
  293. package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
  294. package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
  295. package/src/plugins/builtin/plugin-module.ts +16 -2
  296. package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
  297. package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
  298. package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
  299. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
  300. package/src/plugins/builtin/power/index.tsx +1 -2
  301. package/src/plugins/builtin/rate-path/index.tsx +1 -2
  302. package/src/plugins/builtin/realized-vol/headless.ts +2 -0
  303. package/src/plugins/builtin/realized-vol/index.tsx +1 -2
  304. package/src/plugins/builtin/realized-vol/model.ts +16 -2
  305. package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
  306. package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
  307. package/src/plugins/builtin/research/analyst-headless.ts +33 -6
  308. package/src/plugins/builtin/research/analyst-model.ts +37 -2
  309. package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
  310. package/src/plugins/builtin/research/index.tsx +5 -8
  311. package/src/plugins/builtin/research-search/index.tsx +1 -2
  312. package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
  313. package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
  314. package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
  315. package/src/plugins/builtin/roadmap/index.tsx +2 -3
  316. package/src/plugins/builtin/roadmap/pane.tsx +49 -2
  317. package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
  318. package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
  319. package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
  320. package/src/plugins/builtin/sec/filing-content.ts +38 -6
  321. package/src/plugins/builtin/sec/headless.ts +5 -1
  322. package/src/plugins/builtin/sec/index.tsx +4 -2
  323. package/src/plugins/builtin/sec/model.ts +2 -0
  324. package/src/plugins/builtin/sectors/index.tsx +11 -5
  325. package/src/plugins/builtin/series-inventory.ts +2 -0
  326. package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
  327. package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
  328. package/src/plugins/builtin/shared/forming-bars.ts +29 -0
  329. package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
  330. package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
  331. package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
  332. package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
  333. package/src/plugins/builtin/short-volume/pane.tsx +2 -1
  334. package/src/plugins/builtin/social-mentions/index.tsx +1 -2
  335. package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
  336. package/src/plugins/builtin/supply-chain/client.ts +22 -1
  337. package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
  338. package/src/plugins/builtin/supply-chain/index.tsx +1 -2
  339. package/src/plugins/builtin/thirteenf/api.ts +116 -13
  340. package/src/plugins/builtin/thirteenf/data.ts +132 -30
  341. package/src/plugins/builtin/thirteenf/index.tsx +2 -4
  342. package/src/plugins/builtin/thirteenf/model.ts +2 -0
  343. package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
  344. package/src/plugins/builtin/thirteenf/types.ts +12 -0
  345. package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
  346. package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
  347. package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
  348. package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
  349. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
  350. package/src/plugins/builtin/time-sales/index.tsx +1 -2
  351. package/src/plugins/builtin/time-sales/pane.tsx +3 -1
  352. package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
  353. package/src/plugins/builtin/trace-bonds/model.ts +44 -10
  354. package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
  355. package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
  356. package/src/plugins/builtin/vol-surface/client.ts +18 -3
  357. package/src/plugins/builtin/vol-surface/index.tsx +1 -2
  358. package/src/plugins/builtin/volatility/index.tsx +1 -2
  359. package/src/plugins/builtin/world-indices/footer.ts +6 -1
  360. package/src/plugins/builtin/world-venue-map/client.ts +10 -1
  361. package/src/plugins/builtin/world-venue-map/index.ts +6 -2
  362. package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
  363. package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
  364. package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
  365. package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
  366. package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
  367. package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
  368. package/src/plugins/builtin/yield-curve/curves.ts +16 -2
  369. package/src/plugins/builtin/yield-curve/headless.ts +3 -3
  370. package/src/plugins/bundle.ts +52 -2
  371. package/src/plugins/catalog-browser.ts +2 -2
  372. package/src/plugins/catalog-cli.ts +20 -0
  373. package/src/plugins/host-link.ts +13 -1
  374. package/src/plugins/installer.ts +15 -4
  375. package/src/plugins/loader.ts +43 -11
  376. package/src/plugins/peer-imports.ts +201 -0
  377. package/src/plugins/peer-retry.ts +26 -29
  378. package/src/plugins/plugin-process.ts +128 -0
  379. package/src/plugins/redirection-guard.ts +45 -0
  380. package/src/remote/endpoint.ts +16 -3
  381. package/src/renderers/browser/main.tsx +3 -2
  382. package/src/renderers/cloudflare/http-proxy.ts +7 -123
  383. package/src/renderers/dom/desktop/icons.tsx +2 -0
  384. package/src/renderers/dom/host/image-surface.tsx +46 -21
  385. package/src/renderers/dom/styles.css +7 -0
  386. package/src/renderers/dom/toast-host.tsx +2 -0
  387. package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
  388. package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
  389. package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
  390. package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
  391. package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
  392. package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
  393. package/src/renderers/electrobun/bun/window/create.ts +4 -2
  394. package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
  395. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
  396. package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
  397. package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
  398. package/src/renderers/electrobun/view/main.tsx +7 -2
  399. package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
  400. package/src/renderers/opentui/browser-open.ts +10 -4
  401. package/src/renderers/opentui/image/surface.tsx +1 -1
  402. package/src/renderers/opentui/start.tsx +12 -6
  403. package/src/renderers/opentui/toast-host.tsx +2 -1
  404. package/src/series/registry.ts +18 -0
  405. package/src/series/standalone.ts +10 -1
  406. package/src/series/types.ts +7 -0
  407. package/src/series/validate.ts +11 -2
  408. package/src/sources/gloomberb-cloud/index.ts +28 -5
  409. package/src/sources/gloomberb-cloud/news.ts +24 -3
  410. package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
  411. package/src/sources/provider-errors.ts +26 -3
  412. package/src/sources/provider-router/history.ts +5 -0
  413. package/src/sources/provider-router/index.ts +13 -0
  414. package/src/sync/core-contributors.ts +32 -0
  415. package/src/telemetry/attention-counts.ts +64 -6
  416. package/src/telemetry/attention-setting.ts +33 -0
  417. package/src/telemetry/crash-reports-node.ts +3 -1
  418. package/src/telemetry/stall-clocks.ts +93 -0
  419. package/src/telemetry/stall-reports-node.ts +12 -7
  420. package/src/telemetry/stall-reports.ts +34 -5
  421. package/src/test-support/preload.ts +9 -0
  422. package/src/tickers/company-name.ts +19 -0
  423. package/src/tickers/listing-fate.ts +14 -3
  424. package/src/tickers/saved-instrument.ts +17 -0
  425. package/src/tickers/search/home-listing.ts +13 -2
  426. package/src/tickers/search/index.ts +4 -1
  427. package/src/tickers/search/types.ts +6 -1
  428. package/src/time-series/bar-aggregation.ts +7 -1
  429. package/src/time-series/base-series.ts +3 -3
  430. package/src/time-series/chart-bounds.ts +1 -1
  431. package/src/time-series/chart-data.ts +5 -2
  432. package/src/time-series/chart-seed.ts +1 -1
  433. package/src/time-series/coin-session-close.ts +211 -0
  434. package/src/time-series/live-bars.ts +13 -2
  435. package/src/time-series/live-quotes.ts +1 -1
  436. package/src/time-series/official-close.ts +159 -0
  437. package/src/time-series/price-comparison.ts +55 -6
  438. package/src/time-series/resolve.ts +12 -3
  439. package/src/time-series/series-presentation.ts +1 -1
  440. package/src/time-series/studies/formula.ts +205 -0
  441. package/src/time-series/studies/indicators.ts +299 -0
  442. package/src/time-series/studies/leverage.ts +75 -0
  443. package/src/time-series/studies/pair.ts +138 -0
  444. package/src/time-series/studies/resolve.ts +333 -0
  445. package/src/time-series/studies/shared.ts +141 -0
  446. package/src/time-series/studies/units.ts +243 -0
  447. package/src/time-series/studies/vwap.ts +274 -0
  448. package/src/types/config.ts +18 -2
  449. package/src/types/data-provider.ts +23 -0
  450. package/src/types/financials.ts +22 -0
  451. package/src/types/headless.ts +42 -3
  452. package/src/types/plugin.ts +32 -3
  453. package/src/ui/host.tsx +11 -0
  454. package/src/ui/toast.tsx +26 -1
  455. package/src/utils/ascii-text.ts +140 -0
  456. package/src/utils/cli-output.ts +91 -18
  457. package/src/utils/concurrency.ts +24 -0
  458. package/src/utils/exchanges.ts +6 -0
  459. package/src/utils/external-url.ts +20 -7
  460. package/src/utils/futures-generic.ts +4 -4
  461. package/src/utils/http-proxy-upstream.ts +124 -0
  462. package/src/utils/main-thread-stall.ts +75 -3
  463. package/src/utils/perf-marks.ts +36 -2
  464. package/src/utils/private-files.ts +141 -0
  465. package/src/utils/public-address.ts +99 -0
  466. package/src/account/upgrade-personalization.ts +0 -57
  467. package/src/account/upgrade-tickers.ts +0 -124
  468. package/src/api-client/upgrade-experiment.ts +0 -91
  469. package/src/plugins/builtin/cloud/browser.tsx +0 -3
  470. package/src/renderers/electrobun/launcher/preload.ts +0 -13
  471. package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
  472. package/src/time-series/studies.ts +0 -1628
@@ -0,0 +1,94 @@
1
+ import { useEffect, useRef, useSyncExternalStore } from "react";
2
+ import { apiClient } from "../api-client";
3
+ import { t } from "../i18n";
4
+ import { useAppActive } from "../state/app/activity";
5
+ import { useAppDispatch, useAppGetState, useAppSelector } from "../state/app/context";
6
+ import { scheduleConfigSave } from "../state/config-save-scheduler";
7
+ import { cloudSyncController } from "../sync/controller";
8
+ import { attentionCollectionAvailable } from "../telemetry/attention-counts";
9
+ import { ATTENTION_OFF_MESSAGE, attentionNoticeDue, withAttentionNoticeShown } from "../telemetry/attention-setting";
10
+ import { reportTelemetryConfig } from "../telemetry/live-config";
11
+ import type { TelemetryConfig } from "../types/config";
12
+ import type { AppNotificationRequest } from "../types/plugin";
13
+
14
+ /**
15
+ * The whole notice, with no title: the terminal draws none, and it has to
16
+ * name the setting its Turn off acts on and say plainly what leaves the app.
17
+ */
18
+ const ATTENTION_NOTICE_TEXT = "Attention Counts is now on. Tickers you open are sent to Gloom with your account; only hourly totals of 20+ people are published.";
19
+
20
+ /** The signed-in account with a verified email, or null; the same account the collector counts for. */
21
+ function verifiedAccount(): string | null {
22
+ return apiClient.getVerifiedSessionIdentity() ? apiClient.getCurrentUser()?.id ?? null : null;
23
+ }
24
+
25
+ /** Where the account's synced settings come from; whether they were pulled this session. */
26
+ export interface AccountSettingsSource {
27
+ subscribe(listener: () => void): () => void;
28
+ getStatus(): { lastPullAt: string | null };
29
+ }
30
+
31
+ /**
32
+ * The one-time notice that Attention Counts is on by default: a toast that
33
+ * stays until dismissed, with Turn off. It shows once per account on this
34
+ * installation, while the app is in front, to a verified account whose
35
+ * setting is untouched, and only after this session has pulled the
36
+ * account's synced settings, so an off made on another device arrives
37
+ * before it. Nothing is counted under the default until the toast has been
38
+ * drawn for that account; an explicit on or off never sees it.
39
+ */
40
+ export function AttentionCountsNotice({
41
+ notify,
42
+ accountSettings = cloudSyncController,
43
+ }: {
44
+ notify(request: AppNotificationRequest): void;
45
+ accountSettings?: AccountSettingsSource;
46
+ }) {
47
+ const dispatch = useAppDispatch();
48
+ const getState = useAppGetState();
49
+ const telemetry = useAppSelector((state) => state.config.telemetry);
50
+ const account = useSyncExternalStore((onChange) => apiClient.subscribeCurrentUser(onChange), verifiedAccount, verifiedAccount);
51
+ const pulled = useSyncExternalStore(
52
+ (onChange) => accountSettings.subscribe(onChange),
53
+ () => accountSettings.getStatus().lastPullAt !== null,
54
+ () => accountSettings.getStatus().lastPullAt !== null,
55
+ );
56
+ const onScreen = useAppActive();
57
+ /** Accounts this session already showed it to, so a re-render never stacks a second toast. */
58
+ const asked = useRef(new Set<string>());
59
+ const due = pulled && attentionNoticeDue(telemetry, account) && attentionCollectionAvailable();
60
+
61
+ useEffect(() => {
62
+ if (!due || !onScreen || !account || asked.current.has(account)) return;
63
+ asked.current.add(account);
64
+ const save = (change: (telemetry: TelemetryConfig | undefined) => TelemetryConfig | undefined) => {
65
+ const current = getState().config;
66
+ const next = change(current.telemetry);
67
+ if (next === current.telemetry) return;
68
+ // Reported first, so turning off drops unsent counts before React commits.
69
+ reportTelemetryConfig(next);
70
+ dispatch({ type: "SET_CONFIG", config: { ...current, telemetry: next } });
71
+ scheduleConfigSave(() => {
72
+ const state = getState();
73
+ return { ...state.config, recentTickers: state.recentTickers };
74
+ });
75
+ };
76
+ notify({
77
+ body: t(ATTENTION_NOTICE_TEXT),
78
+ type: "info",
79
+ persistent: true,
80
+ action: {
81
+ label: t("Turn off"),
82
+ onClick: () => {
83
+ save((current) => ({ ...current, attention: false }));
84
+ notify({ body: t(ATTENTION_OFF_MESSAGE), type: "success" });
85
+ },
86
+ },
87
+ onShown: () => {
88
+ if (verifiedAccount() === account) save((current) => withAttentionNoticeShown(current, account));
89
+ },
90
+ });
91
+ }, [account, dispatch, due, getState, notify, onScreen]);
92
+
93
+ return null;
94
+ }
package/src/app.tsx CHANGED
@@ -20,6 +20,7 @@ import { DetachedPaneShell } from "./components/layout/detached-pane-shell";
20
20
  import { TransientLayoutProvider } from "./components/layout/transient-layout";
21
21
  import { CommandBar } from "./components/command-bar/surface";
22
22
  import { OnboardingWizard } from "./components/onboarding/onboarding-wizard";
23
+ import { useRunSetupAgain } from "./components/onboarding/run-setup-again";
23
24
  import { CompanyPickerHost } from "./plugins/builtin/cloud/company-picker";
24
25
  import { TerminalRelayHost } from "./plugins/builtin/cloud/terminal-relay/host";
25
26
  import { SignInGate } from "./components/sign-in-gate";
@@ -30,7 +31,7 @@ import type { AppServicesFactory, AppTickerRepositoryPort } from "./core/app-ser
30
31
  import { useThemeColors } from "./theme/theme-context";
31
32
  import type { AppConfig } from "./types/config";
32
33
  import type { DesktopDeepLinkBridge } from "./types/desktop-deeplink";
33
- import type { CliLaunchRequest, GloomPlugin } from "./types/plugin";
34
+ import type { AppNotificationRequest, CliLaunchRequest, GloomPlugin } from "./types/plugin";
34
35
  import type { DataProvider } from "./types/data-provider";
35
36
  import type { DesktopDockPreviewState, DesktopSharedStateSnapshot, DesktopThemePreviewState, DesktopWindowBridge } from "./types/desktop-window";
36
37
  import type { DesktopApplicationMenuBridge } from "./types/desktop-menu";
@@ -69,6 +70,7 @@ import { AppLanguageConfigObserver } from "./app/language-observer";
69
70
  import { isPaneShareHandoff } from "./shares/location";
70
71
  import { apiClient } from "./api-client";
71
72
  import { reportTelemetryConfig } from "./telemetry/live-config";
73
+ import { AttentionCountsNotice } from "./app/attention-notice";
72
74
  import { recordFunctionOpen } from "./telemetry/usage-counts";
73
75
 
74
76
  const EMPTY_EXTERNAL_PLUGINS: LoadedExternalPlugin[] = [];
@@ -87,6 +89,8 @@ interface AppInnerProps {
87
89
  updatesEnabled?: boolean;
88
90
  onboardingActive?: boolean;
89
91
  onOnboardingComplete?: (config: AppConfig) => void | Promise<void>;
92
+ /** Shows setup again once Run Setup Again has saved its progress; absent where setup cannot run. */
93
+ onRunSetupAgain?: () => void;
90
94
  /** Hosted browser terminal: nothing is reachable until a session exists. */
91
95
  signInGateActive?: boolean;
92
96
  /** Fires once the startup state is in place and the first real layout can paint. */
@@ -127,6 +131,7 @@ function AppInner({
127
131
  updatesEnabled = true,
128
132
  onboardingActive = false,
129
133
  onOnboardingComplete,
134
+ onRunSetupAgain,
130
135
  signInGateActive = false,
131
136
  onInitialized,
132
137
  pluginsReady,
@@ -186,6 +191,8 @@ function AppInner({
186
191
  const dialog = useDialog();
187
192
  const toast = useToastHost();
188
193
  const isDetachedWindow = desktopWindowBridge?.kind === "detached";
194
+ // Not while setup is already showing.
195
+ const runSetupAgain = useRunSetupAgain(onboardingActive ? undefined : onRunSetupAgain);
189
196
  const detachedPaneId = isDetachedWindow ? desktopWindowBridge.paneId ?? null : null;
190
197
  const focusDetachedPane = useMemo(() => {
191
198
  const focus = desktopWindowBridge?.kind === "main" ? desktopWindowBridge.focusDetachedPane : undefined;
@@ -222,6 +229,7 @@ function AppInner({
222
229
  onClick: dismissAfter(() => notification.secondaryAction?.onClick()),
223
230
  }
224
231
  : undefined,
232
+ onShown: notification.onShown,
225
233
  };
226
234
  if (type === "success") toastId = toast.success(notification.body, options);
227
235
  else if (type === "error") toastId = toast.error(notification.body, options);
@@ -232,6 +240,9 @@ function AppInner({
232
240
  const notify = useCallback((body: string, options?: { type?: "info" | "success" | "error" }) => {
233
241
  pluginRegistry.notify({ body, ...options });
234
242
  }, [pluginRegistry]);
243
+ const notifyRequest = useCallback((request: AppNotificationRequest) => {
244
+ pluginRegistry.notify(request);
245
+ }, [pluginRegistry]);
235
246
 
236
247
  useEffect(() => {
237
248
  if (desktopWindowBridge?.kind !== "main" || !desktopWindowBridge.subscribeDockPreview) return;
@@ -489,6 +500,7 @@ function AppInner({
489
500
  />
490
501
  </TransientLayoutProvider>
491
502
  {!onboardingActive ? <CompanyPickerHost pluginRegistry={pluginRegistry} /> : null}
503
+ {initialized && !onboardingActive && !signInGateActive ? <AttentionCountsNotice notify={notifyRequest} /> : null}
492
504
  <TerminalRelayHost device={remoteControlAdapter?.terminalDevice} />
493
505
  {onboardingActive && onOnboardingComplete ? (
494
506
  <OnboardingWizard
@@ -505,6 +517,7 @@ function AppInner({
505
517
  pluginRegistry={pluginRegistry}
506
518
  quitApp={() => rendererHost.requestExit()}
507
519
  onCheckForUpdates={updatesEnabled ? runUpdateCommand : undefined}
520
+ onRunSetupAgain={runSetupAgain}
508
521
  onNativeOccluderChange={setCommandBarNativeOccluder}
509
522
  />
510
523
  )}
@@ -534,6 +547,11 @@ interface AppProps {
534
547
  * terminal sets this; desktop and the TUI keep sign-in optional.
535
548
  */
536
549
  requireSignIn?: boolean;
550
+ /**
551
+ * Settings can open setup again (Run Setup Again). The browser keeps no
552
+ * setup progress, so it turns this off.
553
+ */
554
+ setupEnabled?: boolean;
537
555
  /** Fires once the startup state is in place and the first real layout can paint. */
538
556
  onInitialized?: () => void;
539
557
  }
@@ -552,6 +570,7 @@ export function App({
552
570
  remoteControlAdapter,
553
571
  updatesEnabled = true,
554
572
  requireSignIn = false,
573
+ setupEnabled = true,
555
574
  onInitialized,
556
575
  }: AppProps) {
557
576
  useAppLanguage();
@@ -594,6 +613,8 @@ export function App({
594
613
  && (!effectiveInitialConfig.onboardingComplete || !!effectiveInitialConfig.onboardingProgress)
595
614
  ));
596
615
 
616
+ const showSetup = useCallback(() => setShowOnboarding(true), []);
617
+
597
618
  useEffect(() => bindAppActivity(renderer), [renderer]);
598
619
 
599
620
  const services = useMemo(() => {
@@ -656,6 +677,7 @@ export function App({
656
677
  setConfig(updatedConfig);
657
678
  setShowOnboarding(false);
658
679
  }}
680
+ onRunSetupAgain={setupEnabled && !shareHandoff ? showSetup : undefined}
659
681
  />
660
682
  </AppProvider>
661
683
  </RemoteUiRegistryProvider>
@@ -9,7 +9,7 @@
9
9
  "src/cli/commands/options-chain.ts": ["options","options-calculator"],
10
10
  "src/cli/commands/options-leaps.ts": ["iv-history","options","vol-surface"],
11
11
  "src/cli/commands/overview.ts": ["correlation","econ","market-movers","sectors","world-indices"],
12
- "src/cli/commands/ticker.ts": ["ipo-calendar","notes"],
12
+ "src/cli/commands/ticker.ts": ["ipo-calendar","notes","sec"],
13
13
  "src/cli/desktop-pane-shot.ts": ["market-valuation","time-sales"],
14
14
  "src/cli/pane-functions/data.ts": ["chart-composer","correlation","seasonality","ticker-detail"],
15
15
  "src/cli/pane-functions/financial-ratio-shot.ts": ["ticker-detail"],
@@ -28,6 +28,7 @@
28
28
  "src/components/onboarding/use-onboarding-upgrade.ts": ["chat"],
29
29
  "src/layout/desks.ts": ["chart-composer"],
30
30
  "src/plugins/catalog-browser.ts": ["alerts","analytics","attention","awards","backtest","bond-calculator","catalysts","cds","central-bank-rates","changelog","chart-composer","clinical-trials","cloud","comment-letters","commodities-energy","commodity-balances","company-attention","company-kpis","connections","correlation","cot","cpi","credit-boards","credit-conditions","credit-documents","crypto-board","crypto-etf-flows","debt-maturities","dividend-yield","docs","doe","earnings","earnings-calls","earnings-ripple","econ","econ-statistics","equity-screener","estimate-revisions","executives","exposure","fear-greed","filing-events","fixed-income","futures","futures-curve","fx-matrix","gpu","help","holders","insider","ipo-calendar","iv-history","jobs","jodi","kelly-sizer","layout-manager","macro-day","market-halts","market-heatmap","market-valuation","members","mna","money-markets","news","openfda","options","options-calculator","options-positioning","options-scenario","pe-band","perps","portfolio-list","power","primary-dealers","rate-path","realized-vol","relative-rotation","research","research-search","revenue-breakdown","reverse-dcf","risk-factors","roadmap","scanner","seasonality","sec","sectors","short-interest","short-volume","social-mentions","supply-chain","thirteenf","ticker-detail","time-sales","trace-bonds","treasury-auctions","treasury-daily","vol-surface","volatility","world-indices","world-venue-map","yield-curve"],
31
+ "src/plugins/catalog-cli.ts": ["alerts","fear-greed","news","notes","portfolio-list"],
31
32
  "src/plugins/catalog-ui.ts": ["alerts","clinical-trials","cloud","comment-letters","custom-view","fear-greed","ipo-calendar","market-halts","market-heatmap","members","news","notes","openfda","research-search"],
32
33
  "src/plugins/catalog.ts": ["debug"],
33
34
  "src/remote/controller.ts": ["custom-view"],
@@ -36,6 +36,7 @@ export function assetDataProvider(provider: AssetDataProvider): AssetDataCapabil
36
36
  operations: {
37
37
  canProvide: op((input: any) => provider.canProvide?.(input.ticker, input.exchange, input.context) ?? true, "query"),
38
38
  primaryMarketSourceName: op(() => provider.primaryMarketSourceName?.() ?? provider.name, "query"),
39
+ batchesReads: op((input: any) => provider.batchesReads?.(input.method) ?? false, "query"),
39
40
  getCachedFinancialsForTargets: op((input: any) => provider.getCachedFinancialsForTargets?.(input.targets ?? [], input.options) ?? new Map()),
40
41
  getQuotesBatch: op((input: any) => provider.getQuotesBatch?.(input.targets ?? [], input.options) ?? Promise.resolve([])),
41
42
  getTickerFinancialsBatch: op((input: any) => provider.getTickerFinancialsBatch?.(input.targets ?? [], input.options) ?? Promise.resolve([])),
@@ -0,0 +1,42 @@
1
+ import { format } from "util";
2
+ import { asciiText } from "../utils/ascii-text";
3
+
4
+ const NON_ASCII = /[^\x00-\x7F]/;
5
+
6
+ type ConsoleMethod = "log" | "info" | "warn" | "error";
7
+ const CONSOLE_STREAMS: ReadonlyArray<readonly [ConsoleMethod, "stdout" | "stderr"]> = [
8
+ ["log", "stdout"], ["info", "stdout"], ["warn", "stderr"], ["error", "stderr"],
9
+ ];
10
+
11
+ /**
12
+ * Writes the command's text in ASCII while `enabled` says so (`cliAsciiEnabled`:
13
+ * plain output to a terminal that cannot show UTF-8), whichever way it is
14
+ * written: `process.stdout.write`, `process.stderr.write` or the console, which
15
+ * Bun writes without going through either stream. Tables are already ASCII
16
+ * when they are drawn, so their columns line up (`renderTable`); this catches
17
+ * the lines around them, such as a source line's `·`. A chunk that is not a
18
+ * string (a PNG on stdout) passes as it is, and so does all output while
19
+ * `enabled` is false.
20
+ */
21
+ export function writeAsciiWhile(enabled: () => boolean): void {
22
+ const needsAscii = (text: string) => NON_ASCII.test(text) && enabled();
23
+ for (const name of ["stdout", "stderr"] as const) {
24
+ const stream = process[name];
25
+ const write = stream.write.bind(stream) as (chunk: unknown, ...rest: unknown[]) => boolean;
26
+ stream.write = ((chunk: unknown, ...rest: unknown[]) => write(
27
+ typeof chunk === "string" && needsAscii(chunk) ? asciiText(chunk) : chunk,
28
+ ...rest,
29
+ )) as typeof stream.write;
30
+ }
31
+ for (const [method, name] of CONSOLE_STREAMS) {
32
+ const original = console[method].bind(console);
33
+ console[method] = (...args: unknown[]) => {
34
+ const text = format(...args);
35
+ if (!needsAscii(text)) {
36
+ original(...args);
37
+ return;
38
+ }
39
+ process[name].write(`${asciiText(text)}\n`);
40
+ };
41
+ }
42
+ }
@@ -3,12 +3,14 @@ import { fixedUsdRate, heldCurrencyUnit } from "../utils/currency-units";
3
3
  import { logFailure } from "../utils/log-failure";
4
4
 
5
5
  /** One rate a converter used: units of `baseCurrency` per unit of `currency`, and when it was observed. */
6
- interface BaseConversion {
6
+ export interface BaseConversion {
7
7
  currency: string;
8
8
  baseCurrency: string;
9
9
  rate: number;
10
10
  /** Epoch ms of the older leg's observation; null when the source did not date it. */
11
11
  asOf: number | null;
12
+ /** A leg is past its fresh window, or its refresh failed and the last copy stands. */
13
+ stale: boolean;
12
14
  }
13
15
 
14
16
  /** Converts an amount into the base currency, and lists the rates it used so a report can state them. */
@@ -21,6 +23,8 @@ export type BaseConverter = ((value: number, fromCurrency: string) => Promise<nu
21
23
  interface UsdRate {
22
24
  rate: number;
23
25
  asOf: number | null;
26
+ /** Past its fresh window, or kept after a failed refresh. */
27
+ stale?: boolean;
24
28
  }
25
29
 
26
30
  export function createBaseConverter(
@@ -38,7 +42,10 @@ export function createBaseConverter(
38
42
  if (dataProvider.getCachedQuery) {
39
43
  const entry = await dataProvider.getCachedQuery("getExchangeRate", [currency])
40
44
  .load({ background: dataProvider.revalidatesInBackground });
41
- return { rate: Number.isFinite(entry.value) && entry.value > 0 ? entry.value : Number.NaN, asOf: entry.asOf ?? null };
45
+ return {
46
+ rate: Number.isFinite(entry.value) && entry.value > 0 ? entry.value : Number.NaN, asOf: entry.asOf ?? null,
47
+ stale: entry.refreshError != null || entry.staleAt <= Date.now(),
48
+ };
42
49
  }
43
50
  const rate = await dataProvider.getExchangeRate(currency);
44
51
  return { rate: Number.isFinite(rate) && rate > 0 ? rate : Number.NaN, asOf: null };
@@ -73,6 +80,7 @@ export function createBaseConverter(
73
80
  baseCurrency: base.currency,
74
81
  rate: fromRate.rate / baseRate.rate,
75
82
  asOf: dates.length > 0 ? Math.min(...dates) : null,
83
+ stale: fromRate.stale === true || baseRate.stale === true,
76
84
  });
77
85
  return (value / from.divisor * fromRate.rate) / baseRate.rate * base.divisor;
78
86
  };
@@ -55,6 +55,21 @@ function declaredOptions(command: CliCommandDef): Map<string, DeclaredOption> |
55
55
  const EXPLAIN_HINT = "--explain follows the reports of fn <function> and portfolio show. "
56
56
  + "gloom catalog explain <term> prints what one term means; gloom catalog glossary lists every term.";
57
57
 
58
+ /** Options people reach for to see a price in another currency, which no command takes: `fx` converts. */
59
+ const CURRENCY_OPTIONS = new Set(["--currency", "--ccy", "--cur", "--convert", "--fx", "--to-currency", "--in-currency"]);
60
+
61
+ /**
62
+ * `quote SBLK --currency EUR`: prices are in each listing's currency, and
63
+ * `fx` converts an amount, so the error names it, with the code given.
64
+ */
65
+ function currencyOptionHint(flag: string, args: readonly string[]): string | null {
66
+ if (!CURRENCY_OPTIONS.has(flag)) return null;
67
+ const at = args.findIndex((arg) => arg === flag || arg.startsWith(`${flag}=`));
68
+ const typed = (args[at]?.includes("=") ? args[at]!.slice(flag.length + 1) : args[at + 1] ?? "").trim().toUpperCase();
69
+ const code = /^[A-Z]{3}$/.test(typed) ? typed : "EUR";
70
+ return `Prices are in each listing's own currency. To convert an amount, use fx: gloom fx 100 USD/${code}.`;
71
+ }
72
+
58
73
  function failUnknownOption(
59
74
  command: BuiltinCliCommandDef,
60
75
  flag: string,
@@ -65,7 +80,7 @@ function failUnknownOption(
65
80
  const suggestion = suggestCliOption(flag, flags);
66
81
  const help = `See gloom help ${command.name}.`;
67
82
  fail(`Unknown option ${flag} for ${command.name}.`, [
68
- command.unknownOptionHint?.(flag, args) ?? (flag === "--explain" ? EXPLAIN_HINT : ""),
83
+ command.unknownOptionHint?.(flag, args) ?? currencyOptionHint(flag, args) ?? (flag === "--explain" ? EXPLAIN_HINT : ""),
69
84
  suggestion ? `Did you mean ${suggestion}?` : "",
70
85
  flags.length > 0 ? `Options: ${flags.join(", ")}. ${help}` : `${command.name} takes no options. ${help}`,
71
86
  ].filter(Boolean).join("\n"));
@@ -73,21 +73,32 @@ export function coinElsewhereNote(symbol: string, listings: ReadonlyArray<{ key:
73
73
  return `${symbol} is ${named}, not ${coin}. For ${coin} use ${symbol}-USD.`;
74
74
  }
75
75
 
76
+ /** A top coin quoted in dollars (BTC-USD, or on the crypto venue BTC-USD:CCC), which the CRYP board lists. */
77
+ function isBoardCoinPair(symbol: string): boolean {
78
+ const [pair = "", venue] = symbol.trim().toUpperCase().split(":");
79
+ const match = venue === undefined || venue === "CCC" ? /^([A-Z0-9]+)-USD$/.exec(pair) : null;
80
+ return !!match && MAJOR_COINS.has(match[1]!);
81
+ }
82
+
76
83
  interface QuoteNoteResult {
77
84
  target: { symbol: string };
78
- quote: Pick<Quote, "name" | "instrumentType"> | null;
85
+ quote: Pick<Quote, "name" | "instrumentType"> & Partial<Pick<Quote, "dataSource">> | null;
79
86
  error: string | null;
80
87
  }
81
88
 
82
89
  /**
83
- * The router words a failure as "<reason> for <symbol>"; drop the symbol so equal reasons group.
84
- * Any other line, such as a sentence the data service wrote, is kept as it reads.
90
+ * The router words a failure as "<reason> for <symbol>", alone or closing its
91
+ * first sentence ("Gloom's data service did not answer for X. Try again in a
92
+ * moment."); drop the symbol so equal reasons group. Any other line, such as a
93
+ * sentence the data service wrote, is kept as it reads.
85
94
  */
86
95
  function failureReason(message: string, symbol: string): string {
87
96
  const line = (message.split("\n")[0] ?? "").trim();
88
97
  const bare = line.replace(/\.+$/, "");
89
98
  const suffix = ` for ${symbol}`;
90
- return bare.toLowerCase().endsWith(suffix.toLowerCase()) ? bare.slice(0, -suffix.length) : line;
99
+ if (bare.toLowerCase().endsWith(suffix.toLowerCase())) return bare.slice(0, -suffix.length);
100
+ const at = line.toLowerCase().indexOf(`${suffix.toLowerCase()}. `);
101
+ return at > 0 ? `${line.slice(0, at)}${line.slice(at + suffix.length)}` : line;
91
102
  }
92
103
 
93
104
  /**
@@ -112,6 +123,14 @@ export function quoteNotes(results: QuoteNoteResult[]): string[] {
112
123
  const perpetuals = results
113
124
  .filter((result) => result.quote && isCryptoVenuePerpetual(result.quote.instrumentType, result.quote.name))
114
125
  .map((result) => result.target.symbol);
126
+ // A top coin's delayed quote is live on the crypto board, which needs no plan to stream it.
127
+ const boardCoins = results
128
+ .filter((result) => result.quote?.dataSource === "delayed" && isCryptoInstrumentType(result.quote.instrumentType ?? "")
129
+ && isBoardCoinPair(result.target.symbol))
130
+ .map((result) => result.target.symbol);
131
+ if (boardCoins.length > 0) {
132
+ notes.push(`${boardCoins.join(", ")} ${boardCoins.length === 1 ? "streams" : "stream"} live on the CRYP board (gloom fn CRYP).`);
133
+ }
115
134
  if (perpetuals.length > 0) {
116
135
  notes.push(`${perpetuals.join(", ")}: perpetual futures ${perpetuals.length === 1 ? "price" : "prices"} on crypto venues, 24/7; not the listed future, not spot.`);
117
136
  }