gloomberb 0.10.0 → 0.10.2

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 (353) hide show
  1. package/package.json +8 -1
  2. package/src/api-client/auth.ts +1 -0
  3. package/src/api-client/index.test.ts +359 -108
  4. package/src/api-client/index.ts +24 -13
  5. package/src/api-client/socket.ts +70 -35
  6. package/src/api-client/types.ts +1 -1
  7. package/src/app/language-observer.test.tsx +82 -0
  8. package/src/app/language-observer.tsx +13 -0
  9. package/src/app/pane-runtime/index.tsx +2 -2
  10. package/src/app/pane-runtime/pane-settings-runtime.tsx +2 -2
  11. package/src/app/pane-runtime/pane-template-runtime.ts +2 -2
  12. package/src/app/pane-runtime/plugin-bindings.ts +2 -2
  13. package/src/app/pane-runtime/ticker-open-runtime.ts +2 -2
  14. package/src/app/runtime/broker-import.ts +2 -2
  15. package/src/app/runtime/plugin-bindings.ts +2 -2
  16. package/src/app/runtime/startup.ts +2 -2
  17. package/src/app.tsx +16 -7
  18. package/src/brokers/account-cache.ts +6 -6
  19. package/src/brokers/broker-ticker-sync.ts +3 -3
  20. package/src/brokers/remote-broker-adapter.ts +2 -2
  21. package/src/brokers/sync-broker-instance.ts +5 -6
  22. package/src/cli/commands/api.ts +3 -6
  23. package/src/cli/commands/automation.ts +7 -15
  24. package/src/cli/commands/market.ts +26 -61
  25. package/src/cli/commands/overview.ts +34 -45
  26. package/src/cli/commands/search.ts +25 -27
  27. package/src/cli/commands/system.ts +27 -57
  28. package/src/cli/commands/ticker.ts +62 -63
  29. package/src/cli/context.test.ts +63 -0
  30. package/src/cli/context.ts +15 -1
  31. package/src/cli/index.ts +1 -1
  32. package/src/cli/pane-functions/data.ts +2 -19
  33. package/src/cli/pane-functions/index.ts +26 -25
  34. package/src/cli/pane-functions/report.ts +1 -1
  35. package/src/cli/pane-functions/screenshot.ts +3 -3
  36. package/src/cli/scoped-context.ts +41 -0
  37. package/src/components/chart/composite/composite-chart.test.tsx +615 -18
  38. package/src/components/chart/composite/composite-chart.tsx +179 -79
  39. package/src/components/chart/composite/format.test.ts +20 -7
  40. package/src/components/chart/composite/index.ts +0 -21
  41. package/src/components/chart/composite/interactions.test.ts +90 -3
  42. package/src/components/chart/composite/interactions.ts +189 -24
  43. package/src/components/chart/composite/price-series.ts +0 -2
  44. package/src/components/chart/composite/rasterizer.ts +10 -9
  45. package/src/components/chart/composite/renderers.test.ts +108 -2
  46. package/src/components/chart/composite/scene.test.ts +36 -0
  47. package/src/components/chart/composite/scene.ts +5 -2
  48. package/src/components/chart/composite/text-renderer.ts +37 -25
  49. package/src/components/chart/composite/time-axis.test.ts +243 -0
  50. package/src/components/chart/composite/time-axis.ts +608 -0
  51. package/src/components/chart/composite/types.ts +12 -1
  52. package/src/components/chart/core/data.ts +2 -159
  53. package/src/components/chart/core/date-window.ts +6 -225
  54. package/src/components/chart/core/palette.ts +1 -12
  55. package/src/components/chart/core/pointer.ts +0 -142
  56. package/src/components/chart/core/types.ts +1 -58
  57. package/src/components/chart/cursor-motion.ts +2 -4
  58. package/src/components/chart/indicators/types.ts +0 -9
  59. package/src/components/chart/market-session.ts +0 -11
  60. package/src/components/chart/native/kitty/protocol.ts +1 -1
  61. package/src/components/chart/native/raster/layout.ts +3 -10
  62. package/src/components/chart/static/chart/axis-overlays.test.tsx +64 -0
  63. package/src/components/chart/static/chart/axis-overlays.tsx +59 -14
  64. package/src/components/chart/static/chart/surface.tsx +1 -2
  65. package/src/components/command-bar/action-runtime.ts +2 -2
  66. package/src/components/command-bar/commands/direct/index.ts +1 -1
  67. package/src/components/command-bar/panel/body.tsx +2 -2
  68. package/src/components/command-bar/panel/index.tsx +11 -9
  69. package/src/components/command-bar/routes/ticker-search/actions.ts +2 -2
  70. package/src/components/command-bar/surface/index.tsx +2 -2
  71. package/src/components/command-bar/workflow/body.tsx +3 -3
  72. package/src/components/command-bar/workflow/collection-actions.ts +2 -2
  73. package/src/components/command-bar/workflow/coordinator.ts +2 -2
  74. package/src/components/command-bar/workflow/field-row.tsx +25 -11
  75. package/src/components/command-bar/workflow/tickers.ts +2 -2
  76. package/src/components/feed-data-table/stack-view.test.tsx +28 -36
  77. package/src/components/feed-data-table/stack-view.tsx +3 -1
  78. package/src/components/layout/detached-pane-shell.tsx +6 -6
  79. package/src/components/layout/header.tsx +13 -9
  80. package/src/components/layout/pane/content.test.tsx +42 -3
  81. package/src/components/layout/pane/content.tsx +3 -1
  82. package/src/components/layout/pane/footer/index.test.tsx +45 -2
  83. package/src/components/layout/pane/footer/registration.tsx +3 -1
  84. package/src/components/layout/shell/index.tsx +1 -2
  85. package/src/components/layout/shell/native/surfaces.ts +1 -1
  86. package/src/components/layout/shell/native/window-state.ts +1 -1
  87. package/src/components/layout/status-bar.tsx +12 -8
  88. package/src/components/layout/window-controls.tsx +8 -6
  89. package/src/components/onboarding/onboarding-wizard.tsx +3 -1
  90. package/src/components/pane-settings-dialog/desktop.tsx +0 -2
  91. package/src/components/pane-settings-dialog.tsx +0 -2
  92. package/src/components/pane-template-wizard.tsx +10 -9
  93. package/src/components/price-performance.tsx +2 -113
  94. package/src/components/price-sparkline/model.ts +15 -17
  95. package/src/components/price-sparkline/view.tsx +8 -7
  96. package/src/components/speedometer-gauge/desktop.tsx +2 -1
  97. package/src/components/speedometer-gauge/index.tsx +0 -2
  98. package/src/components/speedometer-gauge/terminal.tsx +24 -5
  99. package/src/components/ticker/badge/index.tsx +1 -18
  100. package/src/components/ui/data-table/opentui/index.tsx +3 -3
  101. package/src/components/ui/frame.tsx +1 -2
  102. package/src/components/ui/page-stack-view.test.tsx +126 -0
  103. package/src/components/ui/page-stack-view.tsx +24 -12
  104. package/src/components/ui/tabs.tsx +7 -3
  105. package/src/components/ui/ui.test.tsx +23 -2
  106. package/src/components/use-external-link-footer.ts +4 -2
  107. package/src/core/app-service-ports.ts +46 -0
  108. package/src/core/app-services.test.ts +66 -0
  109. package/src/core/app-services.ts +17 -15
  110. package/src/data/config/chart-settings.ts +3 -10
  111. package/src/data/config/store/index.test.ts +99 -2
  112. package/src/data/config/store/migrations.ts +278 -0
  113. package/src/data/config/store/normalize.ts +44 -186
  114. package/src/data/resource-store.ts +73 -12
  115. package/src/data/sqlite/cache.test.ts +104 -15
  116. package/src/i18n/index.test.ts +17 -0
  117. package/src/i18n/index.ts +23 -6
  118. package/src/i18n/ja.ts +13 -0
  119. package/src/i18n/ko.ts +13 -0
  120. package/src/i18n/react.ts +6 -0
  121. package/src/i18n/zh-cn.ts +13 -0
  122. package/src/i18n/zh-tw.ts +13 -0
  123. package/src/market-data/coordinator/auxiliary.ts +7 -3
  124. package/src/market-data/coordinator/chart.ts +1 -1
  125. package/src/market-data/coordinator/index.test.ts +26 -0
  126. package/src/market-data/coordinator/index.ts +11 -5
  127. package/src/market-data/coordinator/quotes.ts +4 -40
  128. package/src/market-data/coordinator/subscriptions.test.ts +89 -1
  129. package/src/market-data/hooks.tsx +47 -1
  130. package/src/market-data/market/status.ts +6 -6
  131. package/src/market-data/performance.ts +1 -1
  132. package/src/market-data/quote-subscription-target.test.ts +37 -0
  133. package/src/market-data/quote-subscription-target.ts +60 -0
  134. package/src/market-data/quotes/freshness.ts +1 -0
  135. package/src/market-data/quotes/resolution.ts +4 -0
  136. package/src/market-data/request-types.ts +3 -3
  137. package/src/news/aggregator.test.ts +97 -1
  138. package/src/news/aggregator.ts +100 -57
  139. package/src/news/hooks.test.tsx +64 -0
  140. package/src/news/hooks.ts +18 -25
  141. package/src/plugins/builtin/account-management/footer.ts +4 -2
  142. package/src/plugins/builtin/account-management/pane.tsx +9 -4
  143. package/src/plugins/builtin/ai/agent-history.ts +11 -7
  144. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +0 -5
  145. package/src/plugins/builtin/ai/ask-ai.test.tsx +3 -4
  146. package/src/plugins/builtin/ai/index.test.ts +2 -2
  147. package/src/plugins/builtin/ai/index.tsx +10 -11
  148. package/src/plugins/builtin/ai/pane-settings.ts +1 -1
  149. package/src/plugins/builtin/ai/pi/runtime.ts +6 -5
  150. package/src/plugins/builtin/ai/providers.ts +1 -9
  151. package/src/plugins/builtin/ai/runner.ts +0 -6
  152. package/src/plugins/builtin/ai/screener/footer.ts +4 -1
  153. package/src/plugins/builtin/ai/screener/pane.test.tsx +9 -9
  154. package/src/plugins/builtin/ai/workspace/model.ts +2 -27
  155. package/src/plugins/builtin/ai/workspace/pane.tsx +2 -2
  156. package/src/plugins/builtin/analytics/metrics.ts +1 -7
  157. package/src/plugins/builtin/broker-manager/detail.tsx +3 -2
  158. package/src/plugins/builtin/broker-manager/index.tsx +4 -2
  159. package/src/plugins/builtin/chart-composer/editor/controller.ts +643 -0
  160. package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +134 -0
  161. package/src/plugins/builtin/chart-composer/editor/model.test.ts +85 -0
  162. package/src/plugins/builtin/chart-composer/editor/model.ts +292 -0
  163. package/src/plugins/builtin/chart-composer/editor/opentui-view.tsx +108 -0
  164. package/src/plugins/builtin/chart-composer/editor.tsx +65 -947
  165. package/src/plugins/builtin/chart-composer/index.tsx +0 -6
  166. package/src/plugins/builtin/chart-composer/pane.tsx +69 -36
  167. package/src/plugins/builtin/chart-composer/presets.ts +1 -19
  168. package/src/plugins/builtin/chart-composer/settings.ts +1 -1
  169. package/src/plugins/builtin/chat/content/composer-runtime.ts +1 -1
  170. package/src/plugins/builtin/chat/content/shortcuts.ts +0 -1
  171. package/src/plugins/builtin/chat/controller/fetch.ts +8 -2
  172. package/src/plugins/builtin/chat/controller/persistence.ts +2 -0
  173. package/src/plugins/builtin/chat/controller/session.test.ts +25 -0
  174. package/src/plugins/builtin/chat/controller/session.ts +2 -0
  175. package/src/plugins/builtin/chat/controller/state.ts +7 -0
  176. package/src/plugins/builtin/chat/controller.test.ts +73 -20
  177. package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
  178. package/src/plugins/builtin/cloud-tweets/footer.ts +9 -28
  179. package/src/plugins/builtin/cloud-tweets/pane.tsx +0 -5
  180. package/src/plugins/builtin/cloud-tweets/table.tsx +12 -17
  181. package/src/plugins/builtin/correlation/compute.ts +4 -10
  182. package/src/plugins/builtin/econ/index.tsx +17 -19
  183. package/src/plugins/builtin/holders/types.ts +0 -1
  184. package/src/plugins/builtin/insider/index.tsx +6 -31
  185. package/src/plugins/builtin/news/index.tsx +5 -7
  186. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +6 -8
  187. package/src/plugins/builtin/news/wire/news/footer.ts +10 -11
  188. package/src/plugins/builtin/news/wire/read-state.test.ts +7 -0
  189. package/src/plugins/builtin/news/wire/read-state.ts +18 -28
  190. package/src/plugins/builtin/news/wire/rss/parser.ts +1 -11
  191. package/src/plugins/builtin/options/footer.test.ts +115 -0
  192. package/src/plugins/builtin/options/footer.ts +110 -0
  193. package/src/plugins/builtin/options/live-quotes.test.ts +238 -0
  194. package/src/plugins/builtin/options/live-quotes.ts +147 -0
  195. package/src/plugins/builtin/options/view.test.tsx +71 -1
  196. package/src/plugins/builtin/options/view.tsx +39 -24
  197. package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +103 -117
  198. package/src/plugins/builtin/portfolio-list/cli/render.ts +11 -5
  199. package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -135
  200. package/src/plugins/builtin/portfolio-list/header.tsx +9 -4
  201. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +6 -1
  202. package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +4 -2
  203. package/src/plugins/builtin/research/corporate-actions-pane.tsx +24 -52
  204. package/src/plugins/builtin/sec/filing-content.test.ts +41 -0
  205. package/src/plugins/builtin/sec/filing-content.ts +94 -0
  206. package/src/plugins/builtin/sec/index.tsx +21 -104
  207. package/src/plugins/builtin/sectors/sector-model.ts +1 -7
  208. package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -0
  209. package/src/plugins/builtin/shared/pane-footer.ts +82 -0
  210. package/src/plugins/builtin/shared/read-state.ts +64 -3
  211. package/src/plugins/builtin/substack/read-state.ts +16 -41
  212. package/src/plugins/builtin/thirteenf/pane.tsx +24 -73
  213. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +10 -10
  214. package/src/plugins/builtin/yield-curve/index.tsx +11 -9
  215. package/src/plugins/ibkr/dialogs.tsx +3 -3
  216. package/src/plugins/ibkr/gateway/fundamentals.ts +1 -1
  217. package/src/plugins/ibkr/gateway/history-requests.ts +2 -2
  218. package/src/plugins/ibkr/gateway/history.ts +2 -2
  219. package/src/plugins/ibkr/gateway/selection.ts +2 -2
  220. package/src/plugins/ibkr/gateway/service/index.ts +2 -2
  221. package/src/plugins/ibkr/gateway/service/native.ts +2 -2
  222. package/src/plugins/plugin-persistence.ts +3 -4
  223. package/src/plugins/prediction-markets/controller/keyboard.ts +1 -1
  224. package/src/plugins/prediction-markets/controller/live-updates.ts +6 -5
  225. package/src/plugins/prediction-markets/detail/book.tsx +2 -2
  226. package/src/plugins/prediction-markets/detail/outcomes.tsx +3 -1
  227. package/src/plugins/prediction-markets/detail/pane.tsx +3 -1
  228. package/src/plugins/prediction-markets/detail/trades.tsx +3 -3
  229. package/src/plugins/registry/context.ts +3 -4
  230. package/src/plugins/registry/index.ts +5 -6
  231. package/src/react/input.test.ts +126 -3
  232. package/src/react/input.ts +120 -1
  233. package/src/remote/app-host.tsx +2 -13
  234. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +44 -60
  235. package/src/renderers/electrobun/bun/desktop/capability-bridge.ts +15 -13
  236. package/src/renderers/electrobun/bun/desktop/host-requests.ts +26 -22
  237. package/src/renderers/electrobun/bun/desktop/http-fetch.ts +5 -1
  238. package/src/renderers/electrobun/bun/desktop/initialization.ts +11 -6
  239. package/src/renderers/electrobun/bun/desktop/media.ts +5 -3
  240. package/src/renderers/electrobun/bun/desktop/plugin-state.ts +21 -22
  241. package/src/renderers/electrobun/bun/desktop/window-controls.ts +3 -1
  242. package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +18 -18
  243. package/src/renderers/electrobun/bun/index.ts +123 -83
  244. package/src/renderers/electrobun/shared/protocol.ts +146 -7
  245. package/src/renderers/electrobun/view/app-services.ts +5 -8
  246. package/src/renderers/electrobun/view/backend-rpc.ts +18 -8
  247. package/src/renderers/electrobun/view/broker-remote-client.ts +2 -2
  248. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +5 -22
  249. package/src/renderers/electrobun/view/config-host.ts +1 -1
  250. package/src/renderers/electrobun/view/context-menu.ts +1 -17
  251. package/src/renderers/electrobun/view/data-table/index.tsx +0 -1
  252. package/src/renderers/electrobun/view/data-table/row.tsx +0 -4
  253. package/src/renderers/electrobun/view/desktop/control-styles.ts +37 -34
  254. package/src/renderers/electrobun/view/desktop/controls.tsx +36 -33
  255. package/src/renderers/electrobun/view/desktop/list-view.tsx +15 -12
  256. package/src/renderers/electrobun/view/desktop/popover.tsx +2 -3
  257. package/src/renderers/electrobun/view/dialog-host.tsx +2 -3
  258. package/src/renderers/electrobun/view/fatal-screen.tsx +0 -1
  259. package/src/renderers/electrobun/view/host/box.tsx +0 -1
  260. package/src/renderers/electrobun/view/host/chart-surface.tsx +0 -1
  261. package/src/renderers/electrobun/view/host/input.tsx +0 -1
  262. package/src/renderers/electrobun/view/host/media-surface.tsx +0 -1
  263. package/src/renderers/electrobun/view/host/native.ts +0 -1
  264. package/src/renderers/electrobun/view/host/scroll-box.tsx +0 -1
  265. package/src/renderers/electrobun/view/host/tabs.tsx +0 -1
  266. package/src/renderers/electrobun/view/host/text.tsx +0 -1
  267. package/src/renderers/electrobun/view/http-fetch.ts +3 -11
  268. package/src/renderers/electrobun/view/input-host.tsx +10 -53
  269. package/src/renderers/electrobun/view/key-event.ts +0 -1
  270. package/src/renderers/electrobun/view/main.tsx +3 -3
  271. package/src/renderers/electrobun/view/native-renderer.ts +0 -1
  272. package/src/renderers/electrobun/view/native-stubs/backend-rpc.ts +13 -1
  273. package/src/renderers/electrobun/view/native-stubs/chart/surface-manager.ts +11 -0
  274. package/src/renderers/electrobun/view/native-stubs/native-stubs.test.ts +47 -0
  275. package/src/renderers/electrobun/view/remote/asset-data-client.ts +6 -44
  276. package/src/renderers/electrobun/view/remote/persistence.ts +6 -2
  277. package/src/renderers/electrobun/view/remote/quote-subscription-registry.test.ts +86 -0
  278. package/src/renderers/electrobun/view/remote/quote-subscription-registry.ts +72 -0
  279. package/src/renderers/electrobun/view/remote/ticker-repository.ts +2 -2
  280. package/src/renderers/electrobun/view/toast-host.tsx +0 -1
  281. package/src/renderers/electrobun/view/ui-host.tsx +1 -2
  282. package/src/renderers/electrobun/view/update-host.ts +1 -1
  283. package/src/renderers/opentui/chart-surface.tsx +2 -1
  284. package/src/renderers/opentui/dialog-host.tsx +20 -4
  285. package/src/renderers/opentui/image/loader.ts +12 -2
  286. package/src/renderers/opentui/input-host.tsx +7 -49
  287. package/src/renderers/opentui/start.tsx +2 -0
  288. package/src/sources/gloomberb-cloud/history-quality.ts +44 -0
  289. package/src/sources/gloomberb-cloud/index.test.ts +173 -0
  290. package/src/sources/gloomberb-cloud/index.ts +42 -20
  291. package/src/sources/gloomberb-cloud/normalizers.ts +9 -3
  292. package/src/sources/provider-router/cache.ts +1 -1
  293. package/src/sources/provider-router/document-routes.ts +10 -5
  294. package/src/sources/provider-router/financials.test.ts +14 -0
  295. package/src/sources/provider-router/financials.ts +6 -1
  296. package/src/sources/provider-router/history.test.ts +163 -1
  297. package/src/sources/provider-router/history.ts +278 -347
  298. package/src/sources/provider-router/index.ts +2 -2
  299. package/src/sources/provider-router/routing.test.ts +39 -0
  300. package/src/sources/provider-router/routing.ts +24 -11
  301. package/src/sources/provider-router/supplemental.ts +37 -17
  302. package/src/sources/sec-edgar/content.ts +2 -12
  303. package/src/sources/sec-edgar.ts +2 -13
  304. package/src/sources/yahoo-finance/history.ts +2 -2
  305. package/src/sources/yahoo-finance/options.ts +5 -0
  306. package/src/sources/yahoo-finance.ts +2 -2
  307. package/src/state/app/bootstrap.ts +2 -2
  308. package/src/state/app/context/index.tsx +2 -2
  309. package/src/state/hooks/quote-streaming.ts +66 -1
  310. package/src/state/hooks/session-snapshot.ts +2 -2
  311. package/src/state/selectors.ts +0 -9
  312. package/src/sync/controller.test.ts +98 -0
  313. package/src/sync/controller.ts +29 -5
  314. package/src/sync/react.ts +2 -2
  315. package/src/sync/types.ts +2 -2
  316. package/src/theme/colors.test.ts +10 -0
  317. package/src/theme/colors.ts +75 -78
  318. package/src/theme/theme-context.test.tsx +67 -0
  319. package/src/theme/theme-context.tsx +9 -5
  320. package/src/tickers/open-target.ts +2 -2
  321. package/src/tickers/search/upsert.ts +2 -2
  322. package/src/time-series/alignment.ts +0 -7
  323. package/src/time-series/chart-data.ts +145 -0
  324. package/src/time-series/date-window.ts +44 -0
  325. package/src/time-series/history-window.ts +21 -0
  326. package/src/time-series/hooks.test.ts +59 -0
  327. package/src/time-series/hooks.ts +33 -12
  328. package/src/time-series/market-resolution.ts +2 -2
  329. package/src/time-series/market.ts +1 -1
  330. package/src/time-series/range.ts +9 -0
  331. package/src/{components/chart/core → time-series}/resolution.ts +3 -102
  332. package/src/time-series/resolve.test.ts +203 -1
  333. package/src/time-series/resolve.ts +96 -18
  334. package/src/time-series/spec-studies.test.ts +15 -0
  335. package/src/time-series/spec.ts +2 -2
  336. package/src/time-series/studies.ts +5 -2
  337. package/src/time-series/types.ts +3 -1
  338. package/src/time-series/use-chart-resolution.test.tsx +358 -0
  339. package/src/time-series/use-chart-resolution.ts +46 -4
  340. package/src/types/broker.ts +2 -2
  341. package/src/types/data-provider.ts +3 -3
  342. package/src/types/financials.ts +12 -0
  343. package/src/types/plugin.ts +2 -2
  344. package/src/ui/context-menu.tsx +3 -2
  345. package/src/ui/dialog.tsx +2 -2
  346. package/src/utils/html-entities.ts +3 -5
  347. package/src/utils/main-thread-monitor.ts +1 -1
  348. package/src/utils/price-history.ts +5 -5
  349. package/src/version.ts +3 -1
  350. package/src/components/chart/core/viewport.test.ts +0 -8
  351. package/src/components/chart/core/viewport.ts +0 -24
  352. package/src/time-series/index.ts +0 -14
  353. /package/src/{components/chart/core → time-series}/resolution.test.ts +0 -0
@@ -7,6 +7,7 @@ import { createAlert, deserializeAlerts, serializeAlerts } from "../../plugins/b
7
7
  import type { AlertCondition } from "../../plugins/builtin/alerts/types";
8
8
  import type { CliCommandDef } from "../../types/plugin";
9
9
  import { debugLog, type LogLevel } from "../../utils/debug-log";
10
+ import { withCliServices, withConfigData } from "../context";
10
11
  import { parsePositiveInt, requireArg, takeOption } from "./command-utils";
11
12
 
12
13
  const ALERTS_PLUGIN_ID = "alerts";
@@ -51,9 +52,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
51
52
  execute: async (_args, ctx) => {
52
53
  let dataDir: string | null = null;
53
54
  try {
54
- const config = await ctx.initConfigData();
55
- dataDir = config.dataDir;
56
- config.persistence.close();
55
+ dataDir = await withConfigData(ctx, ({ dataDir: configuredDataDir }) => configuredDataDir);
57
56
  } catch {
58
57
  dataDir = null;
59
58
  }
@@ -74,17 +73,15 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
74
73
  description: "Check local CLI runtime, config, plugins, and capability health",
75
74
  execute: async (_args, ctx) => {
76
75
  const checks: Array<{ check: string; status: string; detail: string }> = [];
77
- let services: Awaited<ReturnType<typeof ctx.initServices>> | null = null;
78
76
  try {
79
- services = await ctx.initServices();
80
- checks.push({ check: "config", status: "ok", detail: services.dataDir });
81
- checks.push({ check: "database", status: "ok", detail: join(services.dataDir, ".gloomberb-cache.db") });
82
- checks.push({ check: "plugins", status: "ok", detail: String(services.services.pluginRegistry.allPlugins.size) });
83
- checks.push({ check: "capabilities", status: "ok", detail: String(services.services.pluginRegistry.capabilities.manifests().length) });
77
+ await withCliServices(ctx, async (services) => {
78
+ checks.push({ check: "config", status: "ok", detail: services.dataDir });
79
+ checks.push({ check: "database", status: "ok", detail: join(services.dataDir, ".gloomberb-cache.db") });
80
+ checks.push({ check: "plugins", status: "ok", detail: String(services.services.pluginRegistry.allPlugins.size) });
81
+ checks.push({ check: "capabilities", status: "ok", detail: String(services.services.pluginRegistry.capabilities.manifests().length) });
82
+ });
84
83
  } catch (error) {
85
84
  checks.push({ check: "runtime", status: "error", detail: error instanceof Error ? error.message : String(error) });
86
- } finally {
87
- services?.destroy();
88
85
  }
89
86
  ctx.printResult({ data: checks }, {
90
87
  columns: [
@@ -102,8 +99,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
102
99
  help: { usage: ["config list", "config get <key>", "config set <key> <value>"] },
103
100
  execute: async (args, ctx) => {
104
101
  const action = args[0] ?? "list";
105
- const context = await ctx.initConfigData();
106
- try {
102
+ await withConfigData(ctx, async (context) => {
107
103
  const safeConfig: Record<string, unknown> = {
108
104
  dataDir: context.config.dataDir,
109
105
  baseCurrency: context.config.baseCurrency,
@@ -146,9 +142,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
146
142
  return;
147
143
  }
148
144
  ctx.fail("Usage: gloomberb config list|get|set");
149
- } finally {
150
- context.persistence.close();
151
- }
145
+ });
152
146
  },
153
147
  };
154
148
 
@@ -158,8 +152,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
158
152
  help: { usage: ["cache status", "cache clear [namespace]"] },
159
153
  execute: async (args, ctx) => {
160
154
  const action = args[0] ?? "status";
161
- const services = await ctx.initServices();
162
- try {
155
+ await withCliServices(ctx, async (services) => {
163
156
  if (action === "status") {
164
157
  ctx.printResult({ data: [resourceCacheStats(services)] });
165
158
  return;
@@ -173,9 +166,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
173
166
  return;
174
167
  }
175
168
  ctx.fail("Usage: gloomberb cache status|clear");
176
- } finally {
177
- services.destroy();
178
- }
169
+ });
179
170
  },
180
171
  };
181
172
 
@@ -185,8 +176,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
185
176
  description: "Inspect provider/source capability status",
186
177
  help: { usage: ["provider status"] },
187
178
  execute: async (_args, ctx) => {
188
- const services = await ctx.initServices();
189
- try {
179
+ await withCliServices(ctx, async (services) => {
190
180
  const disabledSources = new Set(services.config.disabledSources ?? []);
191
181
  const rows = services.services.pluginRegistry.capabilities.manifests().map((manifest) => ({
192
182
  id: manifest.sourceId ?? manifest.id,
@@ -203,9 +193,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
203
193
  { key: "operations", header: "Operations" },
204
194
  ],
205
195
  });
206
- } finally {
207
- services.destroy();
208
- }
196
+ });
209
197
  },
210
198
  };
211
199
 
@@ -215,8 +203,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
215
203
  help: { usage: ["plugin list", "plugin info <id>", "plugin enable <id>", "plugin disable <id>", "plugin doctor"] },
216
204
  execute: async (args, ctx) => {
217
205
  const action = args[0] ?? "list";
218
- const services = await ctx.initServices();
219
- try {
206
+ await withCliServices(ctx, async (services) => {
220
207
  const pluginRows = () => [...services.services.pluginRegistry.allPlugins.values()].map((plugin) => ({
221
208
  id: plugin.id,
222
209
  name: plugin.name,
@@ -255,9 +242,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
255
242
  return;
256
243
  }
257
244
  ctx.fail("Usage: gloomberb plugin list|info|enable|disable|doctor");
258
- } finally {
259
- services.destroy();
260
- }
245
+ });
261
246
  },
262
247
  };
263
248
 
@@ -265,8 +250,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
265
250
  name: "layout",
266
251
  description: "Inspect saved layouts",
267
252
  execute: async (_args, ctx) => {
268
- const config = await ctx.initConfigData();
269
- try {
253
+ await withConfigData(ctx, async (config) => {
270
254
  const rows = config.config.layouts.map((layout, index) => ({
271
255
  index,
272
256
  active: index === config.config.activeLayoutIndex,
@@ -276,9 +260,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
276
260
  detached: layout.layout.detached.length,
277
261
  }));
278
262
  ctx.printResult({ data: rows });
279
- } finally {
280
- config.persistence.close();
281
- }
263
+ });
282
264
  },
283
265
  };
284
266
 
@@ -287,8 +269,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
287
269
  description: "Inspect pane and pane-template inventory",
288
270
  help: { usage: ["pane list"] },
289
271
  execute: async (_args, ctx) => {
290
- const services = await ctx.initServices();
291
- try {
272
+ await withCliServices(ctx, async (services) => {
292
273
  const rows = [
293
274
  ...[...services.services.pluginRegistry.panes.entries()].map(([id, pane]) => ({
294
275
  kind: "pane",
@@ -311,9 +292,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
311
292
  { key: "owner", header: "Owner" },
312
293
  ],
313
294
  });
314
- } finally {
315
- services.destroy();
316
- }
295
+ });
317
296
  },
318
297
  };
319
298
 
@@ -322,9 +301,8 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
322
301
  description: "Read and mutate ticker or quick notes",
323
302
  help: { usage: ["notes show <symbol>", "notes set <symbol> TEXT", "notes delete <symbol>", "notes quick list"] },
324
303
  execute: async (args, ctx) => {
325
- const config = await ctx.initConfigData();
326
- const notes = new NotesFiles(config.dataDir);
327
- try {
304
+ await withConfigData(ctx, async (config) => {
305
+ const notes = new NotesFiles(config.dataDir);
328
306
  const action = args[0] ?? "list";
329
307
  if (action === "quick") {
330
308
  const subaction = args[1] ?? "list";
@@ -352,9 +330,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
352
330
  return;
353
331
  }
354
332
  ctx.fail("Usage: gloomberb notes show|set|delete|quick");
355
- } finally {
356
- config.persistence.close();
357
- }
333
+ });
358
334
  },
359
335
  };
360
336
 
@@ -365,8 +341,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
365
341
  help: { usage: ["alerts list", "alerts add <symbol> <above|below|crosses> <price>", "alerts delete <id>", "alerts rearm <id>"] },
366
342
  execute: async (args, ctx) => {
367
343
  const action = args[0] ?? "list";
368
- const config = await ctx.initConfigData();
369
- try {
344
+ await withConfigData(ctx, async (config) => {
370
345
  const raw = config.config.pluginConfig[ALERTS_PLUGIN_ID]?.[ALERTS_KEY];
371
346
  const alerts = deserializeAlerts(typeof raw === "string" ? raw : "[]");
372
347
  const saveAlerts = async (nextAlerts: typeof alerts) => {
@@ -413,9 +388,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
413
388
  return;
414
389
  }
415
390
  ctx.fail("Usage: gloomberb alerts list|add|delete|rearm");
416
- } finally {
417
- config.persistence.close();
418
- }
391
+ });
419
392
  },
420
393
  };
421
394
 
@@ -485,8 +458,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
485
458
  description: "Show CLI coverage for panes, templates, capabilities, and deferred exceptions",
486
459
  execute: async (_args, ctx) => {
487
460
  const commandNames = new Set(allCommands().map((command) => command.name));
488
- const services = await ctx.initServices();
489
- try {
461
+ await withCliServices(ctx, async (services) => {
490
462
  const rows = [
491
463
  ...[...services.services.pluginRegistry.panes.entries()].map(([id, pane]) => ({
492
464
  surface: "pane",
@@ -531,9 +503,7 @@ export function createSystemCliCommands(allCommands: () => CliCommandDef[]): Cli
531
503
  { key: "label", header: "Label" },
532
504
  ],
533
505
  });
534
- } finally {
535
- services.destroy();
536
- }
506
+ });
537
507
  },
538
508
  };
539
509
 
@@ -19,8 +19,8 @@ import type { NewsArticle } from "../../news/types";
19
19
  import type { TickerRecord } from "../../types/ticker";
20
20
  import type { CliCommandContext } from "../../types/plugin";
21
21
  import { createBaseConverter } from "../base-converter";
22
- import { initMarketData } from "../context";
23
- import { closeAndFail } from "../errors";
22
+ import { initMarketData, withMarketData } from "../context";
23
+ import { fail } from "../errors";
24
24
  import type { MarketContext } from "../types";
25
25
  import {
26
26
  formatBidAsk,
@@ -40,7 +40,7 @@ const SEC_FILING_LIMIT = 5;
40
40
 
41
41
  interface TickerCommandDependencies {
42
42
  initMarketData?: () => Promise<MarketContext>;
43
- closeAndFail?: typeof closeAndFail;
43
+ fail?: (message: string, details?: string) => never;
44
44
  printResult?: CliCommandContext["printResult"];
45
45
  }
46
46
 
@@ -443,74 +443,73 @@ function buildTickerStructuredData({
443
443
 
444
444
  export async function ticker(symbol: string, dependencies: TickerCommandDependencies = {}) {
445
445
  const initMarketDataFn = dependencies.initMarketData ?? initMarketData;
446
- const closeAndFailCommand = dependencies.closeAndFail ?? closeAndFail;
447
- const { config, store, dataProvider, persistence, dataDir } = await initMarketDataFn();
448
- const normalized = symbol.trim().toUpperCase();
449
- const tickerFile = await store.loadTicker(normalized);
450
- const exchange = tickerFile?.metadata.exchange ?? "";
451
- const toBase = createBaseConverter(dataProvider, config.baseCurrency);
452
-
453
- let financials: TickerFinancials | null = null;
454
- try {
455
- financials = await dataProvider.getTickerFinancials(normalized, exchange);
456
- } catch (error) {
457
- closeAndFailCommand(
458
- persistence,
459
- `Failed to fetch data for ${normalized}.`,
460
- error instanceof Error ? error.message : String(error),
461
- );
462
- }
463
-
464
- if (!financials?.quote) {
465
- closeAndFailCommand(persistence, `No quote data available for ${normalized}.`);
466
- }
467
- const resolvedFinancials = financials as TickerFinancials;
468
- const quote = resolvedFinancials.quote!;
469
-
470
- const notesFiles = new NotesFiles(dataDir);
471
- const [notesResult, newsResult, secFilingsResult] = await Promise.allSettled([
472
- notesFiles.load(normalized),
473
- dataProvider.getNews({
474
- feed: "ticker",
475
- scope: "ticker",
476
- ticker: normalized,
477
- exchange: exchange || quote.exchangeName || "",
478
- tickerTier: "primary",
479
- limit: NEWS_ITEM_LIMIT,
480
- }),
481
- shouldFetchSecFilings(tickerFile, resolvedFinancials) && dataProvider.getSecFilings
482
- ? dataProvider.getSecFilings(normalized, SEC_FILING_LIMIT, exchange || quote.exchangeName || "")
483
- : Promise.resolve([]),
484
- ]);
446
+ const failCommand = dependencies.fail ?? fail;
447
+ await withMarketData(initMarketDataFn, async ({ config, store, dataProvider, dataDir }) => {
448
+ const normalized = symbol.trim().toUpperCase();
449
+ const tickerFile = await store.loadTicker(normalized);
450
+ const exchange = tickerFile?.metadata.exchange ?? "";
451
+ const toBase = createBaseConverter(dataProvider, config.baseCurrency);
452
+
453
+ let financials: TickerFinancials | null = null;
454
+ try {
455
+ financials = await dataProvider.getTickerFinancials(normalized, exchange);
456
+ } catch (error) {
457
+ failCommand(
458
+ `Failed to fetch data for ${normalized}.`,
459
+ error instanceof Error ? error.message : String(error),
460
+ );
461
+ }
485
462
 
486
- const notes = notesResult.status === "fulfilled" ? notesResult.value : "";
487
- const recentNews = newsResult.status === "fulfilled" ? newsResult.value : [];
488
- const recentSecFilings = secFilingsResult.status === "fulfilled" ? secFilingsResult.value : [];
463
+ if (!financials?.quote) {
464
+ failCommand(`No quote data available for ${normalized}.`);
465
+ }
466
+ const resolvedFinancials = financials as TickerFinancials;
467
+ const quote = resolvedFinancials.quote!;
468
+
469
+ const notesFiles = new NotesFiles(dataDir);
470
+ const [notesResult, newsResult, secFilingsResult] = await Promise.allSettled([
471
+ notesFiles.load(normalized),
472
+ dataProvider.getNews({
473
+ feed: "ticker",
474
+ scope: "ticker",
475
+ ticker: normalized,
476
+ exchange: exchange || quote.exchangeName || "",
477
+ tickerTier: "primary",
478
+ limit: NEWS_ITEM_LIMIT,
479
+ }),
480
+ shouldFetchSecFilings(tickerFile, resolvedFinancials) && dataProvider.getSecFilings
481
+ ? dataProvider.getSecFilings(normalized, SEC_FILING_LIMIT, exchange || quote.exchangeName || "")
482
+ : Promise.resolve([]),
483
+ ]);
484
+
485
+ const notes = notesResult.status === "fulfilled" ? notesResult.value : "";
486
+ const recentNews = newsResult.status === "fulfilled" ? newsResult.value : [];
487
+ const recentSecFilings = secFilingsResult.status === "fulfilled" ? secFilingsResult.value : [];
488
+
489
+ if (dependencies.printResult) {
490
+ dependencies.printResult({
491
+ data: buildTickerStructuredData({
492
+ symbol: normalized,
493
+ tickerFile,
494
+ financials: resolvedFinancials,
495
+ config,
496
+ notes,
497
+ recentNews,
498
+ recentSecFilings,
499
+ }),
500
+ });
501
+ return;
502
+ }
489
503
 
490
- if (dependencies.printResult) {
491
- dependencies.printResult({
492
- data: buildTickerStructuredData({
504
+ console.log(await buildTickerReport({
493
505
  symbol: normalized,
494
506
  tickerFile,
495
507
  financials: resolvedFinancials,
496
508
  config,
509
+ toBase,
497
510
  notes,
498
511
  recentNews,
499
512
  recentSecFilings,
500
- }),
501
- });
502
- } else {
503
- console.log(await buildTickerReport({
504
- symbol: normalized,
505
- tickerFile,
506
- financials: resolvedFinancials,
507
- config,
508
- toBase,
509
- notes,
510
- recentNews,
511
- recentSecFilings,
512
513
  }));
513
- }
514
-
515
- persistence.close();
514
+ });
516
515
  }
@@ -0,0 +1,63 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { CliCommandContext } from "../types/plugin";
3
+ import type { CliServicesContext, ConfigContext, MarketContext } from "./types";
4
+ import {
5
+ ensureCliServicesReady,
6
+ withCliServices,
7
+ withConfigData,
8
+ withMarketData,
9
+ } from "./context";
10
+
11
+ function initializer<K extends "initConfigData" | "initMarketData" | "initServices", T>(
12
+ key: K,
13
+ value: T,
14
+ ): Pick<CliCommandContext, K> {
15
+ return { [key]: async () => value } as Pick<CliCommandContext, K>;
16
+ }
17
+
18
+ describe("CLI scoped contexts", () => {
19
+ test("closes config and market persistence after successful operations", async () => {
20
+ let configCloses = 0;
21
+ let marketCloses = 0;
22
+ const config = { persistence: { close: () => configCloses++ } } as unknown as ConfigContext;
23
+ const market = { persistence: { close: () => marketCloses++ } } as unknown as MarketContext;
24
+
25
+ expect(await withConfigData(initializer("initConfigData", config), async () => "config"))
26
+ .toBe("config");
27
+ expect(await withMarketData(initializer("initMarketData", market), async () => "market"))
28
+ .toBe("market");
29
+ expect(configCloses).toBe(1);
30
+ expect(marketCloses).toBe(1);
31
+ });
32
+
33
+ test("destroys services when an operation throws", async () => {
34
+ let destroys = 0;
35
+ const services = { destroy: () => destroys++ } as unknown as CliServicesContext;
36
+
37
+ await expect(withCliServices(initializer("initServices", services), async () => {
38
+ throw new Error("operation failed");
39
+ })).rejects.toThrow("operation failed");
40
+ expect(destroys).toBe(1);
41
+ });
42
+
43
+ test("accepts an injected market initializer and closes it when an operation throws", async () => {
44
+ let closes = 0;
45
+ const market = { persistence: { close: () => closes++ } } as unknown as MarketContext;
46
+
47
+ await expect(withMarketData(async () => market, async () => {
48
+ throw new Error("market operation failed");
49
+ })).rejects.toThrow("market operation failed");
50
+ expect(closes).toBe(1);
51
+ });
52
+
53
+ test("destroys partially initialized services when plugin setup rejects", async () => {
54
+ let destroys = 0;
55
+ const services = {
56
+ ready: Promise.reject(new Error("plugin setup failed")),
57
+ destroy: () => destroys++,
58
+ };
59
+
60
+ await expect(ensureCliServicesReady(services)).rejects.toThrow("plugin setup failed");
61
+ expect(destroys).toBe(1);
62
+ });
63
+ });
@@ -14,6 +14,8 @@ import type { LoadedExternalPlugin } from "../plugins/loader";
14
14
  import { fail } from "./errors";
15
15
  import type { ConfigContext, MarketContext } from "./types";
16
16
 
17
+ export { withCliServices, withConfigData, withMarketData } from "./scoped-context";
18
+
17
19
  interface CliContextOptions {
18
20
  plugins?: GloomPlugin[];
19
21
  }
@@ -22,6 +24,18 @@ interface CliServicesOptions {
22
24
  externalPlugins?: LoadedExternalPlugin[];
23
25
  }
24
26
 
27
+ export async function ensureCliServicesReady<T extends { ready: Promise<unknown>; destroy(): void }>(
28
+ services: T,
29
+ ): Promise<T> {
30
+ try {
31
+ await services.ready;
32
+ return services;
33
+ } catch (error) {
34
+ services.destroy();
35
+ throw error;
36
+ }
37
+ }
38
+
25
39
  function resolveCliCapabilities(config: AppConfig, plugins: GloomPlugin[]): PluginCapability[] {
26
40
  const disabledPlugins = new Set(config.disabledPlugins ?? []);
27
41
  const disabledSources = new Set(config.disabledSources ?? []);
@@ -83,7 +97,7 @@ export async function initCliServices(options: CliServicesOptions = {}) {
83
97
  plugins: getLoadablePlugins(options.externalPlugins ?? []),
84
98
  });
85
99
  services.providerRouter.setConfigAccessor(() => config);
86
- await services.ready;
100
+ await ensureCliServicesReady(services);
87
101
  return {
88
102
  config,
89
103
  dataDir,
package/src/cli/index.ts CHANGED
@@ -98,7 +98,7 @@ function createCoreCliCommands(renderHelp: () => string): CliCommandDef[] {
98
98
  }
99
99
  await ticker(symbol!, {
100
100
  initMarketData: ctx.initMarketData,
101
- closeAndFail: ctx.closeAndFail,
101
+ fail: ctx.fail,
102
102
  ...(ctx.cliOptions.format === "text" ? {} : { printResult: ctx.printResult }),
103
103
  });
104
104
  },
@@ -1,7 +1,6 @@
1
- import type { PricePoint, TickerFinancials } from "../../types/financials";
1
+ import type { TickerFinancials } from "../../types/financials";
2
2
  import type { TickerRecord } from "../../types/ticker";
3
- import type { TimeRange } from "../../components/chart/core/types";
4
- import { subtractTimeRange } from "../../components/chart/core/date-window";
3
+ export { clipPriceHistoryToRange } from "../../time-series/history-window";
5
4
  import { CHART_COMPOSER_PANE_ID, LEGACY_TICKER_DETAIL_PANE_ID } from "../../types/config";
6
5
  import { parseChartSpec } from "../../plugins/builtin/chart-composer/chart-spec";
7
6
  import { normalizeTickerInput } from "../../tickers/search";
@@ -102,19 +101,3 @@ export function collectShotSymbols(resolved: ResolvedPaneFunction, rawArg: strin
102
101
  }
103
102
  return [...new Set(symbols.map(cleanTickerInput).filter(Boolean))];
104
103
  }
105
-
106
- export function clipPriceHistoryToRange(points: PricePoint[], range: TimeRange): PricePoint[] {
107
- if (range === "ALL" || points.length === 0) return points;
108
- const dated = points.flatMap((point) => {
109
- const date = point.date instanceof Date ? point.date : new Date(point.date);
110
- return Number.isFinite(date.getTime()) ? [{ point, date }] : [];
111
- });
112
- const end = dated.reduce<Date | null>((latest, entry) => (
113
- !latest || entry.date.getTime() > latest.getTime() ? entry.date : latest
114
- ), null);
115
- if (!end) return [];
116
- const start = subtractTimeRange(end, range);
117
- return dated
118
- .filter(({ date }) => date.getTime() >= start.getTime() && date.getTime() <= end.getTime())
119
- .map(({ point }) => point);
120
- }
@@ -1,6 +1,7 @@
1
1
  import { extname, resolve } from "path";
2
2
  import type { CliCommandContext } from "../../types/plugin";
3
3
  import type { MarketContext } from "../types";
4
+ import { withMarketData } from "../context";
4
5
  import {
5
6
  filterPaneCatalogEntries,
6
7
  renderPaneCatalogReport,
@@ -38,15 +39,15 @@ async function withPaneRuntime<T>(
38
39
  }) => Promise<T>,
39
40
  ): Promise<T> {
40
41
  const parsed = parsePaneFunctionArgs(args);
41
- const context = await ctx.initMarketData();
42
- const registry = await createPaneCatalog(context, ctx.plugins);
43
- try {
44
- const resolved = await resolvePaneFunction(registry, context, parsed);
45
- return await run({ parsed, context, registry, resolved });
46
- } finally {
47
- registry.destroy();
48
- context.persistence.close();
49
- }
42
+ return withMarketData(ctx, async (context) => {
43
+ const registry = await createPaneCatalog(context, ctx.plugins);
44
+ try {
45
+ const resolved = await resolvePaneFunction(registry, context, parsed);
46
+ return await run({ parsed, context, registry, resolved });
47
+ } finally {
48
+ registry.destroy();
49
+ }
50
+ });
50
51
  }
51
52
 
52
53
  export async function runPaneFunction(args: string[], ctx: CliCommandContext) {
@@ -133,23 +134,23 @@ export async function runPaneCatalog(args: string[], ctx: CliCommandContext) {
133
134
  ...parsed,
134
135
  limit: ctx.cliOptions.limit ?? parsed.limit,
135
136
  };
136
- const context = await ctx.initMarketData();
137
- const registry = await createPaneCatalog(context, ctx.plugins);
138
- try {
139
- const entries = await buildPaneCatalogEntries(registry, context);
140
- const botSafeEntries = effectiveParsed.botSafeOnly
141
- ? entries.filter((entry) => entry.capability.botSafe)
142
- : entries;
143
- const filtered = filterPaneCatalogEntries(botSafeEntries, parsed.query);
144
- if (ctx.cliOptions.format === "text") {
145
- console.log(renderPaneCatalogReport(filtered, effectiveParsed));
146
- } else {
147
- ctx.printResult({ data: filtered.slice(0, effectiveParsed.limit) });
137
+ await withMarketData(ctx, async (context) => {
138
+ const registry = await createPaneCatalog(context, ctx.plugins);
139
+ try {
140
+ const entries = await buildPaneCatalogEntries(registry, context);
141
+ const botSafeEntries = effectiveParsed.botSafeOnly
142
+ ? entries.filter((entry) => entry.capability.botSafe)
143
+ : entries;
144
+ const filtered = filterPaneCatalogEntries(botSafeEntries, parsed.query);
145
+ if (ctx.cliOptions.format === "text") {
146
+ console.log(renderPaneCatalogReport(filtered, effectiveParsed));
147
+ } else {
148
+ ctx.printResult({ data: filtered.slice(0, effectiveParsed.limit) });
149
+ }
150
+ } finally {
151
+ registry.destroy();
148
152
  }
149
- } finally {
150
- registry.destroy();
151
- context.persistence.close();
152
- }
153
+ });
153
154
  });
154
155
  }
155
156
 
@@ -1,4 +1,4 @@
1
- import type { TimeRange } from "../../components/chart/core/types";
1
+ import type { TimeRange } from "../../time-series/range";
2
2
  import {
3
3
  buildCorrelationMatrix,
4
4
  pairKey,
@@ -29,12 +29,12 @@ import type {
29
29
  GraphKind,
30
30
  GraphMetricKey,
31
31
  } from "../../time-series/reporting";
32
- import type { TimeRange } from "../../components/chart/core/types";
33
- import { appendLiveQuotePoint } from "../../components/chart/core/data";
32
+ import type { TimeRange } from "../../time-series/range";
33
+ import { appendLiveQuotePoint } from "../../time-series/chart-data";
34
+ import { subtractTimeRange } from "../../time-series/date-window";
34
35
  import {
35
36
  buildPresetDateWindow,
36
37
  getVisibleWindowForDateRange,
37
- subtractTimeRange,
38
38
  } from "../../components/chart/core/date-window";
39
39
  import { parseChartSpec } from "../../plugins/builtin/chart-composer/chart-spec";
40
40
  import { publicTickerKey } from "../../utils/exchanges";
@@ -0,0 +1,41 @@
1
+ import type { CliCommandContext } from "../types/plugin";
2
+ import type { CliServicesContext, ConfigContext, MarketContext } from "./types";
3
+
4
+ type AsyncInitializer<T> = () => Promise<T>;
5
+
6
+ async function withCleanup<TContext, TResult>(
7
+ initialize: AsyncInitializer<TContext>,
8
+ cleanup: (context: TContext) => void,
9
+ operation: (context: TContext) => TResult | Promise<TResult>,
10
+ ): Promise<TResult> {
11
+ const context = await initialize();
12
+ try {
13
+ return await operation(context);
14
+ } finally {
15
+ cleanup(context);
16
+ }
17
+ }
18
+
19
+ export async function withConfigData<TResult>(
20
+ source: Pick<CliCommandContext, "initConfigData"> | AsyncInitializer<ConfigContext>,
21
+ operation: (context: ConfigContext) => TResult | Promise<TResult>,
22
+ ): Promise<TResult> {
23
+ const initialize = typeof source === "function" ? source : () => source.initConfigData();
24
+ return withCleanup(initialize, (context) => context.persistence.close(), operation);
25
+ }
26
+
27
+ export async function withMarketData<TResult>(
28
+ source: Pick<CliCommandContext, "initMarketData"> | AsyncInitializer<MarketContext>,
29
+ operation: (context: MarketContext) => TResult | Promise<TResult>,
30
+ ): Promise<TResult> {
31
+ const initialize = typeof source === "function" ? source : () => source.initMarketData();
32
+ return withCleanup(initialize, (context) => context.persistence.close(), operation);
33
+ }
34
+
35
+ export async function withCliServices<TResult>(
36
+ source: Pick<CliCommandContext, "initServices"> | AsyncInitializer<CliServicesContext>,
37
+ operation: (context: CliServicesContext) => TResult | Promise<TResult>,
38
+ ): Promise<TResult> {
39
+ const initialize = typeof source === "function" ? source : () => source.initServices();
40
+ return withCleanup(initialize, (context) => context.destroy(), operation);
41
+ }