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
@@ -3,7 +3,7 @@
3
3
  // dexterm mcp run the read-only MCP server on stdio (what the analyst uses and
4
4
  // what `claude mcp add dexterm -- dexterm mcp` wires up)
5
5
  // dexterm mcp install <client> write/print the MCP config for a client (claude-code, claude-desktop,
6
- // cursor, codex, openclaw, generic). Shows the exact change, asks first,
6
+ // cursor, codex, generic). Shows the exact change, asks first,
7
7
  // is idempotent, and backs the file up before editing.
8
8
  // dexterm mcp doctor spawn the server over stdio, do the MCP handshake, and list the tools,
9
9
  // prompts and resources the agent sees — proof the connection works.
@@ -60,15 +60,10 @@ export const CLIENTS = [
60
60
  path: (env) => join(env.CODEX_HOME || join(home(env), '.codex'), 'config.toml'),
61
61
  note: 'Codex reads MCP servers from the [mcp_servers.*] tables in ~/.codex/config.toml.',
62
62
  },
63
- {
64
- id: 'openclaw', label: 'OpenClaw / generic agent', format: 'print', keyPath: 'mcpServers',
65
- path: () => null,
66
- note: 'OpenClaw and most agents take a standard mcpServers entry; paste this where your agent reads MCP servers, or pass --config FILE to write a JSON file.',
67
- },
68
63
  {
69
64
  id: 'generic', label: 'Generic (print the snippet)', format: 'print', keyPath: 'mcpServers',
70
65
  path: () => null,
71
- note: 'the standard stdio MCP server entry; add it to your client\'s MCP config.',
66
+ note: 'the standard stdio mcpServers entry most agents take; paste it where your agent reads MCP servers, or pass --config FILE to write a JSON file.',
72
67
  },
73
68
  ];
74
69
  export function clientById(id) { return CLIENTS.find((c) => c.id === id); }
@@ -0,0 +1,225 @@
1
+ // `dexterm` TUI — follow an agent that is driving over MCP (task-1421, "see what your agent is doing").
2
+ //
3
+ // The running terminal can WATCH and visibly FOLLOW an agent connected through `dexterm mcp` (Claude Code,
4
+ // Cursor, Codex, Claude Desktop, …). That MCP server is a separate process the external client spawned, so
5
+ // the app cannot set its env; instead this session, while it is open, writes the ~/.dexterm rendezvous the
6
+ // server reads (packages/mcp spectator-sink) and tails the JSONL channel the server appends to. Each record
7
+ // is folded into a SpectatorHub → a FollowController (the AGENT ACTIVITY panel + a SCREEN model) and, for
8
+ // navigation calls, moved into the REAL terminal through the caller's onNav hook (open a token, switch
9
+ // screen/feed/filter, highlight). Owner's own machine → follow mode is unredacted; the optional read-only
10
+ // stream (SpectatorServer) redacts every copy that leaves the hub, exactly as in task-1406.
11
+ //
12
+ // It adds NO power. A record only DESCRIBES a read/navigation call the gate already allowed; there is no
13
+ // inbound path from here to anything that can arm, sign, confirm, send or raise a cap. "pause / take over /
14
+ // kill" are local follow controls: pause/take-over stop the terminal mirroring the agent (the external agent
15
+ // keeps running — we simply stop moving the screen), and kill removes the rendezvous so the server stops
16
+ // emitting. Any user keypress during an agent move takes control, so the human and the agent never fight.
17
+ import { writeFileSync, readFileSync, rmSync, mkdirSync, statSync } from 'node:fs';
18
+ import { dirname } from 'node:path';
19
+ import { randomBytes } from 'node:crypto';
20
+ import { SpectatorHub, FollowController, SpectatorServer, } from '../../agents/src/index.js';
21
+ import { rendezvousPath } from '../../mcp/src/index.js';
22
+ function navLabel(agent, nav) {
23
+ const coin = nav.mint ? ` ${nav.mint.length > 10 ? nav.mint.slice(0, 4) + '…' + nav.mint.slice(-4) : nav.mint}` : '';
24
+ if (nav.screen === 'token')
25
+ return `agent ${agent}: opened${coin}`;
26
+ if (nav.screen === 'ticket')
27
+ return `agent ${agent}: opened the ${nav.side ?? ''} ticket${coin}`;
28
+ if (nav.screen === 'watchlist')
29
+ return `agent ${agent}: watchlisted${coin}`;
30
+ if (nav.feed)
31
+ return `agent ${agent}: switched the feed to ${nav.feed}`;
32
+ if (nav.filter)
33
+ return `agent ${agent}: filtered ${nav.filter}`;
34
+ if (nav.highlight)
35
+ return `agent ${agent}: highlighted ${nav.highlight.length > 10 ? nav.highlight.slice(0, 4) + '…' + nav.highlight.slice(-4) : nav.highlight}`;
36
+ if (nav.screen)
37
+ return `agent ${agent}: switched to ${nav.screen}`;
38
+ return `agent ${agent}: navigated`;
39
+ }
40
+ /**
41
+ * A follow session for one running terminal. Call start() to arm the rendezvous and begin tailing, pump()
42
+ * to process new records (the timer does this for you), the controls to pause/take-over/kill, userKey() when
43
+ * the human presses any key, and stop() to tear everything down. Never throws into the key loop.
44
+ */
45
+ export class McpFollowSession {
46
+ hub;
47
+ follow;
48
+ opts;
49
+ rvPath;
50
+ channelPath;
51
+ token;
52
+ agentLabel;
53
+ currentAgent;
54
+ offset = 0;
55
+ timer = null;
56
+ stream = null;
57
+ armed = false;
58
+ paused = false;
59
+ constructor(opts) {
60
+ this.opts = opts;
61
+ this.agentLabel = opts.agentLabel || 'mcp-agent';
62
+ this.currentAgent = this.agentLabel;
63
+ this.rvPath = rendezvousPath(opts.env);
64
+ this.channelPath = this.rvPath.replace(/\.json$/, '.jsonl');
65
+ this.token = randomBytes(24).toString('hex');
66
+ this.hub = new SpectatorHub({ cap: Math.max(1, opts.activityCap ?? 200) });
67
+ const controls = {
68
+ pauseAgent: () => true,
69
+ resumeAgent: () => { this.rearm(); return true; },
70
+ kickAgent: () => { this.disarm(); return 1; },
71
+ };
72
+ this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
73
+ this.hub.on((ev) => this.follow.apply(ev));
74
+ }
75
+ /** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
76
+ async start() {
77
+ this.rearm();
78
+ if (this.opts.stream) {
79
+ try {
80
+ this.stream = new SpectatorServer({ hub: this.hub, env: this.opts.env });
81
+ await this.stream.start();
82
+ }
83
+ catch {
84
+ this.stream = null;
85
+ }
86
+ }
87
+ if (!this.timer) {
88
+ this.timer = setInterval(() => this.pump(), Math.max(30, this.opts.pollMs ?? 150));
89
+ this.timer.unref?.();
90
+ }
91
+ }
92
+ /** Write (or re-write) the channel + rendezvous so the server emits here. */
93
+ rearm() {
94
+ try {
95
+ mkdirSync(dirname(this.rvPath), { recursive: true });
96
+ if (!this.armed) {
97
+ writeFileSync(this.channelPath, '', { mode: 0o600 });
98
+ this.offset = 0;
99
+ }
100
+ writeFileSync(this.rvPath, JSON.stringify({ file: this.channelPath, token: this.token, agent: this.agentLabel }) + '\n', { mode: 0o600 });
101
+ this.armed = true;
102
+ }
103
+ catch { /* best-effort local channel */ }
104
+ }
105
+ /** Remove the rendezvous so the server stops emitting (the external agent keeps running; we stop seeing it). */
106
+ disarm() {
107
+ try {
108
+ // A second follow surface may have replaced the rendezvous after this session started. Never remove
109
+ // another surface's live handle when this one pauses, is killed, or exits.
110
+ const current = JSON.parse(readFileSync(this.rvPath, 'utf8'));
111
+ if (current.token === this.token)
112
+ rmSync(this.rvPath, { force: true });
113
+ }
114
+ catch { /* absent/malformed/another surface owns it */ }
115
+ this.armed = false;
116
+ }
117
+ /** Read and fold any records appended since the last pump. Public so tests can drive it without timers. */
118
+ pump() {
119
+ let text;
120
+ try {
121
+ const st = statSync(this.channelPath);
122
+ if (st.size <= this.offset) {
123
+ if (st.size < this.offset)
124
+ this.offset = 0;
125
+ else
126
+ return;
127
+ }
128
+ text = readFileSync(this.channelPath, 'utf8');
129
+ }
130
+ catch {
131
+ return;
132
+ }
133
+ const slice = text.slice(this.offset);
134
+ const nl = slice.lastIndexOf('\n');
135
+ if (nl < 0)
136
+ return; // no complete line yet
137
+ this.offset += nl + 1;
138
+ for (const line of slice.slice(0, nl).split('\n')) {
139
+ const t = line.trim();
140
+ if (!t)
141
+ continue;
142
+ let rec;
143
+ try {
144
+ rec = JSON.parse(t);
145
+ }
146
+ catch {
147
+ continue;
148
+ }
149
+ this.ingest(rec);
150
+ }
151
+ }
152
+ /** Fold one record: verify its token, strip it, emit into the hub, and move the terminal for a nav call. */
153
+ ingest(rec) {
154
+ if (typeof rec.phase !== 'string')
155
+ return;
156
+ // Only accept records written by the server we handed our token to (local 0600 file; defence in depth).
157
+ if (rec.token !== this.token)
158
+ return;
159
+ this.currentAgent = typeof rec.agent === 'string' && rec.agent ? rec.agent : this.agentLabel;
160
+ // The token NEVER enters the hub / activity / stream — fold only the observable fields.
161
+ const ev = this.hub.emit({
162
+ phase: rec.phase,
163
+ agent: this.currentAgent,
164
+ transport: 'mcp',
165
+ tool: rec.tool ?? null,
166
+ summary: rec.summary,
167
+ ms: rec.ms ?? null,
168
+ dataAgeSec: rec.dataAgeSec ?? null,
169
+ nav: rec.nav ?? null,
170
+ ok: rec.ok ?? true,
171
+ t: typeof rec.t === 'number' ? rec.t : undefined,
172
+ });
173
+ // Move the real terminal ONLY while the agent has control (a user keypress / take-over / kill flips this).
174
+ if (ev.phase === 'nav' && ev.ok !== false && ev.nav && this.canFollow()) {
175
+ const label = navLabel(this.currentAgent, ev.nav);
176
+ try {
177
+ this.opts.onNav?.(ev.nav, label);
178
+ }
179
+ catch { /* a bad hook never breaks the tail */ }
180
+ try {
181
+ this.opts.onStatus?.(label);
182
+ }
183
+ catch { /* ignore */ }
184
+ }
185
+ try {
186
+ this.opts.onEvent?.();
187
+ }
188
+ catch { /* ignore */ }
189
+ }
190
+ /** True when the terminal should mirror the agent: the agent still has control and is active. */
191
+ canFollow() {
192
+ if (this.follow.controlState !== 'agent')
193
+ return false;
194
+ const st = this.follow.view().agents.find((a) => a.name === this.currentAgent);
195
+ return !st || st.status === 'active';
196
+ }
197
+ // ----- the owner's controls. Each returns a short line to toast. -----
198
+ pause() { const l = this.follow.pause(this.currentAgent); this.paused = l.startsWith('paused '); this.opts.onStatus?.(l); return l; }
199
+ takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
200
+ resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
201
+ kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.opts.onStatus?.(l); return l; }
202
+ /** Any physical keypress while the agent is driving: the human takes over (no fight over the screen). */
203
+ userKey() { if (this.follow.controlState === 'agent')
204
+ this.takeOver(); }
205
+ get controlState() { return this.follow.controlState; }
206
+ get isPaused() { return this.paused; }
207
+ get screenModel() { return this.follow.screenModel; }
208
+ get streamPort() { return this.stream?.port ?? 0; }
209
+ view() { return this.follow.view(); }
210
+ /** Tear down: stop the timer, remove the rendezvous, close the stream. Safe to call more than once. */
211
+ async stop() {
212
+ if (this.timer) {
213
+ clearInterval(this.timer);
214
+ this.timer = null;
215
+ }
216
+ this.disarm();
217
+ if (this.stream) {
218
+ try {
219
+ await this.stream.close();
220
+ }
221
+ catch { /* ignore */ }
222
+ this.stream = null;
223
+ }
224
+ }
225
+ }
@@ -0,0 +1,221 @@
1
+ // dexterm — PNL CARD glue for the app (task-1407). `H` on the token, positions and followed-wallets screens opens a
2
+ // preview of the coin's PnL card built from dexterm's own records:
3
+ // token → that coin: LIVE when the signer's ledger has fills for it, else PAPER
4
+ // positions → every coin with fills (newest activity first; ← → cycle), LIVE or PAPER as above
5
+ // follows → the selected followed wallet's coins (TRACKED), newest first
6
+ // In the preview: x share to X · t copy as text · s save only · a show/hide wallet · z exact size/band ·
7
+ // f 16:9 ⇄ 1:1 · ← → other coin · esc close. Share = save + clipboard image + X compose intent in the user's own
8
+ // browser (pnl-card.ts shareCard); dexterm posts nothing. Nothing here trades, signs or edits a number.
9
+ import { mix } from '../../tui/src/index.js';
10
+ import { computeCard, renderCard, previewCanvas, cardText, paperCardInput, liveCardInput, trackedCardInput, CardError, } from "./pnl-card.js";
11
+ const MARK_VIA = 'dexscreener';
12
+ function coinMeta(state, mint) {
13
+ const tv = state.live?.token?.view;
14
+ if (tv && tv.mint === mint)
15
+ return { symbol: tv.symbol, name: tv.name, image: tv.image };
16
+ const row = state.screener.rows.find((r) => r.mint === mint);
17
+ return { symbol: row?.symbol ?? null, name: row?.name ?? null, image: row?.image ?? null };
18
+ }
19
+ function imageOf(state, url) {
20
+ if (!url || !state.images)
21
+ return null;
22
+ const st = state.images.get(url, 2);
23
+ return st.state === 'ready' ? st.img : null;
24
+ }
25
+ /** The newest price mark for a mint: the positions mark pass, else the open token's market panel ÷ SOL/USD. */
26
+ export function markFor(state, mint) {
27
+ const pos = state.live?.positions;
28
+ const p = pos?.markOf(mint) ?? null, at = pos?.markAt(mint) ?? null;
29
+ if (p !== null && at !== null)
30
+ return { priceSol: p, at, via: MARK_VIA };
31
+ const tv = state.live?.token?.view;
32
+ const solUsd = pos?.solUsd() ?? null;
33
+ const m = tv?.panels.market;
34
+ if (tv && tv.mint === mint && tv.price && solUsd && m?.fetchedAt)
35
+ return { priceSol: tv.price / solUsd, at: m.fetchedAt, via: `${m.via ?? 'market'} ÷ SOL/USD` };
36
+ return null;
37
+ }
38
+ /** Coins with paper or live fills, newest activity first. */
39
+ function tradedCoins(state) {
40
+ const live = state.live;
41
+ if (!live)
42
+ return [];
43
+ const last = new Map();
44
+ const see = (mint, t, symbol) => { const e = last.get(mint); if (!e || t > e.t)
45
+ last.set(mint, { t, symbol }); };
46
+ for (const r of live.wallet.trades(1000))
47
+ see(r.mint, r.t, r.symbol);
48
+ for (const r of live.livePositions?.records(1000) ?? [])
49
+ see(r.mint, r.t, r.symbol);
50
+ return [...last.entries()].sort((a, b) => b[1].t - a[1].t).map(([mint, v]) => ({ mint, symbol: v.symbol }));
51
+ }
52
+ /** Open the preview for the current screen. Returns a reason when there is nothing to card. */
53
+ export function openPnl(state) {
54
+ if (!state.live)
55
+ return 'PnL cards are made from recorded fills: run dexterm live (not --fixtures / --replay)';
56
+ const base = { idx: 0, stats: null, error: null, busy: false, notice: null, opts: { aspect: 'wide', showWallet: false, exactSize: false } };
57
+ if (state.screen === 'follows') {
58
+ const follows = state.copy?.follows() ?? [];
59
+ const f = follows[Math.max(0, Math.min(follows.length - 1, state.copyUi?.selected ?? 0))];
60
+ if (!f)
61
+ return 'follow a wallet first (i), then H cards its coins';
62
+ const seen = new Set();
63
+ const choices = [];
64
+ for (const tr of [...(state.copy?.trades(f.addr) ?? [])].sort((a, b) => b.t - a.t)) {
65
+ if (seen.has(tr.mint) || tr.side === 'transfer')
66
+ continue;
67
+ seen.add(tr.mint);
68
+ choices.push({ mint: tr.mint, symbol: coinMeta(state, tr.mint).symbol ?? tr.mint.slice(0, 4) });
69
+ }
70
+ if (!choices.length)
71
+ return 'no decoded swaps for this wallet yet (r polls now)';
72
+ state.pnl = { origin: 'follows', choices, wallet: f.addr, ...base };
73
+ }
74
+ else if (state.screen === 'token') {
75
+ const mint = state.live.token?.view?.mint ?? state.token?.mint ?? null;
76
+ if (!mint)
77
+ return 'open a coin first';
78
+ const has = (state.live.livePositions?.records(1, mint).length ?? 0) > 0 || state.live.wallet.trades(1000).some((r) => r.mint === mint);
79
+ if (!has)
80
+ return 'no paper or live fills for this coin yet: PnL cards come from your own recorded trades';
81
+ state.pnl = { origin: 'token', choices: [{ mint, symbol: coinMeta(state, mint).symbol ?? mint.slice(0, 4) }], wallet: null, ...base };
82
+ }
83
+ else if (state.screen === 'positions') {
84
+ const choices = tradedCoins(state);
85
+ if (!choices.length)
86
+ return 'no paper or live fills yet: B / S on a token to trade, then H here';
87
+ state.pnl = { origin: 'positions', choices, wallet: null, ...base };
88
+ }
89
+ else
90
+ return 'PnL cards open from the token, positions (P) or followed wallets (U) screens';
91
+ state.overlay = 'pnl';
92
+ return null;
93
+ }
94
+ /** The card input for the current choice, from the right ledger. Async only for a tracked coin that needs decimals / a price. */
95
+ export async function pnlInput(state, ui) {
96
+ const live = state.live;
97
+ if (!live)
98
+ throw new CardError('no live context');
99
+ const ch = ui.choices[ui.idx];
100
+ const meta = coinMeta(state, ch.mint);
101
+ const image = imageOf(state, meta.image);
102
+ if (ui.origin === 'follows' && ui.wallet && state.copy) {
103
+ const copy = state.copy;
104
+ const st = copy.status();
105
+ const stats = copy.stats(ui.wallet);
106
+ let mark = markFor(state, ch.mint);
107
+ let decimals = null;
108
+ // decimals + a price for a public mint: the same keyless reads the mirror checks make (no wallet, no user data)
109
+ const m = await copy.mirrorMarket(ch.mint, { symbol: meta.symbol });
110
+ decimals = m.decimals;
111
+ const solUsd = live.positions.solUsd();
112
+ if (!mark && m.priceUsd && m.priceAt && solUsd)
113
+ mark = { priceSol: m.priceUsd / solUsd, at: m.priceAt, via: `${MARK_VIA} ÷ SOL/USD` };
114
+ return trackedCardInput(copy.trades(ui.wallet), {
115
+ wallet: ui.wallet, mint: ch.mint, symbol: meta.symbol ?? m.symbol ?? ch.symbol, name: meta.name, decimals, mark,
116
+ historyAt: st.lastPollAt, complete: stats.sample.complete, image,
117
+ });
118
+ }
119
+ const liveRows = live.livePositions?.fills(ch.mint) ?? [];
120
+ if (liveRows.length) {
121
+ return liveCardInput(liveRows, { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, wallet: live.linkedWallet?.publicKey ?? null, mark: markFor(state, ch.mint), image });
122
+ }
123
+ return paperCardInput(live.wallet.trades(1000), { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, mark: markFor(state, ch.mint), image });
124
+ }
125
+ /** (Re)build the card for the current choice; on refusal the preview shows the reason instead of a card. */
126
+ export async function loadPnl(state, now = Date.now()) {
127
+ const ui = state.pnl;
128
+ if (!ui)
129
+ return;
130
+ try {
131
+ ui.stats = computeCard(await pnlInput(state, ui), now);
132
+ ui.error = null;
133
+ }
134
+ catch (e) {
135
+ ui.stats = null;
136
+ ui.error = e instanceof Error ? e.message : String(e);
137
+ }
138
+ }
139
+ /** Keys while the preview is open. Pure: the loop performs reload / share. */
140
+ export function pnlKey(ui, key) {
141
+ if (key.name === 'esc' || (key.name === 'char' && key.ch === 'q'))
142
+ return { kind: 'close' };
143
+ if (ui.busy)
144
+ return { kind: 'none' };
145
+ const ch = key.name === 'char' && !key.ctrl ? key.ch : undefined;
146
+ if (key.name === 'right' || ch === ']' || ch === 'l') {
147
+ if (ui.choices.length > 1) {
148
+ ui.idx = (ui.idx + 1) % ui.choices.length;
149
+ return { kind: 'reload' };
150
+ }
151
+ return { kind: 'none' };
152
+ }
153
+ if (key.name === 'left' || ch === '[' || ch === 'h') {
154
+ if (ui.choices.length > 1) {
155
+ ui.idx = (ui.idx - 1 + ui.choices.length) % ui.choices.length;
156
+ return { kind: 'reload' };
157
+ }
158
+ return { kind: 'none' };
159
+ }
160
+ if (ch === 'a') {
161
+ ui.opts.showWallet = !ui.opts.showWallet;
162
+ ui.notice = ui.opts.showWallet ? 'wallet + tx signatures SHOWN on the card' : 'wallet hidden';
163
+ return { kind: 'none' };
164
+ }
165
+ if (ch === 'z') {
166
+ ui.opts.exactSize = !ui.opts.exactSize;
167
+ ui.notice = ui.opts.exactSize ? 'exact SOL size SHOWN on the card' : 'size shown as a band';
168
+ return { kind: 'none' };
169
+ }
170
+ if (ch === 'f') {
171
+ ui.opts.aspect = ui.opts.aspect === 'wide' ? 'square' : 'wide';
172
+ return { kind: 'none' };
173
+ }
174
+ if (!ui.stats)
175
+ return { kind: 'none' };
176
+ if (ch === 'x')
177
+ return { kind: 'share', action: 'x' };
178
+ if (ch === 't')
179
+ return { kind: 'share', action: 'text' };
180
+ if (ch === 's')
181
+ return { kind: 'share', action: 'save' };
182
+ return { kind: 'none' };
183
+ }
184
+ const HINTS = 'x share to X · t copy text · s save · a wallet · z size · f 16:9/1:1 · ←→ coin · esc';
185
+ /** The preview overlay: the card itself at cell size when the terminal fits it, else its text; then the keys. */
186
+ export function renderPnlOverlay(c, state) {
187
+ const ui = state.pnl;
188
+ if (!ui)
189
+ return;
190
+ const t = c.theme;
191
+ c.noRain(0, 0, c.w, c.h);
192
+ c.fill(0, 0, c.w, c.h, ' ', { bg: mix(t.bg, '#000000', 0.5) });
193
+ const ch = ui.choices[ui.idx];
194
+ const head = `PnL CARD · $${ch?.symbol ?? '?'}${ui.choices.length > 1 ? ` (${ui.idx + 1}/${ui.choices.length})` : ''} · ${ui.opts.aspect === 'wide' ? '16:9' : '1:1'} · wallet ${ui.opts.showWallet ? 'SHOWN' : 'hidden'} · size ${ui.opts.exactSize ? 'EXACT' : 'band'}`;
195
+ c.text(1, 0, head, { fg: t.text, bg: t.headBg, b: true }, c.w - 2);
196
+ c.fill(0, c.h - 1, c.w, 1, ' ', { bg: t.headBg });
197
+ c.text(1, c.h - 1, ui.busy ? 'working… (esc closes)' : (ui.notice ?? HINTS), { fg: ui.notice ? t.warn : t.muted, bg: t.headBg }, c.w - 2);
198
+ const area = { x: 0, y: 1, w: c.w, h: c.h - 2 };
199
+ if (!ui.stats) {
200
+ c.text(2, 3, ui.error ? `no card: ${ui.error}` : 'building the card…', { fg: ui.error ? t.warn : t.muted, bg: c.bg[c.idx(2, 3)] }, c.w - 4);
201
+ return;
202
+ }
203
+ const r = renderCard(ui.stats, ui.opts);
204
+ const pv = previewCanvas(r);
205
+ if (pv.w <= area.w && pv.h <= area.h) {
206
+ const ox = area.x + Math.floor((area.w - pv.w) / 2), oy = area.y + Math.floor((area.h - pv.h) / 2);
207
+ for (let y = 0; y < pv.h; y++)
208
+ for (let x = 0; x < pv.w; x++) {
209
+ const i = pv.idx(x, y), j = c.idx(ox + x, oy + y);
210
+ c.ch[j] = pv.ch[i];
211
+ c.fg[j] = pv.fg[i];
212
+ c.bg[j] = pv.bg[i];
213
+ c.at[j] = pv.at[i];
214
+ }
215
+ return;
216
+ }
217
+ // too small for the card grid: the same facts as text (the PNG is still full size)
218
+ const lines = cardText(ui.stats, ui.opts).trimEnd().split('\n');
219
+ lines.forEach((l, k) => { if (2 + k < c.h - 1)
220
+ c.text(2, 2 + k, l, { fg: k === 0 ? t.warn : t.text, bg: c.bg[c.idx(2, 2 + k)] }, c.w - 4); });
221
+ }