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.
Files changed (221) hide show
  1. package/dist/packages/cli/assets/claude/analyst-system.md +2 -1
  2. package/dist/packages/cli/src/app-help-gen.js +3 -3
  3. package/dist/packages/cli/src/docs-kb-gen.js +228 -0
  4. package/dist/packages/cli/src/doctor.js +22 -7
  5. package/dist/packages/cli/src/index.js +1 -1
  6. package/dist/packages/cli/src/kb-skill-cmd.js +3 -0
  7. package/dist/packages/cli/src/larp-detected.js +209 -0
  8. package/dist/packages/cli/src/live/token.js +38 -1
  9. package/dist/packages/cli/src/main.js +140 -1
  10. package/dist/packages/cli/src/mcp-follow.js +225 -0
  11. package/dist/packages/cli/src/spectate-cmd.js +14 -3
  12. package/dist/packages/cli/src/version.js +1 -1
  13. package/dist/packages/cli/src/x-cmd.js +11 -5
  14. package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +4 -1
  15. package/dist/packages/mcp/assets/kb/docs/docs-agents-capability-tiers-enforced-server-side.md +38 -0
  16. package/dist/packages/mcp/assets/kb/docs/docs-agents-install-in-one-command.md +49 -0
  17. package/dist/packages/mcp/assets/kb/docs/docs-agents-per-agent-mandates.md +35 -0
  18. package/dist/packages/mcp/assets/kb/docs/docs-agents-recipes.md +43 -0
  19. package/dist/packages/mcp/assets/kb/docs/docs-agents-red-team-coverage.md +30 -0
  20. package/dist/packages/mcp/assets/kb/docs/docs-agents-two-ways-to-connect.md +37 -0
  21. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +36 -0
  22. package/dist/packages/mcp/assets/kb/docs/docs-agents-watch-an-agent-work-spectator-view.md +35 -0
  23. package/dist/packages/mcp/assets/kb/docs/docs-agents.md +26 -0
  24. package/dist/packages/mcp/assets/kb/docs/docs-analyst-answer-style.md +35 -0
  25. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +25 -0
  26. package/dist/packages/mcp/assets/kb/docs/docs-analyst-it-knows-the-app.md +33 -0
  27. package/dist/packages/mcp/assets/kb/docs/docs-analyst-mcp-tools.md +33 -0
  28. package/dist/packages/mcp/assets/kb/docs/docs-analyst-plain-language-filters.md +25 -0
  29. package/dist/packages/mcp/assets/kb/docs/docs-analyst-skills.md +31 -0
  30. package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +33 -0
  31. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-can-change-three-tiers.md +33 -0
  32. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +26 -0
  33. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-the-analyst-can-never-do.md +31 -0
  34. package/dist/packages/mcp/assets/kb/docs/docs-analyst.md +23 -0
  35. package/dist/packages/mcp/assets/kb/docs/docs-beginners-1-start-dexterm.md +35 -0
  36. package/dist/packages/mcp/assets/kb/docs/docs-beginners-2-open-a-real-coin-by-contract-address.md +43 -0
  37. package/dist/packages/mcp/assets/kb/docs/docs-beginners-3-read-the-safety-panel.md +40 -0
  38. package/dist/packages/mcp/assets/kb/docs/docs-beginners-4-read-the-holder-panel.md +35 -0
  39. package/dist/packages/mcp/assets/kb/docs/docs-beginners-5-optionally-ask-the-analyst.md +39 -0
  40. package/dist/packages/mcp/assets/kb/docs/docs-beginners-6-export-or-share-what-you-see.md +34 -0
  41. package/dist/packages/mcp/assets/kb/docs/docs-beginners-7-quit-safely.md +34 -0
  42. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +33 -0
  43. package/dist/packages/mcp/assets/kb/docs/docs-beginners-check-the-installation-with-doctor.md +38 -0
  44. package/dist/packages/mcp/assets/kb/docs/docs-beginners-choose-a-setup-path.md +30 -0
  45. package/dist/packages/mcp/assets/kb/docs/docs-beginners-common-mistakes-and-recovery.md +30 -0
  46. package/dist/packages/mcp/assets/kb/docs/docs-beginners-copy-and-paste-without-surprises.md +39 -0
  47. package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-data-says-unavailable-stale-or-rate-limited.md +35 -0
  48. package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-dexterm-says-command-not-found.md +36 -0
  49. package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-optional-help-is-unavailable.md +33 -0
  50. package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-paste-does-nothing-or-pastes-twice.md +35 -0
  51. package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-the-screen-looks-broken.md +33 -0
  52. package/dist/packages/mcp/assets/kb/docs/docs-beginners-if-you-typed-the-wrong-thing.md +33 -0
  53. package/dist/packages/mcp/assets/kb/docs/docs-beginners-install-node-js-first.md +36 -0
  54. package/dist/packages/mcp/assets/kb/docs/docs-beginners-linux.md +30 -0
  55. package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-homebrew.md +46 -0
  56. package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos-or-linux-npm.md +39 -0
  57. package/dist/packages/mcp/assets/kb/docs/docs-beginners-macos.md +30 -0
  58. package/dist/packages/mcp/assets/kb/docs/docs-beginners-open-the-terminal.md +29 -0
  59. package/dist/packages/mcp/assets/kb/docs/docs-beginners-reopen-dexterm-later.md +36 -0
  60. package/dist/packages/mcp/assets/kb/docs/docs-beginners-ten-minute-first-win.md +30 -0
  61. package/dist/packages/mcp/assets/kb/docs/docs-beginners-terminal-shell-prompt-command-output-and-path.md +40 -0
  62. package/dist/packages/mcp/assets/kb/docs/docs-beginners-where-to-go-next.md +32 -0
  63. package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-expectations.md +29 -0
  64. package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows-powershell-experimental.md +39 -0
  65. package/dist/packages/mcp/assets/kb/docs/docs-beginners-windows.md +30 -0
  66. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +29 -0
  67. package/dist/packages/mcp/assets/kb/docs/docs-cli-dexterm-help.md +31 -0
  68. package/dist/packages/mcp/assets/kb/docs/docs-cli-filter-grammar.md +37 -0
  69. package/dist/packages/mcp/assets/kb/docs/docs-cli-flags.md +27 -0
  70. package/dist/packages/mcp/assets/kb/docs/docs-cli-invocations.md +43 -0
  71. package/dist/packages/mcp/assets/kb/docs/docs-cli-presets.md +26 -0
  72. package/dist/packages/mcp/assets/kb/docs/docs-cli-scan-commands.md +26 -0
  73. package/dist/packages/mcp/assets/kb/docs/docs-cli-sort-keys.md +27 -0
  74. package/dist/packages/mcp/assets/kb/docs/docs-cli-subcommands.md +26 -0
  75. package/dist/packages/mcp/assets/kb/docs/docs-cli.md +25 -0
  76. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +45 -0
  77. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +41 -0
  78. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +44 -0
  79. package/dist/packages/mcp/assets/kb/docs/docs-configuration-history-recorder.md +33 -0
  80. package/dist/packages/mcp/assets/kb/docs/docs-configuration-packs.md +38 -0
  81. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +35 -0
  82. package/dist/packages/mcp/assets/kb/docs/docs-configuration-push-alerts-and-dexterm-watch.md +45 -0
  83. package/dist/packages/mcp/assets/kb/docs/docs-configuration-rules-engine.md +36 -0
  84. package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-analyst-s-launch.md +27 -0
  85. package/dist/packages/mcp/assets/kb/docs/docs-configuration-the-optional-free-rpc-key.md +42 -0
  86. package/dist/packages/mcp/assets/kb/docs/docs-configuration-themes-and-keymaps.md +29 -0
  87. package/dist/packages/mcp/assets/kb/docs/docs-configuration-themes-and-look.md +27 -0
  88. package/dist/packages/mcp/assets/kb/docs/docs-configuration-widgets.md +31 -0
  89. package/dist/packages/mcp/assets/kb/docs/docs-configuration-workspaces.md +30 -0
  90. package/dist/packages/mcp/assets/kb/docs/docs-configuration.md +25 -0
  91. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-1-follow-a-wallet-see-what-it-does.md +51 -0
  92. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +56 -0
  93. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +47 -0
  94. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +36 -0
  95. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +31 -0
  96. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-cache.md +32 -0
  97. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-health-registry.md +29 -0
  98. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-rate-limits-and-pacing.md +32 -0
  99. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-request-budget-meter.md +30 -0
  100. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +47 -0
  101. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-the-helius-credit-meter.md +30 -0
  102. package/dist/packages/mcp/assets/kb/docs/docs-data-sources.md +27 -0
  103. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-dexterm-doctor.md +33 -0
  104. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +34 -0
  105. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +71 -0
  106. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-next.md +32 -0
  107. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +35 -0
  108. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +34 -0
  109. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-supported-platforms.md +44 -0
  110. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-updating.md +28 -0
  111. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +27 -0
  112. package/dist/packages/mcp/assets/kb/docs/docs-keyboard-global-keys.md +26 -0
  113. package/dist/packages/mcp/assets/kb/docs/docs-keyboard-keys-handled-by-the-loop.md +29 -0
  114. package/dist/packages/mcp/assets/kb/docs/docs-keyboard-per-screen-keys.md +26 -0
  115. package/dist/packages/mcp/assets/kb/docs/docs-keyboard-what-shows.md +27 -0
  116. package/dist/packages/mcp/assets/kb/docs/docs-keyboard.md +26 -0
  117. package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +55 -0
  118. package/dist/packages/mcp/assets/kb/docs/docs-larp-open-link-keys.md +26 -0
  119. package/dist/packages/mcp/assets/kb/docs/docs-larp-paste-a-coin-on-the-web.md +35 -0
  120. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +29 -0
  121. package/dist/packages/mcp/assets/kb/docs/docs-larp-timestamped-follow-up-drafts.md +35 -0
  122. package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +33 -0
  123. package/dist/packages/mcp/assets/kb/docs/docs-larp.md +24 -0
  124. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-degraded-states-you-will-see.md +36 -0
  125. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-free-vs-paid-x-api-tiers-your-bill-your-limits.md +37 -0
  126. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-kol-lists-and-the-per-handle-filter.md +28 -0
  127. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-tests.md +25 -0
  128. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-what-is-read-and-when.md +35 -0
  129. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-what-you-set-up-once.md +40 -0
  130. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x-where-the-tokens-live.md +27 -0
  131. package/dist/packages/mcp/assets/kb/docs/docs-login-with-x.md +26 -0
  132. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +42 -0
  133. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +34 -0
  134. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +35 -0
  135. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +33 -0
  136. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +32 -0
  137. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +30 -0
  138. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +31 -0
  139. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +44 -0
  140. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +31 -0
  141. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +37 -0
  142. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +41 -0
  143. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +34 -0
  144. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +31 -0
  145. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +34 -0
  146. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +32 -0
  147. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +37 -0
  148. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +41 -0
  149. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +32 -0
  150. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-bubble-map-popup.md +27 -0
  151. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-build-the-native-window.md +33 -0
  152. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-config-dexterm-popup-json.md +38 -0
  153. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-docking-macos.md +27 -0
  154. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-keys-in-the-popup.md +30 -0
  155. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +28 -0
  156. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-security-model.md +33 -0
  157. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-token-and-feeds-popups.md +38 -0
  158. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +30 -0
  159. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart.md +27 -0
  160. package/dist/packages/mcp/assets/kb/docs/docs-scan-cost-control.md +28 -0
  161. package/dist/packages/mcp/assets/kb/docs/docs-scan-keys-flag-command.md +34 -0
  162. package/dist/packages/mcp/assets/kb/docs/docs-scan-safety-four-layers.md +32 -0
  163. package/dist/packages/mcp/assets/kb/docs/docs-scan-states.md +27 -0
  164. package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +25 -0
  165. package/dist/packages/mcp/assets/kb/docs/docs-scan-triggers-and-score.md +37 -0
  166. package/dist/packages/mcp/assets/kb/docs/docs-scan.md +24 -0
  167. package/dist/packages/mcp/assets/kb/docs/docs-scorecard-frozen-canary.md +30 -0
  168. package/dist/packages/mcp/assets/kb/docs/docs-scorecard-frozen-task-1401-evaluation.md +35 -0
  169. package/dist/packages/mcp/assets/kb/docs/docs-scorecard-latest-skill-care-run.md +33 -0
  170. package/dist/packages/mcp/assets/kb/docs/docs-scorecard-links-only-larp-baseline-where-it-is-weak.md +35 -0
  171. package/dist/packages/mcp/assets/kb/docs/docs-scorecard-not-measured.md +29 -0
  172. package/dist/packages/mcp/assets/kb/docs/docs-scorecard-rug-verdict-rules.md +33 -0
  173. package/dist/packages/mcp/assets/kb/docs/docs-scorecard-source-and-rebuild.md +31 -0
  174. package/dist/packages/mcp/assets/kb/docs/docs-scorecard.md +27 -0
  175. package/dist/packages/mcp/assets/kb/docs/docs-screens-1-screener.md +35 -0
  176. package/dist/packages/mcp/assets/kb/docs/docs-screens-2-new-pairs.md +28 -0
  177. package/dist/packages/mcp/assets/kb/docs/docs-screens-3-watchlist.md +27 -0
  178. package/dist/packages/mcp/assets/kb/docs/docs-screens-4-analyst-tab.md +28 -0
  179. package/dist/packages/mcp/assets/kb/docs/docs-screens-bubble-map.md +32 -0
  180. package/dist/packages/mcp/assets/kb/docs/docs-screens-coin-images.md +32 -0
  181. package/dist/packages/mcp/assets/kb/docs/docs-screens-live-holder-and-rug-signals.md +51 -0
  182. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +26 -0
  183. package/dist/packages/mcp/assets/kb/docs/docs-screens-paper-ticket-and-positions.md +29 -0
  184. package/dist/packages/mcp/assets/kb/docs/docs-screens-the-look.md +29 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +36 -0
  186. package/dist/packages/mcp/assets/kb/docs/docs-screens-trending-social-and-kol-lists.md +34 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-screens.md +26 -0
  188. package/dist/packages/mcp/assets/kb/docs/docs-spectator-1-the-agent-activity-window-follow-mode.md +56 -0
  189. package/dist/packages/mcp/assets/kb/docs/docs-spectator-2-the-read-only-stream-spectate.md +42 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-spectator-3-save-and-replay-record-file-dexterm-spectate.md +41 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +39 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +36 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-spectator-quick-start.md +37 -0
  194. package/dist/packages/mcp/assets/kb/docs/docs-spectator-security-model.md +35 -0
  195. package/dist/packages/mcp/assets/kb/docs/docs-spectator-see-also.md +29 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-spectator-streaming-it.md +32 -0
  197. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +37 -0
  198. package/dist/packages/mcp/assets/kb/docs/docs-spectator-what-is-redacted.md +35 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +29 -0
  200. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +33 -0
  201. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +36 -0
  202. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-in-app-messages.md +42 -0
  203. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-reporting-a-problem.md +25 -0
  204. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting.md +23 -0
  205. package/dist/packages/mcp/assets/kb/docs/docs-voice-doctor.md +26 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-voice-it-clarifies-it-never-guesses.md +36 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-voice-latency.md +38 -0
  208. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +32 -0
  209. package/dist/packages/mcp/assets/kb/docs/docs-voice-privacy.md +26 -0
  210. package/dist/packages/mcp/assets/kb/docs/docs-voice-still-unverified.md +27 -0
  211. package/dist/packages/mcp/assets/kb/docs/docs-voice-the-voice-neural-by-default.md +48 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-voice-what-it-can-never-do.md +27 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-voice-what-you-can-say.md +38 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +25 -0
  215. package/dist/packages/mcp/src/app-help.js +123 -0
  216. package/dist/packages/mcp/src/firewall.js +2 -2
  217. package/dist/packages/mcp/src/index.js +5 -4
  218. package/dist/packages/mcp/src/server.js +56 -18
  219. package/dist/packages/mcp/src/spectator-sink.js +74 -2
  220. package/dist/packages/voice/src/stt/whisper.js +4 -2
  221. 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 home = io.env.DEXTERM_HOME || join(io.env.HOME || '', '.dexterm');
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';
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
- 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`);
56
- if ((modes.tokens !== null && modes.tokens !== 0o600) || (modes.dir !== null && modes.dir !== 0o700))
57
- io.stderr.write('⚠ permissions are looser than 0600/0700; dexterm will tighten them on the next write (or chmod them now)\n');
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 spectate --replay FILE` replays a saved agent session so you can watch what an agent did step by step; `dexterm spectate --info` shows the live read-only, token-gated, loopback spectator stream. The stream 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.
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.