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,25 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="dexterm-ws" content="__WS_TOKEN__">
6
+ <meta name="dexterm-popup" content="__POPUP_ID__">
7
+ <meta name="dexterm-port" content="__PORT__">
8
+ <title>dexterm · watch the agent cook</title>
9
+ <style nonce="__NONCE__">/*__CSS__*/</style>
10
+ </head>
11
+ <body>
12
+ <div id="hud" class="hud">
13
+ <div id="drag" class="drag"><span class="mark">✦</span><span id="title" class="title">dexterm · WATCH THE AGENT COOK</span><span id="status" class="status">connecting…</span><button id="close" class="close" type="button" aria-label="close">✕</button></div>
14
+ <div class="stage"><canvas id="rain"></canvas><div class="body">
15
+ <div id="screen" class="spec-screen"></div>
16
+ <div id="agents" class="spec-agents"></div>
17
+ <div class="spec-activity-head">agent activity</div>
18
+ <div id="activity" class="spec-activity"></div>
19
+ </div></div>
20
+ <div id="foot" class="foot"><span id="redact" class="spec-foot-redact">redacted</span><span>read-only · pause / take over / kill</span><span id="fps" class="fps">— fps</span></div>
21
+ </div>
22
+ <script nonce="__NONCE__">/*__COMMON_JS__*/</script>
23
+ <script nonce="__NONCE__">/*__JS__*/</script>
24
+ </body>
25
+ </html>
@@ -0,0 +1,74 @@
1
+ // dexterm popup — spectator page (task-1406, "watch the agent cook"). Renders the SpectatorPopupVM: the
2
+ // screen the agent moved to, the connected agents with an 'agent' badge and status, and an activity feed
3
+ // with timing and data ages. Every string is already REDACTED TUI-side (wallet addresses/keys masked) and
4
+ // is rendered with textContent only — never innerHTML. The ONLY messages this page sends are the
5
+ // allow-listed spectator.pause / resume / takeover / kill events (by agent name); it can never arm, sign,
6
+ // raise a cap or touch a wallet (no such control exists and the server drops anything off the allow-list).
7
+ (() => {
8
+ 'use strict';
9
+ const DT = window.DT; DT.page = 'spectator';
10
+ const $ = (id) => document.getElementById(id);
11
+ const el = (tag, cls, text) => { const e = document.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = String(text); return e; };
12
+ const ago = (t) => { if (!t) return '—'; 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`; };
13
+ const age = (s) => (s == null ? '' : s < 60 ? `${Math.round(s)}s old` : s < 3600 ? `${Math.floor(s / 60)}m old` : `${Math.floor(s / 3600)}h old`);
14
+
15
+ let vm = null;
16
+
17
+ function render() {
18
+ if (!vm) return;
19
+
20
+ const scr = $('screen'); scr.textContent = '';
21
+ const s = vm.screen || {};
22
+ scr.appendChild(el('span', 'spec-screen-k', `screen: ${s.screen || '—'}`));
23
+ if (s.selectedMint) scr.appendChild(el('span', 'spec-screen-dim', ` · token ${s.selectedMint}`));
24
+ if (s.feed) scr.appendChild(el('span', 'spec-screen-dim', ` · feed ${s.feed}`));
25
+ if (s.filter) scr.appendChild(el('span', 'spec-screen-dim', ` · filter ${s.filter}`));
26
+ if (s.highlight) scr.appendChild(el('span', 'spec-screen-dim', ` · ★ ${s.highlight}`));
27
+ if (vm.control === 'user') scr.appendChild(el('span', 'spec-takeover', ' · YOU HAVE TAKEN OVER'));
28
+
29
+ const host = $('agents'); host.textContent = '';
30
+ if (!vm.agents || !vm.agents.length) host.appendChild(el('div', 'spec-dim', 'no agents connected yet'));
31
+ for (const a of (vm.agents || [])) {
32
+ const row = el('div', 'spec-agent' + (a.status !== 'active' ? ' off' : ''));
33
+ row.appendChild(el('span', 'spec-badge', 'agent')); // the 'agent' badge
34
+ row.appendChild(el('span', 'spec-name', a.name)); // textContent — redacted/firewalled, safe
35
+ if (a.tier) row.appendChild(el('span', 'spec-tier tier-' + a.tier, a.tier));
36
+ row.appendChild(el('span', 'spec-status status-' + a.status, a.status));
37
+ row.appendChild(el('span', 'spec-dim', ` · ${ago(a.lastAt)}`));
38
+ const ctl = el('div', 'spec-ctl');
39
+ const mk = (label, name, danger) => { const b = el('button', 'spec-btn' + (danger ? ' danger' : ''), label); b.type = 'button'; b.addEventListener('click', () => DT.send({ type: 'event', name, payload: { agent: a.name } })); return b; };
40
+ if (a.status === 'active') { ctl.appendChild(mk('pause', 'spectator.pause')); ctl.appendChild(mk('take over', 'spectator.takeover')); }
41
+ else ctl.appendChild(mk('resume', 'spectator.resume'));
42
+ ctl.appendChild(mk('kill', 'spectator.kill', true)); // panic
43
+ row.appendChild(ctl);
44
+ host.appendChild(row);
45
+ }
46
+
47
+ const act = $('activity'); act.textContent = '';
48
+ for (const r of (vm.activity || []).slice().reverse().slice(0, 80)) {
49
+ const line = el('div', 'spec-act' + (r.ok ? '' : ' bad') + (r.phase === 'nav' ? ' nav' : ''));
50
+ line.appendChild(el('span', 'spec-act-t', ago(r.t)));
51
+ line.appendChild(el('span', 'spec-badge sm', 'agent'));
52
+ line.appendChild(el('span', 'spec-act-agent', r.agent));
53
+ line.appendChild(el('span', 'spec-act-phase', r.phase));
54
+ if (r.tool) line.appendChild(el('span', 'spec-act-tool', r.tool));
55
+ line.appendChild(el('span', 'spec-act-sum', r.summary || '')); // textContent — redacted upstream
56
+ if (r.ms != null) line.appendChild(el('span', 'spec-act-ms', `${Math.round(r.ms)}ms`));
57
+ if (r.dataAgeSec != null) line.appendChild(el('span', 'spec-act-age', age(r.dataAgeSec)));
58
+ act.appendChild(line);
59
+ }
60
+
61
+ $('redact').textContent = vm.redacted ? 'redacted' : 'local (full)';
62
+ const live = (vm.agents || []).filter((a) => a.status === 'active').length;
63
+ DT.status(`${live} cooking`, live ? 'up' : 'muted');
64
+ }
65
+
66
+ DT.onHello(() => { DT.mountRain('rain'); const r = DT.rain(); if (r) { r.setActivity(0.3); r.setMood('rain'); } });
67
+ DT.onData((payload) => { vm = payload; render(); });
68
+ DT.onUpdate(() => { /* spectator is pushed as full `data` frames */ });
69
+ DT.wireChrome();
70
+ let lastTick = 0;
71
+ DT.rainEvery = 2; DT.fpsCap = 30;
72
+ DT.loop((now) => { if (now - lastTick > 1000) { lastTick = now; if (vm) render(); } });
73
+ DT.connect();
74
+ })();
@@ -62,6 +62,25 @@ export function fixtureNarrativeVM() {
62
62
  limits: 'model output: none (deterministic lexicon v0.1.0). Name-based: X/Telegram-only narratives are missed; 6 copycat symbols.',
63
63
  };
64
64
  }
65
+ /** A deterministic, network-free spectator view for the popup self-test / demo (task-1406). Not real agents. */
66
+ export function fixtureSpectatorVM() {
67
+ const now = Date.now();
68
+ return {
69
+ title: 'watch the agent cook', now, redacted: true, control: 'agent',
70
+ screen: { screen: 'token', selectedMint: 'Fixt…AAAA', feed: 'new', filter: null, highlight: null },
71
+ agents: [
72
+ { name: 'claude-code', tier: 'propose', status: 'active', lastAt: now - 1200 },
73
+ { name: 'scanbot', tier: 'observe', status: 'paused', lastAt: now - 8000 },
74
+ ],
75
+ activity: [
76
+ { seq: 1, t: now - 8000, phase: 'connect', agent: 'claude-code', tier: 'propose', tool: null, summary: 'connected · claude-code 2.1', ms: null, dataAgeSec: null, ok: true, refusal: null },
77
+ { seq: 2, t: now - 5000, phase: 'nav', agent: 'claude-code', tier: 'propose', tool: 'open_token', summary: 'open Fixt…AAAA', ms: 6, dataAgeSec: null, ok: true, refusal: null },
78
+ { seq: 3, t: now - 4200, phase: 'result', agent: 'claude-code', tier: 'propose', tool: 'rugcheck', summary: 'rugcheck Fixt…', ms: 42, dataAgeSec: 3, ok: true, refusal: null },
79
+ { seq: 4, t: now - 2000, phase: 'proposal', agent: 'claude-code', tier: 'propose', tool: 'propose_alert', summary: 'alert when mcap crosses 1M', ms: 2, dataAgeSec: null, ok: true, refusal: null },
80
+ { seq: 5, t: now - 1000, phase: 'refused', agent: 'scanbot', tier: 'observe', tool: 'arm_plan', summary: 'refused: no agent may do that at any tier', ms: null, dataAgeSec: null, ok: false, refusal: 'never' },
81
+ ],
82
+ };
83
+ }
65
84
  /** Run the self-test and resolve with the measured result. Always cleans up the window host and server. */
66
85
  export async function runSelfTest(opts = {}) {
67
86
  const seconds = Math.max(2, Math.min(30, opts.seconds ?? 6));
@@ -52,6 +52,8 @@ export const POPUP_EVENTS = new Set([
52
52
  'event:roster.pause', 'event:roster.resume', 'event:roster.kick',
53
53
  // task-1355 order plans: cancel a plan by id (the ONLY write the plans popup can send — never arm/cap/sign)
54
54
  'event:plan.cancel',
55
+ // task-1406 spectator: pause / resume / take over / kill an agent by name (the only writes this popup sends)
56
+ 'event:spectator.pause', 'event:spectator.resume', 'event:spectator.takeover', 'event:spectator.kill',
55
57
  ]);
56
58
  /**
57
59
  * task-1358 r3: the ONLY messages a pack widget's socket may send — the generic set every popup has. A widget socket
@@ -104,6 +106,10 @@ export function validateEventPayload(name, payload) {
104
106
  case 'roster.pause':
105
107
  case 'roster.resume':
106
108
  case 'roster.kick':
109
+ case 'spectator.pause':
110
+ case 'spectator.resume':
111
+ case 'spectator.takeover':
112
+ case 'spectator.kill':
107
113
  return typeof p.agent === 'string' && ROSTER_AGENT_RE.test(p.agent) ? { agent: p.agent } : null;
108
114
  case 'plan.cancel':
109
115
  return typeof p.id === 'string' && PLAN_ID_RE.test(p.id) ? { id: p.id } : null;
@@ -117,7 +123,7 @@ export function lwcLogoStyleHash(lwcSource) {
117
123
  return m ? `'sha256-${createHash('sha256').update(m[1], 'utf8').digest('base64')}'` : null;
118
124
  }
119
125
  // (r3) 'widget' is the WRAPPER page — ours, nonce-rendered like the rest; the pack html goes in its iframe
120
- const PAGE_KINDS = ['chart', 'token', 'feeds', 'bubbles', 'roster', 'narrative', 'plans', 'widget'];
126
+ const PAGE_KINDS = ['chart', 'token', 'feeds', 'bubbles', 'roster', 'narrative', 'plans', 'widget', 'spectator'];
121
127
  function b64url(buf) { return buf.toString('base64url'); }
122
128
  function safeEq(a, b) {
123
129
  const ba = Buffer.from(a), bb = Buffer.from(b);
@@ -206,6 +206,12 @@ export const DEFAULT_KEYMAP = new Keymap([
206
206
  // task-1282 JARVIS voice: push-to-talk toggle (hold not possible in a terminal). Lowercase v is free
207
207
  // on every screen; the loop auto-enables voice mode on first use and shows a live mic indicator.
208
208
  { keys: ['v'], action: 'voice.talk', label: 'voice' },
209
+ // task-1411: in-voice controls (loop-handled; only act while voice is on). `m` mute, `,` change voice,
210
+ // `<`/`>` slow down / speed up the spoken reply (saved to config.voice.rate). Per-screen `m` bindings
211
+ // (kol/bubbles/follows) override this global one on those screens, exactly like `voice.talk`.
212
+ { keys: ['m'], action: 'voice.mute', label: 'mute voice' },
213
+ { keys: [','], action: 'voice.cycle', label: 'change voice' },
214
+ { keys: ['<', '>'], action: 'voice.rate', label: 'slower / faster voice' },
209
215
  // task-1311: `i` focuses the chat input on the token / analyst screens (QA B1–B3); the loop only
210
216
  // captures keystrokes into the input while it is focused, so the number keys and `q` never type.
211
217
  { keys: ['i'], action: 'input.focus', label: 'ask', screen: 'token' },
@@ -217,6 +223,10 @@ export const DEFAULT_KEYMAP = new Keymap([
217
223
  { keys: ['$'], action: 'display.priceMode', label: 'price ⇄ mcap' },
218
224
  // task-1401: `E` exports the current view (PNG + text report, local only). Free on every screen.
219
225
  { keys: ['E'], action: 'export.view', label: 'export PNG + report' },
226
+ // task-1407: `H` (free capital everywhere) opens the PnL card preview on the screens that have fills to card.
227
+ { keys: ['H'], action: 'pnl.card', label: 'PnL card (share)', screen: 'token' },
228
+ { keys: ['H'], action: 'pnl.card', label: 'PnL card (share)', screen: 'positions' },
229
+ { keys: ['H'], action: 'pnl.card', label: 'PnL card (share)', screen: 'follows' },
220
230
  { keys: ['/'], action: 'filter.edit', label: 'filter', screen: 'screener' },
221
231
  { keys: ['\\'], action: 'filter.clear', label: 'clear filter', screen: 'screener' },
222
232
  { keys: ['s'], action: 'sort.menu', label: 'sort', screen: 'screener' },
@@ -5,7 +5,13 @@
5
5
  // listen() is called — so the on-screen indicator and the real mic state can never disagree.
6
6
  import { route, isSafe } from "./router.js";
7
7
  import { spokenSummary } from "./summary.js";
8
+ import { speakable } from "./speakable.js";
8
9
  import { isBannedVoice } from "./tts/voices.js";
10
+ import { DEFAULT_RATE } from "./tts/say.js";
11
+ /** Live rate-nudge bounds and step (task-1411, the < / > keys). Clamped so a voice stays intelligible. */
12
+ export const RATE_MIN = 120;
13
+ export const RATE_MAX = 320;
14
+ export const RATE_STEP = 15;
9
15
  export class VoiceController {
10
16
  stt;
11
17
  tts;
@@ -44,7 +50,7 @@ export class VoiceController {
44
50
  this.tts = opts.tts;
45
51
  this.muted = opts.muted ?? false;
46
52
  this.voice = opts.voice ?? null;
47
- this.rate = opts.rate ?? 190;
53
+ this.rate = opts.rate ?? DEFAULT_RATE;
48
54
  this.pitch = opts.pitch;
49
55
  this.now = opts.now ?? (() => Date.now());
50
56
  this.maxListenMs = opts.maxListenMs ?? 15_000;
@@ -99,6 +105,12 @@ export class VoiceController {
99
105
  this.message = this.stt ? null : 'voice-in unavailable on this machine — type questions; replies are still spoken';
100
106
  this.setMode('idle');
101
107
  void this.loadVoices();
108
+ // task-1416: pre-load the TTS engine (Piper loads its model into a standby process) so the first
109
+ // reply starts speaking quickly. A no-op for engines that stream from spawn (`say`).
110
+ try {
111
+ this.tts?.warmup?.(this.rate);
112
+ }
113
+ catch { /* warm-up is best-effort; never break enabling voice */ }
102
114
  }
103
115
  disable() {
104
116
  if (!this.enabled)
@@ -107,6 +119,10 @@ export class VoiceController {
107
119
  this.stopSpeaking();
108
120
  this.enabled = false;
109
121
  this.pendingLatency = null;
122
+ try {
123
+ this.tts?.dispose?.();
124
+ }
125
+ catch { /* best-effort cleanup */ }
110
126
  this.setMode('off');
111
127
  }
112
128
  toggle() { this.enabled ? this.disable() : this.enable(); }
@@ -279,6 +295,9 @@ export class VoiceController {
279
295
  }
280
296
  /** Low-level speak: stops any current reply, then reads `text`. Muted → skip audio but update state. */
281
297
  speak(text) {
298
+ // Speak like a trader (task-1411): no digit-by-digit tiny prices, big numbers in human words,
299
+ // natural percents. Applied to EVERY spoken line so an ack or an analyst answer never reads raw.
300
+ text = speakable(text);
282
301
  this.lastSay = text;
283
302
  if (!this.tts || this.muted) {
284
303
  // Still resolve the latency sample (the "spoken word" is just suppressed) so the table is complete.
@@ -386,6 +405,28 @@ export class VoiceController {
386
405
  }
387
406
  currentVoice() { return this.voice; }
388
407
  availableVoices() { return this.voices.slice(); }
408
+ /** The current speaking rate (words-per-minute-ish), for the status line / help. */
409
+ currentRate() { return this.rate; }
410
+ /**
411
+ * Nudge the speaking rate by `steps` × RATE_STEP (— for slower, + for faster), clamp it, persist it,
412
+ * and surface it on the status line (task-1411: the < / > keys). Takes effect on the next reply; it
413
+ * does not restart a line already in flight. A no-op when the rate is already at the clamp.
414
+ */
415
+ nudgeRate(steps) {
416
+ const next = Math.max(RATE_MIN, Math.min(RATE_MAX, this.rate + steps * RATE_STEP));
417
+ if (next === this.rate) {
418
+ this.message = `voice rate ${this.rate} wpm (${next <= RATE_MIN ? 'slowest' : 'fastest'})`;
419
+ this.notify();
420
+ return;
421
+ }
422
+ this.rate = next;
423
+ this.message = `voice rate ${this.rate} wpm`;
424
+ try {
425
+ this.opts.onRateChange?.(this.rate);
426
+ }
427
+ catch { /* persistence is best-effort; never break the UI */ }
428
+ this.notify();
429
+ }
389
430
  /** A compact latency report for the handoff / `--latency` demo. */
390
431
  report() {
391
432
  const paths = ['instant', 'analyst'];
@@ -71,26 +71,43 @@ export function voiceDoctor(deps) {
71
71
  }
72
72
  return checks;
73
73
  }
74
- // Speech OUT — macOS say.
74
+ // Speech OUT — Piper neural (the default when set up, task-1416) → macOS `say` (fallback) → text.
75
+ const macPiperReady = !!deps.piperPresent && !!deps.piperVoicePresent && !!deps.playerName;
76
+ if (macPiperReady) {
77
+ checks.push({
78
+ name: 'Voice out (Piper)', status: 'pass',
79
+ detail: `neural British voice is the default — en_GB-alan-medium, plays via ${deps.playerName} (local, no key); fallback: macOS \`say\``,
80
+ });
81
+ }
75
82
  if (deps.sayAvailable) {
76
- checks.push({ name: 'Voice out (say)', status: 'pass', detail: `macOS \`say\` ready · ${deps.voiceCount} voice${deps.voiceCount === 1 ? '' : 's'} (press the voice key, then cycle the voice)` });
77
- // Which voice speaks, and why — so a machine can never silently use a novelty/odd system voice (task-1393).
83
+ const role = macPiperReady ? 'the fallback if Piper is removed' : 'the default — press the voice key, then cycle the voice';
84
+ checks.push({ name: macPiperReady ? 'Voice out (say, fallback)' : 'Voice out (say)', status: 'pass', detail: `macOS \`say\` ready · ${deps.voiceCount} voice${deps.voiceCount === 1 ? '' : 's'} · ${role}` });
85
+ // Which `say` voice speaks, and why — so a machine can never silently use a novelty/odd system voice (task-1393).
86
+ const voiceLabel = macPiperReady ? 'Fallback voice' : 'Default voice';
78
87
  if (deps.resolvedVoice) {
79
- checks.push({ name: 'Default voice', status: 'pass', detail: `${deps.resolvedVoiceReason ?? deps.resolvedVoice} · , changes it` });
88
+ checks.push({ name: voiceLabel, status: 'pass', detail: `${deps.resolvedVoiceReason ?? deps.resolvedVoice} · , changes it` });
80
89
  }
81
90
  else if (deps.resolvedVoice === null) {
82
91
  checks.push({
83
- name: 'Default voice', status: 'warn',
92
+ name: voiceLabel, status: 'warn',
84
93
  detail: deps.resolvedVoiceReason ?? 'no safe voice installed — spoken replies are off (text still works)',
85
94
  fix: 'Install a British male voice in System Settings → Accessibility → Spoken Content → System Voice → Manage Voices (Daniel, English (UK)); the Enhanced/Premium download sounds best.',
86
95
  });
87
96
  }
97
+ // Offer the higher-quality neural voice when it is not set up yet (never silent; the user decides).
98
+ if (!macPiperReady) {
99
+ checks.push({
100
+ name: 'Neural voice (Piper)', status: 'warn',
101
+ detail: 'a higher-quality neural British voice is available but not set up; `say` is speaking for now',
102
+ fix: 'Run `dexterm voice setup` to install it (asks first; a private Python runtime + a ~61 MB checksum-verified model; runs offline after, `dexterm voice uninstall` removes it).',
103
+ });
104
+ }
88
105
  }
89
- else {
106
+ else if (!macPiperReady) {
90
107
  checks.push({
91
- name: 'Voice out (say)', status: 'warn',
108
+ name: 'Voice out', status: 'warn',
92
109
  detail: 'macOS `say` not found; spoken replies are off, the analyst still answers in text',
93
- fix: '`say` ships with macOS — if it is missing, reinstall the Command Line Tools (`xcode-select --install`).',
110
+ fix: '`say` ships with macOS — if it is missing, reinstall the Command Line Tools (`xcode-select --install`). Or set up the neural voice: `dexterm voice setup`.',
94
111
  });
95
112
  }
96
113
  // Speech IN — Apple Speech (preferred) or whisper (fallback).
@@ -62,8 +62,15 @@ export async function selectTts(choice = process.env.DEXTERM_VOICE_TTS || 'auto'
62
62
  const e = new EspeakTts(espeakVoiceFor(configuredVoice) ?? undefined);
63
63
  return (await e.available()) ? e : null;
64
64
  }
65
- // On macOS, prefer `say`. Off macOS, `say` does not exist — use the portable engines below.
65
+ // On macOS, prefer Piper alan (neural) when the managed runtime is installed (task-1416:
66
+ // `dexterm voice setup` → venv + model + afplay), else the built-in `say`. Off macOS, `say` does not
67
+ // exist — use the portable engines below. An explicit `choice==='say'` always skips Piper.
66
68
  if (choice === 'say' || (choice === 'auto' && process.platform === 'darwin')) {
69
+ if (choice === 'auto') {
70
+ const piperMac = new PiperTts();
71
+ if (await piperMac.available())
72
+ return piperMac;
73
+ }
67
74
  const probe = new SayTts();
68
75
  if (choice === 'auto' && !(await probe.available()))
69
76
  return null;
@@ -6,13 +6,15 @@
6
6
  export * from "./types.js";
7
7
  export { route, isSafe } from "./router.js";
8
8
  export { spokenSummary, splitSentences } from "./summary.js";
9
+ export { speakable, humanizeNumber } from "./speakable.js";
9
10
  export { VoiceController } from "./controller.js";
10
11
  export { selectStt, selectTts, resolveSayVoice } from "./engines.js";
11
12
  export { voiceDoctor } from "./doctor.js";
12
13
  export { resolveVoice, isBannedVoice, normalizeVoiceName, parseSayVoices, sanitizeSpeech, BANNED_VOICE_NAMES, BRITISH_MALE_PREFERENCE, MAX_SPEAK_CHARS, VOICE_STYLE_DEFAULTS, voiceStyleDefaults, } from "./tts/voices.js";
13
14
  export { SayTts, DEFAULT_RATE } from "./tts/say.js";
14
15
  export { EspeakTts, DEFAULT_ESPEAK_VOICE, resolveEspeakBin, espeakVoiceFor } from "./tts/espeak.js";
15
- export { PiperTts, DEFAULT_PIPER_VOICE, PIPER_MODEL, PIPER_PLAYERS, resolvePiperBin, pickPlayer } from "./tts/piper.js";
16
+ export { PiperTts, DEFAULT_PIPER_VOICE, PIPER_MODEL, PIPER_PLAYERS, resolvePiperBin, resolvePiperRuntime, managedVenvPython, pickPlayer, } from "./tts/piper.js";
17
+ export { setupPiperRuntime, uninstallPiperRuntime, piperRuntimeStatus, piperRuntimePaths, readRuntimeRecord, detectPython, parsePythonVersion, pythonVersionOk, pythonFixLine, PIPER_PYPI_VERSION, MIN_PYTHON, DEFAULT_PYTHON_CANDIDATES, } from "./tts/piper-setup.js";
16
18
  export { FakeTts } from "./tts/fake.js";
17
19
  export { WhisperStt, WHISPER_MODEL, WHISPER_MODEL_DIR, pickRecorder } from "./stt/whisper.js";
18
20
  export { modelRegistry, downloadModel, modelStatus, specSizeMB, } from "./download.js";
@@ -0,0 +1,111 @@
1
+ // Make a line "speakable" like a trader, not digit by digit (task-1411). A tester heard Jarvis read a raw
2
+ // per-token price ("zero point zero zero zero zero zero two nine") and big raw numbers, which sounds
3
+ // crazy. This normaliser runs on EVERY spoken line (controller.speak) and applies these rules, in order:
4
+ //
5
+ // 1. Tiny per-token prices are NEVER read digit by digit. A sub-cent decimal ($0.0000029), the app's
6
+ // own subscript rendering ($0.0₅29), or any value below a cent becomes the coin's MARKET CAP when
7
+ // the line (or the caller) knows it, else the phrase "a tiny price". We NEVER invent a number.
8
+ // A sub-cent value that is an AMOUNT of something (e.g. "0.005 SOL", "0.0025 SOL fee") is left
9
+ // alone — it is a quantity, not a per-token price.
10
+ // 2. Big raw numbers become human words: 340000 → "340 thousand", 1200000 → "1.2 million",
11
+ // 4500000000 → "4.5 billion". Word boundaries keep digits inside an address untouched, and a bare
12
+ // 4-digit YEAR (1900–2099) is left as-is so "launched in 2024" never reads "launched in 2 thousand".
13
+ // 3. Percentages read naturally: "12%" → "12 percent".
14
+ //
15
+ // Pure and dependency-free so every rule is unit-tested without spawning a voice.
16
+ const SUBSCRIPT = '₀₁₂₃₄₅₆₇₈₉'; // ₀..₉
17
+ // Units that follow a small decimal and mark it as an AMOUNT, not a per-token price (so "0.005 SOL" is
18
+ // a quantity we read normally). Case-insensitive, whole word.
19
+ const AMOUNT_UNIT = /^\s*(sol|eth|btc|usdc|usdt|tokens?|coins?)\b/i;
20
+ /** Round to one decimal and drop a trailing ".0" so "1.0 million" reads "1 million". */
21
+ function trimNum(n) {
22
+ const r = Math.round(n * 10) / 10;
23
+ return (Number.isInteger(r) ? String(r) : r.toFixed(1));
24
+ }
25
+ /** A raw number (>= 1000) as human words: "340 thousand", "1.2 million", "4.5 billion". */
26
+ export function humanizeNumber(n) {
27
+ const abs = Math.abs(n);
28
+ if (abs >= 1e9)
29
+ return `${trimNum(n / 1e9)} billion`;
30
+ if (abs >= 1e6)
31
+ return `${trimNum(n / 1e6)} million`;
32
+ if (abs >= 1e3)
33
+ return `${trimNum(n / 1e3)} thousand`;
34
+ return trimNum(n);
35
+ }
36
+ /** A number is a 4-digit calendar year (1900–2099): spoken as-is, never "2 thousand twenty four". */
37
+ function isYear(v) {
38
+ return Number.isInteger(v) && v >= 1900 && v <= 2099;
39
+ }
40
+ /**
41
+ * Read a market cap out of the analyst line, e.g. "market cap $340K", "mcap of 1.2M", "cap: 340000",
42
+ * returned as a human phrase ("340 thousand"). The brief: a tiny price is replaced WITH THE COIN'S
43
+ * MARKET CAP WHEN THE ANALYST TEXT INCLUDES IT. Returns null when the line names no market cap.
44
+ */
45
+ function marketCapFromText(text) {
46
+ const m = /\b(?:market\s*cap|mcap|m\.?c\.?|cap)\b[^$\d]{0,8}\$?([\d,]+(?:\.\d+)?)\s*([KkMmBb])?\b/.exec(text);
47
+ if (!m)
48
+ return null;
49
+ let v = parseFloat(m[1].replace(/,/g, ''));
50
+ if (!Number.isFinite(v))
51
+ return null;
52
+ const suf = m[2];
53
+ if (suf)
54
+ v *= /[Kk]/.test(suf) ? 1e3 : /[Mm]/.test(suf) ? 1e6 : 1e9;
55
+ if (v < 1000)
56
+ return null; // "cap 2 holders" etc. is not a market cap
57
+ return humanizeNumber(v);
58
+ }
59
+ /**
60
+ * Normalise a spoken line. `opts.marketCap`, when given, is the human market-cap phrase substituted for
61
+ * a tiny per-token price; otherwise speakable reads a market cap out of the line, and failing that uses
62
+ * "a tiny price" (never a made-up number).
63
+ */
64
+ export function speakable(text, opts = {}) {
65
+ let s = String(text ?? '');
66
+ const mcap = (opts.marketCap && opts.marketCap.trim()) || marketCapFromText(s);
67
+ const tiny = mcap ? `a market cap of ${String(mcap).trim()}` : 'a tiny price';
68
+ // 1a. Subscript price forms the app renders, e.g. "$0.0₅29" / "0.0₄1686" — never read the digits.
69
+ s = s.replace(new RegExp(`\\$?\\d*\\.?\\d*[${SUBSCRIPT}]+[${SUBSCRIPT}0-9]*`, 'g'), tiny);
70
+ // 1b. Sub-cent decimal prices, e.g. "$0.0000029", "0.00004". A value below a cent is a per-token price,
71
+ // not an amount; the leading-zeros case (>=2 zeros after the point) is always a price — UNLESS it
72
+ // is immediately followed by a unit word (SOL/ETH/tokens…), in which case it is an AMOUNT: leave it.
73
+ s = s.replace(/\$?\d*\.\d+/g, (m, idx, full) => {
74
+ const v = parseFloat(m.replace(/[$,]/g, ''));
75
+ if (!Number.isFinite(v))
76
+ return m;
77
+ const after = full.slice(idx + m.length);
78
+ if (AMOUNT_UNIT.test(after))
79
+ return m; // "0.005 SOL" is a quantity, not a per-token price
80
+ if (/\.0{2,}/.test(m) || (v > 0 && v < 0.01))
81
+ return tiny;
82
+ return m;
83
+ });
84
+ // 2. Percentages read as words before the big-number pass (so "12%" isn't seen as a bare number).
85
+ s = s.replace(/(\d+(?:\.\d+)?)\s*%/g, '$1 percent');
86
+ // 2b. Abbreviated magnitudes the app and analysts use: "$340K" / "2.1M" / "4.5B" → words. Lowercase
87
+ // "m" is intentionally EXCLUDED: "15m" / "6h" are a token's AGE (minutes), not millions.
88
+ s = s.replace(/\$?(\d+(?:\.\d+)?)\s*([KkMBb])\b/g, (_m, num, suf) => {
89
+ const word = /[Kk]/.test(suf) ? 'thousand' : suf === 'M' ? 'million' : 'billion';
90
+ return `${num} ${word}`;
91
+ });
92
+ // 3. Big raw numbers -> human words. `\b` keeps runs of digits inside an address/word untouched, and a
93
+ // number already followed by a scale word (k/m/b/thousand/…) or "percent" is left alone, as is a
94
+ // bare 4-digit year.
95
+ s = s.replace(/\$?\b\d{1,3}(?:,\d{3})+(?:\.\d+)?\b|\$?\b\d{4,}(?:\.\d+)?\b/g, (m, ...rest) => {
96
+ const full = rest[rest.length - 1]; // the whole input string
97
+ const idx = rest[rest.length - 2];
98
+ const after = full.slice(idx + m.length).trimStart().toLowerCase();
99
+ if (/^(k|m|b|bn|thousand|million|billion|percent|%)\b/.test(after))
100
+ return m;
101
+ const v = parseFloat(m.replace(/[$,]/g, ''));
102
+ if (!Number.isFinite(v) || Math.abs(v) < 1000)
103
+ return m;
104
+ // A bare 4-digit year ("launched in 2024") stays a year; a dollar-prefixed or comma-grouped number
105
+ // ("$2024", "2,024") is a real amount and is humanised.
106
+ if (isYear(v) && !/[$,]/.test(m))
107
+ return m;
108
+ return humanizeNumber(v);
109
+ });
110
+ return s;
111
+ }
@@ -39,9 +39,11 @@ export function pickRecorder() {
39
39
  }
40
40
  /** Synchronous PATH check without spawning (cheap; used by available() and pickRecorder). */
41
41
  function which(bin) {
42
- const paths = (process.env.PATH ?? '').split(':');
42
+ // Windows (task-1423): `;`-separated PATH and `.exe` binaries, same rule as piper.ts's which()
43
+ const win = process.platform === 'win32';
44
+ const paths = (process.env.PATH ?? '').split(win ? ';' : ':');
43
45
  for (const p of paths) {
44
- if (p && existsSync(join(p, bin)))
46
+ if (p && (existsSync(join(p, bin)) || (win && existsSync(join(p, `${bin}.exe`)))))
45
47
  return true;
46
48
  }
47
49
  return false;