@gloom-sh/terminal 0.19.1 → 0.19.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/README.md +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PluginStateStore } from "./plugin-state-store";
|
|
2
2
|
import { ResourceStore } from "./resource-store";
|
|
3
3
|
import { SessionStore } from "./session-store";
|
|
4
|
-
import { SqliteDatabase } from "./sqlite/database";
|
|
4
|
+
import { SqliteDatabase, type SqliteDatabaseOptions } from "./sqlite/database";
|
|
5
5
|
import { TickerStore } from "./ticker-store";
|
|
6
6
|
|
|
7
7
|
export class AppPersistence {
|
|
@@ -11,15 +11,17 @@ export class AppPersistence {
|
|
|
11
11
|
readonly pluginState: PluginStateStore;
|
|
12
12
|
readonly sessions: SessionStore;
|
|
13
13
|
|
|
14
|
-
constructor(dbPath: string) {
|
|
15
|
-
this.database = new SqliteDatabase(dbPath);
|
|
16
|
-
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
14
|
+
constructor(dbPath: string, options: SqliteDatabaseOptions = {}) {
|
|
15
|
+
this.database = new SqliteDatabase(dbPath, options);
|
|
16
|
+
const { connection, access } = this.database;
|
|
17
|
+
this.tickers = new TickerStore(connection, access);
|
|
18
|
+
this.resources = new ResourceStore(connection, access);
|
|
19
|
+
this.pluginState = new PluginStateStore(connection, access);
|
|
20
|
+
this.sessions = new SessionStore(connection, access);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
close(): void {
|
|
24
|
+
this.resources.close();
|
|
23
25
|
this.database.close();
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { logFailure } from "../../../utils/log-failure";
|
|
2
2
|
import { existsSync, readdirSync, type Dirent } from "fs";
|
|
3
|
-
import {
|
|
3
|
+
import { readFile, rename, rm } from "fs/promises";
|
|
4
4
|
import { dirname, join, resolve } from "path";
|
|
5
5
|
import { expandUserHome, getGloomberbDirs } from "../home";
|
|
6
6
|
import type { AppConfig } from "../../../types/config";
|
|
@@ -8,6 +8,8 @@ import { createDefaultConfig } from "../../../types/config";
|
|
|
8
8
|
import { debugLog } from "../../../utils/debug-log";
|
|
9
9
|
import { findAbsorbedCheckout, isDirectoryOrLink, isPluginDirectory } from "../../../plugins/loader";
|
|
10
10
|
import { EXTRACTED_PLUGINS } from "../../../plugins/extracted-plugins";
|
|
11
|
+
import { makePrivateDir, writePrivateFile } from "../../../utils/private-files";
|
|
12
|
+
import { tightenProfilePermissions } from "../../profile-permissions";
|
|
11
13
|
import type { ConfigMigrationHost } from "./migrations";
|
|
12
14
|
import {
|
|
13
15
|
normalizeConfigForSave,
|
|
@@ -113,11 +115,12 @@ const saveQueues = new Map<string, Promise<void>>();
|
|
|
113
115
|
let tempFileCounter = 0;
|
|
114
116
|
|
|
115
117
|
async function writeConfigFile(configPath: string, contents: string): Promise<void> {
|
|
116
|
-
await
|
|
118
|
+
await makePrivateDir(dirname(configPath));
|
|
117
119
|
// Unique per call: a clock tick is not, and two saves can land in one.
|
|
118
120
|
const tempPath = `${configPath}.${process.pid}.${Date.now()}.${tempFileCounter++}.tmp`;
|
|
119
121
|
try {
|
|
120
|
-
|
|
122
|
+
// Readable by the user alone: config.json holds broker credentials.
|
|
123
|
+
await writePrivateFile(tempPath, contents);
|
|
121
124
|
await rename(tempPath, configPath);
|
|
122
125
|
} catch (error) {
|
|
123
126
|
await rm(tempPath, { force: true }).catch(() => { /* Intentional ignore: Remove a partial temporary file while preserving the write error rethrown below. */ });
|
|
@@ -149,11 +152,12 @@ export async function saveConfig(config: AppConfig): Promise<void> {
|
|
|
149
152
|
|
|
150
153
|
export async function initDataDir(dataDir: string): Promise<AppConfig> {
|
|
151
154
|
configLog.info(`Initializing data directory: ${dataDir}`);
|
|
152
|
-
await
|
|
155
|
+
await makePrivateDir(dataDir);
|
|
153
156
|
const { config, needsSave } = await loadConfigState(dataDir);
|
|
154
157
|
if (needsSave) {
|
|
155
158
|
await saveConfig(config);
|
|
156
159
|
}
|
|
160
|
+
await tightenProfilePermissions(dataDir, [configFileFor(dataDir), getGloomberbDirs().configFile]);
|
|
157
161
|
return config;
|
|
158
162
|
}
|
|
159
163
|
|
|
@@ -166,7 +170,9 @@ export async function resetAllData(dataDir: string): Promise<void> {
|
|
|
166
170
|
|
|
167
171
|
export async function exportConfig(config: AppConfig, destPath: string): Promise<void> {
|
|
168
172
|
const { dataDir, ...rest } = config;
|
|
169
|
-
|
|
173
|
+
// The export carries broker credentials as config.json does: readable by the
|
|
174
|
+
// user alone, an existing file included, and never written through a link.
|
|
175
|
+
await writePrivateFile(expandHomePath(destPath), JSON.stringify(rest, null, 2));
|
|
170
176
|
}
|
|
171
177
|
|
|
172
178
|
export async function importConfig(dataDir: string, srcPath: string): Promise<AppConfig> {
|
|
@@ -30,6 +30,7 @@ import { DEFAULT_THEME, themes } from "../../../theme/themes";
|
|
|
30
30
|
import { sanitizeStarPrompt } from "../../../app/star-prompt/model";
|
|
31
31
|
import { sanitizeCommandBarOpens } from "../../../app/command-bar-intro";
|
|
32
32
|
import { sanitizeUpdatesConfig } from "../../../updater/channel";
|
|
33
|
+
import { ATTENTION_NOTICE_ACCOUNTS } from "../../../telemetry/attention-setting";
|
|
33
34
|
|
|
34
35
|
const configLog = debugLog.createLogger("config");
|
|
35
36
|
const reportedUnknownThemes = new Set<string>();
|
|
@@ -201,17 +202,28 @@ function withUpdates(updates: AppConfig["updates"]): Pick<AppConfig, "updates">
|
|
|
201
202
|
return updates ? { updates } : {};
|
|
202
203
|
}
|
|
203
204
|
|
|
204
|
-
/**
|
|
205
|
+
/**
|
|
206
|
+
* Only the switches that are set survive; an empty object is the same as none.
|
|
207
|
+
* Attention Counts is on by default, so a value that cannot be read turns it
|
|
208
|
+
* off instead: a hand-edited "false" or a broken block never counts as on.
|
|
209
|
+
*/
|
|
205
210
|
function sanitizeTelemetry(value: unknown): TelemetryConfig | undefined {
|
|
206
|
-
if (
|
|
211
|
+
if (value === undefined) return undefined;
|
|
212
|
+
if (!isRecord(value)) return { attention: false };
|
|
207
213
|
const telemetry: TelemetryConfig = {
|
|
208
214
|
...(typeof value.crashReports === "boolean" ? { crashReports: value.crashReports } : {}),
|
|
209
215
|
...(typeof value.usage === "boolean" ? { usage: value.usage } : {}),
|
|
210
|
-
...(
|
|
216
|
+
...(value.attention !== undefined ? { attention: value.attention === true } : {}),
|
|
217
|
+
...sanitizeAttentionNotice(value.attentionNoticeShownTo),
|
|
211
218
|
};
|
|
212
219
|
return Object.keys(telemetry).length > 0 ? telemetry : undefined;
|
|
213
220
|
}
|
|
214
221
|
|
|
222
|
+
function sanitizeAttentionNotice(value: unknown): Pick<TelemetryConfig, "attentionNoticeShownTo"> {
|
|
223
|
+
const accounts = sanitizeUniqueStringList(value).slice(-ATTENTION_NOTICE_ACCOUNTS);
|
|
224
|
+
return accounts.length > 0 ? { attentionNoticeShownTo: accounts } : {};
|
|
225
|
+
}
|
|
226
|
+
|
|
215
227
|
/**
|
|
216
228
|
* Keeps the shape honest without judging the contents: a chord that does not
|
|
217
229
|
* parse stays in the file and is reported when the table resolves, so a typo
|
|
@@ -286,6 +298,7 @@ function sanitizeOnboardingProgress(value: unknown): OnboardingProgress | undefi
|
|
|
286
298
|
accountStatus,
|
|
287
299
|
checkoutOpenedAt: typeof value.checkoutOpenedAt === "string" ? value.checkoutOpenedAt : undefined,
|
|
288
300
|
desks: Array.isArray(value.desks) ? sanitizeUniqueStringList(value.desks) : undefined,
|
|
301
|
+
rerun: value.rerun === true ? true : undefined,
|
|
289
302
|
};
|
|
290
303
|
}
|
|
291
304
|
|
package/src/data/plugin-cache.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { logFailure } from "../utils/log-failure";
|
|
|
2
2
|
import type { CachePolicy } from "../types/persistence";
|
|
3
3
|
import type { PluginPersistence } from "../types/plugin";
|
|
4
4
|
import { CachedQuery, type CachedValue } from "./cached-query";
|
|
5
|
+
import { isTransientFailure } from "../api-client/errors";
|
|
5
6
|
|
|
6
7
|
export interface PluginCacheResult<T> {
|
|
7
8
|
data: T;
|
|
@@ -12,6 +13,20 @@ export interface PluginCacheResult<T> {
|
|
|
12
13
|
source: "cache" | "network" | "stale-fallback";
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* `result`, or the failure of the refresh it stands in for when a saved copy
|
|
18
|
+
* may not answer for it. A saved copy covers only a failure asking again may
|
|
19
|
+
* get past (`isTransientFailure`: no connection, a timeout, a rate-limited or
|
|
20
|
+
* overloaded server, a reply whose source did not answer this time); a
|
|
21
|
+
* refusal (another 4xx, such as a series no longer served or a signed-out
|
|
22
|
+
* session) and an answer that failed validation are thrown, so they show as
|
|
23
|
+
* the errors they are instead of as old data.
|
|
24
|
+
*/
|
|
25
|
+
export function savedCopyForTransientFailure<R extends { source?: PluginCacheResult<unknown>["source"]; error?: unknown }>(result: R): R {
|
|
26
|
+
if (result.source === "stale-fallback" && !isTransientFailure(result.error)) throw result.error;
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
15
30
|
/** Shared persistence/request lifecycle; keys, codecs, validation and policies stay with the plugin. */
|
|
16
31
|
export function createPluginCache<T, Stored = T>(options: {
|
|
17
32
|
kind: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
|
+
import { createSqliteAccess, type SqliteAccess } from "./sqlite/access";
|
|
2
3
|
import { safeParseJson, serializeJson } from "./sqlite/json";
|
|
3
|
-
import { withSqliteBusyRetry } from "./sqlite/retry";
|
|
4
4
|
|
|
5
5
|
const DEFAULT_PLUGIN_STATE_SCHEMA_VERSION = 1;
|
|
6
6
|
|
|
@@ -24,14 +24,39 @@ interface CachedPluginStateRecord {
|
|
|
24
24
|
rawValue: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** A queued write's record (null for a delete), one object per write. */
|
|
28
|
+
interface PendingPluginState {
|
|
29
|
+
pluginId: string;
|
|
30
|
+
key: string;
|
|
31
|
+
record: CachedPluginStateRecord | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function stateKey(pluginId: string, key: string): string {
|
|
35
|
+
return `${pluginId}:${key}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A queued write's row, unambiguous where ids and keys hold colons. */
|
|
39
|
+
function rowKey(pluginId: string, key: string): string {
|
|
40
|
+
return `${pluginId}\u0000${key}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
export class PluginStateStore {
|
|
28
44
|
private readonly cache = new Map<string, CachedPluginStateRecord>();
|
|
45
|
+
/** Writes queued behind another program's lock, read back until they land. */
|
|
46
|
+
private readonly pending = new Map<string, PendingPluginState>();
|
|
29
47
|
|
|
30
|
-
constructor(
|
|
48
|
+
constructor(
|
|
49
|
+
private readonly db: Database,
|
|
50
|
+
private readonly access: SqliteAccess = createSqliteAccess(db),
|
|
51
|
+
) {}
|
|
31
52
|
|
|
32
53
|
get<T>(pluginId: string, key: string, schemaVersion = DEFAULT_PLUGIN_STATE_SCHEMA_VERSION): PluginStateRecord<T> | null {
|
|
33
|
-
const cacheKey =
|
|
34
|
-
const
|
|
54
|
+
const cacheKey = stateKey(pluginId, key);
|
|
55
|
+
const pending = this.pending.get(rowKey(pluginId, key));
|
|
56
|
+
if (pending) {
|
|
57
|
+
return pending.record?.schemaVersion === schemaVersion ? pending.record as PluginStateRecord<T> : null;
|
|
58
|
+
}
|
|
59
|
+
const row = this.access.run("load plugin state", () => (
|
|
35
60
|
this.db
|
|
36
61
|
.query<{ schema_version: number; value: string; updated_at: number }, [string, string]>(
|
|
37
62
|
"SELECT schema_version, value, updated_at FROM plugin_state WHERE plugin_id = ? AND key = ?",
|
|
@@ -77,46 +102,42 @@ export class PluginStateStore {
|
|
|
77
102
|
setMany(entries: PluginStateSetEntry[]): void {
|
|
78
103
|
if (entries.length === 0) return;
|
|
79
104
|
const updatedAt = Date.now();
|
|
80
|
-
const
|
|
105
|
+
const writes: PendingPluginState[] = entries.map((entry) => ({
|
|
81
106
|
pluginId: entry.pluginId,
|
|
82
107
|
key: entry.key,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
record: {
|
|
109
|
+
value: entry.value,
|
|
110
|
+
schemaVersion: entry.schemaVersion ?? DEFAULT_PLUGIN_STATE_SCHEMA_VERSION,
|
|
111
|
+
updatedAt,
|
|
112
|
+
rawValue: serializeJson(entry.value),
|
|
113
|
+
},
|
|
86
114
|
}));
|
|
87
115
|
|
|
88
|
-
|
|
116
|
+
this.write("save plugin state", writes, writes.length === 1, () => {
|
|
89
117
|
const insert = this.db.query(
|
|
90
118
|
`INSERT OR REPLACE INTO plugin_state (plugin_id, key, schema_version, value, updated_at)
|
|
91
119
|
VALUES (?, ?, ?, ?, ?)`,
|
|
92
120
|
);
|
|
93
121
|
const tx = this.db.transaction(() => {
|
|
94
|
-
for (const record of
|
|
95
|
-
insert.run(
|
|
122
|
+
for (const { pluginId, key, record } of writes) {
|
|
123
|
+
insert.run(pluginId, key, record!.schemaVersion, record!.rawValue, updatedAt);
|
|
96
124
|
}
|
|
97
125
|
});
|
|
98
126
|
tx();
|
|
99
127
|
});
|
|
100
128
|
|
|
101
|
-
for (const record of
|
|
102
|
-
this.cache.set(`${record.pluginId}:${record.key}`, {
|
|
103
|
-
value: record.value,
|
|
104
|
-
schemaVersion: record.schemaVersion,
|
|
105
|
-
updatedAt,
|
|
106
|
-
rawValue: record.rawValue,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
129
|
+
for (const { pluginId, key, record } of writes) this.cache.set(stateKey(pluginId, key), record!);
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
delete(pluginId: string, key: string): void {
|
|
112
|
-
|
|
133
|
+
this.write("delete plugin state", [{ pluginId, key, record: null }], true, () => {
|
|
113
134
|
this.db.query("DELETE FROM plugin_state WHERE plugin_id = ? AND key = ?").run(pluginId, key);
|
|
114
135
|
});
|
|
115
|
-
this.cache.delete(
|
|
136
|
+
this.cache.delete(stateKey(pluginId, key));
|
|
116
137
|
}
|
|
117
138
|
|
|
118
139
|
keys(pluginId: string): string[] {
|
|
119
|
-
|
|
140
|
+
const stored = this.access.run("list plugin state keys", () => (
|
|
120
141
|
this.db
|
|
121
142
|
.query<{ key: string }, [string]>(
|
|
122
143
|
"SELECT key FROM plugin_state WHERE plugin_id = ? ORDER BY key",
|
|
@@ -124,10 +145,18 @@ export class PluginStateStore {
|
|
|
124
145
|
.all(pluginId)
|
|
125
146
|
.map((row) => row.key)
|
|
126
147
|
));
|
|
148
|
+
if (this.pending.size === 0) return stored;
|
|
149
|
+
const keys = new Set(stored);
|
|
150
|
+
for (const entry of this.pending.values()) {
|
|
151
|
+
if (entry.pluginId !== pluginId) continue;
|
|
152
|
+
if (entry.record) keys.add(entry.key);
|
|
153
|
+
else keys.delete(entry.key);
|
|
154
|
+
}
|
|
155
|
+
return [...keys].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
127
156
|
}
|
|
128
157
|
|
|
129
158
|
pluginIds(): string[] {
|
|
130
|
-
|
|
159
|
+
const stored = this.access.run("list plugin state ids", () => (
|
|
131
160
|
this.db
|
|
132
161
|
.query<{ plugin_id: string }, []>(
|
|
133
162
|
"SELECT DISTINCT plugin_id FROM plugin_state ORDER BY plugin_id",
|
|
@@ -135,10 +164,19 @@ export class PluginStateStore {
|
|
|
135
164
|
.all()
|
|
136
165
|
.map((row) => row.plugin_id)
|
|
137
166
|
));
|
|
167
|
+
if (this.pending.size === 0) return stored;
|
|
168
|
+
const ids = new Set(stored);
|
|
169
|
+
for (const { pluginId } of this.pending.values()) {
|
|
170
|
+
if (this.keys(pluginId).length > 0) ids.add(pluginId);
|
|
171
|
+
else ids.delete(pluginId);
|
|
172
|
+
}
|
|
173
|
+
return [...ids].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
138
174
|
}
|
|
139
175
|
|
|
140
176
|
clear(pluginId: string): void {
|
|
141
|
-
|
|
177
|
+
const cleared = this.keys(pluginId).map((key): PendingPluginState => ({ pluginId, key, record: null }));
|
|
178
|
+
// Deletes rows this store may not know of: never one row's write.
|
|
179
|
+
this.write("clear plugin state", cleared, false, () => {
|
|
142
180
|
this.db.query("DELETE FROM plugin_state WHERE plugin_id = ?").run(pluginId);
|
|
143
181
|
});
|
|
144
182
|
for (const cacheKey of this.cache.keys()) {
|
|
@@ -147,4 +185,23 @@ export class PluginStateStore {
|
|
|
147
185
|
}
|
|
148
186
|
}
|
|
149
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Runs `statement` through the write queue, reading `entries` back until it
|
|
191
|
+
* lands. A write of exactly one row (`oneRow`) replaces a queued write of
|
|
192
|
+
* the same row.
|
|
193
|
+
*/
|
|
194
|
+
private write(operationName: string, entries: PendingPluginState[], oneRow: boolean, statement: () => void): void {
|
|
195
|
+
for (const entry of entries) this.pending.set(rowKey(entry.pluginId, entry.key), entry);
|
|
196
|
+
const only = oneRow && entries.length === 1 ? entries[0] : undefined;
|
|
197
|
+
this.access.write(operationName, statement, {
|
|
198
|
+
...(only ? { key: `plugin_state\u0000${rowKey(only.pluginId, only.key)}` } : {}),
|
|
199
|
+
settled: () => {
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
const pendingKey = rowKey(entry.pluginId, entry.key);
|
|
202
|
+
if (this.pending.get(pendingKey) === entry) this.pending.delete(pendingKey);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
150
207
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readdir } from "fs/promises";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { logFailure } from "../utils/log-failure";
|
|
4
|
+
import { restrictDir, restrictFile } from "../utils/private-files";
|
|
5
|
+
|
|
6
|
+
/** The data folder's files that hold the Cloud session and cache, or logs of what the user did. */
|
|
7
|
+
const PRIVATE_DATA_FILES = [
|
|
8
|
+
".gloomberb-cache.db",
|
|
9
|
+
".gloomberb-cache.db-wal",
|
|
10
|
+
".gloomberb-cache.db-shm",
|
|
11
|
+
"stall-log.txt",
|
|
12
|
+
"stall-reports.json",
|
|
13
|
+
"crash-reports.json",
|
|
14
|
+
"desktop-view-ids.json",
|
|
15
|
+
];
|
|
16
|
+
/** `remote-control.json` and one per app (`remote-control.tui.json`): each holds the remote-control token. */
|
|
17
|
+
const REMOTE_ENDPOINT_FILE = /^remote-control(?:\.[\w-]+)?\.json$/;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tightens a profile made before its folders and files were created private,
|
|
21
|
+
* or under a umask that let others read them: the data folder and each
|
|
22
|
+
* config.json's folder open to the user alone, config.json (broker
|
|
23
|
+
* credentials) and the files above readable by the user alone. A step that
|
|
24
|
+
* fails (a folder another account owns, a disk without POSIX modes) is logged
|
|
25
|
+
* and the others still run; startup goes on either way. Nothing to do on
|
|
26
|
+
* Windows (see private-files.ts).
|
|
27
|
+
*/
|
|
28
|
+
export async function tightenProfilePermissions(dataDir: string, configFiles: readonly string[]): Promise<void> {
|
|
29
|
+
if (process.platform === "win32") return;
|
|
30
|
+
const steps: Array<() => Promise<void>> = [
|
|
31
|
+
...[...new Set([dataDir, ...configFiles.map((file) => dirname(file))])].map((dir) => () => restrictDir(dir)),
|
|
32
|
+
...[...new Set(configFiles)].map((file) => () => restrictFile(file)),
|
|
33
|
+
...PRIVATE_DATA_FILES.map((name) => () => restrictFile(join(dataDir, name))),
|
|
34
|
+
async () => {
|
|
35
|
+
for (const name of await readdir(dataDir)) {
|
|
36
|
+
if (REMOTE_ENDPOINT_FILE.test(name)) await restrictFile(join(dataDir, name));
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
for (const step of steps) {
|
|
41
|
+
try {
|
|
42
|
+
await step();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
logFailure("data.profile-permissions", "Restricting a profile file to its user failed")(error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|