@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
@@ -0,0 +1,201 @@
1
+ import { readFileSync, realpathSync } from "fs";
2
+ import { dirname, extname, isAbsolute, join, relative } from "path";
3
+ import type { BunPlugin } from "bun";
4
+
5
+ import { installedPeerPlugins } from "./host-link";
6
+
7
+ /**
8
+ * A plugin's imports of a sibling plugin it declares as a peer (IBKR Gateway
9
+ * imports `gloom-ibkr/gateway-bridge`), read from the sibling's own folder
10
+ * instead of through the link in the plugin's `node_modules`.
11
+ *
12
+ * That link is a junction on Windows, and a process can be unable to follow
13
+ * a junction at all. On the first launch after an update the gateway failed
14
+ * with its link in place and pointing at Interactive Brokers: the loader wrote
15
+ * it again four times, never saw it resolve, and an import that looked again
16
+ * failed the same way, while Interactive Brokers itself loaded from its folder
17
+ * in the same process. The next launch worked.
18
+ *
19
+ * So the host resolves those imports itself and hands Bun the sibling's file
20
+ * by its absolute path: the file the link leads to, under the module key the
21
+ * sibling's own imports give it, so the plugin still shares the sibling's
22
+ * modules. The terminal and the desktop's Bun process rewrite the plugin's
23
+ * source as Bun loads it; the desktop's compile for the view resolves them in
24
+ * the bundler. The link stays, for editors, `tsc`, and older Gloom versions.
25
+ */
26
+
27
+ type SourceLoader = "ts" | "tsx" | "js" | "jsx";
28
+
29
+ /** What Bun loads a plugin's own source files with, by extension. */
30
+ export const SOURCE_LOADERS: Readonly<Record<string, SourceLoader>> = {
31
+ ".ts": "ts",
32
+ ".mts": "ts",
33
+ ".tsx": "tsx",
34
+ ".js": "js",
35
+ ".mjs": "js",
36
+ ".jsx": "jsx",
37
+ };
38
+
39
+ export function sourceLoader(file: string): SourceLoader {
40
+ return SOURCE_LOADERS[extname(file).toLowerCase()] ?? "js";
41
+ }
42
+
43
+ export function isInside(root: string, path: string): boolean {
44
+ const inside = relative(root, path);
45
+ return !!inside && !inside.startsWith("..") && !isAbsolute(inside);
46
+ }
47
+
48
+ function escapeRegExp(text: string): string {
49
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50
+ }
51
+
52
+ /** Where a module is named: after `from`, `import` or `import(`, and in `require(`. */
53
+ const IMPORTED_AT = String.raw`(\bfrom\s*|\bimport\s*(?:\(\s*)?|\brequire\s*\(\s*)`;
54
+
55
+ /**
56
+ * `contents` with `specifier` replaced by `replacement` where it is imported.
57
+ * The same text in a string anywhere else stays.
58
+ */
59
+ export function replaceSpecifier(contents: string, specifier: string, replacement: string): string {
60
+ const imported = new RegExp(`${IMPORTED_AT}(["'\`])${escapeRegExp(specifier)}\\2`, "g");
61
+ return contents.replace(imported, (_match, at: string) => `${at}${JSON.stringify(replacement)}`);
62
+ }
63
+
64
+ function realFolder(path: string): string {
65
+ try {
66
+ return realpathSync(path);
67
+ } catch {
68
+ // Intentional ignore: a folder that cannot be resolved is matched as spelled.
69
+ return path;
70
+ }
71
+ }
72
+
73
+ /** Each registered plugin's real folder, with its siblings' folders by package name. */
74
+ const siblingsByPlugin = new Map<string, ReadonlyMap<string, string>>();
75
+ /** Real plugin folders whose source Bun loads through the hook below. */
76
+ const hookedFolders = new Set<string>();
77
+
78
+ /**
79
+ * The installed siblings `pluginDir` declares, by package name, each with its
80
+ * real folder. A sibling that maps its paths with `exports` is left out: only
81
+ * a lookup through `node_modules` reads that map.
82
+ */
83
+ function siblingFolders(pluginDir: string, pluginsDir: string = dirname(pluginDir)): Map<string, string> {
84
+ const folders = new Map<string, string>();
85
+ for (const { peer, directory } of installedPeerPlugins(pluginDir, pluginsDir)) {
86
+ const folder = join(pluginsDir, directory);
87
+ try {
88
+ const pkg = JSON.parse(readFileSync(join(folder, "package.json"), "utf8")) as { exports?: unknown };
89
+ if (pkg.exports !== undefined) continue;
90
+ } catch {
91
+ // Intentional ignore: a sibling without a readable package.json has no export map.
92
+ }
93
+ folders.set(peer, realFolder(folder));
94
+ }
95
+ return folders;
96
+ }
97
+
98
+ /** The file `specifier` names in the sibling `peer`, as Bun resolves it from inside its folder; null when there is none. */
99
+ function siblingModule(peer: string, peerDir: string, specifier: string): string | null {
100
+ const subpath = specifier.slice(peer.length + 1);
101
+ try {
102
+ return Bun.resolveSync(subpath ? `./${subpath}` : ".", peerDir);
103
+ } catch {
104
+ // Intentional ignore: left as written, the import fails the way it always did.
105
+ return null;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * `contents` of `file` with its imports of the siblings its plugin declares
111
+ * naming the siblings' files by absolute path. Anything else is left as
112
+ * written: a file outside a registered plugin, and an import the sibling
113
+ * cannot satisfy, which then fails the way it always did.
114
+ */
115
+ export function withSiblingImports(file: string, contents: string): string {
116
+ let siblings: ReadonlyMap<string, string> | undefined;
117
+ for (const [root, folders] of siblingsByPlugin) {
118
+ if (isInside(root, file)) siblings = folders;
119
+ }
120
+ if (!siblings) return contents;
121
+ let next = contents;
122
+ for (const [peer, peerDir] of siblings) {
123
+ if (!next.includes(peer)) continue;
124
+ const imported = new RegExp(`${IMPORTED_AT}(["'\`])(${escapeRegExp(peer)}(?:/[^"'\`\\s]*)?)\\2`, "g");
125
+ next = next.replace(imported, (match, at: string, _quote: string, specifier: string) => {
126
+ const target = siblingModule(peer, peerDir, specifier);
127
+ return target ? `${at}${JSON.stringify(target)}` : match;
128
+ });
129
+ }
130
+ return next;
131
+ }
132
+
133
+ function pathPattern(path: string): string {
134
+ return path.split(/[\\/]+/).map(escapeRegExp).join("[\\\\/]+");
135
+ }
136
+
137
+ /**
138
+ * The plugin's own source files, with an entry imported fresh (`?reload=`,
139
+ * or the installer's `?validate=`); not its dependencies, and not a retried
140
+ * import's files, which the retry's own hook loads the same way
141
+ * (peer-retry.ts).
142
+ */
143
+ function pluginSourceFilter(root: string): RegExp {
144
+ return new RegExp(
145
+ `^${pathPattern(root)}[\\\\/](?!(?:[^?]*[\\\\/])?node_modules[\\\\/])[^?]*\\.(?:tsx?|mts|jsx?|mjs)(?:\\?(?:reload|validate)=\\d+)?$`,
146
+ process.platform === "win32" ? "i" : "",
147
+ );
148
+ }
149
+
150
+ function hookPluginSource(root: string): boolean {
151
+ if (hookedFolders.has(root)) return true;
152
+ if (typeof Bun === "undefined" || typeof Bun.plugin !== "function") return false;
153
+ Bun.plugin({
154
+ name: "gloomberb-sibling-imports",
155
+ setup(build) {
156
+ build.onLoad({ filter: pluginSourceFilter(root) }, (args) => {
157
+ const file = args.path.replace(/\?.*$/, "");
158
+ return { contents: withSiblingImports(file, readFileSync(file, "utf8")), loader: sourceLoader(file) };
159
+ });
160
+ },
161
+ });
162
+ hookedFolders.add(root);
163
+ return true;
164
+ }
165
+
166
+ /**
167
+ * Makes this process import the siblings `pluginDir` declares from their own
168
+ * folders, from the next import of the plugin on. Returns whether it does: a
169
+ * plugin with no installed sibling, and a process without Bun's runtime
170
+ * plugins, go through the links as before.
171
+ */
172
+ export function importSiblingsFromTheirFolders(pluginDir: string, pluginsDir?: string): boolean {
173
+ const siblings = siblingFolders(pluginDir, pluginsDir);
174
+ const root = realFolder(pluginDir);
175
+ if (siblings.size === 0) {
176
+ siblingsByPlugin.delete(root);
177
+ return false;
178
+ }
179
+ siblingsByPlugin.set(root, siblings);
180
+ return hookPluginSource(root);
181
+ }
182
+
183
+ /**
184
+ * The same for a bundle of `pluginDir`: a bundler plugin that resolves the
185
+ * siblings it declares in their folders, or null when none is installed.
186
+ */
187
+ export function siblingImportsBundlerPlugin(pluginDir: string, pluginsDir?: string): BunPlugin | null {
188
+ const siblings = siblingFolders(pluginDir, pluginsDir);
189
+ if (siblings.size === 0) return null;
190
+ const filter = new RegExp(`^(?:${[...siblings.keys()].map(escapeRegExp).join("|")})(?:/.*)?$`);
191
+ return {
192
+ name: "gloomberb-sibling-imports",
193
+ setup(build) {
194
+ build.onResolve({ filter }, (args) => {
195
+ const peer = [...siblings.keys()].find((name) => args.path === name || args.path.startsWith(`${name}/`));
196
+ const path = peer ? siblingModule(peer, siblings.get(peer)!, args.path) : null;
197
+ return path ? { path } : undefined;
198
+ });
199
+ },
200
+ };
201
+ }
@@ -1,5 +1,8 @@
1
- import { readdirSync, readFileSync, type Dirent } from "fs";
2
- import { dirname, extname, isAbsolute, join, relative } from "path";
1
+ import { existsSync, readdirSync, readFileSync, type Dirent } from "fs";
2
+ import { dirname, extname, join } from "path";
3
+
4
+ import { isInside, replaceSpecifier, SOURCE_LOADERS, sourceLoader, withSiblingImports } from "./peer-imports";
5
+ import { pluginDirectoryNames } from "./plugin-names";
3
6
 
4
7
  /**
5
8
  * A second import of a plugin whose first one failed on a sibling plugin it
@@ -19,19 +22,15 @@ import { dirname, extname, isAbsolute, join, relative } from "path";
19
22
  * nothing from the failed attempt is reused. Imports that leave the plugin,
20
23
  * the sibling's files included, resolve to the same modules as always, so the
21
24
  * plugin shares the sibling's state as on a normal load.
25
+ *
26
+ * A plugin's imports of its siblings no longer go through the link at all
27
+ * (peer-imports.ts), so what is left for the retry is a plugin that Bun
28
+ * already failed in this process, imported before its sibling was there.
22
29
  */
23
30
 
24
31
  /** The query that gives a plugin's files their new module keys. */
25
32
  const RETRY_PARAM = "gloom-plugin-retry";
26
33
  const RETRY_QUERY = new RegExp(`[?&]${RETRY_PARAM}=(\\d+)(?:&|$)`);
27
- const SOURCE_LOADERS: Readonly<Record<string, "ts" | "tsx" | "js" | "jsx">> = {
28
- ".ts": "ts",
29
- ".mts": "ts",
30
- ".tsx": "tsx",
31
- ".js": "js",
32
- ".mjs": "js",
33
- ".jsx": "jsx",
34
- };
35
34
  /** A file name that is never there: looking it up is what clears a folder. */
36
35
  const MISSING_FILE = "./.gloom-resolver-refresh";
37
36
  const SKIPPED_FOLDERS = new Set(["node_modules", ".git", "dist"]);
@@ -49,11 +48,6 @@ const retryRoots = new Map<number, string>();
49
48
  let retries = 0;
50
49
  let installed = false;
51
50
 
52
- function isInside(root: string, path: string): boolean {
53
- const inside = relative(root, path);
54
- return !!inside && !inside.startsWith("..") && !isAbsolute(inside);
55
- }
56
-
57
51
  /** The plugin's own folders, without dependencies or build output. */
58
52
  function sourceFolders(dir: string, out: string[] = [], depth = 0): string[] {
59
53
  out.push(dir);
@@ -72,11 +66,11 @@ function sourceFolders(dir: string, out: string[] = [], depth = 0): string[] {
72
66
  }
73
67
 
74
68
  /**
75
- * Makes Bun read the plugin's folders and its links to `peers` from disk
76
- * again. Bun drops a folder from its resolver cache when a lookup in it
77
- * misses, so this looks up a file that is not there in each one. The
78
- * bundler shares the cache, which is why the desktop's compile of the same
79
- * plugin failed in the same session too.
69
+ * Makes Bun read the plugin's folders, its links to `peers` and the peers'
70
+ * own folders from disk again. Bun drops a folder from its resolver cache
71
+ * when a lookup in it misses, so this looks up a file that is not there in
72
+ * each one. The bundler shares the cache, which is why the desktop's compile
73
+ * of the same plugin failed in the same session too.
80
74
  */
81
75
  export function refreshResolverFolders(pluginDir: string, peers: readonly string[]): void {
82
76
  const modules = join(pluginDir, "node_modules");
@@ -84,6 +78,10 @@ export function refreshResolverFolders(pluginDir: string, peers: readonly string
84
78
  for (const peer of peers) {
85
79
  if (peer.startsWith("@")) folders.push(join(modules, peer.split("/")[0]!));
86
80
  folders.push(join(modules, peer));
81
+ for (const name of pluginDirectoryNames(peer)) {
82
+ const peerDir = join(dirname(pluginDir), name);
83
+ if (existsSync(peerDir)) folders.push(...sourceFolders(peerDir));
84
+ }
87
85
  }
88
86
  for (const folder of folders) {
89
87
  try {
@@ -97,15 +95,17 @@ export function refreshResolverFolders(pluginDir: string, peers: readonly string
97
95
  /**
98
96
  * The plugin's file at `path` (with the retry query), its relative imports
99
97
  * of other source files in the plugin given the same query, so they load
100
- * under new keys as well. Imports that leave the plugin keep their keys.
98
+ * under new keys as well. Imports that leave the plugin keep their keys,
99
+ * and those of a sibling plugin name its file in the sibling's folder, as
100
+ * on a first import (peer-imports.ts).
101
101
  */
102
- function rekeyedSource(path: string): { contents: string; loader: "ts" | "tsx" | "js" | "jsx" } {
102
+ function rekeyedSource(path: string): { contents: string; loader: ReturnType<typeof sourceLoader> } {
103
103
  const match = RETRY_QUERY.exec(path);
104
104
  const file = match ? path.slice(0, match.index) : path;
105
- const loader = SOURCE_LOADERS[extname(file).toLowerCase()] ?? "js";
105
+ const loader = sourceLoader(file);
106
106
  let contents = readFileSync(file, "utf8");
107
107
  const root = match ? retryRoots.get(Number(match[1])) : undefined;
108
- if (!match || !root) return { contents, loader };
108
+ if (!match || !root) return { contents: withSiblingImports(file, contents), loader };
109
109
  const retry = Number(match[1]);
110
110
  const specifiers = new Set(
111
111
  new Bun.Transpiler({ loader }).scanImports(contents)
@@ -121,12 +121,9 @@ function rekeyedSource(path: string): { contents: string; loader: "ts" | "tsx" |
121
121
  continue;
122
122
  }
123
123
  if (!isInside(root, target) || !SOURCE_LOADERS[extname(target).toLowerCase()]) continue;
124
- const rekeyed = `${specifier}?${RETRY_PARAM}=${retry}`;
125
- for (const quote of ["\"", "'", "`"]) {
126
- contents = contents.split(`${quote}${specifier}${quote}`).join(`${quote}${rekeyed}${quote}`);
127
- }
124
+ contents = replaceSpecifier(contents, specifier, `${specifier}?${RETRY_PARAM}=${retry}`);
128
125
  }
129
- return { contents, loader };
126
+ return { contents: withSiblingImports(file, contents), loader };
130
127
  }
131
128
 
132
129
  function installRekeyingLoader(): boolean {
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The `process` a plugin sees in the desktop view and the web app.
3
+ *
4
+ * A plugin written for the terminal runs in Bun and reads `process.env`,
5
+ * `process.platform` and friends without a second thought. The desktop view
6
+ * and the browser are not Node: there is no `process`, so the same plugin
7
+ * died on load with `ReferenceError: process is not defined`. Defining a global
8
+ * `process` would be simpler, but the host itself looks at `globalThis.process`
9
+ * to tell a Node runtime from a browser (updater, platform labels, libc
10
+ * detection), so every plugin would change how the host behaves. The bundler
11
+ * (`bundle.ts`) gives each plugin bundle its own copy instead, as a module
12
+ * level constant that only that bundle's references to `process` read.
13
+ *
14
+ * What it holds is the harmless half of `process`: a copy of `env` with only
15
+ * `NODE_ENV` in it (never the host's real environment, which the view cannot
16
+ * read and a plugin should not receive by RPC), the platform of the window it
17
+ * runs in, empty `argv` and `versions`, and timers and listeners that work or
18
+ * do nothing. What cannot exist in a view (`exit`, `kill`, `chdir`, memory
19
+ * and CPU readings) throws an error naming the plugin, instead of "is not a
20
+ * function". Anything else is left undefined, so feature detection such as
21
+ * `process.stdin?.isTTY` keeps working.
22
+ */
23
+
24
+ /** The name the bundler gives the shim; no plugin source declares it. */
25
+ export const PLUGIN_PROCESS_IDENTIFIER = "__gloomPluginProcess";
26
+
27
+ /**
28
+ * Builds the shim. This function is serialised into the plugin bundle with
29
+ * `toString()`, so it has to stay self-contained: no imports and no module
30
+ * level names, only globals the view provides.
31
+ */
32
+ function createPluginProcess(pluginName: string): Record<string, unknown> {
33
+ const nav = (globalThis as {
34
+ navigator?: { userAgentData?: { platform?: string }; platform?: string; userAgent?: string };
35
+ }).navigator;
36
+ const hint = String(nav?.userAgentData?.platform || nav?.platform || nav?.userAgent || "").toLowerCase();
37
+ // Mac is tested before Windows because "darwin" contains "win".
38
+ const platform = /android/.test(hint)
39
+ ? "android"
40
+ : /mac|iphone|ipad|darwin/.test(hint)
41
+ ? "darwin"
42
+ : /win/.test(hint)
43
+ ? "win32"
44
+ : /linux|x11|cros/.test(hint)
45
+ ? "linux"
46
+ : "browser";
47
+
48
+ const hrtime = (previous?: readonly [number, number]): [number, number] => {
49
+ const ms = performance.now();
50
+ let seconds = Math.floor(ms / 1000);
51
+ let nanos = Math.floor((ms % 1000) * 1e6);
52
+ if (previous) {
53
+ seconds -= previous[0];
54
+ nanos -= previous[1];
55
+ if (nanos < 0) {
56
+ seconds -= 1;
57
+ nanos += 1e9;
58
+ }
59
+ }
60
+ return [seconds, nanos];
61
+ };
62
+ const stream = (log: (line: string) => void) => ({
63
+ isTTY: false,
64
+ write: (chunk: unknown) => {
65
+ log(String(chunk).replace(/\n$/, ""));
66
+ return true;
67
+ },
68
+ });
69
+
70
+ const proc: Record<string, unknown> = {
71
+ env: { NODE_ENV: "production" },
72
+ platform,
73
+ browser: true,
74
+ title: "browser",
75
+ argv: [],
76
+ execArgv: [],
77
+ execPath: "",
78
+ version: "",
79
+ versions: {},
80
+ pid: 0,
81
+ ppid: 0,
82
+ cwd: () => "/",
83
+ uptime: () => performance.now() / 1000,
84
+ hrtime: Object.assign(hrtime, { bigint: () => BigInt(Math.round(performance.now() * 1e6)) }),
85
+ nextTick: (callback: (...args: unknown[]) => void, ...args: unknown[]) => {
86
+ queueMicrotask(() => callback(...args));
87
+ },
88
+ emitWarning: (warning: unknown) => {
89
+ console.warn(String((warning as { message?: string } | null)?.message ?? warning));
90
+ },
91
+ stdout: stream((line) => console.log(line)),
92
+ stderr: stream((line) => console.error(line)),
93
+ emit: () => false,
94
+ listeners: () => [],
95
+ listenerCount: () => 0,
96
+ };
97
+ // Nothing exits or signals in a view, so a handler would never run.
98
+ for (const name of [
99
+ "on", "once", "off", "addListener", "removeListener", "removeAllListeners",
100
+ "prependListener", "prependOnceListener", "setMaxListeners",
101
+ ]) {
102
+ proc[name] = () => proc;
103
+ }
104
+ for (const name of [
105
+ "exit", "abort", "kill", "chdir", "umask", "binding", "memoryUsage", "cpuUsage", "resourceUsage",
106
+ ]) {
107
+ proc[name] = () => {
108
+ throw new Error(
109
+ `Plugin "${pluginName}" called process.${name}(), which is only available in the terminal app, not in the desktop or web app.`,
110
+ );
111
+ };
112
+ }
113
+ return proc;
114
+ }
115
+
116
+ /** The declaration the bundler puts at the top of a bundle that reads `process`. */
117
+ export function pluginProcessDeclaration(pluginName: string): string {
118
+ return `const ${PLUGIN_PROCESS_IDENTIFIER} = (${createPluginProcess.toString()})(${JSON.stringify(pluginName)});\n`;
119
+ }
120
+
121
+ /** Source of `import process from "node:process"`, for a plugin that imports it. */
122
+ export function pluginProcessModuleSource(): string {
123
+ const names = Object.keys(createPluginProcess(""));
124
+ return [
125
+ "export default process;",
126
+ ...names.map((name) => `export const ${name} = process.${name};`),
127
+ ].join("\n");
128
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Whether Windows lets this process follow a junction a standard user made,
3
+ * which is what every link to a sibling plugin is (host-link.ts).
4
+ *
5
+ * Windows 11's RedirectionGuard stops a process that runs with it from
6
+ * following such a junction at all: the junction is there, points where it
7
+ * should, and every read through it fails. That is how the gateway's link
8
+ * looked on the first launch after an update. Some Windows services run with
9
+ * it; whether that launch did is not known, so the report of a link that does
10
+ * not resolve carries the answer.
11
+ */
12
+
13
+ /** `ProcessRedirectionTrustPolicy` in Windows' PROCESS_MITIGATION_POLICY. */
14
+ const PROCESS_REDIRECTION_TRUST_POLICY = 16;
15
+ const CURRENT_PROCESS = -1;
16
+ const ENFORCE = 0x1;
17
+ const AUDIT = 0x2;
18
+
19
+ let answer: string | null | undefined;
20
+
21
+ /** `enforce`, `audit` (only logged), `off`, or `unknown` when Windows does not say. Null on other systems. */
22
+ export function redirectionGuard(): string | null {
23
+ if (process.platform !== "win32") return null;
24
+ if (answer !== undefined) return answer;
25
+ answer = "unknown";
26
+ try {
27
+ // Required when asked, not imported: the pane screenshot page bundles the
28
+ // plugin loader for a browser, which has no `bun:ffi`.
29
+ const { dlopen, ptr } = import.meta.require("bun:ffi") as typeof import("bun:ffi");
30
+ const kernel32 = dlopen("kernel32.dll", {
31
+ GetProcessMitigationPolicy: { args: ["i64", "i32", "ptr", "u64"], returns: "i32" },
32
+ });
33
+ try {
34
+ const flags = new Uint32Array(1);
35
+ if (kernel32.symbols.GetProcessMitigationPolicy(CURRENT_PROCESS, PROCESS_REDIRECTION_TRUST_POLICY, ptr(flags), 4)) {
36
+ answer = (flags[0]! & ENFORCE) ? "enforce" : (flags[0]! & AUDIT) ? "audit" : "off";
37
+ }
38
+ } finally {
39
+ kernel32.close();
40
+ }
41
+ } catch {
42
+ // Intentional ignore: a Windows without the policy leaves the answer unknown.
43
+ }
44
+ return answer;
45
+ }
@@ -1,5 +1,6 @@
1
- import { readFile, rm, writeFile, mkdir } from "fs/promises";
1
+ import { readFile, rename, rm } from "fs/promises";
2
2
  import { dirname, join } from "path";
3
+ import { makePrivateDir, writePrivateFile } from "../utils/private-files";
3
4
  import type { RemoteAppKind, RemoteEndpoint } from "./types";
4
5
 
5
6
  const DEFAULT_ENDPOINT_FILE = "remote-control.json";
@@ -25,9 +26,21 @@ export async function removeRemoteEndpointFiles(dataDir: string, endpoint: Remot
25
26
  ]);
26
27
  }
27
28
 
29
+ /**
30
+ * Readable by the user alone, an older copy replaced: it holds the token that
31
+ * controls the app. Written beside it and moved into place, so a client that
32
+ * finds the file never reads it half written.
33
+ */
28
34
  async function writeRemoteEndpoint(filePath: string, endpoint: RemoteEndpoint): Promise<void> {
29
- await mkdir(dirname(filePath), { recursive: true });
30
- await writeFile(filePath, JSON.stringify(endpoint, null, 2), { encoding: "utf-8", mode: 0o600 });
35
+ await makePrivateDir(dirname(filePath));
36
+ const temporary = `${filePath}.${process.pid}.tmp`;
37
+ try {
38
+ await writePrivateFile(temporary, JSON.stringify(endpoint, null, 2));
39
+ await rename(temporary, filePath);
40
+ } catch (error) {
41
+ await rm(temporary, { force: true });
42
+ throw error;
43
+ }
31
44
  }
32
45
 
33
46
  async function removeRemoteEndpointIfOwned(filePath: string, endpoint: RemoteEndpoint): Promise<void> {
@@ -31,7 +31,7 @@ import {
31
31
  readOrCreateBrowserInstallId,
32
32
  } from "../../telemetry/crash-reports-dom";
33
33
  import { currentTelemetryConfig } from "../../telemetry/live-config";
34
- import { attentionCountsEnabled, installAttentionCounter } from "../../telemetry/attention-counts";
34
+ import { installAttentionCounter } from "../../telemetry/attention-counts";
35
35
  import { installUsageCounter, recordRestoredFunctions, usageCountsEnabled } from "../../telemetry/usage-counts";
36
36
  import { installWindowUsageFlush } from "../../telemetry/usage-counts-dom";
37
37
  import type { AppConfig } from "../../types/config";
@@ -71,7 +71,7 @@ async function boot(): Promise<void> {
71
71
  getInstallId: () => readOrCreateBrowserInstallId(),
72
72
  officialPluginIds: loadOfficialPluginIds,
73
73
  });
74
- installAttentionCounter(() => !browserDoNotTrack() && attentionCountsEnabled(currentTelemetryConfig(loadedConfig)));
74
+ installAttentionCounter({ allowed: () => !browserDoNotTrack(), launchConfig: () => loadedConfig });
75
75
  // A document reload does not unmount React. Flush both config and session
76
76
  // timers while localStorage is still available, including background tabs.
77
77
  window.addEventListener("pagehide", () => { void flushPendingPersistence(); });
@@ -114,6 +114,7 @@ async function boot(): Promise<void> {
114
114
  plugins={getBrowserPlugins(externalPlugins)}
115
115
  desktopDeepLinkBridge={deepLinkBridge}
116
116
  updatesEnabled={false}
117
+ setupEnabled={false}
117
118
  />
118
119
  </DomHostProviders>
119
120
  </DomErrorBoundary>,