gloomberb 0.16.3 → 0.16.4
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.
- package/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Connect an AI assistant": pick an MCP client, copy the one command or
|
|
3
|
+
* config it needs. Signing in from the client needs no key; a key is the
|
|
4
|
+
* fallback, created here through the account API and shown once.
|
|
5
|
+
*
|
|
6
|
+
* The command bar runs outside the React tree, so `requestMcpConnectDialog`
|
|
7
|
+
* hands the request to `McpConnectDialogHost`, which the shell mounts for the
|
|
8
|
+
* life of the app, like the sign-in dialogs. Surfaces that already hold the
|
|
9
|
+
* dialog API (the account pane) call `openMcpConnectDialog` directly.
|
|
10
|
+
*
|
|
11
|
+
* The created key lives in this dialog's state only: it is never logged,
|
|
12
|
+
* stored or synced, and it is gone once the dialog closes.
|
|
13
|
+
*/
|
|
14
|
+
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
15
|
+
import { apiClient } from "../../../../api-client";
|
|
16
|
+
import { usePlanAccess } from "../../../../api-client/plan-access";
|
|
17
|
+
import { Button } from "../../../../components/ui/button";
|
|
18
|
+
import { Divider } from "../../../../components/ui/display";
|
|
19
|
+
import { ExternalLinkText } from "../../../../components/ui/external-link";
|
|
20
|
+
import { DialogFrame } from "../../../../components/ui/frame";
|
|
21
|
+
import { SegmentedControl } from "../../../../components/ui/toggle";
|
|
22
|
+
import { t, tf } from "../../../../i18n";
|
|
23
|
+
import { useAppLanguage } from "../../../../i18n/react";
|
|
24
|
+
import { useViewport } from "../../../../react/input";
|
|
25
|
+
import { useThemeColors } from "../../../../theme/theme-context";
|
|
26
|
+
import { Box, Text, TextAttributes, useRendererHost, useUiCapabilities } from "../../../../ui";
|
|
27
|
+
import { useDialog, useDialogKeyboard, type DialogApi, type PromptContext } from "../../../../ui/dialog";
|
|
28
|
+
import { isPlainKey } from "../../../../utils/keyboard";
|
|
29
|
+
import { useCloudUpgradeAction } from "../../shared/cloud-upgrade";
|
|
30
|
+
import { requestAuthDialog } from "../auth-dialog";
|
|
31
|
+
import {
|
|
32
|
+
currentMcpEndpointUrl,
|
|
33
|
+
MCP_CLIENTS,
|
|
34
|
+
MCP_DOCS_URL,
|
|
35
|
+
mcpClientSetup,
|
|
36
|
+
mcpKeyFailure,
|
|
37
|
+
type McpClientId,
|
|
38
|
+
type McpKeyFailure,
|
|
39
|
+
} from "./model";
|
|
40
|
+
import { useMcpConnectSections } from "./sections";
|
|
41
|
+
|
|
42
|
+
/** Wide enough for a key and its closing quote on one snippet row. */
|
|
43
|
+
const MAX_CONTENT_WIDTH = 78;
|
|
44
|
+
const MIN_CONTENT_WIDTH = 32;
|
|
45
|
+
/** Border plus padding the terminal dialog host draws around the content. */
|
|
46
|
+
const TERMINAL_DIALOG_CHROME = 6;
|
|
47
|
+
/** The access a key made here gets: the server's default, as in Cloud settings. */
|
|
48
|
+
const KEY_SCOPE = "read";
|
|
49
|
+
|
|
50
|
+
/** Content width for a viewport: as wide as a command line reads well, never wider than the screen. */
|
|
51
|
+
function mcpConnectContentWidth(viewportWidth: number | undefined): number {
|
|
52
|
+
const available = (viewportWidth ?? MAX_CONTENT_WIDTH + 10) - 10;
|
|
53
|
+
return Math.max(MIN_CONTENT_WIDTH, Math.min(MAX_CONTENT_WIDTH, Math.floor(available)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type KeyState =
|
|
57
|
+
| { phase: "idle" }
|
|
58
|
+
| { phase: "creating" }
|
|
59
|
+
| { phase: "created"; token: string; name: string }
|
|
60
|
+
| { phase: "failed"; failure: McpKeyFailure };
|
|
61
|
+
|
|
62
|
+
type Status = { tone: "positive" | "warning" | "negative" | "muted"; text: string } | null;
|
|
63
|
+
|
|
64
|
+
function useCloudSignedIn(): boolean {
|
|
65
|
+
return useSyncExternalStore(
|
|
66
|
+
(onChange) => apiClient.subscribeCurrentUser(onChange),
|
|
67
|
+
() => apiClient.isSignedIn(),
|
|
68
|
+
() => apiClient.isSignedIn(),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function statusColor(tone: NonNullable<Status>["tone"], colors: ReturnType<typeof useThemeColors>): string {
|
|
73
|
+
if (tone === "muted") return colors.textDim;
|
|
74
|
+
return colors[tone];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The snippet, one row per line. A line with the key in it runs past the
|
|
79
|
+
* block, and breaking it at its spaces strands a word per row, so it wraps at
|
|
80
|
+
* the edge, as a shell prints it; every other line wraps at its spaces.
|
|
81
|
+
*/
|
|
82
|
+
function SnippetBlock({ text, secret, width }: { text: string; secret: string | null; width: number }) {
|
|
83
|
+
const colors = useThemeColors();
|
|
84
|
+
const { nativePaneChrome } = useUiCapabilities();
|
|
85
|
+
return (
|
|
86
|
+
<Box
|
|
87
|
+
flexDirection="column"
|
|
88
|
+
width={width}
|
|
89
|
+
paddingX={1}
|
|
90
|
+
backgroundColor={nativePaneChrome ? colors.bg : colors.panel}
|
|
91
|
+
style={nativePaneChrome ? { padding: "8px 10px", borderRadius: 4 } : undefined}
|
|
92
|
+
data-gloom-role="mcp-connect-snippet"
|
|
93
|
+
>
|
|
94
|
+
{text.split("\n").map((line, index) => {
|
|
95
|
+
const atEdge = !!secret && line.includes(secret);
|
|
96
|
+
return (
|
|
97
|
+
<Text
|
|
98
|
+
key={index}
|
|
99
|
+
fg={colors.textBright}
|
|
100
|
+
wrapText
|
|
101
|
+
wrapMode={atEdge ? "char" : "word"}
|
|
102
|
+
style={nativePaneChrome && atEdge ? { wordBreak: "break-all" } : undefined}
|
|
103
|
+
>
|
|
104
|
+
{line || " "}
|
|
105
|
+
</Text>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</Box>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function McpConnectDialog({
|
|
113
|
+
dialogId,
|
|
114
|
+
dismiss,
|
|
115
|
+
width: openedWidth,
|
|
116
|
+
}: PromptContext<void> & { width: number }) {
|
|
117
|
+
useAppLanguage();
|
|
118
|
+
// Follows a window or terminal resized while the dialog is open. The
|
|
119
|
+
// terminal's box keeps the width it opened with, so the content never
|
|
120
|
+
// grows past it there; the desktop sizes the box to the content.
|
|
121
|
+
const viewport = useViewport();
|
|
122
|
+
const { nativePaneChrome } = useUiCapabilities();
|
|
123
|
+
const fit = mcpConnectContentWidth(viewport.width);
|
|
124
|
+
const width = nativePaneChrome ? fit : Math.min(openedWidth, fit);
|
|
125
|
+
const colors = useThemeColors();
|
|
126
|
+
const renderer = useRendererHost();
|
|
127
|
+
const access = usePlanAccess();
|
|
128
|
+
const signedIn = useCloudSignedIn();
|
|
129
|
+
const openUpgrade = useCloudUpgradeAction("mcp-connect");
|
|
130
|
+
const sections = useMcpConnectSections();
|
|
131
|
+
const endpoint = useMemo(() => currentMcpEndpointUrl(), []);
|
|
132
|
+
const [client, setClient] = useState<McpClientId>("claude-code");
|
|
133
|
+
const [key, setKey] = useState<KeyState>({ phase: "idle" });
|
|
134
|
+
const [copyStatus, setCopyStatus] = useState<Status>(null);
|
|
135
|
+
const [keyStatus, setKeyStatus] = useState<Status>(null);
|
|
136
|
+
const creatingRef = useRef(false);
|
|
137
|
+
const liveRef = useRef(true);
|
|
138
|
+
useEffect(() => () => {
|
|
139
|
+
liveRef.current = false;
|
|
140
|
+
}, []);
|
|
141
|
+
// A sign-in or an upgrade made from this dialog changes the session; a
|
|
142
|
+
// failure from before it no longer says anything.
|
|
143
|
+
useEffect(() => apiClient.subscribeCurrentUser(() => {
|
|
144
|
+
setKey((current) => (current.phase === "failed" ? { phase: "idle" } : current));
|
|
145
|
+
}), []);
|
|
146
|
+
|
|
147
|
+
const token = key.phase === "created" ? key.token : null;
|
|
148
|
+
const clientDef = MCP_CLIENTS.find((entry) => entry.id === client) ?? MCP_CLIENTS[0]!;
|
|
149
|
+
const setup = mcpClientSetup(client, endpoint, token);
|
|
150
|
+
|
|
151
|
+
const selectClient = useCallback((next: string) => {
|
|
152
|
+
if (!MCP_CLIENTS.some((entry) => entry.id === next)) return;
|
|
153
|
+
setClient(next as McpClientId);
|
|
154
|
+
setCopyStatus(null);
|
|
155
|
+
}, []);
|
|
156
|
+
|
|
157
|
+
const copy = useCallback(async (text: string, done: string, report: (status: Status) => void) => {
|
|
158
|
+
try {
|
|
159
|
+
await renderer.copyText(text);
|
|
160
|
+
report({ tone: "positive", text: done });
|
|
161
|
+
} catch {
|
|
162
|
+
report({ tone: "warning", text: t("Copy failed. Select the text above.") });
|
|
163
|
+
}
|
|
164
|
+
}, [renderer]);
|
|
165
|
+
|
|
166
|
+
const copySetup = useCallback(() => copy(setup.text, t("Copied."), setCopyStatus), [copy, setup.text]);
|
|
167
|
+
|
|
168
|
+
const createKey = useCallback(async () => {
|
|
169
|
+
if (creatingRef.current || token) return;
|
|
170
|
+
creatingRef.current = true;
|
|
171
|
+
setKey({ phase: "creating" });
|
|
172
|
+
setKeyStatus(null);
|
|
173
|
+
try {
|
|
174
|
+
const created = await apiClient.createMcpKey({ name: clientDef.keyName, scope: KEY_SCOPE });
|
|
175
|
+
if (!liveRef.current) return;
|
|
176
|
+
setKey({ phase: "created", token: created.token, name: created.apiKey.name || clientDef.keyName });
|
|
177
|
+
setCopyStatus(null);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (liveRef.current) setKey({ phase: "failed", failure: mcpKeyFailure(error) });
|
|
180
|
+
} finally {
|
|
181
|
+
creatingRef.current = false;
|
|
182
|
+
}
|
|
183
|
+
}, [clientDef.keyName, token]);
|
|
184
|
+
|
|
185
|
+
const signIn = useCallback(() => {
|
|
186
|
+
// Stacks over this dialog; the key row follows the session once it lands.
|
|
187
|
+
if (!requestAuthDialog({ mode: "login" })) {
|
|
188
|
+
setKeyStatus({ tone: "warning", text: t("Sign-in is not available right now.") });
|
|
189
|
+
}
|
|
190
|
+
}, []);
|
|
191
|
+
|
|
192
|
+
const failure = key.phase === "failed" ? key.failure : null;
|
|
193
|
+
// `hint` is the footer's word for the same action.
|
|
194
|
+
const keyAction = ((): { label: string; hint: string; run: () => void; disabled?: boolean } => {
|
|
195
|
+
if (token) return { label: t("Copy key"), hint: t("copy key"), run: () => { void copy(token, t("Key copied."), setKeyStatus); } };
|
|
196
|
+
if (!signedIn || failure?.kind === "signin") return { label: t("Sign in to Gloom Cloud"), hint: t("sign in"), run: signIn };
|
|
197
|
+
if (!access.hasProAccess || failure?.kind === "pro") {
|
|
198
|
+
return { label: t("Upgrade to Pro"), hint: t("upgrade"), run: () => { void openUpgrade(); } };
|
|
199
|
+
}
|
|
200
|
+
if (key.phase === "creating") return { label: t("Creating..."), hint: t("create a key"), run: () => {}, disabled: true };
|
|
201
|
+
return { label: t("Create a key"), hint: t("create a key"), run: () => { void createKey(); } };
|
|
202
|
+
})();
|
|
203
|
+
|
|
204
|
+
// What the key row says beside its button: the plan the key needs, or why
|
|
205
|
+
// the last attempt failed. Signed out, the button says it all.
|
|
206
|
+
const keyNote: Status = (() => {
|
|
207
|
+
if (token || failure?.kind === "signin") return null;
|
|
208
|
+
if (failure?.kind === "offline") return { tone: "muted", text: t(failure.message) };
|
|
209
|
+
if (failure?.kind === "refused") return { tone: "warning", text: t(failure.message) };
|
|
210
|
+
if (signedIn && (!access.hasProAccess || failure?.kind === "pro")) {
|
|
211
|
+
return { tone: "muted", text: t("The MCP server is part of Gloom Pro.") };
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
})();
|
|
215
|
+
|
|
216
|
+
useDialogKeyboard((event) => {
|
|
217
|
+
if (isPlainKey(event, "c", "enter", "return")) {
|
|
218
|
+
event.preventDefault?.();
|
|
219
|
+
event.stopPropagation?.();
|
|
220
|
+
void copySetup();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (isPlainKey(event, "k")) {
|
|
224
|
+
event.preventDefault?.();
|
|
225
|
+
event.stopPropagation?.();
|
|
226
|
+
if (!keyAction.disabled) keyAction.run();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (isPlainKey(event, "d")) {
|
|
230
|
+
event.preventDefault?.();
|
|
231
|
+
event.stopPropagation?.();
|
|
232
|
+
void renderer.openExternal(MCP_DOCS_URL).catch(() => {});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (event.name === "escape") {
|
|
236
|
+
event.stopPropagation?.();
|
|
237
|
+
dismiss();
|
|
238
|
+
}
|
|
239
|
+
}, { scope: dialogId });
|
|
240
|
+
|
|
241
|
+
const footer = tf("c copy · ←/→ client · k {action} · d docs · Esc close", { action: keyAction.hint });
|
|
242
|
+
const narrow = width < 46;
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<DialogFrame
|
|
246
|
+
title={t("Connect an AI assistant")}
|
|
247
|
+
subtitle={t("Gloom's research tools in Claude Code, Codex, Cursor or any MCP client.")}
|
|
248
|
+
// Too narrow for the key line, which is a keyboard's business anyway (a phone).
|
|
249
|
+
footer={narrow ? undefined : footer}
|
|
250
|
+
onClose={dismiss}
|
|
251
|
+
>
|
|
252
|
+
<Box flexDirection="column" width={width} gap={1}>
|
|
253
|
+
{/* A row, so the desktop control is as wide as its options rather than the dialog. */}
|
|
254
|
+
<Box flexDirection="row">
|
|
255
|
+
<SegmentedControl
|
|
256
|
+
options={MCP_CLIENTS.map((entry) => ({ label: t(entry.label), value: entry.id }))}
|
|
257
|
+
value={client}
|
|
258
|
+
onChange={selectClient}
|
|
259
|
+
focused
|
|
260
|
+
shortcutScope={dialogId}
|
|
261
|
+
wrap={narrow}
|
|
262
|
+
/>
|
|
263
|
+
</Box>
|
|
264
|
+
|
|
265
|
+
<Box flexDirection="column">
|
|
266
|
+
<Text fg={colors.textDim} wrapText>{t(setup.hint)}</Text>
|
|
267
|
+
<Box height={1} />
|
|
268
|
+
<SnippetBlock text={setup.text} secret={token} width={width} />
|
|
269
|
+
<Box height={1} />
|
|
270
|
+
<Box flexDirection="row" gap={2} alignItems="center">
|
|
271
|
+
<Button label={t("Copy")} shortcut="c" variant="primary" onPress={() => { void copySetup(); }} />
|
|
272
|
+
{copyStatus ? <Text fg={statusColor(copyStatus.tone, colors)}>{copyStatus.text}</Text> : null}
|
|
273
|
+
</Box>
|
|
274
|
+
</Box>
|
|
275
|
+
|
|
276
|
+
<Box flexDirection="column" data-gloom-role="mcp-connect-key">
|
|
277
|
+
{/* The terminal separates the two paths with a blank row; the desktop draws a rule. */}
|
|
278
|
+
{nativePaneChrome ? <Divider width={width} /> : null}
|
|
279
|
+
{key.phase === "created" ? (
|
|
280
|
+
<Text fg={colors.warning} attributes={TextAttributes.BOLD} wrapText>
|
|
281
|
+
{tf("Key for {name} created. It is filled in above and is shown only once.", { name: key.name })}
|
|
282
|
+
</Text>
|
|
283
|
+
) : (
|
|
284
|
+
<Text fg={colors.textDim}>{t("Use a key instead")}</Text>
|
|
285
|
+
)}
|
|
286
|
+
<Box flexDirection="row" gap={2} alignItems="center" flexWrap="wrap">
|
|
287
|
+
<Button
|
|
288
|
+
label={keyAction.label}
|
|
289
|
+
shortcut="k"
|
|
290
|
+
disabled={keyAction.disabled}
|
|
291
|
+
onPress={keyAction.run}
|
|
292
|
+
/>
|
|
293
|
+
{keyStatus ?? keyNote ? (
|
|
294
|
+
<Text fg={statusColor((keyStatus ?? keyNote)!.tone, colors)} wrapText>{(keyStatus ?? keyNote)!.text}</Text>
|
|
295
|
+
) : null}
|
|
296
|
+
</Box>
|
|
297
|
+
</Box>
|
|
298
|
+
|
|
299
|
+
{sections.map((section) => (
|
|
300
|
+
<Box key={section.id} flexDirection="column">
|
|
301
|
+
{nativePaneChrome ? <Divider width={width} /> : null}
|
|
302
|
+
<section.Component width={width} dialogId={dialogId} dismiss={dismiss} />
|
|
303
|
+
</Box>
|
|
304
|
+
))}
|
|
305
|
+
|
|
306
|
+
<Box flexDirection="row" gap={1}>
|
|
307
|
+
<Text fg={colors.textDim}>{t("Tools and access levels:")}</Text>
|
|
308
|
+
<ExternalLinkText url={MCP_DOCS_URL} label="gloom.sh/docs/mcp" />
|
|
309
|
+
</Box>
|
|
310
|
+
</Box>
|
|
311
|
+
</DialogFrame>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** Opens the dialog and settles once it closes. */
|
|
316
|
+
export function openMcpConnectDialog(dialog: DialogApi, options: { viewportWidth?: number } = {}): Promise<void> {
|
|
317
|
+
const width = mcpConnectContentWidth(options.viewportWidth);
|
|
318
|
+
return dialog
|
|
319
|
+
.prompt<void>({
|
|
320
|
+
// A click beside it must not throw away a key that is shown once.
|
|
321
|
+
closeOnClickOutside: false,
|
|
322
|
+
// The terminal host defaults to 60 columns; the desktop sizes to the content.
|
|
323
|
+
style: { width: width + TERMINAL_DIALOG_CHROME },
|
|
324
|
+
content: (context) => <McpConnectDialog {...context} width={width} />,
|
|
325
|
+
})
|
|
326
|
+
.then(() => {}, () => {});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const dialogRequestListeners = new Set<() => void>();
|
|
330
|
+
|
|
331
|
+
/** Returns false when no dialog host is mounted, so callers can fall back. */
|
|
332
|
+
export function requestMcpConnectDialog(): boolean {
|
|
333
|
+
if (dialogRequestListeners.size === 0) return false;
|
|
334
|
+
for (const listener of dialogRequestListeners) listener();
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function McpConnectDialogHost() {
|
|
339
|
+
const dialog = useDialog();
|
|
340
|
+
const viewport = useViewport();
|
|
341
|
+
const viewportWidthRef = useRef(viewport.width);
|
|
342
|
+
viewportWidthRef.current = viewport.width;
|
|
343
|
+
const openRef = useRef(false);
|
|
344
|
+
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
const listener = () => {
|
|
347
|
+
if (openRef.current) return;
|
|
348
|
+
openRef.current = true;
|
|
349
|
+
void openMcpConnectDialog(dialog, { viewportWidth: viewportWidthRef.current }).finally(() => {
|
|
350
|
+
openRef.current = false;
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
dialogRequestListeners.add(listener);
|
|
354
|
+
return () => {
|
|
355
|
+
dialogRequestListeners.delete(listener);
|
|
356
|
+
};
|
|
357
|
+
}, [dialog]);
|
|
358
|
+
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the "Connect an AI assistant" dialog shows for each MCP client: the
|
|
3
|
+
* endpoint this build talks to, and the one command or config snippet the
|
|
4
|
+
* client needs. The commands are the ones gloom.sh/docs/mcp gives; change them
|
|
5
|
+
* there first.
|
|
6
|
+
*/
|
|
7
|
+
import { ApiRequestError } from "../../../../api-client/errors";
|
|
8
|
+
import { DEFAULT_API_URL, getCloudApiBaseUrl } from "../../../../api-client/request";
|
|
9
|
+
|
|
10
|
+
const MCP_ENDPOINT_PATH = "/mcp";
|
|
11
|
+
/** What a snippet shows until a key is created; the docs use the same stand-in. */
|
|
12
|
+
const MCP_KEY_PLACEHOLDER = "gloom_mcp_...";
|
|
13
|
+
export const MCP_DOCS_URL = "https://gloom.sh/docs/mcp";
|
|
14
|
+
|
|
15
|
+
export type McpClientId = "claude-code" | "codex" | "json" | "other";
|
|
16
|
+
|
|
17
|
+
export interface McpClient {
|
|
18
|
+
id: McpClientId;
|
|
19
|
+
label: string;
|
|
20
|
+
/** The key's name in Cloud settings, so it can be told apart and revoked later. */
|
|
21
|
+
keyName: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const MCP_CLIENTS: readonly McpClient[] = [
|
|
25
|
+
{ id: "claude-code", label: "Claude Code", keyName: "Claude Code" },
|
|
26
|
+
{ id: "codex", label: "Codex", keyName: "Codex" },
|
|
27
|
+
{ id: "json", label: "Cursor / JSON", keyName: "Cursor" },
|
|
28
|
+
{ id: "other", label: "Other", keyName: "MCP client" },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The MCP endpoint next to the API this build is configured for, so a dev or
|
|
33
|
+
* staging build shows its own server. The web app reaches the API through its
|
|
34
|
+
* own `/api` path, which only forwards to the public API and which an MCP
|
|
35
|
+
* client outside the browser cannot use, so it gets the public endpoint.
|
|
36
|
+
*/
|
|
37
|
+
export function mcpEndpointUrl(apiBaseUrl: string, pageOrigin: string | null): string {
|
|
38
|
+
const base = apiBaseUrl.trim().replace(/\/+$/, "");
|
|
39
|
+
if (pageOrigin && base === `${pageOrigin}/api`) return `${DEFAULT_API_URL}${MCP_ENDPOINT_PATH}`;
|
|
40
|
+
return `${base}${MCP_ENDPOINT_PATH}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function currentMcpEndpointUrl(): string {
|
|
44
|
+
const origin = typeof location !== "undefined" && typeof location.origin === "string" ? location.origin : null;
|
|
45
|
+
return mcpEndpointUrl(getCloudApiBaseUrl(), origin);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface McpKeyFailure {
|
|
49
|
+
/**
|
|
50
|
+
* `pro`: the account needs Pro. `signin`: the session is gone. `refused`: the
|
|
51
|
+
* server said no and why (the key limit). `offline`: no answer from the server.
|
|
52
|
+
*/
|
|
53
|
+
kind: "pro" | "signin" | "offline" | "refused";
|
|
54
|
+
message: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Why creating a key failed, in one calm line. A refusal the server explains
|
|
59
|
+
* (the key limit) keeps the server's words; anything without an answer from
|
|
60
|
+
* the server reads as unreachable.
|
|
61
|
+
*/
|
|
62
|
+
export function mcpKeyFailure(error: unknown): McpKeyFailure {
|
|
63
|
+
if (error instanceof ApiRequestError && error.status !== undefined) {
|
|
64
|
+
if (error.status === 402) return { kind: "pro", message: "The MCP server is part of Gloom Pro." };
|
|
65
|
+
if (error.status === 401) return { kind: "signin", message: "Sign in to Gloom Cloud again." };
|
|
66
|
+
if (error.status === 429) return { kind: "refused", message: "Too many requests. Try again in a moment." };
|
|
67
|
+
if (error.status >= 400 && error.status < 500 && error.status !== 408 && error.status !== 429 && error.message.trim()) {
|
|
68
|
+
return { kind: "refused", message: error.message.trim() };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { kind: "offline", message: "Gloom Cloud can't be reached right now. Try again in a moment." };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface McpClientSetup {
|
|
75
|
+
/** What Copy puts on the clipboard, exactly as shown. */
|
|
76
|
+
text: string;
|
|
77
|
+
/** One line on where it goes. */
|
|
78
|
+
hint: string;
|
|
79
|
+
/** The client connects only with a key, and none was created yet. */
|
|
80
|
+
needsKey: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The snippet for one client. Without a key, clients that sign in through the
|
|
85
|
+
* browser (Claude Code, Cursor and most recent clients) only need the
|
|
86
|
+
* endpoint; Codex takes a bearer token from an environment variable, so it
|
|
87
|
+
* shows the docs' placeholder until a key exists.
|
|
88
|
+
*/
|
|
89
|
+
export function mcpClientSetup(client: McpClientId, endpoint: string, key: string | null): McpClientSetup {
|
|
90
|
+
const token = key ?? MCP_KEY_PLACEHOLDER;
|
|
91
|
+
switch (client) {
|
|
92
|
+
case "claude-code":
|
|
93
|
+
return key
|
|
94
|
+
? {
|
|
95
|
+
text: `claude mcp add --transport http gloom ${endpoint} \\\n --header "Authorization: Bearer ${key}"`,
|
|
96
|
+
hint: "Run it in a terminal.",
|
|
97
|
+
needsKey: false,
|
|
98
|
+
}
|
|
99
|
+
: {
|
|
100
|
+
text: `claude mcp add --transport http gloom ${endpoint}`,
|
|
101
|
+
hint: "Run it in a terminal. Claude Code signs in through your browser.",
|
|
102
|
+
needsKey: false,
|
|
103
|
+
};
|
|
104
|
+
case "codex":
|
|
105
|
+
return {
|
|
106
|
+
text: `export GLOOM_MCP_KEY=${token}\ncodex mcp add gloom --url ${endpoint} \\\n --bearer-token-env-var GLOOM_MCP_KEY`,
|
|
107
|
+
hint: key ? "Run it in a terminal." : "Codex connects with a key. Create one below.",
|
|
108
|
+
needsKey: !key,
|
|
109
|
+
};
|
|
110
|
+
case "json": {
|
|
111
|
+
const server = key
|
|
112
|
+
? `{\n "url": ${JSON.stringify(endpoint)},\n "headers": { "Authorization": ${JSON.stringify(`Bearer ${key}`)} }\n }`
|
|
113
|
+
: `{ "url": ${JSON.stringify(endpoint)} }`;
|
|
114
|
+
return {
|
|
115
|
+
text: `{\n "mcpServers": {\n "gloom": ${server}\n }\n}`,
|
|
116
|
+
hint: key
|
|
117
|
+
? "Add it to the client's MCP config."
|
|
118
|
+
: "Add it to the client's MCP config. The client signs in on first use.",
|
|
119
|
+
needsKey: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
case "other":
|
|
123
|
+
return key
|
|
124
|
+
? {
|
|
125
|
+
text: `${endpoint}\nAuthorization: Bearer ${token}`,
|
|
126
|
+
hint: "The endpoint, and the header that sends the key.",
|
|
127
|
+
needsKey: false,
|
|
128
|
+
}
|
|
129
|
+
: {
|
|
130
|
+
text: endpoint,
|
|
131
|
+
hint: "The endpoint. Clients sign in on first use, or send a key as a bearer token.",
|
|
132
|
+
needsKey: false,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EXTENSION POINT for the "Connect an AI assistant" dialog.
|
|
3
|
+
*
|
|
4
|
+
* The dialog sets up a client. Anything that reports on clients already
|
|
5
|
+
* connected registers a section here and the dialog renders it under the key
|
|
6
|
+
* row, in `order`, separated by a divider. The terminal control work ("Allow
|
|
7
|
+
* it to control this terminal": which assistants are connected or approved,
|
|
8
|
+
* and revoking one) plugs in here rather than growing the dialog itself.
|
|
9
|
+
*
|
|
10
|
+
* A section renders inside the dialog, so it gets the content width and the
|
|
11
|
+
* dialog's keyboard scope; keys it binds must not clash with the dialog's own
|
|
12
|
+
* (`c`, `k`, Left/Right, Enter, Esc).
|
|
13
|
+
*/
|
|
14
|
+
import { useSyncExternalStore, type ComponentType } from "react";
|
|
15
|
+
|
|
16
|
+
export interface McpConnectSectionProps {
|
|
17
|
+
/** Content width in cells. */
|
|
18
|
+
width: number;
|
|
19
|
+
/** The dialog's keyboard scope, for `useDialogKeyboard`. */
|
|
20
|
+
dialogId?: string;
|
|
21
|
+
/** Closes the dialog, for a section that sends the person somewhere else. */
|
|
22
|
+
dismiss?: () => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface McpConnectSection {
|
|
26
|
+
id: string;
|
|
27
|
+
/** Lower first. */
|
|
28
|
+
order?: number;
|
|
29
|
+
Component: ComponentType<McpConnectSectionProps>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const sections = new Map<string, McpConnectSection>();
|
|
33
|
+
const listeners = new Set<() => void>();
|
|
34
|
+
let snapshot: readonly McpConnectSection[] = [];
|
|
35
|
+
|
|
36
|
+
function publish(): void {
|
|
37
|
+
snapshot = [...sections.values()].sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
|
|
38
|
+
for (const listener of listeners) listener();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Adds a section to the dialog; the returned function removes it. A second registration with the same id replaces the first. */
|
|
42
|
+
export function registerMcpConnectSection(section: McpConnectSection): () => void {
|
|
43
|
+
sections.set(section.id, section);
|
|
44
|
+
publish();
|
|
45
|
+
return () => {
|
|
46
|
+
if (sections.get(section.id) !== section) return;
|
|
47
|
+
sections.delete(section.id);
|
|
48
|
+
publish();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function subscribe(listener: () => void): () => void {
|
|
53
|
+
listeners.add(listener);
|
|
54
|
+
return () => {
|
|
55
|
+
listeners.delete(listener);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The registered sections, in order; follows registrations made while the dialog is open. */
|
|
60
|
+
export function useMcpConnectSections(): readonly McpConnectSection[] {
|
|
61
|
+
return useSyncExternalStore(subscribe, () => snapshot, () => snapshot);
|
|
62
|
+
}
|
|
@@ -14,12 +14,17 @@ import { askgConversationListStore } from "./askg/conversation-store";
|
|
|
14
14
|
import { ASKG_PANE_ID, ASKGPane } from "./askg/pane";
|
|
15
15
|
import { askGloomQuestion } from "./askg/pending-question";
|
|
16
16
|
import { registerCloudAuthCommands } from "./auth-commands";
|
|
17
|
+
import { registerMcpConnectCommand } from "./mcp-connect/command";
|
|
17
18
|
import { registerCloudUpgradeCommand } from "./upgrade-command";
|
|
18
19
|
import { CloudUpgradeStatusWidget } from "./upgrade-status-widget";
|
|
19
20
|
import { registerTrialOfferCommand, TrialOfferStatusWidget } from "./trial-offer-status-widget";
|
|
20
21
|
import { CloudVerificationStatusWidget } from "./verification-status-widget";
|
|
21
22
|
import { createPublicPaneShare } from "../shared/public-pane";
|
|
22
23
|
import { teamModule } from "./team/module";
|
|
24
|
+
import { registerMcpConnectSection } from "./mcp-connect/sections";
|
|
25
|
+
import { TerminalControlSection } from "./terminal-relay/connect-section";
|
|
26
|
+
import { terminalRelayGrants } from "./terminal-relay/grants";
|
|
27
|
+
import { TerminalRelayStatusWidget } from "./terminal-relay/indicator";
|
|
23
28
|
import { thesisModule } from "./thesis/module";
|
|
24
29
|
|
|
25
30
|
function createCloudDataModule(): PluginModule {
|
|
@@ -39,7 +44,7 @@ function createCloudDataModule(): PluginModule {
|
|
|
39
44
|
};
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
/** Sign-in and
|
|
47
|
+
/** Sign-in, upgrade and MCP setup commands, and the email verification, upgrade and trial prompts. */
|
|
43
48
|
const cloudAccountModule: PluginModule = {
|
|
44
49
|
slots: {
|
|
45
50
|
"status:widget": () => (
|
|
@@ -54,6 +59,7 @@ const cloudAccountModule: PluginModule = {
|
|
|
54
59
|
registerCloudAuthCommands(ctx);
|
|
55
60
|
registerCloudUpgradeCommand(ctx);
|
|
56
61
|
registerTrialOfferCommand(ctx);
|
|
62
|
+
registerMcpConnectCommand(ctx);
|
|
57
63
|
},
|
|
58
64
|
};
|
|
59
65
|
|
|
@@ -95,6 +101,34 @@ const askgModule: PluginModule = {
|
|
|
95
101
|
}],
|
|
96
102
|
};
|
|
97
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Remote control from the Gloom Cloud MCP: which assistants were approved
|
|
106
|
+
* live with the profile, and the status chip shows one at work. The relay
|
|
107
|
+
* itself is mounted by the app (terminal-relay/host.tsx) and stays off while
|
|
108
|
+
* this plugin is.
|
|
109
|
+
*/
|
|
110
|
+
let removeTerminalControlSection: (() => void) | null = null;
|
|
111
|
+
|
|
112
|
+
const terminalRelayModule: PluginModule = {
|
|
113
|
+
slots: {
|
|
114
|
+
"status:widget": () => <TerminalRelayStatusWidget />,
|
|
115
|
+
},
|
|
116
|
+
setup(ctx) {
|
|
117
|
+
terminalRelayGrants.attach(ctx.persistence);
|
|
118
|
+
removeTerminalControlSection?.();
|
|
119
|
+
removeTerminalControlSection = registerMcpConnectSection({
|
|
120
|
+
id: "terminal-control",
|
|
121
|
+
order: 10,
|
|
122
|
+
Component: TerminalControlSection,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
dispose() {
|
|
126
|
+
removeTerminalControlSection?.();
|
|
127
|
+
removeTerminalControlSection = null;
|
|
128
|
+
terminalRelayGrants.detach();
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
98
132
|
const congressTradesModule: PluginModule = {
|
|
99
133
|
panes: [{
|
|
100
134
|
id: CONGRESS_TRADES_PANE_ID,
|
|
@@ -141,6 +175,7 @@ export function createGloomberbCloudPlugin(extraModules: readonly PluginModule[]
|
|
|
141
175
|
accountManagementModule,
|
|
142
176
|
cloudAccountModule,
|
|
143
177
|
askgModule,
|
|
178
|
+
terminalRelayModule,
|
|
144
179
|
...extraModules,
|
|
145
180
|
congressTradesModule,
|
|
146
181
|
cloudTweetsModule,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every ticker the app seeds: `DEFAULT_WATCHLIST_TICKERS` in
|
|
3
|
+
* src/state/app/bootstrap.ts and `FIRST_RUN_WATCHLIST` in
|
|
4
|
+
* src/components/onboarding/first-run-workspace.ts. A test keeps them equal.
|
|
5
|
+
*/
|
|
6
|
+
export const STARTER_SYMBOLS: ReadonlySet<string> = new Set([
|
|
7
|
+
"AAPL", "MSFT", "GOOGL", "AMZN", "NVDA", "TSLA", "META", "BRK.B", "JPM", "V", "BTC-USD", "ETH-USD", "SPY", "QQQ",
|
|
8
|
+
]);
|