dexterm-cli 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/agents/src/bus.js +37 -1
- package/dist/packages/agents/src/follow.js +105 -0
- package/dist/packages/agents/src/index.js +5 -0
- package/dist/packages/agents/src/spectator-server.js +153 -0
- package/dist/packages/agents/src/spectator.js +212 -0
- package/dist/packages/cli/assets/claude/analyst-system.md +7 -1
- package/dist/packages/cli/src/agent-cmd.js +71 -7
- package/dist/packages/cli/src/agent-spectator.js +37 -0
- package/dist/packages/cli/src/alerts/format.js +25 -0
- package/dist/packages/cli/src/app-help-gen.js +365 -0
- package/dist/packages/cli/src/docs-kb-gen.js +228 -0
- package/dist/packages/cli/src/doctor.js +28 -12
- package/dist/packages/cli/src/follow-cmd.js +107 -1
- package/dist/packages/cli/src/help-cmd.js +48 -0
- package/dist/packages/cli/src/index.js +1 -1
- package/dist/packages/cli/src/kb-skill-cmd.js +3 -0
- package/dist/packages/cli/src/larp-detected.js +209 -0
- package/dist/packages/cli/src/live/index.js +64 -3
- package/dist/packages/cli/src/live/screens/token-live.js +24 -0
- package/dist/packages/cli/src/live/signals.js +109 -0
- package/dist/packages/cli/src/live/token.js +38 -1
- package/dist/packages/cli/src/live/trade.js +24 -1
- package/dist/packages/cli/src/main.js +495 -11
- package/dist/packages/cli/src/mcp-cmd.js +2 -7
- package/dist/packages/cli/src/mcp-follow.js +225 -0
- package/dist/packages/cli/src/pnl-app.js +221 -0
- package/dist/packages/cli/src/pnl-card.js +615 -0
- package/dist/packages/cli/src/record-cmd.js +455 -0
- package/dist/packages/cli/src/rules.js +50 -2
- package/dist/packages/cli/src/screens/follows.js +91 -2
- package/dist/packages/cli/src/screens/overlay.js +3 -0
- package/dist/packages/cli/src/screens/positions.js +1 -1
- package/dist/packages/cli/src/screens/rules.js +1 -1
- package/dist/packages/cli/src/spectate-cmd.js +115 -0
- package/dist/packages/cli/src/state.js +1 -1
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/voice-cmd.js +86 -11
- package/dist/packages/cli/src/voice-hud.js +1 -1
- package/dist/packages/cli/src/x-cmd.js +11 -5
- package/dist/packages/custom/src/rules.js +2 -1
- package/dist/packages/data/src/analyst-actions.js +130 -3
- package/dist/packages/data/src/fast-lane.js +16 -2
- package/dist/packages/data/src/index.js +1 -0
- package/dist/packages/data/src/intel/live-signals.js +185 -0
- package/dist/packages/data/src/trade/copy/index.js +1 -1
- package/dist/packages/data/src/trade/copy/stats.js +36 -2
- package/dist/packages/data/src/trade/copy/tracker.js +3 -2
- package/dist/packages/mcp/assets/kb/app-help/app-help-command.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/ask-the-analyst.md +43 -0
- package/dist/packages/mcp/assets/kb/app-help/backtest.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/bubble-map.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/calm-mode.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/change-voice-speed.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/change-voice.md +30 -0
- package/dist/packages/mcp/assets/kb/app-help/chart-popup-selftest.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/coin-images.md +29 -0
- package/dist/packages/mcp/assets/kb/app-help/configure-rpc-key.md +28 -0
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent-registry.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/custom-keymap.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/edit-kb-or-skill.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/edit-rules.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/export-a-view.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/follow-a-wallet.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/getting-started.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/kol-lists.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/larp-check.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +41 -0
- package/dist/packages/mcp/assets/kb/app-help/login-with-x.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/narrative-board.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/open-a-coin.md +43 -0
- package/dist/packages/mcp/assets/kb/app-help/open-chart-popup.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/order-plans.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/paper-trade.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/record-headless.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/recorder-history.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/rug-check.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/run-doctor.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +190 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +180 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +199 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +240 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +192 -0
- package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +181 -0
- package/dist/packages/mcp/assets/kb/app-help/set-a-filter.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/setup-neural-voice.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/share-packs.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/sort-the-screener.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +39 -0
- package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +33 -0
- package/dist/packages/mcp/assets/kb/app-help/switch-theme.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/turn-on-alerts.md +37 -0
- package/dist/packages/mcp/assets/kb/app-help/update-dexterm.md +30 -0
- package/dist/packages/mcp/assets/kb/app-help/use-presets.md +42 -0
- package/dist/packages/mcp/assets/kb/app-help/use-voice-mode.md +36 -0
- package/dist/packages/mcp/assets/kb/app-help/watch-a-coin.md +34 -0
- package/dist/packages/mcp/assets/kb/app-help/watch-headless.md +35 -0
- package/dist/packages/mcp/assets/kb/app-help/what-dexterm-shows-agents.md +31 -0
- package/dist/packages/mcp/assets/kb/app-help/workspaces.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-capability-tiers-enforced-server-side.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-install-in-one-command.md +49 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-per-agent-mandates.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-recipes.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-red-team-coverage.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-two-ways-to-connect.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-watch-an-agent-work-spectator-view.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-answer-style.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-it-knows-the-app.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-mcp-tools.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-plain-language-filters.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-skills.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-can-change-three-tiers.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-the-analyst-can-never-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-1-start-dexterm.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-2-open-a-real-coin-by-contract-address.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-3-read-the-safety-panel.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-4-read-the-holder-panel.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-5-optionally-ask-the-analyst.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-6-export-or-share-what-you-see.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-7-quit-safely.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-check-the-installation-with-doctor.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-choose-a-setup-path.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-common-mistakes-and-recovery.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-copy-and-paste-without-surprises.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-data-says-unavailable-stale-or-rate-limited.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-dexterm-says-command-not-found.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-optional-help-is-unavailable.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-paste-does-nothing-or-pastes-twice.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-the-screen-looks-broken.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-you-typed-the-wrong-thing.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-install-node-js-first.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-linux.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-homebrew.md +46 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-or-linux-npm.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-open-the-terminal.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-reopen-dexterm-later.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-ten-minute-first-win.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-terminal-shell-prompt-command-output-and-path.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-where-to-go-next.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-expectations.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-powershell-experimental.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-dexterm-help.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-filter-grammar.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-flags.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-invocations.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-presets.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-scan-commands.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-sort-keys.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-subcommands.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +45 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-history-recorder.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-packs.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-push-alerts-and-dexterm-watch.md +45 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-rules-engine.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-analyst-s-launch.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-optional-free-rpc-key.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-themes-and-keymaps.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-themes-and-look.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-widgets.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-workspaces.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-1-follow-a-wallet-see-what-it-does.md +51 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-cache.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-health-registry.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-rate-limits-and-pacing.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-request-budget-meter.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-the-helius-credit-meter.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-dexterm-doctor.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +71 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-next.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-supported-platforms.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-updating.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-global-keys.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-keys-handled-by-the-loop.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-per-screen-keys.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-what-shows.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +55 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-open-link-keys.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-paste-a-coin-on-the-web.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-timestamped-follow-up-drafts.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp.md +24 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-degraded-states-you-will-see.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-free-vs-paid-x-api-tiers-your-bill-your-limits.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-kol-lists-and-the-per-handle-filter.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-tests.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-what-is-read-and-when.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-what-you-set-up-once.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-where-the-tokens-live.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-bubble-map-popup.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-build-the-native-window.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-config-dexterm-popup-json.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-docking-macos.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-keys-in-the-popup.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-security-model.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-token-and-feeds-popups.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-cost-control.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-keys-flag-command.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-safety-four-layers.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-states.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-triggers-and-score.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan.md +24 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-frozen-canary.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-frozen-task-1401-evaluation.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-latest-skill-care-run.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-links-only-larp-baseline-where-it-is-weak.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-not-measured.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-rug-verdict-rules.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-source-and-rebuild.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-1-screener.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-2-new-pairs.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-3-watchlist.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-4-analyst-tab.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-bubble-map.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-coin-images.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-live-holder-and-rug-signals.md +51 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-paper-ticket-and-positions.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-the-look.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-trending-social-and-kol-lists.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-1-the-agent-activity-window-follow-mode.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-2-the-read-only-stream-spectate.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-3-save-and-replay-record-file-dexterm-spectate.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-quick-start.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-security-model.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-see-also.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-streaming-it.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-what-is-redacted.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-in-app-messages.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-reporting-a-problem.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-doctor.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-it-clarifies-it-never-guesses.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-latency.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-privacy.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-still-unverified.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-the-voice-neural-by-default.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-what-it-can-never-do.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-what-you-can-say.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice.md +25 -0
- package/dist/packages/mcp/src/app-help.js +258 -0
- package/dist/packages/mcp/src/firewall.js +2 -2
- package/dist/packages/mcp/src/index.js +5 -1
- package/dist/packages/mcp/src/server.js +129 -4
- package/dist/packages/mcp/src/spectator-sink.js +144 -0
- package/dist/packages/packs/src/index.js +2 -0
- package/dist/packages/packs/src/skill-care.js +206 -0
- package/dist/packages/popup/src/pages/spectator.css +45 -0
- package/dist/packages/popup/src/pages/spectator.html +25 -0
- package/dist/packages/popup/src/pages/spectator.js +74 -0
- package/dist/packages/popup/src/selftest.js +19 -0
- package/dist/packages/popup/src/server.js +7 -1
- package/dist/packages/tui/src/keymap.js +10 -0
- package/dist/packages/voice/src/controller.js +42 -1
- package/dist/packages/voice/src/doctor.js +25 -8
- package/dist/packages/voice/src/engines.js +8 -1
- package/dist/packages/voice/src/index.js +3 -1
- package/dist/packages/voice/src/speakable.js +111 -0
- package/dist/packages/voice/src/stt/whisper.js +4 -2
- package/dist/packages/voice/src/tts/piper-setup.js +229 -0
- package/dist/packages/voice/src/tts/piper.js +309 -66
- package/dist/packages/voice/src/tts/say.js +3 -2
- package/dist/packages/voice/src/tts/voices.js +8 -5
- package/package.json +6 -5
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Local-only LARP/high-risk follow-up draft pipeline (task-1433). History mode reads the recorder
|
|
2
|
+
// without network access, scores only the point-in-time t0 row, joins only later outcome labels, and
|
|
3
|
+
// writes drafts/cards for human review. Live mode performs one read-only enrichment for one mint.
|
|
4
|
+
// Nothing here has X credentials, opens a composer, schedules work, trades, arms, signs or posts.
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join, resolve } from 'node:path';
|
|
7
|
+
import { createDataLayer, defaultDbPath } from '../../data/src/index.js';
|
|
8
|
+
import { openHistoryReadOnly, exportRecorded } from '../../recorder/src/index.js';
|
|
9
|
+
import { h1Model, rugFeaturesFromRecord, rugFeaturesFromToken } from '../../packs/src/index.js';
|
|
10
|
+
import { Canvas, THEMES } from '../../tui/src/index.js';
|
|
11
|
+
import { writeExport, fmtAge } from "./export.js";
|
|
12
|
+
export const LARP_DRAFT_MIN_SAMPLE = 50;
|
|
13
|
+
export const LARP_DRAFT_HORIZON = 259_200;
|
|
14
|
+
export const LARP_DRAFT_THRESHOLD = 0.5;
|
|
15
|
+
const isoMinute = (t) => new Date(t).toISOString().replace(/:\d\d\.\d{3}Z$/, 'Z');
|
|
16
|
+
const shortMint = (mint) => `${mint.slice(0, 4)}...${mint.slice(-4)}`;
|
|
17
|
+
const pct = (n, d) => `${d ? ((n / d) * 100).toFixed(1) : '0.0'}%`;
|
|
18
|
+
/** One point-in-time flag per mint: earliest flagged t0 with an observed outcome at this horizon. */
|
|
19
|
+
export function buildFollowUpDataset(records, horizonSec = LARP_DRAFT_HORIZON, threshold = LARP_DRAFT_THRESHOLD) {
|
|
20
|
+
const model = h1Model();
|
|
21
|
+
const byMint = new Map();
|
|
22
|
+
for (const r of records) {
|
|
23
|
+
const outcome = r.outcomes[`${horizonSec}`];
|
|
24
|
+
if (!outcome || byMint.has(r.mint))
|
|
25
|
+
continue;
|
|
26
|
+
const score = model.score(rugFeaturesFromRecord(r.features));
|
|
27
|
+
if (score < threshold)
|
|
28
|
+
continue;
|
|
29
|
+
byMint.set(r.mint, { mint: r.mint, t0: r.t0, score, model: model.id, outcome });
|
|
30
|
+
}
|
|
31
|
+
const flagged = [...byMint.values()];
|
|
32
|
+
const collapsed = flagged.filter((r) => r.outcome.positive);
|
|
33
|
+
return { horizonSec, threshold, flagged, collapsed, didNotCollapse: flagged.length - collapsed.length };
|
|
34
|
+
}
|
|
35
|
+
function outcomeText(o) {
|
|
36
|
+
if (o.label === 'rug')
|
|
37
|
+
return 'recorder label: liquidity-pull condition';
|
|
38
|
+
if (o.label === 'dead')
|
|
39
|
+
return 'recorder label: inactive at follow-up';
|
|
40
|
+
return `recorder label: ${o.label}`;
|
|
41
|
+
}
|
|
42
|
+
function datasetLine(d) {
|
|
43
|
+
return `Same-dataset follow-up: ${d.collapsed.length}/${d.flagged.length} met the condition (${pct(d.collapsed.length, d.flagged.length)}); ${d.didNotCollapse}/${d.flagged.length} did not.`;
|
|
44
|
+
}
|
|
45
|
+
function cardCanvas(row, d, now) {
|
|
46
|
+
const c = new Canvas(92, 25, THEMES.cyberspace);
|
|
47
|
+
const t = c.theme;
|
|
48
|
+
c.fill(0, 0, c.w, c.h, ' ', { bg: t.bg });
|
|
49
|
+
c.fill(0, 0, c.w, 3, ' ', { bg: t.headBg });
|
|
50
|
+
c.text(3, 1, 'LARP DETECTED · RECORDED FOLLOW-UP', { fg: t.warn, bg: t.headBg, b: true });
|
|
51
|
+
c.textR(0, 1, c.w - 3, shortMint(row.mint), { fg: t.muted, bg: t.headBg });
|
|
52
|
+
c.text(4, 5, 'AT THE RECORDED MOMENT', { fg: t.muted, b: true });
|
|
53
|
+
c.text(4, 7, `HIGH RISK ${Math.round(row.score * 100)}/100`, { fg: t.down, b: true });
|
|
54
|
+
c.text(4, 9, `${row.model} heuristic · model output · risk flag, not a certificate`, { fg: t.text });
|
|
55
|
+
c.text(4, 10, `${isoMinute(row.t0)} · local recorder · t0 evidence age ${fmtAge((now - row.t0) / 1000)}`, { fg: t.muted });
|
|
56
|
+
c.text(4, 13, 'LATER OBSERVED', { fg: t.muted, b: true });
|
|
57
|
+
c.text(4, 15, outcomeText(row.outcome).toUpperCase(), { fg: t.warn, b: true });
|
|
58
|
+
c.text(4, 17, `${fmtAge(d.horizonSec)} follow-up · ${row.outcome.tier} · labeler evidence`, { fg: t.text });
|
|
59
|
+
c.text(4, 20, datasetLine(d), { fg: t.muted }, c.w - 8);
|
|
60
|
+
c.text(4, 22, 'Sources: local dexterm recorder · DexScreener + Jupiter · draft only', { fg: t.muted }, c.w - 8);
|
|
61
|
+
c.text(4, 23, 'Not financial advice.', { fg: t.muted });
|
|
62
|
+
return c;
|
|
63
|
+
}
|
|
64
|
+
function historyDraft(row, d, now) {
|
|
65
|
+
return [
|
|
66
|
+
'LARP DETECTED — recorded follow-up',
|
|
67
|
+
'',
|
|
68
|
+
`Here is what the data showed: graded HIGH RISK ${Math.round(row.score * 100)}/100 at ${isoMinute(row.t0)} by ${row.model} (heuristic model output); ${outcomeText(row.outcome)} at the ${fmtAge(d.horizonSec)} follow-up.`,
|
|
69
|
+
'',
|
|
70
|
+
datasetLine(d),
|
|
71
|
+
'',
|
|
72
|
+
`Evidence: local dexterm recorder; DexScreener + Jupiter; t0 data age ${fmtAge((now - row.t0) / 1000)} at export; ${row.outcome.tier}; coin ${shortMint(row.mint)}.`,
|
|
73
|
+
'',
|
|
74
|
+
'A risk flag is not a certificate. Draft only; a human decides whether to post. Not financial advice.',
|
|
75
|
+
].join('\n') + '\n';
|
|
76
|
+
}
|
|
77
|
+
export function renderHistoryDrafts(d, opts) {
|
|
78
|
+
const now = opts.now ?? Date.now();
|
|
79
|
+
const min = opts.minSample ?? LARP_DRAFT_MIN_SAMPLE;
|
|
80
|
+
if (d.flagged.length < min)
|
|
81
|
+
return { drafts: [], cards: [] };
|
|
82
|
+
mkdirSync(opts.outDir, { recursive: true, mode: 0o700 });
|
|
83
|
+
const rows = [...d.collapsed].sort((a, b) => b.t0 - a.t0).slice(0, opts.limit ?? 3);
|
|
84
|
+
const drafts = [], cards = [];
|
|
85
|
+
for (let i = 0; i < rows.length; i++) {
|
|
86
|
+
const row = rows[i];
|
|
87
|
+
const stem = `${String(i + 1).padStart(2, '0')}-${row.mint.slice(0, 4)}-${row.mint.slice(-4)}`;
|
|
88
|
+
const draft = join(opts.outDir, `${stem}.md`);
|
|
89
|
+
writeFileSync(draft, historyDraft(row, d, now), { mode: 0o600 });
|
|
90
|
+
drafts.push(draft);
|
|
91
|
+
const view = {
|
|
92
|
+
kind: 'larp-detected', title: `RECORDED FOLLOW-UP · ${shortMint(row.mint)}`, label: 'replay', canvas: cardCanvas(row, d, now),
|
|
93
|
+
ages: [
|
|
94
|
+
{ name: 't0 evidence', source: 'local recorder · DexScreener + Jupiter', ageSec: (now - row.t0) / 1000 },
|
|
95
|
+
{ name: 'follow-up', source: `${row.outcome.tier} recorder outcome`, ageSec: d.horizonSec },
|
|
96
|
+
],
|
|
97
|
+
report: historyDraft(row, d, now).trimEnd().split('\n'),
|
|
98
|
+
};
|
|
99
|
+
const saved = writeExport(view, { dir: opts.outDir, now: now + i, subject: stem });
|
|
100
|
+
cards.push(saved.png);
|
|
101
|
+
}
|
|
102
|
+
return { drafts, cards };
|
|
103
|
+
}
|
|
104
|
+
function parse(argv) {
|
|
105
|
+
const out = {};
|
|
106
|
+
for (let i = 0; i < argv.length; i++) {
|
|
107
|
+
const a = argv[i];
|
|
108
|
+
if (!a.startsWith('--'))
|
|
109
|
+
continue;
|
|
110
|
+
const value = argv[i + 1];
|
|
111
|
+
if (value !== undefined && !value.startsWith('--')) {
|
|
112
|
+
out[a.slice(2)] = value;
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
out[a.slice(2)] = true;
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
export const LARP_DETECTED_HELP = `dexterm larp-detected: local draft/card pipeline; never posts or schedules
|
|
121
|
+
|
|
122
|
+
dexterm larp-detected --out DIR [--db PATH] [--horizon SEC] [--limit 3]
|
|
123
|
+
dexterm larp-detected --live MINT --out DIR
|
|
124
|
+
|
|
125
|
+
History mode is cron-ready and offline: it reads recorder t0 rows plus later labels, requires at least
|
|
126
|
+
${LARP_DRAFT_MIN_SAMPLE} unique flagged coins, and otherwise writes no drafts. Live mode performs one read-only
|
|
127
|
+
keyless lookup and writes a graded-now card. All output is for human review.`;
|
|
128
|
+
function liveFeatures(row) {
|
|
129
|
+
return rugFeaturesFromToken({
|
|
130
|
+
liquidity_usd: row.pair.liquidityUsd, mcap: row.mcap?.value ?? row.pair.mcap, age_sec: row.token.createdAt === null ? null : Math.max(0, (Date.now() - row.token.createdAt) / 1000),
|
|
131
|
+
top10_pct: row.audit?.top10Pct ?? null, dev_pct: row.audit?.devPct ?? null, organic_score: row.stats?.organicScore ?? null,
|
|
132
|
+
holder_count: row.stats?.holderCount ?? null, mint_authority: row.audit?.mintAuthority ?? null, freeze_authority: row.audit?.freezeAuthority ?? null,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async function liveCard(mint, outDir, dbPath, io) {
|
|
136
|
+
if (!/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(mint)) {
|
|
137
|
+
io.stderr.write('dexterm larp-detected: --live needs a Solana mint\n');
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
140
|
+
const dl = createDataLayer({ dbPath });
|
|
141
|
+
try {
|
|
142
|
+
const result = await dl.store.enrich([mint]);
|
|
143
|
+
const row = result.rows[0];
|
|
144
|
+
if (!row) {
|
|
145
|
+
io.stderr.write('dexterm larp-detected: no current market record found for that mint\n');
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
const now = Date.now(), model = h1Model(), score = model.score(liveFeatures(row));
|
|
149
|
+
const c = new Canvas(92, 20, THEMES.cyberspace), t = c.theme;
|
|
150
|
+
c.fill(0, 0, c.w, c.h, ' ', { bg: t.bg });
|
|
151
|
+
c.fill(0, 0, c.w, 3, ' ', { bg: t.headBg });
|
|
152
|
+
c.text(3, 1, 'LARP CHECK · GRADED NOW', { fg: t.warn, bg: t.headBg, b: true });
|
|
153
|
+
c.textR(0, 1, c.w - 3, shortMint(mint), { fg: t.muted, bg: t.headBg });
|
|
154
|
+
c.text(4, 6, `${score >= LARP_DRAFT_THRESHOLD ? 'HIGH RISK' : 'BELOW HIGH-RISK LINE'} ${Math.round(score * 100)}/100`, { fg: score >= LARP_DRAFT_THRESHOLD ? t.down : t.up, b: true });
|
|
155
|
+
c.text(4, 9, `${model.id} heuristic · model output · risk flag, not a certificate`, { fg: t.text });
|
|
156
|
+
c.text(4, 11, `${isoMinute(now)} · LIVE DATA · DexScreener + Jupiter`, { fg: t.muted });
|
|
157
|
+
c.text(4, 14, 'No later outcome yet. No person, project or wallet is identified.', { fg: t.text });
|
|
158
|
+
c.text(4, 17, 'Draft only · human review · not financial advice', { fg: t.muted });
|
|
159
|
+
const view = { kind: 'larp-now', title: `GRADED NOW · ${shortMint(mint)}`, label: 'live', canvas: c,
|
|
160
|
+
ages: [{ name: 'market', source: 'DexScreener + Jupiter', ageSec: Math.max(0, (now - Math.max(row.token.fetchedAt, row.pair.fetchedAt)) / 1000) }],
|
|
161
|
+
report: [`coin ${shortMint(mint)}`, `graded now ${Math.round(score * 100)}/100 · ${model.id} heuristic model output`, 'No later outcome yet. Risk flag, not a certificate. Draft only.'],
|
|
162
|
+
};
|
|
163
|
+
const saved = writeExport(view, { dir: outDir, now, subject: `${mint.slice(0, 4)}-${mint.slice(-4)}` });
|
|
164
|
+
io.stdout.write(`graded-now card: ${saved.png}\n`);
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
dl.close();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export async function larpDetectedCommand(argv, io) {
|
|
172
|
+
const o = parse(argv);
|
|
173
|
+
if (o.help || o.h) {
|
|
174
|
+
io.stdout.write(LARP_DETECTED_HELP + '\n');
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
const dbPath = typeof o.db === 'string' ? resolve(o.db) : (io.env.DEXTERM_DB_PATH || defaultDbPath(io.env));
|
|
178
|
+
const outDir = typeof o.out === 'string' ? resolve(o.out) : resolve('marketing', 'larp-detected', new Date().toISOString().slice(0, 10));
|
|
179
|
+
if (typeof o.live === 'string')
|
|
180
|
+
return liveCard(o.live, outDir, dbPath, io);
|
|
181
|
+
if (!existsSync(dbPath)) {
|
|
182
|
+
io.stderr.write(`dexterm larp-detected: no recorder history at ${dbPath}\n`);
|
|
183
|
+
return 2;
|
|
184
|
+
}
|
|
185
|
+
const horizon = typeof o.horizon === 'string' ? Number(o.horizon) : LARP_DRAFT_HORIZON;
|
|
186
|
+
if (!Number.isFinite(horizon) || horizon <= 0) {
|
|
187
|
+
io.stderr.write('dexterm larp-detected: --horizon must be positive seconds\n');
|
|
188
|
+
return 2;
|
|
189
|
+
}
|
|
190
|
+
const db = openHistoryReadOnly(dbPath);
|
|
191
|
+
if (!db) {
|
|
192
|
+
io.stderr.write('dexterm larp-detected: recorder database could not be opened read-only\n');
|
|
193
|
+
return 2;
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
const dataset = buildFollowUpDataset(exportRecorded(db, { labelledOnly: true }).records, horizon);
|
|
197
|
+
if (dataset.flagged.length < LARP_DRAFT_MIN_SAMPLE) {
|
|
198
|
+
io.stdout.write(`thin sample: ${dataset.flagged.length} unique flagged coins (< ${LARP_DRAFT_MIN_SAMPLE}); wrote no drafts\n`);
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
const limit = typeof o.limit === 'string' ? Math.max(0, Math.floor(Number(o.limit))) : 3;
|
|
202
|
+
const saved = renderHistoryDrafts(dataset, { outDir, limit: Number.isFinite(limit) ? limit : 3 });
|
|
203
|
+
io.stdout.write(`${datasetLine(dataset)}\nwrote ${saved.drafts.length} draft(s) and ${saved.cards.length} card(s) to ${outDir}\n`);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
db.close();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -10,15 +10,24 @@ import { NewPairsLive } from "./newpairs.js";
|
|
|
10
10
|
import { WatchlistLive } from "./watchlist.js";
|
|
11
11
|
import { TradeTicketLive, PositionsLive, LivePositions, CombinedPositions, POSITIONS_REFRESH_MS } from "./trade.js";
|
|
12
12
|
import { IntelLive } from "./intel.js";
|
|
13
|
+
import { LiveSignalsLive } from "./signals.js";
|
|
13
14
|
import { CopyLive } from "./copy.js";
|
|
14
15
|
export { TokenLive, INTERVALS, panelCaption, emptyView } from "./token.js";
|
|
15
16
|
export { NewPairsLive, latencyStats, percentile, RECENT_POLL_MS, NEW_TOKENS_POLL_MS, ENRICH_POLL_MS } from "./newpairs.js";
|
|
16
17
|
export { WatchlistLive, WATCH_REFRESH_MS } from "./watchlist.js";
|
|
17
18
|
export { TradeTicketLive, PositionsLive, LivePositions, CombinedPositions, POSITIONS_REFRESH_MS } from "./trade.js";
|
|
18
19
|
export { IntelLive, popupBubbleVM, unavailableText, INTEL_AUTOSCAN_DELAY_MS } from "./intel.js";
|
|
20
|
+
export { LiveSignalsLive, buildObservation, SIGNALS_LOG_MAX } from "./signals.js";
|
|
19
21
|
export { renderTokenLive } from "./screens/token-live.js";
|
|
20
22
|
export { renderNewPairsLive } from "./screens/newpairs-live.js";
|
|
21
23
|
export { renderWatchlistLive } from "./screens/watchlist-live.js";
|
|
24
|
+
/** task-1404: how often the open token's credit-metered wallet-intel leg (holders/bundle/sniper/fresh) is re-scanned. */
|
|
25
|
+
export const INTEL_RESCAN_MS = 45_000;
|
|
26
|
+
/** A transient one-liner for a fired live signal (⚑ SYMBOL-less; the token screen shows the full strip). */
|
|
27
|
+
function signalNotice(s) {
|
|
28
|
+
const age = s.dataAgeMs >= 1000 ? ` · ${Math.round(s.dataAgeMs / 1000)}s` : '';
|
|
29
|
+
return `⚑ ${s.title}: ${s.text} (${s.source}${age})`;
|
|
30
|
+
}
|
|
22
31
|
export class LiveContext {
|
|
23
32
|
dl;
|
|
24
33
|
mode;
|
|
@@ -54,6 +63,8 @@ export class LiveContext {
|
|
|
54
63
|
token = null;
|
|
55
64
|
/** task-1340 wallet intel: the holder scan per mint (light on open, deep for watched coins) behind the bubble map. */
|
|
56
65
|
intel;
|
|
66
|
+
/** task-1404 LIVE holder/rug signals: turns the open token's repeated holder/safety reads into signal events. */
|
|
67
|
+
signals;
|
|
57
68
|
/** task-1360 copy trading Tier 1: the follow store + wallet tracker (alerts on a followed wallet's buys/sells). */
|
|
58
69
|
copy;
|
|
59
70
|
/**
|
|
@@ -86,6 +97,8 @@ export class LiveContext {
|
|
|
86
97
|
onLog;
|
|
87
98
|
lastFastLogAt = 0;
|
|
88
99
|
lastFastLog = '';
|
|
100
|
+
/** task-1404: last time the open token's wallet-intel leg was re-scanned (slow cadence; see refreshOpenIntel). */
|
|
101
|
+
lastIntelRescanAt = 0;
|
|
89
102
|
constructor(dl, opts) {
|
|
90
103
|
this.dl = dl;
|
|
91
104
|
this.mode = opts.mode;
|
|
@@ -124,7 +137,7 @@ export class LiveContext {
|
|
|
124
137
|
endpoints: opts.fastEndpoints,
|
|
125
138
|
solUsd: () => this.solUsd,
|
|
126
139
|
ws: opts.ws,
|
|
127
|
-
onPrice: (_p) => emit('fastprice'),
|
|
140
|
+
onPrice: (_p) => { this.observeSignals(); emit('fastprice'); },
|
|
128
141
|
onStatus: () => { emit('fastlane:status'); this.logFast(); },
|
|
129
142
|
})
|
|
130
143
|
: null;
|
|
@@ -166,7 +179,10 @@ export class LiveContext {
|
|
|
166
179
|
? new LivePositions(this.trade.paths?.liveLedger ?? defaultLiveLedgerPath(), { now: this.now, mark: (m) => this.positions.markOf(m), solUsd: () => this.positions.solUsd() ?? this.solUsd })
|
|
167
180
|
: null;
|
|
168
181
|
this.positionSource = new CombinedPositions(this.positions, this.livePositions);
|
|
169
|
-
this.intel = new IntelLive(dl, { now: this.now, onChange: () => { this.syncIntelRows(); emit('intel'); }, isWatched: (mint) => this.watch.has(mint) });
|
|
182
|
+
this.intel = new IntelLive(dl, { now: this.now, onChange: () => { this.syncIntelRows(); this.observeSignals(); emit('intel'); }, isWatched: (mint) => this.watch.has(mint) });
|
|
183
|
+
// task-1404: the live holder/rug signals engine. A fired signal sets a transient notice and redraws; the
|
|
184
|
+
// token screen shows the recent signals, and the rules engine reads `signals.fields()` so each is an alert trigger.
|
|
185
|
+
this.signals = new LiveSignalsLive({ now: this.now, onSignal: (s) => { this.notice = signalNotice(s); emit('signals'); } });
|
|
170
186
|
// task-1360: followed wallets are polled only in a real live run (replay never spends credits); the store is always readable.
|
|
171
187
|
this.copy = new CopyLive(opts.mode === 'live' ? dl : null, { onChange: (r) => emit(r), solUsd: () => this.solUsd, env: opts.copy?.env, followsPath: opts.copy?.followsPath, history: opts.copy?.history, pollMs: opts.copy?.pollMs });
|
|
172
188
|
}
|
|
@@ -376,7 +392,7 @@ export class LiveContext {
|
|
|
376
392
|
}
|
|
377
393
|
/** Open a token: a fresh loader, first load kicked off; the view is readable immediately (row fields). */
|
|
378
394
|
openToken(row) {
|
|
379
|
-
const t = new TokenLive(this.dl, row, { interval: this.token?.view.interval ?? this.interval, now: this.now, onChange: () => { this.syncChainRows(t); this.onChange('token'); } });
|
|
395
|
+
const t = new TokenLive(this.dl, row, { interval: this.token?.view.interval ?? this.interval, now: this.now, onChange: () => { this.syncChainRows(t); this.observeSignals(); this.onChange('token'); } });
|
|
380
396
|
this.token = t;
|
|
381
397
|
this.syncFastSet(); // promote the opened token to the fast lane (slow→fast, no seam)
|
|
382
398
|
this.x?.setFocus({ mint: row.mint, symbol: row.symbol }); // task-1357: the open token's X search (60 s, cached 15 min)
|
|
@@ -423,6 +439,51 @@ export class LiveContext {
|
|
|
423
439
|
if (sigs.length)
|
|
424
440
|
this.social.ingest(sigs.map((s) => chainSignalToRaw(s, this.now())));
|
|
425
441
|
}
|
|
442
|
+
/** The current inputs for the live-signals engine: the open token's view + its settled intel scan + fast-lane curve. */
|
|
443
|
+
signalInputs() {
|
|
444
|
+
if (!this.token)
|
|
445
|
+
return null;
|
|
446
|
+
const mint = this.token.view.mint;
|
|
447
|
+
const result = this.intel.entry(mint)?.result;
|
|
448
|
+
const ok = result && result.state === 'ok' ? result : null;
|
|
449
|
+
return {
|
|
450
|
+
view: this.token.view,
|
|
451
|
+
intel: ok ? { analysis: ok.analysis, fetchedAt: ok.fetchedAt, via: ok.via.history ? 'helius' : ok.via.holders } : null,
|
|
452
|
+
curve: this.fastLane?.curve(mint) ?? null,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* task-1404: feed one moment of the open token's holder/rug state to the live-signals engine. Called whenever the
|
|
457
|
+
* token, the intel scan or a fast-lane push changed something; the engine only emits on a real threshold-crossing
|
|
458
|
+
* change, so this is cheap to call on every redraw. Nothing fires until a second observation of the same field.
|
|
459
|
+
*/
|
|
460
|
+
observeSignals() {
|
|
461
|
+
if (this.closed)
|
|
462
|
+
return;
|
|
463
|
+
const inputs = this.signalInputs();
|
|
464
|
+
if (inputs)
|
|
465
|
+
this.signals.observe(inputs);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* task-1404: re-run the open token's wallet-intel scan on a slow cadence so holder/concentration/bundle/sniper
|
|
469
|
+
* signals stay LIVE, not computed once on open. Gated: live mode, the history leg can actually run (else a scan
|
|
470
|
+
* would only spend public-RPC calls to still print a dash), not already in flight, and at most once per
|
|
471
|
+
* INTEL_RESCAN_MS. The cheap on-chain reads (authorities, LP, liq, price) already refresh with token.load()
|
|
472
|
+
* every 10 s and bonding streams sub-second; this covers the credit-metered Helius leg.
|
|
473
|
+
*/
|
|
474
|
+
refreshOpenIntel(now) {
|
|
475
|
+
if (this.mode !== 'live' || !this.token)
|
|
476
|
+
return;
|
|
477
|
+
if (now - this.lastIntelRescanAt < INTEL_RESCAN_MS)
|
|
478
|
+
return;
|
|
479
|
+
if (!this.intel.historyAvailable().ok)
|
|
480
|
+
return;
|
|
481
|
+
const v = this.token.view;
|
|
482
|
+
if (this.intel.entry(v.mint)?.state === 'loading')
|
|
483
|
+
return;
|
|
484
|
+
this.lastIntelRescanAt = now;
|
|
485
|
+
void this.intel.scan({ mint: v.mint, symbol: v.symbol, name: v.name, holderCount: v.holders ?? null }, { force: true }).catch(() => { });
|
|
486
|
+
}
|
|
426
487
|
/** The token screen's `1`..`6` keys. */
|
|
427
488
|
setTimeframe(n) {
|
|
428
489
|
const iv = INTERVALS[n - 1];
|
|
@@ -214,6 +214,30 @@ export function renderTokenLive(c, state, v) {
|
|
|
214
214
|
sy += 1;
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
+
// task-1404: LIVE holder/rug signals — the changes seen since the token was opened (dev sell, concentration
|
|
218
|
+
// shift, LP unlock, authority flip, new bundle/sniper, fresh inflow, bonding, migration). Each is a risk flag,
|
|
219
|
+
// not a verdict, and carries its SOURCE and the AGE of the data it read; the `stale` mark shows honestly.
|
|
220
|
+
const sigs = state.live?.signals?.recent(v.mint, 4) ?? [];
|
|
221
|
+
if (sigs.length && sy < sb.y + sb.h) {
|
|
222
|
+
sy += sy + 1 < sb.y + sb.h ? 1 : 0;
|
|
223
|
+
if (sy < sb.y + sb.h) {
|
|
224
|
+
c.text(sb.x + 1, sy, 'LIVE SIGNALS', { fg: t.dim, bg: sb.bg });
|
|
225
|
+
sy += 1;
|
|
226
|
+
}
|
|
227
|
+
const staleAfter = 60_000;
|
|
228
|
+
for (const s of sigs) {
|
|
229
|
+
if (sy >= sb.y + sb.h)
|
|
230
|
+
break;
|
|
231
|
+
const color = s.level === 'warn' ? t.warn : s.level === 'down' ? t.down : s.level === 'up' ? t.up : t.muted;
|
|
232
|
+
const ageMs = Math.max(0, now - s.at);
|
|
233
|
+
const stale = ageMs > staleAfter;
|
|
234
|
+
const meta = `${s.source} · ${ageMs >= 1000 ? `${Math.round(ageMs / 1000)}s` : 'now'}${stale ? ' · stale' : ''}`;
|
|
235
|
+
const metaClip = clip(meta, Math.max(8, Math.floor((sb.w - 2) / 2)));
|
|
236
|
+
c.text(sb.x + 1, sy, `⚑ ${s.title}`, { fg: color, bg: sb.bg, b: s.level === 'warn' || s.level === 'down' }, sb.w - 2 - metaClip.length - 1);
|
|
237
|
+
c.textR(sb.x + 1, sy, sb.w - 2, metaClip, { fg: stale ? t.warn : t.dim, bg: sb.bg, i: true });
|
|
238
|
+
sy += 1;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
217
241
|
if (bp !== 'compact') {
|
|
218
242
|
const hx = statsW;
|
|
219
243
|
const hw = mainW - statsW;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// dexterm — LIVE holder/rug SIGNALS binding (task-1404). Turns the open token's repeatedly-refreshed holder and
|
|
2
|
+
// safety reads (market/authorities/rugcheck panels + the wallet-intel scan + the fast-lane bonding curve) into
|
|
3
|
+
// SIGNAL events through the pure @dexterm/data LiveSignalsEngine, keeps a small per-mint log for the token screen,
|
|
4
|
+
// and exposes the latest numeric values as RULE FIELDS so every signal is an alert trigger (task-1398 pattern).
|
|
5
|
+
//
|
|
6
|
+
// It owns no timers and does no I/O: the UI loop / LiveContext calls observe() whenever the token, the intel scan
|
|
7
|
+
// or a fast-lane push changed something. The engine dedups (only a threshold-crossing change emits), so calling it
|
|
8
|
+
// on every redraw is cheap. Read-only: nothing here trades or signs.
|
|
9
|
+
import { LiveSignalsEngine, bondingProgress, } from '../../../data/src/index.js';
|
|
10
|
+
/** Max signals kept per mint for the token-screen strip (newest first). */
|
|
11
|
+
export const SIGNALS_LOG_MAX = 40;
|
|
12
|
+
function fromPanel(p, value, fallbackSource) {
|
|
13
|
+
if (!p || value == null || !Number.isFinite(value) || p.fetchedAt == null)
|
|
14
|
+
return null;
|
|
15
|
+
return { value, source: p.via ?? fallbackSource, fetchedAt: p.fetchedAt, stale: p.stale || p.state === 'error' };
|
|
16
|
+
}
|
|
17
|
+
function authFromPanel(p, present) {
|
|
18
|
+
if (!p || present == null || p.fetchedAt == null)
|
|
19
|
+
return null;
|
|
20
|
+
return { present, source: p.via ?? 'authorities', fetchedAt: p.fetchedAt, stale: p.stale || p.state === 'error' };
|
|
21
|
+
}
|
|
22
|
+
function fromIntel(value, intel) {
|
|
23
|
+
if (value == null || !Number.isFinite(value) || !intel || intel.fetchedAt == null)
|
|
24
|
+
return null;
|
|
25
|
+
return { value, source: intel.via ?? 'helius', fetchedAt: intel.fetchedAt, stale: false };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build one observation from the token view + intel scan + fast-lane curve. Each field keeps its OWN source and
|
|
29
|
+
* fetch time so a signal is honest about where it came from and how old the data is. The richer wallet-intel
|
|
30
|
+
* figures (ex-pool top-10, creator %, bundled, snipers, fresh, cluster) are preferred when the scan has landed;
|
|
31
|
+
* otherwise the panel reads (Jupiter audit top-10/dev) stand in. Pure + exported for the tests.
|
|
32
|
+
*/
|
|
33
|
+
export function buildObservation(inputs, now) {
|
|
34
|
+
const v = inputs.view;
|
|
35
|
+
const I = inputs.intel?.analysis?.insights ?? null;
|
|
36
|
+
const panels = v.panels ?? {};
|
|
37
|
+
const obs = { mint: v.mint, at: now };
|
|
38
|
+
// top-10 concentration (pools excluded): the scan's ex-pool figure first, else the holders panel's top-10.
|
|
39
|
+
obs.top10 = (I && typeof I.top10.exPool === 'number' ? fromIntel(I.top10.exPool, inputs.intel) : null) ?? fromPanel(panels.holders, v.top10, 'holders');
|
|
40
|
+
// creator / dev share: the scan's creatorPctNow first, else the market panel's Jupiter audit devPct.
|
|
41
|
+
obs.dev = (I ? fromIntel(I.dev.creatorPctNow, inputs.intel) : null) ?? fromPanel(panels.market, v.dev, 'jupiter');
|
|
42
|
+
// bundled / snipers / fresh / cluster only exist once the launch window / funders were read (Helius).
|
|
43
|
+
if (I && I.coverage.launchTxs > 0) {
|
|
44
|
+
obs.bundled = fromIntel(I.bundle.bundlePctOfTopSample, inputs.intel);
|
|
45
|
+
obs.snipers = fromIntel(I.snipers.count, inputs.intel);
|
|
46
|
+
}
|
|
47
|
+
if (I && I.coverage.fundingLookups > 0)
|
|
48
|
+
obs.fresh = fromIntel(I.fresh.freshAtBuyPctOfTopSample, inputs.intel);
|
|
49
|
+
const cluster = inputs.intel?.analysis?.clusters?.[0]?.pct ?? null;
|
|
50
|
+
obs.cluster = fromIntel(cluster, inputs.intel);
|
|
51
|
+
// LP locked (RugCheck), liquidity (market), mint/freeze authority (authorities panel).
|
|
52
|
+
obs.lpLocked = fromPanel(panels.rugcheck, v.lpLocked, 'rugcheck');
|
|
53
|
+
obs.liq = fromPanel(panels.market, v.liq, 'market');
|
|
54
|
+
obs.mintAuth = authFromPanel(panels.authorities, v.mintAuth);
|
|
55
|
+
obs.freezeAuth = authFromPanel(panels.authorities, v.freezeAuth);
|
|
56
|
+
// bonding-curve progress + migration from the on-chain fast-lane push (sub-second), when streaming.
|
|
57
|
+
if (inputs.curve) {
|
|
58
|
+
const bp = bondingProgress(inputs.curve.curve.vToken);
|
|
59
|
+
if (bp != null)
|
|
60
|
+
obs.bonding = { value: bp, source: 'onchain-curve', fetchedAt: inputs.curve.at, stale: false };
|
|
61
|
+
obs.migrated = { value: inputs.curve.curve.complete, source: 'onchain-curve', fetchedAt: inputs.curve.at, stale: false };
|
|
62
|
+
}
|
|
63
|
+
return obs;
|
|
64
|
+
}
|
|
65
|
+
export class LiveSignalsLive {
|
|
66
|
+
engine;
|
|
67
|
+
now;
|
|
68
|
+
onSignal;
|
|
69
|
+
logByMint = new Map();
|
|
70
|
+
/** Signals emitted this session (all mints), newest last — a cheap counter/feed for the smoke script + tests. */
|
|
71
|
+
emitted = [];
|
|
72
|
+
constructor(opts = {}) {
|
|
73
|
+
this.engine = new LiveSignalsEngine({ thresholds: opts.thresholds });
|
|
74
|
+
this.now = opts.now ?? (() => Date.now());
|
|
75
|
+
this.onSignal = opts.onSignal;
|
|
76
|
+
}
|
|
77
|
+
/** Observe one moment of a token's state; records and returns any new signals. */
|
|
78
|
+
observe(inputs) {
|
|
79
|
+
const obs = buildObservation(inputs, this.now());
|
|
80
|
+
const sigs = this.engine.observe(obs);
|
|
81
|
+
for (const s of sigs) {
|
|
82
|
+
const arr = this.logByMint.get(s.mint) ?? [];
|
|
83
|
+
arr.unshift(s);
|
|
84
|
+
if (arr.length > SIGNALS_LOG_MAX)
|
|
85
|
+
arr.length = SIGNALS_LOG_MAX;
|
|
86
|
+
this.logByMint.set(s.mint, arr);
|
|
87
|
+
this.emitted.push(s);
|
|
88
|
+
this.onSignal?.(s);
|
|
89
|
+
}
|
|
90
|
+
return sigs;
|
|
91
|
+
}
|
|
92
|
+
/** Recent signals for a mint, newest first (token-screen LIVE SIGNALS strip). */
|
|
93
|
+
recent(mint, limit = 8) {
|
|
94
|
+
return (this.logByMint.get(mint) ?? []).slice(0, limit);
|
|
95
|
+
}
|
|
96
|
+
/** Latest numeric fields for the rules engine (merged into the open-token RuleRow in ruleRowsOf). */
|
|
97
|
+
fields(mint) {
|
|
98
|
+
return this.engine.fields(mint);
|
|
99
|
+
}
|
|
100
|
+
/** Per-field snapshot (value + source + age + stale) for the strip header. */
|
|
101
|
+
snapshot(mint, now = this.now()) {
|
|
102
|
+
return this.engine.snapshot(mint, now);
|
|
103
|
+
}
|
|
104
|
+
/** Drop a mint the UI no longer tracks. */
|
|
105
|
+
forget(mint) {
|
|
106
|
+
this.engine.forget(mint);
|
|
107
|
+
this.logByMint.delete(mint);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -473,6 +473,9 @@ export class CandlePrefetcher {
|
|
|
473
473
|
timer = null;
|
|
474
474
|
busy = false;
|
|
475
475
|
closed = false;
|
|
476
|
+
settledPromise = Promise.resolve();
|
|
477
|
+
resolveSettled = null;
|
|
478
|
+
waitingForSettled = false;
|
|
476
479
|
stamps = [];
|
|
477
480
|
/** Requests actually sent (granted by the limiter). */
|
|
478
481
|
requests = 0;
|
|
@@ -494,9 +497,25 @@ export class CandlePrefetcher {
|
|
|
494
497
|
return;
|
|
495
498
|
if (t && this.target && t.mint === this.target.mint && t.pair === this.target.pair)
|
|
496
499
|
return; // same row: keep waiting
|
|
500
|
+
this.finishSettled(); // a replaced target can no longer do useful work for its caller
|
|
497
501
|
this.target = t;
|
|
502
|
+
if (t?.pair)
|
|
503
|
+
this.settledPromise = new Promise((resolve) => { this.resolveSettled = resolve; });
|
|
498
504
|
this.schedule(this.opts.debounceMs);
|
|
499
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Resolve after the current hover pass has warmed every immediately available base, explicitly
|
|
508
|
+
* deferred the next attempt, failed a real request, been replaced, or been cancelled by close(). This
|
|
509
|
+
* is the completion contract for callers that need to observe the pass result; a debounce timer firing
|
|
510
|
+
* alone does not mean its sequential requests have finished.
|
|
511
|
+
*/
|
|
512
|
+
settled() {
|
|
513
|
+
if (this.resolveSettled) {
|
|
514
|
+
this.waitingForSettled = true;
|
|
515
|
+
this.timer?.ref?.();
|
|
516
|
+
}
|
|
517
|
+
return this.settledPromise;
|
|
518
|
+
}
|
|
500
519
|
/** Requests sent in the trailing 60 s. */
|
|
501
520
|
rate() {
|
|
502
521
|
const now = this.opts.now();
|
|
@@ -511,6 +530,7 @@ export class CandlePrefetcher {
|
|
|
511
530
|
clearTimeout(this.timer);
|
|
512
531
|
this.timer = null;
|
|
513
532
|
}
|
|
533
|
+
this.finishSettled();
|
|
514
534
|
}
|
|
515
535
|
schedule(ms) {
|
|
516
536
|
if (this.timer) {
|
|
@@ -521,7 +541,10 @@ export class CandlePrefetcher {
|
|
|
521
541
|
if (!t || !t.pair || this.closed)
|
|
522
542
|
return;
|
|
523
543
|
this.timer = setTimeout(() => { this.timer = null; void this.step(t); }, Math.max(0, ms));
|
|
524
|
-
this.
|
|
544
|
+
if (this.waitingForSettled)
|
|
545
|
+
this.timer.ref?.();
|
|
546
|
+
else
|
|
547
|
+
this.timer.unref?.();
|
|
525
548
|
}
|
|
526
549
|
bases() {
|
|
527
550
|
const iv = this.opts.interval();
|
|
@@ -541,6 +564,7 @@ export class CandlePrefetcher {
|
|
|
541
564
|
continue;
|
|
542
565
|
if (this.rate() >= this.opts.maxPerMin) {
|
|
543
566
|
this.schedule(this.stamps[0] + 60_000 - this.opts.now() + 50);
|
|
567
|
+
this.finishSettled(t);
|
|
544
568
|
return;
|
|
545
569
|
}
|
|
546
570
|
this.busy = true;
|
|
@@ -554,9 +578,11 @@ export class CandlePrefetcher {
|
|
|
554
578
|
// Try again when the bucket could be idle again (still only if the cursor stays here).
|
|
555
579
|
const wait = this.dl.limiter.bucket('geckoterminal').delayFor('idle', this.opts.now());
|
|
556
580
|
this.schedule(Number.isFinite(wait) ? Math.max(250, wait) : 60_000);
|
|
581
|
+
this.finishSettled(t);
|
|
557
582
|
return;
|
|
558
583
|
}
|
|
559
584
|
this.sent(); // a real request that failed (HTTP error) still counts against the budget
|
|
585
|
+
this.finishSettled(t);
|
|
560
586
|
return;
|
|
561
587
|
}
|
|
562
588
|
finally {
|
|
@@ -565,6 +591,17 @@ export class CandlePrefetcher {
|
|
|
565
591
|
if (this.closed || this.target !== t)
|
|
566
592
|
return; // the cursor moved on: its own debounce takes over
|
|
567
593
|
}
|
|
594
|
+
this.finishSettled(t);
|
|
595
|
+
}
|
|
596
|
+
finishSettled(t) {
|
|
597
|
+
if (t && this.target !== t)
|
|
598
|
+
return;
|
|
599
|
+
const resolve = this.resolveSettled;
|
|
600
|
+
this.resolveSettled = null;
|
|
601
|
+
if (this.waitingForSettled)
|
|
602
|
+
this.timer?.unref?.();
|
|
603
|
+
this.waitingForSettled = false;
|
|
604
|
+
resolve?.();
|
|
568
605
|
}
|
|
569
606
|
sent() {
|
|
570
607
|
this.requests++;
|
|
@@ -680,6 +680,8 @@ export class PositionsLive {
|
|
|
680
680
|
now;
|
|
681
681
|
/** mint → current price in SOL per whole token. */
|
|
682
682
|
marks = new Map();
|
|
683
|
+
/** task-1407: when each mark was taken (ms), so a PnL card can print the mark's age. */
|
|
684
|
+
markTimes = new Map();
|
|
683
685
|
solPriceUsd = null;
|
|
684
686
|
loading = false;
|
|
685
687
|
heldMemo = new Map();
|
|
@@ -703,6 +705,10 @@ export class PositionsLive {
|
|
|
703
705
|
markOf(mint) {
|
|
704
706
|
return this.marks.get(mint) ?? null;
|
|
705
707
|
}
|
|
708
|
+
/** task-1407: when the mark for a mint was taken (ms), or null when there is none. */
|
|
709
|
+
markAt(mint) {
|
|
710
|
+
return this.marks.has(mint) ? this.markTimes.get(mint) ?? null : null;
|
|
711
|
+
}
|
|
706
712
|
/**
|
|
707
713
|
* PositionSource: the open paper position for a mint, or null. One indexed SQLite read, reused for
|
|
708
714
|
* HELD_MEMO_MS so a 30 fps render loop does not hit the ledger every frame. A closed wallet reads as flat.
|
|
@@ -758,8 +764,10 @@ export class PositionsLive {
|
|
|
758
764
|
continue;
|
|
759
765
|
}
|
|
760
766
|
const usd = byMint.get(m)?.pair.priceUsd ?? null;
|
|
761
|
-
if (usd && usd > 0)
|
|
767
|
+
if (usd && usd > 0) {
|
|
762
768
|
this.marks.set(m, usd / this.solPriceUsd);
|
|
769
|
+
this.markTimes.set(m, this.now());
|
|
770
|
+
}
|
|
763
771
|
}
|
|
764
772
|
}
|
|
765
773
|
}
|
|
@@ -865,6 +873,21 @@ export class LivePositions {
|
|
|
865
873
|
this.rowsMemo = { at: now, rows };
|
|
866
874
|
return rows;
|
|
867
875
|
}
|
|
876
|
+
/** task-1407: the raw ledger rows (newest first, swaps only) with network + signature, for the LIVE PnL card. */
|
|
877
|
+
fills(mint, limit = 500) {
|
|
878
|
+
try {
|
|
879
|
+
const l = new LiveLedger({ path: this.path, readOnly: true });
|
|
880
|
+
try {
|
|
881
|
+
return l.trades(limit * 2).filter((r) => r.kind === 'swap' && r.mint === mint).slice(0, limit);
|
|
882
|
+
}
|
|
883
|
+
finally {
|
|
884
|
+
l.close();
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
return [];
|
|
889
|
+
}
|
|
890
|
+
}
|
|
868
891
|
/** Confirmed live fills (newest first) for the popup markers / history; probes excluded. */
|
|
869
892
|
records(limit = 100, mint) {
|
|
870
893
|
try {
|