dexterm-cli 0.1.5 → 0.1.7

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 (379) hide show
  1. package/dist/packages/agents/src/index.js +2 -0
  2. package/dist/packages/agents/src/schema.js +22 -1
  3. package/dist/packages/agents/src/spectator.js +54 -0
  4. package/dist/packages/agents/src/story.js +532 -0
  5. package/dist/packages/agents/src/tiers.js +28 -2
  6. package/dist/packages/agents/src/tools.js +31 -2
  7. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  8. package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
  9. package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
  10. package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
  11. package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
  12. package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
  13. package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
  14. package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
  15. package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
  16. package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
  17. package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
  18. package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
  19. package/dist/packages/autotrade/src/account.js +74 -0
  20. package/dist/packages/autotrade/src/backtest.js +357 -0
  21. package/dist/packages/autotrade/src/basis-text.js +32 -0
  22. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  23. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  24. package/dist/packages/autotrade/src/chat/config.js +93 -0
  25. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  26. package/dist/packages/autotrade/src/chat/control.js +103 -0
  27. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  28. package/dist/packages/autotrade/src/chat/format.js +71 -0
  29. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  30. package/dist/packages/autotrade/src/chat/index.js +18 -0
  31. package/dist/packages/autotrade/src/chat/notify.js +292 -0
  32. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  33. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  34. package/dist/packages/autotrade/src/chat/secrets.js +99 -0
  35. package/dist/packages/autotrade/src/chat/service.js +248 -0
  36. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  37. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  38. package/dist/packages/autotrade/src/engine.js +387 -0
  39. package/dist/packages/autotrade/src/execution-port.js +97 -0
  40. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  41. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  42. package/dist/packages/autotrade/src/fleet.js +639 -0
  43. package/dist/packages/autotrade/src/index.js +53 -0
  44. package/dist/packages/autotrade/src/ledger.js +108 -0
  45. package/dist/packages/autotrade/src/live-source.js +182 -0
  46. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  47. package/dist/packages/autotrade/src/monitor.js +190 -0
  48. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  49. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  50. package/dist/packages/autotrade/src/replay.js +198 -0
  51. package/dist/packages/autotrade/src/runner-config.js +99 -0
  52. package/dist/packages/autotrade/src/runner.js +872 -0
  53. package/dist/packages/autotrade/src/safety.js +243 -0
  54. package/dist/packages/autotrade/src/session.js +269 -0
  55. package/dist/packages/autotrade/src/sessions.js +503 -0
  56. package/dist/packages/autotrade/src/store.js +250 -0
  57. package/dist/packages/autotrade/src/strategy.js +256 -0
  58. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  59. package/dist/packages/autotrade/src/yolo.js +140 -0
  60. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  61. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
  62. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
  63. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  64. package/dist/packages/cli/src/agent-cmd.js +6 -4
  65. package/dist/packages/cli/src/agent-spectator.js +17 -1
  66. package/dist/packages/cli/src/ai-cmd.js +412 -0
  67. package/dist/packages/cli/src/analyst/backend.js +105 -15
  68. package/dist/packages/cli/src/analyst/codex.js +14 -9
  69. package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
  70. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  71. package/dist/packages/cli/src/analyst/session.js +17 -3
  72. package/dist/packages/cli/src/app-help-gen.js +51 -15
  73. package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
  74. package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
  75. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  76. package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
  77. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  78. package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
  79. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  80. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  81. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  82. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  83. package/dist/packages/cli/src/autotrade-cmd.js +168 -0
  84. package/dist/packages/cli/src/backtest-cmd.js +13 -2
  85. package/dist/packages/cli/src/display.js +208 -23
  86. package/dist/packages/cli/src/doctor.js +62 -3
  87. package/dist/packages/cli/src/export.js +10 -2
  88. package/dist/packages/cli/src/index.js +1 -1
  89. package/dist/packages/cli/src/ledger-cmd.js +288 -0
  90. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  91. package/dist/packages/cli/src/live/dev-history.js +31 -15
  92. package/dist/packages/cli/src/live/index.js +28 -90
  93. package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
  94. package/dist/packages/cli/src/live/screens/token-live.js +48 -15
  95. package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
  96. package/dist/packages/cli/src/live/trade.js +30 -453
  97. package/dist/packages/cli/src/live/watchlist.js +1 -1
  98. package/dist/packages/cli/src/main.js +290 -199
  99. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  100. package/dist/packages/cli/src/mcp-follow.js +103 -6
  101. package/dist/packages/cli/src/plan-cmd.js +15 -5
  102. package/dist/packages/cli/src/pnl-app.js +31 -21
  103. package/dist/packages/cli/src/pnl-card.js +158 -21
  104. package/dist/packages/cli/src/popup.js +38 -6
  105. package/dist/packages/cli/src/record-cmd.js +141 -20
  106. package/dist/packages/cli/src/scan/ui.js +7 -1
  107. package/dist/packages/cli/src/screens/agent-hud.js +773 -0
  108. package/dist/packages/cli/src/screens/analyst-session.js +11 -4
  109. package/dist/packages/cli/src/screens/analyst.js +3 -2
  110. package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
  111. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  112. package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
  113. package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
  114. package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
  115. package/dist/packages/cli/src/screens/brain.js +634 -0
  116. package/dist/packages/cli/src/screens/newpairs.js +1 -1
  117. package/dist/packages/cli/src/screens/overlay.js +5 -2
  118. package/dist/packages/cli/src/screens/plans.js +5 -17
  119. package/dist/packages/cli/src/screens/pnl-chip.js +144 -0
  120. package/dist/packages/cli/src/screens/positions.js +6 -28
  121. package/dist/packages/cli/src/screens/screener.js +1 -0
  122. package/dist/packages/cli/src/screens/ticket.js +38 -205
  123. package/dist/packages/cli/src/screens/token.js +18 -9
  124. package/dist/packages/cli/src/screens/trending.js +1 -1
  125. package/dist/packages/cli/src/state.js +88 -18
  126. package/dist/packages/cli/src/telegram-cmd.js +451 -0
  127. package/dist/packages/cli/src/trade.js +3 -128
  128. package/dist/packages/cli/src/version.js +1 -1
  129. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  130. package/dist/packages/cli/src/wallet-command.js +8 -11
  131. package/dist/packages/cli/src/widgets.js +0 -12
  132. package/dist/packages/custom/src/config.js +20 -2
  133. package/dist/packages/custom/src/schema.js +41 -10
  134. package/dist/packages/custom/src/workspace.js +3 -3
  135. package/dist/packages/data/src/basis.js +191 -0
  136. package/dist/packages/data/src/detail.js +53 -2
  137. package/dist/packages/data/src/index.js +2 -14
  138. package/dist/packages/data/src/larp/score.js +220 -0
  139. package/dist/packages/data/src/larp/x-account.js +316 -0
  140. package/dist/packages/data/src/store.js +14 -5
  141. package/dist/packages/data/src/trade/agent/paper-desk.js +300 -23
  142. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  143. package/dist/packages/data/src/trade/bridge.js +4 -102
  144. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  145. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  146. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  147. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  148. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  149. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  150. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  151. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  152. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  153. package/dist/packages/data/src/trade/plans/engine.js +7 -0
  154. package/dist/packages/data/src/trade/plans/index.js +0 -1
  155. package/dist/packages/data/src/trade/plans/mandate.js +28 -15
  156. package/dist/packages/data/src/trade/plans/service.js +4 -28
  157. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  158. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  159. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  160. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  161. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  162. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  163. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  164. package/dist/packages/mcp/assets/kb/app-help/dev-history.md +1 -1
  165. package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
  166. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  167. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  168. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  169. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  170. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  171. package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
  172. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  173. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
  174. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  175. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  176. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  177. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  178. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +6 -0
  179. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  180. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  181. package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
  182. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  183. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  184. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +7 -3
  186. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  188. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  189. package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
  194. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  195. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  197. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  198. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  200. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  201. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  202. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  203. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  204. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  205. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  208. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  209. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  210. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  211. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  219. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  220. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  223. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  225. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +9 -7
  226. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  227. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  228. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  229. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  230. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  231. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  232. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  233. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  234. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  235. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
  236. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  237. package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -0
  238. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
  240. package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
  241. package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
  242. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  243. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  244. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
  246. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  247. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  248. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  249. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  250. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  251. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  254. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  255. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  256. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  257. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  260. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  262. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  263. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  264. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +4 -13
  266. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  267. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +40 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  269. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  270. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  271. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  272. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  273. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  274. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  275. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  276. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
  277. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +3 -1
  278. package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
  279. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  280. package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +10 -7
  281. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  282. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  283. package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
  284. package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
  285. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
  286. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
  287. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
  288. package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
  289. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  290. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  291. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  292. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  293. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  294. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  295. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  296. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  297. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  298. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  299. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  300. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  301. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  302. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  303. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  304. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  305. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  306. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  307. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  308. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  309. package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
  310. package/dist/packages/mcp/src/app-help.js +1 -1
  311. package/dist/packages/mcp/src/autotrade-tools.js +944 -0
  312. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  313. package/dist/packages/mcp/src/engine/config.js +159 -0
  314. package/dist/packages/mcp/src/engine/index.js +10 -0
  315. package/dist/packages/mcp/src/engine/loop.js +101 -0
  316. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  317. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  318. package/dist/packages/mcp/src/engine/probe.js +110 -0
  319. package/dist/packages/mcp/src/engine/reason.js +35 -0
  320. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  321. package/dist/packages/mcp/src/index.js +7 -1
  322. package/dist/packages/mcp/src/ledger-tools.js +346 -0
  323. package/dist/packages/mcp/src/paper-agent.js +5 -2
  324. package/dist/packages/mcp/src/server.js +285 -36
  325. package/dist/packages/mcp/src/spectator-sink.js +85 -0
  326. package/dist/packages/packs/src/backtest.js +3 -1
  327. package/dist/packages/packs/src/index.js +1 -0
  328. package/dist/packages/packs/src/larp-score-backtest.js +119 -0
  329. package/dist/packages/popup/src/pages/chart-lib.js +169 -35
  330. package/dist/packages/popup/src/pages/chart.css +18 -0
  331. package/dist/packages/popup/src/pages/chart.html +6 -1
  332. package/dist/packages/popup/src/pages/chart.js +136 -10
  333. package/dist/packages/popup/src/pages/spectator.css +45 -0
  334. package/dist/packages/popup/src/pages/spectator.html +1 -0
  335. package/dist/packages/popup/src/pages/spectator.js +69 -0
  336. package/dist/packages/popup/src/protocol.js +4 -0
  337. package/dist/packages/popup/src/server.js +9 -1
  338. package/dist/packages/recorder/src/dev-history.js +7 -3
  339. package/dist/packages/recorder/src/index.js +2 -0
  340. package/dist/packages/recorder/src/recorder.js +63 -6
  341. package/dist/packages/recorder/src/retention.js +100 -0
  342. package/dist/packages/recorder/src/schema.js +34 -1
  343. package/dist/packages/recorder/src/ticks.js +237 -0
  344. package/dist/packages/tui/src/canvas.js +7 -0
  345. package/dist/packages/tui/src/fmt.js +53 -0
  346. package/dist/packages/tui/src/index.js +1 -0
  347. package/dist/packages/tui/src/keymap.js +25 -1
  348. package/dist/packages/tui/src/markers.js +219 -0
  349. package/dist/packages/tui/src/widgets.js +158 -6
  350. package/package.json +2 -1
  351. package/dist/packages/data/src/trade/caps.js +0 -323
  352. package/dist/packages/data/src/trade/executor.js +0 -933
  353. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  354. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  355. package/dist/packages/data/src/trade/signer/client.js +0 -197
  356. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  357. package/dist/packages/data/src/trade/signer/host.js +0 -97
  358. package/dist/packages/data/src/trade/signer/main.js +0 -121
  359. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  360. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  361. package/dist/packages/data/src/trade/signer/service.js +0 -400
  362. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  363. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  364. package/dist/packages/data/src/trade/swap-build.js +0 -108
  365. package/dist/packages/data/src/trade/token-delta.js +0 -201
  366. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  367. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  368. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  369. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  370. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  371. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  372. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  373. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  374. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  375. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  376. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  377. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  378. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  379. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -302,7 +302,13 @@ const USAGE = `dexterm mcp [<command>]
302
302
  --print only print the snippet, never write
303
303
  --node emit a "node <path> mcp" command (when dexterm is not on PATH)
304
304
  --yes write without the confirmation prompt
305
- doctor spawn the server, do the MCP handshake, and list the tools/prompts/resources`;
305
+ doctor spawn the server, do the MCP handshake, and list the tools/prompts/resources
306
+
307
+ for agent authors: \`dexterm mcp doctor\` lists every tool with its description. Reads (market data, the trade
308
+ ledger: ledger_trades / positions / summary / equity / compare, candidate_dossier, outcomes_lookup, trading_status,
309
+ telegram_status) never change anything; strategy_propose_* and backtest_request only file a proposal the user applies
310
+ or runs; the paper_* tools trade the SIMULATED wallet and only inside a mandate the user armed. No tool can sign, send,
311
+ enable live trading or raise a cap. Docs: https://dexterm.xyz/docs/agents/`;
306
312
  /** `dexterm mcp …`. With no subcommand (or an arg the server consumes, e.g. --replay) it runs the server. */
307
313
  export async function runMcp(rest, io) {
308
314
  const sub = rest[0];
@@ -14,11 +14,52 @@
14
14
  // kill" are local follow controls: pause/take-over stop the terminal mirroring the agent (the external agent
15
15
  // keeps running — we simply stop moving the screen), and kill removes the rendezvous so the server stops
16
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';
17
+ import { writeFileSync, readFileSync, rmSync, mkdirSync, statSync, openSync, readSync, closeSync } from 'node:fs';
18
18
  import { dirname } from 'node:path';
19
19
  import { randomBytes } from 'node:crypto';
20
- import { SpectatorHub, FollowController, SpectatorServer, } from '../../agents/src/index.js';
20
+ import { SpectatorHub, FollowController, SpectatorServer, AgentStory, sanitizeDetail, } from '../../agents/src/index.js';
21
21
  import { rendezvousPath, writeFollowScreen, clearFollowScreen } from '../../mcp/src/index.js';
22
+ /**
23
+ * task-1466: the agent's own words in one JSONL line of a thoughts feed. Understands Claude Code stream-json
24
+ * (`assistant` messages: text and thinking blocks), the capture harness's brain feed (`{tEpoch, ev}` wrapping one),
25
+ * and plain `{"thought": "…"}` / `{"text": "…"}` lines. Anything else yields nothing. Text only; never acted on.
26
+ */
27
+ export function thoughtsOf(line) {
28
+ const s = line.trim();
29
+ if (!s || s[0] !== '{' || s.length > 200_000)
30
+ return [];
31
+ let o;
32
+ try {
33
+ o = JSON.parse(s);
34
+ }
35
+ catch {
36
+ return [];
37
+ }
38
+ let t = typeof o.tEpoch === 'number' && Number.isFinite(o.tEpoch) ? o.tEpoch : null;
39
+ if (o.ev && typeof o.ev === 'object')
40
+ o = o.ev;
41
+ if (t === null && typeof o.t === 'number' && Number.isFinite(o.t) && o.t > 1e12)
42
+ t = o.t;
43
+ // task-1473: a demo / stand-in feed marks its scripted lines `"demo": true` (the brain view tags them "demo text")
44
+ if (typeof o.thought === 'string')
45
+ return [{ text: o.thought, t, ...(o.demo === true ? { demo: true } : {}) }];
46
+ if (o.type === 'assistant' && o.message && typeof o.message === 'object') {
47
+ const content = o.message.content;
48
+ if (!Array.isArray(content))
49
+ return [];
50
+ const out = [];
51
+ for (const b of content) {
52
+ if (b?.type === 'text' && typeof b.text === 'string')
53
+ out.push({ text: b.text, t });
54
+ else if (b?.type === 'thinking' && typeof b.thinking === 'string' && b.thinking.trim())
55
+ out.push({ text: b.thinking, t });
56
+ }
57
+ return out;
58
+ }
59
+ if (typeof o.text === 'string' && !o.type)
60
+ return [{ text: o.text, t }];
61
+ return [];
62
+ }
22
63
  function navLabel(agent, nav) {
23
64
  const coin = nav.mint ? ` ${nav.mint.length > 10 ? nav.mint.slice(0, 4) + '…' + nav.mint.slice(-4) : nav.mint}` : '';
24
65
  if (nav.screen === 'token')
@@ -45,6 +86,8 @@ function navLabel(agent, nav) {
45
86
  export class McpFollowSession {
46
87
  hub;
47
88
  follow;
89
+ /** task-1466: the story the spectator v2 HUD draws (phase rail, timeline, thoughts, PAPER trade arc). */
90
+ story;
48
91
  opts;
49
92
  rvPath;
50
93
  channelPath;
@@ -52,6 +95,7 @@ export class McpFollowSession {
52
95
  agentLabel;
53
96
  currentAgent;
54
97
  offset = 0;
98
+ thoughtsOffset = 0;
55
99
  timer = null;
56
100
  stream = null;
57
101
  armed = false;
@@ -71,7 +115,8 @@ export class McpFollowSession {
71
115
  kickAgent: () => { this.disarm(); return 1; },
72
116
  };
73
117
  this.follow = new FollowController({ controls, activityCap: Math.max(1, opts.activityCap ?? 200) });
74
- this.hub.on((ev) => this.follow.apply(ev));
118
+ this.story = new AgentStory(this.agentLabel);
119
+ this.hub.on((ev) => { this.follow.apply(ev); this.story.apply(ev); });
75
120
  }
76
121
  /** Arm the rendezvous (so a `dexterm mcp` server starts emitting) and begin tailing. Idempotent. */
77
122
  async start() {
@@ -133,6 +178,51 @@ export class McpFollowSession {
133
178
  }
134
179
  /** Read and fold any records appended since the last pump. Public so tests can drive it without timers. */
135
180
  pump() {
181
+ this.pumpThoughts();
182
+ this.pumpChannel();
183
+ }
184
+ /** task-1466: new lines of the agent's own words → the story's thought ribbon (bounded: ≤ 256 KB per pump). */
185
+ pumpThoughts() {
186
+ const file = this.opts.thoughtsFile;
187
+ if (!file)
188
+ return;
189
+ let text;
190
+ try {
191
+ const st = statSync(file);
192
+ if (st.size < this.thoughtsOffset)
193
+ this.thoughtsOffset = 0;
194
+ if (st.size === this.thoughtsOffset)
195
+ return;
196
+ if (st.size - this.thoughtsOffset > 256 * 1024)
197
+ this.thoughtsOffset = st.size - 256 * 1024; // skip a backlog, keep the tail
198
+ const fd = openSync(file, 'r');
199
+ try {
200
+ const buf = Buffer.alloc(st.size - this.thoughtsOffset);
201
+ readSync(fd, buf, 0, buf.length, this.thoughtsOffset);
202
+ text = buf.toString('utf8');
203
+ }
204
+ finally {
205
+ closeSync(fd);
206
+ }
207
+ }
208
+ catch {
209
+ return;
210
+ }
211
+ const nl = text.lastIndexOf('\n');
212
+ if (nl < 0)
213
+ return;
214
+ this.thoughtsOffset += Buffer.byteLength(text.slice(0, nl + 1), 'utf8');
215
+ const now = (this.opts.now ?? Date.now)();
216
+ for (const line of text.slice(0, nl).split('\n')) {
217
+ for (const th of thoughtsOf(line))
218
+ this.story.thought(th.text, th.t ?? now, 'agent', th.demo === true);
219
+ }
220
+ try {
221
+ this.opts.onEvent?.();
222
+ }
223
+ catch { /* ignore */ }
224
+ }
225
+ pumpChannel() {
136
226
  let text;
137
227
  try {
138
228
  const st = statSync(this.channelPath);
@@ -184,6 +274,7 @@ export class McpFollowSession {
184
274
  ms: rec.ms ?? null,
185
275
  dataAgeSec: rec.dataAgeSec ?? null,
186
276
  nav: rec.nav ?? null,
277
+ detail: sanitizeDetail(rec.detail),
187
278
  ok: rec.ok ?? true,
188
279
  t: typeof rec.t === 'number' ? rec.t : undefined,
189
280
  });
@@ -225,10 +316,14 @@ export class McpFollowSession {
225
316
  takeOver() { const l = this.follow.takeOver(this.currentAgent); this.paused = false; this.control('takeover'); this.opts.onStatus?.(l); return l; }
226
317
  resume() { const l = this.follow.resume(this.currentAgent); this.paused = false; this.control('running'); this.opts.onStatus?.(l); return l; }
227
318
  kill() { const l = this.follow.kill(this.currentAgent); this.paused = false; this.control('stopped'); this.opts.onStatus?.(l); return l; }
228
- control(state) { try {
229
- this.opts.onControl?.(state);
319
+ control(state) {
320
+ const now = (this.opts.now ?? Date.now)();
321
+ this.story.setControl(state === 'stopped' ? 'killed' : state, now);
322
+ try {
323
+ this.opts.onControl?.(state);
324
+ }
325
+ catch { /* never breaks the key loop */ }
230
326
  }
231
- catch { /* never breaks the key loop */ } }
232
327
  /** Any physical keypress while the agent is driving: the human takes over (no fight over the screen). */
233
328
  userKey() { if (this.follow.controlState === 'agent')
234
329
  this.takeOver(); }
@@ -237,6 +332,8 @@ export class McpFollowSession {
237
332
  get screenModel() { return this.follow.screenModel; }
238
333
  get streamPort() { return this.stream?.port ?? 0; }
239
334
  view() { return this.follow.view(); }
335
+ /** task-1466: the story at `now` for the HUD. */
336
+ storyView(now = (this.opts.now ?? Date.now)()) { return this.story.view(now); }
240
337
  /** Tear down: stop the timer, remove the rendezvous, close the stream. Safe to call more than once. */
241
338
  async stop() {
242
339
  if (this.timer) {
@@ -9,6 +9,7 @@
9
9
  // dexterm plan journal [--verify] [--export]
10
10
  // dexterm mandate draft [--total 100] [--per-trade 20] [--per-coin 40] [--daily-loss 50]
11
11
  // [--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4]
12
+ // [--max-open 20] [--max-impact 3] [--paper]
12
13
  // dexterm mandate list [--json] | arm <id> | revoke <id>
13
14
  //
14
15
  // Arming is the only gate: `arm` reads ONE real keystroke in raw mode (ttyKeypress) and refuses a pipe,
@@ -44,7 +45,10 @@ const PLAN_USAGE = `dexterm plan <command>
44
45
  journal [--verify] [--export]`;
45
46
  const MANDATE_USAGE = `dexterm mandate <command>
46
47
  draft [--total 100] [--per-trade 20] [--per-coin 40] [--daily-loss 50]
47
- [--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4] [--json]
48
+ [--max-tier keypress|ptt-held|hands-free] [--ttl-hours 4] [--max-open 20] [--max-impact 3]
49
+ [--paper] [--json]
50
+ (--paper: a PAPER-ONLY mandate for the agent paper desk, with its own
51
+ paper ceilings; no plan, copy trade or signer can ever use it)
48
52
  list [--json]
49
53
  arm <id> (needs a physical keypress on a TTY; a pipe/agent is refused)
50
54
  revoke <id>`;
@@ -203,16 +207,20 @@ export async function runMandate(rest, io) {
203
207
  const num = (n) => (flag(rest, n) !== undefined ? parseAmount(flag(rest, n)) : undefined);
204
208
  const maxTier = flag(rest, '--max-tier');
205
209
  const ttlHours = flag(rest, '--ttl-hours') ? Number(flag(rest, '--ttl-hours')) : undefined;
210
+ const plain = (n) => (flag(rest, n) !== undefined ? Number(flag(rest, n)) : undefined);
206
211
  const m = svc.draftMandate({
207
212
  totalUsd: num('--total'), perTradeUsd: num('--per-trade'), perCoinUsd: num('--per-coin'),
208
213
  dailyLossUsd: num('--daily-loss'), maxTier, ttlMs: ttlHours ? ttlHours * 3600_000 : undefined,
214
+ maxOpenPositions: plain('--max-open'), maxPriceImpactPct: plain('--max-impact'),
215
+ ...(has(rest, '--paper') ? { paperOnly: true } : {}),
209
216
  });
210
217
  if (has(rest, '--json')) {
211
- out(JSON.stringify({ id: m.id, budget: m.budget, maxTier: m.maxTier }));
218
+ out(JSON.stringify({ id: m.id, budget: m.budget, maxTier: m.maxTier, paperOnly: m.paperOnly === true }));
212
219
  return 0;
213
220
  }
214
- out(`drafted ${m.id}`);
221
+ out(`drafted ${m.id}${m.paperOnly ? ' (PAPER-ONLY: agent paper desk only)' : ''}`);
215
222
  out(` total ${fmtUsd(m.budget.totalUsd)} · per-trade ${fmtUsd(m.budget.perTradeUsd)} · per-coin ${fmtUsd(m.budget.perCoinUsd)} · daily-loss ${fmtUsd(m.budget.dailyLossUsd)} · maxTier ${m.maxTier}`);
223
+ out(` max open ${m.limits.maxOpenPositions} · max price impact ${m.limits.maxPriceImpactPct}%`);
216
224
  out(' DRAFT only — not armed. Arm with: dexterm mandate arm ' + m.id + ' (physical keypress)');
217
225
  return 0;
218
226
  }
@@ -228,7 +236,7 @@ export async function runMandate(rest, io) {
228
236
  return 0;
229
237
  }
230
238
  for (const r of rows)
231
- out(`${r.id.padEnd(24)} ${r.status.padEnd(7)} total ${fmtUsd(r.totalUsd)} · reserved ${fmtUsd(r.reservedUsd)} · spent ${fmtUsd(r.spentUsd)} · maxTier ${r.maxTier}`);
239
+ out(`${r.id.padEnd(24)} ${r.status.padEnd(7)} total ${fmtUsd(r.totalUsd)} · reserved ${fmtUsd(r.reservedUsd)} · spent ${fmtUsd(r.spentUsd)} · maxTier ${r.maxTier}${r.paperOnly ? ' · PAPER-ONLY' : ''}`);
232
240
  return 0;
233
241
  }
234
242
  case 'arm': {
@@ -243,7 +251,9 @@ export async function runMandate(rest, io) {
243
251
  errOut(`no such mandate ${id}`);
244
252
  return 1;
245
253
  }
246
- out(`Arm standing mandate ${id}? This lets armed plans fire at machine speed from the capped fast wallet.`);
254
+ out(row.paperOnly
255
+ ? `Arm PAPER-ONLY mandate ${id}? This lets the agent paper desk book simulated fills under these caps. No plan, copy trade or signer can use it.`
256
+ : `Arm standing mandate ${id}? This lets armed plans fire at machine speed on PAPER (simulated fills; dexterm places no live order).`);
247
257
  out(` total ${fmtUsd(row.totalUsd)} · per-trade ${fmtUsd(row.perTradeUsd)} · per-coin ${fmtUsd(row.perCoinUsd)} · maxTier ${row.maxTier}`);
248
258
  out('Hold any key to confirm (physical keypress). A pipe or agent cannot arm. Waiting 15s…');
249
259
  const key = await ttyKeypress(io.stdin, 15_000);
@@ -4,17 +4,26 @@
4
4
  // positions → every coin with fills (newest activity first; ← → cycle), LIVE or PAPER as above
5
5
  // follows → the selected followed wallet's coins (TRACKED), newest first
6
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
7
+ // m market cap ⇄ SOL price (task-1518; mcap is the default when the coin's supply is known) · f 16:9 ⇄ 1:1 ·
8
+ // ← → other coin · esc close. Share = save + clipboard image + X compose intent in the user's own
8
9
  // browser (pnl-card.ts shareCard); dexterm posts nothing. Nothing here trades, signs or edits a number.
9
10
  import { mix } from '../../tui/src/index.js';
10
- import { computeCard, renderCard, previewCanvas, cardText, paperCardInput, liveCardInput, trackedCardInput, CardError, } from "./pnl-card.js";
11
+ import { computeCard, renderCard, previewCanvas, cardText, paperCardInput, trackedCardInput, cardSupply, mcapView, CardError, } from "./pnl-card.js";
11
12
  const MARK_VIA = 'dexscreener';
12
13
  function coinMeta(state, mint) {
13
14
  const tv = state.live?.token?.view;
14
15
  if (tv && tv.mint === mint)
15
- return { symbol: tv.symbol, name: tv.name, image: tv.image };
16
+ return { symbol: tv.symbol, name: tv.name, image: tv.image, launchpad: tv.launchpad ?? null, fdv: tv.fdv ?? null, price: tv.price ?? null };
16
17
  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
+ return { symbol: row?.symbol ?? null, name: row?.name ?? null, image: row?.image ?? null, launchpad: row?.launchpad ?? null, fdv: row?.fdv ?? null, price: row?.price ?? null };
19
+ }
20
+ /** The market-cap inputs for a coin: its recorded supply (FDV ÷ price, else pump.fun's 1B) and today's SOL/USD. */
21
+ function mcapInputs(state, mint, meta) {
22
+ const solUsd = state.live?.positions.solUsd() ?? null;
23
+ return {
24
+ supply: cardSupply({ mint, launchpad: meta.launchpad, fdv: meta.fdv, price: meta.price, via: 'market data' }),
25
+ usdPerSol: solUsd && solUsd > 0 ? { usd: solUsd, source: `$${solUsd.toFixed(2)}/SOL now (display conversion)` } : null,
26
+ };
18
27
  }
19
28
  function imageOf(state, url) {
20
29
  if (!url || !state.images)
@@ -35,7 +44,7 @@ export function markFor(state, mint) {
35
44
  return { priceSol: tv.price / solUsd, at: m.fetchedAt, via: `${m.via ?? 'market'} ÷ SOL/USD` };
36
45
  return null;
37
46
  }
38
- /** Coins with paper or live fills, newest activity first. */
47
+ /** Coins with paper fills, newest activity first. */
39
48
  function tradedCoins(state) {
40
49
  const live = state.live;
41
50
  if (!live)
@@ -45,8 +54,6 @@ function tradedCoins(state) {
45
54
  last.set(mint, { t, symbol }); };
46
55
  for (const r of live.wallet.trades(1000))
47
56
  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
57
  return [...last.entries()].sort((a, b) => b[1].t - a[1].t).map(([mint, v]) => ({ mint, symbol: v.symbol }));
51
58
  }
52
59
  /** Open the preview for the current screen. Returns a reason when there is nothing to card. */
@@ -75,15 +82,15 @@ export function openPnl(state) {
75
82
  const mint = state.live.token?.view?.mint ?? state.token?.mint ?? null;
76
83
  if (!mint)
77
84
  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);
85
+ const has = state.live.wallet.trades(1000).some((r) => r.mint === mint);
79
86
  if (!has)
80
- return 'no paper or live fills for this coin yet: PnL cards come from your own recorded trades';
87
+ return 'no paper fills for this coin yet: PnL cards come from your own recorded trades';
81
88
  state.pnl = { origin: 'token', choices: [{ mint, symbol: coinMeta(state, mint).symbol ?? mint.slice(0, 4) }], wallet: null, ...base };
82
89
  }
83
90
  else if (state.screen === 'positions') {
84
91
  const choices = tradedCoins(state);
85
92
  if (!choices.length)
86
- return 'no paper or live fills yet: B / S on a token to trade, then H here';
93
+ return 'no paper fills yet: B / S on a token to paper-trade, then H here';
87
94
  state.pnl = { origin: 'positions', choices, wallet: null, ...base };
88
95
  }
89
96
  else
@@ -111,16 +118,12 @@ export async function pnlInput(state, ui) {
111
118
  const solUsd = live.positions.solUsd();
112
119
  if (!mark && m.priceUsd && m.priceAt && solUsd)
113
120
  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 });
121
+ return { ...trackedCardInput(copy.trades(ui.wallet), {
122
+ wallet: ui.wallet, mint: ch.mint, symbol: meta.symbol ?? m.symbol ?? ch.symbol, name: meta.name, decimals, mark,
123
+ historyAt: st.lastPollAt, complete: stats.sample.complete, image,
124
+ }), ...mcapInputs(state, ch.mint, meta) };
122
125
  }
123
- return paperCardInput(live.wallet.trades(1000), { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, mark: markFor(state, ch.mint), image });
126
+ return { ...paperCardInput(live.wallet.trades(1000), { mint: ch.mint, symbol: meta.symbol ?? undefined, name: meta.name, mark: markFor(state, ch.mint), image }), ...mcapInputs(state, ch.mint, meta) };
124
127
  }
125
128
  /** (Re)build the card for the current choice; on refusal the preview shows the reason instead of a card. */
126
129
  export async function loadPnl(state, now = Date.now()) {
@@ -167,6 +170,13 @@ export function pnlKey(ui, key) {
167
170
  ui.notice = ui.opts.exactSize ? 'exact SOL size SHOWN on the card' : 'size shown as a band';
168
171
  return { kind: 'none' };
169
172
  }
173
+ if (ch === 'm') {
174
+ const now = ui.stats ? mcapView(ui.stats, ui.opts).basis : (ui.opts.basis ?? 'mcap');
175
+ ui.opts.basis = now === 'mcap' ? 'price' : 'mcap';
176
+ const v = ui.stats ? mcapView(ui.stats, ui.opts) : null;
177
+ ui.notice = ui.opts.basis === 'price' ? 'entry / exit as SOL price' : v?.fallback ?? 'entry / exit as market cap';
178
+ return { kind: 'none' };
179
+ }
170
180
  if (ch === 'f') {
171
181
  ui.opts.aspect = ui.opts.aspect === 'wide' ? 'square' : 'wide';
172
182
  return { kind: 'none' };
@@ -181,7 +191,7 @@ export function pnlKey(ui, key) {
181
191
  return { kind: 'share', action: 'save' };
182
192
  return { kind: 'none' };
183
193
  }
184
- const HINTS = 'x share to X · t copy text · s save · a wallet · z size · f 16:9/1:1 · ←→ coin · esc';
194
+ const HINTS = 'x share to X · t copy text · s save · a wallet · z size · m mcap/price · f 16:9/1:1 · ←→ coin · esc';
185
195
  /** The preview overlay: the card itself at cell size when the terminal fits it, else its text; then the keys. */
186
196
  export function renderPnlOverlay(c, state) {
187
197
  const ui = state.pnl;
@@ -191,7 +201,7 @@ export function renderPnlOverlay(c, state) {
191
201
  c.noRain(0, 0, c.w, c.h);
192
202
  c.fill(0, 0, c.w, c.h, ' ', { bg: mix(t.bg, '#000000', 0.5) });
193
203
  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'}`;
204
+ 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'}${ui.stats ? ` · ${mcapView(ui.stats, ui.opts).basis === 'mcap' ? 'mcap' : 'SOL price'}` : ''}`;
195
205
  c.text(1, 0, head, { fg: t.text, bg: t.headBg, b: true }, c.w - 2);
196
206
  c.fill(0, c.h - 1, c.w, 1, ' ', { bg: t.headBg });
197
207
  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);
@@ -7,12 +7,16 @@
7
7
  // Honesty rules, enforced by construction:
8
8
  // - The stamp (PAPER / LIVE / TRACKED WALLET) comes from `CardInput.source` and is drawn by the renderer in three
9
9
  // places (the stamp block, the frame colour, the stamp strip). No option turns it off; an unknown source throws.
10
- // - Numbers come only from fills (the paper ledger, the signer's live ledger, a tracked wallet's decoded history).
10
+ // - Numbers come only from fills (the paper ledger, the trade ledger, a tracked wallet's decoded history).
11
11
  // There is no API that takes a PnL, a price or a size; every figure is computed here from those fills, with
12
12
  // the time window, the fee basis, what was excluded and the data ages printed on the card.
13
13
  // - Wallet addresses (and LIVE tx signatures, which point at the wallet) are hidden unless `showWallet`; size is a
14
14
  // band unless `exactSize`; no balance, no key, no file path is ever drawn.
15
15
  // - Footer: "not financial advice" and the dexterm URL. No performance claims beyond this one position's fills.
16
+ // - Market cap (task-1518): ENTRY / EXIT are shown as market cap when the token supply is known (`basis: 'mcap'`,
17
+ // the default then; `basis: 'price'` keeps the SOL price view). Each mcap is a fill-derived price × the supply,
18
+ // never a quoted mcap, and the card names the supply source (recorded FDV ÷ price, or pump.fun's fixed 1B) and
19
+ // the USD conversion. Exit mcap is the blended average of every sell; the last sell's mcap is its own labelled row.
16
20
  //
17
21
  // Rendering reuses the task-1401 exporter stack: a cell Canvas rasterized by @dexterm/tui (the 5×9 bitmap font) and
18
22
  // encoded by @dexterm/data. Big type (the stamp, the ticker, the PnL %) is the same font rasterized at a larger
@@ -20,7 +24,7 @@
20
24
  import { mkdirSync, writeFileSync } from 'node:fs';
21
25
  import { join } from 'node:path';
22
26
  import { spawn } from 'node:child_process';
23
- import { Canvas, THEMES, rasterizeCanvas, fmtPriceBare, mix } from '../../tui/src/index.js';
27
+ import { Canvas, THEMES, rasterizeCanvas, fmtPriceBare, fmtUsd, fmtNum, mix } from '../../tui/src/index.js';
24
28
  import { encodePng, lamportsToSol } from '../../data/src/index.js';
25
29
  import { exportsDir, fmtAge } from "./export.js";
26
30
  import { cleanText } from "./screener/rows.js";
@@ -41,18 +45,6 @@ export function paperCardInput(records, o) {
41
45
  excluded: [], fees: 'simulated network fee included', image: o.image ?? null,
42
46
  };
43
47
  }
44
- /** LIVE: the signer's ledger rows (confirmed on-chain fills, swaps only) for one mint. */
45
- export function liveCardInput(rows, o) {
46
- const rs = rows.filter((r) => r.mint === o.mint && r.kind === 'swap');
47
- const nets = new Set(rs.map((r) => r.network));
48
- const network = nets.size > 1 ? 'mixed' : rs[0]?.network === 'devnet' ? 'devnet' : 'mainnet';
49
- return {
50
- source: 'live', network, mint: o.mint, symbol: o.symbol ?? rs[0]?.symbol ?? '?', name: o.name ?? null, decimals: rs[0]?.decimals ?? null,
51
- fills: rs.map((r) => ({ t: r.t, side: r.side === 'sell' ? 'sell' : 'buy', solDelta: lamportsToSol(BigInt(r.solDelta)), tokenDelta: BigInt(r.tokenDelta), feeSol: lamportsToSol(BigInt(r.feeLamports)), sig: r.signature })),
52
- ledger: 'signer live ledger', wallet: o.wallet ?? null, mark: o.mark ?? null,
53
- excluded: network === 'mixed' ? ['devnet and mainnet fills are both in the ledger for this coin'] : [], fees: 'network + priority fees included', image: o.image ?? null,
54
- };
55
- }
56
48
  /** TRACKED: a followed wallet's decoded swaps for one mint (read-only public history). */
57
49
  export function trackedCardInput(trades, o) {
58
50
  const all = trades.filter((t) => t.mint === o.mint && t.wallet === o.wallet);
@@ -99,6 +91,74 @@ export function trackedCardInput(trades, o) {
99
91
  excluded, fees: 'network fees + tips included (balance deltas)', image: o.image ?? null,
100
92
  };
101
93
  }
94
+ /** pump.fun mints a fixed 1,000,000,000 supply per coin (none is minted later), so its market cap is price × 1B. */
95
+ export const PUMP_FUN_SUPPLY = 1_000_000_000;
96
+ /**
97
+ * The supply for the market-cap view: the token's recorded FDV ÷ price when both are known (total supply, so on a
98
+ * non-pump coin this is a fully diluted cap and says so), else pump.fun's fixed 1B for a pump.fun coin, else null
99
+ * (the card then stays on the price view; no supply is ever guessed).
100
+ */
101
+ export function cardSupply(o) {
102
+ if (o.fdv && o.price && Number.isFinite(o.fdv / o.price) && o.fdv > 0 && o.price > 0) {
103
+ return { tokens: o.fdv / o.price, source: `recorded FDV ÷ price${o.via ? ` (${o.via})` : ''}` };
104
+ }
105
+ if (o.launchpad === 'pump.fun' || ((!o.launchpad || o.launchpad === 'unknown') && o.mint.endsWith('pump'))) {
106
+ return { tokens: PUMP_FUN_SUPPLY, source: 'pump.fun fixed supply' };
107
+ }
108
+ return null;
109
+ }
110
+ /**
111
+ * AUTOTRADE PAPER: one position's fills from an autotrade run ledger. The desk books in USD, so each fill's SOL leg
112
+ * is its USD at `solUsd` (said on the card); the market caps come straight from the USD fills × supply, so the SOL
113
+ * rate changes only the size band, never a cap or the return.
114
+ */
115
+ export function autotradeCardInput(events, o) {
116
+ if (!(o.solUsd.usd > 0))
117
+ throw new CardError('an autotrade card needs a SOL/USD rate to show the USD desk in SOL');
118
+ const fs = events.filter((e) => e.kind === 'fill' && e.positionId === o.positionId && (e.side === 'buy' || e.side === 'sell') && (e.usd ?? 0) > 0 && (e.tokens ?? 0) > 0);
119
+ const mint = fs[0]?.mint ?? '';
120
+ const closed = events.find((e) => e.kind === 'trade_closed' && e.trade?.id === o.positionId);
121
+ const decimals = o.decimals ?? 6;
122
+ return {
123
+ source: 'paper', mint, symbol: o.symbol ?? closed?.trade?.symbol ?? mint.slice(0, 4), name: o.name ?? null, decimals,
124
+ fills: fs.map((e) => ({
125
+ t: e.t, side: e.side, solDelta: (e.side === 'buy' ? -1 : 1) * e.usd / o.solUsd.usd,
126
+ tokenDelta: BigInt(Math.round(e.tokens * 10 ** decimals)) * (e.side === 'buy' ? 1n : -1n), feeSol: null, sig: null,
127
+ })),
128
+ ledger: o.runId ? `autotrade paper run ${o.runId}` : 'autotrade paper ledger', wallet: null, mark: o.mark ?? null, excluded: [],
129
+ fees: `USD paper desk, impact in fills · SOL $${o.solUsd.usd.toFixed(2)} (${o.solUsd.source})`, image: o.image ?? null,
130
+ supply: o.supply === undefined ? cardSupply({ mint }) : o.supply,
131
+ usdPerSol: { usd: o.solUsd.usd, source: 'the desk\'s USD fills', native: true },
132
+ };
133
+ }
134
+ /**
135
+ * TRADE LEDGER (task-1530): one round trip of the unified ledger as a card. The stamp follows the wallet mode
136
+ * (paper → PAPER; devnet / live → LIVE with its network), and ENTRY / EXIT use the market-cap basis whenever the
137
+ * ledger recorded a supply (the same mcap code as every other card). A fill with no SOL leg (the autotrade USD desk)
138
+ * needs `solUsd`; a fill with no token amount refuses the card rather than guess.
139
+ */
140
+ export function ledgerCardInput(row, o = {}) {
141
+ const decimals = row.decimals ?? 6;
142
+ const usdDesk = row.fills.some((f) => f.sol === null);
143
+ if (usdDesk && !(o.solUsd && o.solUsd.usd > 0))
144
+ throw new CardError('this trade was booked in USD: a card needs a SOL/USD rate (--sol-usd)');
145
+ const fills = row.fills.map((f) => {
146
+ if (!(f.tokens !== null && f.tokens > 0))
147
+ throw new CardError(`fill ${f.t} has no token amount in the ledger`);
148
+ const sol = f.sol !== null && !usdDesk ? f.sol : (f.usd ?? 0) / o.solUsd.usd;
149
+ return { t: f.t, side: f.side, solDelta: (f.side === 'buy' ? -1 : 1) * Math.abs(sol), tokenDelta: BigInt(Math.round(f.tokens * 10 ** decimals)) * (f.side === 'buy' ? 1n : -1n), feeSol: f.feeSol, sig: row.walletMode === 'paper' ? null : f.txSig };
150
+ });
151
+ const where = row.source === 'autotrade' ? `autotrade paper run ${row.runId ?? ''}`.trim() : row.source === 'desk' ? 'agent paper desk' : row.source === 'live' ? 'signer live ledger' : 'dexterm paper ledger';
152
+ return {
153
+ source: row.walletMode === 'paper' ? 'paper' : 'live', network: row.walletMode === 'paper' ? null : row.walletMode === 'devnet' ? 'devnet' : 'mainnet',
154
+ mint: row.mint, symbol: row.symbol, name: o.name ?? null, decimals, fills,
155
+ ledger: `trade ledger · ${where} · ${row.tradeId}`, wallet: null, mark: o.mark ?? null, excluded: [],
156
+ fees: usdDesk ? `USD paper desk, impact in fills · SOL $${o.solUsd.usd.toFixed(2)} (${o.solUsd.source})` : row.walletMode === 'paper' ? 'simulated network fee included' : 'network + priority fees included',
157
+ image: o.image ?? null,
158
+ supply: row.supply ?? cardSupply({ mint: row.mint }),
159
+ ...(usdDesk ? { usdPerSol: { usd: o.solUsd.usd, source: 'the desk\'s USD fills', native: true } } : o.solUsd ? { usdPerSol: o.solUsd } : {}),
160
+ };
161
+ }
102
162
  /** Cost-basis maths over the fills (average cost, the paper and live ledgers' own rule). Throws CardError when a card would mislead. */
103
163
  export function computeCard(input, now) {
104
164
  if (input.source !== 'paper' && input.source !== 'live' && input.source !== 'tracked')
@@ -109,7 +169,7 @@ export function computeCard(input, now) {
109
169
  if (!fills.some((f) => f.side === 'buy'))
110
170
  throw new CardError('no recorded buy for this coin, so there is no cost basis to measure against');
111
171
  let held = 0n, peak = 0n, cost = 0, costIn = 0, proceeds = 0, realized = 0, boughtRaw = 0n, soldRaw = 0n, buys = 0, sells = 0, unmatched = 0;
112
- let fees = 0, feesKnown = false, firstBuy = Infinity, lastSell = -Infinity;
172
+ let fees = 0, feesKnown = false, firstBuy = Infinity, lastSell = -Infinity, lastSellAvg = null;
113
173
  for (const f of fills) {
114
174
  if (f.feeSol !== null) {
115
175
  fees += f.feeSol;
@@ -147,6 +207,8 @@ export function computeCard(input, now) {
147
207
  cost -= removed;
148
208
  held -= matched;
149
209
  soldRaw += matched;
210
+ if (input.decimals !== null)
211
+ lastSellAvg = got / (Number(matched) / 10 ** input.decimals);
150
212
  }
151
213
  }
152
214
  if (!buys)
@@ -174,7 +236,7 @@ export function computeCard(input, now) {
174
236
  input, buys, sells, firstT: fills[0].t, lastT: fills[fills.length - 1].t, open,
175
237
  costInSol: costIn, proceedsSol: proceeds, realizedSol: realized, unrealizedSol: unrealized, pnlSol: pnl, pnlPct: costIn > 0 ? pnl / costIn : 0,
176
238
  kind: !open ? 'realized' : sells ? 'realized + unrealized' : 'unrealized',
177
- entryAvg: boughtW ? costIn / boughtW : null, exitAvg: soldW ? proceeds / soldW : null,
239
+ entryAvg: boughtW ? costIn / boughtW : null, exitAvg: soldW ? proceeds / soldW : null, lastSellAvg,
178
240
  holdMs: Math.max(0, (open ? now : lastSell === -Infinity ? now : lastSell) - (firstBuy === Infinity ? fills[0].t : firstBuy)),
179
241
  feesSol: feesKnown ? fees : null, now,
180
242
  };
@@ -217,6 +279,66 @@ export function fmtWhen(t) {
217
279
  }
218
280
  const signedSol = (n) => `${n >= 0 ? '+' : '-'}${Math.abs(n).toFixed(4)} SOL`;
219
281
  const short = (a) => (a.length > 10 ? `${a.slice(0, 4)}…${a.slice(-4)}` : a);
282
+ /** `1B` / `998.8M` / `21,000,000` → a short supply figure for the provenance line. */
283
+ const fmtSupply = (n) => (n === PUMP_FUN_SUPPLY ? '1B' : fmtNum(n));
284
+ /** Resolve the basis and the market caps for a card: every figure is a fill-derived SOL price × supply (× USD/SOL). */
285
+ export function mcapView(s, o = {}) {
286
+ const sup = s.input.supply;
287
+ const want = o.basis ?? (sup ? 'mcap' : 'price');
288
+ const none = { basis: 'price', fallback: null, unit: 'SOL', entry: null, exitBlended: null, lastSell: null, mark: null };
289
+ if (want === 'price')
290
+ return none;
291
+ if (!sup || !(sup.tokens > 0))
292
+ return { ...none, fallback: 'market cap not shown: no token supply recorded, so prices are in SOL' };
293
+ if (s.input.decimals === null)
294
+ return { ...none, fallback: 'market cap not shown: token decimals unknown, so prices are in SOL' };
295
+ const k = sup.tokens * (s.input.usdPerSol && s.input.usdPerSol.usd > 0 ? s.input.usdPerSol.usd : 1);
296
+ const m = (p) => (p === null || p === undefined || !Number.isFinite(p) ? null : p * k);
297
+ return {
298
+ basis: 'mcap', fallback: null, unit: s.input.usdPerSol && s.input.usdPerSol.usd > 0 ? 'USD' : 'SOL',
299
+ entry: m(s.entryAvg), exitBlended: m(s.exitAvg), lastSell: m(s.lastSellAvg), mark: s.open ? m(s.input.mark?.priceSol) : null,
300
+ };
301
+ }
302
+ /** `$69.7K` / `$1.23M` in USD, `412.3 SOL` / `1.20K SOL` in SOL. */
303
+ export function fmtMcap(v, unit) {
304
+ if (v === null)
305
+ return '—';
306
+ return unit === 'USD' ? fmtUsd(v) : `${fmtNum(v)} SOL`;
307
+ }
308
+ /** The ENTRY / EXIT rows for the market-cap view (closed: entry, blended exit, last sell when there are 2+ sells). */
309
+ function mcapRows(s, v) {
310
+ const f = (x) => fmtMcap(x, v.unit);
311
+ const rows = [['ENTRY mcap', `${f(v.entry)} (avg fill)`]];
312
+ if (s.open)
313
+ rows.push(['MARK mcap', f(v.mark)]);
314
+ else
315
+ rows.push(['EXIT mcap', s.sells > 1 ? `${f(v.exitBlended)} (blended)` : f(v.exitBlended)]);
316
+ if (s.sells > 1 || (s.open && s.sells))
317
+ rows.push(['LAST SELL', `${f(v.lastSell)} mcap`]);
318
+ return rows;
319
+ }
320
+ /**
321
+ * The card's own statement of where the caps come from, as two stat rows (the provenance block is full on a LIVE
322
+ * card with the wallet shown, and OPENED / CLOSED repeat its window line, so these two take their place).
323
+ */
324
+ function mcapSourceRows(s, v) {
325
+ const sup = s.input.supply, usd = s.input.usdPerSol;
326
+ const src = sup.source.startsWith('pump.fun') ? 'pump.fun fixed' : sup.source.startsWith('recorded FDV') ? 'FDV/price' : cleanText(sup.source, 12);
327
+ return [
328
+ ['SUPPLY', `${fmtSupply(sup.tokens)} · ${src}`],
329
+ ['MCAP IN', v.unit === 'SOL' ? 'SOL (no USD rate)' : usd?.native ? 'USD (desk fills)' : `USD at $${usd.usd.toFixed(2)}/SOL`],
330
+ ];
331
+ }
332
+ /** The provenance line of the market-cap view (or of a fallback to the price view). */
333
+ function mcapLine(s, v) {
334
+ if (v.fallback)
335
+ return v.fallback;
336
+ if (v.basis !== 'mcap' || !s.input.supply)
337
+ return null;
338
+ const sup = s.input.supply, usd = s.input.usdPerSol;
339
+ const conv = v.unit === 'USD' ? ` · USD: ${usd.source}` : ' · in SOL';
340
+ return `mcap = fill price × ${fmtSupply(sup.tokens)} supply (${sup.source})${conv}`;
341
+ }
220
342
  /** The stamp of a source. Exhaustive: anything else throws, so a card can never be drawn without one. */
221
343
  export function stampOf(s, t) {
222
344
  const src = s.input.source;
@@ -251,6 +373,10 @@ export function provenance(s, o, mode = 'text') {
251
373
  if (mode === 'text')
252
374
  ages.push(`card made ${fmtWhen(s.now)}`);
253
375
  L.push(`data ages: ${ages.join(' · ')}`);
376
+ // the card states the market-cap source in its stat rows (mcapSourceRows); the text spells it out here
377
+ const ml = mode === 'text' ? mcapLine(s, mcapView(s, o)) : null;
378
+ if (ml)
379
+ L.push(ml);
254
380
  if (o.showWallet && i.wallet)
255
381
  L.push(`wallet: ${i.wallet}`);
256
382
  if (i.source === 'live' && o.showWallet) {
@@ -351,15 +477,22 @@ export function renderCard(s, o = {}) {
351
477
  : 'return on SOL spent · net of fees · size shown as a band';
352
478
  const dec = s.input.decimals !== null;
353
479
  const px = (p) => (p === null ? (dec ? '—' : 'n/a (decimals unknown)') : `${fmtPriceBare(p)} SOL`);
480
+ const mv = mcapView(s, o);
354
481
  const stats = [
355
- ['ENTRY avg', px(s.entryAvg)],
356
- s.open ? ['MARK', px(s.input.mark?.priceSol ?? null)] : ['EXIT avg', px(s.exitAvg)],
482
+ ...(mv.basis === 'mcap' ? mcapRows(s, mv) : [
483
+ ['ENTRY avg', px(s.entryAvg)],
484
+ s.open ? ['MARK', px(s.input.mark?.priceSol ?? null)] : ['EXIT avg', px(s.exitAvg)],
485
+ ]),
357
486
  ['HOLD', `${fmtHold(s.holdMs)}${s.open ? ' (open)' : ''}`],
358
487
  ['SIZE', o.exactSize ? `${s.costInSol.toFixed(4)} SOL in` : `${sizeBand(s.costInSol)} (band)`],
359
488
  ['FILLS', `${s.buys} buy${s.buys === 1 ? '' : 's'} · ${s.sells} sell${s.sells === 1 ? '' : 's'}`],
360
- ['OPENED', fmtWhen(s.firstT)],
361
- ['CLOSED', s.open ? 'still open' : fmtWhen(s.lastT)],
489
+ ...(mv.basis === 'mcap' ? mcapSourceRows(s, mv) : [
490
+ ['OPENED', fmtWhen(s.firstT)],
491
+ ['CLOSED', s.open ? 'still open' : fmtWhen(s.lastT)],
492
+ ]),
362
493
  ];
494
+ if (mv.fallback)
495
+ stats.push(['MCAP', mv.fallback.startsWith('market cap not shown: no token supply') ? 'n/a: no supply known' : 'n/a: decimals unknown']);
363
496
  const prov = [...provenance(s, o, 'card'), stamp.note];
364
497
  const coinTile = (x, y, w, h) => {
365
498
  if (s.input.image && s.input.image.w > 0 && s.input.image.h > 0) {
@@ -510,7 +643,11 @@ export function cardText(s, o = {}) {
510
643
  L.push(`${s.kind} PnL ${fmtPnlPct(s.pnlPct)} (net of fees)${o.exactSize ? ` · ${signedSol(s.pnlSol)} on ${s.costInSol.toFixed(4)} SOL in` : ` · size ${sizeBand(s.costInSol)} (band)`}`);
511
644
  const dec = s.input.decimals !== null;
512
645
  const p = (v) => (v === null ? (dec ? '—' : 'n/a') : `${fmtPriceBare(v)} SOL`);
513
- L.push(`entry avg ${p(s.entryAvg)} · ${s.open ? `mark ${p(s.input.mark?.priceSol ?? null)}` : `exit avg ${p(s.exitAvg)}`} · hold ${fmtHold(s.holdMs)}${s.open ? ' (open)' : ''} · ${s.buys} buys · ${s.sells} sells`);
646
+ const mv = mcapView(s, o);
647
+ const head = mv.basis === 'mcap'
648
+ ? mcapRows(s, mv).map(([k, v]) => `${k.toLowerCase()} ${v}`).join(' · ')
649
+ : `entry avg ${p(s.entryAvg)} · ${s.open ? `mark ${p(s.input.mark?.priceSol ?? null)}` : `exit avg ${p(s.exitAvg)}`}`;
650
+ L.push(`${head} · hold ${fmtHold(s.holdMs)}${s.open ? ' (open)' : ''} · ${s.buys} buys · ${s.sells} sells`);
514
651
  L.push(...provenance(s, o));
515
652
  L.push(st.note + ' Trading in dexterm is experimental.');
516
653
  L.push(`not financial advice · ${DEXTERM_URL}`);