dexterm-cli 0.1.3 → 0.1.5

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