dexterm-cli 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/cli/assets/claude/analyst-system.md +2 -1
- package/dist/packages/cli/src/app-help-gen.js +3 -3
- package/dist/packages/cli/src/docs-kb-gen.js +228 -0
- package/dist/packages/cli/src/doctor.js +22 -7
- 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/token.js +38 -1
- package/dist/packages/cli/src/main.js +140 -1
- package/dist/packages/cli/src/mcp-follow.js +225 -0
- 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/mcp/assets/kb/app-help/spectate-an-agent.md +4 -1
- package/dist/packages/mcp/assets/kb/docs/docs-agents-capability-tiers-enforced-server-side.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-install-in-one-command.md +49 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-per-agent-mandates.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-recipes.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-red-team-coverage.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-two-ways-to-connect.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents-watch-an-agent-work-spectator-view.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-agents.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-answer-style.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-it-knows-the-app.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-mcp-tools.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-plain-language-filters.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-skills.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-can-change-three-tiers.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-the-analyst-can-never-do.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-analyst.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-1-start-dexterm.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-2-open-a-real-coin-by-contract-address.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-3-read-the-safety-panel.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-4-read-the-holder-panel.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-5-optionally-ask-the-analyst.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-6-export-or-share-what-you-see.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-7-quit-safely.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-check-the-installation-with-doctor.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-choose-a-setup-path.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-common-mistakes-and-recovery.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-copy-and-paste-without-surprises.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-data-says-unavailable-stale-or-rate-limited.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-dexterm-says-command-not-found.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-optional-help-is-unavailable.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-paste-does-nothing-or-pastes-twice.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-the-screen-looks-broken.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-you-typed-the-wrong-thing.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-install-node-js-first.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-linux.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-homebrew.md +46 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-or-linux-npm.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-open-the-terminal.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-reopen-dexterm-later.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-ten-minute-first-win.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-terminal-shell-prompt-command-output-and-path.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-where-to-go-next.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-expectations.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-powershell-experimental.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-dexterm-help.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-filter-grammar.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-flags.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-invocations.md +43 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-presets.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-scan-commands.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-sort-keys.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli-subcommands.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-cli.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +45 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-history-recorder.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-packs.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-push-alerts-and-dexterm-watch.md +45 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-rules-engine.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-analyst-s-launch.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-optional-free-rpc-key.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-themes-and-keymaps.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-themes-and-look.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-widgets.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration-workspaces.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-configuration.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-1-follow-a-wallet-see-what-it-does.md +51 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-cache.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-health-registry.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-rate-limits-and-pacing.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-request-budget-meter.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +47 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources-the-helius-credit-meter.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-data-sources.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-dexterm-doctor.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +71 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-next.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-supported-platforms.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started-updating.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-global-keys.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-keys-handled-by-the-loop.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-per-screen-keys.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard-what-shows.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-keyboard.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +55 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-open-link-keys.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-paste-a-coin-on-the-web.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-timestamped-follow-up-drafts.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-larp.md +24 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-degraded-states-you-will-see.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-free-vs-paid-x-api-tiers-your-bill-your-limits.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-kol-lists-and-the-per-handle-filter.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-tests.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-what-is-read-and-when.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-what-you-set-up-once.md +40 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-where-the-tokens-live.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-login-with-x.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +44 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-bubble-map-popup.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-build-the-native-window.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-config-dexterm-popup-json.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-docking-macos.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-keys-in-the-popup.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-security-model.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-token-and-feeds-popups.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-popup-chart.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-cost-control.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-keys-flag-command.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-safety-four-layers.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-states.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan-triggers-and-score.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scan.md +24 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-frozen-canary.md +30 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-frozen-task-1401-evaluation.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-latest-skill-care-run.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-links-only-larp-baseline-where-it-is-weak.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-not-measured.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-rug-verdict-rules.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard-source-and-rebuild.md +31 -0
- package/dist/packages/mcp/assets/kb/docs/docs-scorecard.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-1-screener.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-2-new-pairs.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-3-watchlist.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-4-analyst-tab.md +28 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-bubble-map.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-coin-images.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-live-holder-and-rug-signals.md +51 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-paper-ticket-and-positions.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-the-look.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens-trending-social-and-kol-lists.md +34 -0
- package/dist/packages/mcp/assets/kb/docs/docs-screens.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-1-the-agent-activity-window-follow-mode.md +56 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-2-the-read-only-stream-spectate.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-3-save-and-replay-record-file-dexterm-spectate.md +41 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +39 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-quick-start.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-security-model.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-see-also.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-streaming-it.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +37 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator-what-is-redacted.md +35 -0
- package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +29 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +33 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-in-app-messages.md +42 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-reporting-a-problem.md +25 -0
- package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting.md +23 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-doctor.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-it-clarifies-it-never-guesses.md +36 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-latency.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +32 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-privacy.md +26 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-still-unverified.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-the-voice-neural-by-default.md +48 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-what-it-can-never-do.md +27 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice-what-you-can-say.md +38 -0
- package/dist/packages/mcp/assets/kb/docs/docs-voice.md +25 -0
- package/dist/packages/mcp/src/app-help.js +123 -0
- package/dist/packages/mcp/src/firewall.js +2 -2
- package/dist/packages/mcp/src/index.js +5 -4
- package/dist/packages/mcp/src/server.js +56 -18
- package/dist/packages/mcp/src/spectator-sink.js +74 -2
- package/dist/packages/voice/src/stt/whisper.js +4 -2
- package/package.json +6 -5
|
@@ -29,6 +29,7 @@ import { makeFixtureState, makeFeedState, syncFeed, renderFrame, isScreenKey, ac
|
|
|
29
29
|
import { mulberry32, tick, spawnPair, shock } from "./fixtures.js";
|
|
30
30
|
import { createLiveFeed, createReplayFeed } from "./feed.js";
|
|
31
31
|
import { LiveContext, INTERVALS, WATCH_REFRESH_MS, POSITIONS_REFRESH_MS, popupBubbleVM } from "./live/index.js";
|
|
32
|
+
import { McpFollowSession } from "./mcp-follow.js";
|
|
32
33
|
import { TOKEN_REFRESH_MS, CandlePrefetcher } from "./live/token.js";
|
|
33
34
|
import { PRESETS, presetSpec } from "./screener/presets.js";
|
|
34
35
|
import { parseSortArg, SORT_KEYS } from "./screener/sort.js";
|
|
@@ -272,6 +273,7 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
272
273
|
dexterm alerts status|enable|disable|set-telegram|set-discord|test where rule alerts are delivered (desktop / your Telegram / Discord; creds stay local, never logged)
|
|
273
274
|
dexterm watch [--interval S] [--once] [--replay FILE] run your rules headless and push alerts when the terminal is closed (no signer, proposals only)
|
|
274
275
|
dexterm record run|install|uninstall|status keep the history recorder fed with no TUI open, as a user-level launchd agent (no network beyond the data layer; 'dexterm record help')
|
|
276
|
+
dexterm larp-detected --out DIR [--db PATH] build timestamped high-risk follow-up drafts/cards from recorder history; never posts
|
|
275
277
|
dexterm --fixtures interactive demo on seeded fixtures
|
|
276
278
|
dexterm --screen <name> one-shot frame to stdout (live)
|
|
277
279
|
dexterm --screen <name> --fixtures one-shot frame from fixtures
|
|
@@ -284,6 +286,8 @@ export const HELP = `dexterm ${VERSION} (live: screener, token, new pairs, watch
|
|
|
284
286
|
--sort KEY[:asc|desc] ${SORT_KEYS.map((k) => k.key).join(' | ')}
|
|
285
287
|
--preset NAME ${PRESETS.map((p) => p.id).join(' | ')}
|
|
286
288
|
--record FILE save every live response (and the PumpPortal session) to FILE (replay it with --replay)
|
|
289
|
+
--spectate-mcp follow an MCP agent's allowed navigation in this terminal; Ctrl+P pauses, Ctrl+K stops,
|
|
290
|
+
and any normal user keypress takes control (refused actions never move the UI)
|
|
287
291
|
--max-requests N hard cap on live requests for this run (default: none)
|
|
288
292
|
--max-credits N hard cap on Helius credits for this run (default: 5000)
|
|
289
293
|
--mint MINT|LINK token to open on the token screen (a CA or a dexscreener / pump.fun / solscan / birdeye link)
|
|
@@ -392,6 +396,7 @@ export function parseArgs(argv) {
|
|
|
392
396
|
sound: false,
|
|
393
397
|
trade: [], live: true,
|
|
394
398
|
workspace: null, priceMode: null,
|
|
399
|
+
spectateMcp: false,
|
|
395
400
|
help: false, version: false, unknown: [],
|
|
396
401
|
};
|
|
397
402
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -573,6 +578,9 @@ export function parseArgs(argv) {
|
|
|
573
578
|
throw new Error(`--price-mode expects price|mcap, got ${JSON.stringify(s.slice(0, 20))}`);
|
|
574
579
|
a.priceMode = s;
|
|
575
580
|
}
|
|
581
|
+
// task-1421: follow an agent driving over MCP (the running terminal moves with it; a read-only stream opens).
|
|
582
|
+
else if (k === '--spectate-mcp')
|
|
583
|
+
a.spectateMcp = true;
|
|
576
584
|
else if (k === '--workspace') {
|
|
577
585
|
const s = str();
|
|
578
586
|
if (!/^[a-z][a-z0-9-]{0,39}(\/[a-z][a-z0-9-]{0,39})?$/.test(s))
|
|
@@ -790,6 +798,11 @@ export async function main(argv, io = process, proc = process) {
|
|
|
790
798
|
const { backtestCommand } = await import("./backtest-cmd.js");
|
|
791
799
|
return backtestCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
792
800
|
}
|
|
801
|
+
// Local-only marketing evidence pipeline (task-1433): drafts and cards for human review, never posts.
|
|
802
|
+
if (argv[0] === 'larp-detected') {
|
|
803
|
+
const { larpDetectedCommand } = await import("./larp-detected.js");
|
|
804
|
+
return larpDetectedCommand(argv.slice(1), { stdout: io.stdout, stderr: io.stderr, env: io.env });
|
|
805
|
+
}
|
|
793
806
|
// `dexterm kol …` → KOL lists on the shared DB (task-1330): list/create/add/remove/mute/unmute/enable/disable/import.
|
|
794
807
|
if (argv[0] === 'kol')
|
|
795
808
|
return kolCommand(argv.slice(1), io);
|
|
@@ -2895,6 +2908,76 @@ export function applyAnalystAction(state, raw, stack) {
|
|
|
2895
2908
|
times.push(now);
|
|
2896
2909
|
return { ok: true, label: res.label };
|
|
2897
2910
|
}
|
|
2911
|
+
/**
|
|
2912
|
+
* task-1421: move the REAL terminal to mirror an agent's navigation (follow mode, on the owner's own
|
|
2913
|
+
* machine). This is the same VIEW-ONLY navigation the analyst / go-to path uses — open a token, switch
|
|
2914
|
+
* screen/feed, set a filter, highlight a row, open the PAPER ticket editor. It never trades, sizes, confirms
|
|
2915
|
+
* or signs, and the key loop flips control to the user before any of it runs, so the human always wins the
|
|
2916
|
+
* screen. Unlike the analyst boundary it does not re-check the on-screen allow-list: the agent already made
|
|
2917
|
+
* the call on its own server and this only reflects where it went. A no-op when live navigation is off.
|
|
2918
|
+
*/
|
|
2919
|
+
export function applyFollowNav(state, nav, stack) {
|
|
2920
|
+
const sc = state.screener;
|
|
2921
|
+
const live = state.live && state.screener.source !== 'fixtures' ? state.live : null;
|
|
2922
|
+
const toScreen = (next) => { if (state.screen !== next) {
|
|
2923
|
+
stack.push(state.screen);
|
|
2924
|
+
state.screen = next;
|
|
2925
|
+
} };
|
|
2926
|
+
if (nav.mint && (!nav.screen || nav.screen === 'token')) {
|
|
2927
|
+
if (!live)
|
|
2928
|
+
return;
|
|
2929
|
+
try {
|
|
2930
|
+
live.openToken(seedForMint(state, live, nav.mint));
|
|
2931
|
+
toScreen('token');
|
|
2932
|
+
}
|
|
2933
|
+
catch { /* a stale mint never breaks follow */ }
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (nav.screen === 'ticket') {
|
|
2937
|
+
if (!live || !nav.mint)
|
|
2938
|
+
return;
|
|
2939
|
+
try {
|
|
2940
|
+
const seed = seedForMint(state, live, nav.mint);
|
|
2941
|
+
live.openTicket({ mint: seed.mint, symbol: seed.symbol, name: seed.name }, nav.side === 'sell' ? 'sell' : 'buy');
|
|
2942
|
+
toScreen('ticket');
|
|
2943
|
+
}
|
|
2944
|
+
catch { /* ignore */ }
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
if (nav.screen === 'watchlist') {
|
|
2948
|
+
if (live && nav.mint) {
|
|
2949
|
+
try {
|
|
2950
|
+
if (!live.watch.has(nav.mint))
|
|
2951
|
+
live.watch.add(LiveContext.candidate(seedForMint(state, live, nav.mint)));
|
|
2952
|
+
}
|
|
2953
|
+
catch { /* ignore */ }
|
|
2954
|
+
}
|
|
2955
|
+
toScreen('watchlist');
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
if (nav.feed || nav.filter || nav.highlight || nav.screen === 'screener') {
|
|
2959
|
+
toScreen('screener');
|
|
2960
|
+
if (nav.feed) {
|
|
2961
|
+
if (setPreset(sc, nav.feed) && state.feed)
|
|
2962
|
+
state.feed.setPreset(sc.preset);
|
|
2963
|
+
}
|
|
2964
|
+
if (nav.filter) {
|
|
2965
|
+
try {
|
|
2966
|
+
setFilter(sc, nav.filter);
|
|
2967
|
+
}
|
|
2968
|
+
catch { /* an invalid agent filter is ignored */ }
|
|
2969
|
+
}
|
|
2970
|
+
if (nav.highlight) {
|
|
2971
|
+
const i = screenerView(sc).findIndex((r) => r.mint === nav.highlight);
|
|
2972
|
+
if (i >= 0)
|
|
2973
|
+
state.selected = i;
|
|
2974
|
+
}
|
|
2975
|
+
state.selected = clampSelection(sc, state.selected);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
if (nav.screen && isScreenKey(nav.screen))
|
|
2979
|
+
toScreen(nav.screen);
|
|
2980
|
+
}
|
|
2898
2981
|
/**
|
|
2899
2982
|
* task-1413: apply an accepted tier-(b) config proposal through the journaled path as actor `analyst`
|
|
2900
2983
|
* (so `ctrl+z` undoes it), creating the parent section when absent (exactly like the `$` / `,` handlers),
|
|
@@ -3730,6 +3813,9 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3730
3813
|
let timer = null;
|
|
3731
3814
|
let lastRefresh = 0;
|
|
3732
3815
|
let done = false;
|
|
3816
|
+
// task-1421: the MCP follow session (set below when `--spectate-mcp` is on). Declared here so the quit /
|
|
3817
|
+
// terminal-guard teardown closures can stop it.
|
|
3818
|
+
let followSession = null;
|
|
3733
3819
|
// Idle economy (task-1303, retuned for task-1319 AC3): between events the ambient rain redraws at
|
|
3734
3820
|
// IDLE_FPS instead of the full frame cap, cutting idle CPU. A keypress opens a KEY_WAKE_S full-rate
|
|
3735
3821
|
// window (`wakeUntil`) so input and transitions stay smooth; data landings paint at once on their own
|
|
@@ -3759,7 +3845,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3759
3845
|
}
|
|
3760
3846
|
};
|
|
3761
3847
|
const guard = terminalGuard(io, () => { done = true; prefetcher?.close(); if (timer)
|
|
3762
|
-
clearInterval(timer); state.analyst?.cancel(); if (state.analystByMint)
|
|
3848
|
+
clearInterval(timer); void followSession?.stop(); state.analyst?.cancel(); if (state.analystByMint)
|
|
3763
3849
|
for (const s of state.analystByMint.values())
|
|
3764
3850
|
s.cancel(); state.popups?.shutdown(); voice?.disable(); images?.close(); live?.close(); feed?.close(); }, proc, { beforeLeave: () => layer?.teardown() ?? '' });
|
|
3765
3851
|
let lastTokenRefresh = 0;
|
|
@@ -3930,6 +4016,37 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
3930
4016
|
// task-1326: an image landing (decoded, or found missing) repaints at most once per macrotask.
|
|
3931
4017
|
if (images)
|
|
3932
4018
|
images.onChange = () => scheduleDraw();
|
|
4019
|
+
// task-1421: `--spectate-mcp` → follow an agent driving over MCP. The rendezvous lets a `dexterm mcp`
|
|
4020
|
+
// server the external client spawned emit here; we tail it, move the terminal for navigation calls, and
|
|
4021
|
+
// the owner's controls (ctrl+p pause/resume · ctrl+k stop · any key = take over) are handled in onInput.
|
|
4022
|
+
// A read-only, loopback, token-gated stream opens too (`dexterm spectate --info`). Off (and zero-cost)
|
|
4023
|
+
// unless the flag is set; nothing it folds can arm, sign, confirm, send or raise a cap.
|
|
4024
|
+
if (args.spectateMcp) {
|
|
4025
|
+
followSession = new McpFollowSession({
|
|
4026
|
+
env: io.env,
|
|
4027
|
+
stream: true,
|
|
4028
|
+
onNav: (nav) => {
|
|
4029
|
+
const before = state.screen;
|
|
4030
|
+
try {
|
|
4031
|
+
applyFollowNav(state, nav, stack);
|
|
4032
|
+
}
|
|
4033
|
+
catch { /* a stale mint never breaks follow */ }
|
|
4034
|
+
if (state.screen !== before) {
|
|
4035
|
+
try {
|
|
4036
|
+
startTransition();
|
|
4037
|
+
}
|
|
4038
|
+
catch {
|
|
4039
|
+
transition = null;
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
scheduleDraw();
|
|
4043
|
+
},
|
|
4044
|
+
onStatus: (line) => { say(line, 'ai', 4); scheduleDraw(); },
|
|
4045
|
+
onEvent: () => scheduleDraw(),
|
|
4046
|
+
});
|
|
4047
|
+
void followSession.start();
|
|
4048
|
+
say('following agents over MCP · ctrl+p pause · ctrl+k stop · any key takes over', 'ai', 6);
|
|
4049
|
+
}
|
|
3933
4050
|
// Launch a real Claude analyst session when a key asked for one (applyAction cannot spawn). A
|
|
3934
4051
|
// `question` means start it as a chat turn, not a paid structured skill run (task-1311, QA B3).
|
|
3935
4052
|
const beginAnalyst = () => {
|
|
@@ -4919,6 +5036,7 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4919
5036
|
prefetcher?.close();
|
|
4920
5037
|
if (timer)
|
|
4921
5038
|
clearInterval(timer);
|
|
5039
|
+
void followSession?.stop();
|
|
4922
5040
|
guard.restore();
|
|
4923
5041
|
guard.uninstall();
|
|
4924
5042
|
cancelAllAnalysts();
|
|
@@ -4973,6 +5091,27 @@ export function interactive(state, io, depth, args, proc = process) {
|
|
|
4973
5091
|
quit();
|
|
4974
5092
|
return;
|
|
4975
5093
|
}
|
|
5094
|
+
// task-1421 follow-mode controls (only while following an MCP agent). ctrl+p pauses / resumes the
|
|
5095
|
+
// terminal mirroring; ctrl+k stops following (removes the rendezvous so the server stops emitting);
|
|
5096
|
+
// ANY other key while the agent is driving hands control to the user — the human and the agent never
|
|
5097
|
+
// fight over the screen. The control keys are consumed; the take-over key still does its own job.
|
|
5098
|
+
if (followSession) {
|
|
5099
|
+
if (key.ctrl && key.ch === 'k') {
|
|
5100
|
+
followSession.kill();
|
|
5101
|
+
draw();
|
|
5102
|
+
continue;
|
|
5103
|
+
}
|
|
5104
|
+
if (key.ctrl && key.ch === 'p') {
|
|
5105
|
+
if (followSession.isPaused)
|
|
5106
|
+
followSession.resume();
|
|
5107
|
+
else
|
|
5108
|
+
followSession.pause();
|
|
5109
|
+
draw();
|
|
5110
|
+
continue;
|
|
5111
|
+
}
|
|
5112
|
+
if (followSession.controlState === 'agent')
|
|
5113
|
+
followSession.userKey(); // take over, then fall through
|
|
5114
|
+
}
|
|
4976
5115
|
// task-1413: a pending analyst config PROPOSAL owns the next key. `y` accepts and journals it as
|
|
4977
5116
|
// actor 'analyst' (ctrl+z undoes); any other key dismisses it. A stray key never persists a change.
|
|
4978
5117
|
// Two guards (defect c): the proposal does NOT steal a key while a TEXT INPUT is open — the GO-TO
|
|
@@ -0,0 +1,225 @@
|
|
|
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 } 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
|
+
constructor(opts) {
|
|
60
|
+
this.opts = opts;
|
|
61
|
+
this.agentLabel = opts.agentLabel || 'mcp-agent';
|
|
62
|
+
this.currentAgent = this.agentLabel;
|
|
63
|
+
this.rvPath = rendezvousPath(opts.env);
|
|
64
|
+
this.channelPath = this.rvPath.replace(/\.json$/, '.jsonl');
|
|
65
|
+
this.token = randomBytes(24).toString('hex');
|
|
66
|
+
this.hub = new SpectatorHub({ cap: Math.max(1, opts.activityCap ?? 200) });
|
|
67
|
+
const controls = {
|
|
68
|
+
pauseAgent: () => true,
|
|
69
|
+
resumeAgent: () => { this.rearm(); return true; },
|
|
70
|
+
kickAgent: () => { this.disarm(); return 1; },
|
|
71
|
+
};
|
|
72
|
+
this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
|
|
73
|
+
this.hub.on((ev) => this.follow.apply(ev));
|
|
74
|
+
}
|
|
75
|
+
/** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
|
|
76
|
+
async start() {
|
|
77
|
+
this.rearm();
|
|
78
|
+
if (this.opts.stream) {
|
|
79
|
+
try {
|
|
80
|
+
this.stream = new SpectatorServer({ hub: this.hub, env: this.opts.env });
|
|
81
|
+
await this.stream.start();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
this.stream = null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!this.timer) {
|
|
88
|
+
this.timer = setInterval(() => this.pump(), Math.max(30, this.opts.pollMs ?? 150));
|
|
89
|
+
this.timer.unref?.();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Write (or re-write) the channel + rendezvous so the server emits here. */
|
|
93
|
+
rearm() {
|
|
94
|
+
try {
|
|
95
|
+
mkdirSync(dirname(this.rvPath), { recursive: true });
|
|
96
|
+
if (!this.armed) {
|
|
97
|
+
writeFileSync(this.channelPath, '', { mode: 0o600 });
|
|
98
|
+
this.offset = 0;
|
|
99
|
+
}
|
|
100
|
+
writeFileSync(this.rvPath, JSON.stringify({ file: this.channelPath, token: this.token, agent: this.agentLabel }) + '\n', { mode: 0o600 });
|
|
101
|
+
this.armed = true;
|
|
102
|
+
}
|
|
103
|
+
catch { /* best-effort local channel */ }
|
|
104
|
+
}
|
|
105
|
+
/** Remove the rendezvous so the server stops emitting (the external agent keeps running; we stop seeing it). */
|
|
106
|
+
disarm() {
|
|
107
|
+
try {
|
|
108
|
+
// A second follow surface may have replaced the rendezvous after this session started. Never remove
|
|
109
|
+
// another surface's live handle when this one pauses, is killed, or exits.
|
|
110
|
+
const current = JSON.parse(readFileSync(this.rvPath, 'utf8'));
|
|
111
|
+
if (current.token === this.token)
|
|
112
|
+
rmSync(this.rvPath, { force: true });
|
|
113
|
+
}
|
|
114
|
+
catch { /* absent/malformed/another surface owns it */ }
|
|
115
|
+
this.armed = false;
|
|
116
|
+
}
|
|
117
|
+
/** Read and fold any records appended since the last pump. Public so tests can drive it without timers. */
|
|
118
|
+
pump() {
|
|
119
|
+
let text;
|
|
120
|
+
try {
|
|
121
|
+
const st = statSync(this.channelPath);
|
|
122
|
+
if (st.size <= this.offset) {
|
|
123
|
+
if (st.size < this.offset)
|
|
124
|
+
this.offset = 0;
|
|
125
|
+
else
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
text = readFileSync(this.channelPath, 'utf8');
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const slice = text.slice(this.offset);
|
|
134
|
+
const nl = slice.lastIndexOf('\n');
|
|
135
|
+
if (nl < 0)
|
|
136
|
+
return; // no complete line yet
|
|
137
|
+
this.offset += nl + 1;
|
|
138
|
+
for (const line of slice.slice(0, nl).split('\n')) {
|
|
139
|
+
const t = line.trim();
|
|
140
|
+
if (!t)
|
|
141
|
+
continue;
|
|
142
|
+
let rec;
|
|
143
|
+
try {
|
|
144
|
+
rec = JSON.parse(t);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
this.ingest(rec);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Fold one record: verify its token, strip it, emit into the hub, and move the terminal for a nav call. */
|
|
153
|
+
ingest(rec) {
|
|
154
|
+
if (typeof rec.phase !== 'string')
|
|
155
|
+
return;
|
|
156
|
+
// Only accept records written by the server we handed our token to (local 0600 file; defence in depth).
|
|
157
|
+
if (rec.token !== this.token)
|
|
158
|
+
return;
|
|
159
|
+
this.currentAgent = typeof rec.agent === 'string' && rec.agent ? rec.agent : this.agentLabel;
|
|
160
|
+
// The token NEVER enters the hub / activity / stream — fold only the observable fields.
|
|
161
|
+
const ev = this.hub.emit({
|
|
162
|
+
phase: rec.phase,
|
|
163
|
+
agent: this.currentAgent,
|
|
164
|
+
transport: 'mcp',
|
|
165
|
+
tool: rec.tool ?? null,
|
|
166
|
+
summary: rec.summary,
|
|
167
|
+
ms: rec.ms ?? null,
|
|
168
|
+
dataAgeSec: rec.dataAgeSec ?? null,
|
|
169
|
+
nav: rec.nav ?? null,
|
|
170
|
+
ok: rec.ok ?? true,
|
|
171
|
+
t: typeof rec.t === 'number' ? rec.t : undefined,
|
|
172
|
+
});
|
|
173
|
+
// Move the real terminal ONLY while the agent has control (a user keypress / take-over / kill flips this).
|
|
174
|
+
if (ev.phase === 'nav' && ev.ok !== false && ev.nav && this.canFollow()) {
|
|
175
|
+
const label = navLabel(this.currentAgent, ev.nav);
|
|
176
|
+
try {
|
|
177
|
+
this.opts.onNav?.(ev.nav, label);
|
|
178
|
+
}
|
|
179
|
+
catch { /* a bad hook never breaks the tail */ }
|
|
180
|
+
try {
|
|
181
|
+
this.opts.onStatus?.(label);
|
|
182
|
+
}
|
|
183
|
+
catch { /* ignore */ }
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
this.opts.onEvent?.();
|
|
187
|
+
}
|
|
188
|
+
catch { /* ignore */ }
|
|
189
|
+
}
|
|
190
|
+
/** True when the terminal should mirror the agent: the agent still has control and is active. */
|
|
191
|
+
canFollow() {
|
|
192
|
+
if (this.follow.controlState !== 'agent')
|
|
193
|
+
return false;
|
|
194
|
+
const st = this.follow.view().agents.find((a) => a.name === this.currentAgent);
|
|
195
|
+
return !st || st.status === 'active';
|
|
196
|
+
}
|
|
197
|
+
// ----- the owner's controls. Each returns a short line to toast. -----
|
|
198
|
+
pause() { const l = this.follow.pause(this.currentAgent); this.paused = l.startsWith('paused '); this.opts.onStatus?.(l); return l; }
|
|
199
|
+
takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
|
|
200
|
+
resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
|
|
201
|
+
kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
|
|
202
|
+
/** Any physical keypress while the agent is driving: the human takes over (no fight over the screen). */
|
|
203
|
+
userKey() { if (this.follow.controlState === 'agent')
|
|
204
|
+
this.takeOver(); }
|
|
205
|
+
get controlState() { return this.follow.controlState; }
|
|
206
|
+
get isPaused() { return this.paused; }
|
|
207
|
+
get screenModel() { return this.follow.screenModel; }
|
|
208
|
+
get streamPort() { return this.stream?.port ?? 0; }
|
|
209
|
+
view() { return this.follow.view(); }
|
|
210
|
+
/** Tear down: stop the timer, remove the rendezvous, close the stream. Safe to call more than once. */
|
|
211
|
+
async stop() {
|
|
212
|
+
if (this.timer) {
|
|
213
|
+
clearInterval(this.timer);
|
|
214
|
+
this.timer = null;
|
|
215
|
+
}
|
|
216
|
+
this.disarm();
|
|
217
|
+
if (this.stream) {
|
|
218
|
+
try {
|
|
219
|
+
await this.stream.close();
|
|
220
|
+
}
|
|
221
|
+
catch { /* ignore */ }
|
|
222
|
+
this.stream = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -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.4';
|
|
@@ -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) {
|
|
@@ -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`.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs-agents-capability-tiers-enforced-server-side
|
|
3
|
+
title: Bring your own agent › Capability tiers (enforced server-side)
|
|
4
|
+
kind: docs
|
|
5
|
+
page: agents
|
|
6
|
+
page_title: Bring your own agent
|
|
7
|
+
heading: Capability tiers (enforced server-side)
|
|
8
|
+
anchor: capability-tiers-enforced-server-side
|
|
9
|
+
url: https://dexterm.xyz/docs/agents#capability-tiers-enforced-server-side
|
|
10
|
+
tags:
|
|
11
|
+
- docs
|
|
12
|
+
- agents
|
|
13
|
+
- capability
|
|
14
|
+
- tiers
|
|
15
|
+
- enforced
|
|
16
|
+
- server-side
|
|
17
|
+
- bring
|
|
18
|
+
- own
|
|
19
|
+
- agent
|
|
20
|
+
confidence: high
|
|
21
|
+
owner: maintainers
|
|
22
|
+
origin: docs-generated
|
|
23
|
+
reviewed_by: generated@2026-10-16
|
|
24
|
+
generated_from: dexterm/site content/docs (task-1420)
|
|
25
|
+
sources:
|
|
26
|
+
- site/content/docs/agents.md
|
|
27
|
+
date: 2026-10-16
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
The tier comes from your registry at connection time, never from anything the agent sends; a client may only ask to run *below* its grant.
|
|
31
|
+
|
|
32
|
+
| tier | what it can do |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `observe` | read tools (screener, token, dossier, holders, trades, candles, rugcheck, dev history, the analysis packs `analyze_token` / `narrative_board` / `rug_backtest`, KB, cases, LARP checks) and live stream subscriptions |
|
|
35
|
+
| `annotate` | + non-financial UI navigation and the agent's own notes/tags on coins |
|
|
36
|
+
| `propose` | + proposals (alerts, order-plan drafts, watchlist adds) that appear **labelled with the agent** and that **you** confirm with a keypress |
|
|
37
|
+
|
|
38
|
+
No tier can arm, sign, send, raise or change a cap, change the gate, or reach a wallet/mandate/signer: no such tool exists in the table, and a "never-list" refuses the *shape* of such a request before any lookup.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: docs-agents-install-in-one-command
|
|
3
|
+
title: Bring your own agent › Install in one command
|
|
4
|
+
kind: docs
|
|
5
|
+
page: agents
|
|
6
|
+
page_title: Bring your own agent
|
|
7
|
+
heading: Install in one command
|
|
8
|
+
anchor: install-in-one-command
|
|
9
|
+
url: https://dexterm.xyz/docs/agents#install-in-one-command
|
|
10
|
+
tags:
|
|
11
|
+
- docs
|
|
12
|
+
- agents
|
|
13
|
+
- install
|
|
14
|
+
- command
|
|
15
|
+
- bring
|
|
16
|
+
- own
|
|
17
|
+
- agent
|
|
18
|
+
confidence: high
|
|
19
|
+
owner: maintainers
|
|
20
|
+
origin: docs-generated
|
|
21
|
+
reviewed_by: generated@2026-10-16
|
|
22
|
+
generated_from: dexterm/site content/docs (task-1420)
|
|
23
|
+
sources:
|
|
24
|
+
- site/content/docs/agents.md
|
|
25
|
+
date: 2026-10-16
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
`dexterm mcp install <client>` writes (or prints) the MCP config for your client. It prints the exact snippet, shows the file it will change, **asks first**, is **idempotent** (re-running changes nothing), and **backs the file up** before editing.
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
$ dexterm mcp install claude-code # writes ./.mcp.json in this project (asks first)
|
|
32
|
+
$ dexterm mcp install cursor # writes ~/.cursor/mcp.json
|
|
33
|
+
$ dexterm mcp install claude-desktop # writes the Claude Desktop config for your OS
|
|
34
|
+
$ dexterm mcp install codex # appends [mcp_servers.dexterm] to ~/.codex/config.toml
|
|
35
|
+
$ dexterm mcp install generic --print # just print the snippet to paste anywhere
|
|
36
|
+
$ dexterm mcp doctor # spawn the server and list the tools your agent will see
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| client | what it writes |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `claude-code` | project `.mcp.json` (or run `claude mcp add dexterm -- dexterm mcp`) |
|
|
42
|
+
| `claude-desktop` | `claude_desktop_config.json` for your OS (restart the app after) |
|
|
43
|
+
| `cursor` | `~/.cursor/mcp.json` |
|
|
44
|
+
| `codex` | a `[mcp_servers.dexterm]` table in `~/.codex/config.toml` |
|
|
45
|
+
| `generic` | prints a standard `mcpServers` entry to paste where your agent reads MCP servers (`--config FILE` writes it) |
|
|
46
|
+
|
|
47
|
+
Flags: `--config FILE` writes to a specific file instead of the client default; `--print` only prints; `--node` emits a `node <path> mcp` command for when `dexterm` is not on your PATH; `--yes` skips the confirmation prompt. On a non-interactive shell the command prints the plan and writes nothing unless you pass `--yes`.
|
|
48
|
+
|
|
49
|
+
`dexterm mcp doctor` spawns the server over stdio, does the MCP handshake, and lists the tools, prompts and resources the agent sees — proof the connection works before you point an agent at it.
|