dexterm-cli 0.1.2 → 0.1.4

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