gloomberb 0.5.0 → 0.6.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.
- package/README.md +91 -11
- package/package.json +12 -5
- package/src/app.tsx +414 -191
- package/src/architecture/import-boundaries.test.ts +118 -0
- package/src/assets/gloomberb-logo.png +0 -0
- package/src/assets/gloomberb-logo.svg +73 -0
- package/src/brokers/account-cache.ts +157 -0
- package/src/brokers/profile-form.test.ts +73 -0
- package/src/brokers/profile-form.ts +109 -0
- package/src/brokers/remote-broker-adapter.ts +91 -0
- package/src/brokers/sync-broker-instance.test.ts +124 -1
- package/src/brokers/sync-broker-instance.ts +113 -17
- package/src/capabilities/factories.ts +100 -0
- package/src/capabilities/index.ts +4 -0
- package/src/capabilities/registry.test.ts +137 -0
- package/src/capabilities/registry.ts +131 -0
- package/src/capabilities/service-capabilities.ts +27 -0
- package/src/capabilities/types.ts +84 -0
- package/src/cli/commands/ticker.ts +11 -3
- package/src/cli/context.ts +19 -13
- package/src/cli/helpers.ts +1 -0
- package/src/cli/types.ts +2 -2
- package/src/components/chart/chart-controller.ts +4 -24
- package/src/components/chart/chart-data.ts +3 -14
- package/src/components/chart/chart-indicator-selector.tsx +28 -6
- package/src/components/chart/chart-pane-settings.ts +15 -28
- package/src/components/chart/chart-renderer.ts +74 -99
- package/src/components/chart/chart-resolution.ts +10 -10
- package/src/components/chart/chart-scroll.test.ts +6 -5
- package/src/components/chart/chart-scroll.ts +2 -2
- package/src/components/chart/chart-types.ts +0 -1
- package/src/components/chart/chart-viewport.ts +2 -1
- package/src/components/chart/chart.test.ts +37 -0
- package/src/components/chart/comparison-chart-data.ts +3 -17
- package/src/components/chart/comparison-chart-renderer.test.ts +27 -4
- package/src/components/chart/comparison-chart-renderer.ts +1 -1
- package/src/components/chart/comparison-stock-chart.tsx +304 -108
- package/src/components/chart/cursor-motion.ts +3 -2
- package/src/components/chart/indicators/bands.test.ts +0 -14
- package/src/components/chart/indicators/moving-averages.test.ts +0 -15
- package/src/components/chart/indicators/options.ts +3 -3
- package/src/components/chart/indicators/oscillators.test.ts +0 -21
- package/src/components/chart/indicators/types.ts +1 -2
- package/src/components/chart/native/chart-rasterizer.ts +37 -44
- package/src/components/chart/native/kitty-adapter.ts +2 -5
- package/src/components/chart/native/kitty-manager.ts +1 -1
- package/src/components/chart/native/kitty-protocol.ts +0 -1
- package/src/components/chart/native/kitty-support.ts +1 -1
- package/src/components/chart/native/kitty.test.ts +30 -13
- package/src/components/chart/native/renderer-selection.ts +1 -1
- package/src/components/chart/native/surface-manager.ts +1 -1
- package/src/components/chart/static-chart-surface.test.tsx +51 -0
- package/src/components/chart/static-chart-surface.tsx +191 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +242 -35
- package/src/components/chart/stock-chart.tsx +438 -141
- package/src/components/command-bar/__snapshots__/command-bar.test.tsx.snap +4 -4
- package/src/components/command-bar/command-bar.test.tsx +269 -20
- package/src/components/command-bar/command-bar.tsx +830 -353
- package/src/components/command-bar/command-registry.ts +10 -20
- package/src/components/command-bar/helpers.test.ts +1 -1
- package/src/components/command-bar/helpers.ts +9 -11
- package/src/components/command-bar/pane-template-display.ts +4 -8
- package/src/components/command-bar/root-shortcuts.ts +12 -11
- package/src/components/command-bar/view-model.test.ts +8 -42
- package/src/components/command-bar/view-model.ts +8 -36
- package/src/components/command-bar/workflow-ops.test.ts +76 -2
- package/src/components/command-bar/workflow-ops.ts +40 -9
- package/src/components/command-bar/workflow-types.ts +2 -1
- package/src/components/data-table-stack-view.test.tsx +206 -0
- package/src/components/data-table-stack-view.tsx +152 -0
- package/src/components/data-table-view.test.tsx +188 -0
- package/src/components/data-table-view.tsx +279 -0
- package/src/components/feed-data-table-stack-view.test.tsx +225 -0
- package/src/components/feed-data-table-stack-view.tsx +365 -0
- package/src/components/index.ts +26 -31
- package/src/components/layout/detached-pane-shell.tsx +185 -0
- package/src/components/layout/floating-pane.tsx +87 -13
- package/src/components/layout/header.tsx +139 -49
- package/src/components/layout/pane-content.tsx +40 -0
- package/src/components/layout/pane-footer.test.tsx +195 -0
- package/src/components/layout/pane-footer.tsx +410 -0
- package/src/components/layout/pane-header.test.tsx +39 -0
- package/src/components/layout/pane-header.tsx +192 -14
- package/src/components/layout/pane-sizing.ts +13 -4
- package/src/components/layout/pane-title.ts +35 -0
- package/src/components/layout/pane.tsx +56 -8
- package/src/components/layout/shell.test.tsx +322 -19
- package/src/components/layout/shell.tsx +1010 -366
- package/src/components/layout/status-bar.test.tsx +17 -10
- package/src/components/layout/status-bar.tsx +367 -100
- package/src/components/layout/titlebar-overlay.ts +2 -0
- package/src/components/markdown-editor.tsx +29 -14
- package/src/components/markdown-text.tsx +25 -24
- package/src/components/onboarding/onboarding-wizard.test.tsx +8 -3
- package/src/components/onboarding/onboarding-wizard.tsx +347 -473
- package/src/components/pane-settings-dialog.tsx +521 -89
- package/src/components/pane-template-wizard.test.tsx +1 -1
- package/src/components/pane-template-wizard.tsx +38 -37
- package/src/components/price-selector-dialog.tsx +22 -21
- package/src/components/ticker-badge-list.tsx +53 -0
- package/src/components/ticker-badge-text.test.tsx +44 -1
- package/src/components/ticker-badge-text.tsx +78 -38
- package/src/components/ticker-badge.tsx +28 -7
- package/src/components/ticker-list-table-view.tsx +248 -0
- package/src/components/ticker-list-table.test.tsx +47 -1
- package/src/components/ticker-list-table.tsx +212 -41
- package/src/components/toggle-list.tsx +114 -8
- package/src/components/ui/button.tsx +24 -16
- package/src/components/ui/choice-dialog.tsx +89 -0
- package/src/components/ui/data-table.tsx +313 -114
- package/src/components/ui/external-link.tsx +52 -13
- package/src/components/ui/fields.tsx +44 -30
- package/src/components/ui/frame.tsx +24 -57
- package/src/components/ui/index.ts +15 -50
- package/src/components/ui/list-view.tsx +65 -25
- package/src/components/ui/loading.tsx +5 -60
- package/src/components/ui/message-composer.test.tsx +81 -0
- package/src/components/ui/message-composer.tsx +139 -0
- package/src/components/ui/multi-select-dialog.tsx +100 -32
- package/src/components/ui/multi-select.ts +112 -0
- package/src/components/ui/native-select.tsx +120 -0
- package/src/components/ui/page-stack-view.tsx +49 -19
- package/src/components/ui/remote-image.tsx +75 -0
- package/src/components/ui/row-value-cache.test.ts +17 -0
- package/src/components/ui/row-value-cache.ts +41 -0
- package/src/components/ui/shortcut-hint.tsx +55 -0
- package/src/components/ui/status.tsx +12 -67
- package/src/components/ui/table-layout.ts +59 -0
- package/src/components/ui/tabs.tsx +249 -27
- package/src/components/ui/toggle.tsx +19 -127
- package/src/components/ui/ui.test.tsx +266 -53
- package/src/components/use-double-click-activation.ts +7 -2
- package/src/components/use-external-link-footer.ts +79 -0
- package/src/core/app-services.ts +95 -0
- package/src/core/state/app-state.ts +886 -0
- package/src/{state → core/state}/session-persistence.ts +7 -6
- package/src/data/config-layout.ts +225 -0
- package/src/data/config-store-node.ts +390 -0
- package/src/data/config-store.test.ts +72 -0
- package/src/data/config-store.ts +35 -478
- package/src/data/sqlite-json.ts +2 -5
- package/src/data/sqlite-retry.ts +1 -0
- package/src/index.tsx +2 -68
- package/src/market-data/coordinator-subscriptions.test.ts +125 -0
- package/src/market-data/coordinator.test.ts +73 -4
- package/src/market-data/coordinator.ts +190 -24
- package/src/market-data/hooks.test.tsx +1 -1
- package/src/market-data/hooks.tsx +109 -87
- package/src/market-data/query-store.ts +3 -3
- package/src/market-data/request-types.test.ts +0 -14
- package/src/market-data/result-types.ts +1 -0
- package/src/news/aggregator.test.ts +267 -0
- package/src/news/aggregator.ts +452 -0
- package/src/news/hooks.ts +39 -0
- package/src/news/ticker-symbols.ts +21 -0
- package/src/news/types.ts +77 -0
- package/src/notifications/app-notifier.ts +21 -10
- package/src/pane-settings.test.ts +0 -2
- package/src/plugins/builtin/ai/ask-ai-detail-tab.tsx +124 -134
- package/src/plugins/builtin/ai/gloomberb-cli.ts +14 -13
- package/src/plugins/builtin/ai/providers.ts +1 -9
- package/src/plugins/builtin/ai/runner.ts +41 -5
- package/src/plugins/builtin/ai/screener-pane.test.tsx +37 -55
- package/src/plugins/builtin/ai/screener-pane.tsx +231 -276
- package/src/plugins/builtin/ai/settings.ts +0 -1
- package/src/plugins/builtin/ai/utils.ts +1 -71
- package/src/plugins/builtin/alerts/alert-engine.test.ts +1 -38
- package/src/plugins/builtin/alerts/index.test.tsx +61 -82
- package/src/plugins/builtin/alerts/index.tsx +263 -155
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.test.tsx +13 -8
- package/src/plugins/builtin/analytics/index.tsx +78 -120
- package/src/plugins/builtin/analytics/metrics.test.ts +0 -4
- package/src/plugins/builtin/ask-ai.test.tsx +55 -54
- package/src/plugins/builtin/broker-manager/index.test.tsx +168 -0
- package/src/plugins/builtin/broker-manager/index.tsx +759 -0
- package/src/plugins/builtin/broker-manager/model.test.ts +107 -0
- package/src/plugins/builtin/broker-manager/model.ts +142 -0
- package/src/plugins/builtin/chat-controller.test.ts +150 -0
- package/src/plugins/builtin/chat-controller.ts +88 -16
- package/src/plugins/builtin/chat.test.tsx +283 -36
- package/src/plugins/builtin/chat.tsx +373 -172
- package/src/plugins/builtin/cloud-auth-actions.tsx +72 -0
- package/src/plugins/builtin/cloud-tweets.tsx +896 -0
- package/src/plugins/builtin/comparison-chart.test.tsx +46 -57
- package/src/plugins/builtin/comparison-chart.tsx +11 -11
- package/src/plugins/builtin/correlation/compute.test.ts +0 -17
- package/src/plugins/builtin/correlation/index.tsx +51 -46
- package/src/plugins/builtin/correlation/settings.ts +1 -2
- package/src/plugins/builtin/debug.tsx +99 -70
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +2 -2
- package/src/plugins/builtin/earnings/earnings-cache.ts +12 -3
- package/src/plugins/builtin/earnings/earnings-data.test.ts +42 -11
- package/src/plugins/builtin/earnings/earnings-data.ts +70 -5
- package/src/plugins/builtin/earnings/index.tsx +187 -94
- package/src/plugins/builtin/econ/calendar-source.ts +9 -116
- package/src/plugins/builtin/econ/fred-client.test.ts +1 -14
- package/src/plugins/builtin/econ/fred-series-map.ts +1 -0
- package/src/plugins/builtin/econ/index.tsx +236 -421
- package/src/plugins/builtin/econ/types.ts +1 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +43 -45
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -0
- package/src/plugins/builtin/help.test.tsx +120 -60
- package/src/plugins/builtin/help.tsx +266 -82
- package/src/plugins/builtin/holders/index.test.ts +45 -0
- package/src/plugins/builtin/holders/index.tsx +949 -0
- package/src/plugins/builtin/insider/index.tsx +207 -253
- package/src/plugins/builtin/insider/insider-data.test.ts +1 -11
- package/src/plugins/builtin/insider/insider-data.ts +1 -0
- package/src/plugins/builtin/layout-manager.tsx +12 -40
- package/src/plugins/builtin/market-movers/index.tsx +70 -144
- package/src/plugins/builtin/market-movers/screener.test.ts +0 -18
- package/src/plugins/builtin/news-wire/breaking-notifications.test.ts +98 -0
- package/src/plugins/builtin/news-wire/breaking-notifications.ts +110 -0
- package/src/plugins/builtin/news-wire/breaking-pane.tsx +157 -0
- package/src/plugins/builtin/news-wire/categories.ts +114 -0
- package/src/plugins/builtin/news-wire/default-feeds.ts +3 -0
- package/src/plugins/builtin/news-wire/digest-store.ts +44 -0
- package/src/plugins/builtin/news-wire/feed-config.test.ts +90 -0
- package/src/plugins/builtin/news-wire/feed-config.ts +216 -0
- package/src/plugins/builtin/news-wire/feed-pane.tsx +21 -0
- package/src/plugins/builtin/news-wire/hash.ts +9 -0
- package/src/plugins/builtin/news-wire/index.tsx +92 -0
- package/src/plugins/builtin/news-wire/industry-pane.tsx +140 -0
- package/src/plugins/builtin/news-wire/news-detail-view.tsx +256 -0
- package/src/plugins/builtin/news-wire/news-footer.ts +28 -0
- package/src/plugins/builtin/news-wire/news-preset-pane.tsx +94 -0
- package/src/plugins/builtin/news-wire/news-query-presets.ts +40 -0
- package/src/plugins/builtin/news-wire/news-table.test.tsx +206 -0
- package/src/plugins/builtin/news-wire/news-table.tsx +296 -0
- package/src/plugins/builtin/news-wire/persisted-articles.ts +92 -0
- package/src/plugins/builtin/news-wire/read-state.test.ts +30 -0
- package/src/plugins/builtin/news-wire/read-state.ts +76 -0
- package/src/plugins/builtin/news-wire/rss-parser.test.ts +184 -0
- package/src/plugins/builtin/news-wire/rss-parser.ts +213 -0
- package/src/plugins/builtin/news-wire/rss-source.test.ts +159 -0
- package/src/plugins/builtin/news-wire/rss-source.ts +181 -0
- package/src/plugins/builtin/news-wire/top-pane.tsx +21 -0
- package/src/plugins/builtin/news.tsx +98 -25
- package/src/plugins/builtin/notes-files.ts +51 -10
- package/src/plugins/builtin/notes.tsx +332 -131
- package/src/plugins/builtin/options.test.tsx +185 -0
- package/src/plugins/builtin/options.tsx +346 -206
- package/src/plugins/builtin/plugin-groups.ts +92 -0
- package/src/plugins/builtin/portfolio-list/header.tsx +72 -37
- package/src/plugins/builtin/portfolio-list/index.tsx +25 -42
- package/src/plugins/builtin/portfolio-list/metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane.tsx +246 -164
- package/src/plugins/builtin/portfolio-list/settings.ts +19 -51
- package/src/plugins/builtin/portfolio-list/summary.tsx +21 -16
- package/src/plugins/builtin/portfolio-list/table.tsx +66 -25
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.test.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/use-throttled-cursor-symbol.ts +1 -1
- package/src/plugins/builtin/portfolio-list.test.ts +60 -1
- package/src/plugins/builtin/portfolio-list.test.tsx +122 -55
- package/src/plugins/builtin/quote-monitor.test.tsx +2 -27
- package/src/plugins/builtin/research/index.tsx +724 -0
- package/src/plugins/builtin/sec.tsx +67 -19
- package/src/plugins/builtin/sectors/index.tsx +42 -80
- package/src/plugins/builtin/sectors/sector-data.ts +1 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +25 -39
- package/src/plugins/builtin/ticker-detail/financials-tab.tsx +108 -58
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +383 -271
- package/src/plugins/builtin/ticker-detail/pane.tsx +135 -49
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +43 -27
- package/src/plugins/builtin/ticker-detail/settings.ts +3 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +16 -10
- package/src/plugins/builtin/ticker-detail.test.tsx +156 -33
- package/src/plugins/builtin/ticker-surface.ts +68 -0
- package/src/plugins/builtin/world-indices/index.tsx +44 -82
- package/src/plugins/builtin/world-indices/indices.ts +1 -0
- package/src/plugins/builtin/yahoo.ts +55 -1
- package/src/plugins/builtin/yield-curve/index.tsx +87 -145
- package/src/plugins/builtin/yield-curve/treasury-data.ts +6 -77
- package/src/plugins/catalog-ui.test.ts +24 -0
- package/src/plugins/catalog-ui.ts +44 -0
- package/src/plugins/catalog.ts +2 -50
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/account-cache.test.ts +15 -14
- package/src/plugins/ibkr/account-cache.ts +15 -120
- package/src/plugins/ibkr/broker-adapter.ts +67 -0
- package/src/plugins/ibkr/config.test.ts +0 -18
- package/src/plugins/ibkr/dialogs.tsx +14 -56
- package/src/plugins/ibkr/flex.test.ts +57 -2
- package/src/plugins/ibkr/flex.ts +116 -12
- package/src/plugins/ibkr/gateway-helpers.ts +8 -3
- package/src/plugins/ibkr/gateway-service-native.ts +2381 -0
- package/src/plugins/ibkr/gateway-service.test.ts +47 -9
- package/src/plugins/ibkr/gateway-service.ts +264 -2235
- package/src/plugins/ibkr/gateway-types.ts +27 -0
- package/src/plugins/ibkr/index.test.tsx +3 -11
- package/src/plugins/ibkr/index.tsx +1 -0
- package/src/plugins/ibkr/price-normalization.test.ts +1 -6
- package/src/plugins/ibkr/price-normalization.ts +1 -0
- package/src/plugins/ibkr/trade-badge.tsx +6 -5
- package/src/plugins/ibkr/trade-tab.test.tsx +2 -2
- package/src/plugins/ibkr/trade-tab.tsx +164 -117
- package/src/plugins/ibkr/trading-pane.tsx +117 -83
- package/src/plugins/pane-manager.test.ts +3 -1
- package/src/plugins/pane-manager.ts +93 -19
- package/src/plugins/plugin-runtime.test.tsx +176 -74
- package/src/plugins/plugin-runtime.tsx +228 -10
- package/src/plugins/prediction-markets/cache.ts +49 -0
- package/src/plugins/prediction-markets/categories.ts +0 -10
- package/src/plugins/prediction-markets/chart.test.tsx +1 -1
- package/src/plugins/prediction-markets/chart.tsx +21 -20
- package/src/plugins/prediction-markets/cli.ts +1 -0
- package/src/plugins/prediction-markets/controller-data.ts +135 -89
- package/src/plugins/prediction-markets/controller.ts +37 -108
- package/src/plugins/prediction-markets/detail/book.tsx +23 -22
- package/src/plugins/prediction-markets/detail/outcomes.tsx +33 -32
- package/src/plugins/prediction-markets/detail/overview.tsx +17 -12
- package/src/plugins/prediction-markets/detail/pane.tsx +59 -45
- package/src/plugins/prediction-markets/detail/rules.tsx +22 -7
- package/src/plugins/prediction-markets/detail/shared.tsx +3 -2
- package/src/plugins/prediction-markets/detail/trades.tsx +12 -11
- package/src/plugins/prediction-markets/detail.test.tsx +1 -1
- package/src/plugins/prediction-markets/keyboard.ts +1 -0
- package/src/plugins/prediction-markets/launch.test.ts +1 -1
- package/src/plugins/prediction-markets/launch.ts +1 -1
- package/src/plugins/prediction-markets/metrics.ts +25 -12
- package/src/plugins/prediction-markets/pane.test.tsx +16 -7
- package/src/plugins/prediction-markets/pane.tsx +180 -82
- package/src/plugins/prediction-markets/plugin.test.tsx +37 -45
- package/src/plugins/prediction-markets/services/fetch.ts +27 -10
- package/src/plugins/prediction-markets/services/kalshi-adapter.ts +20 -3
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/settings.ts +0 -5
- package/src/plugins/prediction-markets/test-helpers.tsx +19 -44
- package/src/plugins/registry.test.ts +307 -0
- package/src/plugins/registry.ts +293 -82
- package/src/react/input.ts +53 -0
- package/src/react/plugins/plugin-slot.tsx +21 -0
- package/src/react/use-raf-callback.ts +44 -0
- package/src/renderers/electrobun/bun/application-menu-click.test.ts +41 -0
- package/src/renderers/electrobun/bun/application-menu-click.ts +55 -0
- package/src/renderers/electrobun/bun/application-menu.ts +118 -0
- package/src/renderers/electrobun/bun/context-menu-click.test.ts +67 -0
- package/src/renderers/electrobun/bun/context-menu-click.ts +61 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.test.ts +52 -0
- package/src/renderers/electrobun/bun/context-menu-normalize.ts +67 -0
- package/src/renderers/electrobun/bun/core-capabilities.ts +328 -0
- package/src/renderers/electrobun/bun/desktop-workspace.test.ts +63 -0
- package/src/renderers/electrobun/bun/desktop-workspace.ts +162 -0
- package/src/renderers/electrobun/bun/index.ts +1149 -0
- package/src/renderers/electrobun/bun/window-frame.test.ts +40 -0
- package/src/renderers/electrobun/bun/window-frame.ts +46 -0
- package/src/renderers/electrobun/shared/protocol.ts +72 -0
- package/src/renderers/electrobun/view/ai-host.ts +93 -0
- package/src/renderers/electrobun/view/app-services.ts +348 -0
- package/src/renderers/electrobun/view/application-menu-bridge.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +173 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +154 -0
- package/src/renderers/electrobun/view/config-host.ts +35 -0
- package/src/renderers/electrobun/view/context-menu.test.ts +140 -0
- package/src/renderers/electrobun/view/context-menu.ts +164 -0
- package/src/renderers/electrobun/view/data-table.tsx +773 -0
- package/src/renderers/electrobun/view/desktop-controls.tsx +651 -0
- package/src/renderers/electrobun/view/desktop-window-bridge.ts +59 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.test.ts +57 -0
- package/src/renderers/electrobun/view/desktop-window-snapshot.ts +41 -0
- package/src/renderers/electrobun/view/dialog-host.tsx +62 -0
- package/src/renderers/electrobun/view/http-fetch.ts +113 -0
- package/src/renderers/electrobun/view/input-host.tsx +65 -0
- package/src/renderers/electrobun/view/key-event.test.ts +34 -0
- package/src/renderers/electrobun/view/key-event.ts +134 -0
- package/src/renderers/electrobun/view/main.tsx +104 -0
- package/src/renderers/electrobun/view/native-renderer.ts +110 -0
- package/src/renderers/electrobun/view/native-stubs/chart-kitty-support.ts +9 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-manager.ts +25 -0
- package/src/renderers/electrobun/view/native-stubs/chart-surface-sync.ts +1 -0
- package/src/renderers/electrobun/view/notes-files.ts +56 -0
- package/src/renderers/electrobun/view/resource-store.test.ts +44 -0
- package/src/renderers/electrobun/view/resource-store.ts +106 -0
- package/src/renderers/electrobun/view/rpc-codec.ts +42 -0
- package/src/renderers/electrobun/view/scrollbar-activity.ts +23 -0
- package/src/renderers/electrobun/view/toast-host.tsx +65 -0
- package/src/renderers/electrobun/view/ui-host.tsx +1490 -0
- package/src/renderers/electrobun/view/update-host.ts +38 -0
- package/src/renderers/opentui/dialog-host.test.tsx +44 -0
- package/src/renderers/opentui/dialog-host.tsx +56 -0
- package/src/renderers/opentui/host.tsx +134 -0
- package/src/renderers/opentui/input-host.tsx +24 -0
- package/src/renderers/opentui/start.tsx +71 -0
- package/src/renderers/opentui/test-utils.tsx +76 -0
- package/src/renderers/opentui/toast-host.tsx +10 -0
- package/src/renderers/opentui/ui-host.tsx +97 -0
- package/src/sources/gloomberb-cloud.test.ts +288 -17
- package/src/sources/gloomberb-cloud.ts +176 -9
- package/src/sources/provider-errors.ts +1 -0
- package/src/sources/provider-router.test.ts +220 -81
- package/src/sources/provider-router.ts +294 -111
- package/src/sources/sec-edgar.ts +19 -6
- package/src/sources/yahoo-finance.test.ts +0 -5
- package/src/sources/yahoo-finance.ts +420 -11
- package/src/state/app-activity.ts +7 -7
- package/src/state/app-bootstrap.ts +114 -40
- package/src/state/app-context-selector.test.tsx +70 -1
- package/src/state/app-context.test.ts +71 -3
- package/src/state/app-context.test.tsx +14 -7
- package/src/state/app-context.tsx +205 -869
- package/src/state/config-save-scheduler.ts +35 -0
- package/src/state/persist-scheduler.test.ts +67 -0
- package/src/state/persist-scheduler.ts +69 -0
- package/src/state/selectors-ui.ts +55 -0
- package/src/state/selectors.ts +15 -71
- package/src/state/session-persistence.test.ts +1 -1
- package/src/state/ticker-refresh-queue.ts +52 -3
- package/src/state/use-inline-tickers.ts +18 -16
- package/src/state/use-quote-streaming.test.ts +0 -6
- package/src/state/use-quote-streaming.test.tsx +1 -1
- package/src/state/use-quote-streaming.ts +3 -2
- package/src/state/use-session-snapshot.ts +32 -17
- package/src/test-support/plugin-runtime.ts +149 -0
- package/src/theme/color-utils.ts +1 -0
- package/src/theme/colors.ts +16 -0
- package/src/types/broker.ts +16 -1
- package/src/types/capability-route-source.ts +23 -0
- package/src/types/config.test.ts +23 -0
- package/src/types/config.ts +145 -10
- package/src/types/context-menu.ts +95 -0
- package/src/types/data-provider.ts +34 -3
- package/src/types/desktop-menu.ts +13 -0
- package/src/types/desktop-window.ts +29 -0
- package/src/types/financials.ts +123 -2
- package/src/types/instrument.ts +1 -0
- package/src/types/news-source.ts +9 -0
- package/src/types/persistence.ts +1 -0
- package/src/types/plugin.ts +32 -8
- package/src/ui/ascii-font.ts +85 -0
- package/src/ui/context-menu.test.ts +94 -0
- package/src/ui/context-menu.tsx +433 -0
- package/src/ui/dialog.tsx +55 -0
- package/src/ui/host.tsx +361 -0
- package/src/ui/index.tsx +126 -0
- package/src/ui/toast.tsx +40 -0
- package/src/updater.test.ts +69 -0
- package/src/updater.ts +61 -18
- package/src/utils/api-client.test.ts +186 -6
- package/src/utils/api-client.ts +433 -33
- package/src/utils/back-navigation.ts +6 -0
- package/src/utils/cli-output.ts +1 -0
- package/src/utils/currency-units.test.ts +1 -6
- package/src/utils/currency-units.ts +1 -0
- package/src/utils/debug-log.ts +68 -0
- package/src/utils/dom-screenshot.ts +326 -0
- package/src/utils/exchange-rate-map.ts +1 -0
- package/src/utils/exchanges.ts +214 -0
- package/src/utils/format.test.ts +7 -0
- package/src/utils/format.ts +1 -0
- package/src/utils/fuzzy-search.ts +1 -0
- package/src/utils/html-entities.test.ts +8 -0
- package/src/utils/html-entities.ts +32 -0
- package/src/utils/http-transport.ts +11 -0
- package/src/utils/main-thread-monitor.ts +127 -0
- package/src/utils/perf-marks.ts +54 -0
- package/src/utils/quote-freshness.ts +27 -50
- package/src/utils/quote-resolution.test.ts +5 -5
- package/src/utils/quote-resolution.ts +0 -1
- package/src/utils/quote-time.ts +1 -5
- package/src/utils/sec.test.ts +0 -12
- package/src/utils/selection-clipboard.ts +14 -6
- package/src/utils/shortcut-labels.test.ts +20 -0
- package/src/utils/shortcut-labels.ts +67 -0
- package/src/utils/slugify.ts +1 -0
- package/src/utils/terminal-input-reset.ts +1 -0
- package/src/utils/text-wrap.ts +82 -0
- package/src/utils/throttled-fetch.test.ts +19 -0
- package/src/utils/throttled-fetch.ts +11 -9
- package/src/utils/ticker-list.test.ts +1 -7
- package/src/utils/ticker-list.ts +1 -0
- package/src/utils/timestamp.ts +29 -0
- package/src/utils/tweet-text.test.ts +13 -0
- package/src/utils/tweet-text.ts +18 -0
- package/src/utils/url.ts +11 -0
- package/src/version.ts +1 -1
- package/src/components/chart/chart-control-hint.tsx +0 -39
- package/src/components/detail-feed-view.test.tsx +0 -180
- package/src/components/detail-feed-view.tsx +0 -531
- package/src/components/layout/pixel-mouse-mode.test.ts +0 -122
- package/src/components/layout/pixel-mouse.test.ts +0 -40
- package/src/components/spinner.tsx +0 -2
- package/src/components/tab-bar.tsx +0 -17
- package/src/components/ui/multi-select-chips.tsx +0 -69
- package/src/plugins/builtin/ai/index.test.tsx +0 -139
- package/src/plugins/builtin/ask-ai.tsx +0 -9
- package/src/plugins/builtin/correlation/plugin.test.ts +0 -44
- package/src/plugins/builtin/correlation/settings.test.ts +0 -34
- package/src/plugins/builtin/econ/calendar-source.test.ts +0 -219
- package/src/plugins/builtin/econ/fred-client.ts +0 -79
- package/src/plugins/builtin/fred-settings.ts +0 -10
- package/src/plugins/builtin/options-availability.test.tsx +0 -179
- package/src/plugins/builtin/options-availability.ts +0 -158
- package/src/plugins/builtin/portfolio-list/metrics.test.ts +0 -61
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +0 -212
- package/src/plugins/prediction-markets/status.ts +0 -24
- package/src/plugins/prediction-markets/table.tsx +0 -87
- package/src/utils/quote-time.test.ts +0 -22
- package/src/utils/selection-clipboard.test.ts +0 -43
- package/src/utils/terminal-input-reset.test.ts +0 -50
package/src/utils/api-client.ts
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AnalystResearchData,
|
|
3
|
+
CompanyProfile,
|
|
4
|
+
CorporateActionsData,
|
|
5
|
+
Fundamentals,
|
|
6
|
+
HolderData,
|
|
7
|
+
HolderRecord,
|
|
8
|
+
PricePoint,
|
|
9
|
+
Quote,
|
|
10
|
+
TickerFinancials,
|
|
11
|
+
} from "../types/financials";
|
|
2
12
|
import type { InstrumentSearchResult } from "../types/instrument";
|
|
3
13
|
import { debugLog } from "./debug-log";
|
|
14
|
+
import { canonicalExchange, normalizeSymbol, publicTickerKey } from "./exchanges";
|
|
4
15
|
import { normalizeTimestamp } from "./timestamp";
|
|
5
16
|
|
|
6
17
|
const DEFAULT_API_URL = "https://api.gloom.sh";
|
|
7
18
|
const SESSION_COOKIE_NAMES = ["__Secure-gloomberb.session_token", "gloomberb.session_token"] as const;
|
|
8
19
|
const cloudApiLog = debugLog.createLogger("cloud-api");
|
|
20
|
+
const HARD_SESSION_INVALID_PATTERNS = [
|
|
21
|
+
/\b(user|account)\b.*\b(not found|deleted|removed|disabled|deactivated|suspended)\b/i,
|
|
22
|
+
/\b(user|account)\b.*\bdoes(?:\s+not|n't)\s+exist\b/i,
|
|
23
|
+
/\b(no|unknown|missing)\s+(user|account)\b/i,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
type CloudApiResponse = Pick<Response, "ok" | "status" | "headers" | "text">;
|
|
27
|
+
type CloudApiFetchTransport = (url: string, init?: RequestInit) => Promise<CloudApiResponse>;
|
|
28
|
+
|
|
29
|
+
let cloudApiFetchTransport: CloudApiFetchTransport = (url, init) => fetch(url, init);
|
|
30
|
+
|
|
31
|
+
export function setCloudApiFetchTransport(transport: CloudApiFetchTransport | null): void {
|
|
32
|
+
cloudApiFetchTransport = transport ?? ((url, init) => fetch(url, init));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getCloudApiBaseUrl(): string {
|
|
36
|
+
if (typeof process === "undefined") {
|
|
37
|
+
return DEFAULT_API_URL;
|
|
38
|
+
}
|
|
39
|
+
return process.env.GLOOMBERB_API_URL ?? DEFAULT_API_URL;
|
|
40
|
+
}
|
|
9
41
|
|
|
10
42
|
export interface ChatMessage {
|
|
11
43
|
id: string;
|
|
@@ -48,6 +80,24 @@ export interface CloudCompanyProfile extends CompanyProfile {}
|
|
|
48
80
|
|
|
49
81
|
export interface CloudFundamentals extends Fundamentals {}
|
|
50
82
|
|
|
83
|
+
export interface CloudHolderPayload extends HolderRecord {
|
|
84
|
+
providerId: "gloomberb-cloud";
|
|
85
|
+
ownerType: "institution";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CloudHoldersPayload extends HolderData {
|
|
89
|
+
providerId: "gloomberb-cloud";
|
|
90
|
+
holders: CloudHolderPayload[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface CloudAnalystResearchPayload extends AnalystResearchData {
|
|
94
|
+
providerId: "gloomberb-cloud";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface CloudCorporateActionsPayload extends CorporateActionsData {
|
|
98
|
+
providerId: "gloomberb-cloud";
|
|
99
|
+
}
|
|
100
|
+
|
|
51
101
|
export interface CloudPricePointPayload {
|
|
52
102
|
date: string;
|
|
53
103
|
open?: number;
|
|
@@ -57,6 +107,178 @@ export interface CloudPricePointPayload {
|
|
|
57
107
|
volume?: number;
|
|
58
108
|
}
|
|
59
109
|
|
|
110
|
+
export type CloudEconImpact = "high" | "medium" | "low";
|
|
111
|
+
|
|
112
|
+
export interface CloudEconEventPayload {
|
|
113
|
+
id: string;
|
|
114
|
+
date: string;
|
|
115
|
+
time: string;
|
|
116
|
+
country: string;
|
|
117
|
+
event: string;
|
|
118
|
+
actual: string | null;
|
|
119
|
+
forecast: string | null;
|
|
120
|
+
prior: string | null;
|
|
121
|
+
impact: CloudEconImpact;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface CloudFredObservationPayload {
|
|
125
|
+
date: string;
|
|
126
|
+
value: number | null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface CloudFredSeriesInfoPayload {
|
|
130
|
+
id: string;
|
|
131
|
+
title: string;
|
|
132
|
+
units: string;
|
|
133
|
+
frequency: string;
|
|
134
|
+
seasonalAdjustment: string;
|
|
135
|
+
source: string;
|
|
136
|
+
notes: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface CloudFredSeriesPayload {
|
|
140
|
+
observations: CloudFredObservationPayload[];
|
|
141
|
+
info: CloudFredSeriesInfoPayload | null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface CloudYieldPointPayload {
|
|
145
|
+
maturity: string;
|
|
146
|
+
maturityYears: number;
|
|
147
|
+
yield: number | null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface CloudNewsEntityPayload {
|
|
151
|
+
id: string;
|
|
152
|
+
entityType: string;
|
|
153
|
+
name: string;
|
|
154
|
+
symbol: string | null;
|
|
155
|
+
exchange: string | null;
|
|
156
|
+
canonicalTicker: string | null;
|
|
157
|
+
role: string | null;
|
|
158
|
+
confidence: number | null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface CloudNewsTickerLinkPayload {
|
|
162
|
+
symbol: string;
|
|
163
|
+
exchange: string;
|
|
164
|
+
canonicalTicker: string;
|
|
165
|
+
relationType: string;
|
|
166
|
+
displayTier: "primary" | "related";
|
|
167
|
+
confidence: number;
|
|
168
|
+
relevanceScore: number;
|
|
169
|
+
impactScore?: number;
|
|
170
|
+
sentiment?: "positive" | "neutral" | "negative" | null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface CloudNewsStoryItemPayload {
|
|
174
|
+
id: string;
|
|
175
|
+
sourceKey: string;
|
|
176
|
+
sourceName: string;
|
|
177
|
+
title: string;
|
|
178
|
+
summary?: string;
|
|
179
|
+
url: string;
|
|
180
|
+
publishedAt: string;
|
|
181
|
+
hasArticleText?: boolean;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface CloudNewsPayload {
|
|
185
|
+
id: string;
|
|
186
|
+
headline: string;
|
|
187
|
+
summary: string;
|
|
188
|
+
topic?: string;
|
|
189
|
+
topics?: string[];
|
|
190
|
+
category: string;
|
|
191
|
+
sentiment: "positive" | "neutral" | "negative";
|
|
192
|
+
sectors: string[];
|
|
193
|
+
scope?: string;
|
|
194
|
+
firstPublishedAt: string;
|
|
195
|
+
lastPublishedAt: string;
|
|
196
|
+
firstSeenAt: string;
|
|
197
|
+
lastSeenAt: string;
|
|
198
|
+
primaryUrl: string;
|
|
199
|
+
primarySource: string;
|
|
200
|
+
scores?: {
|
|
201
|
+
importance?: number;
|
|
202
|
+
urgency?: number;
|
|
203
|
+
marketImpact?: number;
|
|
204
|
+
novelty?: number;
|
|
205
|
+
confidence?: number;
|
|
206
|
+
};
|
|
207
|
+
flags?: {
|
|
208
|
+
breaking?: boolean;
|
|
209
|
+
developing?: boolean;
|
|
210
|
+
stale?: boolean;
|
|
211
|
+
};
|
|
212
|
+
variantCount: number;
|
|
213
|
+
sourceCount: number;
|
|
214
|
+
sources: string[];
|
|
215
|
+
entities: CloudNewsEntityPayload[];
|
|
216
|
+
tickerLinks: CloudNewsTickerLinkPayload[];
|
|
217
|
+
items?: CloudNewsStoryItemPayload[];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface CloudNewsListResponse {
|
|
221
|
+
items: CloudNewsPayload[];
|
|
222
|
+
nextCursor: string | null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface CloudTweetUserPayload {
|
|
226
|
+
id: string;
|
|
227
|
+
userName: string;
|
|
228
|
+
name: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface CloudTweetMetricsPayload {
|
|
232
|
+
retweets: number | null;
|
|
233
|
+
replies: number | null;
|
|
234
|
+
likes: number | null;
|
|
235
|
+
quotes: number | null;
|
|
236
|
+
views: number | null;
|
|
237
|
+
bookmarks: number | null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface CloudTweetMediaPayload {
|
|
241
|
+
type?: string;
|
|
242
|
+
url?: string;
|
|
243
|
+
mediaUrl?: string;
|
|
244
|
+
media_url?: string;
|
|
245
|
+
media_url_https?: string;
|
|
246
|
+
previewImageUrl?: string;
|
|
247
|
+
preview_image_url?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export interface CloudTweetPayload {
|
|
251
|
+
id: string;
|
|
252
|
+
url: string;
|
|
253
|
+
text: string;
|
|
254
|
+
createdAt: string;
|
|
255
|
+
lang: string;
|
|
256
|
+
isReply: boolean;
|
|
257
|
+
author: CloudTweetUserPayload;
|
|
258
|
+
metrics: CloudTweetMetricsPayload;
|
|
259
|
+
media?: CloudTweetMediaPayload[];
|
|
260
|
+
photos?: CloudTweetMediaPayload[];
|
|
261
|
+
images?: CloudTweetMediaPayload[];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export type CloudTweetQueryType = "Latest" | "Top";
|
|
265
|
+
|
|
266
|
+
export interface CloudTweetSearchResponse {
|
|
267
|
+
ticker?: string;
|
|
268
|
+
cashtag?: string;
|
|
269
|
+
query: string;
|
|
270
|
+
queryType: CloudTweetQueryType;
|
|
271
|
+
since: string;
|
|
272
|
+
until: string;
|
|
273
|
+
limit: number;
|
|
274
|
+
hours: number;
|
|
275
|
+
includeReplies?: boolean;
|
|
276
|
+
cached: boolean;
|
|
277
|
+
cacheTtlMs: number;
|
|
278
|
+
asOf: string;
|
|
279
|
+
tweets: CloudTweetPayload[];
|
|
280
|
+
}
|
|
281
|
+
|
|
60
282
|
export type CloudMarketStatus =
|
|
61
283
|
| "success"
|
|
62
284
|
| "partial"
|
|
@@ -95,17 +317,9 @@ export interface QuoteStreamTarget {
|
|
|
95
317
|
exchange?: string;
|
|
96
318
|
}
|
|
97
319
|
|
|
98
|
-
function normalizeSymbol(value: string): string {
|
|
99
|
-
return value.trim().toUpperCase();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function normalizeExchange(value?: string): string {
|
|
103
|
-
return (value ?? "").trim().toUpperCase();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
320
|
function marketKey(symbol: string, exchange?: string): string {
|
|
107
321
|
const normalizedSymbolValue = normalizeSymbol(symbol);
|
|
108
|
-
const normalizedExchangeValue =
|
|
322
|
+
const normalizedExchangeValue = canonicalExchange(exchange);
|
|
109
323
|
return normalizedExchangeValue ? `${normalizedSymbolValue}:${normalizedExchangeValue}` : normalizedSymbolValue;
|
|
110
324
|
}
|
|
111
325
|
|
|
@@ -120,6 +334,23 @@ function normalizeChatMessages(messages: ChatMessage[]): ChatMessage[] {
|
|
|
120
334
|
return messages.map((message) => normalizeChatMessage(message));
|
|
121
335
|
}
|
|
122
336
|
|
|
337
|
+
function normalizeTweet(tweet: CloudTweetPayload): CloudTweetPayload {
|
|
338
|
+
return {
|
|
339
|
+
...tweet,
|
|
340
|
+
createdAt: normalizeTimestamp(tweet.createdAt),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function normalizeTweetSearchResponse(response: CloudTweetSearchResponse): CloudTweetSearchResponse {
|
|
345
|
+
return {
|
|
346
|
+
...response,
|
|
347
|
+
since: normalizeTimestamp(response.since),
|
|
348
|
+
until: normalizeTimestamp(response.until),
|
|
349
|
+
asOf: normalizeTimestamp(response.asOf),
|
|
350
|
+
tweets: response.tweets.map(normalizeTweet),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
123
354
|
type ChannelListener = (message: ChatMessage) => void;
|
|
124
355
|
type QuoteListener = (target: QuoteStreamTarget, quote: CloudQuotePayload) => void;
|
|
125
356
|
type SessionCookieName = (typeof SESSION_COOKIE_NAMES)[number];
|
|
@@ -131,6 +362,22 @@ class ApiRequestError extends Error {
|
|
|
131
362
|
}
|
|
132
363
|
}
|
|
133
364
|
|
|
365
|
+
function parseApiErrorMessage(body: string): string {
|
|
366
|
+
try {
|
|
367
|
+
const parsed = JSON.parse(body) as Record<string, unknown>;
|
|
368
|
+
const parts = [parsed.message, parsed.error, parsed.code, parsed.reason]
|
|
369
|
+
.filter((value): value is string => typeof value === "string" && value.trim().length > 0);
|
|
370
|
+
return parts.join(" ") || body;
|
|
371
|
+
} catch {
|
|
372
|
+
return body;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function isHardSessionInvalidMessage(message: string): boolean {
|
|
377
|
+
const normalized = message.replace(/[_-]+/g, " ");
|
|
378
|
+
return HARD_SESSION_INVALID_PATTERNS.some((pattern) => pattern.test(normalized));
|
|
379
|
+
}
|
|
380
|
+
|
|
134
381
|
class GloomApiClient {
|
|
135
382
|
private sessionToken: string | null = null;
|
|
136
383
|
private sessionCookieName: SessionCookieName | null = null;
|
|
@@ -147,7 +394,7 @@ class GloomApiClient {
|
|
|
147
394
|
private readonly quoteTargets = new Map<string, QuoteStreamTarget>();
|
|
148
395
|
|
|
149
396
|
constructor() {
|
|
150
|
-
this.baseUrl =
|
|
397
|
+
this.baseUrl = getCloudApiBaseUrl();
|
|
151
398
|
}
|
|
152
399
|
|
|
153
400
|
getSessionToken(): string | null {
|
|
@@ -213,6 +460,13 @@ class GloomApiClient {
|
|
|
213
460
|
this.ensureSocket();
|
|
214
461
|
}
|
|
215
462
|
|
|
463
|
+
private requireCapturedSession(message: string): void {
|
|
464
|
+
if (this.sessionToken) return;
|
|
465
|
+
this.websocketToken = null;
|
|
466
|
+
this.setCurrentUser(null);
|
|
467
|
+
throw new Error(message);
|
|
468
|
+
}
|
|
469
|
+
|
|
216
470
|
private extractSessionCookie(res: Response): void {
|
|
217
471
|
const setCookie = res.headers.getSetCookie?.() ?? [];
|
|
218
472
|
const fallbackHeader = res.headers.get("set-cookie");
|
|
@@ -252,7 +506,7 @@ class GloomApiClient {
|
|
|
252
506
|
this.setSessionCookieHeader(headers);
|
|
253
507
|
headers.set("Origin", this.baseUrl);
|
|
254
508
|
|
|
255
|
-
const res = await
|
|
509
|
+
const res = await cloudApiFetchTransport(`${this.baseUrl}${path}`, {
|
|
256
510
|
...options,
|
|
257
511
|
headers,
|
|
258
512
|
credentials: "include",
|
|
@@ -262,12 +516,7 @@ class GloomApiClient {
|
|
|
262
516
|
|
|
263
517
|
if (!res.ok) {
|
|
264
518
|
const body = await res.text();
|
|
265
|
-
|
|
266
|
-
try {
|
|
267
|
-
msg = JSON.parse(body).message ?? body;
|
|
268
|
-
} catch {
|
|
269
|
-
msg = body;
|
|
270
|
-
}
|
|
519
|
+
const msg = parseApiErrorMessage(body);
|
|
271
520
|
throw new ApiRequestError(msg, res.status);
|
|
272
521
|
}
|
|
273
522
|
|
|
@@ -300,10 +549,11 @@ class GloomApiClient {
|
|
|
300
549
|
|
|
301
550
|
const socketToken = this.getSocketAuthToken();
|
|
302
551
|
if (!socketToken) return;
|
|
552
|
+
const usingWebSocketToken = !!this.websocketToken;
|
|
303
553
|
const url = `${this.getWebSocketBaseUrl()}/cloud/ws?token=${encodeURIComponent(socketToken)}`;
|
|
304
554
|
cloudApiLog.info("open websocket", {
|
|
305
555
|
hasToken: !!socketToken,
|
|
306
|
-
tokenSource:
|
|
556
|
+
tokenSource: usingWebSocketToken ? "websocket" : "session",
|
|
307
557
|
quoteTargets: this.quoteTargets.size,
|
|
308
558
|
channelTargets: this.channelListeners.size,
|
|
309
559
|
});
|
|
@@ -321,11 +571,24 @@ class GloomApiClient {
|
|
|
321
571
|
void this.handleSocketMessage(String(event.data));
|
|
322
572
|
};
|
|
323
573
|
|
|
324
|
-
ws.onclose = () => {
|
|
574
|
+
ws.onclose = (event) => {
|
|
575
|
+
const activeSocket = this.ws === ws;
|
|
325
576
|
if (this.ws === ws) {
|
|
326
577
|
this.ws = null;
|
|
327
578
|
}
|
|
328
|
-
|
|
579
|
+
const closeEvent = event as CloseEvent | undefined;
|
|
580
|
+
cloudApiLog.warn("websocket closed", {
|
|
581
|
+
quoteTargets: this.quoteTargets.size,
|
|
582
|
+
channelTargets: this.channelListeners.size,
|
|
583
|
+
code: closeEvent?.code,
|
|
584
|
+
reason: closeEvent?.reason,
|
|
585
|
+
tokenSource: usingWebSocketToken ? "websocket" : "session",
|
|
586
|
+
});
|
|
587
|
+
if (activeSocket && usingWebSocketToken && this.sessionToken) {
|
|
588
|
+
this.websocketToken = null;
|
|
589
|
+
this.reconnectDelayMs = 1000;
|
|
590
|
+
cloudApiLog.warn("cleared websocket token after socket close; falling back to session token");
|
|
591
|
+
}
|
|
329
592
|
if (!this.shouldKeepSocketOpen()) return;
|
|
330
593
|
this.scheduleReconnect();
|
|
331
594
|
};
|
|
@@ -410,6 +673,14 @@ class GloomApiClient {
|
|
|
410
673
|
|
|
411
674
|
if (parsed?.type === "auth.unverified") {
|
|
412
675
|
cloudApiLog.warn("websocket marked unverified");
|
|
676
|
+
if (this.websocketToken && this.sessionToken) {
|
|
677
|
+
this.websocketToken = null;
|
|
678
|
+
this.reconnectDelayMs = 1000;
|
|
679
|
+
cloudApiLog.warn("cleared websocket token after auth rejection; falling back to session token");
|
|
680
|
+
this.teardownSocket();
|
|
681
|
+
this.scheduleReconnect();
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
413
684
|
if (this.currentUser) {
|
|
414
685
|
this.currentUser = { ...this.currentUser, emailVerified: false };
|
|
415
686
|
}
|
|
@@ -429,7 +700,7 @@ class GloomApiClient {
|
|
|
429
700
|
const key = marketKey(parsed.symbol, parsed.exchange);
|
|
430
701
|
const target = this.quoteTargets.get(key) ?? {
|
|
431
702
|
symbol: normalizeSymbol(parsed.symbol),
|
|
432
|
-
exchange:
|
|
703
|
+
exchange: canonicalExchange(parsed.exchange),
|
|
433
704
|
};
|
|
434
705
|
for (const listener of this.quoteListeners.get(key) ?? []) {
|
|
435
706
|
listener(target, parsed.quote as CloudQuotePayload);
|
|
@@ -446,43 +717,51 @@ class GloomApiClient {
|
|
|
446
717
|
}
|
|
447
718
|
|
|
448
719
|
async signUp(email: string, username: string, name: string, password: string): Promise<AuthUser> {
|
|
449
|
-
const result = await this.request<{ user: AuthUser }>("/
|
|
720
|
+
const result = await this.request<{ user: AuthUser }>("/auth/sign-up/email", {
|
|
450
721
|
method: "POST",
|
|
451
722
|
body: JSON.stringify({ email, username, name, password }),
|
|
452
723
|
});
|
|
724
|
+
this.requireCapturedSession(
|
|
725
|
+
"Account created, but Gloomberb could not save the login session. Please try logging in again.",
|
|
726
|
+
);
|
|
453
727
|
this.setCurrentUser(result.user);
|
|
454
728
|
return result.user;
|
|
455
729
|
}
|
|
456
730
|
|
|
457
731
|
async signIn(email: string, password: string): Promise<AuthUser> {
|
|
458
|
-
const result = await this.request<{ user: AuthUser }>("/
|
|
732
|
+
const result = await this.request<{ user: AuthUser }>("/auth/sign-in/email", {
|
|
459
733
|
method: "POST",
|
|
460
734
|
body: JSON.stringify({ email, password }),
|
|
461
735
|
});
|
|
736
|
+
this.requireCapturedSession(
|
|
737
|
+
"Logged in, but Gloomberb could not save the login session. Please try again.",
|
|
738
|
+
);
|
|
462
739
|
this.setCurrentUser(result.user);
|
|
463
740
|
return result.user;
|
|
464
741
|
}
|
|
465
742
|
|
|
466
743
|
async signOut(): Promise<void> {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
744
|
+
try {
|
|
745
|
+
await this.request("/auth/sign-out", { method: "POST" });
|
|
746
|
+
} finally {
|
|
747
|
+
this.setSessionToken(null);
|
|
748
|
+
}
|
|
470
749
|
}
|
|
471
750
|
|
|
472
751
|
async getSession(): Promise<AuthUser | null> {
|
|
473
752
|
try {
|
|
474
|
-
const result = await this.request<{ user: AuthUser }>("/
|
|
753
|
+
const result = await this.request<{ user: AuthUser }>("/auth/get-session", {
|
|
475
754
|
method: "GET",
|
|
476
755
|
});
|
|
477
756
|
const user = result?.user ?? null;
|
|
478
757
|
this.setCurrentUser(user);
|
|
479
758
|
return user;
|
|
480
759
|
} catch (error) {
|
|
481
|
-
if (
|
|
482
|
-
|
|
760
|
+
if (error instanceof ApiRequestError && isHardSessionInvalidMessage(error.message)) {
|
|
761
|
+
this.setSessionToken(null);
|
|
762
|
+
return null;
|
|
483
763
|
}
|
|
484
|
-
|
|
485
|
-
return null;
|
|
764
|
+
throw error;
|
|
486
765
|
}
|
|
487
766
|
}
|
|
488
767
|
|
|
@@ -576,7 +855,7 @@ class GloomApiClient {
|
|
|
576
855
|
.map((target) => {
|
|
577
856
|
const normalized = {
|
|
578
857
|
symbol: normalizeSymbol(target.symbol),
|
|
579
|
-
exchange:
|
|
858
|
+
exchange: canonicalExchange(target.exchange),
|
|
580
859
|
} satisfies QuoteStreamTarget;
|
|
581
860
|
return [marketKey(normalized.symbol, normalized.exchange), normalized] as const;
|
|
582
861
|
}),
|
|
@@ -686,6 +965,24 @@ class GloomApiClient {
|
|
|
686
965
|
return this.request<CloudMarketResponse<CloudFundamentals>>(`/market/fundamentals?${params.toString()}`);
|
|
687
966
|
}
|
|
688
967
|
|
|
968
|
+
async getCloudHolders(symbol: string, exchange?: string): Promise<CloudMarketResponse<CloudHoldersPayload>> {
|
|
969
|
+
const params = new URLSearchParams({ symbol });
|
|
970
|
+
if (exchange) params.set("exchange", exchange);
|
|
971
|
+
return this.request<CloudMarketResponse<CloudHoldersPayload>>(`/market/holders?${params.toString()}`);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
async getCloudAnalystResearch(symbol: string, exchange?: string): Promise<CloudMarketResponse<CloudAnalystResearchPayload>> {
|
|
975
|
+
const params = new URLSearchParams({ symbol });
|
|
976
|
+
if (exchange) params.set("exchange", exchange);
|
|
977
|
+
return this.request<CloudMarketResponse<CloudAnalystResearchPayload>>(`/market/analyst?${params.toString()}`);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async getCloudCorporateActions(symbol: string, exchange?: string): Promise<CloudMarketResponse<CloudCorporateActionsPayload>> {
|
|
981
|
+
const params = new URLSearchParams({ symbol });
|
|
982
|
+
if (exchange) params.set("exchange", exchange);
|
|
983
|
+
return this.request<CloudMarketResponse<CloudCorporateActionsPayload>>(`/market/corporate-actions?${params.toString()}`);
|
|
984
|
+
}
|
|
985
|
+
|
|
689
986
|
async getCloudStatements(
|
|
690
987
|
symbol: string,
|
|
691
988
|
exchange?: string,
|
|
@@ -720,6 +1017,109 @@ class GloomApiClient {
|
|
|
720
1017
|
const params = new URLSearchParams({ fromCurrency });
|
|
721
1018
|
return this.request<CloudMarketResponse<{ rate: number }>>(`/market/exchange-rate?${params.toString()}`);
|
|
722
1019
|
}
|
|
1020
|
+
|
|
1021
|
+
async getCloudEconomicCalendar(): Promise<CloudEconEventPayload[]> {
|
|
1022
|
+
return this.request<CloudEconEventPayload[]>("/cloud/econ/calendar");
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
async getCloudFredSeries(
|
|
1026
|
+
seriesId: string,
|
|
1027
|
+
params: {
|
|
1028
|
+
startDate?: string;
|
|
1029
|
+
endDate?: string;
|
|
1030
|
+
limit?: number;
|
|
1031
|
+
sortOrder?: "asc" | "desc";
|
|
1032
|
+
} = {},
|
|
1033
|
+
): Promise<CloudFredSeriesPayload> {
|
|
1034
|
+
const search = new URLSearchParams();
|
|
1035
|
+
if (params.startDate) search.set("startDate", params.startDate);
|
|
1036
|
+
if (params.endDate) search.set("endDate", params.endDate);
|
|
1037
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
1038
|
+
if (params.sortOrder) search.set("sortOrder", params.sortOrder);
|
|
1039
|
+
const qs = search.toString();
|
|
1040
|
+
return this.request<CloudFredSeriesPayload>(`/cloud/econ/series/${encodeURIComponent(seriesId)}${qs ? `?${qs}` : ""}`);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
async getCloudYieldCurve(): Promise<CloudYieldPointPayload[]> {
|
|
1044
|
+
return this.request<CloudYieldPointPayload[]>("/cloud/econ/yield-curve");
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
async getCloudNews(params: {
|
|
1048
|
+
feed?: "latest" | "top" | "breaking" | "ticker" | "sector" | "topic";
|
|
1049
|
+
ticker?: string;
|
|
1050
|
+
exchange?: string;
|
|
1051
|
+
tickerTier?: "primary" | "related" | "any";
|
|
1052
|
+
tickerRelations?: string[];
|
|
1053
|
+
limit?: number;
|
|
1054
|
+
topics?: string[];
|
|
1055
|
+
categories?: string[];
|
|
1056
|
+
sectors?: string[];
|
|
1057
|
+
sources?: string[];
|
|
1058
|
+
excludeSources?: string[];
|
|
1059
|
+
sentiment?: "positive" | "neutral" | "negative";
|
|
1060
|
+
minImportance?: number;
|
|
1061
|
+
minUrgency?: number;
|
|
1062
|
+
breaking?: boolean;
|
|
1063
|
+
since?: Date;
|
|
1064
|
+
until?: Date;
|
|
1065
|
+
cursor?: string;
|
|
1066
|
+
} = {}): Promise<CloudNewsListResponse> {
|
|
1067
|
+
const search = new URLSearchParams();
|
|
1068
|
+
if (params.feed) search.set("feed", params.feed);
|
|
1069
|
+
if (params.ticker) {
|
|
1070
|
+
const tickerFilter = params.exchange
|
|
1071
|
+
? publicTickerKey(params.ticker, params.exchange)
|
|
1072
|
+
: normalizeSymbol(params.ticker);
|
|
1073
|
+
search.set("tickers", tickerFilter);
|
|
1074
|
+
}
|
|
1075
|
+
if (params.tickerTier) search.set("tickerTier", params.tickerTier);
|
|
1076
|
+
if (params.tickerRelations?.length) search.set("tickerRelations", params.tickerRelations.join(","));
|
|
1077
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
1078
|
+
if (params.topics?.length) search.set("topics", params.topics.join(","));
|
|
1079
|
+
if (params.categories?.length) {
|
|
1080
|
+
search.set("categories", params.categories.join(","));
|
|
1081
|
+
}
|
|
1082
|
+
if (params.sectors?.length) search.set("sectors", params.sectors.join(","));
|
|
1083
|
+
if (params.sources?.length) search.set("sources", params.sources.join(","));
|
|
1084
|
+
if (params.excludeSources?.length) search.set("excludeSources", params.excludeSources.join(","));
|
|
1085
|
+
if (params.sentiment) search.set("sentiment", params.sentiment);
|
|
1086
|
+
if (params.minImportance != null) search.set("minImportance", String(params.minImportance));
|
|
1087
|
+
if (params.minUrgency != null) search.set("minUrgency", String(params.minUrgency));
|
|
1088
|
+
if (params.breaking != null) search.set("breaking", String(params.breaking));
|
|
1089
|
+
if (params.since) search.set("since", params.since.toISOString());
|
|
1090
|
+
if (params.until) search.set("until", params.until.toISOString());
|
|
1091
|
+
if (params.cursor) search.set("cursor", params.cursor);
|
|
1092
|
+
const query = search.toString();
|
|
1093
|
+
return this.request<CloudNewsListResponse>(`/news${query ? `?${query}` : ""}`);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async getCloudTickerTweets(params: {
|
|
1097
|
+
ticker: string;
|
|
1098
|
+
limit?: number;
|
|
1099
|
+
hours?: number;
|
|
1100
|
+
includeReplies?: boolean;
|
|
1101
|
+
}): Promise<CloudTweetSearchResponse> {
|
|
1102
|
+
const search = new URLSearchParams({ ticker: params.ticker });
|
|
1103
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
1104
|
+
if (params.hours != null) search.set("hours", String(params.hours));
|
|
1105
|
+
if (params.includeReplies != null) search.set("includeReplies", String(params.includeReplies));
|
|
1106
|
+
const response = await this.request<CloudTweetSearchResponse>(`/news/tweets?${search.toString()}`);
|
|
1107
|
+
return normalizeTweetSearchResponse(response);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
async searchCloudTweets(params: {
|
|
1111
|
+
query: string;
|
|
1112
|
+
queryType?: CloudTweetQueryType;
|
|
1113
|
+
limit?: number;
|
|
1114
|
+
hours?: number;
|
|
1115
|
+
}): Promise<CloudTweetSearchResponse> {
|
|
1116
|
+
const search = new URLSearchParams({ query: params.query });
|
|
1117
|
+
if (params.queryType) search.set("queryType", params.queryType);
|
|
1118
|
+
if (params.limit != null) search.set("limit", String(params.limit));
|
|
1119
|
+
if (params.hours != null) search.set("hours", String(params.hours));
|
|
1120
|
+
const response = await this.request<CloudTweetSearchResponse>(`/news/tweets/search?${search.toString()}`);
|
|
1121
|
+
return normalizeTweetSearchResponse(response);
|
|
1122
|
+
}
|
|
723
1123
|
}
|
|
724
1124
|
|
|
725
1125
|
export const apiClient = new GloomApiClient();
|
|
@@ -25,3 +25,9 @@ export function isPlainEscape(event: BackNavigationEventLike): boolean {
|
|
|
25
25
|
export function isBackNavigationKey(event: BackNavigationEventLike): boolean {
|
|
26
26
|
return isPlainBackspace(event) || isPlainEscape(event);
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
export function isDetailBackNavigationKey(
|
|
30
|
+
event: BackNavigationEventLike,
|
|
31
|
+
): boolean {
|
|
32
|
+
return isPlainBackspace(event);
|
|
33
|
+
}
|
package/src/utils/cli-output.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { hasLikelyQuoteUnitMismatch,
|
|
2
|
+
import { hasLikelyQuoteUnitMismatch, resolveCurrencyUnit } from "./currency-units";
|
|
3
3
|
|
|
4
4
|
describe("currency unit helpers", () => {
|
|
5
5
|
test("normalizes known sub-unit currencies to their main currency", () => {
|
|
@@ -7,11 +7,6 @@ describe("currency unit helpers", () => {
|
|
|
7
7
|
expect(resolveCurrencyUnit("GBP")).toEqual({ currency: "GBP", divisor: 1 });
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
test("scales quote prices by the configured divisor", () => {
|
|
11
|
-
expect(normalizePriceValueByDivisor(23.1, 100)).toBeCloseTo(0.231, 8);
|
|
12
|
-
expect(normalizePriceValueByDivisor(125, 1)).toBe(125);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
10
|
test("detects sub-unit quotes that match the same main-currency price", () => {
|
|
16
11
|
expect(hasLikelyQuoteUnitMismatch(
|
|
17
12
|
{ price: 23.1, currency: "GBp" },
|