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
@@ -0,0 +1,228 @@
1
+ // dexterm — generator for the docs knowledge base (task-1420).
2
+ //
3
+ // Builds the `@dexterm/mcp` docs KB entries (assets/kb/docs/*.md) from EVERY page of the site's docs
4
+ // (dexterm/site/content/docs/*.md), chunked by heading, so the analyst can answer from the docs and cite the
5
+ // exact page section (https://dexterm.xyz/docs/<slug>#<anchor>). The app-help KB (app-help-gen.ts) stays the
6
+ // authority for an exact key or command; these entries carry the docs' explanations and never a structured
7
+ // key/command/flag/tool list (docs text is data: nothing in it becomes a key, a command or an action).
8
+ //
9
+ // The site is a sibling checkout (../site from the app root) that a public checkout does not have: `haveSite`
10
+ // is false there, the drift test skips, and the committed entries still ship.
11
+ //
12
+ // Deterministic by construction: files in name order, no clock, no network, no writes here (the script does
13
+ // that). The site's `{{placeholders}}` are filled from the app at site build time; they are NOT expanded here
14
+ // (that would tie the KB to the app's current commit). `{{minNode}}` comes from site.json; the commit stamp
15
+ // sentences are dropped; every other placeholder becomes a pointer to the docs page section.
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
20
+ /** App root: packages/cli/src → up three. */
21
+ const ROOT = path.resolve(HERE, '..', '..', '..');
22
+ /** The site checkout next to the app (absent in a public checkout). `DEXTERM_SITE_DIR` overrides it. */
23
+ export const SITE_DIR = process.env.DEXTERM_SITE_DIR || path.resolve(ROOT, '..', 'site');
24
+ export const haveSite = fs.existsSync(path.join(SITE_DIR, 'content', 'docs'));
25
+ /** Stamped into every generated entry; bump on a deliberate regenerate. */
26
+ export const DOCS_GENERATED_DATE = '2026-10-16';
27
+ export const DOCS_BASE_URL = 'https://dexterm.xyz';
28
+ const SLUG_FILE = /^[a-z0-9][a-z0-9-]*\.md$/;
29
+ const MAX_ID = 64;
30
+ /** The site's heading slug (site/lib/html.mjs `slug`), applied to heading text without backticks. */
31
+ export function siteSlug(s) {
32
+ return String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
33
+ }
34
+ /** The site's frontmatter split (site/lib/pages/docs.mjs `frontMatter`): flat `key: value` lines. */
35
+ function siteFrontMatter(src) {
36
+ const m = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(src.replace(/\r/g, ''));
37
+ if (!m)
38
+ return { meta: {}, body: src };
39
+ const meta = {};
40
+ for (const line of m[1].split('\n')) {
41
+ const i = line.indexOf(':');
42
+ if (i > 0)
43
+ meta[line.slice(0, i).trim()] = line.slice(i + 1).trim();
44
+ }
45
+ return { meta, body: m[2] };
46
+ }
47
+ /** One frontmatter value on one line: no newlines, no leading/trailing quote the KB parser would strip. */
48
+ function oneLine(s) {
49
+ return s.replace(/[\u0000-\u001F\u007F]/g, ' ').replace(/\s+/g, ' ').trim().replace(/^"+|"+$/g, '');
50
+ }
51
+ /** Absolute URL for a docs link: `/docs/x/#a` → https://dexterm.xyz/docs/x#a, `#a` → this page. */
52
+ export function docsUrl(href, pageUrl) {
53
+ if (/^https?:\/\//i.test(href))
54
+ return href;
55
+ if (href.startsWith('#'))
56
+ return `${pageUrl}${href}`;
57
+ if (!href.startsWith('/'))
58
+ return href;
59
+ const d = /^\/docs\/([a-z0-9-]+)\/?(#[a-z0-9-]+)?$/.exec(href);
60
+ if (d)
61
+ return `${DOCS_BASE_URL}/docs/${d[1]}${d[2] ?? ''}`;
62
+ return `${DOCS_BASE_URL}${href}`;
63
+ }
64
+ const HTML_TAG = /<\/?(?:kbd|code|a|em|strong|b|i|br|span|div|p|details|summary|table|thead|tbody|tr|td|th|img|ul|ol|li|sup|sub|small|figure|figcaption|hr)\b[^>]*>/gi;
65
+ const TAG_STOP = new Set(['the', 'and', 'for', 'with', 'what', 'how', 'you', 'your', 'its', 'not', 'one', 'see', 'also', 'from', 'into', 'when', 'it', 'is', 'of', 'to', 'in', 'on', 'a', 'an', 'vs', 'by', 'or']);
66
+ /** Prose → KB body: placeholders resolved, links made absolute, inline HTML tags dropped (code fences kept as-is). */
67
+ function cleanBody(lines, ctx) {
68
+ const out = [];
69
+ let fence = false;
70
+ for (const raw of lines) {
71
+ let line = raw;
72
+ if (/^```/.test(line)) {
73
+ fence = !fence;
74
+ out.push(line);
75
+ continue;
76
+ }
77
+ if (!fence) {
78
+ // The commit-stamp sentences ("Generated against … at commit {{head}}.") describe the site build, not the app.
79
+ line = line.replace(/(^|(?<=[.!?]\s))[^.!?\n]*\{\{head(?:Date)?\}\}[^\n]*?[.!?](?=\s|$)\s*/g, '');
80
+ line = line.replace(/\[\[([^\]]+)\]\]/g, '`$1`'); // the site's [[key]] → <kbd> shorthand
81
+ line = line.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1');
82
+ line = line.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, text, href) => `${text} (${docsUrl(href, ctx.pageUrl)})`);
83
+ line = line.replace(HTML_TAG, '');
84
+ }
85
+ line = line.replace(/\{\{minNode\}\}/g, ctx.nodeMin ?? `(see ${ctx.sectionUrl})`);
86
+ line = line.replace(/\{\{[^}]*\}\}/g, `(generated from the app: see ${ctx.sectionUrl}; app-help has the exact keys and commands)`);
87
+ out.push(line.replace(/\s+$/, ''));
88
+ }
89
+ return out.join('\n').replace(/\n{3,}/g, '\n\n').trim();
90
+ }
91
+ function tagsFor(...texts) {
92
+ const out = [];
93
+ for (const t of texts) {
94
+ for (const w of t.replace(/`/g, '').toLowerCase().match(/[a-z0-9][a-z0-9-]+/g) ?? []) {
95
+ if (w.length < 3 || TAG_STOP.has(w) || out.includes(w))
96
+ continue;
97
+ out.push(w);
98
+ }
99
+ }
100
+ return out.slice(0, 12);
101
+ }
102
+ /** A KB id ≤ 64 chars (cut at a dash), unique within this run. */
103
+ function uniqueId(base, used) {
104
+ let id = base;
105
+ if (id.length > MAX_ID) {
106
+ id = id.slice(0, MAX_ID);
107
+ const d = id.lastIndexOf('-');
108
+ if (d > 20)
109
+ id = id.slice(0, d);
110
+ }
111
+ let out = id, n = 2;
112
+ while (used.has(out)) {
113
+ const suf = `-${n++}`;
114
+ out = id.slice(0, MAX_ID - suf.length) + suf;
115
+ }
116
+ used.add(out);
117
+ return out;
118
+ }
119
+ /** Split a page into its intro + one section per h2/h3, with anchors exactly as the site renders them. */
120
+ export function splitPage(body) {
121
+ const ids = new Set();
122
+ const uid = (text) => { const base = siteSlug(text.replace(/`/g, '')); let id = base, n = 2; while (ids.has(id))
123
+ id = `${base}-${n++}`; ids.add(id); return id; };
124
+ const lines = body.replace(/\r/g, '').split('\n');
125
+ const intro = [];
126
+ const sections = [];
127
+ let cur = intro;
128
+ for (let i = 0; i < lines.length; i++) {
129
+ const line = lines[i];
130
+ if (/^```/.test(line)) {
131
+ // a fenced block: copied whole, never scanned for headings (mirrors the site renderer)
132
+ cur.push(line);
133
+ for (i++; i < lines.length && !/^```/.test(lines[i]); i++)
134
+ cur.push(lines[i]);
135
+ if (i < lines.length)
136
+ cur.push(lines[i]);
137
+ continue;
138
+ }
139
+ if (/^<(?!kbd|code|a |em|strong)[a-z!]/.test(line)) {
140
+ // a raw HTML block (until a blank line): the site does not read headings inside it either
141
+ for (; i < lines.length && lines[i].trim(); i++)
142
+ cur.push(lines[i]);
143
+ i--;
144
+ continue;
145
+ }
146
+ const h = /^(#{1,4})\s+(.+?)\s*(?:\{#([a-z0-9-]+)\})?$/.exec(line);
147
+ if (h) {
148
+ const level = h[1].length;
149
+ const anchor = h[3] ? (ids.add(h[3]), h[3]) : uid(h[2]);
150
+ if (level === 2 || level === 3) {
151
+ const s = { level, heading: h[2].replace(/`/g, ''), anchor, lines: [] };
152
+ sections.push(s);
153
+ cur = s.lines;
154
+ continue;
155
+ }
156
+ cur.push(line); // h1/h4 stay inside their section as text
157
+ continue;
158
+ }
159
+ cur.push(line);
160
+ }
161
+ return { intro, sections };
162
+ }
163
+ /** Every docs page file (name order), or [] when the site is absent. */
164
+ export function docsPages(siteDir = SITE_DIR) {
165
+ const dir = path.join(siteDir, 'content', 'docs');
166
+ try {
167
+ return fs.readdirSync(dir).filter((f) => SLUG_FILE.test(f)).sort();
168
+ }
169
+ catch {
170
+ return [];
171
+ }
172
+ }
173
+ /** Build every docs KB entry from the site's docs pages. Throws when the site has no docs directory. */
174
+ export function buildDocsEntries(siteDir = SITE_DIR) {
175
+ const dir = path.join(siteDir, 'content', 'docs');
176
+ if (!fs.existsSync(dir))
177
+ throw new Error(`no site docs at ${dir} (the site checkout is not next to the app)`);
178
+ let nodeMin = null;
179
+ try {
180
+ nodeMin = String(JSON.parse(fs.readFileSync(path.join(siteDir, 'site.json'), 'utf8')).nodeMin ?? '') || null;
181
+ }
182
+ catch { /* no site.json */ }
183
+ const out = [];
184
+ const used = new Set();
185
+ for (const file of docsPages(siteDir)) {
186
+ const slug = file.slice(0, -3);
187
+ const { meta, body } = siteFrontMatter(fs.readFileSync(path.join(dir, file), 'utf8'));
188
+ const pageTitle = oneLine(meta.title || slug);
189
+ const description = oneLine(meta.description || '');
190
+ const pageUrl = `${DOCS_BASE_URL}/docs/${slug}`;
191
+ const { intro, sections } = splitPage(body);
192
+ const entry = (heading, anchor, lines, parent) => {
193
+ const url = anchor ? `${pageUrl}#${anchor}` : pageUrl;
194
+ let text = cleanBody(lines, { pageUrl, sectionUrl: url, nodeMin });
195
+ if (!anchor && description)
196
+ text = text ? `${description}\n\n${text}` : description;
197
+ if (!text)
198
+ return;
199
+ const head = oneLine(heading);
200
+ const title = head ? `${pageTitle} › ${parent ? `${oneLine(parent)} › ` : ''}${head}` : pageTitle;
201
+ const id = uniqueId(anchor ? `docs-${slug}-${anchor}` : `docs-${slug}`, used);
202
+ const fmo = [
203
+ ['id', id], ['title', title], ['kind', 'docs'], ['page', slug], ['page_title', pageTitle],
204
+ ['heading', head], ['anchor', anchor], ['url', url],
205
+ ['tags', ['docs', slug, ...tagsFor(head, parent ?? '', pageTitle).filter((t) => t !== slug && t !== 'docs')]],
206
+ ['confidence', 'high'], ['owner', 'maintainers'], ['origin', 'docs-generated'],
207
+ ['reviewed_by', `generated@${DOCS_GENERATED_DATE}`],
208
+ ['generated_from', 'dexterm/site content/docs (task-1420)'],
209
+ ['sources', [`site/content/docs/${file}`]],
210
+ ['date', DOCS_GENERATED_DATE],
211
+ ];
212
+ const frontmatter = {};
213
+ for (const [k, v] of fmo)
214
+ if (v !== '' && !(Array.isArray(v) && v.length === 0))
215
+ frontmatter[k] = v;
216
+ out.push({ id, frontmatter, body: text });
217
+ };
218
+ entry('', '', intro, null);
219
+ let h2 = null;
220
+ for (const s of sections) {
221
+ if (s.level === 2)
222
+ h2 = s.heading;
223
+ entry(s.heading, s.anchor, s.lines, s.level === 3 ? h2 : null);
224
+ }
225
+ }
226
+ out.sort((a, b) => a.id.localeCompare(b.id));
227
+ return out;
228
+ }
@@ -14,16 +14,24 @@ import { shellBinaryPath } from '../../popup/src/index.js';
14
14
  import { detectColorDepth, protocolFromEnv } from '../../tui/src/index.js';
15
15
  import { resolveRpcUrl, defaultDbPath } from '../../data/src/index.js';
16
16
  import { XTokenStore } from '../../data/src/index.js';
17
- import { SayTts, voiceDoctor, resolveVoice, WHISPER_MODEL, resolvePiperBin, pickPlayer, PIPER_MODEL } from '../../voice/src/index.js';
17
+ import { SayTts, voiceDoctor, resolveVoice, WHISPER_MODEL, resolvePiperRuntime, pickPlayer, PIPER_MODEL } from '../../voice/src/index.js';
18
18
  import { loadConfig, dextermHome } from '../../custom/src/index.js';
19
19
  import { kbStore, skillStore } from "./kb-skill-cmd.js";
20
20
  /** The minimum Node that runs dexterm (type-stripping of the .ts entry needs 22.18). */
21
21
  export const MIN_NODE = [22, 18, 0];
22
- /** Cheap PATH lookup (no spawn) for the voice probe. */
23
- function onPath(bin) {
24
- for (const p of (process.env.PATH ?? '').split(':'))
25
- if (p && existsSync(join(p, bin)))
26
- return true;
22
+ /** Cheap PATH lookup (no spawn). Windows (task-1423): PATH is `;`-separated and commands carry an extension
23
+ * (`claude.cmd` from npm, `claude.exe` from the native installer), so try the PATHEXT list there. */
24
+ export function onPath(bin, env = process.env, platform = process.platform) {
25
+ const win = platform === 'win32';
26
+ // Windows file names are case-insensitive; lower-case keeps the lookup exact on the case-sensitive test hosts
27
+ const exts = win ? ['', ...(env.PATHEXT || '.COM;.EXE;.BAT;.CMD').toLowerCase().split(';').filter(Boolean)] : [''];
28
+ for (const p of (env.PATH ?? env.Path ?? '').split(win ? ';' : ':')) {
29
+ if (!p)
30
+ continue;
31
+ for (const ext of exts)
32
+ if (existsSync(join(p, bin + ext)))
33
+ return true;
34
+ }
27
35
  return false;
28
36
  }
29
37
  /**
@@ -42,10 +50,11 @@ export async function defaultVoiceProbe(platform = process.platform, configuredV
42
50
  const resolvedStt = swiftcPresent ? 'apple' : (whisperBinPresent && whisperModelPresent && micToolPresent) ? 'whisper' : null;
43
51
  // espeak-ng is the baseline portable speech-out engine off macOS.
44
52
  const espeakPresent = platform !== 'darwin' && (onPath('espeak-ng') || onPath('espeak'));
45
- // Piper is the preferred neural speech-out engine off macOS (task-1408): binary + voice model + a player.
46
- const piperPresent = platform !== 'darwin' && resolvePiperBin() !== null;
47
- const piperVoicePresent = platform !== 'darwin' && existsSync(PIPER_MODEL);
48
- const playerName = platform !== 'darwin' ? pickPlayer() : null;
53
+ // Piper is the preferred neural speech-out engine EVERYWHERE (task-1408, task-1416): a runtime (the
54
+ // prebuilt binary off macOS, the managed venv on macOS) + the voice model + a player (afplay on macOS).
55
+ const piperPresent = resolvePiperRuntime() !== null;
56
+ const piperVoicePresent = existsSync(PIPER_MODEL);
57
+ const playerName = pickPlayer();
49
58
  // task-1393: show which voice will actually speak, and why — never a silent system/novelty voice.
50
59
  const r = sayAvailable ? resolveVoice({ configured: configuredVoice, installed: voices }) : { voice: null, reason: '' };
51
60
  return {
@@ -103,7 +112,12 @@ export async function livingProbe(url, timeoutMs = 4000) {
103
112
  }
104
113
  }
105
114
  /** Default claude probe: spawn `claude --version` with a timeout. Presence only (login is not asserted here). */
106
- export async function claudeProbe(timeoutMs = 4000) {
115
+ export async function claudeProbe(timeoutMs = 4000, platform = process.platform, env = process.env) {
116
+ // Windows (task-1423): answer from PATH without a spawn, and count only what the analyst's shell-less spawn can
117
+ // start: `claude.exe` / `.com` (the native installer). An npm `claude.cmd` alone is 'missing', because the
118
+ // analyst cannot run it, so "found on PATH" would be a false pass.
119
+ if (platform === 'win32')
120
+ return onPath('claude', { ...env, PATHEXT: '.EXE;.COM' }, platform) ? 'present' : 'missing';
107
121
  const { execFile } = await import('node:child_process');
108
122
  return new Promise((resolve) => {
109
123
  let done = false;
@@ -236,8 +250,10 @@ export async function runDoctor(deps) {
236
250
  const modes = xs.modes();
237
251
  const clientId = xs.clientId(deps.env);
238
252
  if (meta.loggedIn) {
253
+ const windows = deps.platform === 'win32';
239
254
  const loose = (modes.tokens !== null && modes.tokens !== 0o600) || (modes.dir !== null && modes.dir !== 0o700);
240
- checks.push({ name: 'Login with X', status: loose ? 'warn' : 'pass', detail: `logged in${meta.user ? ` as @${meta.user.username}` : ''} · scopes ${meta.scopes.join(' ') || '?'} · refresh ${meta.hasRefresh ? 'yes' : 'no'}${loose ? ' · token file permissions are looser than 0600' : ''}`, ...(loose ? { fix: `chmod 700 ${xs.dir} && chmod 600 ${xs.tokensPath}` } : {}) });
255
+ const warning = windows ? ' · Windows ACL isolation is not verified' : loose ? ' · token file permissions are looser than 0600' : '';
256
+ checks.push({ name: 'Login with X', status: windows || loose ? 'warn' : 'pass', detail: `logged in${meta.user ? ` as @${meta.user.username}` : ''} · scopes ${meta.scopes.join(' ') || '?'} · refresh ${meta.hasRefresh ? 'yes' : 'no'}${warning}`, ...(windows ? { fix: 'Review the token directory Security permissions in Windows; chmod does not enforce owner-only ACLs.' } : loose ? { fix: `chmod 700 ${xs.dir} && chmod 600 ${xs.tokensPath}` } : {}) });
241
257
  }
242
258
  else if (clientId) {
243
259
  checks.push({ name: 'Login with X', status: 'warn', detail: 'client id stored, not logged in — X posts stay off (keyless venues, news and on-chain rows still work)', fix: 'dexterm x login' });
@@ -15,8 +15,11 @@
15
15
  // Helius key (keyless prints the fix) and reads public history. Secrets never print: the RPC URL stays inside the
16
16
  // data layer.
17
17
  import { createDataLayer, resolveRpcUrl, HeliusHistory } from '../../data/src/index.js';
18
- import { FollowStore, WalletTracker, defaultFollowsPath, isWalletAddress, shortAddr, CopyAuto, defaultCopyJournalPath, AUTO_HARD, autoSizeCap } from '../../data/src/trade/copy/index.js';
18
+ import { FollowStore, WalletTracker, defaultFollowsPath, isWalletAddress, shortAddr, fmtHoldMs, CopyAuto, defaultCopyJournalPath, AUTO_HARD, autoSizeCap, buildMirror, leaderSoldSince } from '../../data/src/trade/copy/index.js';
19
19
  import { PlanService, defaultPlansJournalPath } from '../../data/src/trade/plans/index.js';
20
+ import { CopyLive } from "./live/copy.js";
21
+ /** Default per-mirror cap for the headless path (same as the TUI's MIRROR_DEFAULT_CAP_USD); overridable with --cap. */
22
+ export const FOLLOW_MIRROR_DEFAULT_CAP_USD = 20;
20
23
  function flag(rest, name) { const i = rest.indexOf(name); return i >= 0 ? rest[i + 1] : undefined; }
21
24
  function has(rest, name) { return rest.includes(name); }
22
25
  const USAGE = `dexterm follow <command>
@@ -25,6 +28,9 @@ const USAGE = `dexterm follow <command>
25
28
  list [--json]
26
29
  stats <addr> [--json] one Helius history fetch: trades, FIFO realised PnL, win rate (hidden under n=10)
27
30
  trades <addr> [--limit N] the decoded swaps behind the stats
31
+ mirror <addr> [--cap USD] [--k X] [--propose] [--json]
32
+ preview a MIRROR TICKET from this wallet's latest BUY (checks + size);
33
+ --propose writes it as an INERT order plan (arm only in the app, keypress/Phantom gate)
28
34
  purge --yes delete every follow and label
29
35
  auto <addr> [--per-trade USD] [--daily USD] [--max-open N] [--cooldown SEC] [--k X] [--disarm N] [--off]
30
36
  draft the auto-mirror caps (always OFF here: enabling is a keypress in the app, U then A,
@@ -35,7 +41,9 @@ function fmtStats(s) {
35
41
  `${shortAddr(s.wallet)} ${s.trades} trades (${s.buys} buys · ${s.sells} sells · ${s.transfers} transfers) over ${s.mints} mints · ${s.sample.note}`,
36
42
  `realised ${s.realisedSol >= 0 ? '+' : ''}${s.realisedSol.toFixed(4)} SOL${s.realisedUsd !== null ? ` (${s.realisedUsd >= 0 ? '+' : ''}$${s.realisedUsd.toFixed(2)} at $${s.solUsdAt}/SOL now)` : ' (no SOL price: USD not shown)'} · FIFO over fetched history, fees and tips included`,
37
43
  `win rate ${s.winRate === null ? '—' : `${(s.winRate * 100).toFixed(0)}%`} · ${s.winRateNote} · open positions ${s.open}`,
44
+ `hold time ${fmtHoldMs(s.holdMedianMs)} · ${s.holdNote}`,
38
45
  `early-entry score ${s.earlyEntry.score === null ? '—' : s.earlyEntry.score.toFixed(2)} · ${s.earlyEntry.note}`,
46
+ `source ${s.sample.source}${s.sample.asOf ? ` · as of ${new Date(s.sample.asOf).toISOString().replace('T', ' ').slice(0, 19)} (${Math.max(0, Math.round((Date.now() - s.sample.asOf) / 1000))}s ago)` : ' · not fetched yet'}`,
39
47
  ];
40
48
  for (const p of s.positions.slice(0, 12))
41
49
  out.push(` ${shortAddr(p.mint)} ${p.closed ? 'closed' : 'open '} realised ${p.realisedSol >= 0 ? '+' : ''}${p.realisedSol.toFixed(4)} SOL ${p.buys}b/${p.sells}s${p.flagged ? ' (cost-0 lot: airdrop/transfer/unpriced leg)' : ''}`);
@@ -209,6 +217,104 @@ export async function runFollow(rest, io, over = {}) {
209
217
  dl?.close();
210
218
  }
211
219
  }
220
+ // task-1400 Tier 2 (headless): the one-key MIRROR TICKET from a shell. Reuses the exact TUI path — poll the
221
+ // tracker, take the wallet's latest BUY alert, resolve its market, run buildMirror. --propose writes the result
222
+ // as an INERT `proposed` order plan through PlanService; it is armable ONLY through the app's keypress / Phantom
223
+ // gate. There is no arm/sign/execute path in this command (the plan engine refuses to arm without a gesture and
224
+ // an armed mandate), so a pipe or an agent calling `follow mirror --propose` can never trade.
225
+ case 'mirror': {
226
+ const addr = rest[1];
227
+ if (!isWalletAddress(addr)) {
228
+ errOut('usage: dexterm follow mirror <addr> [--cap USD] [--k X] [--propose] [--json]');
229
+ return 2;
230
+ }
231
+ if (!store.has(addr)) {
232
+ errOut(`dexterm follow mirror: ${shortAddr(addr)} is not followed (dexterm follow add <addr> first)`);
233
+ return 1;
234
+ }
235
+ const capUsd = flag(rest, '--cap') ? Number(flag(rest, '--cap')) : FOLLOW_MIRROR_DEFAULT_CAP_USD;
236
+ if (!(capUsd > 0)) {
237
+ errOut('dexterm follow mirror: --cap needs a USD number > 0');
238
+ return 2;
239
+ }
240
+ const kRaw = flag(rest, '--k');
241
+ const k = kRaw ? Number(kRaw) : undefined;
242
+ if (kRaw !== undefined && !(k > 0)) {
243
+ errOut('dexterm follow mirror: --k needs a number > 0');
244
+ return 2;
245
+ }
246
+ let dl = null;
247
+ let history;
248
+ if (over.history !== undefined)
249
+ history = over.history;
250
+ else {
251
+ const rpc = resolveRpcUrl({ env: io.env });
252
+ if (rpc.provider !== 'helius')
253
+ history = null;
254
+ else {
255
+ dl = createDataLayer({ rpcUrl: rpc.url, rpcFallbackUrl: rpc.fallbackUrl });
256
+ history = dl.rpcPool.heliusUrl ? new HeliusHistory(dl.http, dl.rpcPool.heliusUrl) : null;
257
+ }
258
+ }
259
+ try {
260
+ const copy = new CopyLive(dl, { followsPath: null, history, solUsd: () => over.solUsd ?? null, ...(over.clock ? { clock: over.clock } : {}) });
261
+ copy.store.add(addr);
262
+ if (copy.status().state === 'unavailable') {
263
+ errOut(`dexterm follow mirror: ${copy.status().why}`);
264
+ return 1;
265
+ }
266
+ await copy.tracker.pollOnce();
267
+ const after = copy.status();
268
+ if (after.state === 'degraded') {
269
+ errOut(`dexterm follow mirror: ${after.why}`);
270
+ return 1;
271
+ }
272
+ // Prefer a live "news" buy alert (a buy the tracker saw AFTER priming). A one-shot shell call primes and
273
+ // sees the history in a single poll, so there is usually no news alert yet; fall back to the latest BUY in
274
+ // the fetched history, synthesising the same CopyAlert shape and captioning it as history, not live news.
275
+ let alert = copy.alerts(200).find((a) => a.wallet === addr && a.trade.side === 'buy');
276
+ let fromHistory = false;
277
+ if (!alert) {
278
+ const lastBuy = copy.trades(addr).filter((t) => t.side === 'buy').sort((a, b) => b.slot - a.slot || b.sig.localeCompare(a.sig))[0];
279
+ if (!lastBuy) {
280
+ out(`no BUY from ${shortAddr(addr)} in the fetched history (nothing to mirror)`);
281
+ return 0;
282
+ }
283
+ alert = { id: `copy_hist_${lastBuy.sig.slice(0, 12)}`, t: copy.now(), wallet: addr, name: store.get(addr)?.name ?? null, trade: lastBuy, lagMs: lastBuy.tKnown ? Math.max(0, copy.now() - lastBuy.t) : null };
284
+ fromHistory = true;
285
+ }
286
+ const market = over.mirrorMarket ? await over.mirrorMarket(alert.trade.mint) : await copy.mirrorMarket(alert.trade.mint);
287
+ const soldSince = leaderSoldSince(alert, copy.trades(addr));
288
+ // No wallet-intel scan from a shell: the leader's dev-cluster / holdings stay unknown (shown as such).
289
+ const result = buildMirror(alert, { now: copy.now(), capUsd, k, market, leaderSoldSince: soldSince, leaderIntel: null });
290
+ if (has(rest, '--json')) {
291
+ out(JSON.stringify(result));
292
+ return result.ok ? 0 : 1;
293
+ }
294
+ out(`mirror of ${shortAddr(addr)}'s latest BUY — ${shortAddr(alert.trade.mint)}${market.symbol ? ` (${market.symbol})` : ''}${fromHistory ? ' · from fetched history (not a live alert)' : ' · live alert'}`);
295
+ for (const c of result.checks)
296
+ out(` ${c.ok === true ? 'ok ' : c.ok === false ? 'NO ' : '? '} ${c.name.padEnd(12)} ${c.detail}`);
297
+ if (!result.ok) {
298
+ errOut(`refused: ${result.reason} — ${result.message}`);
299
+ return 1;
300
+ }
301
+ out(` size $${result.sizeUsd.toFixed(2)} = ${result.sizeWhy}`);
302
+ if (!has(rest, '--propose')) {
303
+ out('preview only · add --propose to write the inert plan (then arm it in the app: O screen, hold a — keypress/Phantom gate)');
304
+ out('not a recommendation · a wallet\'s past fills say nothing about its next one');
305
+ return 0;
306
+ }
307
+ const plans = new PlanService({ journalPath: over.plansJournalPath === undefined ? defaultPlansJournalPath(io.env) : over.plansJournalPath });
308
+ const plan = plans.propose(result.input, { supply: result.supply, by: 'copy', ref: result.ref });
309
+ out(`proposed ${plan.id} · ${plan.status.toUpperCase()} (${plan.side} ${plan.symbol} ≈ $${plan.sizeUsd.toFixed(2)}) · INERT — nothing is armed`);
310
+ out('arm it in the app: U → O (the plans screen), select it, hold a (keypress) or approve in Phantom · the gate stays on');
311
+ out('not a recommendation · a wallet\'s past fills say nothing about its next one');
312
+ return 0;
313
+ }
314
+ finally {
315
+ dl?.close();
316
+ }
317
+ }
212
318
  default:
213
319
  if (cmd) {
214
320
  errOut(`dexterm follow: unknown subcommand ${JSON.stringify(cmd)}`);
@@ -0,0 +1,48 @@
1
+ // `dexterm help [topic]` (task-1413) — the app-help knowledge base from the command line, backed by the
2
+ // same generated entries as the analyst's `app_help` MCP tool. No network, no model.
3
+ // dexterm help list the topics
4
+ // dexterm help export show the best-matching topic (exact key/command + how-to)
5
+ // dexterm help screen token multi-word query
6
+ import { loadAppHelp, searchAppHelp, getAppHelp } from '../../mcp/src/index.js';
7
+ function show(e, io) {
8
+ io.stdout.write(`\n${e.title} (${e.id})\n`);
9
+ io.stdout.write(`${'─'.repeat(Math.min(60, e.title.length + e.id.length + 4))}\n`);
10
+ if (e.keys.length)
11
+ io.stdout.write(`Keys: ${e.keys.join(' ')}\n`);
12
+ if (e.commands.length)
13
+ io.stdout.write(`Commands: ${e.commands.map((c) => `dexterm ${c}`).join(' ')}\n`);
14
+ if (e.flags.length)
15
+ io.stdout.write(`Flags: ${e.flags.join(' ')}\n`);
16
+ io.stdout.write(`\n${e.body}\n`);
17
+ }
18
+ export function helpCommand(argv, io) {
19
+ const entries = loadAppHelp();
20
+ if (!entries.length) {
21
+ io.stderr.write('no app-help entries found\n');
22
+ return 1;
23
+ }
24
+ const query = argv.join(' ').trim();
25
+ if (!query) {
26
+ io.stdout.write('dexterm help <topic> — how the app works. Topics:\n\n');
27
+ for (const e of entries)
28
+ io.stdout.write(` ${e.id.padEnd(26)} ${e.title}\n`);
29
+ io.stdout.write('\nExamples: dexterm help export · dexterm help voice speed · dexterm help screener\n');
30
+ return 0;
31
+ }
32
+ // Exact id wins; otherwise the best retrieval match, with near-misses listed.
33
+ const exact = getAppHelp(query.replace(/\s+/g, '-'), entries) ?? getAppHelp(query, entries);
34
+ if (exact) {
35
+ show(exact, io);
36
+ return 0;
37
+ }
38
+ const results = searchAppHelp(query, 5, entries);
39
+ if (!results.length) {
40
+ io.stdout.write(`No app-help topic matches "${query}". Run \`dexterm help\` to list topics.\n`);
41
+ return 1;
42
+ }
43
+ show(results[0], io);
44
+ if (results.length > 1) {
45
+ io.stdout.write(`\nRelated: ${results.slice(1).map((r) => r.id).join(', ')}\n`);
46
+ }
47
+ return 0;
48
+ }
@@ -1,7 +1,7 @@
1
1
  // dexterm — library surface of the CLI package (used by tests and tools).
2
2
  export * from "./state.js";
3
3
  export * from "./fixtures.js";
4
- export { parseArgs, stateFromArgs, applyScreenerArgs, applyEffectArgs, applyAction, applyAnalystAction, applyKey, colorDepthFor, intensityFor, asciiRainFor, fpsFor, demoEvent, main, interactive, terminalGuard, describeError, buildScreenState, HELP, LIVE_REFRESH_MS, FPS_BY_INTENSITY, MAX_FPS, DEMO_KEYS, } from "./main.js";
4
+ export { parseArgs, stateFromArgs, applyScreenerArgs, applyEffectArgs, applyAction, applyAnalystAction, applyFollowNav, applyKey, colorDepthFor, intensityFor, asciiRainFor, fpsFor, demoEvent, main, interactive, terminalGuard, describeError, buildScreenState, HELP, LIVE_REFRESH_MS, FPS_BY_INTENSITY, MAX_FPS, DEMO_KEYS, } from "./main.js";
5
5
  export { AnalystSession, parseResults, parseFilterApplied } from "./analyst/session.js";
6
6
  export { resolveAnalystResults } from "./analyst/results.js";
7
7
  export { runAnalyst, buildArgv, turnPrompt, analystContextBlock, untrusted, ASSETS as ANALYST_ASSETS, MCP_BIN } from "./analyst/backend.js";
@@ -66,6 +66,9 @@ function openEditor(io, file) {
66
66
  const ed = (io.env.VISUAL || io.env.EDITOR || '').trim();
67
67
  if (!ed || io.isTTY === false)
68
68
  return 'no-editor';
69
+ // task-1423: Windows has no /bin/sh, so the spawn below always failed there; print the path to edit instead
70
+ if ((io.platform ?? process.platform) === 'win32')
71
+ return 'no-editor';
69
72
  // `sh -c '$EDITOR "$1"'` so an EDITOR with arguments ("code -w") works; the path is passed as $1, never interpolated.
70
73
  const r = spawnSync('/bin/sh', ['-c', `${ed} "$1"`, 'sh', file], { stdio: 'inherit', env: io.env });
71
74
  return r.status === 0 ? 'edited' : 'failed';