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,255 @@
|
|
|
1
|
+
// `dexterm` TUI — follow an agent that is driving over MCP (task-1421, "see what your agent is doing").
|
|
2
|
+
//
|
|
3
|
+
// The running terminal can WATCH and visibly FOLLOW an agent connected through `dexterm mcp` (Claude Code,
|
|
4
|
+
// Cursor, Codex, Claude Desktop, …). That MCP server is a separate process the external client spawned, so
|
|
5
|
+
// the app cannot set its env; instead this session, while it is open, writes the ~/.dexterm rendezvous the
|
|
6
|
+
// server reads (packages/mcp spectator-sink) and tails the JSONL channel the server appends to. Each record
|
|
7
|
+
// is folded into a SpectatorHub → a FollowController (the AGENT ACTIVITY panel + a SCREEN model) and, for
|
|
8
|
+
// navigation calls, moved into the REAL terminal through the caller's onNav hook (open a token, switch
|
|
9
|
+
// screen/feed/filter, highlight). Owner's own machine → follow mode is unredacted; the optional read-only
|
|
10
|
+
// stream (SpectatorServer) redacts every copy that leaves the hub, exactly as in task-1406.
|
|
11
|
+
//
|
|
12
|
+
// It adds NO power. A record only DESCRIBES a read/navigation call the gate already allowed; there is no
|
|
13
|
+
// inbound path from here to anything that can arm, sign, confirm, send or raise a cap. "pause / take over /
|
|
14
|
+
// kill" are local follow controls: pause/take-over stop the terminal mirroring the agent (the external agent
|
|
15
|
+
// keeps running — we simply stop moving the screen), and kill removes the rendezvous so the server stops
|
|
16
|
+
// emitting. Any user keypress during an agent move takes control, so the human and the agent never fight.
|
|
17
|
+
import { writeFileSync, readFileSync, rmSync, mkdirSync, statSync } from 'node:fs';
|
|
18
|
+
import { dirname } from 'node:path';
|
|
19
|
+
import { randomBytes } from 'node:crypto';
|
|
20
|
+
import { SpectatorHub, FollowController, SpectatorServer, } from '../../agents/src/index.js';
|
|
21
|
+
import { rendezvousPath, writeFollowScreen, clearFollowScreen } from '../../mcp/src/index.js';
|
|
22
|
+
function navLabel(agent, nav) {
|
|
23
|
+
const coin = nav.mint ? ` ${nav.mint.length > 10 ? nav.mint.slice(0, 4) + '…' + nav.mint.slice(-4) : nav.mint}` : '';
|
|
24
|
+
if (nav.screen === 'token')
|
|
25
|
+
return `agent ${agent}: opened${coin}`;
|
|
26
|
+
if (nav.screen === 'ticket')
|
|
27
|
+
return `agent ${agent}: opened the ${nav.side ?? ''} ticket${coin}`;
|
|
28
|
+
if (nav.screen === 'watchlist')
|
|
29
|
+
return `agent ${agent}: watchlisted${coin}`;
|
|
30
|
+
if (nav.feed)
|
|
31
|
+
return `agent ${agent}: switched the feed to ${nav.feed}`;
|
|
32
|
+
if (nav.filter)
|
|
33
|
+
return `agent ${agent}: filtered ${nav.filter}`;
|
|
34
|
+
if (nav.highlight)
|
|
35
|
+
return `agent ${agent}: highlighted ${nav.highlight.length > 10 ? nav.highlight.slice(0, 4) + '…' + nav.highlight.slice(-4) : nav.highlight}`;
|
|
36
|
+
if (nav.screen)
|
|
37
|
+
return `agent ${agent}: switched to ${nav.screen}`;
|
|
38
|
+
return `agent ${agent}: navigated`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A follow session for one running terminal. Call start() to arm the rendezvous and begin tailing, pump()
|
|
42
|
+
* to process new records (the timer does this for you), the controls to pause/take-over/kill, userKey() when
|
|
43
|
+
* the human presses any key, and stop() to tear everything down. Never throws into the key loop.
|
|
44
|
+
*/
|
|
45
|
+
export class McpFollowSession {
|
|
46
|
+
hub;
|
|
47
|
+
follow;
|
|
48
|
+
opts;
|
|
49
|
+
rvPath;
|
|
50
|
+
channelPath;
|
|
51
|
+
token;
|
|
52
|
+
agentLabel;
|
|
53
|
+
currentAgent;
|
|
54
|
+
offset = 0;
|
|
55
|
+
timer = null;
|
|
56
|
+
stream = null;
|
|
57
|
+
armed = false;
|
|
58
|
+
paused = false;
|
|
59
|
+
lastSnapshot = 0;
|
|
60
|
+
constructor(opts) {
|
|
61
|
+
this.opts = opts;
|
|
62
|
+
this.agentLabel = opts.agentLabel || 'mcp-agent';
|
|
63
|
+
this.currentAgent = this.agentLabel;
|
|
64
|
+
this.rvPath = rendezvousPath(opts.env);
|
|
65
|
+
this.channelPath = this.rvPath.replace(/\.json$/, '.jsonl');
|
|
66
|
+
this.token = randomBytes(24).toString('hex');
|
|
67
|
+
this.hub = new SpectatorHub({ cap: Math.max(1, opts.activityCap ?? 200) });
|
|
68
|
+
const controls = {
|
|
69
|
+
pauseAgent: () => true,
|
|
70
|
+
resumeAgent: () => { this.rearm(); return true; },
|
|
71
|
+
kickAgent: () => { this.disarm(); return 1; },
|
|
72
|
+
};
|
|
73
|
+
this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
|
|
74
|
+
this.hub.on((ev) => this.follow.apply(ev));
|
|
75
|
+
}
|
|
76
|
+
/** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
|
|
77
|
+
async start() {
|
|
78
|
+
this.rearm();
|
|
79
|
+
if (this.opts.stream) {
|
|
80
|
+
try {
|
|
81
|
+
this.stream = new SpectatorServer({ hub: this.hub, env: this.opts.env });
|
|
82
|
+
await this.stream.start();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
this.stream = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!this.timer) {
|
|
89
|
+
this.timer = setInterval(() => { this.pump(); this.publish(); }, Math.max(30, this.opts.pollMs ?? 150));
|
|
90
|
+
this.timer.unref?.();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Publish the follow-mode screen snapshot (throttled to ~1/s; only while armed). Never throws. */
|
|
94
|
+
publish(force = false) {
|
|
95
|
+
if (!this.opts.snapshot || !this.armed)
|
|
96
|
+
return;
|
|
97
|
+
const now = (this.opts.now ?? Date.now)();
|
|
98
|
+
if (!force && now - this.lastSnapshot < 1000)
|
|
99
|
+
return;
|
|
100
|
+
this.lastSnapshot = now;
|
|
101
|
+
try {
|
|
102
|
+
writeFollowScreen(this.opts.env, this.opts.snapshot());
|
|
103
|
+
}
|
|
104
|
+
catch { /* fail closed: stale → nav refused */ }
|
|
105
|
+
}
|
|
106
|
+
/** Write (or re-write) the channel + rendezvous so the server emits here. */
|
|
107
|
+
rearm() {
|
|
108
|
+
try {
|
|
109
|
+
mkdirSync(dirname(this.rvPath), { recursive: true });
|
|
110
|
+
if (!this.armed) {
|
|
111
|
+
writeFileSync(this.channelPath, '', { mode: 0o600 });
|
|
112
|
+
this.offset = 0;
|
|
113
|
+
}
|
|
114
|
+
writeFileSync(this.rvPath, JSON.stringify({ file: this.channelPath, token: this.token, agent: this.agentLabel }) + '\n', { mode: 0o600 });
|
|
115
|
+
this.armed = true;
|
|
116
|
+
}
|
|
117
|
+
catch { /* best-effort local channel */ }
|
|
118
|
+
}
|
|
119
|
+
/** Remove the rendezvous so the server stops emitting (the external agent keeps running; we stop seeing it). */
|
|
120
|
+
disarm() {
|
|
121
|
+
try {
|
|
122
|
+
// A second follow surface may have replaced the rendezvous after this session started. Never remove
|
|
123
|
+
// another surface's live handle when this one pauses, is killed, or exits.
|
|
124
|
+
const current = JSON.parse(readFileSync(this.rvPath, 'utf8'));
|
|
125
|
+
if (current.token === this.token) {
|
|
126
|
+
rmSync(this.rvPath, { force: true });
|
|
127
|
+
if (this.opts.snapshot)
|
|
128
|
+
clearFollowScreen(this.opts.env);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch { /* absent/malformed/another surface owns it */ }
|
|
132
|
+
this.armed = false;
|
|
133
|
+
}
|
|
134
|
+
/** Read and fold any records appended since the last pump. Public so tests can drive it without timers. */
|
|
135
|
+
pump() {
|
|
136
|
+
let text;
|
|
137
|
+
try {
|
|
138
|
+
const st = statSync(this.channelPath);
|
|
139
|
+
if (st.size <= this.offset) {
|
|
140
|
+
if (st.size < this.offset)
|
|
141
|
+
this.offset = 0;
|
|
142
|
+
else
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
text = readFileSync(this.channelPath, 'utf8');
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const slice = text.slice(this.offset);
|
|
151
|
+
const nl = slice.lastIndexOf('\n');
|
|
152
|
+
if (nl < 0)
|
|
153
|
+
return; // no complete line yet
|
|
154
|
+
this.offset += nl + 1;
|
|
155
|
+
for (const line of slice.slice(0, nl).split('\n')) {
|
|
156
|
+
const t = line.trim();
|
|
157
|
+
if (!t)
|
|
158
|
+
continue;
|
|
159
|
+
let rec;
|
|
160
|
+
try {
|
|
161
|
+
rec = JSON.parse(t);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
this.ingest(rec);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/** Fold one record: verify its token, strip it, emit into the hub, and move the terminal for a nav call. */
|
|
170
|
+
ingest(rec) {
|
|
171
|
+
if (typeof rec.phase !== 'string')
|
|
172
|
+
return;
|
|
173
|
+
// Only accept records written by the server we handed our token to (local 0600 file; defence in depth).
|
|
174
|
+
if (rec.token !== this.token)
|
|
175
|
+
return;
|
|
176
|
+
this.currentAgent = typeof rec.agent === 'string' && rec.agent ? rec.agent : this.agentLabel;
|
|
177
|
+
// The token NEVER enters the hub / activity / stream — fold only the observable fields.
|
|
178
|
+
const ev = this.hub.emit({
|
|
179
|
+
phase: rec.phase,
|
|
180
|
+
agent: this.currentAgent,
|
|
181
|
+
transport: 'mcp',
|
|
182
|
+
tool: rec.tool ?? null,
|
|
183
|
+
summary: rec.summary,
|
|
184
|
+
ms: rec.ms ?? null,
|
|
185
|
+
dataAgeSec: rec.dataAgeSec ?? null,
|
|
186
|
+
nav: rec.nav ?? null,
|
|
187
|
+
ok: rec.ok ?? true,
|
|
188
|
+
t: typeof rec.t === 'number' ? rec.t : undefined,
|
|
189
|
+
});
|
|
190
|
+
// Move the real terminal ONLY while the agent has control (a user keypress / take-over / kill flips this).
|
|
191
|
+
if (ev.phase === 'nav' && ev.ok !== false && ev.nav && this.canFollow()) {
|
|
192
|
+
const label = navLabel(this.currentAgent, ev.nav);
|
|
193
|
+
try {
|
|
194
|
+
this.opts.onNav?.(ev.nav, label);
|
|
195
|
+
}
|
|
196
|
+
catch { /* a bad hook never breaks the tail */ }
|
|
197
|
+
try {
|
|
198
|
+
this.opts.onStatus?.(label);
|
|
199
|
+
}
|
|
200
|
+
catch { /* ignore */ }
|
|
201
|
+
}
|
|
202
|
+
// task-1443: every agent PAPER write (fill, close, size, or a refusal) is toasted in the terminal, whatever the
|
|
203
|
+
// follow state, so the watcher always sees what the agent did to the simulated ledger.
|
|
204
|
+
if (typeof ev.tool === 'string' && /^paper_(open_ticket|size_ticket|confirm|close)$/.test(ev.tool)) {
|
|
205
|
+
const what = ev.ok === false ? `PAPER ${ev.tool.slice(6).replace('_', ' ')} refused · ${String(ev.summary ?? '').replace(/^error: /, '').slice(0, 70)}` : String(ev.summary ?? ev.tool);
|
|
206
|
+
try {
|
|
207
|
+
this.opts.onStatus?.(`agent ${this.currentAgent}: ${what}`);
|
|
208
|
+
}
|
|
209
|
+
catch { /* ignore */ }
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
this.opts.onEvent?.();
|
|
213
|
+
}
|
|
214
|
+
catch { /* ignore */ }
|
|
215
|
+
}
|
|
216
|
+
/** True when the terminal should mirror the agent: the agent still has control and is active. */
|
|
217
|
+
canFollow() {
|
|
218
|
+
if (this.follow.controlState !== 'agent')
|
|
219
|
+
return false;
|
|
220
|
+
const st = this.follow.view().agents.find((a) => a.name === this.currentAgent);
|
|
221
|
+
return !st || st.status === 'active';
|
|
222
|
+
}
|
|
223
|
+
// ----- the owner's controls. Each returns a short line to toast. -----
|
|
224
|
+
pause() { const l = this.follow.pause(this.currentAgent); this.paused = l.startsWith('paused '); this.control('paused'); this.opts.onStatus?.(l); return l; }
|
|
225
|
+
takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.control('takeover'); this.opts.onStatus?.(l); return l; }
|
|
226
|
+
resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.control('running'); this.opts.onStatus?.(l); return l; }
|
|
227
|
+
kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.control('stopped'); this.opts.onStatus?.(l); return l; }
|
|
228
|
+
control(state) { try {
|
|
229
|
+
this.opts.onControl?.(state);
|
|
230
|
+
}
|
|
231
|
+
catch { /* never breaks the key loop */ } }
|
|
232
|
+
/** Any physical keypress while the agent is driving: the human takes over (no fight over the screen). */
|
|
233
|
+
userKey() { if (this.follow.controlState === 'agent')
|
|
234
|
+
this.takeOver(); }
|
|
235
|
+
get controlState() { return this.follow.controlState; }
|
|
236
|
+
get isPaused() { return this.paused; }
|
|
237
|
+
get screenModel() { return this.follow.screenModel; }
|
|
238
|
+
get streamPort() { return this.stream?.port ?? 0; }
|
|
239
|
+
view() { return this.follow.view(); }
|
|
240
|
+
/** Tear down: stop the timer, remove the rendezvous, close the stream. Safe to call more than once. */
|
|
241
|
+
async stop() {
|
|
242
|
+
if (this.timer) {
|
|
243
|
+
clearInterval(this.timer);
|
|
244
|
+
this.timer = null;
|
|
245
|
+
}
|
|
246
|
+
this.disarm();
|
|
247
|
+
if (this.stream) {
|
|
248
|
+
try {
|
|
249
|
+
await this.stream.close();
|
|
250
|
+
}
|
|
251
|
+
catch { /* ignore */ }
|
|
252
|
+
this.stream = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -46,6 +46,8 @@ export function buildLiveEngine(env, opts) {
|
|
|
46
46
|
let newpairs = null;
|
|
47
47
|
if (!opts.noNewpairs) {
|
|
48
48
|
newpairs = new NewPairsLive(feed.dl, {});
|
|
49
|
+
// 0.1.5 dev history: keep the creator of every new pair in the recorder's creator index.
|
|
50
|
+
newpairs.onCreator = (mint, creator, source, createdAt) => { recorder.recordCreator(mint, creator, source, undefined, createdAt); };
|
|
49
51
|
const client = new PumpPortalClient({ health: feed.dl.health, clock: feed.dl.clock, onEvent: (ev) => newpairs.handleEvent(ev), onStatus: () => { } });
|
|
50
52
|
newpairs.attach(client, 'live');
|
|
51
53
|
newpairs.start();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { devBookOf } from "./live/dev-history.js";
|
|
1
2
|
import { RulesEngine, ruleToJson, ruleToText, slug, applyAndJournal, describeActions, } from '../../custom/src/index.js';
|
|
2
3
|
import { alertToDelivery, proposalToDelivery } from "./alerts/format.js";
|
|
3
4
|
export const RULE_INPUT_MAX = 400;
|
|
@@ -66,11 +67,14 @@ export function ruleRowsOf(state) {
|
|
|
66
67
|
return c ? c.pct : null;
|
|
67
68
|
};
|
|
68
69
|
const openMint = state.live?.token?.view.mint ?? null;
|
|
70
|
+
// 0.1.5 dev history: the creator's recorded rugs as the `devrugs` field (local recorder only; null = unknown).
|
|
71
|
+
const book = devBookOf(state);
|
|
72
|
+
const devRugsOf = (mint) => book ? book.rugsFor(mint) : null;
|
|
69
73
|
for (const r of state.screener.rows) {
|
|
70
74
|
if (!r.mint || r.skeleton || seen.has(r.mint))
|
|
71
75
|
continue;
|
|
72
76
|
seen.add(r.mint);
|
|
73
|
-
const row = Object.assign(Object.create(null), r, { cluster: clusterOf(r.mint), larp: larpGrade(state.larpByMint?.get(r.mint)?.verdict) });
|
|
77
|
+
const row = Object.assign(Object.create(null), r, { cluster: clusterOf(r.mint), larp: larpGrade(state.larpByMint?.get(r.mint)?.verdict), devRugs: devRugsOf(r.mint) });
|
|
74
78
|
if (r.mint === openMint)
|
|
75
79
|
overlaySignalFields(row, state, r.mint);
|
|
76
80
|
out.push(row);
|
|
@@ -81,12 +85,31 @@ export function ruleRowsOf(state) {
|
|
|
81
85
|
const row = Object.assign(Object.create(null), {
|
|
82
86
|
mint: v.mint, symbol: v.symbol, price: v.price, mcap: v.mcap, fdv: v.fdv, liq: v.liq, vol24: v.vol24,
|
|
83
87
|
holders: v.holders, org: v.org, top10: v.top10, dev: v.dev, lpLocked: v.lpLocked,
|
|
84
|
-
cluster: clusterOf(v.mint), larp: larpGrade(state.larpByMint?.get(v.mint)?.verdict),
|
|
88
|
+
cluster: clusterOf(v.mint), larp: larpGrade(state.larpByMint?.get(v.mint)?.verdict), devRugs: devRugsOf(v.mint),
|
|
85
89
|
ageMin: v.ageMin, chg: v.chg, launchpad: v.launchpad, flags: v.flags, verified: v.verified, sus: v.sus,
|
|
86
90
|
});
|
|
87
91
|
overlaySignalFields(row, state, v.mint);
|
|
88
92
|
out.push(row);
|
|
89
93
|
}
|
|
94
|
+
// 0.1.5 dev history: a brand-new pair (PumpPortal / RugCheck) whose creator has a labelled record becomes a rule
|
|
95
|
+
// row too, so `devrugs >= N scope newpairs` can alert on a new coin from a wallet that rugged before. Only pairs
|
|
96
|
+
// with a known record are added (no flood of unknowns); every other field is what the new-pairs feed knows.
|
|
97
|
+
const np = state.live?.newpairs;
|
|
98
|
+
if (book && np) {
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
for (const p of np.rows.values()) {
|
|
101
|
+
if (seen.has(p.mint) || !p.creator)
|
|
102
|
+
continue;
|
|
103
|
+
const devRugs = devRugsOf(p.mint);
|
|
104
|
+
if (devRugs === null)
|
|
105
|
+
continue;
|
|
106
|
+
seen.add(p.mint);
|
|
107
|
+
out.push(Object.assign(Object.create(null), {
|
|
108
|
+
mint: p.mint, symbol: p.symbol, mcap: p.mcap, liq: p.liq, holders: p.holders, launchpad: p.launchpad,
|
|
109
|
+
ageMin: p.createdAt ? Math.max(0, (now - p.createdAt) / 60_000) : null, bonding: p.bonding, devRugs,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
90
113
|
return out;
|
|
91
114
|
}
|
|
92
115
|
/** Overlay the live-signals engine's latest values (non-null wins) onto a rule row (task-1404). */
|
|
@@ -18,9 +18,11 @@ export function renderAnalystSession(c, x, y, w, h, s, opts) {
|
|
|
18
18
|
const bc = mix(t.border, t.ai, 0.55);
|
|
19
19
|
// Narrow panels (token screen) drop the model so cost · latency · tools still fits beside the title.
|
|
20
20
|
const full = s.status === 'error' ? 'analyst unavailable' : s.footer;
|
|
21
|
-
|
|
21
|
+
// 0.1.5: a codex conversation says so in the title; claude panels keep the original default.
|
|
22
|
+
const title = opts.title ?? (s.engine === 'codex' ? '✦ ANALYST · codex' : '✦ ANALYST · claude');
|
|
23
|
+
const caption = title.length + full.length + 10 <= w ? full : full.split(' · ').slice(0, 3).join(' · ');
|
|
22
24
|
const capFg = s.status === 'error' ? t.warn : t.dim;
|
|
23
|
-
const box = c.box(x, y, w, h, { title
|
|
25
|
+
const box = c.box(x, y, w, h, { title, titleFg: t.ai, caption, captionFg: capFg, border: bc });
|
|
24
26
|
const bg = box.bg;
|
|
25
27
|
const inner = box.w - 2;
|
|
26
28
|
const frame = opts.frame;
|
|
@@ -47,7 +49,7 @@ export function renderAnalystSession(c, x, y, w, h, s, opts) {
|
|
|
47
49
|
const drawTail = () => drawInput(c, box, bc, inputY, opts, s);
|
|
48
50
|
// earlier exchanges on this warm session (compact marker; the current turn is drawn in full below)
|
|
49
51
|
if (s.history.length && cy < inputY - 1) {
|
|
50
|
-
c.text(box.x + 1, cy, `⟲ ${s.history.length} earlier ${s.history.length === 1 ? 'exchange' : 'exchanges'} · one warm
|
|
52
|
+
c.text(box.x + 1, cy, `⟲ ${s.history.length} earlier ${s.history.length === 1 ? 'exchange' : 'exchanges'} · one warm ${s.engine} session`, { fg: t.dim, bg, i: true }, inner - 1);
|
|
51
53
|
cy += 1;
|
|
52
54
|
const last = s.history[s.history.length - 1];
|
|
53
55
|
const prev = last.error ? `last: ${last.error}` : last.verdict ? `last: ${last.verdict}` : last.summary[0] ? `last: ${last.summary[0]}` : '';
|
|
@@ -42,7 +42,8 @@ export function renderAnalyst(c, state) {
|
|
|
42
42
|
// next to live data — in live mode the empty tab says how to start a real conversation instead.
|
|
43
43
|
if (state.screener.source !== 'fixtures') {
|
|
44
44
|
const live = state.aiFocused === true;
|
|
45
|
-
const
|
|
45
|
+
const eng = state.analystEngine ?? 'claude'; // 0.1.5: the user's own CLI that will answer (claude | codex)
|
|
46
|
+
const b = c.box(0, top, chatW, bottom - top, { title: `✦ ANALYST · ${eng}`, titleFg: t.ai, caption: `read-only · local ${eng} CLI · on demand`, captionFg: t.dim, border: bc });
|
|
46
47
|
const bbg = b.bg;
|
|
47
48
|
const bin = b.w - 2;
|
|
48
49
|
let by = b.y + 1;
|
|
@@ -54,7 +55,7 @@ export function renderAnalyst(c, state) {
|
|
|
54
55
|
} };
|
|
55
56
|
say2('No conversation yet.');
|
|
56
57
|
by += 1;
|
|
57
|
-
say2(
|
|
58
|
+
say2(`Press i to ask ${eng} about the market, a token, or your watchlist. It can read the live screener, a token's holders / tape / RugCheck, and your watchlist — read-only, never trades.`);
|
|
58
59
|
const iy = b.y + b.h - 2;
|
|
59
60
|
c.hline(b.x, iy - 1, b.w, { fg: bc, bg: bbg, soft: true });
|
|
60
61
|
c.put(b.x, iy - 1, '├', { fg: bc, bg: bbg, soft: true });
|
|
@@ -64,7 +65,7 @@ export function renderAnalyst(c, state) {
|
|
|
64
65
|
if (typed)
|
|
65
66
|
c.text(b.x + 2, iy, typed + (live ? '█' : ''), { fg: t.text, bg: bbg }, bin - 2);
|
|
66
67
|
else
|
|
67
|
-
c.text(b.x + 2, iy, live ? '█ type your question… ⏎ send · esc blur' :
|
|
68
|
+
c.text(b.x + 2, iy, live ? '█ type your question… ⏎ send · esc blur' : `press i to ask ${eng} · ⏎ to focus`, { fg: t.dim, bg: bbg, i: true }, bin - 2);
|
|
68
69
|
if (railW)
|
|
69
70
|
renderCoinList(c, chatW, top, railW, bottom - top, state);
|
|
70
71
|
footer(c, analystFooter(state, live), state.theme.name, isCalm(state.vfx));
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// Replay is deterministic and offline. The live stream itself is started by `dexterm agent serve --spectate`;
|
|
9
9
|
// it is read-only, loopback-only and token-gated, and redacts wallet addresses/keys (see SPECTATOR.md).
|
|
10
10
|
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
11
12
|
import { join } from 'node:path';
|
|
12
13
|
import { FollowController, loadSpectatorRecording } from '../../agents/src/index.js';
|
|
13
14
|
function flag(rest, name) { const i = rest.indexOf(name); return i >= 0 ? rest[i + 1] : undefined; }
|
|
@@ -15,7 +16,18 @@ function has(rest, name) { return rest.includes(name); }
|
|
|
15
16
|
const USAGE = `dexterm spectate <command>
|
|
16
17
|
--replay FILE [--json] replay a saved session (fold agent actions into the screen + activity panel)
|
|
17
18
|
--info [--json] show the live stream endpoint from ~/.dexterm/spectator.json (if streaming)
|
|
18
|
-
read-only · redacts wallet addresses/keys · the live stream is started by \`dexterm agent serve --spectate
|
|
19
|
+
read-only · redacts wallet addresses/keys · the live stream is started by \`dexterm agent serve --spectate\`
|
|
20
|
+
or by a running app in follow mode (\`dexterm --spectate-mcp\`, for an agent connected over MCP)
|
|
21
|
+
Full docs: https://dexterm.xyz/docs/spectator/`;
|
|
22
|
+
/**
|
|
23
|
+
* Where `agent serve --spectate` writes its discovery file: the same rule as the writer (@dexterm/agents
|
|
24
|
+
* `dextermHome`), so a blank DEXTERM_HOME falls back and a missing HOME (Windows) does not turn into a
|
|
25
|
+
* relative `.dexterm` under the current directory.
|
|
26
|
+
*/
|
|
27
|
+
export function spectatorInfoPath(env, home = homedir()) {
|
|
28
|
+
const dir = env.DEXTERM_HOME && env.DEXTERM_HOME.trim() ? env.DEXTERM_HOME : join(home, '.dexterm');
|
|
29
|
+
return join(dir, 'spectator.json');
|
|
30
|
+
}
|
|
19
31
|
function ago(t) { const s = Math.max(0, Math.round((Date.now() - t) / 1000)); return s < 60 ? `${s}s` : s < 3600 ? `${Math.floor(s / 60)}m` : `${Math.floor(s / 3600)}h`; }
|
|
20
32
|
function age(s) { if (s == null)
|
|
21
33
|
return ''; return s < 60 ? ` · ${Math.round(s)}s old` : s < 3600 ? ` · ${Math.floor(s / 60)}m old` : ` · ${Math.floor(s / 3600)}h old`; }
|
|
@@ -75,8 +87,7 @@ export async function runSpectate(rest, io) {
|
|
|
75
87
|
return 0;
|
|
76
88
|
}
|
|
77
89
|
if (has(rest, '--info')) {
|
|
78
|
-
const
|
|
79
|
-
const path = join(home, 'spectator.json');
|
|
90
|
+
const path = spectatorInfoPath(io.env);
|
|
80
91
|
let info;
|
|
81
92
|
try {
|
|
82
93
|
info = JSON.parse(readFileSync(path, 'utf8'));
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// The one place the app's version lives (task-1397). --version, the help banner and `dexterm update` read it;
|
|
2
2
|
// cli.test.ts keeps it equal to packages/cli/package.json (release.yml refuses a tag that differs).
|
|
3
|
-
export const VERSION = '0.1.
|
|
3
|
+
export const VERSION = '0.1.5';
|
|
@@ -52,9 +52,15 @@ function printStatus(io, store, now) {
|
|
|
52
52
|
else {
|
|
53
53
|
io.stdout.write(`login: ${meta.user ? `@${meta.user.username} (id ${meta.user.id})` : 'logged in (handle unknown)'} · scopes ${meta.scopes.join(' ') || '—'} · access token ${meta.expiresAt !== null ? (meta.expiresAt > now ? `valid ${fmtIn(meta.expiresAt - now)}` : 'expired (refreshes on next use)') : '?'} · refresh ${meta.hasRefresh ? 'yes' : 'no (login again)'}\n`);
|
|
54
54
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
io.stderr.write('
|
|
55
|
+
if (process.platform === 'win32') {
|
|
56
|
+
io.stdout.write(`files: ${store.dir} · tokens.json ${modes.tokens === null ? 'absent' : 'present'} · client-id ${modes.clientId === null ? 'absent' : 'present'} · Windows ACL isolation is not verified\n`);
|
|
57
|
+
io.stderr.write('Review the token directory Security permissions in Windows; chmod does not enforce owner-only ACLs.\n');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
io.stdout.write(`files: ${store.dir} (mode ${oct(modes.dir)}) · tokens.json ${modes.tokens === null ? 'absent' : `mode ${oct(modes.tokens)}`} · client-id ${modes.clientId === null ? 'absent' : `mode ${oct(modes.clientId)}`}\n`);
|
|
61
|
+
if ((modes.tokens !== null && modes.tokens !== 0o600) || (modes.dir !== null && modes.dir !== 0o700))
|
|
62
|
+
io.stderr.write('⚠ permissions are looser than 0600/0700; dexterm will tighten them on the next write (or chmod them now)\n');
|
|
63
|
+
}
|
|
58
64
|
io.stdout.write(`callback: ${callbackUri()} · tokens are never printed, logged, or sent to a model · X reads are user-context only (your rate limits, your bill)\n`);
|
|
59
65
|
}
|
|
60
66
|
/** Run the `x` subcommand. Returns a process exit code. */
|
|
@@ -84,7 +90,7 @@ export async function runX(argv, io, over = {}) {
|
|
|
84
90
|
io.stderr.write(`dexterm x set-client-id: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
85
91
|
return 2;
|
|
86
92
|
}
|
|
87
|
-
io.stdout.write(`stored (${v.length} chars) at ${store.clientIdPath} (mode 600). Next: dexterm x login\n`);
|
|
93
|
+
io.stdout.write(`stored (${v.length} chars) at ${store.clientIdPath} (${process.platform === 'win32' ? 'Windows ACL isolation unverified' : 'mode 600'}). Next: dexterm x login\n`);
|
|
88
94
|
return 0;
|
|
89
95
|
}
|
|
90
96
|
if (sub === 'login') {
|
|
@@ -101,7 +107,7 @@ export async function runX(argv, io, over = {}) {
|
|
|
101
107
|
const res = await oauth.login({ env: io.env });
|
|
102
108
|
clearTimeout(t);
|
|
103
109
|
io.stdout.write(`✓ logged in${res.user ? ` as @${res.user.username}` : ''} · scopes ${res.scopes.join(' ')} · access token valid ${fmtIn(res.expiresAt - now())} · refresh ${res.hasRefresh ? 'yes' : 'no'} · browser ${Math.round(res.callbackMs / 100) / 10}s · exchange ${res.exchangeMs}ms\n`);
|
|
104
|
-
io.stdout.write(`tokens: ${store.tokensPath} (mode 600). The SOCIAL panel and TRENDING board now read X with your own limits.\n`);
|
|
110
|
+
io.stdout.write(`tokens: ${store.tokensPath} (${process.platform === 'win32' ? 'Windows ACL isolation unverified' : 'mode 600'}). The SOCIAL panel and TRENDING board now read X with your own limits.\n`);
|
|
105
111
|
return 0;
|
|
106
112
|
}
|
|
107
113
|
catch (e) {
|
|
@@ -3,13 +3,13 @@ export const FIELDS = {
|
|
|
3
3
|
vol5m: (r) => r.vol5m, vol1h: (r) => r.vol1h, vol24: (r) => r.vol24,
|
|
4
4
|
holders: (r) => r.holders, org: (r) => r.org, top10: (r) => r.top10, dev: (r) => r.dev,
|
|
5
5
|
lplocked: (r) => r.lpLocked, bundled: (r) => r.bundled, snipers: (r) => r.snipers,
|
|
6
|
-
cluster: (r) => r.cluster, larp: (r) => r.larp,
|
|
6
|
+
cluster: (r) => r.cluster, larp: (r) => r.larp, devrugs: (r) => r.devRugs,
|
|
7
7
|
fresh: (r) => r.fresh, mintable: (r) => r.mintable, freezable: (r) => r.freezable,
|
|
8
8
|
age: (r) => r.ageMin, m5: (r) => r.chg?.m5, h1: (r) => r.chg?.h1, h6: (r) => r.chg?.h6, h24: (r) => r.chg?.h24,
|
|
9
9
|
buys: (r) => r.buys, sells: (r) => r.sells, bonding: (r) => r.bonding,
|
|
10
10
|
};
|
|
11
11
|
export const FIELD_NAMES = Object.keys(FIELDS);
|
|
12
|
-
const ALIASES = { vol: 'vol24', volume: 'vol24', liquidity: 'liq', cap: 'mcap', mc: 'mcap', holder: 'holders', organic: 'org', top: 'top10', locked: 'lplocked', 'cluster%': 'cluster', clusterpct: 'cluster', freshwallets: 'fresh', mintauth: 'mintable', freezeauth: 'freezable' };
|
|
12
|
+
const ALIASES = { vol: 'vol24', volume: 'vol24', liquidity: 'liq', cap: 'mcap', mc: 'mcap', holder: 'holders', organic: 'org', top: 'top10', locked: 'lplocked', 'cluster%': 'cluster', clusterpct: 'cluster', freshwallets: 'fresh', mintauth: 'mintable', freezeauth: 'freezable', devrug: 'devrugs', serialrugger: 'devrugs' };
|
|
13
13
|
const OPS = { '>': (a, b) => a > b, '>=': (a, b) => a >= b, '<': (a, b) => a < b, '<=': (a, b) => a <= b, '=': (a, b) => a === b, '==': (a, b) => a === b, '!=': (a, b) => a !== b };
|
|
14
14
|
const TAGS = {
|
|
15
15
|
launchpad: (r, v) => (r.launchpad ?? '').toLowerCase().replace('.fun', '') === v.toLowerCase().replace('.fun', ''),
|
|
@@ -261,6 +261,10 @@ export const SCHEMAS = {
|
|
|
261
261
|
/** Reserved: the trading gate is NOT here (signer policy owns it); listed so a proposal that tries gets a precise refusal. */
|
|
262
262
|
gate: { type: 'string', enum: ['keypress'] },
|
|
263
263
|
} },
|
|
264
|
+
/** 0.1.5: which of the user's own CLIs runs the analyst. `/analyst` is user-only (DENIED_FOR_MODELS): a model never picks its own engine. */
|
|
265
|
+
analyst: { type: 'object', additional: false, props: {
|
|
266
|
+
engine: { type: 'string', enum: ['auto', 'claude', 'codex'] },
|
|
267
|
+
} },
|
|
264
268
|
/** task-1393: voice-out preferences. `/voice` is user-only (DENIED_FOR_MODELS), so only the keyboard sets it. */
|
|
265
269
|
voice: { type: 'object', additional: false, props: {
|
|
266
270
|
name: { type: 'string', max: 64 },
|
|
@@ -426,7 +430,7 @@ export function applyOps(doc, ops) {
|
|
|
426
430
|
export const DENIED_FOR_MODELS = [
|
|
427
431
|
/^\/signer(\/|$)/, /^\/trading(\/|$)/, /^\/keys(\/|$)/, /^\/caps(\/|$)/, /^\/mandates?(\/|$)/, /^\/wallet(\/|$)/,
|
|
428
432
|
/^\/packs(\/|$)/, /^\/plugins\/[^/]+\/(capabilities|enabled)(\/|$)/, /^\/plugins\/[^/]+$/, /^\/plugins$/,
|
|
429
|
-
/^\/jarvis\/gate(\/|$)/, /^\/jarvis\/configAssistant$/, /^\/voice(\/|$)/, /^\/alerts(\/|$)/, /^\/agents(\/|$)/, /^\/version$/,
|
|
433
|
+
/^\/jarvis\/gate(\/|$)/, /^\/jarvis\/configAssistant$/, /^\/voice(\/|$)/, /^\/analyst(\/|$)/, /^\/alerts(\/|$)/, /^\/agents(\/|$)/, /^\/version$/,
|
|
430
434
|
/^\/rules\/(\d+|-)\/then\/(\d+|-)\/args\/(arm|armed|auto|autoArm|confirm|execute|sign|send)(\/|$)/,
|
|
431
435
|
];
|
|
432
436
|
/** Keys a model-authored VALUE may never contain at any depth (money/secret-shaped), whatever the path. */
|
|
@@ -62,21 +62,43 @@ export class FixtureStore {
|
|
|
62
62
|
}
|
|
63
63
|
save(path, note) {
|
|
64
64
|
mkdirSync(dirname(path), { recursive: true });
|
|
65
|
-
const
|
|
65
|
+
const raw = {
|
|
66
66
|
version: 1,
|
|
67
67
|
recordedAt: new Date().toISOString(),
|
|
68
68
|
note: note ?? this.note,
|
|
69
69
|
entries: this.list(),
|
|
70
70
|
};
|
|
71
71
|
if (this.streams.length)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
72
|
+
raw.streams = this.streams;
|
|
73
|
+
// Request URLs are redacted at capture time, but live response bodies echo third-party URLs with key-shaped
|
|
74
|
+
// params (DexScreener image URLs carry `&key=<cache id>`); refusing those threw away whole live sessions at
|
|
75
|
+
// quit (task-1443). Redact every string instead. Replay still matches: requestKey() redacts the same way.
|
|
76
|
+
const file = scrubSecrets(raw);
|
|
77
|
+
// Belt and braces: never write a file that still carries a key value (checked per string, so escaping is moot).
|
|
78
|
+
if (anyString(file, containsSecret))
|
|
76
79
|
throw new Error('refusing to save a recording that contains an api-key value');
|
|
77
|
-
writeFileSync(path,
|
|
80
|
+
writeFileSync(path, JSON.stringify(file, null, 1) + '\n');
|
|
78
81
|
}
|
|
79
82
|
}
|
|
83
|
+
/** A deep copy of `v` with redactSecrets() applied to every string. */
|
|
84
|
+
function scrubSecrets(v) {
|
|
85
|
+
if (typeof v === 'string')
|
|
86
|
+
return redactSecrets(v);
|
|
87
|
+
if (Array.isArray(v))
|
|
88
|
+
return v.map(scrubSecrets);
|
|
89
|
+
if (v && typeof v === 'object')
|
|
90
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, scrubSecrets(x)]));
|
|
91
|
+
return v;
|
|
92
|
+
}
|
|
93
|
+
function anyString(v, pred) {
|
|
94
|
+
if (typeof v === 'string')
|
|
95
|
+
return pred(v);
|
|
96
|
+
if (Array.isArray(v))
|
|
97
|
+
return v.some((x) => anyString(x, pred));
|
|
98
|
+
if (v && typeof v === 'object')
|
|
99
|
+
return Object.values(v).some((x) => anyString(x, pred));
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
80
102
|
export function toEntry(req, res, recordedAt = new Date().toISOString()) {
|
|
81
103
|
let body = res.body;
|
|
82
104
|
let bodyIsJson = false;
|
|
@@ -38,6 +38,9 @@ export * from "./trade/tx-decode.js";
|
|
|
38
38
|
export * from "./trade/sol-usd.js";
|
|
39
39
|
export * from "./trade/kill-switch.js";
|
|
40
40
|
export * from "./trade/token-delta.js";
|
|
41
|
+
// task-1443: the paper agent desk + its rendezvous (agents paper-trade inside an armed mandate; never sign/send).
|
|
42
|
+
export * from "./trade/agent/paper-desk.js";
|
|
43
|
+
export * from "./trade/agent/rendezvous.js";
|
|
41
44
|
export * from "./trade/signer/protocol.js";
|
|
42
45
|
export * from "./trade/signer/service.js";
|
|
43
46
|
export * from "./trade/signer/host.js";
|
|
@@ -106,6 +106,7 @@ export function normalizeJupToken(raw, fetchedAt) {
|
|
|
106
106
|
top10Pct: numOrNull(raw.audit?.topHoldersPercentage),
|
|
107
107
|
devPct: numOrNull(raw.audit?.devBalancePercentage),
|
|
108
108
|
devMints: numOrNull(raw.audit?.devMints),
|
|
109
|
+
devAddress: typeof raw.dev === 'string' && /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(raw.dev) ? raw.dev : null,
|
|
109
110
|
lpLockedPct: null,
|
|
110
111
|
rugRisks: [],
|
|
111
112
|
rugScore: null,
|