dexterm-cli 0.1.3 → 0.1.5
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/cli/assets/claude/analyst-system.md +2 -1
- package/dist/packages/cli/src/agent-paper.js +60 -0
- package/dist/packages/cli/src/analyst/backend.js +183 -0
- package/dist/packages/cli/src/analyst/codex.js +301 -0
- package/dist/packages/cli/src/analyst/session.js +9 -2
- package/dist/packages/cli/src/app-help-gen.js +18 -9
- package/dist/packages/cli/src/docs-kb-gen.js +228 -0
- package/dist/packages/cli/src/doctor.js +80 -9
- 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/dev-history.js +99 -0
- package/dist/packages/cli/src/live/newpairs.js +12 -0
- package/dist/packages/cli/src/live/screens/token-live.js +11 -1
- package/dist/packages/cli/src/live/token.js +39 -1
- package/dist/packages/cli/src/main.js +260 -7
- package/dist/packages/cli/src/mcp-follow.js +255 -0
- package/dist/packages/cli/src/record-cmd.js +2 -0
- package/dist/packages/cli/src/rules.js +25 -2
- package/dist/packages/cli/src/screens/analyst-session.js +5 -3
- package/dist/packages/cli/src/screens/analyst.js +4 -3
- package/dist/packages/cli/src/spectate-cmd.js +14 -3
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/x-cmd.js +11 -5
- package/dist/packages/custom/src/rules.js +2 -2
- package/dist/packages/custom/src/schema.js +5 -1
- package/dist/packages/data/src/fixtures.js +28 -6
- package/dist/packages/data/src/index.js +3 -0
- package/dist/packages/data/src/sources/jupiter.js +1 -0
- package/dist/packages/data/src/trade/agent/paper-desk.js +336 -0
- package/dist/packages/data/src/trade/agent/rendezvous.js +161 -0
- package/dist/packages/mcp/assets/kb/app-help/agent-paper-trade.md +38 -0
- package/dist/packages/mcp/assets/kb/app-help/ask-the-analyst.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/connect-an-agent.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/dev-history.md +32 -0
- package/dist/packages/mcp/assets/kb/app-help/paper-trade.md +1 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +4 -1
- package/dist/packages/mcp/assets/kb/app-help/screen-token.md +1 -0
- package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +4 -1
- package/dist/packages/mcp/assets/kb/app-help/use-codex-analyst.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +50 -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-directory-metadata-prepared-not-submitted.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-install-in-one-command.md +51 -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-use-codex-instead-of-claude.md +35 -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 +47 -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 +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-analyst-s-launch.md +29 -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 +81 -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 +53 -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-letting-an-agent-paper-trade-experimental.md +32 -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-dev-history.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-how-well-it-predicts-measured-held-out.md +46 -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 +38 -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 +123 -0
- package/dist/packages/mcp/src/firewall.js +2 -2
- package/dist/packages/mcp/src/index.js +8 -4
- package/dist/packages/mcp/src/paper-agent.js +92 -0
- package/dist/packages/mcp/src/server.js +261 -29
- package/dist/packages/mcp/src/spectator-sink.js +146 -5
- package/dist/packages/mcp/src/user-content.js +1 -0
- package/dist/packages/recorder/src/dev-history.js +129 -0
- package/dist/packages/recorder/src/index.js +1 -0
- package/dist/packages/recorder/src/recorder.js +23 -0
- package/dist/packages/recorder/src/schema.js +11 -0
- package/dist/packages/voice/src/stt/whisper.js +4 -2
- package/package.json +6 -5
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// task-1443 — the PAPER AGENT DESK: the one surface an untrusted agent (Claude Code, a custom bot) uses to
|
|
2
|
+
// open, size, confirm and close a PAPER position, and to read paper positions and paper PnL — strictly inside
|
|
3
|
+
// a user-armed mandate's caps (SPEC §6.13, ROADMAP Pillar 4: "agents are untrusted and can never arm, sign or
|
|
4
|
+
// raise caps").
|
|
5
|
+
//
|
|
6
|
+
// Hard boundaries this module keeps, which the public claim "no MCP tool can sign or send" rests on:
|
|
7
|
+
// (a) PAPER LEDGER ONLY. The only trade op is `executor.execute`, a `TradeExecutor` whose `kind` MUST be
|
|
8
|
+
// 'paper' (asserted at construction). This file imports NOTHING from the signer, the live executor, the
|
|
9
|
+
// swap BUILD adapter, the rpc sender or the bridge — only the paper ledger (./paper.ts), the mandate
|
|
10
|
+
// SCHEMA (../plans/mandate.ts), the audit journal, the panic kill-switch and the keyless quote TYPE. It
|
|
11
|
+
// is therefore structurally unable to reach any live / wallet / signer / send path (enforced by the
|
|
12
|
+
// no-sign registry test, task-1443).
|
|
13
|
+
// (b) CAPS COME FROM THE USER-SET MANDATE. Every limit is read from the armed `Mandate`, RE-READ on every
|
|
14
|
+
// call (in production: replayed from the hash-chained plans journal the app arms it in, so a revoke or a
|
|
15
|
+
// panic in the app halts the agent at once); there is NO method that changes a cap. An agent runs at the most-restricted confirmation tier (`AGENT_TIER`), so
|
|
16
|
+
// its per-trade cap is the mandate's per-trade cap times that tier's multiplier — it can never meet or
|
|
17
|
+
// exceed a human keypress tier.
|
|
18
|
+
// (c) EVERY CALL IS VALIDATED, JOURNALED AND HALTABLE. Before any state change the desk re-reads the control
|
|
19
|
+
// surface (pause / take-over / stop) and the panic file and re-checks the mandate is alive; a refusal is
|
|
20
|
+
// journaled too, so the spectator view and the audit log see it. The user always wins.
|
|
21
|
+
import { systemClock } from "../../clock.js";
|
|
22
|
+
import { mandateAlive, TIERS } from "../plans/mandate.js";
|
|
23
|
+
import { KillSwitch } from "../kill-switch.js";
|
|
24
|
+
import { capsHash } from "../plans/mandate.js";
|
|
25
|
+
import { SOL_MINT, buildQuoteRequest, lamportsToSol, solToLamports, rawToTokens, } from "../../paper.js";
|
|
26
|
+
/** The confirmation tier an agent is pinned to: the weakest, so an agent can never match a human keypress. */
|
|
27
|
+
export const AGENT_TIER = 'hands-free';
|
|
28
|
+
/** A refusal from the desk. `reason` is a stable code; `message` is human text (both are journaled). */
|
|
29
|
+
export class PaperDeskError extends Error {
|
|
30
|
+
reason;
|
|
31
|
+
constructor(reason, message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = 'PaperDeskError';
|
|
34
|
+
this.reason = reason;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const isFiniteNum = (x) => typeof x === 'number' && Number.isFinite(x);
|
|
38
|
+
/**
|
|
39
|
+
* The paper agent desk. One instance per armed mandate + paper wallet. All four verbs (open / size / confirm /
|
|
40
|
+
* close) and both reads (positions / pnl) run here; nothing else is exposed, and there is no cap setter.
|
|
41
|
+
*/
|
|
42
|
+
export class PaperAgentDesk {
|
|
43
|
+
mandateSrc;
|
|
44
|
+
/** The mandate the current call runs under (set by `gate`, which every mutating call runs first). */
|
|
45
|
+
mandate;
|
|
46
|
+
wallet;
|
|
47
|
+
executor;
|
|
48
|
+
quote;
|
|
49
|
+
solUsd;
|
|
50
|
+
control;
|
|
51
|
+
journal;
|
|
52
|
+
kill;
|
|
53
|
+
clock;
|
|
54
|
+
agent;
|
|
55
|
+
tickets = new Map();
|
|
56
|
+
ticketSeq = 0;
|
|
57
|
+
constructor(deps) {
|
|
58
|
+
if (deps.executor.kind !== 'paper') {
|
|
59
|
+
// The one invariant that makes the "no MCP tool can sign or send" claim true: the desk refuses to hold a
|
|
60
|
+
// live executor. There is no code path from here to a signer, so a live executor is a programming error.
|
|
61
|
+
throw new PaperDeskError('not-paper', `the paper agent desk only accepts a paper executor, got "${deps.executor.kind}"`);
|
|
62
|
+
}
|
|
63
|
+
const m = deps.mandate;
|
|
64
|
+
this.mandateSrc = typeof m === 'function' ? m : () => m;
|
|
65
|
+
this.wallet = deps.wallet;
|
|
66
|
+
this.executor = deps.executor;
|
|
67
|
+
this.quote = deps.quote;
|
|
68
|
+
this.solUsd = deps.solUsd;
|
|
69
|
+
this.control = deps.control;
|
|
70
|
+
this.journal = deps.journal;
|
|
71
|
+
this.kill = deps.killSwitch ?? new KillSwitch();
|
|
72
|
+
this.clock = deps.clock ?? systemClock;
|
|
73
|
+
this.agent = deps.agent ?? 'mcp-agent';
|
|
74
|
+
}
|
|
75
|
+
/** The agent's effective per-trade cap: the mandate cap times the weakest-tier multiplier. */
|
|
76
|
+
perTradeCapUsd() {
|
|
77
|
+
return this.mandate.budget.perTradeUsd * TIERS[AGENT_TIER].perTradeMultiplier;
|
|
78
|
+
}
|
|
79
|
+
/** Journal one audit event (kept out of the hot path's failure mode: never throws). */
|
|
80
|
+
note(type, data) {
|
|
81
|
+
try {
|
|
82
|
+
this.journal.append(type, { mandateId: this.mandate?.id ?? null, data: { agent: this.agent, ...data } });
|
|
83
|
+
}
|
|
84
|
+
catch { /* audit is best-effort */ }
|
|
85
|
+
}
|
|
86
|
+
/** Refuse (journaling the refusal) — the one place a PaperDeskError is raised for a gate failure. */
|
|
87
|
+
refuse(stage, reason, message, extra = {}) {
|
|
88
|
+
this.note('agent.paper.refused', { stage, reason, message, ...extra });
|
|
89
|
+
throw new PaperDeskError(reason, message);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The gate every mutating call runs first: panic file → control surface → mandate alive. A failure here is
|
|
93
|
+
* journaled and throws, so a paused / taken-over / stopped session (or a dead mandate) halts the agent.
|
|
94
|
+
*/
|
|
95
|
+
gate(stage, extra = {}) {
|
|
96
|
+
const panic = this.kill.engaged();
|
|
97
|
+
if (panic)
|
|
98
|
+
this.refuse(stage, 'panic', `trading is halted (panic): ${panic.reason}`, extra);
|
|
99
|
+
const c = this.control();
|
|
100
|
+
if (c.state !== 'running') {
|
|
101
|
+
const msg = c.state === 'takeover' ? 'the user has taken over — agent paper trading is paused'
|
|
102
|
+
: c.state === 'paused' ? `agent paper trading is paused${c.reason ? `: ${c.reason}` : ''}`
|
|
103
|
+
: `agent paper trading is stopped${c.reason ? `: ${c.reason}` : ''}`;
|
|
104
|
+
this.refuse(stage, c.state, msg, extra);
|
|
105
|
+
}
|
|
106
|
+
const m = this.mandateSrc();
|
|
107
|
+
if (!m)
|
|
108
|
+
this.refuse(stage, 'no-mandate', 'no armed mandate is available for agent paper trading', extra);
|
|
109
|
+
this.mandate = m;
|
|
110
|
+
const alive = mandateAlive(this.mandate, this.clock.now());
|
|
111
|
+
if (!alive.ok)
|
|
112
|
+
this.refuse(stage, alive.reason, `the mandate is not usable (${alive.reason})`, extra);
|
|
113
|
+
}
|
|
114
|
+
requireAction(stage, side, extra = {}) {
|
|
115
|
+
if (!this.mandate.allowed.actions.includes(side)) {
|
|
116
|
+
this.refuse(stage, 'action-not-allowed', `${side} is not permitted by the mandate`, extra);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
requireMintAllowed(stage, mint, extra = {}) {
|
|
120
|
+
if (this.mandate.allowed.mints && !this.mandate.allowed.mints.includes(mint)) {
|
|
121
|
+
this.refuse(stage, 'mint-not-allowed', 'this mint is not on the mandate allow-list', extra);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
price(stage, extra = {}) {
|
|
125
|
+
const p = this.solUsd();
|
|
126
|
+
if (!isFiniteNum(p) || p <= 0)
|
|
127
|
+
this.refuse(stage, 'no-usd-price', 'no fresh SOL/USD price — cannot price the mandate caps', extra);
|
|
128
|
+
return p;
|
|
129
|
+
}
|
|
130
|
+
// ── USD exposure derived from the paper wallet (source of truth), so caps survive a restart ──
|
|
131
|
+
costUsd(pos, solUsd) {
|
|
132
|
+
return pos ? lamportsToSol(pos.costLamports) * solUsd : 0;
|
|
133
|
+
}
|
|
134
|
+
openCostUsd(solUsd) {
|
|
135
|
+
return this.wallet.positions().reduce((s, p) => s + lamportsToSol(p.costLamports) * solUsd, 0);
|
|
136
|
+
}
|
|
137
|
+
/** Realized LOSS (USD, positive) booked since the mandate was armed. Over the whole paper wallet. */
|
|
138
|
+
realizedLossUsd(solUsd) {
|
|
139
|
+
const since = this.mandate.armedAt ?? this.mandate.createdAt;
|
|
140
|
+
let lamports = 0n;
|
|
141
|
+
for (const t of this.wallet.trades(1000)) {
|
|
142
|
+
if (t.t < since)
|
|
143
|
+
continue;
|
|
144
|
+
if (t.realizedLamports < 0n)
|
|
145
|
+
lamports += -t.realizedLamports;
|
|
146
|
+
}
|
|
147
|
+
return lamportsToSol(lamports) * solUsd;
|
|
148
|
+
}
|
|
149
|
+
/** Check the buy caps for `usd`, throwing a cap-named refusal on any breach. */
|
|
150
|
+
admitBuy(stage, mint, usd, solUsd, extra) {
|
|
151
|
+
const perTrade = this.perTradeCapUsd();
|
|
152
|
+
if (usd > perTrade + 1e-9)
|
|
153
|
+
this.refuse(stage, 'per-trade-cap', `$${usd.toFixed(2)} exceeds the agent per-trade cap $${perTrade.toFixed(2)}`, extra);
|
|
154
|
+
const coin = this.costUsd(this.wallet.position(mint), solUsd);
|
|
155
|
+
if (coin + usd > this.mandate.budget.perCoinUsd + 1e-9) {
|
|
156
|
+
this.refuse(stage, 'per-coin-cap', `$${(coin + usd).toFixed(2)} on this coin exceeds the per-coin cap $${this.mandate.budget.perCoinUsd.toFixed(2)}`, extra);
|
|
157
|
+
}
|
|
158
|
+
const total = this.openCostUsd(solUsd);
|
|
159
|
+
if (total + usd > this.mandate.budget.totalUsd + 1e-9) {
|
|
160
|
+
this.refuse(stage, 'total-cap', `$${(total + usd).toFixed(2)} deployed exceeds the total budget $${this.mandate.budget.totalUsd.toFixed(2)}`, extra);
|
|
161
|
+
}
|
|
162
|
+
const held = this.wallet.positions().filter((p) => p.tokensRaw > 0n);
|
|
163
|
+
if (!held.some((p) => p.mint === mint) && held.length >= this.mandate.limits.maxOpenPositions) {
|
|
164
|
+
this.refuse(stage, 'open-positions-cap', `${held.length} open paper positions reached the mandate limit ${this.mandate.limits.maxOpenPositions}`, extra);
|
|
165
|
+
}
|
|
166
|
+
const loss = this.realizedLossUsd(solUsd);
|
|
167
|
+
if (loss >= this.mandate.budget.dailyLossUsd - 1e-9) {
|
|
168
|
+
this.refuse(stage, 'daily-loss-limit', `realized loss $${loss.toFixed(2)} has reached the daily loss limit $${this.mandate.budget.dailyLossUsd.toFixed(2)}; no new buys`, extra);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// ───────────────────────────── the four verbs ─────────────────────────────
|
|
172
|
+
/** Open an inert PAPER ticket for a mint + side. Validates the gate, the action and the allow-list only. */
|
|
173
|
+
open(input) {
|
|
174
|
+
const side = input.side;
|
|
175
|
+
this.gate('open', { mint: input.mint, side });
|
|
176
|
+
if (typeof input.mint !== 'string' || input.mint.length < 32)
|
|
177
|
+
this.refuse('open', 'bad-request', 'a base58 mint is required');
|
|
178
|
+
if (!isFiniteNum(input.decimals) || input.decimals < 0 || input.decimals > 18)
|
|
179
|
+
this.refuse('open', 'bad-request', 'token decimals (0..18) are required');
|
|
180
|
+
this.requireAction('open', side, { mint: input.mint });
|
|
181
|
+
this.requireMintAllowed('open', input.mint, { side });
|
|
182
|
+
const t = {
|
|
183
|
+
id: `pt_${++this.ticketSeq}_${this.clock.now().toString(36)}`,
|
|
184
|
+
mint: input.mint, symbol: input.symbol, decimals: input.decimals, side, usd: null, fraction: null, openedAt: this.clock.now(),
|
|
185
|
+
};
|
|
186
|
+
this.tickets.set(t.id, t);
|
|
187
|
+
this.note('agent.paper.open', { ticketId: t.id, mint: t.mint, symbol: t.symbol, side });
|
|
188
|
+
return { ...t };
|
|
189
|
+
}
|
|
190
|
+
/** Size a ticket. Buy: `usd` (checked against the caps, never raised). Sell: `fraction` in (0, 1]. */
|
|
191
|
+
size(ticketId, input) {
|
|
192
|
+
const t = this.tickets.get(ticketId);
|
|
193
|
+
if (!t)
|
|
194
|
+
this.refuse('size', 'unknown-ticket', 'no such open ticket');
|
|
195
|
+
this.gate('size', { ticketId });
|
|
196
|
+
if (t.side === 'buy') {
|
|
197
|
+
const usd = input.usd;
|
|
198
|
+
if (!isFiniteNum(usd) || usd <= 0)
|
|
199
|
+
this.refuse('size', 'bad-request', 'a positive USD size is required for a buy', { ticketId });
|
|
200
|
+
const solUsd = this.price('size', { ticketId });
|
|
201
|
+
this.admitBuy('size', t.mint, usd, solUsd, { ticketId });
|
|
202
|
+
t.usd = usd;
|
|
203
|
+
t.fraction = null;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
const frac = input.fraction;
|
|
207
|
+
if (!isFiniteNum(frac) || !(frac > 0 && frac <= 1))
|
|
208
|
+
this.refuse('size', 'bad-request', 'a sell fraction in (0, 1] is required', { ticketId });
|
|
209
|
+
t.fraction = frac;
|
|
210
|
+
t.usd = null;
|
|
211
|
+
}
|
|
212
|
+
this.note('agent.paper.size', { ticketId, side: t.side, usd: t.usd, fraction: t.fraction });
|
|
213
|
+
return { ...t };
|
|
214
|
+
}
|
|
215
|
+
/** Confirm a sized ticket: re-check the gate and the caps, fetch a FRESH quote, book the PAPER fill. */
|
|
216
|
+
async confirm(ticketId) {
|
|
217
|
+
const t = this.tickets.get(ticketId);
|
|
218
|
+
if (!t)
|
|
219
|
+
this.refuse('confirm', 'unknown-ticket', 'no such open ticket');
|
|
220
|
+
this.gate('confirm', { ticketId });
|
|
221
|
+
this.requireAction('confirm', t.side, { ticketId });
|
|
222
|
+
this.requireMintAllowed('confirm', t.mint, { ticketId });
|
|
223
|
+
const order = await this.buildOrder('confirm', t);
|
|
224
|
+
const rec = await this.book('confirm', order, { ticketId });
|
|
225
|
+
this.tickets.delete(ticketId);
|
|
226
|
+
return { fill: rec, position: this.positionView(this.wallet.position(t.mint)) };
|
|
227
|
+
}
|
|
228
|
+
/** Close a fraction of a held PAPER position in one call (open sell → size fraction → confirm). */
|
|
229
|
+
async close(input) {
|
|
230
|
+
this.gate('close', { mint: input.mint });
|
|
231
|
+
this.requireAction('close', 'sell', { mint: input.mint });
|
|
232
|
+
this.requireMintAllowed('close', input.mint, {});
|
|
233
|
+
const pos = this.wallet.position(input.mint);
|
|
234
|
+
if (!pos || pos.tokensRaw <= 0n)
|
|
235
|
+
this.refuse('close', 'no-position', 'no open paper position in this mint to close', { mint: input.mint });
|
|
236
|
+
const fraction = input.fraction ?? 1;
|
|
237
|
+
if (!isFiniteNum(fraction) || !(fraction > 0 && fraction <= 1))
|
|
238
|
+
this.refuse('close', 'bad-request', 'fraction must be in (0, 1]', { mint: input.mint });
|
|
239
|
+
const t = { id: `pt_close_${++this.ticketSeq}`, mint: input.mint, symbol: pos.symbol, decimals: pos.decimals, side: 'sell', usd: null, fraction, openedAt: this.clock.now() };
|
|
240
|
+
this.note('agent.paper.close', { mint: input.mint, fraction });
|
|
241
|
+
const order = await this.buildOrder('close', t);
|
|
242
|
+
const rec = await this.book('close', order, { mint: input.mint });
|
|
243
|
+
return { fill: rec, position: this.positionView(this.wallet.position(input.mint)) };
|
|
244
|
+
}
|
|
245
|
+
/** Build the paper order from a ticket, re-checking the buy caps against the live wallet at execute time. */
|
|
246
|
+
async buildOrder(stage, t) {
|
|
247
|
+
if (t.side === 'buy') {
|
|
248
|
+
if (!isFiniteNum(t.usd) || t.usd <= 0)
|
|
249
|
+
this.refuse(stage, 'not-sized', 'the ticket has no size — call size first', { ticketId: t.id });
|
|
250
|
+
const solUsd = this.price(stage, { ticketId: t.id });
|
|
251
|
+
this.admitBuy(stage, t.mint, t.usd, solUsd, { ticketId: t.id });
|
|
252
|
+
const amountRaw = solToLamports(t.usd / solUsd);
|
|
253
|
+
if (amountRaw <= 0n)
|
|
254
|
+
this.refuse(stage, 'bad-request', 'the sized amount rounds to zero lamports', { ticketId: t.id });
|
|
255
|
+
return { mint: t.mint, symbol: t.symbol, side: 'buy', decimals: t.decimals, amountRaw, slippageBps: this.mandate.limits.maxSlippageBps };
|
|
256
|
+
}
|
|
257
|
+
const pos = this.wallet.position(t.mint);
|
|
258
|
+
if (!pos || pos.tokensRaw <= 0n)
|
|
259
|
+
this.refuse(stage, 'no-position', 'no paper tokens to sell', { ticketId: t.id });
|
|
260
|
+
const frac = t.fraction ?? 1;
|
|
261
|
+
let amountRaw = BigInt(Math.round(Number(pos.tokensRaw) * frac));
|
|
262
|
+
if (amountRaw > pos.tokensRaw)
|
|
263
|
+
amountRaw = pos.tokensRaw;
|
|
264
|
+
if (amountRaw <= 0n)
|
|
265
|
+
this.refuse(stage, 'bad-request', 'the sell fraction rounds to zero tokens', { ticketId: t.id });
|
|
266
|
+
return { mint: t.mint, symbol: pos.symbol, side: 'sell', decimals: pos.decimals, amountRaw, slippageBps: this.mandate.limits.maxSlippageBps };
|
|
267
|
+
}
|
|
268
|
+
/** Fetch a fresh quote and book the fill through the PAPER executor; journal the booked fill. */
|
|
269
|
+
async book(stage, order, extra) {
|
|
270
|
+
let quote;
|
|
271
|
+
try {
|
|
272
|
+
quote = await this.quote(buildQuoteRequest(order));
|
|
273
|
+
}
|
|
274
|
+
catch (e) {
|
|
275
|
+
this.refuse(stage, 'quote-failed', `could not price the trade: ${e instanceof Error ? e.message : String(e)}`, extra);
|
|
276
|
+
}
|
|
277
|
+
// Buys honour the mandate's price-impact ceiling (SwapQuote.priceImpactPct is already a PERCENT — the source
|
|
278
|
+
// converts Jupiter's 0..1 fraction once). Exits are never trapped by it, like the plan engine's stop leg.
|
|
279
|
+
if (order.side === 'buy' && isFiniteNum(quote.priceImpactPct) && quote.priceImpactPct > this.mandate.limits.maxPriceImpactPct + 1e-9) {
|
|
280
|
+
this.refuse(stage, 'price-impact', `price impact ${quote.priceImpactPct.toFixed(2)}% exceeds the mandate max ${this.mandate.limits.maxPriceImpactPct}%`, extra);
|
|
281
|
+
}
|
|
282
|
+
// The control surface may have flipped while the quote was in flight: re-check right before booking.
|
|
283
|
+
this.gate(stage, extra);
|
|
284
|
+
let rec;
|
|
285
|
+
try {
|
|
286
|
+
rec = await this.executor.execute(order, quote);
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
this.refuse(stage, 'fill-failed', e instanceof Error ? e.message : String(e), extra);
|
|
290
|
+
}
|
|
291
|
+
this.note('agent.paper.fill', {
|
|
292
|
+
...extra, mint: rec.mint, symbol: rec.symbol, side: rec.side, paper: true,
|
|
293
|
+
solDelta: rec.solDelta.toString(), tokenDelta: rec.tokenDelta.toString(),
|
|
294
|
+
priceSol: rec.priceSol, priceUsd: rec.priceUsd, realizedLamports: rec.realizedLamports.toString(), fillId: rec.id,
|
|
295
|
+
});
|
|
296
|
+
return rec;
|
|
297
|
+
}
|
|
298
|
+
// ───────────────────────────── the two reads ─────────────────────────────
|
|
299
|
+
positionView(pos) {
|
|
300
|
+
if (!pos)
|
|
301
|
+
return null;
|
|
302
|
+
const tokens = rawToTokens(pos.tokensRaw, pos.decimals);
|
|
303
|
+
const costSol = lamportsToSol(pos.costLamports);
|
|
304
|
+
return {
|
|
305
|
+
mint: pos.mint, symbol: pos.symbol, decimals: pos.decimals, tokens,
|
|
306
|
+
costSol, realizedSol: lamportsToSol(pos.realizedLamports),
|
|
307
|
+
avgEntrySol: tokens > 0 ? costSol / tokens : null,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** Read paper positions (cost basis + realized; the caller may layer a live mark). Read-only. */
|
|
311
|
+
positions() {
|
|
312
|
+
return this.wallet.positions().map((p) => this.positionView(p)).filter(Boolean);
|
|
313
|
+
}
|
|
314
|
+
/** Read the paper PnL snapshot (balance, realized, open cost, per-position). Read-only, always PAPER. */
|
|
315
|
+
pnl() {
|
|
316
|
+
const s = this.wallet.summary();
|
|
317
|
+
const m = this.mandateSrc();
|
|
318
|
+
const positions = this.positions();
|
|
319
|
+
const realizedSol = positions.reduce((a, p) => a + p.realizedSol, 0);
|
|
320
|
+
const openCostSol = positions.reduce((a, p) => a + p.costSol, 0);
|
|
321
|
+
return {
|
|
322
|
+
paper: true,
|
|
323
|
+
balanceSol: lamportsToSol(s.balanceLamports),
|
|
324
|
+
startSol: lamportsToSol(s.startLamports),
|
|
325
|
+
realizedSol,
|
|
326
|
+
openCostSol,
|
|
327
|
+
positions,
|
|
328
|
+
mandate: m ? {
|
|
329
|
+
id: m.id, capsHash: capsHash(m), perTradeUsd: m.budget.perTradeUsd * TIERS[AGENT_TIER].perTradeMultiplier, perCoinUsd: m.budget.perCoinUsd,
|
|
330
|
+
totalUsd: m.budget.totalUsd, dailyLossUsd: m.budget.dailyLossUsd, maxOpenPositions: m.limits.maxOpenPositions,
|
|
331
|
+
maxPriceImpactPct: m.limits.maxPriceImpactPct, expiresAt: m.expiresAt,
|
|
332
|
+
} : null,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
export { SOL_MINT };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// task-1443 — the rendezvous a running dexterm writes so an EXTERNAL `dexterm mcp` server (spawned by the
|
|
2
|
+
// user's own agent: Claude Code, Cursor, a custom bot) may paper-trade inside an armed mandate.
|
|
3
|
+
//
|
|
4
|
+
// Agent paper trading is OFF unless the user turns it on. When the user starts the app with the spectator
|
|
5
|
+
// view open and names a mandate they ARMED by keypress (`--spectate-mcp --agent-paper <mandate-id>`), the app
|
|
6
|
+
// writes `~/.dexterm/paper-agent.json` naming the shared paper wallet DB and the mandate ID — never the caps.
|
|
7
|
+
// The MCP side REPLAYS that mandate from the hash-chained plans journal the app armed it in (verifying the
|
|
8
|
+
// chain and the caps hash), so editing the rendezvous cannot raise a cap, and a revoke / panic in the app is
|
|
9
|
+
// seen on the next call. A companion `~/.dexterm/paper-agent.control` carries the live control state (running /
|
|
10
|
+
// paused / stopped / takeover), re-read on every mutating call. Agent calls journal to their OWN chain
|
|
11
|
+
// (`paper-agent.journal`), so the app's plans journal keeps a single writer. All files are local and 0600;
|
|
12
|
+
// nothing here is a key, a signer or a send path.
|
|
13
|
+
import { readFileSync, writeFileSync, rmSync, mkdirSync, existsSync, statSync } from 'node:fs';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
16
|
+
import { Journal } from "../plans/journal.js";
|
|
17
|
+
import { capsHash } from "../plans/mandate.js";
|
|
18
|
+
export const PAPER_AGENT_RENDEZVOUS = 'paper-agent.json';
|
|
19
|
+
export const PAPER_AGENT_CONTROL = 'paper-agent.control';
|
|
20
|
+
export const PAPER_AGENT_JOURNAL = 'paper-agent.journal';
|
|
21
|
+
function home(env) {
|
|
22
|
+
return env.DEXTERM_HOME && env.DEXTERM_HOME.trim() ? env.DEXTERM_HOME : join(homedir(), '.dexterm');
|
|
23
|
+
}
|
|
24
|
+
export function paperAgentRendezvousPath(env = process.env) { return join(home(env), PAPER_AGENT_RENDEZVOUS); }
|
|
25
|
+
export function paperAgentControlPath(env = process.env) { return join(home(env), PAPER_AGENT_CONTROL); }
|
|
26
|
+
export function paperAgentJournalPath(env = process.env) { return join(home(env), PAPER_AGENT_JOURNAL); }
|
|
27
|
+
/** The plans/mandate journal (same location as plans/service.ts `defaultPlansJournalPath`, which this module
|
|
28
|
+
* deliberately does not import: the service graph reaches the live executor). Never taken from the rendezvous. */
|
|
29
|
+
export function plansJournalPathFor(env = process.env) { return join(home(env), 'plans.journal'); }
|
|
30
|
+
/** Turn agent paper trading ON: write the rendezvous (0600) and a `running` control state. */
|
|
31
|
+
export function writePaperAgentRendezvous(rv, env = process.env) {
|
|
32
|
+
const p = paperAgentRendezvousPath(env);
|
|
33
|
+
mkdirSync(dirname(p), { recursive: true, mode: 0o700 });
|
|
34
|
+
writeFileSync(p, JSON.stringify({ v: 2, db: rv.db, mandateId: rv.mandateId, ...(rv.agent ? { agent: rv.agent } : {}) }), { mode: 0o600 });
|
|
35
|
+
writeDeskControl({ state: 'running' }, env);
|
|
36
|
+
}
|
|
37
|
+
/** Turn it OFF: remove both files. Idempotent. */
|
|
38
|
+
export function clearPaperAgentRendezvous(env = process.env) {
|
|
39
|
+
rmSync(paperAgentRendezvousPath(env), { force: true });
|
|
40
|
+
rmSync(paperAgentControlPath(env), { force: true });
|
|
41
|
+
}
|
|
42
|
+
/** Read the rendezvous, or null when agent paper trading is not enabled (or the file is malformed). Never throws. */
|
|
43
|
+
export function readPaperAgentRendezvous(env = process.env) {
|
|
44
|
+
const p = paperAgentRendezvousPath(env);
|
|
45
|
+
if (!existsSync(p))
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const o = JSON.parse(readFileSync(p, 'utf8'));
|
|
49
|
+
if (!o || o.v !== 2 || typeof o.db !== 'string' || !o.db || typeof o.mandateId !== 'string' || !o.mandateId)
|
|
50
|
+
return null;
|
|
51
|
+
return { v: 2, db: o.db, mandateId: o.mandateId, ...(typeof o.agent === 'string' ? { agent: o.agent.slice(0, 40) } : {}) };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const VALID_STATES = ['running', 'paused', 'stopped', 'takeover'];
|
|
58
|
+
/** Write the live control state (0600). The app calls this on pause / take-over / stop / resume. */
|
|
59
|
+
export function writeDeskControl(control, env = process.env) {
|
|
60
|
+
const p = paperAgentControlPath(env);
|
|
61
|
+
mkdirSync(dirname(p), { recursive: true, mode: 0o700 });
|
|
62
|
+
writeFileSync(p, JSON.stringify(control), { mode: 0o600 });
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the control state, re-read on EVERY call (never cached), fail-closed:
|
|
66
|
+
* - file absent → stopped (the app writes `running` when it enables agent paper trading)
|
|
67
|
+
* - a known state → that state
|
|
68
|
+
* - present but broken → stopped
|
|
69
|
+
*/
|
|
70
|
+
export function readDeskControl(env = process.env) {
|
|
71
|
+
const p = paperAgentControlPath(env);
|
|
72
|
+
if (!existsSync(p))
|
|
73
|
+
return { state: 'stopped', reason: 'agent paper trading is not running in the app' };
|
|
74
|
+
try {
|
|
75
|
+
const o = JSON.parse(readFileSync(p, 'utf8'));
|
|
76
|
+
const state = typeof o.state === 'string' && VALID_STATES.includes(o.state) ? o.state : 'stopped';
|
|
77
|
+
return { state, reason: typeof o.reason === 'string' ? o.reason.slice(0, 200) : undefined };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { state: 'stopped', reason: 'control file unreadable (treated as stopped)' };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Freshest mtime of a file (for mtime caches); 0 when absent. */
|
|
84
|
+
export function fileMtimeMs(path) {
|
|
85
|
+
try {
|
|
86
|
+
return statSync(path).mtimeMs;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function rendezvousMtimeMs(env = process.env) { return fileMtimeMs(paperAgentRendezvousPath(env)); }
|
|
93
|
+
/**
|
|
94
|
+
* Replay ONE mandate from the hash-chained plans journal, read-only. Fails closed (null + reason) when the
|
|
95
|
+
* journal is absent, its chain does not verify, the mandate was never drafted, or the caps hash recorded at
|
|
96
|
+
* arming does not match the drafted caps. Status follows the journal: draft → armed → revoked/expired.
|
|
97
|
+
*/
|
|
98
|
+
export function loadMandateFromJournal(path, mandateId, clock) {
|
|
99
|
+
if (!existsSync(path))
|
|
100
|
+
return { mandate: null, reason: 'no plans journal (arm a mandate first)' };
|
|
101
|
+
let j;
|
|
102
|
+
try {
|
|
103
|
+
j = new Journal({ path, clock });
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return { mandate: null, reason: 'plans journal unreadable' };
|
|
107
|
+
}
|
|
108
|
+
const v = j.verify();
|
|
109
|
+
if (!v.ok)
|
|
110
|
+
return { mandate: null, reason: `plans journal chain broken at seq ${v.atSeq} (${v.why})` };
|
|
111
|
+
let m = null;
|
|
112
|
+
for (const e of j.events) {
|
|
113
|
+
if (e.mandateId !== mandateId)
|
|
114
|
+
continue;
|
|
115
|
+
const d = (e.data ?? {});
|
|
116
|
+
if (e.type === 'mandate.draft') {
|
|
117
|
+
const drafted = structuredClone(d.mandate);
|
|
118
|
+
if (!drafted || drafted.id !== mandateId || capsHash(drafted) !== d.capsHash)
|
|
119
|
+
return { mandate: null, reason: 'mandate draft does not match its caps hash' };
|
|
120
|
+
m = drafted;
|
|
121
|
+
}
|
|
122
|
+
else if (e.type === 'mandate.armed' && m) {
|
|
123
|
+
if (d.capsHash !== capsHash(m))
|
|
124
|
+
return { mandate: null, reason: 'mandate armed with different caps than drafted' };
|
|
125
|
+
m.status = 'armed';
|
|
126
|
+
m.armedAt = d.armedAt;
|
|
127
|
+
m.armedBy = d.armedBy;
|
|
128
|
+
}
|
|
129
|
+
else if (e.type === 'mandate.revoked' && m) {
|
|
130
|
+
m.status = d.status ?? 'revoked';
|
|
131
|
+
m.revokedAt = e.ts;
|
|
132
|
+
m.revokedReason = d.reason ?? null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!m)
|
|
136
|
+
return { mandate: null, reason: `no mandate ${mandateId} in the plans journal` };
|
|
137
|
+
Object.freeze(m.budget);
|
|
138
|
+
Object.freeze(m.limits);
|
|
139
|
+
Object.freeze(m.fees);
|
|
140
|
+
Object.freeze(m.allowed);
|
|
141
|
+
return { mandate: Object.freeze(m) };
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A journal that may have several writers (one per `dexterm mcp` process): it re-loads the chain from disk
|
|
145
|
+
* before every append so each event links to the true head. Append-only, 0600 directory, same hash chain and
|
|
146
|
+
* `redact()` as the plans journal.
|
|
147
|
+
*/
|
|
148
|
+
export class SharedJournal {
|
|
149
|
+
path;
|
|
150
|
+
j;
|
|
151
|
+
constructor(path, clock) { this.path = path; this.j = new Journal({ path, clock }); }
|
|
152
|
+
append(type, fields = {}) {
|
|
153
|
+
if (existsSync(this.path))
|
|
154
|
+
this.j.load(this.path);
|
|
155
|
+
return this.j.append(type, fields);
|
|
156
|
+
}
|
|
157
|
+
verify() { if (existsSync(this.path))
|
|
158
|
+
this.j.load(this.path); return this.j.verify(); }
|
|
159
|
+
types() { if (existsSync(this.path))
|
|
160
|
+
this.j.load(this.path); return this.j.types(); }
|
|
161
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: agent-paper-trade
|
|
3
|
+
title: Let an agent paper trade
|
|
4
|
+
kind: app-help
|
|
5
|
+
screen: all
|
|
6
|
+
feature: mcp
|
|
7
|
+
tags:
|
|
8
|
+
- app-help
|
|
9
|
+
- agent
|
|
10
|
+
- mcp
|
|
11
|
+
- paper
|
|
12
|
+
- autonomous
|
|
13
|
+
- experimental
|
|
14
|
+
- confirm
|
|
15
|
+
- close
|
|
16
|
+
- simulated
|
|
17
|
+
flags:
|
|
18
|
+
- --agent-paper
|
|
19
|
+
tools:
|
|
20
|
+
- paper_open_ticket
|
|
21
|
+
- paper_size_ticket
|
|
22
|
+
- paper_confirm
|
|
23
|
+
- paper_close
|
|
24
|
+
- paper_positions
|
|
25
|
+
- paper_pnl
|
|
26
|
+
confidence: high
|
|
27
|
+
owner: maintainers
|
|
28
|
+
origin: app-help-generated
|
|
29
|
+
reviewed_by: generated@2026-10-16
|
|
30
|
+
generated_from: dexterm/app keymap + overlay + mcp tools + cli (task-1413)
|
|
31
|
+
sources:
|
|
32
|
+
- packages/tui/src/keymap.ts
|
|
33
|
+
- packages/cli/src/main.ts
|
|
34
|
+
- packages/mcp/src/server.ts
|
|
35
|
+
date: 2026-10-16
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
Experimental, paper only, off by default. Arm a mandate yourself (its caps, by keypress), then start `dexterm --spectate-mcp --agent-paper <mandate-id>`: an agent connected through `dexterm mcp` can then open, size, confirm and close SIMULATED (paper) trades inside those caps with paper_open_ticket / paper_size_ticket / paper_confirm / paper_close, and read paper_positions / paper_pnl. The agent is untrusted: it can never sign, send a real transaction, touch keys, or raise a cap; its per-trade cap is a quarter of the mandate's, and a size over any cap is refused. Caps are re-read from your armed mandate on every call, so revoking it or the panic key stops the agent. Every call is journaled and shows in this terminal's agent activity; Ctrl+P pauses, Ctrl+K stops for the rest of the run, any key takes over, and quitting turns it off. Figures are simulated and are not a result.
|
|
@@ -38,6 +38,6 @@ sources:
|
|
|
38
38
|
date: 2026-10-16
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
The analyst is your own `claude` login. ⏎ on a coin runs a full dossier; `a` runs a rug-check (or a trench-scan of the screener when nothing is selected). Type a follow-up and ⏎ to send. If `claude` is missing or logged out the panel shows the one-line fix and the rest of dexterm keeps working. Start without AI using `--no-ai`.
|
|
41
|
+
The analyst is your own `claude` login (or your own `codex` login: `/engine codex`, see "Run the analyst on Codex"). ⏎ on a coin runs a full dossier; `a` runs a rug-check (or a trench-scan of the screener when nothing is selected). Type a follow-up and ⏎ to send. If `claude` is missing or logged out the panel shows the one-line fix and the rest of dexterm keeps working. Start without AI using `--no-ai`.
|
|
42
42
|
|
|
43
43
|
Keys: `a`, `⏎` (screener).
|
|
@@ -34,6 +34,6 @@ sources:
|
|
|
34
34
|
date: 2026-10-16
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
`dexterm mcp` is a plain stdio MCP server exposing the same read-only tools the analyst uses. Add it to any Claude Code with `claude mcp add dexterm -- node packages/mcp/bin/dexterm-mcp.mjs
|
|
37
|
+
`dexterm mcp` is a plain stdio MCP server exposing the same read-only tools the analyst uses. Add it to any Claude Code with `claude mcp add dexterm -- node packages/mcp/bin/dexterm-mcp.mjs`, or set up Claude Code, Claude Desktop, Cursor or Codex in one command with `dexterm mcp install <client>`. Agents are untrusted: every tool is read-only and an agent can never sign, arm, raise a cap or touch keys. Use `app_help` for "how do I" questions and `app_state` to see what the user sees.
|
|
38
38
|
|
|
39
39
|
CLI: `dexterm mcp`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: dev-history
|
|
3
|
+
title: Check if a coin's creator rugged before (dev history)
|
|
4
|
+
kind: app-help
|
|
5
|
+
screen: token
|
|
6
|
+
feature: token-intel
|
|
7
|
+
tags:
|
|
8
|
+
- app-help
|
|
9
|
+
- dev
|
|
10
|
+
- deployer
|
|
11
|
+
- creator
|
|
12
|
+
- serial
|
|
13
|
+
- rugger
|
|
14
|
+
- rugged
|
|
15
|
+
- history
|
|
16
|
+
- launches
|
|
17
|
+
tools:
|
|
18
|
+
- dev_history
|
|
19
|
+
- get_dev_history
|
|
20
|
+
confidence: high
|
|
21
|
+
owner: maintainers
|
|
22
|
+
origin: app-help-generated
|
|
23
|
+
reviewed_by: generated@2026-10-16
|
|
24
|
+
generated_from: dexterm/app keymap + overlay + mcp tools + cli (task-1413)
|
|
25
|
+
sources:
|
|
26
|
+
- packages/tui/src/keymap.ts
|
|
27
|
+
- packages/cli/src/main.ts
|
|
28
|
+
- packages/mcp/src/server.ts
|
|
29
|
+
date: 2026-10-16
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
The token screen's ON-CHAIN panel shows the creator's recorded track record (0.1.5): `Dev rugs (recorded)` (rugs out of labelled prior launches, with the sample n), `Dev launches seen`, `Dev sold its bag` (median minutes to the dev's exit) and `Dev linked` (funder or cluster from the wallet-intel scan). It comes from dexterm's own local history recorder (keyless creator links + code-only outcome labels), so it says `unknown` when there is no record; it is a risk flag from observed history, never a certificate. Alert on it with the rule field `devrugs`, e.g. `when devrugs >= 2 then alert(warn, "new coin from a wallet that rugged before") scope newpairs`. Agents and the analyst use the `dev_history` MCP tool.
|
|
@@ -31,6 +31,6 @@ sources:
|
|
|
31
31
|
date: 2026-10-16
|
|
32
32
|
---
|
|
33
33
|
|
|
34
|
-
Press `B` / `S` on the token screen to open a PAPER buy/sell ticket in its editing phase. dexterm holds no keys and signs nothing — paper trading is a simulation. Confirming a ticket needs your explicit keypress (the gate); a stray key never confirms. The analyst can open a PAPER ticket but can never size, confirm or send it.
|
|
34
|
+
Press `B` / `S` on the token screen to open a PAPER buy/sell ticket in its editing phase. dexterm holds no keys and signs nothing — paper trading is a simulation. Confirming a ticket needs your explicit keypress (the gate); a stray key never confirms. The in-app analyst can open a PAPER ticket but can never size, confirm or send it. If you separately arm a mandate and enable agent paper trading, a connected agent can open, size, confirm and close PAPER trades inside your caps (never raising them, never signing) — see "Let an agent paper trade".
|
|
35
35
|
|
|
36
36
|
Keys: `B` (token), `S` (token).
|
|
@@ -116,6 +116,9 @@ actions:
|
|
|
116
116
|
- workspace.popups
|
|
117
117
|
- workspace.default
|
|
118
118
|
- screen.rules
|
|
119
|
+
tools:
|
|
120
|
+
- paper_positions
|
|
121
|
+
- paper_pnl
|
|
119
122
|
confidence: high
|
|
120
123
|
owner: maintainers
|
|
121
124
|
origin: app-help-generated
|
|
@@ -127,7 +130,7 @@ sources:
|
|
|
127
130
|
date: 2026-10-16
|
|
128
131
|
---
|
|
129
132
|
|
|
130
|
-
Your simulated (PAPER) positions and their P&L. dexterm holds no keys and signs nothing; paper trading is a simulation for learning the flow.
|
|
133
|
+
Your simulated (PAPER) positions and their P&L. dexterm holds no keys and signs nothing; paper trading is a simulation for learning the flow. An agent you enabled can read these over MCP with the read-only paper_positions and paper_pnl tools.
|
|
131
134
|
|
|
132
135
|
Keys on this screen:
|
|
133
136
|
`q/^C` — quit
|
|
@@ -15,10 +15,13 @@ tags:
|
|
|
15
15
|
- replay
|
|
16
16
|
- stream
|
|
17
17
|
- record
|
|
18
|
+
- mcp
|
|
19
|
+
- follow
|
|
18
20
|
commands:
|
|
19
21
|
- spectate
|
|
20
22
|
flags:
|
|
21
23
|
- --replay
|
|
24
|
+
- --spectate-mcp
|
|
22
25
|
confidence: high
|
|
23
26
|
owner: maintainers
|
|
24
27
|
origin: app-help-generated
|
|
@@ -31,6 +34,6 @@ sources:
|
|
|
31
34
|
date: 2026-10-16
|
|
32
35
|
---
|
|
33
36
|
|
|
34
|
-
`dexterm
|
|
37
|
+
Watch an agent work. For an agent on the loopback bus, `dexterm agent serve --spectator-popup` opens the AGENT ACTIVITY window, `--spectate` starts the read-only, token-gated, loopback stream, and `--record FILE` saves a redacted session. For an agent connected over MCP (`dexterm mcp install`), run the app with `dexterm --spectate-mcp`: the running terminal then follows the agent — a navigation call moves the screen, a status line shows `agent <name>: opened <coin>`, ctrl+p pauses, ctrl+k stops and any key you press takes control. `dexterm spectate --replay FILE` replays a saved session step by step and `dexterm spectate --info` shows the live stream endpoint. Every shared copy is redacted — a tool name, refusal, agent line or navigation can never leak a wallet or key — and a spectator can only watch, never act. Full docs: https://dexterm.xyz/docs/spectator/
|
|
35
38
|
|
|
36
39
|
CLI: `dexterm spectate`.
|