gloomberb 0.18.1 → 0.18.3
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 +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
package/src/types/trading.ts
CHANGED
|
@@ -3,6 +3,36 @@ import type { BrokerContractRef } from "./instrument";
|
|
|
3
3
|
type BrokerOrderAction = "BUY" | "SELL";
|
|
4
4
|
export type BrokerOrderType = "MKT" | "LMT" | "STP" | "STP LMT";
|
|
5
5
|
|
|
6
|
+
/** Explicit account classification for order confirmation. Missing values are treated as unknown. */
|
|
7
|
+
export type BrokerTradingMode = "simulation" | "live" | "unknown";
|
|
8
|
+
|
|
9
|
+
/** Synchronous ticket metadata. Adapters may vary this by contract and order type. */
|
|
10
|
+
export interface BrokerTradingCapabilities {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
disabledReason?: string;
|
|
13
|
+
orderTypes: BrokerOrderType[];
|
|
14
|
+
tif: string[];
|
|
15
|
+
extendedHours?: boolean;
|
|
16
|
+
/** When set, extended hours are available only for these time-in-force values. */
|
|
17
|
+
extendedHoursTif?: string[];
|
|
18
|
+
fractionalQuantity?: boolean;
|
|
19
|
+
/** Explicitly permits sells beyond a known long position. Omission blocks short sales. */
|
|
20
|
+
shortSelling?: boolean;
|
|
21
|
+
minQuantity?: number;
|
|
22
|
+
quantityStep?: number;
|
|
23
|
+
priceDecimals?: number;
|
|
24
|
+
/** Broker-reported price increment for this instrument/order. Omit if unknown. */
|
|
25
|
+
priceStep?: number;
|
|
26
|
+
modify?: boolean;
|
|
27
|
+
cancel?: boolean;
|
|
28
|
+
executionKind?: "fills" | "order-summaries";
|
|
29
|
+
/**
|
|
30
|
+
* Multi-leg option orders take up to this many legs (at least 2). Declared
|
|
31
|
+
* for a combo contract (`secType: "BAG"`); omit when the broker takes none.
|
|
32
|
+
*/
|
|
33
|
+
maxLegs?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
6
36
|
export interface BrokerCashBalance {
|
|
7
37
|
currency: string;
|
|
8
38
|
quantity: number;
|
|
@@ -13,6 +43,9 @@ export interface BrokerCashBalance {
|
|
|
13
43
|
export interface BrokerAccount {
|
|
14
44
|
accountId: string;
|
|
15
45
|
name: string;
|
|
46
|
+
tradingMode?: BrokerTradingMode;
|
|
47
|
+
/** Broker-reported account classification, such as Cash or Margin. Omit when unavailable. */
|
|
48
|
+
accountType?: string;
|
|
16
49
|
currency?: string;
|
|
17
50
|
/** "cloud" is a connection held by the user's Gloom Cloud account. */
|
|
18
51
|
source?: "gateway" | "flex" | "cloud";
|
|
@@ -64,10 +97,25 @@ export interface BrokerPortfolioPerformance {
|
|
|
64
97
|
points: BrokerPortfolioPerformancePoint[];
|
|
65
98
|
}
|
|
66
99
|
|
|
100
|
+
/**
|
|
101
|
+
* One leg of a multi-leg option order. The action is what happens to this
|
|
102
|
+
* contract when the order fills; the ratio is its contracts per spread.
|
|
103
|
+
*/
|
|
104
|
+
export interface BrokerOrderLeg {
|
|
105
|
+
contract: BrokerContractRef;
|
|
106
|
+
action: BrokerOrderAction;
|
|
107
|
+
ratio: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
67
110
|
export interface BrokerOrderRequest {
|
|
68
111
|
brokerInstanceId?: string;
|
|
69
112
|
accountId?: string;
|
|
113
|
+
/** A combo contract (`secType: "BAG"`, the underlying's symbol) when the order has legs. */
|
|
70
114
|
contract: BrokerContractRef;
|
|
115
|
+
/**
|
|
116
|
+
* BUY pays a net debit and SELL receives a net credit. With legs, the
|
|
117
|
+
* legs' own actions say which contracts are bought and sold.
|
|
118
|
+
*/
|
|
71
119
|
action: BrokerOrderAction;
|
|
72
120
|
orderType: BrokerOrderType;
|
|
73
121
|
quantity: number;
|
|
@@ -75,9 +123,23 @@ export interface BrokerOrderRequest {
|
|
|
75
123
|
stopPrice?: number;
|
|
76
124
|
tif?: string;
|
|
77
125
|
outsideRth?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* 2 or more option legs filled as one order. `quantity` counts spreads and
|
|
128
|
+
* `limitPrice` is the positive net price of one spread.
|
|
129
|
+
*/
|
|
130
|
+
legs?: BrokerOrderLeg[];
|
|
78
131
|
}
|
|
79
132
|
|
|
80
133
|
export interface BrokerOrderPreview {
|
|
134
|
+
currency?: string;
|
|
135
|
+
estimatedCost?: number;
|
|
136
|
+
fees?: number;
|
|
137
|
+
buyingPowerBefore?: number;
|
|
138
|
+
buyingPowerAfter?: number;
|
|
139
|
+
buyingPowerImpact?: number;
|
|
140
|
+
warnings?: string[];
|
|
141
|
+
/** Blocking broker validation errors. A ticket must not submit when these are present. */
|
|
142
|
+
errors?: string[];
|
|
81
143
|
initMarginBefore?: number;
|
|
82
144
|
initMarginAfter?: number;
|
|
83
145
|
maintMarginBefore?: number;
|
|
@@ -91,6 +153,8 @@ export interface BrokerOrderPreview {
|
|
|
91
153
|
|
|
92
154
|
export interface BrokerOrder {
|
|
93
155
|
orderId: number;
|
|
156
|
+
/** Broker-native identity, scoped to profile and account. Numeric orderId remains the mutation handle. */
|
|
157
|
+
brokerOrderId?: string;
|
|
94
158
|
brokerInstanceId?: string;
|
|
95
159
|
accountId?: string;
|
|
96
160
|
status: string;
|
|
@@ -108,12 +172,15 @@ export interface BrokerOrder {
|
|
|
108
172
|
reviewUrl?: string;
|
|
109
173
|
updatedAt: number;
|
|
110
174
|
contract: BrokerContractRef;
|
|
175
|
+
/** A multi-leg order's legs, as on its request. */
|
|
176
|
+
legs?: BrokerOrderLeg[];
|
|
111
177
|
}
|
|
112
178
|
|
|
113
179
|
export interface BrokerExecution {
|
|
114
180
|
execId: string;
|
|
115
181
|
brokerInstanceId?: string;
|
|
116
182
|
orderId?: number;
|
|
183
|
+
brokerOrderId?: string;
|
|
117
184
|
accountId?: string;
|
|
118
185
|
side: string;
|
|
119
186
|
shares: number;
|
package/src/ui/host.tsx
CHANGED
|
@@ -447,6 +447,8 @@ export interface AsciiTextProps extends BoxProps {
|
|
|
447
447
|
interface HostTabItem {
|
|
448
448
|
label: string;
|
|
449
449
|
value: string;
|
|
450
|
+
/** Artwork before the label, such as a logo; decoration only. */
|
|
451
|
+
leading?: ReactNode;
|
|
450
452
|
disabled?: boolean;
|
|
451
453
|
reorderable?: boolean;
|
|
452
454
|
onClose?: (value: string) => void;
|
|
@@ -611,6 +613,8 @@ export interface HostMenuItem {
|
|
|
611
613
|
checked?: boolean;
|
|
612
614
|
/** Tints the label, e.g. a status in its tone; the words carry the meaning. */
|
|
613
615
|
color?: string;
|
|
616
|
+
/** Artwork before the label, such as a logo; decoration only. */
|
|
617
|
+
leading?: ReactNode;
|
|
614
618
|
}
|
|
615
619
|
|
|
616
620
|
export interface HostMenuProps {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why an update step failed, as a fixed phase and code rather than the error
|
|
3
|
+
* text: the header says the reason in a few words, and error tracking gets
|
|
4
|
+
* the same two values, which carry no path, URL or account name.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** The step that failed. `relaunch` is getting back into the app after an install. */
|
|
8
|
+
export type UpdateFailurePhase = "check" | "download" | "verify" | "apply" | "relaunch";
|
|
9
|
+
|
|
10
|
+
const UPDATE_FAILURE_CODES = [
|
|
11
|
+
/** No route to the release server: offline, DNS, refused, TLS. */
|
|
12
|
+
"offline",
|
|
13
|
+
"timeout",
|
|
14
|
+
/** GitHub answered 403 or 429, its limit for requests without an account. */
|
|
15
|
+
"rate_limited",
|
|
16
|
+
"server_error",
|
|
17
|
+
/** The release has no such file (404). */
|
|
18
|
+
"not_found",
|
|
19
|
+
/** Any other refusal from the release server. */
|
|
20
|
+
"http_error",
|
|
21
|
+
/** The release list or manifest did not read as one. */
|
|
22
|
+
"bad_response",
|
|
23
|
+
/** The release has no build for this platform, or none with a checksum. */
|
|
24
|
+
"no_build",
|
|
25
|
+
/** The download stopped before the whole file arrived. */
|
|
26
|
+
"interrupted",
|
|
27
|
+
/** The file arrived whole but does not match its checksum, or does not unpack. */
|
|
28
|
+
"corrupt",
|
|
29
|
+
"permission_denied",
|
|
30
|
+
/** The macOS app runs from where it cannot be replaced: a disk image, App Translocation, another volume. */
|
|
31
|
+
"wrong_location",
|
|
32
|
+
/** The downloaded update and the installed app are on different disks, so the one cannot be moved onto the other. */
|
|
33
|
+
"cross_device",
|
|
34
|
+
"read_only",
|
|
35
|
+
"disk_full",
|
|
36
|
+
/** Another process holds the files being replaced. */
|
|
37
|
+
"file_in_use",
|
|
38
|
+
/** The update downloaded earlier is no longer where the install expects it. */
|
|
39
|
+
"staged_missing",
|
|
40
|
+
/** A restart into the update came back on the old version. */
|
|
41
|
+
"not_applied",
|
|
42
|
+
/** The app quit with a downloaded update waiting and was opened again on the old version. */
|
|
43
|
+
"not_restarted",
|
|
44
|
+
/** The install finished but the app did not come back by itself. */
|
|
45
|
+
"relaunch_missed",
|
|
46
|
+
"unknown",
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export type UpdateFailureCode = typeof UPDATE_FAILURE_CODES[number];
|
|
50
|
+
|
|
51
|
+
export interface UpdateFailure {
|
|
52
|
+
phase: UpdateFailurePhase;
|
|
53
|
+
code: UpdateFailureCode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** What an HTTP status from the release server means for an update. */
|
|
57
|
+
export function httpStatusFailureCode(status: number): UpdateFailureCode {
|
|
58
|
+
if (status === 403 || status === 429) return "rate_limited";
|
|
59
|
+
if (status === 404 || status === 410) return "not_found";
|
|
60
|
+
if (status >= 500) return "server_error";
|
|
61
|
+
return "http_error";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const OFFLINE_ERRNO = new Set([
|
|
65
|
+
"ENOTFOUND",
|
|
66
|
+
"EAI_AGAIN",
|
|
67
|
+
"ECONNREFUSED",
|
|
68
|
+
"ENETUNREACH",
|
|
69
|
+
"EHOSTUNREACH",
|
|
70
|
+
"ENETDOWN",
|
|
71
|
+
// Bun's fetch
|
|
72
|
+
"ConnectionRefused",
|
|
73
|
+
"FailedToOpenSocket",
|
|
74
|
+
"UnableToConnect",
|
|
75
|
+
"CERT_HAS_EXPIRED",
|
|
76
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
77
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
78
|
+
]);
|
|
79
|
+
const INTERRUPTED_ERRNO = new Set(["ECONNRESET", "EPIPE", "ECONNABORTED", "ConnectionClosed"]);
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The code for an error a step threw, from its system error code first and its
|
|
83
|
+
* message second. Only the code leaves the machine; the message stays local.
|
|
84
|
+
*/
|
|
85
|
+
export function classifyUpdateError(error: unknown): UpdateFailureCode {
|
|
86
|
+
const record = (error && typeof error === "object" ? error : {}) as { code?: unknown; name?: unknown; message?: unknown };
|
|
87
|
+
const message = typeof record.message === "string" ? record.message : typeof error === "string" ? error : "";
|
|
88
|
+
// A system error code, or one named in the message ("Failed to replace app: EACCES: ...").
|
|
89
|
+
const errno = typeof record.code === "string" ? record.code : /\b(E[A-Z]{3,})\b/.exec(message)?.[1] ?? "";
|
|
90
|
+
switch (errno) {
|
|
91
|
+
case "EACCES":
|
|
92
|
+
case "EPERM":
|
|
93
|
+
return "permission_denied";
|
|
94
|
+
case "EROFS":
|
|
95
|
+
return "read_only";
|
|
96
|
+
case "ENOSPC":
|
|
97
|
+
case "EDQUOT":
|
|
98
|
+
return "disk_full";
|
|
99
|
+
case "EBUSY":
|
|
100
|
+
case "ETXTBSY":
|
|
101
|
+
return "file_in_use";
|
|
102
|
+
case "EXDEV":
|
|
103
|
+
return "cross_device";
|
|
104
|
+
case "ETIMEDOUT":
|
|
105
|
+
return "timeout";
|
|
106
|
+
}
|
|
107
|
+
if (OFFLINE_ERRNO.has(errno)) return "offline";
|
|
108
|
+
if (INTERRUPTED_ERRNO.has(errno)) return "interrupted";
|
|
109
|
+
if (record.name === "TimeoutError" || record.name === "AbortError") return "timeout";
|
|
110
|
+
|
|
111
|
+
const status = /\b(?:HTTP|returned|status|failed:?)\s*\(?(\d{3})\b/i.exec(message)?.[1];
|
|
112
|
+
if (status) return httpStatusFailureCode(Number(status));
|
|
113
|
+
if (/checksum mismatch|hash mismatch/i.test(message)) return "corrupt";
|
|
114
|
+
if (/zstd|decompress|gunzip|incorrect header|unexpected end of (?:file|data)/i.test(message)) return "corrupt";
|
|
115
|
+
if (/timed? ?out/i.test(message)) return "timeout";
|
|
116
|
+
if (/rate limit/i.test(message)) return "rate_limited";
|
|
117
|
+
if (/read-only file system/i.test(message)) return "read_only";
|
|
118
|
+
if (/cross-device/i.test(message)) return "cross_device";
|
|
119
|
+
if (/permission denied|operation not permitted|access is denied/i.test(message)) return "permission_denied";
|
|
120
|
+
if (/no space left|disk full/i.test(message)) return "disk_full";
|
|
121
|
+
if (/resource busy|text file busy|being used by another process/i.test(message)) return "file_in_use";
|
|
122
|
+
if (/connection (?:reset|closed|aborted)|socket hang up|premature close|terminated/i.test(message)) return "interrupted";
|
|
123
|
+
if (/unable to connect|fetch failed|failed to fetch|network|getaddrinfo|could not resolve|certificate/i.test(message)) return "offline";
|
|
124
|
+
if (/invalid update\.json|unexpected .*payload|json/i.test(message)) return "bad_response";
|
|
125
|
+
return "unknown";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** When the failure has no more specific reason: what the step was. */
|
|
129
|
+
const PHASE_REASONS: Record<UpdateFailurePhase, string> = {
|
|
130
|
+
check: "check failed",
|
|
131
|
+
download: "download failed",
|
|
132
|
+
verify: "could not verify the download",
|
|
133
|
+
apply: "could not install",
|
|
134
|
+
relaunch: "could not restart",
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const CODE_REASONS: Partial<Record<UpdateFailureCode, string>> = {
|
|
138
|
+
offline: "no connection",
|
|
139
|
+
timeout: "connection timed out",
|
|
140
|
+
rate_limited: "GitHub rate limit",
|
|
141
|
+
server_error: "release server error",
|
|
142
|
+
not_found: "release server error",
|
|
143
|
+
http_error: "release server error",
|
|
144
|
+
bad_response: "unreadable release data",
|
|
145
|
+
no_build: "no build for this system",
|
|
146
|
+
interrupted: "download interrupted",
|
|
147
|
+
corrupt: "download corrupted",
|
|
148
|
+
permission_denied: "no permission to replace the app",
|
|
149
|
+
wrong_location: "move the app to Applications",
|
|
150
|
+
cross_device: "app and update on different disks",
|
|
151
|
+
read_only: "install folder is read-only",
|
|
152
|
+
disk_full: "disk full",
|
|
153
|
+
file_in_use: "app files in use",
|
|
154
|
+
staged_missing: "downloaded update missing",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The reason in a few words, in English (the caller translates it), sized
|
|
159
|
+
* for the header: "download interrupted", "no permission to replace the app".
|
|
160
|
+
*/
|
|
161
|
+
export function updateFailureReason(failure: UpdateFailure): string {
|
|
162
|
+
return CODE_REASONS[failure.code] ?? PHASE_REASONS[failure.phase];
|
|
163
|
+
}
|
package/src/updater/index.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { UpdateChannel } from "../types/config";
|
|
2
|
+
import { reportUpdateFailure, type UpdateInstallKind } from "../telemetry/update-failures";
|
|
2
3
|
import { compareSemver, isPrereleaseVersion } from "../utils/semver";
|
|
4
|
+
import {
|
|
5
|
+
classifyUpdateError,
|
|
6
|
+
httpStatusFailureCode,
|
|
7
|
+
type UpdateFailure,
|
|
8
|
+
type UpdateFailureCode,
|
|
9
|
+
type UpdateFailurePhase,
|
|
10
|
+
} from "./failure";
|
|
3
11
|
import {
|
|
4
12
|
GITHUB_LATEST_RELEASE_API_URL,
|
|
5
13
|
GITHUB_RELEASES_API_URL,
|
|
@@ -49,6 +57,8 @@ export interface UpdateProgress {
|
|
|
49
57
|
message?: string;
|
|
50
58
|
/** With `ready`: this host can apply the update and relaunch on request. */
|
|
51
59
|
canRestart?: boolean;
|
|
60
|
+
/** With `error`: the step that failed and why, which the header says in a few words. */
|
|
61
|
+
failure?: UpdateFailure;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
export type UpdateAction =
|
|
@@ -68,7 +78,7 @@ export type UpdateCheckResult =
|
|
|
68
78
|
/** `notice`: why a newer-looking state is not an update, such as a nightly build waiting for a stable release. */
|
|
69
79
|
| { kind: "current"; notice?: string }
|
|
70
80
|
| { kind: "disabled" }
|
|
71
|
-
| { kind: "error"; error: string };
|
|
81
|
+
| { kind: "error"; error: string; failure?: UpdateFailure };
|
|
72
82
|
|
|
73
83
|
/**
|
|
74
84
|
* On the stable channel a nightly build is never replaced by an older stable
|
|
@@ -302,6 +312,23 @@ export function canSelfUpdate(release: Pick<ReleaseInfo, "updateAction"> | null
|
|
|
302
312
|
return release?.updateAction.kind === "self" || release?.updateAction.kind === "desktop";
|
|
303
313
|
}
|
|
304
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Whether the install-update key has something to retry: a release this build
|
|
317
|
+
* can install itself whose last attempt failed. A fresh one downloads on its
|
|
318
|
+
* own, and a manual or managed one names its own command. (A downloaded
|
|
319
|
+
* desktop update has its own use of the key: restarting into it.)
|
|
320
|
+
*/
|
|
321
|
+
export function canRetryUpdate(state: {
|
|
322
|
+
updateAvailable: ReleaseInfo | null;
|
|
323
|
+
updateProgress: UpdateProgress | null;
|
|
324
|
+
updateCheckInProgress: boolean;
|
|
325
|
+
}): boolean {
|
|
326
|
+
return !!state.updateAvailable
|
|
327
|
+
&& state.updateProgress?.phase === "error"
|
|
328
|
+
&& !state.updateCheckInProgress
|
|
329
|
+
&& canSelfUpdate(state.updateAvailable);
|
|
330
|
+
}
|
|
331
|
+
|
|
305
332
|
/** A downloaded update this host can install and relaunch into when the user asks. */
|
|
306
333
|
export function canRestartToApply(progress: UpdateProgress | null | undefined): boolean {
|
|
307
334
|
return progress?.phase === "ready" && progress.canRestart === true;
|
|
@@ -344,6 +371,11 @@ export async function checkForUpdateDetailed(
|
|
|
344
371
|
return { kind: "disabled" };
|
|
345
372
|
}
|
|
346
373
|
const nightly = channel === "nightly" && updateAction.kind === "self";
|
|
374
|
+
const failed = (error: string, code: UpdateFailureCode, targetVersion?: string): UpdateCheckResult => {
|
|
375
|
+
const failure: UpdateFailure = { phase: "check", code };
|
|
376
|
+
reportUpdateFailure(failure, { targetVersion, channel: nightly ? "nightly" : "stable" });
|
|
377
|
+
return { kind: "error", error, failure };
|
|
378
|
+
};
|
|
347
379
|
|
|
348
380
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
349
381
|
try {
|
|
@@ -356,7 +388,7 @@ export async function checkForUpdateDetailed(
|
|
|
356
388
|
});
|
|
357
389
|
|
|
358
390
|
if (!res.ok) {
|
|
359
|
-
return
|
|
391
|
+
return failed(`GitHub returned ${res.status}`, httpStatusFailureCode(res.status));
|
|
360
392
|
}
|
|
361
393
|
|
|
362
394
|
const payload = await res.json();
|
|
@@ -379,16 +411,10 @@ export async function checkForUpdateDetailed(
|
|
|
379
411
|
|
|
380
412
|
const asset = resolveReleaseAsset(data.assets);
|
|
381
413
|
if (!asset) {
|
|
382
|
-
return {
|
|
383
|
-
kind: "error",
|
|
384
|
-
error: `No compatible release asset found for ${getAssetBaseName()}`,
|
|
385
|
-
};
|
|
414
|
+
return failed(`No compatible release asset found for ${getAssetBaseName()}`, "no_build", version);
|
|
386
415
|
}
|
|
387
416
|
if (!asset.checksum) {
|
|
388
|
-
return {
|
|
389
|
-
kind: "error",
|
|
390
|
-
error: `Release asset ${asset.name} is missing a valid SHA-256 digest`,
|
|
391
|
-
};
|
|
417
|
+
return failed(`Release asset ${asset.name} is missing a valid SHA-256 digest`, "no_build", version);
|
|
392
418
|
}
|
|
393
419
|
|
|
394
420
|
return {
|
|
@@ -406,12 +432,10 @@ export async function checkForUpdateDetailed(
|
|
|
406
432
|
};
|
|
407
433
|
} catch (error: unknown) {
|
|
408
434
|
if (error instanceof Error && error.name === "AbortError") {
|
|
409
|
-
return
|
|
435
|
+
return failed("Update check timed out", "timeout");
|
|
410
436
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
error: error instanceof Error ? error.message : "Update check failed",
|
|
414
|
-
};
|
|
437
|
+
const code = classifyUpdateError(error);
|
|
438
|
+
return failed(error instanceof Error ? error.message : "Update check failed", code === "unknown" && error instanceof SyntaxError ? "bad_response" : code);
|
|
415
439
|
} finally {
|
|
416
440
|
if (timeout) clearTimeout(timeout);
|
|
417
441
|
}
|
|
@@ -507,14 +531,20 @@ export async function performUpdate(
|
|
|
507
531
|
}
|
|
508
532
|
|
|
509
533
|
const checksum = /^[a-f\d]{64}$/i.exec(release.checksum ?? "")?.[0].toLowerCase();
|
|
534
|
+
const fail = (failure: UpdateFailure, error: string) => {
|
|
535
|
+
reportUpdateFailure(failure, { targetVersion: release.version, channel: release.channel });
|
|
536
|
+
onProgress({ phase: "error", error, failure });
|
|
537
|
+
};
|
|
510
538
|
if (!checksum) {
|
|
511
|
-
|
|
539
|
+
fail({ phase: "verify", code: "no_build" }, "Self-update requires a valid SHA-256 checksum.");
|
|
512
540
|
return;
|
|
513
541
|
}
|
|
514
542
|
|
|
515
543
|
const updatePath = execPath + ".update";
|
|
516
544
|
const oldPath = execPath + ".old";
|
|
517
545
|
let unlinkUpdatePath: ((path: string) => void) | null = null;
|
|
546
|
+
/** The step under way, which a thrown error is reported against. */
|
|
547
|
+
let phase: UpdateFailurePhase = "download";
|
|
518
548
|
|
|
519
549
|
try {
|
|
520
550
|
// Not import(): Bun folds even a specifier held in a variable and bundles
|
|
@@ -531,7 +561,7 @@ export async function performUpdate(
|
|
|
531
561
|
|
|
532
562
|
const res = await fetch(release.downloadUrl);
|
|
533
563
|
if (!res.ok || !res.body) {
|
|
534
|
-
throw new
|
|
564
|
+
throw new UpdateStepError({ phase: "download", code: httpStatusFailureCode(res.status) }, `Download failed: ${res.status}`);
|
|
535
565
|
}
|
|
536
566
|
|
|
537
567
|
const contentLength = Number(res.headers.get("content-length") || 0);
|
|
@@ -540,7 +570,18 @@ export async function performUpdate(
|
|
|
540
570
|
let received = 0;
|
|
541
571
|
|
|
542
572
|
while (true) {
|
|
543
|
-
|
|
573
|
+
let chunk: Awaited<ReturnType<typeof reader.read>>;
|
|
574
|
+
try {
|
|
575
|
+
chunk = await reader.read();
|
|
576
|
+
} catch (error) {
|
|
577
|
+
// The connection broke partway: whatever it says, the file is incomplete.
|
|
578
|
+
const code = classifyUpdateError(error);
|
|
579
|
+
throw new UpdateStepError(
|
|
580
|
+
{ phase: "download", code: code === "timeout" ? code : "interrupted" },
|
|
581
|
+
error instanceof Error ? error.message : "Download interrupted",
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
const { done, value } = chunk;
|
|
544
585
|
if (done) break;
|
|
545
586
|
chunks.push(value);
|
|
546
587
|
received += value.length;
|
|
@@ -551,7 +592,14 @@ export async function performUpdate(
|
|
|
551
592
|
});
|
|
552
593
|
}
|
|
553
594
|
}
|
|
595
|
+
if (contentLength > 0 && received < contentLength) {
|
|
596
|
+
throw new UpdateStepError(
|
|
597
|
+
{ phase: "download", code: "interrupted" },
|
|
598
|
+
`Download stopped at ${received} of ${contentLength} bytes`,
|
|
599
|
+
);
|
|
600
|
+
}
|
|
554
601
|
|
|
602
|
+
phase = "verify";
|
|
555
603
|
const downloaded = new Uint8Array(received);
|
|
556
604
|
let offset = 0;
|
|
557
605
|
for (const chunk of chunks) {
|
|
@@ -560,11 +608,12 @@ export async function performUpdate(
|
|
|
560
608
|
}
|
|
561
609
|
const actual = createHash("sha256").update(downloaded).digest("hex");
|
|
562
610
|
if (actual !== checksum) {
|
|
563
|
-
throw new
|
|
611
|
+
throw new UpdateStepError({ phase: "verify", code: "corrupt" }, `Checksum mismatch: expected ${checksum}, got ${actual}`);
|
|
564
612
|
}
|
|
565
613
|
const nextBinary = release.compressed
|
|
566
614
|
? new Uint8Array(gunzipSync(downloaded))
|
|
567
615
|
: downloaded;
|
|
616
|
+
phase = "apply";
|
|
568
617
|
await Bun.write(updatePath, nextBinary);
|
|
569
618
|
chmodSync(updatePath, 0o755);
|
|
570
619
|
|
|
@@ -600,9 +649,24 @@ export async function performUpdate(
|
|
|
600
649
|
try {
|
|
601
650
|
unlinkUpdatePath?.(updatePath);
|
|
602
651
|
} catch { /* Intentional ignore: Remove a partial update download while the original failure is reported through update progress. */ }
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
error: err instanceof Error ? err.message : "Update failed",
|
|
606
|
-
});
|
|
652
|
+
const failure = err instanceof UpdateStepError ? err.failure : { phase, code: classifyUpdateError(err) };
|
|
653
|
+
fail(failure, err instanceof Error ? err.message : "Update failed");
|
|
607
654
|
}
|
|
608
655
|
}
|
|
656
|
+
|
|
657
|
+
/** A failed step whose code is known where it is thrown. */
|
|
658
|
+
class UpdateStepError extends Error {
|
|
659
|
+
constructor(readonly failure: UpdateFailure, message: string) {
|
|
660
|
+
super(message);
|
|
661
|
+
this.name = "UpdateStepError";
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** How the running terminal app was installed, as update failure reports name it. */
|
|
666
|
+
export function terminalInstallKind(action: UpdateAction | null = detectUpdateAction()): UpdateInstallKind {
|
|
667
|
+
if (!action) return "terminal-source";
|
|
668
|
+
if (action.kind === "self") return "terminal-binary";
|
|
669
|
+
if (action.kind === "managed") return "terminal-managed";
|
|
670
|
+
if (action.kind === "manual") return "terminal-package-manager";
|
|
671
|
+
return "unknown";
|
|
672
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { currenciesNamed } from "./currency-names";
|
|
2
|
+
import { isCurrencyCode } from "./currency-pair";
|
|
3
|
+
import { isIsoCurrencyCode } from "./crypto-pair";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Signs people write for a currency, as they read them: C$ and US$ as
|
|
7
|
+
* Canadians and Americans write them, S$, NZ$, R$. These settle a sign the
|
|
8
|
+
* runtime gives another currency too (C$ is also the cordoba's). The runtime
|
|
9
|
+
* adds CA$, A$, HK$, €, £, ₹ and the rest, so a sign several currencies use
|
|
10
|
+
* ($, ¥) names all of them.
|
|
11
|
+
*/
|
|
12
|
+
const EXTRA_SIGNS: ReadonlyArray<readonly [sign: string, code: string]> = [
|
|
13
|
+
["US$", "USD"], ["U$S", "USD"], ["C$", "CAD"], ["CAN$", "CAD"], ["AU$", "AUD"], ["S$", "SGD"], ["SG$", "SGD"],
|
|
14
|
+
["NZ$", "NZD"], ["R$", "BRL"], ["NT$", "TWD"], ["RMB", "CNY"], ["Fr", "CHF"], ["SFr", "CHF"], ["zł", "PLN"],
|
|
15
|
+
["kr", "SEK"], ["kr", "NOK"], ["kr", "DKK"], ["kr", "ISK"], ["Kč", "CZK"], ["Ft", "HUF"], ["RM", "MYR"],
|
|
16
|
+
["£", "GBP"], ["¥", "JPY"], ["¥", "CNY"],
|
|
17
|
+
["$", "USD"], ["$", "CAD"], ["$", "AUD"], ["$", "NZD"], ["$", "HKD"], ["$", "SGD"], ["$", "MXN"],
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/** The most traded currencies, first among those a shared sign ($, ¥, kr) names. */
|
|
21
|
+
const SIGN_ORDER = ["USD", "EUR", "JPY", "GBP", "CNY", "CAD", "AUD", "CHF", "HKD", "SGD", "NZD", "MXN", "SEK", "NOK", "DKK"];
|
|
22
|
+
|
|
23
|
+
let signIndex: ReadonlyMap<string, readonly string[]> | undefined;
|
|
24
|
+
|
|
25
|
+
function signKey(sign: string): string {
|
|
26
|
+
return sign.normalize("NFC").replace(/\s+/g, "").toUpperCase();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function signs(): ReadonlyMap<string, readonly string[]> {
|
|
30
|
+
if (signIndex) return signIndex;
|
|
31
|
+
const built = new Map<string, string[]>();
|
|
32
|
+
const curated = new Set(EXTRA_SIGNS.map(([sign]) => signKey(sign)));
|
|
33
|
+
const add = (sign: string, code: string, extra = false) => {
|
|
34
|
+
const key = signKey(sign);
|
|
35
|
+
// A code is its own spelling, not a sign: CHF reads as the code.
|
|
36
|
+
if (!key || key === code || (!extra && curated.has(key))) return;
|
|
37
|
+
const codes = built.get(key) ?? [];
|
|
38
|
+
if (!codes.includes(code)) codes.push(code);
|
|
39
|
+
built.set(key, codes);
|
|
40
|
+
};
|
|
41
|
+
const codes = typeof Intl.supportedValuesOf === "function" ? Intl.supportedValuesOf("currency") : [];
|
|
42
|
+
for (const code of codes) {
|
|
43
|
+
if (!isCurrencyCode(code)) continue;
|
|
44
|
+
for (const currencyDisplay of ["symbol", "narrowSymbol"] as const) {
|
|
45
|
+
try {
|
|
46
|
+
const sign = new Intl.NumberFormat("en-US", { style: "currency", currency: code, currencyDisplay })
|
|
47
|
+
.formatToParts(0).find((part) => part.type === "currency")?.value;
|
|
48
|
+
if (sign) add(sign, code);
|
|
49
|
+
} catch {
|
|
50
|
+
// Intentional ignore: a code the runtime lists but cannot format has no sign to read.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [sign, code] of EXTRA_SIGNS) add(sign, code, true);
|
|
55
|
+
const rank = (code: string) => {
|
|
56
|
+
const at = SIGN_ORDER.indexOf(code);
|
|
57
|
+
return at < 0 ? SIGN_ORDER.length : at;
|
|
58
|
+
};
|
|
59
|
+
for (const codesForSign of built.values()) codesForSign.sort((left, right) => rank(left) - rank(right));
|
|
60
|
+
signIndex = built;
|
|
61
|
+
return built;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The currencies a name (`Canadian dollar`, `yen`) or a sign (`CA$`, `C$`, `€`) stands for, most traded first. */
|
|
65
|
+
function currenciesForNameOrSign(typed: string): string[] {
|
|
66
|
+
const named = currenciesNamed(typed).map(({ code }) => code);
|
|
67
|
+
if (named.length > 0) return named;
|
|
68
|
+
return [...signs().get(signKey(typed.trim())) ?? []];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** What a typed base currency is: its ISO 4217 code, or why it is none and the codes it may have meant. */
|
|
72
|
+
export type BaseCurrencyReading =
|
|
73
|
+
| { code: string }
|
|
74
|
+
| { error: string; suggestions: string[] };
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A base currency as `config set baseCurrency` takes it: an ISO 4217 code in
|
|
78
|
+
* any case (`cad` is CAD). A currency's name or sign (`Canadian dollar`,
|
|
79
|
+
* `CA$`) is refused with the code it stands for, so the setting is always a
|
|
80
|
+
* code the conversions can price; so is a code that is no currency (XAU, XYZ).
|
|
81
|
+
*/
|
|
82
|
+
export function readBaseCurrency(typed: string): BaseCurrencyReading {
|
|
83
|
+
const text = typed.trim();
|
|
84
|
+
const upper = text.toUpperCase();
|
|
85
|
+
if (/^[A-Z]{3}$/.test(upper) && isCurrencyCode(upper)) return { code: upper };
|
|
86
|
+
if (/^[A-Z]{3}$/.test(upper) && isIsoCurrencyCode(upper)) {
|
|
87
|
+
return { error: `${upper} is a metal, drawing-right or test code, not a currency.`, suggestions: [] };
|
|
88
|
+
}
|
|
89
|
+
const suggestions = text ? currenciesForNameOrSign(text) : [];
|
|
90
|
+
const shown = text ? JSON.stringify(text) : "An empty value";
|
|
91
|
+
if (suggestions.length === 1) return { error: `${shown} is not a currency code. Did you mean ${suggestions[0]}?`, suggestions };
|
|
92
|
+
if (suggestions.length > 1) {
|
|
93
|
+
return { error: `${shown} is not a currency code; it can be ${suggestions.slice(0, 6).join(", ")}.`, suggestions };
|
|
94
|
+
}
|
|
95
|
+
return { error: `${shown} is not an ISO 4217 currency code.`, suggestions };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A base currency as it was saved, as the setting reads it now: a code in
|
|
100
|
+
* capitals, and a name or sign an older version stored (`Canadian dollar`,
|
|
101
|
+
* `CA$`, `cad`) as the one code it stands for. Anything else stays as saved,
|
|
102
|
+
* for `doctor` and `config get` to flag, rather than turning into USD.
|
|
103
|
+
*/
|
|
104
|
+
export function normalizeStoredBaseCurrency(value: string): string {
|
|
105
|
+
const reading = readBaseCurrency(value);
|
|
106
|
+
if ("code" in reading) return reading.code;
|
|
107
|
+
return reading.suggestions.length === 1 ? reading.suggestions[0]! : value.trim();
|
|
108
|
+
}
|