gloomberb 0.11.3 → 0.12.0

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 (489) hide show
  1. package/README.md +6 -4
  2. package/README.zh-CN.md +4 -2
  3. package/package.json +28 -1
  4. package/src/api-client/auth.ts +10 -0
  5. package/src/api-client/data.ts +103 -1
  6. package/src/api-client/index.test.ts +127 -0
  7. package/src/api-client/index.ts +129 -1
  8. package/src/api-client/normalizers.ts +39 -0
  9. package/src/api-client/paths.ts +85 -1
  10. package/src/api-client/types.ts +196 -0
  11. package/src/app/pane-runtime/index.tsx +15 -12
  12. package/src/app/pane-runtime/layout-placement.test.ts +27 -0
  13. package/src/app/pane-runtime/layout-placement.ts +13 -0
  14. package/src/app/pane-runtime/plugin-bindings.ts +62 -0
  15. package/src/app.tsx +60 -18
  16. package/src/architecture/browser-entry-graph.test.ts +96 -0
  17. package/src/brokers/profile-form.test.ts +25 -27
  18. package/src/brokers/test-broker.ts +68 -0
  19. package/src/cli/commands/plugins.ts +39 -11
  20. package/src/cli/desktop-pane-shot.ts +285 -16
  21. package/src/cli/index.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +58 -28
  23. package/src/cli/pane-functions/catalog.ts +16 -4
  24. package/src/cli/pane-functions/discovery.ts +1 -0
  25. package/src/cli/pane-functions/headless.test.ts +247 -0
  26. package/src/cli/pane-functions/headless.ts +423 -0
  27. package/src/cli/pane-functions/index.test.ts +9 -0
  28. package/src/cli/pane-functions/index.ts +6 -2
  29. package/src/cli/pane-functions/options.ts +27 -2
  30. package/src/cli/pane-functions/report.ts +4 -0
  31. package/src/cli/pane-functions/resolver.ts +26 -8
  32. package/src/cli/pane-functions/screenshot.test.ts +76 -0
  33. package/src/cli/pane-functions/screenshot.ts +236 -20
  34. package/src/cli/restore-plugins.ts +34 -0
  35. package/src/components/chart/{static/chart → composite}/axis-overlays.test.tsx +3 -3
  36. package/src/components/chart/{static/chart → composite}/axis-overlays.tsx +6 -11
  37. package/src/components/chart/{static/chart → composite}/bitmap.ts +17 -3
  38. package/src/components/chart/composite/composite-chart.test.tsx +68 -20
  39. package/src/components/chart/composite/composite-chart.tsx +301 -204
  40. package/src/components/chart/composite/format.ts +8 -2
  41. package/src/components/chart/composite/interactions.test.ts +68 -102
  42. package/src/components/chart/composite/interactions.ts +214 -264
  43. package/src/components/chart/composite/price-axis-labels.test.ts +21 -0
  44. package/src/components/chart/{price-axis-labels.tsx → composite/price-axis-labels.tsx} +9 -4
  45. package/src/components/chart/composite/scene.ts +57 -3
  46. package/src/components/chart/composite/text-renderer.ts +3 -2
  47. package/src/components/chart/composite/time-scale.ts +138 -57
  48. package/src/components/chart/composite/types.ts +25 -1
  49. package/src/components/chart/core/data.ts +9 -13
  50. package/src/components/chart/core/date-window.ts +7 -1
  51. package/src/components/chart/core/pointer.ts +3 -0
  52. package/src/components/chart/core/types.ts +0 -45
  53. package/src/components/chart/native/chart-rasterizer.ts +0 -1
  54. package/src/components/chart/native/kitty/index.test.ts +0 -188
  55. package/src/components/chart/native/raster/crosshair.ts +6 -5
  56. package/src/components/chart/native/raster/primitives.ts +0 -23
  57. package/src/components/chart/native/renderer-selection.ts +81 -31
  58. package/src/components/chart/static/{chart/surface.test.tsx → chart-surface.test.tsx} +28 -80
  59. package/src/components/chart/static/chart-surface.tsx +150 -0
  60. package/src/components/chart/static/index.ts +8 -0
  61. package/src/components/chart/static/multi-line-chart-surface.tsx +51 -166
  62. package/src/components/chart/static/scatter-chart-surface.test.ts +30 -0
  63. package/src/components/chart/static/scatter-chart-surface.tsx +135 -34
  64. package/src/components/chart/static/series.ts +49 -0
  65. package/src/components/command-bar/action-runtime.ts +1 -3
  66. package/src/components/command-bar/assist/model.test.ts +17 -2
  67. package/src/components/command-bar/assist/model.ts +40 -9
  68. package/src/components/command-bar/commands/direct/index.ts +1 -1
  69. package/src/components/command-bar/commands/direct/runtime.ts +1 -1
  70. package/src/components/command-bar/commands/registry.ts +0 -11
  71. package/src/components/command-bar/helpers.test.ts +2 -2
  72. package/src/components/command-bar/helpers.ts +2 -6
  73. package/src/components/command-bar/keyboard-shortcuts.ts +0 -11
  74. package/src/components/command-bar/list/badge.test.ts +54 -0
  75. package/src/components/command-bar/list/badge.ts +62 -0
  76. package/src/components/command-bar/list/model.test.ts +103 -0
  77. package/src/components/command-bar/list/model.ts +61 -4
  78. package/src/components/command-bar/list/view.test.tsx +113 -0
  79. package/src/components/command-bar/list/view.tsx +147 -131
  80. package/src/components/command-bar/pane-templates/workflow.ts +1 -1
  81. package/src/components/command-bar/panel/body.tsx +51 -65
  82. package/src/components/command-bar/panel/index.tsx +48 -13
  83. package/src/components/command-bar/panel/layout.test.ts +139 -0
  84. package/src/components/command-bar/panel/layout.ts +65 -28
  85. package/src/components/command-bar/panel/native-surface.ts +24 -0
  86. package/src/components/command-bar/panel/prompt-binding.ts +54 -0
  87. package/src/components/command-bar/panel/runtime.ts +6 -3
  88. package/src/components/command-bar/panel/state.ts +41 -7
  89. package/src/components/command-bar/panel/types.ts +7 -1
  90. package/src/components/command-bar/routes/root/provider-search.ts +34 -15
  91. package/src/components/command-bar/routes/root/results.test.ts +161 -0
  92. package/src/components/command-bar/routes/root/results.ts +30 -28
  93. package/src/components/command-bar/routes/root/runtime.ts +37 -20
  94. package/src/components/command-bar/routes/root/search-providers.test.tsx +224 -0
  95. package/src/components/command-bar/routes/root/search-providers.ts +205 -0
  96. package/src/components/command-bar/routes/root/selection.ts +1 -12
  97. package/src/components/command-bar/routes/root/shortcuts.test.ts +8 -8
  98. package/src/components/command-bar/routes/ticker-search/actions.ts +10 -9
  99. package/src/components/command-bar/routes/ticker-search/results.test.ts +54 -1
  100. package/src/components/command-bar/routes/ticker-search/results.ts +60 -19
  101. package/src/components/command-bar/routes/ticker-search/route.ts +1 -0
  102. package/src/components/command-bar/routing/actions.ts +1 -81
  103. package/src/components/command-bar/routing/launch-request.ts +1 -1
  104. package/src/components/command-bar/routing/list-state.ts +6 -21
  105. package/src/components/command-bar/selection-runtime.ts +1 -1
  106. package/src/components/command-bar/surface/assist.test.tsx +14 -10
  107. package/src/components/command-bar/surface/environment.ts +8 -1
  108. package/src/components/command-bar/surface/index.test.tsx +84 -68
  109. package/src/components/command-bar/surface/index.tsx +33 -36
  110. package/src/components/command-bar/surface/test-harness.tsx +15 -3
  111. package/src/components/command-bar/theme-picker.tsx +43 -15
  112. package/src/components/command-bar/view-model.test.ts +53 -8
  113. package/src/components/command-bar/view-model.ts +43 -14
  114. package/src/components/command-bar/workflow/tickers.ts +1 -0
  115. package/src/components/command-bar/workflow/types.ts +1 -1
  116. package/src/components/data-table/view.test.tsx +45 -6
  117. package/src/components/data-table/view.tsx +55 -9
  118. package/src/components/index.ts +10 -2
  119. package/src/components/input-search-bar.test.tsx +36 -2
  120. package/src/components/input-search-bar.tsx +18 -1
  121. package/src/components/layout/header.test.tsx +57 -10
  122. package/src/components/layout/header.tsx +292 -149
  123. package/src/components/layout/market-summary.test.ts +81 -0
  124. package/src/components/layout/market-summary.ts +109 -0
  125. package/src/components/layout/shell/chrome.ts +96 -1
  126. package/src/components/layout/shell/index.tsx +11 -3
  127. package/src/components/layout/shell/menu.ts +9 -0
  128. package/src/components/layout/shell/pane/actions.ts +29 -0
  129. package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
  130. package/src/components/layout/shell/shortcuts.ts +3 -0
  131. package/src/components/layout/status-bar.test.tsx +9 -8
  132. package/src/components/layout/status-bar.tsx +85 -54
  133. package/src/components/onboarding/onboarding-frame.tsx +2 -26
  134. package/src/components/price-sparkline/index.ts +61 -34
  135. package/src/components/price-sparkline/view.tsx +1 -1
  136. package/src/components/sign-in-gate.test.tsx +128 -0
  137. package/src/components/sign-in-gate.tsx +149 -0
  138. package/src/components/speedometer-gauge/desktop.tsx +2 -12
  139. package/src/components/ticker/badge/list.tsx +17 -3
  140. package/src/components/ticker/list-table-view.tsx +6 -3
  141. package/src/components/ui/data-table/index.tsx +3 -2
  142. package/src/components/ui/data-table/opentui/index.tsx +165 -120
  143. package/src/components/ui/frame.tsx +28 -0
  144. package/src/components/ui/index.ts +3 -0
  145. package/src/components/ui/native-select.tsx +20 -10
  146. package/src/components/ui/select-button.tsx +138 -0
  147. package/src/components/ui/status.tsx +2 -2
  148. package/src/components/ui/table-layout.ts +1 -1
  149. package/src/components/use-double-click-activation.ts +7 -3
  150. package/src/core/app-services.ts +3 -3
  151. package/src/data/config/layout.ts +12 -6
  152. package/src/data/config/store/index.test.ts +97 -0
  153. package/src/data/config/store/migrations.ts +41 -0
  154. package/src/data/config/store/normalize.ts +5 -3
  155. package/src/i18n/es.ts +6 -2
  156. package/src/i18n/ja.ts +6 -2
  157. package/src/i18n/ko.ts +6 -2
  158. package/src/i18n/zh-cn.ts +6 -2
  159. package/src/i18n/zh-tw.ts +6 -2
  160. package/src/layout-marketplace/pane-imagery.ts +1 -0
  161. package/src/layout-marketplace/payload.test.ts +17 -0
  162. package/src/layout-marketplace/payload.ts +40 -5
  163. package/src/market-data/coordinator/index.ts +3 -2
  164. package/src/market-data/coordinator/quotes.ts +173 -66
  165. package/src/market-data/coordinator/subscriptions.test.ts +215 -37
  166. package/src/pane-settings.ts +1 -1
  167. package/src/plugins/builtin/analytics/pane-model.ts +1 -1
  168. package/src/plugins/builtin/analytics/view.tsx +1 -1
  169. package/src/plugins/builtin/broker-manager/index.test.tsx +3 -2
  170. package/src/plugins/builtin/broker-manager/model.test.ts +2 -2
  171. package/src/plugins/builtin/chart-composer/catalog-inventory.ts +16 -2
  172. package/src/plugins/builtin/chart-composer/pane.tsx +22 -8
  173. package/src/plugins/builtin/chat/content/composer.tsx +8 -4
  174. package/src/plugins/builtin/cloud/auth-dialog.tsx +14 -228
  175. package/src/plugins/builtin/cloud/auth-form.tsx +277 -0
  176. package/src/plugins/builtin/cloud/plugin.tsx +1 -0
  177. package/src/plugins/builtin/composite-plugins.ts +14 -1
  178. package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
  179. package/src/plugins/builtin/correlation/relationship/pane.tsx +2 -6
  180. package/src/plugins/builtin/correlation/relationship/view-model.ts +2 -2
  181. package/src/plugins/builtin/credit-conditions/index.test.tsx +12 -6
  182. package/src/plugins/builtin/credit-conditions/index.tsx +2 -21
  183. package/src/plugins/builtin/dividend-yield/index.tsx +1 -0
  184. package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
  185. package/src/plugins/builtin/earnings-calls/data.ts +223 -0
  186. package/src/plugins/builtin/earnings-calls/format.ts +49 -0
  187. package/src/plugins/builtin/earnings-calls/index.tsx +69 -0
  188. package/src/plugins/builtin/earnings-calls/pane.tsx +756 -0
  189. package/src/plugins/builtin/earnings-calls/prose.test.ts +36 -0
  190. package/src/plugins/builtin/earnings-calls/prose.ts +59 -0
  191. package/src/plugins/builtin/earnings-calls/transcript-view.tsx +334 -0
  192. package/src/plugins/builtin/econ/detail-view.tsx +1 -2
  193. package/src/plugins/builtin/econ/fred-series-map.ts +16 -1
  194. package/src/plugins/builtin/econ/index.tsx +2 -1
  195. package/src/plugins/builtin/econ-statistics/cache.ts +4 -0
  196. package/src/plugins/builtin/econ-statistics/client.ts +92 -0
  197. package/src/plugins/builtin/econ-statistics/defs.ts +66 -0
  198. package/src/plugins/builtin/econ-statistics/detail.tsx +154 -0
  199. package/src/plugins/builtin/econ-statistics/headless.test.ts +54 -0
  200. package/src/plugins/builtin/econ-statistics/headless.ts +171 -0
  201. package/src/plugins/builtin/econ-statistics/index.tsx +54 -0
  202. package/src/plugins/builtin/econ-statistics/pane.tsx +356 -0
  203. package/src/plugins/builtin/econ-statistics/stats.ts +522 -0
  204. package/src/plugins/builtin/econ-statistics/transform.test.ts +86 -0
  205. package/src/plugins/builtin/econ-statistics/transform.ts +85 -0
  206. package/src/plugins/builtin/econ-statistics/trend.ts +57 -0
  207. package/src/plugins/builtin/econ-statistics/view.ts +90 -0
  208. package/src/plugins/builtin/fear-greed/charts.tsx +7 -18
  209. package/src/plugins/builtin/fear-greed/data.ts +6 -1
  210. package/src/plugins/builtin/help/index.tsx +4 -0
  211. package/src/plugins/builtin/holders/index.tsx +1 -0
  212. package/src/plugins/builtin/insider/index.tsx +1 -0
  213. package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
  214. package/src/plugins/builtin/kelly-sizer/sections.tsx +2 -3
  215. package/src/plugins/builtin/market-valuation/align.ts +122 -0
  216. package/src/plugins/builtin/market-valuation/cache.ts +32 -0
  217. package/src/plugins/builtin/market-valuation/chart-projection.ts +217 -0
  218. package/src/plugins/builtin/market-valuation/chart-series.ts +89 -0
  219. package/src/plugins/builtin/market-valuation/client.test.ts +141 -0
  220. package/src/plugins/builtin/market-valuation/client.ts +116 -0
  221. package/src/plugins/builtin/market-valuation/defs.ts +251 -0
  222. package/src/plugins/builtin/market-valuation/detail.tsx +159 -0
  223. package/src/plugins/builtin/market-valuation/headless.test.ts +58 -0
  224. package/src/plugins/builtin/market-valuation/headless.ts +170 -0
  225. package/src/plugins/builtin/market-valuation/index.tsx +64 -0
  226. package/src/plugins/builtin/market-valuation/indicators.ts +422 -0
  227. package/src/plugins/builtin/market-valuation/model.test.ts +350 -0
  228. package/src/plugins/builtin/market-valuation/model.ts +8 -0
  229. package/src/plugins/builtin/market-valuation/pane.test.tsx +247 -0
  230. package/src/plugins/builtin/market-valuation/pane.tsx +344 -0
  231. package/src/plugins/builtin/market-valuation/series.ts +12 -0
  232. package/src/plugins/builtin/market-valuation/settings.ts +38 -0
  233. package/src/plugins/builtin/market-valuation/sources.ts +130 -0
  234. package/src/plugins/builtin/market-valuation/trend.ts +89 -0
  235. package/src/plugins/builtin/market-valuation/view.ts +137 -0
  236. package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +123 -0
  237. package/src/plugins/builtin/market-valuation/zone-scale/index.tsx +12 -0
  238. package/src/plugins/builtin/market-valuation/zone-scale/model.ts +60 -0
  239. package/src/plugins/builtin/market-valuation/zone-scale/terminal.tsx +94 -0
  240. package/src/plugins/builtin/news/wire/breaking/filters.test.ts +99 -0
  241. package/src/plugins/builtin/news/wire/breaking/filters.ts +110 -0
  242. package/src/plugins/builtin/news/wire/breaking/notifications.test.ts +157 -53
  243. package/src/plugins/builtin/news/wire/breaking/notifications.ts +91 -25
  244. package/src/plugins/builtin/news/wire/index.tsx +31 -44
  245. package/src/plugins/builtin/options/index.tsx +1 -0
  246. package/src/plugins/builtin/options/view.test.tsx +29 -1
  247. package/src/plugins/builtin/options/view.tsx +1 -22
  248. package/src/plugins/builtin/plugin-marketplace/feed.ts +80 -0
  249. package/src/plugins/builtin/plugin-marketplace/index.tsx +31 -0
  250. package/src/plugins/builtin/plugin-marketplace/model.test.ts +200 -0
  251. package/src/plugins/builtin/plugin-marketplace/model.ts +204 -0
  252. package/src/plugins/builtin/plugin-marketplace/pane.tsx +385 -0
  253. package/src/plugins/builtin/plugin-marketplace/store.ts +46 -0
  254. package/src/plugins/builtin/plugin-module.ts +9 -1
  255. package/src/plugins/builtin/research/index.tsx +4 -0
  256. package/src/plugins/builtin/research-search/command-bar-search.test.ts +81 -0
  257. package/src/plugins/builtin/research-search/command-bar-search.ts +175 -0
  258. package/src/plugins/builtin/research-search/data.ts +109 -0
  259. package/src/plugins/builtin/research-search/document-view.test.tsx +75 -0
  260. package/src/plugins/builtin/research-search/document-view.tsx +254 -0
  261. package/src/plugins/builtin/research-search/filter-bar.tsx +62 -0
  262. package/src/plugins/builtin/research-search/focus-handoff.ts +47 -0
  263. package/src/plugins/builtin/research-search/index.tsx +85 -0
  264. package/src/plugins/builtin/research-search/model.test.ts +40 -0
  265. package/src/plugins/builtin/research-search/model.ts +291 -0
  266. package/src/plugins/builtin/research-search/pane.test.tsx +241 -0
  267. package/src/plugins/builtin/research-search/pane.tsx +678 -0
  268. package/src/plugins/builtin/research-search/saved-view.tsx +171 -0
  269. package/src/plugins/builtin/research-search/snippet-text.tsx +35 -0
  270. package/src/plugins/builtin/research-search/snippet.test.ts +78 -0
  271. package/src/plugins/builtin/research-search/snippet.ts +134 -0
  272. package/src/plugins/builtin/scanner/flow-model.test.ts +11 -1
  273. package/src/plugins/builtin/scanner/flow-model.ts +29 -1
  274. package/src/plugins/builtin/scanner/flow-pane.tsx +78 -22
  275. package/src/plugins/builtin/sec/index.tsx +1 -0
  276. package/src/plugins/builtin/shared/series-cache.ts +93 -0
  277. package/src/plugins/builtin/short-interest/index.tsx +1 -0
  278. package/src/plugins/builtin/short-interest/pane.tsx +1 -1
  279. package/src/plugins/builtin/thirteenf/index.tsx +1 -0
  280. package/src/plugins/builtin/ticker-detail/index.tsx +0 -14
  281. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
  282. package/src/plugins/builtin/volatility/index.tsx +1 -1
  283. package/src/plugins/builtin/world-venue-map/map.desktop.test.tsx +106 -0
  284. package/src/plugins/builtin/world-venue-map/map.tsx +213 -69
  285. package/src/plugins/builtin/world-venue-map/model.test.ts +32 -0
  286. package/src/plugins/builtin/world-venue-map/model.ts +134 -13
  287. package/src/plugins/builtin/yield-curve/index.tsx +1 -1
  288. package/src/plugins/bundle.test.ts +109 -0
  289. package/src/plugins/bundle.ts +139 -0
  290. package/src/plugins/catalog-browser.test.ts +1 -0
  291. package/src/plugins/catalog-browser.ts +10 -0
  292. package/src/plugins/catalog-manifest.test.ts +43 -0
  293. package/src/plugins/catalog-manifest.ts +92 -0
  294. package/src/plugins/catalog-ui.ts +20 -4
  295. package/src/plugins/catalog.ts +9 -2
  296. package/src/plugins/current-target.ts +28 -0
  297. package/src/plugins/host-contract.ts +37 -0
  298. package/src/plugins/host-link.ts +158 -0
  299. package/src/plugins/host-modules.ts +61 -0
  300. package/src/plugins/loader.ts +41 -22
  301. package/src/plugins/ownership.ts +1 -0
  302. package/src/plugins/prediction-markets/index.tsx +1 -0
  303. package/src/plugins/registry/context.ts +3 -0
  304. package/src/plugins/registry/contributions.ts +27 -0
  305. package/src/plugins/registry/index.test.ts +25 -0
  306. package/src/plugins/registry/index.ts +8 -0
  307. package/src/plugins/registry/pane-settings.ts +6 -13
  308. package/src/plugins/seed.test.ts +83 -0
  309. package/src/plugins/seed.ts +80 -0
  310. package/src/public/broker.ts +31 -0
  311. package/src/public/market-data.ts +9 -0
  312. package/src/public/pane-hooks.ts +57 -0
  313. package/src/public/public-api.test.ts +285 -0
  314. package/src/public/react.ts +62 -0
  315. package/src/public/test-support.ts +23 -0
  316. package/src/public/time-series.ts +8 -0
  317. package/src/public/utils.ts +77 -0
  318. package/src/remote/layout-helpers.ts +6 -8
  319. package/src/remote/resources.ts +2 -0
  320. package/src/remote/schema.ts +1 -0
  321. package/src/remote/semantic-tree.test.tsx +105 -0
  322. package/src/remote/semantic-tree.tsx +53 -35
  323. package/src/renderers/browser/main.tsx +6 -0
  324. package/src/renderers/cloudflare/worker.test.ts +19 -0
  325. package/src/renderers/cloudflare/worker.ts +1 -1
  326. package/src/renderers/electrobun/bun/external-plugins.ts +140 -0
  327. package/src/renderers/electrobun/bun/index.ts +5 -2
  328. package/src/renderers/electrobun/shared/protocol.ts +21 -0
  329. package/src/renderers/electrobun/view/build-assets.ts +0 -1
  330. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +51 -1
  331. package/src/renderers/electrobun/view/external-plugins.test.ts +45 -0
  332. package/src/renderers/electrobun/view/external-plugins.ts +63 -0
  333. package/src/renderers/electrobun/view/host/ascii-text.test.ts +48 -0
  334. package/src/renderers/electrobun/view/host/ascii-text.ts +11 -1
  335. package/src/renderers/electrobun/view/host/chart-surface.tsx +38 -32
  336. package/src/renderers/electrobun/view/host/mouse.ts +16 -2
  337. package/src/renderers/electrobun/view/main.tsx +27 -2
  338. package/src/renderers/electrobun/view/styles.css +5 -0
  339. package/src/renderers/electrobun/view/toast-host.tsx +18 -0
  340. package/src/renderers/opentui/chart-surface.tsx +5 -1
  341. package/src/renderers/opentui/host.tsx +16 -2
  342. package/src/renderers/opentui/interaction-performance.test.ts +79 -0
  343. package/src/renderers/opentui/interaction-performance.ts +188 -0
  344. package/src/renderers/opentui/start.tsx +26 -1
  345. package/src/renderers/opentui/test-utils.tsx +13 -0
  346. package/src/renderers/opentui/toast-host.tsx +26 -2
  347. package/src/renderers/opentui/ui-host.tsx +1 -0
  348. package/src/sources/provider-router/search.test.ts +191 -0
  349. package/src/sources/provider-router/search.ts +92 -39
  350. package/src/state/app/context/index.tsx +2 -0
  351. package/src/state/app/context/selector.test.tsx +33 -1
  352. package/src/state/hooks/quote-streaming.test.tsx +93 -0
  353. package/src/state/hooks/quote-streaming.ts +106 -30
  354. package/src/state/pane-table-export-registry.ts +19 -0
  355. package/src/theme/color-utils.ts +1 -1
  356. package/src/theme/colors.ts +50 -0
  357. package/src/theme/themes.test.ts +15 -2
  358. package/src/theme/themes.ts +19 -1
  359. package/src/tickers/search/index.test.ts +26 -0
  360. package/src/tickers/search/index.ts +42 -17
  361. package/src/tickers/search/ranking.ts +14 -0
  362. package/src/time-series/chart-data.test.ts +216 -0
  363. package/src/time-series/resolution.test.ts +25 -26
  364. package/src/time-series/resolution.ts +41 -10
  365. package/src/time-series/resolve.ts +7 -0
  366. package/src/time-series/types.ts +3 -1
  367. package/src/time-series/use-chart-resolution.ts +5 -0
  368. package/src/types/config.ts +123 -3
  369. package/src/types/data-provider.ts +11 -0
  370. package/src/types/headless.ts +161 -0
  371. package/src/types/plugin.ts +102 -0
  372. package/src/ui/host.tsx +12 -1
  373. package/src/ui/index.tsx +1 -0
  374. package/src/ui/toast.tsx +8 -4
  375. package/src/utils/format.ts +35 -0
  376. package/src/utils/fuzzy-search.test.ts +94 -0
  377. package/src/utils/fuzzy-search.ts +81 -22
  378. package/src/utils/shortcut-labels.ts +14 -0
  379. package/src/components/chart/core/axis-format.ts +0 -157
  380. package/src/components/chart/core/index.test.ts +0 -1075
  381. package/src/components/chart/core/projection.ts +0 -357
  382. package/src/components/chart/core/render-utils.ts +0 -4
  383. package/src/components/chart/core/renderer.ts +0 -244
  384. package/src/components/chart/core/scene.ts +0 -180
  385. package/src/components/chart/core/time-axis.ts +0 -330
  386. package/src/components/chart/cursor-motion.ts +0 -47
  387. package/src/components/chart/indicators/types.ts +0 -26
  388. package/src/components/chart/market-session.ts +0 -176
  389. package/src/components/chart/multi-line/renderer.test.ts +0 -83
  390. package/src/components/chart/multi-line/renderer.ts +0 -161
  391. package/src/components/chart/multi-line/scene.ts +0 -188
  392. package/src/components/chart/native/raster/layout.ts +0 -129
  393. package/src/components/chart/native/raster/price-chart.ts +0 -244
  394. package/src/components/chart/rendering/chart-draw.ts +0 -270
  395. package/src/components/chart/rendering/geometry.ts +0 -95
  396. package/src/components/chart/rendering/pixel-buffer.ts +0 -267
  397. package/src/components/chart/scatter-chart-renderer.test.ts +0 -31
  398. package/src/components/chart/scatter-chart-renderer.ts +0 -223
  399. package/src/components/chart/static/chart/surface.tsx +0 -358
  400. package/src/components/command-bar/routes/root/article-results.test.ts +0 -83
  401. package/src/components/command-bar/routes/root/article-results.ts +0 -55
  402. package/src/plugins/builtin/news/wire/article-search.test.ts +0 -72
  403. package/src/plugins/builtin/news/wire/article-search.ts +0 -140
  404. package/src/plugins/builtin/substack/api/auth.ts +0 -185
  405. package/src/plugins/builtin/substack/api/cache.ts +0 -82
  406. package/src/plugins/builtin/substack/api/loaders.ts +0 -170
  407. package/src/plugins/builtin/substack/api/store.ts +0 -186
  408. package/src/plugins/builtin/substack/api/types.ts +0 -42
  409. package/src/plugins/builtin/substack/api.test.ts +0 -298
  410. package/src/plugins/builtin/substack/article-detail.tsx +0 -397
  411. package/src/plugins/builtin/substack/article-stack.tsx +0 -133
  412. package/src/plugins/builtin/substack/content.ts +0 -478
  413. package/src/plugins/builtin/substack/feed-tabs.tsx +0 -44
  414. package/src/plugins/builtin/substack/index.tsx +0 -49
  415. package/src/plugins/builtin/substack/login-view.tsx +0 -126
  416. package/src/plugins/builtin/substack/model.test.ts +0 -155
  417. package/src/plugins/builtin/substack/normalize.ts +0 -343
  418. package/src/plugins/builtin/substack/pane-footer.ts +0 -52
  419. package/src/plugins/builtin/substack/pane-state.ts +0 -170
  420. package/src/plugins/builtin/substack/pane.tsx +0 -547
  421. package/src/plugins/builtin/substack/read-state.ts +0 -30
  422. package/src/plugins/builtin/substack/table.ts +0 -111
  423. package/src/plugins/builtin/substack/types.ts +0 -71
  424. package/src/plugins/builtin/substack/utils.ts +0 -131
  425. package/src/plugins/builtin/ticker-detail/data-panes/provider-search.tsx +0 -229
  426. package/src/plugins/ibkr/account-cache.test.ts +0 -102
  427. package/src/plugins/ibkr/account-cache.ts +0 -24
  428. package/src/plugins/ibkr/broker-adapter.ts +0 -286
  429. package/src/plugins/ibkr/config.test.ts +0 -79
  430. package/src/plugins/ibkr/config.ts +0 -250
  431. package/src/plugins/ibkr/dialogs.tsx +0 -40
  432. package/src/plugins/ibkr/flex/index.test.ts +0 -264
  433. package/src/plugins/ibkr/flex/index.ts +0 -367
  434. package/src/plugins/ibkr/flex/statement-client.ts +0 -235
  435. package/src/plugins/ibkr/fundamental-parser.ts +0 -149
  436. package/src/plugins/ibkr/gateway/account-loaders.ts +0 -266
  437. package/src/plugins/ibkr/gateway/account-summary.ts +0 -164
  438. package/src/plugins/ibkr/gateway/client-lock.ts +0 -154
  439. package/src/plugins/ibkr/gateway/connection-events.ts +0 -114
  440. package/src/plugins/ibkr/gateway/connection.ts +0 -152
  441. package/src/plugins/ibkr/gateway/contracts.ts +0 -132
  442. package/src/plugins/ibkr/gateway/fundamentals.ts +0 -83
  443. package/src/plugins/ibkr/gateway/helpers.ts +0 -33
  444. package/src/plugins/ibkr/gateway/history-requests.ts +0 -158
  445. package/src/plugins/ibkr/gateway/history.ts +0 -120
  446. package/src/plugins/ibkr/gateway/instrument-lookup.ts +0 -117
  447. package/src/plugins/ibkr/gateway/market-data.ts +0 -345
  448. package/src/plugins/ibkr/gateway/order-execution.ts +0 -218
  449. package/src/plugins/ibkr/gateway/orders.ts +0 -63
  450. package/src/plugins/ibkr/gateway/price-normalization.test.ts +0 -44
  451. package/src/plugins/ibkr/gateway/price-normalization.ts +0 -50
  452. package/src/plugins/ibkr/gateway/quote-stream-controller.ts +0 -203
  453. package/src/plugins/ibkr/gateway/quote-stream.ts +0 -171
  454. package/src/plugins/ibkr/gateway/selection.ts +0 -51
  455. package/src/plugins/ibkr/gateway/service/index.test.ts +0 -675
  456. package/src/plugins/ibkr/gateway/service/index.ts +0 -429
  457. package/src/plugins/ibkr/gateway/service/lifecycle.ts +0 -191
  458. package/src/plugins/ibkr/gateway/service/manager.ts +0 -52
  459. package/src/plugins/ibkr/gateway/service/market-data.ts +0 -48
  460. package/src/plugins/ibkr/gateway/service/native-loader.ts +0 -12
  461. package/src/plugins/ibkr/gateway/service/native.ts +0 -543
  462. package/src/plugins/ibkr/gateway/timeouts.ts +0 -13
  463. package/src/plugins/ibkr/gateway/types.ts +0 -27
  464. package/src/plugins/ibkr/index.test.tsx +0 -162
  465. package/src/plugins/ibkr/index.tsx +0 -159
  466. package/src/plugins/ibkr/instance-selection.test.ts +0 -120
  467. package/src/plugins/ibkr/instance-selection.ts +0 -36
  468. package/src/plugins/ibkr/portfolio-performance.test.ts +0 -32
  469. package/src/plugins/ibkr/portfolio-performance.ts +0 -129
  470. package/src/plugins/ibkr/trade/badge.tsx +0 -28
  471. package/src/plugins/ibkr/trade/dialogs.tsx +0 -44
  472. package/src/plugins/ibkr/trade/preview-panel.tsx +0 -130
  473. package/src/plugins/ibkr/trade/tab/actions.tsx +0 -443
  474. package/src/plugins/ibkr/trade/tab/field-editors.tsx +0 -79
  475. package/src/plugins/ibkr/trade/tab/footer.ts +0 -53
  476. package/src/plugins/ibkr/trade/tab/header.tsx +0 -167
  477. package/src/plugins/ibkr/trade/tab/index.test.tsx +0 -178
  478. package/src/plugins/ibkr/trade/tab/index.tsx +0 -298
  479. package/src/plugins/ibkr/trade/tab/model.ts +0 -159
  480. package/src/plugins/ibkr/trade/tab/order-request.ts +0 -64
  481. package/src/plugins/ibkr/trade/tab/shortcuts.ts +0 -89
  482. package/src/plugins/ibkr/trade/tab/state-sync.ts +0 -86
  483. package/src/plugins/ibkr/trade/ticket-panel.tsx +0 -301
  484. package/src/plugins/ibkr/trade/utils.test.ts +0 -175
  485. package/src/plugins/ibkr/trade/utils.ts +0 -144
  486. package/src/plugins/ibkr/trading/pane-view.tsx +0 -237
  487. package/src/plugins/ibkr/trading/pane.tsx +0 -332
  488. package/src/plugins/ibkr/trading/state.test.ts +0 -109
  489. package/src/plugins/ibkr/trading/state.ts +0 -335
@@ -0,0 +1,31 @@
1
+ import type { PluginModule } from "../plugin-module";
2
+ import { PluginMarketplacePane, PLUGIN_MARKETPLACE_PANE_ID } from "./pane";
3
+
4
+ export { PLUGIN_MARKETPLACE_PANE_ID } from "./pane";
5
+ export { setMarketplaceHost } from "./store";
6
+
7
+ export const pluginMarketplaceModule: PluginModule = {
8
+ panes: [
9
+ {
10
+ id: PLUGIN_MARKETPLACE_PANE_ID,
11
+ name: "Plugins",
12
+ icon: "P",
13
+ component: PluginMarketplacePane,
14
+ defaultPosition: "right",
15
+ defaultMode: "floating",
16
+ defaultFloatingSize: { width: 118, height: 34 },
17
+ },
18
+ ],
19
+
20
+ paneTemplates: [
21
+ {
22
+ id: "plugin-marketplace-pane",
23
+ paneId: PLUGIN_MARKETPLACE_PANE_ID,
24
+ label: "Plugins",
25
+ description: "Browse and install Gloomberb plugins, and enable or disable the ones you have.",
26
+ keywords: ["plugin", "plugins", "marketplace", "install", "extend", "addon", "extension"],
27
+ shortcut: { prefix: "PL" },
28
+ createInstance: () => ({ placement: "floating" }),
29
+ },
30
+ ],
31
+ };
@@ -0,0 +1,200 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ collectCategories,
5
+ isInstallable,
6
+ mergeCatalog,
7
+ sortEntries,
8
+ unsupportedLabel,
9
+ type InstalledPlugin,
10
+ type RegistryPlugin,
11
+ } from "./model";
12
+
13
+ function registryPlugin(overrides: Partial<RegistryPlugin> & Pick<RegistryPlugin, "id">): RegistryPlugin {
14
+ return {
15
+ name: overrides.id,
16
+ repo: `gloom-sh/${overrides.id}`,
17
+ tagline: "",
18
+ author: { name: "Someone" },
19
+ categories: ["data"],
20
+ targets: ["cli", "tui", "desktop", "web"],
21
+ hosts: [],
22
+ contributes: { panes: [], capabilities: [], broker: false },
23
+ tier: "community",
24
+ bundled: false,
25
+ stars: 0,
26
+ ...overrides,
27
+ };
28
+ }
29
+
30
+ function installedPlugin(overrides: Partial<InstalledPlugin> & Pick<InstalledPlugin, "id">): InstalledPlugin {
31
+ return {
32
+ name: overrides.id,
33
+ version: "1.0.0",
34
+ toggleable: true,
35
+ enabled: true,
36
+ source: "external",
37
+ ...overrides,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * The merge is the only real logic in this pane, and it reconciles three
43
+ * sources that disagree during normal use. Each case below is a state a user
44
+ * can actually reach, and getting one wrong either hides a plugin they have
45
+ * installed or offers an install button for something already present.
46
+ */
47
+ describe("mergeCatalog", () => {
48
+ test("marks a bundled plugin installed even when the local catalog has not reported it", () => {
49
+ // Happens whenever the feed ships an entry before the user upgrades.
50
+ const [entry] = mergeCatalog({
51
+ registry: [registryPlugin({ id: "gloomberb-cloud", bundled: true, tier: "official" })],
52
+ installed: [],
53
+ target: "tui",
54
+ });
55
+
56
+ expect(entry?.installed).toBe(true);
57
+ expect(entry?.enabled).toBe(true);
58
+ });
59
+
60
+ test("takes enabled state from the local catalog, not the registry", () => {
61
+ const [entry] = mergeCatalog({
62
+ registry: [registryPlugin({ id: "hackernews" })],
63
+ installed: [installedPlugin({ id: "hackernews", enabled: false, version: "0.2.0" })],
64
+ target: "tui",
65
+ });
66
+
67
+ expect(entry?.installed).toBe(true);
68
+ expect(entry?.enabled).toBe(false);
69
+ expect(entry?.installedVersion).toBe("0.2.0");
70
+ });
71
+
72
+ test("keeps a side-loaded plugin the registry has never seen", () => {
73
+ const entries = mergeCatalog({
74
+ registry: [registryPlugin({ id: "hackernews" })],
75
+ installed: [installedPlugin({ id: "my-private-plugin" })],
76
+ target: "tui",
77
+ });
78
+
79
+ const sideloaded = entries.find((entry) => entry.id === "my-private-plugin");
80
+ expect(sideloaded?.installed).toBe(true);
81
+ expect(sideloaded?.categories).toEqual(["unlisted"]);
82
+ });
83
+
84
+ test("flags a plugin the current renderer cannot run without calling it uninstalled", () => {
85
+ const [entry] = mergeCatalog({
86
+ registry: [registryPlugin({ id: "ibkr-gateway", targets: ["cli", "tui", "desktop"] })],
87
+ installed: [installedPlugin({ id: "ibkr-gateway" })],
88
+ target: "web",
89
+ });
90
+
91
+ expect(entry?.installed).toBe(true);
92
+ expect(entry?.unsupportedHere).toBe(true);
93
+ expect(unsupportedLabel(entry!)).toBe("Desktop only");
94
+ });
95
+
96
+ test("does not flag a plugin that supports the current renderer", () => {
97
+ const [entry] = mergeCatalog({
98
+ registry: [registryPlugin({ id: "hackernews" })],
99
+ installed: [],
100
+ target: "web",
101
+ });
102
+
103
+ expect(entry?.unsupportedHere).toBe(false);
104
+ expect(unsupportedLabel(entry!)).toBeNull();
105
+ });
106
+
107
+ test("surfaces a load error so a broken install is visible rather than missing", () => {
108
+ const [entry] = mergeCatalog({
109
+ registry: [registryPlugin({ id: "broken" })],
110
+ installed: [installedPlugin({ id: "broken", loadError: "SyntaxError" })],
111
+ target: "tui",
112
+ });
113
+
114
+ expect(entry?.loadError).toBe("SyntaxError");
115
+ });
116
+ });
117
+
118
+ describe("sortEntries", () => {
119
+ test("puts what is installed above the rest of the catalog", () => {
120
+ // One list rather than two tabs: acting on what you already have should not
121
+ // require a mode switch, and an uninstalled plugin should never outrank one
122
+ // that is present just because it has more stars.
123
+ const entries = mergeCatalog({
124
+ registry: [
125
+ registryPlugin({ id: "popular-uninstalled", tier: "official", stars: 5000 }),
126
+ registryPlugin({ id: "quiet-installed", tier: "community", stars: 0 }),
127
+ ],
128
+ installed: [installedPlugin({ id: "quiet-installed" })],
129
+ target: "tui",
130
+ });
131
+
132
+ expect(sortEntries(entries).map((entry) => entry.id))
133
+ .toEqual(["quiet-installed", "popular-uninstalled"]);
134
+ });
135
+
136
+ test("puts the featured plugin first, then tier, then stars", () => {
137
+ const entries = mergeCatalog({
138
+ registry: [
139
+ registryPlugin({ id: "community-popular", tier: "community", stars: 900 }),
140
+ registryPlugin({ id: "official-quiet", tier: "official", stars: 2 }),
141
+ registryPlugin({ id: "cloud", tier: "official", featured: true, bundled: true, stars: 0 }),
142
+ registryPlugin({ id: "verified-mid", tier: "verified", stars: 50 }),
143
+ ],
144
+ installed: [],
145
+ target: "tui",
146
+ });
147
+
148
+ // All uninstalled, so the curated order applies within that half.
149
+ expect(sortEntries(entries).map((entry) => entry.id)).toEqual([
150
+ "cloud",
151
+ "official-quiet",
152
+ "verified-mid",
153
+ "community-popular",
154
+ ]);
155
+ });
156
+ });
157
+
158
+ describe("collectCategories", () => {
159
+ test("deduplicates and sorts categories across the catalog", () => {
160
+ const entries = mergeCatalog({
161
+ registry: [
162
+ registryPlugin({ id: "a", categories: ["news", "data"] }),
163
+ registryPlugin({ id: "b", categories: ["data"] }),
164
+ ],
165
+ installed: [],
166
+ target: "tui",
167
+ });
168
+
169
+ expect(collectCategories(entries)).toEqual(["data", "news"]);
170
+ });
171
+ });
172
+
173
+ describe("isInstallable", () => {
174
+ test("offers an install only for a catalog plugin that is absent", () => {
175
+ const [available, bundled, installed] = mergeCatalog({
176
+ registry: [
177
+ registryPlugin({ id: "available" }),
178
+ registryPlugin({ id: "bundled", bundled: true }),
179
+ registryPlugin({ id: "installed" }),
180
+ ],
181
+ installed: [installedPlugin({ id: "installed" })],
182
+ target: "tui",
183
+ });
184
+
185
+ expect(isInstallable(available!)).toBe(true);
186
+ // Ships with the app; there is nothing to fetch.
187
+ expect(isInstallable(bundled!)).toBe(false);
188
+ expect(isInstallable(installed!)).toBe(false);
189
+ });
190
+
191
+ test("does not offer an install without a repository to clone", () => {
192
+ const [entry] = mergeCatalog({
193
+ registry: [],
194
+ installed: [installedPlugin({ id: "sideloaded" })],
195
+ target: "tui",
196
+ });
197
+
198
+ expect(isInstallable(entry!)).toBe(false);
199
+ });
200
+ });
@@ -0,0 +1,204 @@
1
+ import type { PluginTarget } from "../../../types/plugin";
2
+
3
+ export type PluginTier = "official" | "verified" | "community";
4
+
5
+ /** One record from https://plugins.gloom.sh/registry.json. */
6
+ export interface RegistryPlugin {
7
+ id: string;
8
+ name: string;
9
+ tagline: string;
10
+ description?: string;
11
+ repo?: string;
12
+ ref?: string;
13
+ commit?: string;
14
+ author: { name: string; github?: string };
15
+ categories: string[];
16
+ targets: PluginTarget[];
17
+ hosts: string[];
18
+ contributes: { panes: string[]; capabilities: string[]; broker: boolean };
19
+ minGloomberb?: string;
20
+ tier: PluginTier;
21
+ bundled: boolean;
22
+ featured?: boolean;
23
+ stars: number;
24
+ updatedAt?: string;
25
+ readme?: string;
26
+ }
27
+
28
+ export interface RegistryFeed {
29
+ version: number;
30
+ generatedAt: string;
31
+ plugins: RegistryPlugin[];
32
+ }
33
+
34
+ /** What the app knows about a plugin it has actually loaded. */
35
+ export interface InstalledPlugin {
36
+ id: string;
37
+ name: string;
38
+ version: string;
39
+ description?: string;
40
+ toggleable: boolean;
41
+ enabled: boolean;
42
+ source: "builtin" | "external";
43
+ /** Set when the plugin is installed but cannot run on this renderer. */
44
+ unsupportedTarget?: PluginTarget;
45
+ loadError?: string;
46
+ }
47
+
48
+ export interface MarketplaceEntry {
49
+ id: string;
50
+ name: string;
51
+ tagline: string;
52
+ description?: string;
53
+ categories: string[];
54
+ tier: PluginTier;
55
+ targets: PluginTarget[];
56
+ hosts: string[];
57
+ repo?: string;
58
+ stars: number;
59
+ featured: boolean;
60
+ /** Ships inside Gloomberb; there is nothing to install or remove. */
61
+ bundled: boolean;
62
+ installed: boolean;
63
+ enabled: boolean;
64
+ toggleable: boolean;
65
+ installedVersion?: string;
66
+ /** The registry's tag, when the plugin is installable. */
67
+ availableVersion?: string;
68
+ contributes?: RegistryPlugin["contributes"];
69
+ loadError?: string;
70
+ /**
71
+ * The plugin cannot run on the current renderer — IBKR Gateway in a browser,
72
+ * for example. Distinct from "not installed": the user may have it installed
73
+ * and working elsewhere.
74
+ */
75
+ unsupportedHere: boolean;
76
+ }
77
+
78
+ const TIER_RANK: Record<PluginTier, number> = { official: 0, verified: 1, community: 2 };
79
+
80
+ /**
81
+ * Merges the remote catalog with what is actually loaded.
82
+ *
83
+ * Three sources disagree in normal operation and all three matter: the registry
84
+ * knows about plugins the user has not installed, the app knows about plugins
85
+ * the registry has never heard of (installed straight from a git URL), and a
86
+ * plugin can be installed but inert because this renderer cannot run it. The
87
+ * merge keeps all three visible rather than showing whichever list is handy.
88
+ */
89
+ export function mergeCatalog(options: {
90
+ registry: readonly RegistryPlugin[];
91
+ installed: readonly InstalledPlugin[];
92
+ target: PluginTarget;
93
+ }): MarketplaceEntry[] {
94
+ const { registry, installed, target } = options;
95
+ const installedById = new Map(installed.map((entry) => [entry.id, entry]));
96
+ const entries: MarketplaceEntry[] = [];
97
+
98
+ for (const plugin of registry) {
99
+ const local = installedById.get(plugin.id);
100
+ installedById.delete(plugin.id);
101
+
102
+ entries.push({
103
+ id: plugin.id,
104
+ name: plugin.name,
105
+ tagline: plugin.tagline,
106
+ description: plugin.description,
107
+ categories: plugin.categories,
108
+ tier: plugin.tier,
109
+ targets: plugin.targets,
110
+ hosts: plugin.hosts,
111
+ repo: plugin.repo,
112
+ stars: plugin.stars,
113
+ featured: plugin.featured === true,
114
+ bundled: plugin.bundled,
115
+ // A bundled plugin is present whether or not the local catalog reports it,
116
+ // which matters when the feed is newer than the running build.
117
+ installed: plugin.bundled || !!local,
118
+ enabled: local ? local.enabled : plugin.bundled,
119
+ toggleable: local ? local.toggleable : true,
120
+ installedVersion: local?.version,
121
+ availableVersion: plugin.ref,
122
+ contributes: plugin.contributes,
123
+ loadError: local?.loadError,
124
+ unsupportedHere: !plugin.targets.includes(target),
125
+ });
126
+ }
127
+
128
+ // Installed but unlisted: side-loaded from a git URL, or listed under a
129
+ // different id. Still needs to be manageable.
130
+ for (const local of installedById.values()) {
131
+ entries.push({
132
+ id: local.id,
133
+ name: local.name,
134
+ tagline: local.description ?? "Installed outside the registry",
135
+ description: local.description,
136
+ categories: ["unlisted"],
137
+ tier: "community",
138
+ targets: local.unsupportedTarget ? [] : [target],
139
+ hosts: [],
140
+ stars: 0,
141
+ featured: false,
142
+ bundled: local.source === "builtin",
143
+ installed: true,
144
+ enabled: local.enabled,
145
+ toggleable: local.toggleable,
146
+ installedVersion: local.version,
147
+ loadError: local.loadError,
148
+ unsupportedHere: !!local.unsupportedTarget,
149
+ });
150
+ }
151
+
152
+ return entries;
153
+ }
154
+
155
+ /**
156
+ * One ordered list rather than an installed/available split.
157
+ *
158
+ * What you already have is what you act on most, so it sorts to the top; the
159
+ * rest of the catalog continues below without a mode switch to find it. Within
160
+ * each half the order is the curated one: featured, then tier, then stars.
161
+ */
162
+ export function sortEntries(entries: readonly MarketplaceEntry[]): MarketplaceEntry[] {
163
+ return [...entries].sort((a, b) => {
164
+ if (a.installed !== b.installed) return a.installed ? -1 : 1;
165
+ if (a.featured !== b.featured) return a.featured ? -1 : 1;
166
+ if (a.tier !== b.tier) return TIER_RANK[a.tier] - TIER_RANK[b.tier];
167
+ if (a.stars !== b.stars) return b.stars - a.stars;
168
+ return a.name.localeCompare(b.name);
169
+ });
170
+ }
171
+
172
+ export function filterEntries(
173
+ entries: readonly MarketplaceEntry[],
174
+ options: { query: string; category: string | null },
175
+ ): MarketplaceEntry[] {
176
+ const query = options.query.trim().toLowerCase();
177
+
178
+ return entries.filter((entry) => {
179
+ if (options.category && !entry.categories.includes(options.category)) return false;
180
+ if (!query) return true;
181
+ return [entry.name, entry.id, entry.tagline, entry.description, ...entry.categories]
182
+ .some((field) => typeof field === "string" && field.toLowerCase().includes(query));
183
+ });
184
+ }
185
+
186
+ export function collectCategories(entries: readonly MarketplaceEntry[]): string[] {
187
+ const seen = new Set<string>();
188
+ for (const entry of entries) {
189
+ for (const category of entry.categories) seen.add(category);
190
+ }
191
+ return [...seen].sort();
192
+ }
193
+
194
+ /** Whether this entry can be installed from inside the app right now. */
195
+ export function isInstallable(entry: MarketplaceEntry): boolean {
196
+ return !entry.installed && !entry.bundled && !!entry.repo;
197
+ }
198
+
199
+ /** Short label for a plugin that cannot run on the current renderer. */
200
+ export function unsupportedLabel(entry: MarketplaceEntry): string | null {
201
+ if (!entry.unsupportedHere) return null;
202
+ if (entry.targets.length === 0) return "Unavailable here";
203
+ return entry.targets.includes("desktop") ? "Desktop only" : "Terminal only";
204
+ }