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
|
@@ -19,14 +19,17 @@ import { spawn } from 'node:child_process';
|
|
|
19
19
|
import { dirname, join } from 'node:path';
|
|
20
20
|
import { homedir } from 'node:os';
|
|
21
21
|
import { writeExport, exportViewFor } from "./export.js";
|
|
22
|
+
import { openPnl, loadPnl, pnlKey } from "./pnl-app.js";
|
|
23
|
+
import { shareCard, runLocal } from "./pnl-card.js";
|
|
22
24
|
import { parseMintTarget, parseCoinTarget, resolveCoinTarget, isBareOpen } from "./coin-target.js";
|
|
23
25
|
import { THEMES, isThemeName, DEFAULT_THEME, THEME_NAMES, Flusher, ENTER_SCREEN, LEAVE_SCREEN, DEFAULT_KEYMAP, Keymap, nextTheme, parseKeys, keyId, detectColorDepth, parseColorDepth, blinkFrame, blinkFrames, stepVfx, pulse, isCalm, fxBusy, blendActivity, ImageLayer, queryTerminal, resolveProtocol, fireMoment, fireInflight, clearInflight, skipMoment, momentKindFor, playMoment, soundOn, breakpoint, fmtUsd as fmtUsdTui, } from '../../tui/src/index.js';
|
|
24
|
-
import { resolveRpcUrl, redactSecrets, defaultDbPath, extraRpcUrls, wsUrlFor, SOLANA_PUBLIC_RPC_WS, parseHttpUrl, validateAnalystAction, ACTION_RATE, ImageService, encodePng, SummaryLimiter, buildSummaryWindow, KolStore, Cache, PackError, normalizeHandle, terminalSafe } from '../../data/src/index.js';
|
|
26
|
+
import { resolveRpcUrl, redactSecrets, defaultDbPath, extraRpcUrls, wsUrlFor, SOLANA_PUBLIC_RPC_WS, parseHttpUrl, validateAnalystAction, ACTION_RATE, ACTION_TIER, configChangeForAction, ImageService, encodePng, SummaryLimiter, buildSummaryWindow, KolStore, Cache, PackError, normalizeHandle, terminalSafe } from '../../data/src/index.js';
|
|
25
27
|
import { Recorder, recorderEnabled, MaintenanceSchedule } from '../../recorder/src/index.js';
|
|
26
28
|
import { makeFixtureState, makeFeedState, syncFeed, renderFrame, isScreenKey, activityFor, SCREEN_NAMES, analystKey, syncAnalyst, pinNewpairsSelection, ANALYST_TAB_KEY, computeNarrativeView, newPlansUi, plansService, plansRows, newFollowsUi, copyService, copyAutoService, MIRROR_DEFAULT_CAP_USD } from "./state.js";
|
|
27
29
|
import { mulberry32, tick, spawnPair, shock } from "./fixtures.js";
|
|
28
30
|
import { createLiveFeed, createReplayFeed } from "./feed.js";
|
|
29
31
|
import { LiveContext, INTERVALS, WATCH_REFRESH_MS, POSITIONS_REFRESH_MS, popupBubbleVM } from "./live/index.js";
|
|
32
|
+
import { McpFollowSession } from "./mcp-follow.js";
|
|
30
33
|
import { TOKEN_REFRESH_MS, CandlePrefetcher } from "./live/token.js";
|
|
31
34
|
import { PRESETS, presetSpec } from "./screener/presets.js";
|
|
32
35
|
import { parseSortArg, SORT_KEYS } from "./screener/sort.js";
|
|
@@ -39,6 +42,8 @@ import { widgetPage, widgetEnabled, syncWidgetPopups } from "./widgets.js";
|
|
|
39
42
|
import { recordLarpVerdict } from "./larp-record.js";
|
|
40
43
|
import { rulesUiOf, rulesTick, rulesMove, beginRuleInput, saveRuleInput, dryRunNow, removeSelectedRule, toggleSelectedRule, dismissSelectedProposal, actOnSelectedProposal, RULE_INPUT_MAX } from "./rules.js";
|
|
41
44
|
import { buildNotifier, hasPushChannel } from "./alerts/notifier.js";
|
|
45
|
+
import { copyAlertToDelivery } from "./alerts/format.js";
|
|
46
|
+
import { readTelegramCreds, readDiscordCreds } from "./alerts/creds.js";
|
|
42
47
|
import { launchIntelCells, launchRetryable } from "./live/intel.js";
|
|
43
48
|
import { AnalystSession } from "./analyst/session.js";
|
|
44
49
|
import { resolveAnalystResults } from "./analyst/results.js";
|
|
@@ -57,7 +62,7 @@ import { FOLLOW_INPUT_MAX } from "./screens/follows.js";
|
|
|
57
62
|
import { isPriceMode, configPriceMode, togglePriceMode, priceModeOf, planLevels } from "./display.js";
|
|
58
63
|
import { VERSION } from "./version.js";
|
|
59
64
|
import { initCustom, planWorkspace, planPopupOpens, findWorkspace, nextWorkspaceName, workspaceSlots, saveWorkspace, themeFor, themeByName, cycleTheme, themeNames, CHORD_TTL_MS, MAX_WORKSPACE_NAME } from "./custom.js";
|
|
60
|
-
import { DEFAULT_WORKSPACE, MORNING_HUD, applyAndJournal } from '../../custom/src/index.js';
|
|
65
|
+
import { DEFAULT_WORKSPACE, MORNING_HUD, applyAndJournal, policyCheck } from '../../custom/src/index.js';
|
|
61
66
|
/**
|
|
62
67
|
* Context the `screen_state` MCP tool returns AND the per-turn context block is rendered from
|
|
63
68
|
* (task-1313): what the user is looking at right now — selected/hovered coin, screen, the visible
|
|
@@ -65,6 +70,37 @@ import { DEFAULT_WORKSPACE, MORNING_HUD, applyAndJournal } from '../../custom/sr
|
|
|
65
70
|
* that bounds what the analyst's UI actions may reference. Token-derived text stays raw here; the
|
|
66
71
|
* context-block renderer wraps it as untrusted before a model sees it.
|
|
67
72
|
*/
|
|
73
|
+
/** Screens the analyst's `switch_screen` action may target (task-1413); the user-facing set. */
|
|
74
|
+
export const SWITCHABLE_SCREENS = ['screener', 'token', 'newpairs', 'watchlist', 'analyst', 'positions', 'trending', 'kol'];
|
|
75
|
+
/**
|
|
76
|
+
* A read-only snapshot of local UI state for the `app_state` MCP tool (task-1413): what the user sees.
|
|
77
|
+
* NEVER includes a secret — alert channels report a boolean "configured", computed from creds presence,
|
|
78
|
+
* never the token/webhook/key itself. `null` when there is no live config (one-shot fixture frames).
|
|
79
|
+
*/
|
|
80
|
+
export function appStateBlock(state) {
|
|
81
|
+
const ui = state.custom;
|
|
82
|
+
if (!ui)
|
|
83
|
+
return null;
|
|
84
|
+
const cfg = ui.config;
|
|
85
|
+
const home = ui.home;
|
|
86
|
+
const ch = (cfg.alerts?.channels ?? {});
|
|
87
|
+
const alerts = {
|
|
88
|
+
// toast is on unless explicitly disabled; the rest are off unless enabled. telegram/discord also need creds.
|
|
89
|
+
toast: ch.toast?.enabled !== false,
|
|
90
|
+
desktop: ch.desktop?.enabled === true,
|
|
91
|
+
telegram: ch.telegram?.enabled === true && readTelegramCreds(home) !== null,
|
|
92
|
+
discord: ch.discord?.enabled === true && readDiscordCreds(home) !== null,
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
theme: state.theme.name,
|
|
96
|
+
priceMode: priceModeOf(state),
|
|
97
|
+
intensity: state.vfx.intensity,
|
|
98
|
+
calm: isCalm(state.vfx),
|
|
99
|
+
voice: { name: cfg.voice?.name ?? null, rate: cfg.voice?.rate ?? null },
|
|
100
|
+
scan: { on: state.scan?.engine.on ?? false },
|
|
101
|
+
alerts, // booleans only — never a token, webhook or key
|
|
102
|
+
};
|
|
103
|
+
}
|
|
68
104
|
export function buildScreenState(state) {
|
|
69
105
|
const live = state.live;
|
|
70
106
|
const sc = state.screener;
|
|
@@ -127,7 +163,14 @@ export function buildScreenState(state) {
|
|
|
127
163
|
watchlist,
|
|
128
164
|
ticket,
|
|
129
165
|
positions,
|
|
130
|
-
actions: {
|
|
166
|
+
actions: {
|
|
167
|
+
enabled: !!live, mints: [...mints], feeds, sortKeys: SORT_KEYS.map((k) => k.key),
|
|
168
|
+
// task-1413: targets the new tiered actions may reference.
|
|
169
|
+
screens: [...SWITCHABLE_SCREENS],
|
|
170
|
+
workspaces: Object.keys(state.custom?.config.layout?.workspaces ?? {}),
|
|
171
|
+
themes: themeNames(state.custom),
|
|
172
|
+
},
|
|
173
|
+
app: appStateBlock(state),
|
|
131
174
|
now: Date.now(),
|
|
132
175
|
};
|
|
133
176
|
}
|
|
@@ -225,10 +268,12 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
225
268
|
dexterm wallet link|unlink|status|panic|resume link a Phantom wallet via a local bridge (pubkey only); panic disables live trading, resume needs a TTY keypress
|
|
226
269
|
dexterm plan propose|list|arm|cancel|journal conditional order plans (entry/ladder/take-profit/moon bag/stop); arming needs a physical keypress
|
|
227
270
|
dexterm mandate draft|list|arm|revoke standing capped mandates (fast wallet); arming needs a physical keypress — voice/agents can never arm
|
|
228
|
-
dexterm follow add|remove|list|stats|trades|purge copy trading tier 1: follow a wallet (your list, local labels), its decoded swaps + FIFO PnL;
|
|
229
|
-
dexterm mcp [install <client>|doctor] read-only MCP server for your own agent; 'install' writes the config for claude-code|claude-desktop|cursor|codex|
|
|
271
|
+
dexterm follow add|remove|list|stats|trades|mirror|purge copy trading tier 1: follow a wallet (your list, local labels), its decoded swaps + FIFO PnL + win rate/early-entry; 'mirror' previews a one-key MIRROR TICKET from the wallet's latest buy (inert; arm in the app)
|
|
272
|
+
dexterm mcp [install <client>|doctor] read-only MCP server for your own agent; 'install' writes the config for claude-code|claude-desktop|cursor|codex|generic (asks first, idempotent, backs up); 'doctor' verifies the connection
|
|
230
273
|
dexterm alerts status|enable|disable|set-telegram|set-discord|test where rule alerts are delivered (desktop / your Telegram / Discord; creds stay local, never logged)
|
|
231
274
|
dexterm watch [--interval S] [--once] [--replay FILE] run your rules headless and push alerts when the terminal is closed (no signer, proposals only)
|
|
275
|
+
dexterm record run|install|uninstall|status keep the history recorder fed with no TUI open, as a user-level launchd agent (no network beyond the data layer; 'dexterm record help')
|
|
276
|
+
dexterm larp-detected --out DIR [--db PATH] build timestamped high-risk follow-up drafts/cards from recorder history; never posts
|
|
232
277
|
dexterm --fixtures interactive demo on seeded fixtures
|
|
233
278
|
dexterm --screen <name> one-shot frame to stdout (live)
|
|
234
279
|
dexterm --screen <name> --fixtures one-shot frame from fixtures
|
|
@@ -241,6 +286,8 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
241
286
|
--sort KEY[:asc|desc] ${SORT_KEYS.map((k) => k.key).join(' | ')}
|
|
242
287
|
--preset NAME ${PRESETS.map((p) => p.id).join(' | ')}
|
|
243
288
|
--record FILE save every live response (and the PumpPortal session) to FILE (replay it with --replay)
|
|
289
|
+
--spectate-mcp follow an MCP agent's allowed navigation in this terminal; Ctrl+P pauses, Ctrl+K stops,
|
|
290
|
+
and any normal user keypress takes control (refused actions never move the UI)
|
|
244
291
|
--max-requests N hard cap on live requests for this run (default: none)
|
|
245
292
|
--max-credits N hard cap on Helius credits for this run (default: 5000)
|
|
246
293
|
--mint MINT|LINK token to open on the token screen (a CA or a dexscreener / pump.fun / solscan / birdeye link)
|
|
@@ -315,9 +362,10 @@ ${X_HELP}
|
|
|
315
362
|
--version print the version
|
|
316
363
|
|
|
317
364
|
Keys: 1-4 screens · tab cycle · j/k move · ⏎ open · esc back · a analyst · v voice · t theme · c calm · p pause · q quit
|
|
318
|
-
voice (JARVIS, macOS): v push-to-talk (press to talk, press to send) · m mute · , change voice · esc stop speaking
|
|
365
|
+
voice (JARVIS, macOS): v push-to-talk (press to talk, press to send) · m mute · , change voice · < / > slower/faster · esc stop speaking
|
|
319
366
|
voice asks the analyst or triggers safe actions (open/filter/watch/open a PAPER ticket);
|
|
320
367
|
a spoken "buy"/"sell" only OPENS a ticket — you still confirm it with a keypress
|
|
368
|
+
spoken replies talk in market cap and round numbers (never a raw per-token price); < / > set the rate (saved to config)
|
|
321
369
|
screener: / filter · s sort · [ ] presets · r refresh · g/G top/bottom
|
|
322
370
|
social (task-1330): T trending in the space · K KOL lists · token screen: s social panel · f filter · x hidden spam
|
|
323
371
|
dexterm kol list | create <name> | add <list> @handle | remove <list> @handle | mute @handle | unmute @handle | enable <list> | disable <list> | import <dir> | import-x
|
|
@@ -348,6 +396,7 @@ export function parseArgs(argv) {
|
|
|
348
396
|
sound: false,
|
|
349
397
|
trade: [], live: true,
|
|
350
398
|
workspace: null, priceMode: null,
|
|
399
|
+
spectateMcp: false,
|
|
351
400
|
help: false, version: false, unknown: [],
|
|
352
401
|
};
|
|
353
402
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -529,6 +578,9 @@ export function parseArgs(argv) {
|
|
|
529
578
|
throw new Error(`--price-mode expects price|mcap, got ${JSON.stringify(s.slice(0, 20))}`);
|
|
530
579
|
a.priceMode = s;
|
|
531
580
|
}
|
|
581
|
+
// task-1421: follow an agent driving over MCP (the running terminal moves with it; a read-only stream opens).
|
|
582
|
+
else if (k === '--spectate-mcp')
|
|
583
|
+
a.spectateMcp = true;
|
|
532
584
|
else if (k === '--workspace') {
|
|
533
585
|
const s = str();
|
|
534
586
|
if (!/^[a-z][a-z0-9-]{0,39}(\/[a-z][a-z0-9-]{0,39})?$/.test(s))
|
|
@@ -725,6 +777,12 @@ export async function main(argv, io = process, proc = process) {
|
|
|
725
777
|
const { runAgent } = await import("./agent-cmd.js");
|
|
726
778
|
return runAgent(argv.slice(1), io);
|
|
727
779
|
}
|
|
780
|
+
// `dexterm spectate --replay FILE | --info` → watch an agent drive dexterm (task-1406, "watch the agent
|
|
781
|
+
// cook"): replay a saved session, or show the live read-only, token-gated, loopback spectator stream.
|
|
782
|
+
if (argv[0] === 'spectate') {
|
|
783
|
+
const { runSpectate } = await import("./spectate-cmd.js");
|
|
784
|
+
return runSpectate(argv.slice(1), io);
|
|
785
|
+
}
|
|
728
786
|
// `dexterm recorder <cmd>` → the history recorder's tables: stats, label, prune, export (task-1329). No network.
|
|
729
787
|
if (argv[0] === 'recorder') {
|
|
730
788
|
const { recorderCli } = await import('../../recorder/src/index.js');
|
|
@@ -740,6 +798,11 @@ export async function main(argv, io = process, proc = process) {
|
|
|
740
798
|
const { backtestCommand } = await import("./backtest-cmd.js");
|
|
741
799
|
return backtestCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
742
800
|
}
|
|
801
|
+
// Local-only marketing evidence pipeline (task-1433): drafts and cards for human review, never posts.
|
|
802
|
+
if (argv[0] === 'larp-detected') {
|
|
803
|
+
const { larpDetectedCommand } = await import("./larp-detected.js");
|
|
804
|
+
return larpDetectedCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
805
|
+
}
|
|
743
806
|
// `dexterm kol …` → KOL lists on the shared DB (task-1330): list/create/add/remove/mute/unmute/enable/disable/import.
|
|
744
807
|
if (argv[0] === 'kol')
|
|
745
808
|
return kolCommand(argv.slice(1), io);
|
|
@@ -760,6 +823,12 @@ export async function main(argv, io = process, proc = process) {
|
|
|
760
823
|
const { kbSkillCommand } = await import("./kb-skill-cmd.js");
|
|
761
824
|
return kbSkillCommand(argv[0], argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env, isTTY: io.stdout.isTTY === true });
|
|
762
825
|
}
|
|
826
|
+
// `dexterm help [topic]` → the app-help KB (task-1413): how the app works, with the exact key/command.
|
|
827
|
+
// Backed by the same generated entries the analyst's `app_help` tool uses. No network, no model.
|
|
828
|
+
if (argv[0] === 'help') {
|
|
829
|
+
const { helpCommand } = await import("./help-cmd.js");
|
|
830
|
+
return helpCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr });
|
|
831
|
+
}
|
|
763
832
|
// `dexterm theme list|show|init|check` → user themes under ~/.dexterm/themes (task-1358 Tier 2). No network.
|
|
764
833
|
if (argv[0] === 'theme') {
|
|
765
834
|
const { themeCommand } = await import("./theme-cmd.js");
|
|
@@ -792,6 +861,13 @@ export async function main(argv, io = process, proc = process) {
|
|
|
792
861
|
const { watchCommand } = await import("./watch-cmd.js");
|
|
793
862
|
return watchCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
794
863
|
}
|
|
864
|
+
// `dexterm record [run|install|uninstall|status]` → the headless collector that keeps the history recorder
|
|
865
|
+
// fed with no TUI open (task-1414), to be driven by a user-level launchd LaunchAgent. No signer, no trading;
|
|
866
|
+
// requests honour the data layer limits / health gate / sweep budgets, and the Helius key is never printed.
|
|
867
|
+
if (argv[0] === 'record') {
|
|
868
|
+
const { recordCommand } = await import("./record-cmd.js");
|
|
869
|
+
return recordCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
870
|
+
}
|
|
795
871
|
// `dexterm pack list|verify|add|remove|export|lock|sign|trust|signers|keymap|widget` → shareable packs (task-1358 Tier 4):
|
|
796
872
|
// plain files + sha256 lock + optional ssh signature. No network, no model; a pack never runs code (widgets land
|
|
797
873
|
// disabled, unsigned rules load disabled, a keymap merges only on the explicit command).
|
|
@@ -1228,6 +1304,10 @@ function followsMove(state, delta) {
|
|
|
1228
1304
|
return false;
|
|
1229
1305
|
if (state.copyUi.input !== null || state.copyUi.mirror || state.copyUi.autoModal)
|
|
1230
1306
|
return true;
|
|
1307
|
+
if (state.copyUi.detail) {
|
|
1308
|
+
state.copyUi.detail.scroll = Math.max(0, state.copyUi.detail.scroll + (delta > 0 ? 1 : -1));
|
|
1309
|
+
return true;
|
|
1310
|
+
} // task-1399: j/k scroll the detail view
|
|
1231
1311
|
const n = state.copy?.follows().length ?? 0;
|
|
1232
1312
|
if (n <= 0) {
|
|
1233
1313
|
state.copyUi.selected = 0;
|
|
@@ -1513,6 +1593,10 @@ export function applyAction(state, action, stack) {
|
|
|
1513
1593
|
state.copyUi.notice = 'mirror preview closed';
|
|
1514
1594
|
break;
|
|
1515
1595
|
} // task-1360: esc closes the mirror preview first
|
|
1596
|
+
if (state.screen === 'follows' && state.copyUi?.detail) {
|
|
1597
|
+
state.copyUi.detail = null;
|
|
1598
|
+
break;
|
|
1599
|
+
} // task-1399: esc closes the wallet detail view before leaving the screen
|
|
1516
1600
|
const prev = stack.pop();
|
|
1517
1601
|
if (prev)
|
|
1518
1602
|
state.screen = prev;
|
|
@@ -2337,6 +2421,10 @@ export function applyAction(state, action, stack) {
|
|
|
2337
2421
|
case 'export.view':
|
|
2338
2422
|
state.pendingExport = true;
|
|
2339
2423
|
break;
|
|
2424
|
+
// task-1407 PNL CARD: the loop opens the preview (recorded fills only) and runs the share keys.
|
|
2425
|
+
case 'pnl.card':
|
|
2426
|
+
state.pendingPnl = true;
|
|
2427
|
+
break;
|
|
2340
2428
|
case 'display.priceMode':
|
|
2341
2429
|
state.pendingToast = togglePriceMode(state);
|
|
2342
2430
|
break; // task-1366: display only, persisted
|
|
@@ -2708,6 +2796,12 @@ export function applyAnalystAction(state, raw, stack) {
|
|
|
2708
2796
|
if (!res.ok)
|
|
2709
2797
|
return { ok: false, error: res.error };
|
|
2710
2798
|
const action = res.action;
|
|
2799
|
+
// task-1413 tier (b): a persisted-config action is never applied by the model — return it as a PROPOSAL
|
|
2800
|
+
// the user accepts with a keypress (applyAnalystConfig then journals it as actor 'analyst').
|
|
2801
|
+
if (ACTION_TIER[action.type] === 'config') {
|
|
2802
|
+
times.push(now);
|
|
2803
|
+
return { ok: true, label: res.label, proposal: { action, summary: res.label } };
|
|
2804
|
+
}
|
|
2711
2805
|
// Resolve a mint to a row object from what is on screen (allow-list guarantees it was visible).
|
|
2712
2806
|
const findSeed = (mint) => seedForMint(state, live, mint);
|
|
2713
2807
|
const toScreen = (next) => { if (state.screen !== next) {
|
|
@@ -2747,6 +2841,10 @@ export function applyAnalystAction(state, raw, stack) {
|
|
|
2747
2841
|
}
|
|
2748
2842
|
case 'show_in_screener': {
|
|
2749
2843
|
toScreen('screener');
|
|
2844
|
+
if (action.preset && !setPreset(sc, action.preset))
|
|
2845
|
+
return { ok: false, error: `preset "${action.preset}" is unavailable` };
|
|
2846
|
+
if (action.preset && state.feed)
|
|
2847
|
+
state.feed.setPreset(sc.preset);
|
|
2750
2848
|
if (action.filter) {
|
|
2751
2849
|
const err = setFilter(sc, action.filter);
|
|
2752
2850
|
if (err)
|
|
@@ -2760,12 +2858,171 @@ export function applyAnalystAction(state, raw, stack) {
|
|
|
2760
2858
|
state.selected = i;
|
|
2761
2859
|
}
|
|
2762
2860
|
state.selected = clampSelection(sc, state.selected);
|
|
2861
|
+
if (action.preset) {
|
|
2862
|
+
times.push(now);
|
|
2863
|
+
return { ok: true, label: res.label, refresh: true };
|
|
2864
|
+
}
|
|
2865
|
+
break;
|
|
2866
|
+
}
|
|
2867
|
+
// --- task-1413 view-only actions: applied immediately, shown as "analyst did X".
|
|
2868
|
+
case 'switch_screen': {
|
|
2869
|
+
toScreen(action.screen);
|
|
2870
|
+
state.selected = 0;
|
|
2871
|
+
break;
|
|
2872
|
+
}
|
|
2873
|
+
case 'switch_workspace': {
|
|
2874
|
+
const r = switchWorkspace(state, action.name, stack);
|
|
2875
|
+
if (!r.ok)
|
|
2876
|
+
return { ok: false, error: r.reason };
|
|
2877
|
+
times.push(now);
|
|
2878
|
+
return { ok: true, label: res.label, refresh: r.refresh };
|
|
2879
|
+
}
|
|
2880
|
+
case 'chart_popup': {
|
|
2881
|
+
// Open the chart popup (the loop performs the open/close); needs a selected coin to chart.
|
|
2882
|
+
state.pendingPopup = action.open ? 'chart' : null;
|
|
2883
|
+
break;
|
|
2884
|
+
}
|
|
2885
|
+
case 'toggle_calm': {
|
|
2886
|
+
if (action.on) {
|
|
2887
|
+
if (!isCalm(state.vfx)) {
|
|
2888
|
+
state.vfx.intensity = 'off';
|
|
2889
|
+
state.vfx.trail.clear();
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
else if (isCalm(state.vfx)) {
|
|
2893
|
+
const back = 'fever';
|
|
2894
|
+
state.vfx.intensity = back;
|
|
2895
|
+
state.vfx.baseline = 0.12;
|
|
2896
|
+
}
|
|
2897
|
+
break;
|
|
2898
|
+
}
|
|
2899
|
+
case 'set_scan': {
|
|
2900
|
+
scanCommand(state, action.on ? 'on' : 'off', stack);
|
|
2901
|
+
break;
|
|
2902
|
+
}
|
|
2903
|
+
case 'export_view': {
|
|
2904
|
+
state.pendingExport = true;
|
|
2763
2905
|
break;
|
|
2764
2906
|
}
|
|
2765
2907
|
}
|
|
2766
2908
|
times.push(now);
|
|
2767
2909
|
return { ok: true, label: res.label };
|
|
2768
2910
|
}
|
|
2911
|
+
/**
|
|
2912
|
+
* task-1421: move the REAL terminal to mirror an agent's navigation (follow mode, on the owner's own
|
|
2913
|
+
* machine). This is the same VIEW-ONLY navigation the analyst / go-to path uses — open a token, switch
|
|
2914
|
+
* screen/feed, set a filter, highlight a row, open the PAPER ticket editor. It never trades, sizes, confirms
|
|
2915
|
+
* or signs, and the key loop flips control to the user before any of it runs, so the human always wins the
|
|
2916
|
+
* screen. Unlike the analyst boundary it does not re-check the on-screen allow-list: the agent already made
|
|
2917
|
+
* the call on its own server and this only reflects where it went. A no-op when live navigation is off.
|
|
2918
|
+
*/
|
|
2919
|
+
export function applyFollowNav(state, nav, stack) {
|
|
2920
|
+
const sc = state.screener;
|
|
2921
|
+
const live = state.live && state.screener.source !== 'fixtures' ? state.live : null;
|
|
2922
|
+
const toScreen = (next) => { if (state.screen !== next) {
|
|
2923
|
+
stack.push(state.screen);
|
|
2924
|
+
state.screen = next;
|
|
2925
|
+
} };
|
|
2926
|
+
if (nav.mint && (!nav.screen || nav.screen === 'token')) {
|
|
2927
|
+
if (!live)
|
|
2928
|
+
return;
|
|
2929
|
+
try {
|
|
2930
|
+
live.openToken(seedForMint(state, live, nav.mint));
|
|
2931
|
+
toScreen('token');
|
|
2932
|
+
}
|
|
2933
|
+
catch { /* a stale mint never breaks follow */ }
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (nav.screen === 'ticket') {
|
|
2937
|
+
if (!live || !nav.mint)
|
|
2938
|
+
return;
|
|
2939
|
+
try {
|
|
2940
|
+
const seed = seedForMint(state, live, nav.mint);
|
|
2941
|
+
live.openTicket({ mint: seed.mint, symbol: seed.symbol, name: seed.name }, nav.side === 'sell' ? 'sell' : 'buy');
|
|
2942
|
+
toScreen('ticket');
|
|
2943
|
+
}
|
|
2944
|
+
catch { /* ignore */ }
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
if (nav.screen === 'watchlist') {
|
|
2948
|
+
if (live && nav.mint) {
|
|
2949
|
+
try {
|
|
2950
|
+
if (!live.watch.has(nav.mint))
|
|
2951
|
+
live.watch.add(LiveContext.candidate(seedForMint(state, live, nav.mint)));
|
|
2952
|
+
}
|
|
2953
|
+
catch { /* ignore */ }
|
|
2954
|
+
}
|
|
2955
|
+
toScreen('watchlist');
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
if (nav.feed || nav.filter || nav.highlight || nav.screen === 'screener') {
|
|
2959
|
+
toScreen('screener');
|
|
2960
|
+
if (nav.feed) {
|
|
2961
|
+
if (setPreset(sc, nav.feed) && state.feed)
|
|
2962
|
+
state.feed.setPreset(sc.preset);
|
|
2963
|
+
}
|
|
2964
|
+
if (nav.filter) {
|
|
2965
|
+
try {
|
|
2966
|
+
setFilter(sc, nav.filter);
|
|
2967
|
+
}
|
|
2968
|
+
catch { /* an invalid agent filter is ignored */ }
|
|
2969
|
+
}
|
|
2970
|
+
if (nav.highlight) {
|
|
2971
|
+
const i = screenerView(sc).findIndex((r) => r.mint === nav.highlight);
|
|
2972
|
+
if (i >= 0)
|
|
2973
|
+
state.selected = i;
|
|
2974
|
+
}
|
|
2975
|
+
state.selected = clampSelection(sc, state.selected);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
if (nav.screen && isScreenKey(nav.screen))
|
|
2979
|
+
toScreen(nav.screen);
|
|
2980
|
+
}
|
|
2981
|
+
/**
|
|
2982
|
+
* task-1413: apply an accepted tier-(b) config proposal through the journaled path as actor `analyst`
|
|
2983
|
+
* (so `ctrl+z` undoes it), creating the parent section when absent (exactly like the `$` / `,` handlers),
|
|
2984
|
+
* then reflect it in the live UI where cheap. Throws on a refusal (schema / policy); the caller toasts it.
|
|
2985
|
+
* Returns the one-line label for the confirmation toast.
|
|
2986
|
+
*/
|
|
2987
|
+
export function applyAnalystConfig(state, proposal) {
|
|
2988
|
+
const ui = state.custom;
|
|
2989
|
+
if (!ui)
|
|
2990
|
+
throw new Error('config changes need a loaded config (not available on this run)');
|
|
2991
|
+
const change = configChangeForAction(proposal.action);
|
|
2992
|
+
if (!change)
|
|
2993
|
+
throw new Error('not a persisted-config action');
|
|
2994
|
+
const parent = ui.config[change.section];
|
|
2995
|
+
const op = parent && typeof parent === 'object'
|
|
2996
|
+
? { op: 'add', path: change.path, value: change.value }
|
|
2997
|
+
: { op: 'add', path: `/${change.section}`, value: { [change.key]: change.value } };
|
|
2998
|
+
// THE GATE (task-1413 r2): re-run the model-actor policy on the exact journal op BEFORE writing. The
|
|
2999
|
+
// analyst is not the user, so `DENIED_FOR_MODELS` applies — a config action can never reach a signer /
|
|
3000
|
+
// trading / keys / caps / mandates / wallet / alerts / VOICE path, even behind the keypress confirm.
|
|
3001
|
+
// CONFIG_ACTION_PATHS is already a closed non-denied allow-list; this is the server-side belt-and-braces.
|
|
3002
|
+
const denied = policyCheck([op], 'claude');
|
|
3003
|
+
if (denied.length)
|
|
3004
|
+
throw new Error(`refused: ${denied.map((e) => e.msg).join('; ')}`);
|
|
3005
|
+
const { config } = applyAndJournal(ui.home, [op], { actor: 'analyst', why: `analyst: ${proposal.summary}`, now: state.now.getTime() });
|
|
3006
|
+
ui.config = config;
|
|
3007
|
+
// Reflect the change in the running UI where it is cheap and local.
|
|
3008
|
+
switch (proposal.action.type) {
|
|
3009
|
+
case 'set_theme': {
|
|
3010
|
+
const t = themeByName(ui, proposal.action.name);
|
|
3011
|
+
if (t)
|
|
3012
|
+
state.theme = t;
|
|
3013
|
+
break;
|
|
3014
|
+
}
|
|
3015
|
+
case 'set_price_mode':
|
|
3016
|
+
state.priceMode = proposal.action.mode;
|
|
3017
|
+
break;
|
|
3018
|
+
case 'set_intensity':
|
|
3019
|
+
state.vfx.intensity = proposal.action.intensity;
|
|
3020
|
+
state.vfx.baseline = proposal.action.intensity === 'fever' ? 0.12 : 0;
|
|
3021
|
+
break;
|
|
3022
|
+
default: break;
|
|
3023
|
+
}
|
|
3024
|
+
return proposal.summary;
|
|
3025
|
+
}
|
|
2769
3026
|
/**
|
|
2770
3027
|
* Route one decoded key: the screener's filter editor and sort menu swallow keys while open,
|
|
2771
3028
|
* everything else goes through the keymap. Exported for tests.
|
|
@@ -2993,6 +3250,22 @@ export function applyKey(state, key, stack) {
|
|
|
2993
3250
|
ui.input = cur + key.ch;
|
|
2994
3251
|
return;
|
|
2995
3252
|
}
|
|
3253
|
+
// task-1399: on the follows screen (not typing, no modal), enter opens/closes the selected wallet's DETAIL view.
|
|
3254
|
+
if (state.screen === 'follows' && state.copyUi && state.copyUi.input === null && !state.copyUi.mirror && !state.copyUi.autoModal && key.name === 'enter') {
|
|
3255
|
+
const ui = state.copyUi;
|
|
3256
|
+
if (ui.detail) {
|
|
3257
|
+
ui.detail = null;
|
|
3258
|
+
return;
|
|
3259
|
+
}
|
|
3260
|
+
const svc = state.copy;
|
|
3261
|
+
const rows = svc?.follows() ?? [];
|
|
3262
|
+
const f = rows[Math.max(0, Math.min(rows.length - 1, ui.selected))];
|
|
3263
|
+
if (f)
|
|
3264
|
+
ui.detail = { wallet: f.addr, scroll: 0 };
|
|
3265
|
+
else
|
|
3266
|
+
ui.notice = 'follow a wallet first (i)';
|
|
3267
|
+
return;
|
|
3268
|
+
}
|
|
2996
3269
|
if (state.screen === 'kol' && state.kolInput !== null && state.kolInput !== undefined) {
|
|
2997
3270
|
const kol = kolOf(state);
|
|
2998
3271
|
if (key.name === 'esc') {
|
|
@@ -3540,6 +3813,9 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3540
3813
|
let timer = null;
|
|
3541
3814
|
let lastRefresh = 0;
|
|
3542
3815
|
let done = false;
|
|
3816
|
+
// task-1421: the MCP follow session (set below when `--spectate-mcp` is on). Declared here so the quit /
|
|
3817
|
+
// terminal-guard teardown closures can stop it.
|
|
3818
|
+
let followSession = null;
|
|
3543
3819
|
// Idle economy (task-1303, retuned for task-1319 AC3): between events the ambient rain redraws at
|
|
3544
3820
|
// IDLE_FPS instead of the full frame cap, cutting idle CPU. A keypress opens a KEY_WAKE_S full-rate
|
|
3545
3821
|
// window (`wakeUntil`) so input and transitions stay smooth; data landings paint at once on their own
|
|
@@ -3569,7 +3845,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3569
3845
|
}
|
|
3570
3846
|
};
|
|
3571
3847
|
const guard = terminalGuard(io, () => { done = true; prefetcher?.close(); if (timer)
|
|
3572
|
-
clearInterval(timer); state.analyst?.cancel(); if (state.analystByMint)
|
|
3848
|
+
clearInterval(timer); void followSession?.stop(); state.analyst?.cancel(); if (state.analystByMint)
|
|
3573
3849
|
for (const s of state.analystByMint.values())
|
|
3574
3850
|
s.cancel(); state.popups?.shutdown(); voice?.disable(); images?.close(); live?.close(); feed?.close(); }, proc, { beforeLeave: () => layer?.teardown() ?? '' });
|
|
3575
3851
|
let lastTokenRefresh = 0;
|
|
@@ -3623,6 +3899,10 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3623
3899
|
// Live bindings redraw as soon as something lands (one frame per macrotask at most).
|
|
3624
3900
|
let drawPending = false;
|
|
3625
3901
|
let lastFastDraw = 0;
|
|
3902
|
+
// task-1399: a followed wallet's buy/sell also PUSHES through the alerts engine (desktop / Telegram / Discord)
|
|
3903
|
+
// when the user enabled a push channel. Forward-declared; the real impl is set once the Notifier exists below.
|
|
3904
|
+
// Read-only, like a rule alert — nothing here trades, arms or signs.
|
|
3905
|
+
let pushCopyAlert = () => { };
|
|
3626
3906
|
if (live) {
|
|
3627
3907
|
// task-1360: poll the followed wallets for the whole run (live only; replay never spends credits) and toast
|
|
3628
3908
|
// every new buy/sell a followed wallet made. Information only: no path from an alert to a trade exists here.
|
|
@@ -3635,6 +3915,8 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3635
3915
|
const a = as[as.length - 1];
|
|
3636
3916
|
const who = a.name ?? shortAddr(a.wallet);
|
|
3637
3917
|
say(`${who} ${a.trade.side === 'buy' ? 'BOUGHT' : 'SOLD'} ${shortAddr(a.trade.mint)}${a.trade.usd !== null ? ` ≈ $${a.trade.usd.toFixed(0)}` : ''}${a.lagMs !== null ? ` · lag ${Math.round(a.lagMs / 1000)}s` : ''}${as.length > 1 ? ` (+${as.length - 1} more · U)` : ' (U)'}`, a.trade.side === 'buy' ? 'up' : 'down', 6);
|
|
3918
|
+
for (const ca of as)
|
|
3919
|
+
pushCopyAlert(ca); // task-1399: push each one out through the alerts engine too
|
|
3638
3920
|
}
|
|
3639
3921
|
}
|
|
3640
3922
|
if (done || drawPending)
|
|
@@ -3734,6 +4016,37 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3734
4016
|
// task-1326: an image landing (decoded, or found missing) repaints at most once per macrotask.
|
|
3735
4017
|
if (images)
|
|
3736
4018
|
images.onChange = () => scheduleDraw();
|
|
4019
|
+
// task-1421: `--spectate-mcp` → follow an agent driving over MCP. The rendezvous lets a `dexterm mcp`
|
|
4020
|
+
// server the external client spawned emit here; we tail it, move the terminal for navigation calls, and
|
|
4021
|
+
// the owner's controls (ctrl+p pause/resume · ctrl+k stop · any key = take over) are handled in onInput.
|
|
4022
|
+
// A read-only, loopback, token-gated stream opens too (`dexterm spectate --info`). Off (and zero-cost)
|
|
4023
|
+
// unless the flag is set; nothing it folds can arm, sign, confirm, send or raise a cap.
|
|
4024
|
+
if (args.spectateMcp) {
|
|
4025
|
+
followSession = new McpFollowSession({
|
|
4026
|
+
env: io.env,
|
|
4027
|
+
stream: true,
|
|
4028
|
+
onNav: (nav) => {
|
|
4029
|
+
const before = state.screen;
|
|
4030
|
+
try {
|
|
4031
|
+
applyFollowNav(state, nav, stack);
|
|
4032
|
+
}
|
|
4033
|
+
catch { /* a stale mint never breaks follow */ }
|
|
4034
|
+
if (state.screen !== before) {
|
|
4035
|
+
try {
|
|
4036
|
+
startTransition();
|
|
4037
|
+
}
|
|
4038
|
+
catch {
|
|
4039
|
+
transition = null;
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
scheduleDraw();
|
|
4043
|
+
},
|
|
4044
|
+
onStatus: (line) => { say(line, 'ai', 4); scheduleDraw(); },
|
|
4045
|
+
onEvent: () => scheduleDraw(),
|
|
4046
|
+
});
|
|
4047
|
+
void followSession.start();
|
|
4048
|
+
say('following agents over MCP · ctrl+p pause · ctrl+k stop · any key takes over', 'ai', 6);
|
|
4049
|
+
}
|
|
3737
4050
|
// Launch a real Claude analyst session when a key asked for one (applyAction cannot spawn). A
|
|
3738
4051
|
// `question` means start it as a chat turn, not a paid structured skill run (task-1311, QA B3).
|
|
3739
4052
|
const beginAnalyst = () => {
|
|
@@ -3755,6 +4068,15 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3755
4068
|
const r = applyAnalystAction(state, raw, stack);
|
|
3756
4069
|
if (!r.ok || !r.label)
|
|
3757
4070
|
return;
|
|
4071
|
+
// task-1413 tier (b): a persisted-config request is a PROPOSAL — show it and wait for the user's key.
|
|
4072
|
+
if (r.proposal) {
|
|
4073
|
+
// The proposal expires with its 8s toast (task-1413 defect c): a 'y' typed after it fades never
|
|
4074
|
+
// persists a stale change. `now` is the frame clock so it stays deterministic under replay.
|
|
4075
|
+
state.analystConfigProposal = { ...r.proposal, expiresAt: state.now.getTime() + 8000 };
|
|
4076
|
+
say(`analyst suggests: ${r.proposal.summary} — press y to accept, any other key to dismiss`, 'ai', 8);
|
|
4077
|
+
scheduleDraw();
|
|
4078
|
+
return;
|
|
4079
|
+
}
|
|
3758
4080
|
const log = (state.analystActions ??= []);
|
|
3759
4081
|
log.push(r.label);
|
|
3760
4082
|
if (log.length > 6)
|
|
@@ -4232,6 +4554,10 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4232
4554
|
const alertNotifier = state.custom && hasPushChannel(state.custom.config.alerts)
|
|
4233
4555
|
? buildNotifier(state.custom.home, state.custom.config.alerts, { toastSink: () => { } })
|
|
4234
4556
|
: null;
|
|
4557
|
+
// task-1399: now the Notifier exists, a followed wallet's buy/sell can be pushed. Fire-and-forget; a slow
|
|
4558
|
+
// channel never blocks the loop; routing's `levels` filter lets the user scope (a buy is `up`, a sell `down`).
|
|
4559
|
+
if (alertNotifier)
|
|
4560
|
+
pushCopyAlert = (ca) => { void alertNotifier.deliver(copyAlertToDelivery(ca)).catch(() => { }); };
|
|
4235
4561
|
const drainDeliveries = () => {
|
|
4236
4562
|
const ui = state.rulesUi;
|
|
4237
4563
|
if (!ui || !ui.pendingDeliveries.length)
|
|
@@ -4402,6 +4728,47 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4402
4728
|
return { price: np.price ?? null, mcap: np.mcap ?? null };
|
|
4403
4729
|
return { price: null, mcap: null };
|
|
4404
4730
|
};
|
|
4731
|
+
// task-1411: a short instruction appended to a VOICE analyst question so the answer is built for the
|
|
4732
|
+
// ear, not the screen — market cap not a per-token price, round numbers, short.
|
|
4733
|
+
const VOICE_ANALYST_SPEECH_HINT = 'Answer out loud for text-to-speech: talk about price as market cap, never a raw per-token price; '
|
|
4734
|
+
+ 'round big numbers like "340 thousand" or "1.2 million"; no long addresses; keep it to two or three short sentences.';
|
|
4735
|
+
// A spoken pronoun that points at the highlighted row ("this one", "this coin", "it").
|
|
4736
|
+
const VOICE_SELECTION_PRONOUN = /\b(?:this (?:one|coin|token)|that (?:one|coin|token)|this|it)\b/i;
|
|
4737
|
+
// A friendly, complete spoken ack for each navigable screen (the router's `say` is the terse key).
|
|
4738
|
+
const NAV_ACK = {
|
|
4739
|
+
screener: 'the screener', newpairs: 'new pairs', watchlist: 'your watchlist', analyst: 'the analyst', positions: 'your positions',
|
|
4740
|
+
};
|
|
4741
|
+
/**
|
|
4742
|
+
* Run a VOICE-originated analyst question (task-1411). Fixes the screener silence: "what do you think
|
|
4743
|
+
* of THIS ONE" aimed at a highlighted screener row used to start a context-free `trench-scan` with no
|
|
4744
|
+
* mint, so the analyst never learned which coin "this one" was and answered with a short, useless
|
|
4745
|
+
* fragment (the tester heard nothing useful). We now attach the SELECTED coin as a token-dossier target —
|
|
4746
|
+
* exactly the context a token screen already gives — so it answers for real. The question also carries
|
|
4747
|
+
* a short "answer for speech" instruction (market cap, round numbers, short).
|
|
4748
|
+
*/
|
|
4749
|
+
const askVoiceAnalyst = (question) => {
|
|
4750
|
+
const q = question.trim().slice(0, 400);
|
|
4751
|
+
const instructed = `${q}\n\n${VOICE_ANALYST_SPEECH_HINT}`;
|
|
4752
|
+
// A warm session: follow up on it so the model keeps context (the screen is already analyst/token).
|
|
4753
|
+
if (state.analyst) {
|
|
4754
|
+
state.analyst.ask(instructed, buildScreenState(state));
|
|
4755
|
+
return;
|
|
4756
|
+
}
|
|
4757
|
+
const tv = live?.token?.view;
|
|
4758
|
+
const sel = voiceSelected();
|
|
4759
|
+
if (state.screen === 'token' && live && tv?.mint) {
|
|
4760
|
+
state.pendingAnalyst = { skill: 'token-dossier', mint: tv.mint, symbol: tv.symbol, target: 'token', question: instructed };
|
|
4761
|
+
}
|
|
4762
|
+
else if (live && sel && VOICE_SELECTION_PRONOUN.test(q)) {
|
|
4763
|
+
// the screener/analyst/watchlist/new-pairs highlighted coin IS "this one" (task-1411)
|
|
4764
|
+
state.pendingAnalyst = { skill: 'token-dossier', mint: sel.mint, symbol: sel.symbol, target: 'analyst', question: instructed };
|
|
4765
|
+
}
|
|
4766
|
+
else {
|
|
4767
|
+
state.pendingAnalyst = { skill: 'trench-scan', target: 'analyst', question: instructed };
|
|
4768
|
+
}
|
|
4769
|
+
if (state.pendingAnalyst)
|
|
4770
|
+
beginAnalyst();
|
|
4771
|
+
};
|
|
4405
4772
|
/**
|
|
4406
4773
|
* Perform a voice decision. Returns `false` when the app spoke its own reply (an ask / clarify), so
|
|
4407
4774
|
* the controller does not read the decision's `say` line over it (task-1339, re-verify N1: the
|
|
@@ -4417,6 +4784,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4417
4784
|
const a = SCREEN_ACTION[d.screen];
|
|
4418
4785
|
if (a)
|
|
4419
4786
|
applyAction(state, a, stack);
|
|
4787
|
+
ask(NAV_ACK[d.screen] ?? d.say);
|
|
4420
4788
|
break;
|
|
4421
4789
|
}
|
|
4422
4790
|
case 'open-token': {
|
|
@@ -4541,14 +4909,14 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4541
4909
|
case 'analyst': {
|
|
4542
4910
|
// Route the question to the analyst, surface the chat, and let the answer be spoken when it lands.
|
|
4543
4911
|
// Capped like typed chat (400 chars): a runaway transcript never becomes a 200 KB argv (N7).
|
|
4912
|
+
// task-1411: askVoiceAnalyst attaches the highlighted coin as context so a screener "this one"
|
|
4913
|
+
// question is answered for real, and appends the "answer for speech" instruction.
|
|
4544
4914
|
if (state.screen !== 'token' && state.screen !== 'analyst') {
|
|
4545
4915
|
stack.push(state.screen);
|
|
4546
4916
|
state.screen = 'analyst';
|
|
4547
4917
|
}
|
|
4548
4918
|
syncAnalyst(state);
|
|
4549
|
-
|
|
4550
|
-
if (state.pendingAnalyst)
|
|
4551
|
-
beginAnalyst();
|
|
4919
|
+
askVoiceAnalyst(d.question);
|
|
4552
4920
|
break;
|
|
4553
4921
|
}
|
|
4554
4922
|
case 'workspace': {
|
|
@@ -4615,6 +4983,21 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4615
4983
|
}
|
|
4616
4984
|
catch { /* keep the voice for this run; nothing to surface */ }
|
|
4617
4985
|
};
|
|
4986
|
+
// task-1411: persist the < / > speaking-rate nudge to config.voice.rate (actor 'user', journaled →
|
|
4987
|
+
// undo works). Best-effort: a failure keeps the rate for this run and never breaks the UI.
|
|
4988
|
+
const persistVoiceRate = (rate) => {
|
|
4989
|
+
const ui = state.custom;
|
|
4990
|
+
if (!ui)
|
|
4991
|
+
return;
|
|
4992
|
+
try {
|
|
4993
|
+
const op = ui.config.voice
|
|
4994
|
+
? { op: 'add', path: '/voice/rate', value: rate }
|
|
4995
|
+
: { op: 'add', path: '/voice', value: { rate } };
|
|
4996
|
+
const { config } = applyAndJournal(ui.home, [op], { actor: 'user', why: `voice rate ${rate}`, now: state.now.getTime() });
|
|
4997
|
+
ui.config = config;
|
|
4998
|
+
}
|
|
4999
|
+
catch { /* keep the rate for this run; nothing to surface */ }
|
|
5000
|
+
};
|
|
4618
5001
|
const initVoice = async () => {
|
|
4619
5002
|
if (voiceInitStarted)
|
|
4620
5003
|
return;
|
|
@@ -4643,6 +5026,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4643
5026
|
} },
|
|
4644
5027
|
onDecision: handleVoiceDecision,
|
|
4645
5028
|
onVoiceChange: persistVoiceChoice,
|
|
5029
|
+
onRateChange: persistVoiceRate,
|
|
4646
5030
|
});
|
|
4647
5031
|
voice.enable();
|
|
4648
5032
|
await voice.toggleTalk(); // the first `v` press both enables and starts listening
|
|
@@ -4652,6 +5036,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4652
5036
|
prefetcher?.close();
|
|
4653
5037
|
if (timer)
|
|
4654
5038
|
clearInterval(timer);
|
|
5039
|
+
void followSession?.stop();
|
|
4655
5040
|
guard.restore();
|
|
4656
5041
|
guard.uninstall();
|
|
4657
5042
|
cancelAllAnalysts();
|
|
@@ -4706,6 +5091,56 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4706
5091
|
quit();
|
|
4707
5092
|
return;
|
|
4708
5093
|
}
|
|
5094
|
+
// task-1421 follow-mode controls (only while following an MCP agent). ctrl+p pauses / resumes the
|
|
5095
|
+
// terminal mirroring; ctrl+k stops following (removes the rendezvous so the server stops emitting);
|
|
5096
|
+
// ANY other key while the agent is driving hands control to the user — the human and the agent never
|
|
5097
|
+
// fight over the screen. The control keys are consumed; the take-over key still does its own job.
|
|
5098
|
+
if (followSession) {
|
|
5099
|
+
if (key.ctrl && key.ch === 'k') {
|
|
5100
|
+
followSession.kill();
|
|
5101
|
+
draw();
|
|
5102
|
+
continue;
|
|
5103
|
+
}
|
|
5104
|
+
if (key.ctrl && key.ch === 'p') {
|
|
5105
|
+
if (followSession.isPaused)
|
|
5106
|
+
followSession.resume();
|
|
5107
|
+
else
|
|
5108
|
+
followSession.pause();
|
|
5109
|
+
draw();
|
|
5110
|
+
continue;
|
|
5111
|
+
}
|
|
5112
|
+
if (followSession.controlState === 'agent')
|
|
5113
|
+
followSession.userKey(); // take over, then fall through
|
|
5114
|
+
}
|
|
5115
|
+
// task-1413: a pending analyst config PROPOSAL owns the next key. `y` accepts and journals it as
|
|
5116
|
+
// actor 'analyst' (ctrl+z undoes); any other key dismisses it. A stray key never persists a change.
|
|
5117
|
+
// Two guards (defect c): the proposal does NOT steal a key while a TEXT INPUT is open — the GO-TO
|
|
5118
|
+
// prompt or a focused analyst chat line — so a 'y' being typed goes into the field, not the accept;
|
|
5119
|
+
// and an EXPIRED proposal (its 8s toast has faded) is silently dropped, never accepted by a late 'y'.
|
|
5120
|
+
if (state.analystConfigProposal) {
|
|
5121
|
+
const inputOpen = !!state.goto || (state.aiFocused === true);
|
|
5122
|
+
const expired = state.now.getTime() >= state.analystConfigProposal.expiresAt;
|
|
5123
|
+
if (expired) {
|
|
5124
|
+
state.analystConfigProposal = null; // let the key fall through to its normal handler below
|
|
5125
|
+
}
|
|
5126
|
+
else if (!inputOpen) {
|
|
5127
|
+
const prop = state.analystConfigProposal;
|
|
5128
|
+
state.analystConfigProposal = null;
|
|
5129
|
+
if (key.ch === 'y' || key.ch === 'Y') {
|
|
5130
|
+
try {
|
|
5131
|
+
say(`analyst ${applyAnalystConfig(state, prop)} · ctrl+z undoes`, 'ai', 5);
|
|
5132
|
+
}
|
|
5133
|
+
catch (e) {
|
|
5134
|
+
say(`change refused: ${describeError(e)}`, 'warn', 4);
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
5137
|
+
else {
|
|
5138
|
+
say('dismissed the analyst change', 'ai', 3);
|
|
5139
|
+
}
|
|
5140
|
+
draw();
|
|
5141
|
+
continue;
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
4709
5144
|
// task-1388 GO-TO prompt: while the "open any coin" input is open it owns the keyboard. Route the key
|
|
4710
5145
|
// straight to applyKey (which does the capture / open) and skip the voice, calm, demo and keymap
|
|
4711
5146
|
// handlers below — otherwise a base58 char like v / m / c would toggle voice or calm instead of typing.
|
|
@@ -4740,7 +5175,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4740
5175
|
// `micEngaged` also covers the window while the mic is still OPENING (listen() in flight), so a key
|
|
4741
5176
|
// pressed before the engine came up closes the session the moment it arrives (task-1339, N3/N8).
|
|
4742
5177
|
if (voice?.micEngaged) {
|
|
4743
|
-
const isVoiceCtl = key.name === 'char' && !key.ctrl && !key.alt && (key.ch === 'v' || key.ch === 'm' || key.ch === ',');
|
|
5178
|
+
const isVoiceCtl = key.name === 'char' && !key.ctrl && !key.alt && (key.ch === 'v' || key.ch === 'm' || key.ch === ',' || key.ch === '<' || key.ch === '>');
|
|
4744
5179
|
if (key.name === 'esc') {
|
|
4745
5180
|
voice.cancelListen();
|
|
4746
5181
|
draw();
|
|
@@ -4782,6 +5217,16 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4782
5217
|
draw();
|
|
4783
5218
|
continue;
|
|
4784
5219
|
}
|
|
5220
|
+
if (voice?.isEnabled && key.ch === '<') {
|
|
5221
|
+
voice.nudgeRate(-1);
|
|
5222
|
+
draw();
|
|
5223
|
+
continue;
|
|
5224
|
+
} // slower (task-1411)
|
|
5225
|
+
if (voice?.isEnabled && key.ch === '>') {
|
|
5226
|
+
voice.nudgeRate(1);
|
|
5227
|
+
draw();
|
|
5228
|
+
continue;
|
|
5229
|
+
} // faster (task-1411)
|
|
4785
5230
|
}
|
|
4786
5231
|
}
|
|
4787
5232
|
// A modal overlay (welcome/help) is open: any key dismisses it; `?` switches welcome→help (task-1305).
|
|
@@ -4800,6 +5245,32 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4800
5245
|
draw();
|
|
4801
5246
|
continue;
|
|
4802
5247
|
}
|
|
5248
|
+
// task-1407: the PnL card preview owns the keys while open (share / save / toggles / other coin / esc).
|
|
5249
|
+
if (state.overlay === 'pnl' && state.pnl) {
|
|
5250
|
+
const ui = state.pnl;
|
|
5251
|
+
const eff = pnlKey(ui, key);
|
|
5252
|
+
if (eff.kind === 'close') {
|
|
5253
|
+
state.overlay = null;
|
|
5254
|
+
state.pnl = null;
|
|
5255
|
+
}
|
|
5256
|
+
else if (eff.kind === 'reload') {
|
|
5257
|
+
ui.stats = null;
|
|
5258
|
+
ui.error = null;
|
|
5259
|
+
void loadPnl(state).then(() => { if (!done)
|
|
5260
|
+
draw(); });
|
|
5261
|
+
}
|
|
5262
|
+
else if (eff.kind === 'share' && ui.stats) {
|
|
5263
|
+
ui.busy = true;
|
|
5264
|
+
ui.notice = null;
|
|
5265
|
+
void shareCard(ui.stats, { ...ui.opts }, eff.action, { platform: process.platform, env: io.env, run: runLocal, openUrl: (u) => openInBrowser(u), copyText: copyToClipboard })
|
|
5266
|
+
.then((r) => { ui.notice = r.message; say(r.message, 'info', 10); })
|
|
5267
|
+
.catch((e) => { ui.notice = `card failed: ${describeError(e)}`; })
|
|
5268
|
+
.finally(() => { ui.busy = false; if (!done)
|
|
5269
|
+
draw(); });
|
|
5270
|
+
}
|
|
5271
|
+
draw();
|
|
5272
|
+
continue;
|
|
5273
|
+
}
|
|
4803
5274
|
// task-1358: the WORKSPACES card — a digit picks a slot, j/k move, ⏎ activates, esc/q/ctrl+w w close.
|
|
4804
5275
|
if (state.overlay === 'workspaces' && state.custom) {
|
|
4805
5276
|
const ui = state.custom;
|
|
@@ -5449,6 +5920,16 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
5449
5920
|
say(`export failed: ${describeError(e)}`, 'warn', 6);
|
|
5450
5921
|
}
|
|
5451
5922
|
}
|
|
5923
|
+
if (state.pendingPnl) {
|
|
5924
|
+
// task-1407: open the PnL card preview for this screen (recorded fills only), then build the card.
|
|
5925
|
+
state.pendingPnl = false;
|
|
5926
|
+
const why = openPnl(state);
|
|
5927
|
+
if (why)
|
|
5928
|
+
say(why, 'warn', 6);
|
|
5929
|
+
else
|
|
5930
|
+
void loadPnl(state).then(() => { if (!done)
|
|
5931
|
+
draw(); });
|
|
5932
|
+
}
|
|
5452
5933
|
if (state.pendingToast) {
|
|
5453
5934
|
// task-1315: surface the link list / "no link" reply as a durable toast. Drained BEFORE
|
|
5454
5935
|
// pendingOpen so that on a successful open the "opened <url>" toast overwrites the "open <url>" one.
|
|
@@ -5528,6 +6009,9 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
5528
6009
|
lastTokenRefresh = Date.now();
|
|
5529
6010
|
void live.token.load();
|
|
5530
6011
|
}
|
|
6012
|
+
// task-1404: keep the open token's credit-metered holder/rug intel LIVE (slow cadence, budget-gated inside).
|
|
6013
|
+
if (state.screen === 'token' && live.token)
|
|
6014
|
+
live.refreshOpenIntel(Date.now());
|
|
5531
6015
|
if (state.screen === 'watchlist' && Date.now() - lastWatchRefresh >= WATCH_REFRESH_MS) {
|
|
5532
6016
|
lastWatchRefresh = Date.now();
|
|
5533
6017
|
void live.watch.refresh();
|