gloomberb 0.9.9 → 0.10.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 (519) hide show
  1. package/README.md +41 -3
  2. package/README.zh-CN.md +294 -0
  3. package/package.json +10 -4
  4. package/patches/@opentui%2Fcore@0.3.2.patch +26 -69
  5. package/src/api-client/auth.ts +2 -0
  6. package/src/api-client/index.test.ts +4 -0
  7. package/src/api-client/types.ts +3 -0
  8. package/src/app/pane-runtime/pane-settings-runtime.tsx +1 -0
  9. package/src/app/pane-template-dialog-wizard.test.tsx +36 -0
  10. package/src/app/pane-template-dialog-wizard.tsx +10 -3
  11. package/src/app/runtime/desktop-deeplink.test.ts +15 -0
  12. package/src/app/runtime/desktop-deeplink.ts +21 -6
  13. package/src/app.tsx +40 -33
  14. package/src/capabilities/service-capabilities.ts +7 -2
  15. package/src/cli/commands/automation.test.ts +202 -0
  16. package/src/cli/commands/automation.ts +153 -36
  17. package/src/cli/commands/market.ts +1 -14
  18. package/src/cli/commands/system.ts +4 -1
  19. package/src/cli/context.ts +1 -1
  20. package/src/cli/desktop-pane-shot.ts +65 -4
  21. package/src/cli/entry.ts +1 -1
  22. package/src/cli/pane-functions/capabilities.ts +532 -0
  23. package/src/cli/pane-functions/catalog.ts +39 -4
  24. package/src/cli/pane-functions/data.test.ts +17 -0
  25. package/src/cli/pane-functions/data.ts +55 -13
  26. package/src/cli/pane-functions/discovery.ts +36 -9
  27. package/src/cli/pane-functions/index.test.ts +104 -1
  28. package/src/cli/pane-functions/index.ts +64 -5
  29. package/src/cli/pane-functions/options.ts +21 -6
  30. package/src/cli/pane-functions/report.ts +735 -72
  31. package/src/cli/pane-functions/resolver.ts +70 -6
  32. package/src/cli/pane-functions/screenshot.test.ts +431 -0
  33. package/src/cli/pane-functions/screenshot.ts +952 -8
  34. package/src/components/chart/composite/column-layout.ts +215 -0
  35. package/src/components/chart/composite/composite-chart.test.tsx +630 -0
  36. package/src/components/chart/composite/composite-chart.tsx +1148 -0
  37. package/src/components/chart/composite/format.test.ts +137 -0
  38. package/src/components/chart/composite/format.ts +135 -0
  39. package/src/components/chart/composite/index.ts +38 -0
  40. package/src/components/chart/composite/interactions.test.ts +109 -0
  41. package/src/components/chart/composite/interactions.ts +247 -0
  42. package/src/components/chart/composite/price-series.test.ts +39 -0
  43. package/src/components/chart/composite/price-series.ts +78 -0
  44. package/src/components/chart/composite/rasterizer.ts +359 -0
  45. package/src/components/chart/composite/renderers.test.ts +610 -0
  46. package/src/components/chart/composite/scene.test.ts +464 -0
  47. package/src/components/chart/composite/scene.ts +553 -0
  48. package/src/components/chart/composite/text-renderer.ts +285 -0
  49. package/src/components/chart/composite/time-scale.test.ts +88 -0
  50. package/src/components/chart/composite/time-scale.ts +217 -0
  51. package/src/components/chart/composite/types.ts +141 -0
  52. package/src/components/chart/core/data.ts +81 -5
  53. package/src/components/chart/core/index.test.ts +128 -43
  54. package/src/components/chart/core/resolution.ts +0 -6
  55. package/src/components/chart/native/chart-rasterizer.ts +1 -3
  56. package/src/components/chart/native/kitty/index.test.ts +99 -120
  57. package/src/components/chart/native/kitty/manager.ts +3 -0
  58. package/src/components/chart/native/kitty/protocol.ts +4 -1
  59. package/src/components/chart/native/raster/crosshair.ts +79 -46
  60. package/src/components/chart/native/raster/types.ts +2 -11
  61. package/src/components/chart/native/surface/manager.ts +48 -3
  62. package/src/components/chart/price-axis-labels.tsx +18 -4
  63. package/src/components/command-bar/commands/direct/index.ts +25 -0
  64. package/src/components/command-bar/commands/registry.ts +9 -0
  65. package/src/components/command-bar/helpers.test.ts +4 -1
  66. package/src/components/command-bar/list/view.tsx +18 -6
  67. package/src/components/command-bar/multi-select-picker.tsx +6 -5
  68. package/src/components/command-bar/pane-settings.test.ts +141 -0
  69. package/src/components/command-bar/pane-settings.ts +61 -1
  70. package/src/components/command-bar/panel/body.tsx +7 -6
  71. package/src/components/command-bar/routes/root/command-items.ts +4 -4
  72. package/src/components/command-bar/routes/root/results.ts +10 -1
  73. package/src/components/command-bar/routes/root/selection.test.ts +54 -0
  74. package/src/components/command-bar/routes/root/selection.ts +13 -0
  75. package/src/components/command-bar/routing/actions.ts +18 -2
  76. package/src/components/command-bar/routing/list-state.ts +2 -1
  77. package/src/components/command-bar/surface/index.test.tsx +36 -4
  78. package/src/components/command-bar/surface/pane.test.tsx +93 -0
  79. package/src/components/command-bar/theme-picker.tsx +2 -1
  80. package/src/components/command-bar/view-model.ts +19 -20
  81. package/src/components/command-bar/workflow/body.tsx +2 -1
  82. package/src/components/command-bar/workflow/field-row.tsx +3 -2
  83. package/src/components/command-bar/workflow/fields.ts +4 -2
  84. package/src/components/command-bar/workflow/ops.test.ts +161 -0
  85. package/src/components/command-bar/workflow/ops.ts +20 -50
  86. package/src/components/command-bar/workflow/route-actions.test.ts +34 -0
  87. package/src/components/command-bar/workflow/route-actions.ts +4 -0
  88. package/src/components/command-bar/workflow/types.ts +1 -0
  89. package/src/components/feed-data-table/stack-view.tsx +6 -5
  90. package/src/components/index.ts +13 -1
  91. package/src/components/layout/detached-pane-shell.tsx +2 -1
  92. package/src/components/layout/floating-pane.tsx +3 -0
  93. package/src/components/layout/header.tsx +15 -8
  94. package/src/components/layout/pane/footer/index.test.tsx +22 -3
  95. package/src/components/layout/pane/footer/index.tsx +3 -1
  96. package/src/components/layout/pane/footer/model.ts +9 -2
  97. package/src/components/layout/pane/header.tsx +4 -6
  98. package/src/components/layout/pane/index.tsx +3 -0
  99. package/src/components/layout/pane/sidebar.test.tsx +100 -0
  100. package/src/components/layout/pane/sidebar.tsx +239 -0
  101. package/src/components/layout/pane/title.ts +4 -3
  102. package/src/components/layout/shell/action-menu-overlay.tsx +5 -3
  103. package/src/components/layout/shell/drag/runtime.ts +2 -0
  104. package/src/components/layout/shell/menu.test.ts +19 -0
  105. package/src/components/layout/shell/menu.ts +8 -5
  106. package/src/components/layout/shell/native/pointer-runtime.ts +4 -1
  107. package/src/components/layout/shell/pane/layers.tsx +2 -2
  108. package/src/components/layout/status-bar.tsx +8 -7
  109. package/src/components/layout/window-controls.tsx +4 -3
  110. package/src/components/metric-treemap/index.tsx +3 -2
  111. package/src/components/onboarding/onboarding-steps.tsx +30 -29
  112. package/src/components/onboarding/onboarding-wizard.tsx +5 -4
  113. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +15 -12
  114. package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +17 -16
  115. package/src/components/onboarding/portfolio-step/broker-sync-panel.tsx +6 -5
  116. package/src/components/onboarding/portfolio-step/choose-panel.tsx +5 -4
  117. package/src/components/pane-settings-dialog/desktop.tsx +25 -3
  118. package/src/components/pane-settings-dialog/field-dialogs.tsx +5 -4
  119. package/src/components/pane-settings-dialog/tui.tsx +11 -9
  120. package/src/components/pane-settings-dialog/value.ts +8 -5
  121. package/src/components/pane-settings-dialog.test.tsx +235 -0
  122. package/src/components/pane-settings-dialog.tsx +24 -0
  123. package/src/components/pane-template-wizard.tsx +6 -2
  124. package/src/components/ticker/list-table-view.tsx +3 -2
  125. package/src/components/ui/button.tsx +3 -1
  126. package/src/components/ui/choice-dialog.tsx +6 -1
  127. package/src/components/ui/confirm-dialog.tsx +3 -2
  128. package/src/components/ui/fields.tsx +10 -1
  129. package/src/components/ui/frame.tsx +4 -1
  130. package/src/components/ui/index.ts +5 -0
  131. package/src/components/ui/inline-quick-add.tsx +104 -0
  132. package/src/components/ui/list-view.tsx +5 -4
  133. package/src/components/ui/loading.tsx +2 -1
  134. package/src/components/ui/multi-select/dialog.tsx +188 -44
  135. package/src/components/ui/popover.tsx +10 -0
  136. package/src/components/ui/shortcut-hint.tsx +2 -0
  137. package/src/components/ui/status.tsx +4 -3
  138. package/src/components/ui/tabs.tsx +19 -8
  139. package/src/components/ui/toggle.tsx +44 -3
  140. package/src/components/ui/ui.test.tsx +67 -5
  141. package/src/core/app-services.ts +4 -6
  142. package/src/core/state/session-persistence.ts +3 -2
  143. package/src/data/config/chart-settings.ts +712 -0
  144. package/src/data/config/layout.ts +24 -5
  145. package/src/data/config/store/index.test.ts +377 -14
  146. package/src/data/config/store/normalize.ts +79 -65
  147. package/src/data/fred-series.ts +137 -0
  148. package/src/i18n/index.test.ts +114 -0
  149. package/src/i18n/index.ts +147 -0
  150. package/src/i18n/ja.ts +1013 -0
  151. package/src/i18n/ko.ts +1011 -0
  152. package/src/i18n/languages.test.ts +31 -0
  153. package/src/i18n/languages.ts +72 -0
  154. package/src/i18n/zh-cn.ts +1013 -0
  155. package/src/i18n/zh-tw.ts +1013 -0
  156. package/src/plugins/builtin/account-management/footer.ts +3 -2
  157. package/src/plugins/builtin/account-management/form-components.tsx +3 -2
  158. package/src/plugins/builtin/account-management/keyboard.ts +6 -0
  159. package/src/plugins/builtin/account-management/model.ts +25 -21
  160. package/src/plugins/builtin/account-management/navigation.test.ts +27 -0
  161. package/src/plugins/builtin/account-management/navigation.ts +24 -0
  162. package/src/plugins/builtin/account-management/pane.tsx +120 -85
  163. package/src/plugins/builtin/account-management/password-dialog.tsx +10 -9
  164. package/src/plugins/builtin/ai/agent-history.ts +139 -0
  165. package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +133 -51
  166. package/src/plugins/builtin/ai/ask-ai.test.tsx +83 -22
  167. package/src/plugins/builtin/ai/index.test.ts +302 -0
  168. package/src/plugins/builtin/ai/index.tsx +374 -40
  169. package/src/plugins/builtin/ai/pane-settings.test.ts +155 -0
  170. package/src/plugins/builtin/ai/pane-settings.ts +319 -0
  171. package/src/plugins/builtin/ai/pi/credential-store.test.ts +80 -0
  172. package/src/plugins/builtin/ai/pi/credential-store.ts +138 -0
  173. package/src/plugins/builtin/ai/pi/file-store.ts +138 -0
  174. package/src/plugins/builtin/ai/pi/host.test.ts +500 -0
  175. package/src/plugins/builtin/ai/pi/host.ts +564 -0
  176. package/src/plugins/builtin/ai/pi/index.ts +5 -0
  177. package/src/plugins/builtin/ai/pi/models-store.test.ts +44 -0
  178. package/src/plugins/builtin/ai/pi/models-store.ts +141 -0
  179. package/src/plugins/builtin/ai/pi/providers.ts +68 -0
  180. package/src/plugins/builtin/ai/pi/runtime.test.ts +240 -0
  181. package/src/plugins/builtin/ai/pi/runtime.ts +635 -0
  182. package/src/plugins/builtin/ai/providers.test.ts +68 -0
  183. package/src/plugins/builtin/ai/providers.ts +163 -38
  184. package/src/plugins/builtin/ai/runner-selection.test.ts +174 -0
  185. package/src/plugins/builtin/ai/runner-selection.ts +193 -0
  186. package/src/plugins/builtin/ai/runner-selector.tsx +153 -0
  187. package/src/plugins/builtin/ai/runner.test.ts +198 -0
  188. package/src/plugins/builtin/ai/runner.ts +276 -73
  189. package/src/plugins/builtin/ai/screener/contract.test.ts +26 -73
  190. package/src/plugins/builtin/ai/screener/contract.ts +14 -52
  191. package/src/plugins/builtin/ai/screener/editor-runtime.ts +55 -5
  192. package/src/plugins/builtin/ai/screener/editor.tsx +42 -36
  193. package/src/plugins/builtin/ai/screener/footer.ts +82 -67
  194. package/src/plugins/builtin/ai/screener/keyboard.ts +27 -11
  195. package/src/plugins/builtin/ai/screener/model.test.ts +24 -0
  196. package/src/plugins/builtin/ai/screener/model.ts +9 -3
  197. package/src/plugins/builtin/ai/screener/pane.test.tsx +142 -55
  198. package/src/plugins/builtin/ai/screener/pane.tsx +95 -97
  199. package/src/plugins/builtin/ai/screener/results-view.tsx +8 -41
  200. package/src/plugins/builtin/ai/screener/runner.ts +41 -25
  201. package/src/plugins/builtin/ai/screener/tabs-bar.tsx +2 -2
  202. package/src/plugins/builtin/ai/use-runtime-providers.ts +28 -0
  203. package/src/plugins/builtin/ai/workspace/model.test.ts +227 -0
  204. package/src/plugins/builtin/ai/workspace/model.ts +308 -0
  205. package/src/plugins/builtin/ai/workspace/pane.test.tsx +197 -0
  206. package/src/plugins/builtin/ai/workspace/pane.tsx +896 -0
  207. package/src/plugins/builtin/analytics/index.test.tsx +3 -3
  208. package/src/plugins/builtin/analytics/index.tsx +3 -8
  209. package/src/plugins/builtin/broker-manager/detail.tsx +25 -24
  210. package/src/plugins/builtin/broker-manager/index.test.tsx +1 -1
  211. package/src/plugins/builtin/broker-manager/index.tsx +9 -11
  212. package/src/plugins/builtin/broker-manager/model.ts +11 -10
  213. package/src/plugins/builtin/broker-manager/pane-actions.tsx +22 -21
  214. package/src/plugins/builtin/broker-manager/table.ts +9 -8
  215. package/src/plugins/builtin/changelog/index.tsx +3 -8
  216. package/src/plugins/builtin/chart-composer/chart-spec.ts +89 -0
  217. package/src/plugins/builtin/chart-composer/cli-options.ts +98 -0
  218. package/src/plugins/builtin/chart-composer/date-window-dialog.tsx +120 -0
  219. package/src/plugins/builtin/chart-composer/editor.test.tsx +332 -0
  220. package/src/plugins/builtin/chart-composer/editor.tsx +1107 -0
  221. package/src/plugins/builtin/chart-composer/index.tsx +228 -0
  222. package/src/plugins/builtin/chart-composer/pane.tsx +645 -0
  223. package/src/plugins/builtin/chart-composer/presets.test.ts +561 -0
  224. package/src/plugins/builtin/chart-composer/presets.ts +759 -0
  225. package/src/plugins/builtin/chart-composer/quick-add.test.tsx +245 -0
  226. package/src/plugins/builtin/chart-composer/quick-add.tsx +409 -0
  227. package/src/plugins/builtin/chart-composer/semantic.ts +86 -0
  228. package/src/plugins/builtin/chart-composer/series-catalog.test.ts +65 -0
  229. package/src/plugins/builtin/chart-composer/series-catalog.ts +295 -0
  230. package/src/plugins/builtin/chart-composer/settings.test.ts +163 -0
  231. package/src/plugins/builtin/chart-composer/settings.ts +397 -0
  232. package/src/plugins/builtin/chart-composer/shortcuts.test.ts +44 -0
  233. package/src/plugins/builtin/chart-composer/shortcuts.ts +35 -0
  234. package/src/plugins/builtin/chart-composer/use-series-catalog.ts +103 -0
  235. package/src/plugins/builtin/chat/channel-labels.ts +5 -4
  236. package/src/plugins/builtin/chat/channels.ts +12 -11
  237. package/src/plugins/builtin/chat/chat.test.tsx +32 -0
  238. package/src/plugins/builtin/chat/content/composer-runtime.ts +6 -1
  239. package/src/plugins/builtin/chat/content/composer.tsx +9 -8
  240. package/src/plugins/builtin/chat/content/index.tsx +36 -1
  241. package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +8 -7
  242. package/src/plugins/builtin/chat/content/transcript.tsx +9 -3
  243. package/src/plugins/builtin/chat/controller/utils.ts +10 -9
  244. package/src/plugins/builtin/chat/message/desktop.tsx +17 -10
  245. package/src/plugins/builtin/chat/message/profile-popover.test.tsx +122 -3
  246. package/src/plugins/builtin/chat/message/profile-popover.tsx +38 -1
  247. package/src/plugins/builtin/chat/message/terminal.tsx +19 -10
  248. package/src/plugins/builtin/chat/pane-state.test.ts +31 -0
  249. package/src/plugins/builtin/chat/pane-state.ts +30 -0
  250. package/src/plugins/builtin/chat/pane.tsx +27 -4
  251. package/src/plugins/builtin/chat/profile-popover.ts +62 -5
  252. package/src/plugins/builtin/chat/sidebar.test.tsx +4 -7
  253. package/src/plugins/builtin/chat/sidebar.tsx +107 -168
  254. package/src/plugins/builtin/chat/test-harness.tsx +6 -0
  255. package/src/plugins/builtin/cloud/auth-actions.tsx +5 -4
  256. package/src/plugins/builtin/cloud/plugin.tsx +150 -127
  257. package/src/plugins/builtin/composite-plugins.ts +71 -0
  258. package/src/plugins/builtin/correlation/index.test.tsx +4 -4
  259. package/src/plugins/builtin/correlation/index.tsx +3 -8
  260. package/src/plugins/builtin/earnings/index.tsx +37 -21
  261. package/src/plugins/builtin/earnings/model.test.ts +52 -0
  262. package/src/plugins/builtin/earnings/model.ts +32 -0
  263. package/src/plugins/builtin/econ/detail-view.tsx +17 -4
  264. package/src/plugins/builtin/econ/fred-cache.test.ts +57 -11
  265. package/src/plugins/builtin/econ/index.tsx +16 -20
  266. package/src/plugins/builtin/fear-greed/index.tsx +2 -8
  267. package/src/plugins/builtin/fx-matrix/index.tsx +3 -8
  268. package/src/plugins/builtin/help/components.tsx +5 -4
  269. package/src/plugins/builtin/help/index.tsx +14 -17
  270. package/src/plugins/builtin/holders/index.tsx +2 -8
  271. package/src/plugins/builtin/insider/index.tsx +2 -7
  272. package/src/plugins/builtin/kelly-sizer/index.test.tsx +2 -2
  273. package/src/plugins/builtin/kelly-sizer/index.tsx +3 -7
  274. package/src/plugins/builtin/kelly-sizer/test-support.tsx +3 -3
  275. package/src/plugins/builtin/layout-manager/index.tsx +3 -7
  276. package/src/plugins/builtin/market-heatmap/index.tsx +3 -8
  277. package/src/plugins/builtin/market-movers/index.tsx +3 -8
  278. package/src/plugins/builtin/news/index.tsx +12 -16
  279. package/src/plugins/builtin/news/wire/breaking/pane.test.tsx +3 -3
  280. package/src/plugins/builtin/news/wire/index.tsx +83 -73
  281. package/src/plugins/builtin/options/index.tsx +2 -8
  282. package/src/plugins/builtin/plugin-module.test.ts +89 -0
  283. package/src/plugins/builtin/plugin-module.ts +116 -0
  284. package/src/plugins/builtin/portfolio-list/header.tsx +4 -3
  285. package/src/plugins/builtin/portfolio-list/index.test.ts +17 -0
  286. package/src/plugins/builtin/portfolio-list/index.tsx +3 -5
  287. package/src/plugins/builtin/portfolio-list/pane/index.test.tsx +3 -3
  288. package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +53 -53
  289. package/src/plugins/builtin/portfolio-list/settings.ts +5 -3
  290. package/src/plugins/builtin/portfolio-list/summary/index.tsx +4 -3
  291. package/src/plugins/builtin/research/index.tsx +2 -8
  292. package/src/plugins/builtin/sec/index.tsx +2 -8
  293. package/src/plugins/builtin/sectors/index.tsx +3 -8
  294. package/src/plugins/builtin/thirteenf/index.tsx +3 -8
  295. package/src/plugins/builtin/thirteenf/pane.test.tsx +1 -1
  296. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +49 -23
  297. package/src/plugins/builtin/ticker-detail/index.test.tsx +15 -136
  298. package/src/plugins/builtin/ticker-detail/index.tsx +2 -93
  299. package/src/plugins/builtin/ticker-detail/overview/components.tsx +6 -5
  300. package/src/plugins/builtin/ticker-detail/overview-tab.tsx +41 -17
  301. package/src/plugins/builtin/ticker-detail/pane.tsx +4 -3
  302. package/src/plugins/builtin/ticker-detail/quote-monitor/index.test.tsx +1 -1
  303. package/src/plugins/builtin/ticker-detail/settings.ts +3 -50
  304. package/src/plugins/builtin/ticker-research-backend-plugin.ts +17 -0
  305. package/src/plugins/builtin/ticker-research-plugin.ts +27 -0
  306. package/src/plugins/builtin/tv/channels.ts +27 -0
  307. package/src/plugins/builtin/tv/index.tsx +40 -0
  308. package/src/plugins/builtin/tv/pane.tsx +286 -0
  309. package/src/plugins/builtin/tv/youtube-stream.ts +100 -0
  310. package/src/plugins/builtin/world-indices/index.tsx +3 -8
  311. package/src/plugins/builtin/yield-curve/index.tsx +8 -8
  312. package/src/plugins/catalog-backend.test.ts +21 -0
  313. package/src/plugins/catalog-backend.ts +42 -0
  314. package/src/plugins/catalog-ui.ts +6 -14
  315. package/src/plugins/ibkr/instance-selection.test.ts +0 -1
  316. package/src/plugins/ownership.ts +143 -0
  317. package/src/plugins/prediction-markets/backend-plugin.ts +19 -0
  318. package/src/plugins/prediction-markets/chart.test.tsx +7 -5
  319. package/src/plugins/prediction-markets/chart.tsx +25 -7
  320. package/src/plugins/registry/index.test.ts +51 -0
  321. package/src/plugins/registry/index.ts +8 -0
  322. package/src/plugins/registry/pane-settings.ts +1 -1
  323. package/src/plugins/runtime/index.test.tsx +2 -2
  324. package/src/remote/app-host.tsx +16 -3
  325. package/src/remote/controller.test.ts +102 -1
  326. package/src/remote/controller.ts +122 -0
  327. package/src/remote/types.ts +11 -0
  328. package/src/renderers/electrobun/bun/core-capabilities.ts +123 -27
  329. package/src/renderers/electrobun/bun/desktop/backend-requests.ts +5 -1
  330. package/src/renderers/electrobun/bun/desktop/initialization.ts +5 -1
  331. package/src/renderers/electrobun/bun/desktop/media.ts +15 -0
  332. package/src/renderers/electrobun/bun/desktop/window-style.ts +4 -1
  333. package/src/renderers/electrobun/bun/desktop/workspace/index.test.ts +32 -1
  334. package/src/renderers/electrobun/bun/desktop/workspace/index.ts +10 -0
  335. package/src/renderers/electrobun/bun/index.ts +5 -0
  336. package/src/renderers/electrobun/bun/tui-entry.ts +1 -6
  337. package/src/renderers/electrobun/view/ai-host.ts +124 -19
  338. package/src/renderers/electrobun/view/backend-rpc.ts +4 -0
  339. package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +151 -39
  340. package/src/renderers/electrobun/view/desktop/popover.tsx +166 -0
  341. package/src/renderers/electrobun/view/dialog-host.test.ts +25 -0
  342. package/src/renderers/electrobun/view/dialog-host.tsx +75 -12
  343. package/src/renderers/electrobun/view/host/box.tsx +5 -0
  344. package/src/renderers/electrobun/view/host/chart-surface.tsx +10 -9
  345. package/src/renderers/electrobun/view/host/input.tsx +9 -19
  346. package/src/renderers/electrobun/view/host/media-surface.tsx +170 -0
  347. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  348. package/src/renderers/electrobun/view/host/style.ts +1 -1
  349. package/src/renderers/electrobun/view/host/tabs.tsx +4 -3
  350. package/src/renderers/electrobun/view/main.tsx +10 -3
  351. package/src/renderers/electrobun/view/styles.css +17 -0
  352. package/src/renderers/electrobun/view/ui-host.tsx +7 -0
  353. package/src/renderers/opentui/chart-surface.test.tsx +52 -6
  354. package/src/renderers/opentui/chart-surface.tsx +95 -11
  355. package/src/renderers/opentui/dialog-host.tsx +36 -5
  356. package/src/renderers/opentui/host.tsx +39 -0
  357. package/src/renderers/opentui/start.tsx +30 -0
  358. package/src/renderers/opentui/ui-host.tsx +2 -0
  359. package/src/sources/provider-router/index.test.ts +43 -0
  360. package/src/sources/provider-router/index.ts +4 -0
  361. package/src/sources/provider-router/supplemental.ts +46 -0
  362. package/src/sources/sec-edgar.test.ts +203 -2
  363. package/src/sources/sec-edgar.ts +70 -15
  364. package/src/state/app/context/index.tsx +13 -1
  365. package/src/state/app/context/selector.test.tsx +62 -1
  366. package/src/state/session-persistence.test.ts +13 -2
  367. package/src/sync/controller.test.ts +190 -3
  368. package/src/sync/controller.ts +76 -45
  369. package/src/sync/core-contributors.test.ts +121 -0
  370. package/src/sync/core-contributors.ts +104 -27
  371. package/src/sync/react.ts +6 -8
  372. package/src/sync/types.ts +3 -0
  373. package/src/time-series/alignment.ts +158 -0
  374. package/src/time-series/economic.ts +49 -0
  375. package/src/time-series/field-catalog.ts +306 -0
  376. package/src/time-series/fundamentals.test.ts +505 -0
  377. package/src/time-series/fundamentals.ts +842 -0
  378. package/src/time-series/hooks.ts +45 -0
  379. package/src/time-series/index.ts +14 -0
  380. package/src/time-series/live-quotes.test.ts +219 -0
  381. package/src/time-series/live-quotes.ts +203 -0
  382. package/src/time-series/market-resolution.ts +53 -0
  383. package/src/time-series/market.ts +125 -0
  384. package/src/time-series/reporting.ts +98 -0
  385. package/src/time-series/resolve.test.ts +1281 -0
  386. package/src/time-series/resolve.ts +783 -0
  387. package/src/time-series/spec-studies.test.ts +400 -0
  388. package/src/time-series/spec.ts +487 -0
  389. package/src/time-series/studies.ts +563 -0
  390. package/src/time-series/transforms.test.ts +105 -0
  391. package/src/time-series/transforms.ts +152 -0
  392. package/src/time-series/types.ts +162 -0
  393. package/src/time-series/use-chart-resolution.ts +166 -0
  394. package/src/types/config.ts +9 -11
  395. package/src/types/desktop-window.ts +1 -0
  396. package/src/types/financials.ts +4 -0
  397. package/src/types/media.ts +17 -0
  398. package/src/types/plugin.ts +32 -1
  399. package/src/ui/context-menu.tsx +15 -3
  400. package/src/ui/dialog.tsx +14 -2
  401. package/src/ui/host.tsx +40 -1
  402. package/src/ui/index.tsx +9 -0
  403. package/src/utils/exchanges.test.ts +13 -1
  404. package/src/utils/exchanges.ts +10 -0
  405. package/src/utils/financial-statements.test.ts +138 -0
  406. package/src/utils/financial-statements.ts +115 -8
  407. package/src/utils/format.test.ts +11 -1
  408. package/src/utils/format.ts +7 -0
  409. package/src/version.ts +1 -1
  410. package/src/components/chart/bar-chart-renderer.test.ts +0 -233
  411. package/src/components/chart/bar-chart-renderer.ts +0 -445
  412. package/src/components/chart/comparison/data.test.ts +0 -159
  413. package/src/components/chart/comparison/data.ts +0 -302
  414. package/src/components/chart/comparison/renderer.test.ts +0 -187
  415. package/src/components/chart/comparison/renderer.ts +0 -282
  416. package/src/components/chart/comparison/scene.ts +0 -178
  417. package/src/components/chart/comparison-stock-chart/axis-width.ts +0 -99
  418. package/src/components/chart/comparison-stock-chart/canvas.ts +0 -148
  419. package/src/components/chart/comparison-stock-chart/controls.ts +0 -252
  420. package/src/components/chart/comparison-stock-chart/helpers.ts +0 -101
  421. package/src/components/chart/comparison-stock-chart/interactions.ts +0 -208
  422. package/src/components/chart/comparison-stock-chart/layout-metrics.ts +0 -62
  423. package/src/components/chart/comparison-stock-chart/layout.tsx +0 -155
  424. package/src/components/chart/comparison-stock-chart/legend.tsx +0 -74
  425. package/src/components/chart/comparison-stock-chart/native-surfaces.ts +0 -89
  426. package/src/components/chart/comparison-stock-chart/render-data.ts +0 -227
  427. package/src/components/chart/comparison-stock-chart/render-output.ts +0 -140
  428. package/src/components/chart/comparison-stock-chart/selection-runtime.ts +0 -128
  429. package/src/components/chart/comparison-stock-chart/sources.ts +0 -51
  430. package/src/components/chart/comparison-stock-chart/surface-runtime.ts +0 -159
  431. package/src/components/chart/comparison-stock-chart/toolbar.tsx +0 -117
  432. package/src/components/chart/comparison-stock-chart/types.ts +0 -22
  433. package/src/components/chart/comparison-stock-chart/view.tsx +0 -572
  434. package/src/components/chart/comparison-stock-chart/viewport-runtime.ts +0 -239
  435. package/src/components/chart/comparison-stock-chart.tsx +0 -24
  436. package/src/components/chart/core/axis-measure.ts +0 -33
  437. package/src/components/chart/core/controller.test.ts +0 -157
  438. package/src/components/chart/core/controller.ts +0 -349
  439. package/src/components/chart/core/indicator-selector.tsx +0 -75
  440. package/src/components/chart/core/keyboard.ts +0 -24
  441. package/src/components/chart/core/pane-settings.test.ts +0 -52
  442. package/src/components/chart/core/pane-settings.ts +0 -141
  443. package/src/components/chart/core/scroll.test.ts +0 -64
  444. package/src/components/chart/core/scroll.ts +0 -82
  445. package/src/components/chart/core/window-viewport.test.ts +0 -69
  446. package/src/components/chart/core/window-viewport.ts +0 -162
  447. package/src/components/chart/display-cursor.ts +0 -153
  448. package/src/components/chart/indicators/bands.test.ts +0 -36
  449. package/src/components/chart/indicators/bands.ts +0 -29
  450. package/src/components/chart/indicators/moving-averages.test.ts +0 -39
  451. package/src/components/chart/indicators/moving-averages.ts +0 -42
  452. package/src/components/chart/indicators/options.test.ts +0 -32
  453. package/src/components/chart/indicators/options.ts +0 -107
  454. package/src/components/chart/indicators/oscillators.test.ts +0 -58
  455. package/src/components/chart/indicators/oscillators.ts +0 -92
  456. package/src/components/chart/native/canvas-bitmaps.ts +0 -140
  457. package/src/components/chart/native/raster/comparison-chart.ts +0 -162
  458. package/src/components/chart/native/surface/hooks.ts +0 -144
  459. package/src/components/chart/native/surface/rendering.ts +0 -211
  460. package/src/components/chart/static/bar-chart-surface.test.tsx +0 -101
  461. package/src/components/chart/static/bar-chart-surface.tsx +0 -292
  462. package/src/components/chart/stock-chart/auto.ts +0 -216
  463. package/src/components/chart/stock-chart/axis-width.ts +0 -146
  464. package/src/components/chart/stock-chart/bitmaps.ts +0 -22
  465. package/src/components/chart/stock-chart/canvas.ts +0 -202
  466. package/src/components/chart/stock-chart/controls.tsx +0 -149
  467. package/src/components/chart/stock-chart/cursor.ts +0 -60
  468. package/src/components/chart/stock-chart/data-runtime.ts +0 -128
  469. package/src/components/chart/stock-chart/footer.test.ts +0 -65
  470. package/src/components/chart/stock-chart/footer.ts +0 -303
  471. package/src/components/chart/stock-chart/geometry.ts +0 -160
  472. package/src/components/chart/stock-chart/index.test.ts +0 -532
  473. package/src/components/chart/stock-chart/index.tsx +0 -29
  474. package/src/components/chart/stock-chart/indicators.ts +0 -226
  475. package/src/components/chart/stock-chart/interaction-runtime.ts +0 -44
  476. package/src/components/chart/stock-chart/interactions.ts +0 -300
  477. package/src/components/chart/stock-chart/keyboard.ts +0 -373
  478. package/src/components/chart/stock-chart/layout-metrics.ts +0 -62
  479. package/src/components/chart/stock-chart/layout.tsx +0 -177
  480. package/src/components/chart/stock-chart/native-surfaces.ts +0 -121
  481. package/src/components/chart/stock-chart/presentation.ts +0 -67
  482. package/src/components/chart/stock-chart/renderer-switch.test.tsx +0 -263
  483. package/src/components/chart/stock-chart/rendering/bounds.ts +0 -116
  484. package/src/components/chart/stock-chart/rendering/candidates.ts +0 -145
  485. package/src/components/chart/stock-chart/rendering/data.ts +0 -370
  486. package/src/components/chart/stock-chart/rendering/invalidation.ts +0 -68
  487. package/src/components/chart/stock-chart/rendering/output.ts +0 -304
  488. package/src/components/chart/stock-chart/rendering/planning.ts +0 -122
  489. package/src/components/chart/stock-chart/rendering/projection.test.tsx +0 -98
  490. package/src/components/chart/stock-chart/rendering/projection.ts +0 -112
  491. package/src/components/chart/stock-chart/rendering/state.test.ts +0 -70
  492. package/src/components/chart/stock-chart/rendering/state.ts +0 -283
  493. package/src/components/chart/stock-chart/requests.ts +0 -337
  494. package/src/components/chart/stock-chart/resolution-support.ts +0 -96
  495. package/src/components/chart/stock-chart/resolved/runtime.ts +0 -289
  496. package/src/components/chart/stock-chart/resolved.tsx +0 -359
  497. package/src/components/chart/stock-chart/settings.ts +0 -81
  498. package/src/components/chart/stock-chart/surface-runtime.ts +0 -62
  499. package/src/components/chart/stock-chart/types.ts +0 -25
  500. package/src/components/chart/stock-chart/view.tsx +0 -155
  501. package/src/components/chart/stock-chart/viewport/effects.ts +0 -293
  502. package/src/components/chart/stock-chart/viewport/index.ts +0 -144
  503. package/src/components/chart/stock-chart/viewport/runtime.ts +0 -375
  504. package/src/components/chart/time-axis-label.tsx +0 -175
  505. package/src/plugins/builtin/ai/gloomberb-cli.ts +0 -71
  506. package/src/plugins/builtin/ai/screener/action-bar.tsx +0 -58
  507. package/src/plugins/builtin/comparison-chart/index.test.tsx +0 -848
  508. package/src/plugins/builtin/comparison-chart/index.tsx +0 -194
  509. package/src/plugins/builtin/econ/fred-cache.ts +0 -106
  510. package/src/plugins/builtin/plugin-groups.ts +0 -130
  511. package/src/plugins/builtin/ticker-detail/chart-pane.tsx +0 -91
  512. package/src/plugins/builtin/ticker-detail/chart-tab.tsx +0 -100
  513. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/content.tsx +0 -296
  514. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/data.ts +0 -62
  515. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/model.ts +0 -608
  516. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/settings.ts +0 -65
  517. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph/types.ts +0 -58
  518. package/src/plugins/builtin/ticker-detail/data-panes/fundamental-graph.tsx +0 -138
  519. package/src/plugins/builtin/ticker-detail/data-panes.test.ts +0 -220
@@ -1,83 +1,417 @@
1
1
  import type { GloomPlugin } from "../../../types/plugin";
2
- import { detectProviders } from "./providers";
2
+ import type { AppConfig } from "../../../types/config";
3
3
  import { AskAiResearchTab } from "./ask-ai-detail-tab";
4
+ import {
5
+ detectProviders,
6
+ resolveDefaultAiProviderId,
7
+ type AiProvider,
8
+ } from "./providers";
4
9
  import { AiScreenerPane } from "./screener/pane";
5
10
  import { buildAiScreenerPaneSettingsDef, getAiScreenerPaneSettings } from "./settings";
11
+ import { t } from "../../../i18n";
12
+ import {
13
+ LOCAL_AGENT_WORKSPACE_SCHEMA_VERSION,
14
+ LOCAL_AGENT_WORKSPACE_STATE_KEY,
15
+ LocalAgentWorkspacePane,
16
+ } from "./workspace/pane";
17
+ import {
18
+ buildAiRunnerWizard,
19
+ getSelectableAiRunners,
20
+ resolveAiRunnerWizardModel,
21
+ resolveReadyAiRunnerDefault,
22
+ supportsAiRunOutputMode,
23
+ } from "./runner-selection";
24
+ import {
25
+ AI_DEFAULT_MODEL_SETTING_KEY,
26
+ AI_DEFAULT_PROVIDER_SETTING_KEY,
27
+ AI_PANE_MODEL_SETTING_KEY,
28
+ AI_PANE_PROVIDER_SETTING_KEY,
29
+ buildAiPaneSettingsDef,
30
+ resolveAiSharedDefaults,
31
+ type AiAccountSettingRow,
32
+ type AiSharedDefaults,
33
+ } from "./pane-settings";
34
+ import {
35
+ connectAiRuntimeProvider,
36
+ disconnectAiRuntimeProvider,
37
+ getAiRuntimeCatalog,
38
+ subscribeAiRuntimeCatalog,
39
+ } from "./runner";
40
+ import {
41
+ EMPTY_LOCAL_AGENT_WORKSPACE,
42
+ normalizeLocalAgentWorkspace,
43
+ type LocalAgentWorkspaceState,
44
+ } from "./workspace/model";
45
+ import {
46
+ EMPTY_PANE_STATE,
47
+ normalizeTabs,
48
+ type PersistedAiScreenerPaneState,
49
+ } from "./screener/model";
50
+
51
+ function settingOrFallback(
52
+ settings: Record<string, unknown>,
53
+ key: string,
54
+ fallback: string | null | undefined,
55
+ ): string {
56
+ if (!Object.prototype.hasOwnProperty.call(settings, key)) return fallback?.trim() ?? "";
57
+ return typeof settings[key] === "string" ? settings[key].trim() : "";
58
+ }
59
+
60
+ function modelSettingOrFallback(
61
+ settings: Record<string, unknown>,
62
+ savedProviderId: string | null | undefined,
63
+ savedModelId: string | null | undefined,
64
+ ): string {
65
+ if (Object.prototype.hasOwnProperty.call(settings, AI_PANE_MODEL_SETTING_KEY)) {
66
+ return settingOrFallback(settings, AI_PANE_MODEL_SETTING_KEY, null);
67
+ }
68
+ if (Object.prototype.hasOwnProperty.call(settings, AI_PANE_PROVIDER_SETTING_KEY)) {
69
+ const configuredProviderId = settingOrFallback(settings, AI_PANE_PROVIDER_SETTING_KEY, null);
70
+ if (configuredProviderId !== (savedProviderId?.trim() ?? "")) return "";
71
+ }
72
+ return savedModelId?.trim() ?? "";
73
+ }
74
+
75
+ function settingsProviders(providers: readonly AiProvider[]) {
76
+ const accounts = new Map(
77
+ getAiRuntimeCatalog().accounts.map((account) => [account.providerId, account] as const),
78
+ );
79
+ return providers.map((provider) => ({
80
+ id: provider.id,
81
+ label: provider.name,
82
+ description: accounts.get(provider.id)?.connectionState === "connected"
83
+ ? accounts.get(provider.id)?.connectionLabel
84
+ : provider.status === "ready"
85
+ ? `${provider.name} is ready.`
86
+ : accounts.get(provider.id)?.canLogin
87
+ ? `${provider.name} can be connected here.`
88
+ : accounts.get(provider.id)?.authMethods.some((method) => method.type === "api_key")
89
+ ? `${provider.name} needs an API key supplied through its standard environment variable.`
90
+ : `${provider.name} is not currently available.`,
91
+ }));
92
+ }
93
+
94
+ function settingsModels(providerIds: ReadonlySet<string>) {
95
+ return getAiRuntimeCatalog().models
96
+ .filter((model) => providerIds.has(model.providerId))
97
+ .map((model) => ({
98
+ id: model.id,
99
+ providerId: model.providerId,
100
+ label: model.label,
101
+ description: model.available ? "Available for the connected account." : undefined,
102
+ }));
103
+ }
104
+
105
+ function accountSettingRows(providerIds: ReadonlySet<string>): AiAccountSettingRow[] {
106
+ return getAiRuntimeCatalog().accounts
107
+ .filter((account) => (
108
+ providerIds.has(account.providerId)
109
+ && (account.canLogin || account.canDisconnect)
110
+ ))
111
+ .map((account) => {
112
+ const disconnectable = account.canDisconnect;
113
+ return {
114
+ providerId: account.providerId,
115
+ providerLabel: account.providerLabel,
116
+ description: account.connectionLabel,
117
+ actionLabel: disconnectable ? "Disconnect" : "Connect",
118
+ async action(context) {
119
+ context.close();
120
+ if (disconnectable) {
121
+ context.notify({
122
+ body: `Disconnecting ${account.providerLabel}…`,
123
+ type: "info",
124
+ duration: 5_000,
125
+ });
126
+ try {
127
+ const catalog = await disconnectAiRuntimeProvider(account.providerId);
128
+ const remainingAccount = catalog.accounts.find((candidate) => (
129
+ candidate.providerId === account.providerId
130
+ ));
131
+ context.notify({
132
+ body: remainingAccount?.connectionState === "connected"
133
+ ? `${account.providerLabel}'s Gloomberb account was removed. ${remainingAccount.connectionLabel} remains active.`
134
+ : `${account.providerLabel} is disconnected from Gloomberb.`,
135
+ type: "success",
136
+ });
137
+ } catch (error) {
138
+ context.notify({
139
+ body: error instanceof Error ? error.message : `${account.providerLabel} disconnection failed.`,
140
+ type: "error",
141
+ persistent: true,
142
+ });
143
+ }
144
+ return;
145
+ }
146
+ context.notify({
147
+ body: `Opening ${account.providerLabel} sign-in in your browser…`,
148
+ type: "info",
149
+ duration: 5_000,
150
+ });
151
+ try {
152
+ await connectAiRuntimeProvider(account.providerId, undefined, (event) => {
153
+ if (event.type !== "device_code") return;
154
+ context.notify({
155
+ title: `${account.providerLabel} device sign-in`,
156
+ body: `Enter code ${event.userCode} at ${event.verificationUri}. The sign-in page has been opened in your browser.`,
157
+ type: "info",
158
+ persistent: true,
159
+ });
160
+ });
161
+ context.notify({
162
+ body: `${account.providerLabel} is connected.`,
163
+ type: "success",
164
+ });
165
+ } catch (error) {
166
+ context.notify({
167
+ body: error instanceof Error ? error.message : `${account.providerLabel} sign-in failed.`,
168
+ type: "error",
169
+ persistent: true,
170
+ });
171
+ }
172
+ },
173
+ };
174
+ });
175
+ }
176
+
177
+ function defaultsFromConfig(
178
+ config: AppConfig,
179
+ fallbackProviderId: string,
180
+ ): AiSharedDefaults {
181
+ return resolveAiSharedDefaults(config.pluginConfig.ai, fallbackProviderId);
182
+ }
6
183
 
7
184
  export const aiPlugin: GloomPlugin = {
8
185
  id: "ai",
9
186
  name: "AI",
10
187
  version: "1.0.0",
11
- description: "Use local AI CLI with your financial data.",
188
+ description: t("Use AI providers with your financial data."),
12
189
  toggleable: true,
13
190
 
14
191
  setup(ctx) {
15
- const detectedProviders = detectProviders();
16
- const selectableProviders = detectedProviders.filter((provider) => provider.available);
17
- const providerOptions = (selectableProviders.length > 0 ? selectableProviders : detectedProviders)
18
- .map((provider) => ({
19
- label: provider.name,
20
- value: provider.id,
21
- }));
22
- const defaultProviderId = providerOptions[0]?.value ?? "claude";
192
+ const initialProviders = detectProviders();
193
+ const initialScreenerRunners = getSelectableAiRunners(initialProviders, { outputMode: "screener" });
194
+ const fallbackProviderId = resolveDefaultAiProviderId(initialScreenerRunners);
195
+ const initialDefaults = resolveAiSharedDefaults(
196
+ ctx.getConfig().pluginConfig.ai,
197
+ fallbackProviderId,
198
+ );
199
+ const screenerRunnerWizard = buildAiRunnerWizard(initialProviders, {
200
+ defaultProviderId: initialDefaults.providerId,
201
+ defaultModelId: initialDefaults.modelId,
202
+ outputMode: "screener",
203
+ });
204
+ const screenerWizard = [
205
+ ...screenerRunnerWizard,
206
+ {
207
+ key: "prompt",
208
+ label: t("Screener Prompt"),
209
+ type: "textarea" as const,
210
+ placeholder: t("Examples: humanoid robot suppliers, defense software compounders, EM payment rails, obesity-drug picks-and-shovels..."),
211
+ body: [
212
+ t("Describe the screening idea in plain English."),
213
+ t("The AI will return validated ticker ideas with a short reason for each one."),
214
+ ],
215
+ },
216
+ ];
217
+ const updateWizards = (config: AppConfig) => {
218
+ const providers = detectProviders();
219
+ const screenerRunners = getSelectableAiRunners(providers, { outputMode: "screener" });
220
+ const currentFallbackProviderId = resolveDefaultAiProviderId(screenerRunners);
221
+ const defaults = defaultsFromConfig(config, currentFallbackProviderId);
222
+ const screenerDefaults = resolveReadyAiRunnerDefault(
223
+ screenerRunners,
224
+ defaults.providerId,
225
+ defaults.modelId,
226
+ );
227
+ const nextRunnerWizard = buildAiRunnerWizard(providers, {
228
+ defaultProviderId: screenerDefaults.providerId,
229
+ defaultModelId: screenerDefaults.modelId,
230
+ outputMode: "screener",
231
+ });
232
+ screenerWizard.splice(0, Math.max(0, screenerWizard.length - 1), ...nextRunnerWizard);
233
+ };
234
+
235
+ const seedDefaultProvider = ctx.configState.get(AI_DEFAULT_PROVIDER_SETTING_KEY) === null;
236
+ const seedDefaultModel = ctx.configState.get(AI_DEFAULT_MODEL_SETTING_KEY) === null;
237
+ if (seedDefaultProvider || seedDefaultModel) {
238
+ void (async () => {
239
+ try {
240
+ if (seedDefaultProvider) {
241
+ await ctx.configState.set(AI_DEFAULT_PROVIDER_SETTING_KEY, initialDefaults.providerId);
242
+ }
243
+ if (seedDefaultModel) {
244
+ await ctx.configState.set(AI_DEFAULT_MODEL_SETTING_KEY, initialDefaults.modelId ?? "");
245
+ }
246
+ } catch (error) {
247
+ ctx.log.warn("Failed to save the default AI provider settings.", error);
248
+ }
249
+ })();
250
+ }
251
+ ctx.on("config:changed", ({ config }) => updateWizards(config));
252
+ subscribeAiRuntimeCatalog(() => updateWizards(ctx.getConfig()));
23
253
 
24
254
  ctx.registerTickerResearchTab({
25
255
  id: "ai-chat",
26
- name: "Ask AI",
256
+ name: t("Ask AI"),
27
257
  order: 60,
28
258
  component: AskAiResearchTab,
29
259
  });
30
260
 
261
+ ctx.registerPane({
262
+ id: "local-agent-workspace",
263
+ name: "AI Agent",
264
+ icon: "A",
265
+ component: LocalAgentWorkspacePane,
266
+ defaultPosition: "right",
267
+ defaultMode: "floating",
268
+ defaultFloatingSize: { width: 96, height: 32 },
269
+ settings: (context) => {
270
+ const providers = detectProviders();
271
+ const workspaceProviders = providers.filter((provider) => (
272
+ supportsAiRunOutputMode(provider, "structured")
273
+ ));
274
+ const workspaceRunners = getSelectableAiRunners(workspaceProviders, {
275
+ outputMode: "structured",
276
+ });
277
+ const workspaceFallbackProviderId = resolveDefaultAiProviderId(workspaceRunners);
278
+ const defaults = defaultsFromConfig(context.config, workspaceFallbackProviderId);
279
+ const workspaceDefaults = workspaceProviders.some((provider) => provider.id === defaults.providerId)
280
+ ? defaults
281
+ : { providerId: workspaceFallbackProviderId, modelId: null };
282
+ const workspaceProviderIds = new Set(workspaceProviders.map((provider) => provider.id));
283
+ const persisted = ctx.resume.getState<LocalAgentWorkspaceState>(
284
+ LOCAL_AGENT_WORKSPACE_STATE_KEY,
285
+ { schemaVersion: LOCAL_AGENT_WORKSPACE_SCHEMA_VERSION },
286
+ ) ?? EMPTY_LOCAL_AGENT_WORKSPACE;
287
+ const workspace = normalizeLocalAgentWorkspace(persisted);
288
+ const activeThreadId = typeof context.paneState.activeThreadId === "string"
289
+ ? context.paneState.activeThreadId
290
+ : workspace.activeThreadId;
291
+ const activeThread = workspace.threads.find((thread) => thread.id === activeThreadId)
292
+ ?? workspace.threads[0]
293
+ ?? null;
294
+ return buildAiPaneSettingsDef({
295
+ title: "AI Agent Settings",
296
+ providers: settingsProviders(workspaceProviders),
297
+ models: settingsModels(workspaceProviderIds),
298
+ defaultProviderId: workspaceDefaults.providerId,
299
+ defaultModelId: workspaceDefaults.modelId,
300
+ paneOverride: {
301
+ providerId: settingOrFallback(context.settings, AI_PANE_PROVIDER_SETTING_KEY, activeThread?.providerId),
302
+ modelId: modelSettingOrFallback(
303
+ context.settings,
304
+ activeThread?.providerId,
305
+ activeThread?.modelId,
306
+ ),
307
+ },
308
+ accountRows: accountSettingRows(workspaceProviderIds),
309
+ });
310
+ },
311
+ });
312
+
313
+ ctx.registerPaneTemplate({
314
+ id: "new-local-agent-workspace",
315
+ paneId: "local-agent-workspace",
316
+ label: "AI Agent",
317
+ description: "Create a persistent AI thread with optional model selection.",
318
+ keywords: ["ai", "agent", "claude", "openai", "chatgpt", "gemini", "copilot", "grok", "openrouter", "research", "thread"],
319
+ shortcut: { prefix: "AGENT" },
320
+ createInstance: () => ({
321
+ title: "AI Agent",
322
+ placement: "floating",
323
+ params: { newThreadId: crypto.randomUUID() },
324
+ }),
325
+ });
326
+
31
327
  ctx.registerPane({
32
328
  id: "ai-screener",
33
- name: "AI Screener",
329
+ name: t("AI Screener"),
34
330
  icon: "A",
35
331
  component: AiScreenerPane,
36
332
  defaultPosition: "right",
37
333
  defaultMode: "floating",
38
334
  defaultFloatingSize: { width: 76, height: 24 },
39
- settings: (context) => buildAiScreenerPaneSettingsDef(
40
- getAiScreenerPaneSettings(context.settings),
41
- ),
335
+ settings: (context) => {
336
+ const providers = detectProviders();
337
+ const screenerProviders = providers.filter((provider) => (
338
+ supportsAiRunOutputMode(provider, "screener")
339
+ ));
340
+ const screenerRunners = getSelectableAiRunners(screenerProviders, {
341
+ outputMode: "screener",
342
+ });
343
+ const currentFallbackProviderId = resolveDefaultAiProviderId(screenerRunners);
344
+ const defaults = defaultsFromConfig(context.config, currentFallbackProviderId);
345
+ const providerIds = new Set(screenerProviders.map((provider) => provider.id));
346
+ const persisted = ctx.resume.getState<PersistedAiScreenerPaneState>(
347
+ `screener-pane:${context.paneId}`,
348
+ { schemaVersion: 1 },
349
+ ) ?? EMPTY_PANE_STATE;
350
+ const tabs = normalizeTabs(persisted);
351
+ const activeTabId = typeof context.paneState.activeTabId === "string"
352
+ ? context.paneState.activeTabId
353
+ : null;
354
+ const activeTab = tabs.find((tab) => tab.id === activeTabId) ?? tabs[0] ?? null;
355
+ return buildAiPaneSettingsDef({
356
+ title: "AI Screener Settings",
357
+ providers: settingsProviders(screenerProviders),
358
+ models: settingsModels(providerIds),
359
+ defaultProviderId: defaults.providerId,
360
+ defaultModelId: defaults.modelId,
361
+ paneOverride: {
362
+ providerId: settingOrFallback(context.settings, AI_PANE_PROVIDER_SETTING_KEY, activeTab?.providerId),
363
+ modelId: modelSettingOrFallback(
364
+ context.settings,
365
+ activeTab?.providerId,
366
+ activeTab?.modelId,
367
+ ),
368
+ },
369
+ accountRows: accountSettingRows(providerIds),
370
+ additional: buildAiScreenerPaneSettingsDef(
371
+ getAiScreenerPaneSettings(context.settings),
372
+ ),
373
+ });
374
+ },
42
375
  });
43
376
 
44
377
  ctx.registerPaneTemplate({
45
378
  id: "new-ai-screener-pane",
46
379
  paneId: "ai-screener",
47
- label: "AI Screener",
48
- description: "Create a prompt-driven AI screener pane with reusable screening tabs.",
380
+ label: t("AI Screener"),
381
+ description: t("Create a prompt-driven AI screener pane with reusable screening tabs."),
49
382
  keywords: ["ai", "screener", "screen", "watchlist", "prompt"],
50
383
  shortcut: { prefix: "AI", argPlaceholder: "prompt", argKind: "text" },
51
- wizard: [
52
- {
53
- key: "providerId",
54
- label: "AI Provider",
55
- type: "select",
56
- defaultValue: defaultProviderId,
57
- options: providerOptions,
58
- body: ["Choose which local AI CLI should run the initial screener."],
59
- },
60
- {
61
- key: "prompt",
62
- label: "Screener Prompt",
63
- type: "textarea",
64
- placeholder: "Examples: humanoid robot suppliers, defense software compounders, EM payment rails, obesity-drug picks-and-shovels...",
65
- body: [
66
- "Describe the screening idea in plain English.",
67
- "The AI will return validated ticker ideas with a short reason for each one.",
68
- ],
69
- },
70
- ],
71
- createInstance: (_context, options) => {
384
+ wizard: screenerWizard,
385
+ createInstance: (context, options) => {
72
386
  const prompt = options?.values?.prompt?.trim() || options?.arg?.trim() || "";
73
387
  if (!prompt) return null;
74
- const providerId = options?.values?.providerId || defaultProviderId;
388
+ const providers = detectProviders();
389
+ const screenerRunners = getSelectableAiRunners(providers, {
390
+ outputMode: "screener",
391
+ });
392
+ const currentFallbackProviderId = resolveDefaultAiProviderId(screenerRunners);
393
+ const defaults = resolveAiSharedDefaults(
394
+ context.config.pluginConfig.ai,
395
+ currentFallbackProviderId,
396
+ );
397
+ const readyDefaults = resolveReadyAiRunnerDefault(
398
+ screenerRunners,
399
+ defaults.providerId,
400
+ defaults.modelId,
401
+ );
402
+ const providerId = options?.values?.providerId || readyDefaults.providerId;
403
+ const modelId = resolveAiRunnerWizardModel(
404
+ options?.values,
405
+ providerId,
406
+ providerId === readyDefaults.providerId ? readyDefaults.modelId : null,
407
+ );
75
408
  return {
76
- title: "AI Screener",
409
+ title: t("AI Screener"),
77
410
  placement: "floating",
78
411
  params: {
79
412
  prompt,
80
413
  providerId,
414
+ ...(modelId ? { modelId } : {}),
81
415
  },
82
416
  };
83
417
  },
@@ -0,0 +1,155 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { PaneSettingActionContext } from "../../../types/plugin";
3
+ import {
4
+ AI_DEFAULT_MODEL_SETTING_KEY,
5
+ AI_DEFAULT_PROVIDER_SETTING_KEY,
6
+ buildAiPaneSettingsDef,
7
+ resolveAiPaneSelection,
8
+ resolveAiSharedDefaults,
9
+ } from "./pane-settings";
10
+
11
+ const providers = [
12
+ { id: "anthropic", label: "Claude" },
13
+ { id: "openai-codex", label: "OpenAI" },
14
+ ];
15
+
16
+ const models = [
17
+ { id: "claude-sonnet-4-5", label: "Sonnet 4.5", providerId: "anthropic" },
18
+ { id: "gpt-5.4", label: "GPT-5.4", providerId: "openai-codex" },
19
+ ];
20
+
21
+ describe("shared AI pane settings", () => {
22
+ test("composes global defaults, pane overrides, accounts, and pane-specific fields", async () => {
23
+ const actions: string[] = [];
24
+ const definition = buildAiPaneSettingsDef({
25
+ title: "AI Screener Settings",
26
+ providers,
27
+ models,
28
+ defaultProviderId: "anthropic",
29
+ defaultModelId: "claude-sonnet-4-5",
30
+ paneOverride: {
31
+ providerId: null,
32
+ modelId: "claude-custom",
33
+ },
34
+ accountRows: [{
35
+ providerId: "anthropic",
36
+ providerLabel: "Claude",
37
+ description: "Connected as vince@example.com",
38
+ actionLabel: "Manage",
39
+ action: () => { actions.push("anthropic"); },
40
+ }],
41
+ manageAccounts: {
42
+ action: () => { actions.push("manage"); },
43
+ },
44
+ additional: {
45
+ values: { columnIds: ["ticker", "reason"] },
46
+ fields: [{
47
+ key: "columnIds",
48
+ label: "Columns",
49
+ type: "ordered-multi-select",
50
+ options: [{ value: "ticker", label: "Ticker" }],
51
+ }],
52
+ },
53
+ });
54
+
55
+ expect(definition.values).toEqual({
56
+ defaultProviderId: "anthropic",
57
+ defaultModelId: "claude-sonnet-4-5",
58
+ providerId: "",
59
+ modelId: "claude-custom",
60
+ columnIds: ["ticker", "reason"],
61
+ });
62
+ expect(definition.fields.slice(0, 4).map((field) => [
63
+ field.key,
64
+ field.type === "action" ? undefined : field.storage,
65
+ ])).toEqual([
66
+ [AI_DEFAULT_PROVIDER_SETTING_KEY, "plugin"],
67
+ [AI_DEFAULT_MODEL_SETTING_KEY, "plugin"],
68
+ ["providerId", undefined],
69
+ ["modelId", undefined],
70
+ ]);
71
+ expect(definition.fields[0]?.type !== "action" && definition.fields[0]?.clearOnChange)
72
+ .toEqual([AI_DEFAULT_MODEL_SETTING_KEY]);
73
+ expect(definition.fields[2]?.type !== "action" && definition.fields[2]?.clearOnChange)
74
+ .toEqual(["modelId"]);
75
+ expect(definition.fields.at(-1)?.key).toBe("columnIds");
76
+
77
+ const accountAction = definition.fields.find((field) => field.key === "account:anthropic");
78
+ const manageAction = definition.fields.find((field) => field.key === "manageAiAccounts");
79
+ if (accountAction?.type !== "action" || manageAction?.type !== "action") {
80
+ throw new Error("Expected AI account actions");
81
+ }
82
+ await accountAction.action({} as PaneSettingActionContext);
83
+ await manageAction.action({} as PaneSettingActionContext);
84
+ expect(actions).toEqual(["anthropic", "manage"]);
85
+ });
86
+
87
+ test("keeps persisted models selectable when a provider catalog no longer returns them", () => {
88
+ const definition = buildAiPaneSettingsDef({
89
+ title: "Agent Settings",
90
+ providers,
91
+ models,
92
+ defaultProviderId: "openai-codex",
93
+ defaultModelId: "retired-gpt",
94
+ paneOverride: {
95
+ providerId: "anthropic",
96
+ modelId: "retired-claude",
97
+ },
98
+ });
99
+
100
+ const defaultModel = definition.fields.find((field) => field.key === "defaultModelId");
101
+ const paneModel = definition.fields.find((field) => field.key === "modelId");
102
+ if (defaultModel?.type !== "select" || paneModel?.type !== "select") {
103
+ throw new Error("Expected model selectors");
104
+ }
105
+
106
+ expect(defaultModel.options.map((option) => option.value)).toEqual(["", "gpt-5.4", "retired-gpt"]);
107
+ expect(paneModel.options.map((option) => option.value)).toEqual(["", "claude-sonnet-4-5", "retired-claude"]);
108
+ expect(definition.fields.some((field) => field.type === "action")).toBe(false);
109
+ });
110
+
111
+ test("migrates legacy runner ids while preserving saved identity until an override is configured", () => {
112
+ expect(resolveAiPaneSelection({
113
+ settings: {},
114
+ savedProviderId: "claude",
115
+ savedModelId: "sonnet",
116
+ defaultProviderId: "codex",
117
+ defaultModelId: "gpt-default",
118
+ })).toEqual({ providerId: "anthropic", modelId: "sonnet" });
119
+
120
+ expect(resolveAiPaneSelection({
121
+ settings: { providerId: "", modelId: "" },
122
+ savedProviderId: "claude",
123
+ savedModelId: "sonnet",
124
+ defaultProviderId: "codex",
125
+ defaultModelId: "gpt-default",
126
+ })).toEqual({ providerId: "openai-codex", modelId: "gpt-default" });
127
+
128
+ expect(resolveAiPaneSelection({
129
+ settings: { providerId: "" },
130
+ savedProviderId: "claude",
131
+ savedModelId: "old-sonnet",
132
+ defaultProviderId: "claude",
133
+ defaultModelId: "new-sonnet",
134
+ })).toEqual({ providerId: "anthropic", modelId: "new-sonnet" });
135
+ });
136
+
137
+ test("does not carry a saved model across an explicit provider change", () => {
138
+ expect(resolveAiPaneSelection({
139
+ settings: { providerId: "openai-codex" },
140
+ savedProviderId: "anthropic",
141
+ savedModelId: "claude-sonnet-4-5",
142
+ defaultProviderId: "anthropic",
143
+ defaultModelId: "claude-sonnet-4-5",
144
+ })).toEqual({ providerId: "openai-codex", modelId: null });
145
+ });
146
+
147
+ test("normalizes shared defaults from plugin config", () => {
148
+ expect(resolveAiSharedDefaults({
149
+ defaultProviderId: " codex ",
150
+ defaultModelId: " gpt-default ",
151
+ }, "claude")).toEqual({ providerId: "openai-codex", modelId: "gpt-default" });
152
+ expect(resolveAiSharedDefaults(undefined, "claude"))
153
+ .toEqual({ providerId: "anthropic", modelId: null });
154
+ });
155
+ });