dexterm-cli 0.1.6 → 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 (331) hide show
  1. package/dist/packages/agents/src/schema.js +22 -1
  2. package/dist/packages/agents/src/story.js +11 -2
  3. package/dist/packages/agents/src/tiers.js +27 -2
  4. package/dist/packages/agents/src/tools.js +30 -2
  5. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  6. package/dist/packages/autotrade/assets/profiles/moonbag.json +46 -0
  7. package/dist/packages/autotrade/assets/profiles/runner.json +88 -0
  8. package/dist/packages/autotrade/assets/profiles/scalp.json +88 -0
  9. package/dist/packages/autotrade/assets/profiles/swing.json +88 -0
  10. package/dist/packages/autotrade/assets/strategies/moonbag.json +101 -0
  11. package/dist/packages/autotrade/assets/strategies/runner.json +101 -0
  12. package/dist/packages/autotrade/assets/strategies/sample-dip-ladder.json +188 -0
  13. package/dist/packages/autotrade/assets/strategies/scalp.json +98 -0
  14. package/dist/packages/autotrade/assets/strategies/swing.json +97 -0
  15. package/dist/packages/autotrade/assets/strategies/yolo.json +157 -0
  16. package/dist/packages/autotrade/assets/tapes/yolo-rehearsal.json +1 -0
  17. package/dist/packages/autotrade/src/account.js +74 -0
  18. package/dist/packages/autotrade/src/backtest.js +357 -0
  19. package/dist/packages/autotrade/src/basis-text.js +32 -0
  20. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  21. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  22. package/dist/packages/autotrade/src/chat/config.js +93 -0
  23. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  24. package/dist/packages/autotrade/src/chat/control.js +103 -0
  25. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  26. package/dist/packages/autotrade/src/chat/format.js +71 -0
  27. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  28. package/dist/packages/autotrade/src/chat/index.js +18 -0
  29. package/dist/packages/autotrade/src/chat/notify.js +292 -0
  30. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  31. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  32. package/dist/packages/autotrade/src/chat/secrets.js +99 -0
  33. package/dist/packages/autotrade/src/chat/service.js +248 -0
  34. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  35. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  36. package/dist/packages/autotrade/src/engine.js +387 -0
  37. package/dist/packages/autotrade/src/execution-port.js +97 -0
  38. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  39. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  40. package/dist/packages/autotrade/src/fleet.js +639 -0
  41. package/dist/packages/autotrade/src/index.js +53 -0
  42. package/dist/packages/autotrade/src/ledger.js +108 -0
  43. package/dist/packages/autotrade/src/live-source.js +182 -0
  44. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  45. package/dist/packages/autotrade/src/monitor.js +190 -0
  46. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  47. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  48. package/dist/packages/autotrade/src/replay.js +198 -0
  49. package/dist/packages/autotrade/src/runner-config.js +99 -0
  50. package/dist/packages/autotrade/src/runner.js +872 -0
  51. package/dist/packages/autotrade/src/safety.js +243 -0
  52. package/dist/packages/autotrade/src/session.js +269 -0
  53. package/dist/packages/autotrade/src/sessions.js +503 -0
  54. package/dist/packages/autotrade/src/store.js +250 -0
  55. package/dist/packages/autotrade/src/strategy.js +256 -0
  56. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  57. package/dist/packages/autotrade/src/yolo.js +140 -0
  58. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  59. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  60. package/dist/packages/cli/src/ai-cmd.js +412 -0
  61. package/dist/packages/cli/src/analyst/backend.js +82 -15
  62. package/dist/packages/cli/src/analyst/codex.js +14 -9
  63. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  64. package/dist/packages/cli/src/analyst/session.js +5 -2
  65. package/dist/packages/cli/src/app-help-gen.js +44 -8
  66. package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
  67. package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
  68. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  69. package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
  70. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  71. package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
  72. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  73. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  74. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  75. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  76. package/dist/packages/cli/src/autotrade-cmd.js +168 -0
  77. package/dist/packages/cli/src/display.js +156 -17
  78. package/dist/packages/cli/src/doctor.js +62 -3
  79. package/dist/packages/cli/src/export.js +10 -2
  80. package/dist/packages/cli/src/index.js +1 -1
  81. package/dist/packages/cli/src/ledger-cmd.js +288 -0
  82. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  83. package/dist/packages/cli/src/live/index.js +23 -89
  84. package/dist/packages/cli/src/live/screens/token-live.js +9 -6
  85. package/dist/packages/cli/src/live/trade.js +12 -457
  86. package/dist/packages/cli/src/live/watchlist.js +1 -1
  87. package/dist/packages/cli/src/main.js +147 -190
  88. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  89. package/dist/packages/cli/src/plan-cmd.js +1 -1
  90. package/dist/packages/cli/src/pnl-app.js +31 -21
  91. package/dist/packages/cli/src/pnl-card.js +158 -21
  92. package/dist/packages/cli/src/popup.js +22 -6
  93. package/dist/packages/cli/src/record-cmd.js +141 -20
  94. package/dist/packages/cli/src/screens/agent-hud.js +34 -2
  95. package/dist/packages/cli/src/screens/analyst-session.js +7 -4
  96. package/dist/packages/cli/src/screens/analyst.js +3 -2
  97. package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
  98. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  99. package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
  100. package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
  101. package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
  102. package/dist/packages/cli/src/screens/brain.js +37 -11
  103. package/dist/packages/cli/src/screens/overlay.js +5 -2
  104. package/dist/packages/cli/src/screens/plans.js +5 -17
  105. package/dist/packages/cli/src/screens/pnl-chip.js +9 -8
  106. package/dist/packages/cli/src/screens/positions.js +6 -28
  107. package/dist/packages/cli/src/screens/ticket.js +38 -205
  108. package/dist/packages/cli/src/screens/token.js +3 -3
  109. package/dist/packages/cli/src/state.js +25 -19
  110. package/dist/packages/cli/src/telegram-cmd.js +451 -0
  111. package/dist/packages/cli/src/trade.js +3 -128
  112. package/dist/packages/cli/src/version.js +1 -1
  113. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  114. package/dist/packages/cli/src/wallet-command.js +8 -11
  115. package/dist/packages/cli/src/widgets.js +0 -12
  116. package/dist/packages/custom/src/config.js +20 -2
  117. package/dist/packages/custom/src/schema.js +35 -10
  118. package/dist/packages/custom/src/workspace.js +3 -3
  119. package/dist/packages/data/src/basis.js +191 -0
  120. package/dist/packages/data/src/detail.js +53 -2
  121. package/dist/packages/data/src/index.js +0 -14
  122. package/dist/packages/data/src/store.js +14 -5
  123. package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
  124. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  125. package/dist/packages/data/src/trade/bridge.js +4 -102
  126. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  127. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  128. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  129. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  130. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  131. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  132. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  133. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  134. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  135. package/dist/packages/data/src/trade/plans/index.js +0 -1
  136. package/dist/packages/data/src/trade/plans/service.js +3 -27
  137. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  138. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  139. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  140. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  141. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  142. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  143. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  144. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  145. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  146. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  147. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  148. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  149. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  150. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
  151. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  152. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  153. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  154. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  155. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
  156. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  157. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  158. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  159. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  160. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  161. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +4 -1
  162. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  163. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  164. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  165. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  166. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  167. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  168. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
  169. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  170. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  171. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  172. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  173. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
  174. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  175. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  176. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  177. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  178. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  179. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  180. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
  181. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  182. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  183. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  184. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  186. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  188. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
  189. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  194. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  195. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +1 -1
  197. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  198. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  200. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +8 -6
  201. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  202. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  203. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  204. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  205. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  208. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  209. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  210. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  211. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  219. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  220. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  223. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  225. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  226. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  227. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  228. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  229. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  230. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  231. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  232. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  233. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  234. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +3 -4
  235. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  236. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +6 -7
  237. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  238. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  240. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  241. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  242. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  243. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  244. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +2 -1
  246. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  247. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  248. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  249. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +1 -1
  250. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  251. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  254. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  255. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  256. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  257. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  260. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  262. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  263. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  264. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  266. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  267. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  269. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  270. package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
  271. package/dist/packages/mcp/src/app-help.js +1 -1
  272. package/dist/packages/mcp/src/autotrade-tools.js +944 -0
  273. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  274. package/dist/packages/mcp/src/engine/config.js +159 -0
  275. package/dist/packages/mcp/src/engine/index.js +10 -0
  276. package/dist/packages/mcp/src/engine/loop.js +101 -0
  277. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  278. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  279. package/dist/packages/mcp/src/engine/probe.js +110 -0
  280. package/dist/packages/mcp/src/engine/reason.js +35 -0
  281. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  282. package/dist/packages/mcp/src/index.js +7 -1
  283. package/dist/packages/mcp/src/ledger-tools.js +346 -0
  284. package/dist/packages/mcp/src/paper-agent.js +2 -0
  285. package/dist/packages/mcp/src/server.js +127 -19
  286. package/dist/packages/packs/src/backtest.js +3 -1
  287. package/dist/packages/popup/src/pages/chart-lib.js +134 -21
  288. package/dist/packages/popup/src/pages/chart.css +9 -0
  289. package/dist/packages/popup/src/pages/chart.html +3 -1
  290. package/dist/packages/popup/src/pages/chart.js +104 -7
  291. package/dist/packages/popup/src/protocol.js +2 -0
  292. package/dist/packages/popup/src/server.js +4 -0
  293. package/dist/packages/recorder/src/index.js +2 -0
  294. package/dist/packages/recorder/src/recorder.js +63 -6
  295. package/dist/packages/recorder/src/retention.js +100 -0
  296. package/dist/packages/recorder/src/schema.js +34 -1
  297. package/dist/packages/recorder/src/ticks.js +237 -0
  298. package/dist/packages/tui/src/index.js +1 -0
  299. package/dist/packages/tui/src/keymap.js +21 -1
  300. package/dist/packages/tui/src/markers.js +219 -0
  301. package/dist/packages/tui/src/widgets.js +149 -2
  302. package/package.json +2 -1
  303. package/dist/packages/data/src/trade/caps.js +0 -323
  304. package/dist/packages/data/src/trade/executor.js +0 -933
  305. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  306. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  307. package/dist/packages/data/src/trade/signer/client.js +0 -197
  308. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  309. package/dist/packages/data/src/trade/signer/host.js +0 -97
  310. package/dist/packages/data/src/trade/signer/main.js +0 -121
  311. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  312. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  313. package/dist/packages/data/src/trade/signer/service.js +0 -400
  314. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  315. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  316. package/dist/packages/data/src/trade/swap-build.js +0 -108
  317. package/dist/packages/data/src/trade/token-delta.js +0 -201
  318. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  319. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  320. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  321. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  322. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  323. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  324. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  325. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  326. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  327. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  328. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  329. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  330. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  331. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -4,8 +4,9 @@
4
4
  // (observe snapshots + feed membership, the 1 h/2 h/6 h/24 h/72 h outcome sweeps, labels, retention) with no
5
5
  // TUI, to be driven by a user-level launchd LaunchAgent on the Mac mini.
6
6
  //
7
- // dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--no-newpairs] [--db PATH] [--verbose]
8
- // dexterm record install [--interval S] [--max-db-mb N] [--load] [--dry-run]
7
+ // dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--retention-mb N] [--retention-days D]
8
+ // [--focus-ticks] [--no-newpairs] [--db PATH] [--verbose]
9
+ // dexterm record install [--interval S] [--max-db-mb N] [--retention-mb N] [--retention-days D] [--focus-ticks] [--load] [--dry-run]
9
10
  // dexterm record uninstall [--unload]
10
11
  // dexterm record status [--db PATH]
11
12
  //
@@ -15,13 +16,18 @@
15
16
  // the data layer; only the provider name is ever logged). Modest CPU: one feed refresh per `--interval` (60 s
16
17
  // default) and recorder maintenance at most every `--maintain` seconds. A soft disk cap pauses OBSERVATION
17
18
  // (the growth source) above `--max-db-mb` while labels + retention keep running, so the DB cannot run away.
19
+ // task-1504 (AUTOTRADE-SPEC §3, §10 Q4), both opt-in so an installed collector behaves as before until it is
20
+ // re-installed with them: `--retention-mb N` replaces that pause with eviction of the oldest history (observation
21
+ // never pauses), `--retention-days D` is the age rule for snapshot rows, and `--focus-ticks` runs the autotrade
22
+ // tick lane (5–10 s DexScreener samples of watchlist / held / candidate / open coins, plus 1m candles).
18
23
  import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
19
24
  import { spawnSync } from 'node:child_process';
20
25
  import { dirname, join, resolve } from 'node:path';
21
26
  import { fileURLToPath } from 'node:url';
22
27
  import { defaultDbPath, resolveRpcUrl, extraRpcUrls, redactSecrets } from '../../data/src/index.js';
23
28
  import { PumpPortalClient } from '../../data/src/index.js';
24
- import { Recorder, recorderEnabled } from '../../recorder/src/index.js';
29
+ import { Recorder, recorderEnabled, dbFocus, focusFilePath, readFocusFile, DEFAULT_TICKS } from '../../recorder/src/index.js';
30
+ import { dextermHome } from "./boot-state.js";
25
31
  import { createLiveFeed } from "./feed.js";
26
32
  import { NewPairsLive } from "./live/index.js";
27
33
  /** The LaunchAgent label (reverse-DNS). One collector per user. */
@@ -41,7 +47,17 @@ export function buildLiveEngine(env, opts) {
41
47
  dbPath: opts.dbPath, rpcUrl: rpc.url, rpcFallbackUrl: rpc.fallbackUrl, rpcPublicPool: true, rpcExtraUrls: extraRpcUrls(env),
42
48
  onRequest: opts.verbose ? (req, meta, o) => opts.onLog?.(` ${meta.source.padEnd(14)} ${String(o.status ?? 'ERR').padStart(3)} ${String(o.latencyMs).padStart(5)}ms ${redactSecrets(req.url).slice(0, 110)}\n`) : undefined,
43
49
  });
44
- const recorder = new Recorder({ path: opts.dbPath, onLog: opts.verbose ? opts.onLog : undefined });
50
+ // Focus coins for the tick lane: the watchlist + paper positions in this DB, and whatever the autotrade runner /
51
+ // the app publish in the focus file. Read on every poll (two indexed reads + one small file).
52
+ const focusFile = focusFilePath(dextermHome(env));
53
+ const recorder = new Recorder({
54
+ path: opts.dbPath, onLog: opts.verbose ? opts.onLog : undefined,
55
+ // The cap reclaims the data layer's expired cache entries (its own rule: a day past their TTL) before it evicts
56
+ // any history; `feed` is built above, so the hook can reach it.
57
+ ...(opts.retentionBytes ? { cap: { maxBytes: opts.retentionBytes, reclaim: () => feed.dl.cache.prune() } } : {}),
58
+ ...(opts.retentionDays ? { retention: { columnsDays: opts.retentionDays } } : {}),
59
+ ...(opts.focusTicks ? { ticks: { focus: () => [...dbFocus(recorder.database), ...readFocusFile(focusFile, Date.now())] } } : {}),
60
+ });
45
61
  recorder.attach(feed.dl.store);
46
62
  let newpairs = null;
47
63
  if (!opts.noNewpairs) {
@@ -66,6 +82,8 @@ export function buildLiveEngine(env, opts) {
66
82
  recorder.prune();
67
83
  }
68
84
  catch { /* logged inside; never fatal */ } },
85
+ enforceCap() { return recorder.enforceCap(); },
86
+ ...(opts.focusTicks ? { pollTicks: () => recorder.pollTicks(dl) } : {}),
69
87
  dbBytes() {
70
88
  try {
71
89
  const db = recorder.database;
@@ -101,7 +119,7 @@ export function buildLiveEngine(env, opts) {
101
119
  * Never throws out of the loop: an engine error is logged and the loop continues to the next tick.
102
120
  */
103
121
  export async function runCollectorLoop(engine, opts) {
104
- const res = { ticks: 0, observePasses: 0, rowsWritten: 0, maintainPasses: 0, cappedTicks: 0 };
122
+ const res = { ticks: 0, observePasses: 0, rowsWritten: 0, maintainPasses: 0, cappedTicks: 0, evictions: 0 };
105
123
  let lastMaintain = -Infinity;
106
124
  for (let i = 0; i < opts.ticks && !opts.aborted(); i++) {
107
125
  if (i > 0) {
@@ -111,7 +129,19 @@ export async function runCollectorLoop(engine, opts) {
111
129
  }
112
130
  res.ticks++;
113
131
  let over = false;
114
- if (opts.maxDbBytes !== null) {
132
+ if (opts.capMode === 'evict') {
133
+ try {
134
+ const c = engine.enforceCap?.() ?? null;
135
+ if (c?.evicted)
136
+ res.evictions++;
137
+ if (c?.over)
138
+ opts.onLog?.(`record: DB still at ${(c.usedAfter / 1e6).toFixed(0)} MB used after evicting all but the newest hour (other tables are large); observation continues\n`);
139
+ }
140
+ catch (e) {
141
+ opts.onLog?.(`record: retention failed: ${redactSecrets(e.message)}\n`);
142
+ }
143
+ }
144
+ else if (opts.maxDbBytes !== null) {
115
145
  const b = engine.dbBytes();
116
146
  if (b !== null && b >= opts.maxDbBytes) {
117
147
  engine.prune();
@@ -146,6 +176,29 @@ export async function runCollectorLoop(engine, opts) {
146
176
  }
147
177
  return res;
148
178
  }
179
+ /** task-1504: drive the tick lane every `everyMs` until aborted. Never throws (a failed poll is logged). */
180
+ export async function runTickLoop(engine, opts) {
181
+ const res = { polls: 0, calls: 0, written: 0 };
182
+ if (!engine.pollTicks)
183
+ return res;
184
+ while (!opts.aborted()) {
185
+ try {
186
+ const r = await engine.pollTicks();
187
+ res.polls++;
188
+ res.calls += r.calls;
189
+ res.written += r.written;
190
+ if (r.error)
191
+ opts.onLog?.(`record: tick poll failed: ${redactSecrets(r.error)}\n`);
192
+ }
193
+ catch (e) {
194
+ opts.onLog?.(`record: tick poll failed: ${redactSecrets(e.message)}\n`);
195
+ }
196
+ if (opts.aborted())
197
+ break;
198
+ await opts.sleep(opts.everyMs);
199
+ }
200
+ return res;
201
+ }
149
202
  const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
150
203
  /**
151
204
  * A user-level LaunchAgent (no sudo). KeepAlive so a crash or a reboot-after-login brings it back, RunAtLoad,
@@ -155,7 +208,7 @@ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g,
155
208
  * collector to run; a true pre-login daemon would be a LaunchDaemon under sudo, which this task does not use.
156
209
  */
157
210
  export function renderCollectorPlist(o) {
158
- const args = [o.nodePath, o.binPath, 'record', 'run', '--interval', String(Math.round(o.intervalMs / 1000)), '--max-db-mb', String(o.maxDbMb)];
211
+ const args = [o.nodePath, o.binPath, 'record', 'run', '--interval', String(Math.round(o.intervalMs / 1000)), '--max-db-mb', String(o.maxDbMb), ...(o.extraArgs ?? [])];
159
212
  const argXml = args.map((a) => ` <string>${esc(a)}</string>`).join('\n');
160
213
  return `<?xml version="1.0" encoding="UTF-8"?>
161
214
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -202,10 +255,37 @@ function dataDir(env, db) {
202
255
  function sourceBin() {
203
256
  return fileURLToPath(new URL('../bin/dexterm.mjs', import.meta.url));
204
257
  }
258
+ /** task-1504: the shared `--retention-mb` / `--retention-days` / `--focus-ticks` parser (run + install). */
259
+ function parseRetention(t, next, a) {
260
+ if (t === '--focus-ticks') {
261
+ a.focusTicks = true;
262
+ return null;
263
+ }
264
+ if (t === '--retention-mb') {
265
+ const n = Number(next());
266
+ if (!Number.isFinite(n) || n < 1)
267
+ return '--retention-mb needs a number ≥ 1';
268
+ a.retentionBytes = Math.round(n * 1_000_000);
269
+ return null;
270
+ }
271
+ if (t === '--retention-days') {
272
+ const n = Number(next());
273
+ if (!Number.isFinite(n) || n < 1)
274
+ return '--retention-days needs a number of days ≥ 1';
275
+ a.retentionDays = n;
276
+ return null;
277
+ }
278
+ return undefined;
279
+ }
205
280
  export function parseRun(argv) {
206
- const a = { intervalMs: DEFAULT_INTERVAL_MS, ticks: Infinity, maintainMs: DEFAULT_MAINTAIN_MS, maxDbBytes: DEFAULT_MAX_DB_MB * 1_000_000, noNewpairs: false, db: null, verbose: false };
281
+ const a = { intervalMs: DEFAULT_INTERVAL_MS, ticks: Infinity, maintainMs: DEFAULT_MAINTAIN_MS, maxDbBytes: DEFAULT_MAX_DB_MB * 1_000_000, noNewpairs: false, db: null, verbose: false, retentionBytes: null, retentionDays: null, focusTicks: false };
207
282
  for (let i = 0; i < argv.length; i++) {
208
283
  const t = argv[i];
284
+ const r = parseRetention(t, () => argv[++i], a);
285
+ if (typeof r === 'string')
286
+ return { error: r };
287
+ if (r === null)
288
+ continue;
209
289
  if (t === '--verbose' || t === '-v')
210
290
  a.verbose = true;
211
291
  else if (t === '--no-newpairs')
@@ -248,15 +328,20 @@ export function parseRun(argv) {
248
328
  }
249
329
  export const RECORD_USAGE = `dexterm record: keep the history recorder fed with no TUI open (task-1414).
250
330
 
251
- dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--no-newpairs] [--db PATH] [--verbose]
252
- dexterm record install [--interval S] [--max-db-mb N] [--load] [--dry-run]
331
+ dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--retention-mb N] [--retention-days D]
332
+ [--focus-ticks] [--no-newpairs] [--db PATH] [--verbose]
333
+ dexterm record install [--interval S] [--max-db-mb N] [--retention-mb N] [--retention-days D] [--focus-ticks] [--load] [--dry-run]
253
334
  dexterm record uninstall [--unload]
254
335
  dexterm record status [--db PATH]
255
336
 
256
337
  run the collector loop: one keyless feed refresh per --interval (default 60 s) feeds the recorder
257
338
  (snapshots + feed membership + new pairs), recorder maintenance (outcome sweeps, labels, retention)
258
339
  every --maintain seconds. --max-db-mb is a soft cap (default 1024): above it observation pauses while
259
- labels + retention keep running. --no-newpairs drops the PumpPortal stream. Ctrl-C stops cleanly
340
+ labels + retention keep running. --retention-mb N replaces that pause: the oldest history is evicted
341
+ to stay under N MB and observation never pauses. --retention-days D drops snapshot rows (sweep rows
342
+ too) older than D days (default 90). --focus-ticks samples watchlist / held / candidate / open coins
343
+ every 5-10 s (DexScreener batches of 30, a daily call ceiling) with 1m candles; autotrade backtests
344
+ read them. --no-newpairs drops the PumpPortal stream. Ctrl-C stops cleanly
260
345
  (labels whatever is due). Requests honour the data layer's rate limits, health gate and sweep budgets.
261
346
  install write a user-level launchd LaunchAgent (no sudo) that runs \`record run\`; prints the launchctl
262
347
  command. --load also runs launchctl (off by default). --dry-run prints the plist, writes nothing.
@@ -300,47 +385,77 @@ async function run(argv, io, deps) {
300
385
  return 2;
301
386
  }
302
387
  // SIGINT/SIGTERM-aware cancellable sleeper (launchd sends SIGTERM to stop the agent).
388
+ // Several loops may sleep at once (the collector + the tick lane), so every sleeper is woken on abort.
303
389
  const stop = (() => {
304
390
  let aborted = false;
305
- let wake = () => { };
391
+ const wakes = new Set();
306
392
  return {
307
393
  get aborted() { return aborted; },
308
- abort() { aborted = true; wake(); },
309
- sleep(ms) { return new Promise((r) => { const t = setTimeout(r, ms); t.unref?.(); wake = () => { clearTimeout(t); r(); }; }); },
394
+ abort() { aborted = true; for (const w of [...wakes])
395
+ w(); },
396
+ sleep(ms) {
397
+ return new Promise((r) => {
398
+ const done = () => { clearTimeout(t); wakes.delete(done); r(); };
399
+ const t = setTimeout(done, ms);
400
+ t.unref?.();
401
+ wakes.add(done);
402
+ });
403
+ },
310
404
  };
311
405
  })();
312
406
  const log = (s) => io.stderr.write(s);
313
- const engine = deps.engine ?? buildLiveEngine(io.env, { dbPath, noNewpairs: parsed.noNewpairs, verbose: parsed.verbose, onLog: log });
407
+ const engine = deps.engine ?? buildLiveEngine(io.env, { dbPath, noNewpairs: parsed.noNewpairs, verbose: parsed.verbose, onLog: log, retentionBytes: parsed.retentionBytes, retentionDays: parsed.retentionDays, focusTicks: parsed.focusTicks });
314
408
  const own = !deps.engine;
315
409
  const onSig = () => { io.stderr.write('\ndexterm record: stopping…\n'); stop.abort(); };
316
410
  if (own) {
317
411
  process.on('SIGINT', onSig);
318
412
  process.on('SIGTERM', onSig);
319
413
  }
414
+ // The sleeps are unref'd; without the PumpPortal socket (--no-newpairs) nothing else would keep the event loop
415
+ // alive between passes and node would exit mid-run (task-1504 smoke). Hold it open until the loops finish.
416
+ const hold = own ? setInterval(() => { }, 1 << 30) : null;
320
417
  if (parsed.verbose || parsed.ticks !== Infinity)
321
- io.stderr.write(`dexterm record: db ${dbPath} · interval ${Math.round(parsed.intervalMs / 1000)}s · maintain ${Math.round(parsed.maintainMs / 1000)}s · cap ${parsed.maxDbBytes === null ? 'off' : `${Math.round(parsed.maxDbBytes / 1e6)} MB`} · newpairs ${parsed.noNewpairs ? 'off' : 'on'}\n`);
418
+ io.stderr.write(`dexterm record: db ${dbPath} · interval ${Math.round(parsed.intervalMs / 1000)}s · maintain ${Math.round(parsed.maintainMs / 1000)}s · cap ${parsed.maxDbBytes === null ? 'off' : `${Math.round(parsed.maxDbBytes / 1e6)} MB`} · newpairs ${parsed.noNewpairs ? 'off' : 'on'}${parsed.retentionBytes !== null ? ` · retention ${Math.round(parsed.retentionBytes / 1e6)} MB (evict, never pause)` : ''}${parsed.retentionDays !== null ? ` · rows ${parsed.retentionDays} d` : ''} · focus ticks ${parsed.focusTicks ? 'on' : 'off'}\n`);
322
419
  let result;
420
+ let tickRes = { polls: 0, calls: 0, written: 0 };
323
421
  try {
324
- result = await runCollectorLoop(engine, {
422
+ let mainDone = false;
423
+ const main = runCollectorLoop(engine, {
325
424
  intervalMs: parsed.intervalMs, ticks: parsed.ticks, maintainEveryMs: parsed.maintainMs, maxDbBytes: parsed.maxDbBytes,
425
+ capMode: parsed.retentionBytes !== null ? 'evict' : 'pause',
326
426
  now, sleep: (ms) => stop.sleep(ms), aborted: () => stop.aborted, onLog: log,
327
- });
427
+ }).finally(() => { mainDone = true; if (parsed.ticks !== Infinity)
428
+ stop.abort(); });
429
+ const lane = parsed.focusTicks && engine.pollTicks
430
+ ? runTickLoop(engine, { everyMs: DEFAULT_TICKS.fastEveryMs, sleep: (ms) => stop.sleep(ms), aborted: () => stop.aborted || mainDone, onLog: log })
431
+ : Promise.resolve(tickRes);
432
+ [result, tickRes] = await Promise.all([main, lane]);
328
433
  }
329
434
  finally {
330
435
  if (own) {
331
436
  process.removeListener('SIGINT', onSig);
332
437
  process.removeListener('SIGTERM', onSig);
333
438
  }
439
+ if (hold)
440
+ clearInterval(hold);
334
441
  engine.close();
335
442
  }
336
443
  if (parsed.verbose || parsed.ticks !== Infinity)
337
- io.stdout.write(`record: ${result.observePasses} observe pass(es), ${result.rowsWritten} snapshot row(s), ${result.maintainPasses} maintenance pass(es)${result.cappedTicks ? `, ${result.cappedTicks} tick(s) paused at the disk cap` : ''}\n`);
444
+ io.stdout.write(`record: ${result.observePasses} observe pass(es), ${result.rowsWritten} snapshot row(s), ${result.maintainPasses} maintenance pass(es)${result.cappedTicks ? `, ${result.cappedTicks} tick(s) paused at the disk cap` : ''}${result.evictions ? `, ${result.evictions} retention eviction(s)` : ''}${tickRes.polls ? `, ${tickRes.polls} focus-tick poll(s) (${tickRes.calls} call(s), ${tickRes.written} tick row(s))` : ''}\n`);
338
445
  return 0;
339
446
  }
340
447
  function install(argv, io) {
341
448
  let intervalMs = DEFAULT_INTERVAL_MS, maxDbMb = DEFAULT_MAX_DB_MB, load = false, dry = false;
449
+ const ret = { retentionBytes: null, retentionDays: null, focusTicks: false };
342
450
  for (let i = 0; i < argv.length; i++) {
343
451
  const t = argv[i];
452
+ const r = parseRetention(t, () => argv[++i], ret);
453
+ if (typeof r === 'string') {
454
+ io.stderr.write(`dexterm record: ${r}\n`);
455
+ return 2;
456
+ }
457
+ if (r === null)
458
+ continue;
344
459
  if (t === '--load')
345
460
  load = true;
346
461
  else if (t === '--dry-run')
@@ -369,7 +484,12 @@ function install(argv, io) {
369
484
  const path = plistPath(io.env);
370
485
  const logPath = join(dataDir(io.env), 'collector.log');
371
486
  const bin = sourceBin();
372
- const plist = renderCollectorPlist({ label: COLLECTOR_LABEL, nodePath: process.execPath, binPath: bin, intervalMs, maxDbMb, logPath, workingDir: resolve(dirname(bin), '..', '..', '..') });
487
+ const extraArgs = [
488
+ ...(ret.retentionBytes !== null ? ['--retention-mb', String(Math.round(ret.retentionBytes / 1e6))] : []),
489
+ ...(ret.retentionDays !== null ? ['--retention-days', String(ret.retentionDays)] : []),
490
+ ...(ret.focusTicks ? ['--focus-ticks'] : []),
491
+ ];
492
+ const plist = renderCollectorPlist({ label: COLLECTOR_LABEL, nodePath: process.execPath, binPath: bin, intervalMs, maxDbMb, extraArgs, logPath, workingDir: resolve(dirname(bin), '..', '..', '..') });
373
493
  if (dry) {
374
494
  io.stdout.write(plist);
375
495
  io.stderr.write(`\ndexterm record: dry run — would write ${path}\n`);
@@ -427,6 +547,7 @@ function status(argv, io) {
427
547
  `snapshots ${s.snapshots} (live ${s.live}, sweep ${s.sweep}, t0 anchors ${s.t0})`,
428
548
  `outcomes ${s.outcomes}${s.outcomes ? ` (${Object.entries(s.outcomesByLabel).map(([k, v]) => `${k} ${v}`).join(', ')})` : ''}`,
429
549
  `sweeps ${s.sweeps} runs · ${s.sweepCallsToday} DexScreener calls today`,
550
+ `focus ticks ${s.ticks} rows · ${s.candles} 1m candles · ${s.tickCallsToday} calls today`,
430
551
  `span ${when(s.oldestT)} → ${when(s.newestT)}`,
431
552
  ].join('\n') + '\n');
432
553
  }
@@ -11,7 +11,7 @@
11
11
  // Pure: (canvas, StoryView, opts) → cells. Every number comes from the story (app-computed); nothing here can act.
12
12
  // Reduced motion (calm mode / DEXTERM_REDUCED_MOTION) drops every sweep, flash, glitch and typewriter: the same
13
13
  // information appears at once. NO_COLOR keeps meaning in glyphs (▸ active phase, ✓ ! ✗ chips, ▶ cursor).
14
- import { mix, hash3, KATAKANA, decryptText, fmtCompactUsd } from '../../../tui/src/index.js';
14
+ import { mix, hash3, KATAKANA, decryptText, fmtCompactUsd, markerGlyph, markerLetter } from '../../../tui/src/index.js';
15
15
  import { STORY_RAIL, fmtQty, fmtPx } from '../../../agents/src/index.js';
16
16
  import { pnlChipVM, renderPnlChip } from "./pnl-chip.js";
17
17
  /** Rows the dock takes at the bottom of the terminal. */
@@ -550,7 +550,8 @@ function ticketCard(c, v, arc, o, screenH) {
550
550
  ['SIZE', arc.sizeUsd != null ? `$${arc.sizeUsd.toFixed(2)}${v.caps ? ` cap $${v.caps.perTradeUsd.toFixed(2)}/trade · $${v.caps.totalUsd.toFixed(0)} total` : ''}` : 'sizing…', arc.sizeUsd != null ? t.text : t.dim],
551
551
  ['GATE', 'YOUR keypress armed the mandate · tier hands-free', t.muted],
552
552
  ['', 'no keypress per trade inside it · desk enforces caps', mix(bg, t.dim, 0.85)],
553
- ['ENTRY', fill ? `${Math.abs(fill.sol).toFixed(4)} SOL → ${fmtQty(fill.qty)} $${arc.symbol}${fill.priceUsd != null ? ` @ $${fmtPx(fill.priceUsd)}` : ''}` : refused ? '—' : 'awaiting confirm · fresh quote at fill', fill ? t.text : t.dim],
553
+ // task-1537: the fill reads as the market cap it filled at when the HUD's cap scale knows the supply (like the position card)
554
+ ['ENTRY', fill ? `${Math.abs(fill.sol).toFixed(4)} SOL → ${fmtQty(fill.qty)} $${arc.symbol}${fill.priceUsd != null ? ` @ ${((sc) => (sc ? `MC ${fmtCompactUsd(fill.priceUsd * sc)}` : `$${fmtPx(fill.priceUsd)}`))(o.mcapScale?.(arc.mint) ?? null)}` : ''}` : refused ? '—' : 'awaiting confirm · fresh quote at fill', fill ? t.text : t.dim],
554
555
  ['IMPACT', fill?.impactPct != null ? `${fill.impactPct.toFixed(2)}%${v.caps?.maxPriceImpactPct != null ? ` max ${v.caps.maxPriceImpactPct}%` : ''}` : '—', t.muted],
555
556
  ['EXITS', 'agent-managed · no resting orders on PAPER', t.muted],
556
557
  ];
@@ -585,6 +586,26 @@ function ticketCard(c, v, arc, o, screenH) {
585
586
  c.text(x + Math.floor((w - s.length) / 2), sy, s, { fg: mix(bg, t.meteora, pulse), bg });
586
587
  }
587
588
  }
589
+ /**
590
+ * task-1483: where the arc's fills sit on the PnL trace: the column of the first sample at or after the fill (the sample
591
+ * that first reflects it). A fill before the first SHOWN sample when older samples were cut off, or after the last
592
+ * sample, gets no column (it is not moved onto another one). Columns are 0-based into `shownT`.
593
+ */
594
+ export function traceFillCols(allT, shownFrom, fills) {
595
+ const by = new Map();
596
+ for (const f of fills) {
597
+ const i = allT.findIndex((t) => t >= f.t);
598
+ if (i < 0 || i < shownFrom)
599
+ continue;
600
+ const key = `${i}|${f.side}`;
601
+ const e = by.get(key);
602
+ if (e)
603
+ e.count++;
604
+ else
605
+ by.set(key, { col: i - shownFrom, side: f.side, count: 1 });
606
+ }
607
+ return [...by.values()].sort((a, b) => a.col - b.col);
608
+ }
588
609
  function positionCard(c, v, arc, o, screenH) {
589
610
  const t = c.theme;
590
611
  const w = Math.min(62, c.w - 4);
@@ -619,6 +640,17 @@ function positionCard(c, v, arc, o, screenH) {
619
640
  const lv = Math.round(((s.pct - lo) / span) * 7);
620
641
  c.put(x + 2 + i, y + 6, LV[lv], { fg: s.pct >= 0 ? t.up : t.down, bg, soft: true });
621
642
  });
643
+ // task-1483: the agent's PAPER fills under the trace (△B / ▽S, hollow = paper), at the sample that first shows them
644
+ const used = new Set();
645
+ for (const m of traceFillCols(arc.trace.map((s) => s.t), arc.trace.length - tr.length, arc.fills)) {
646
+ const cx = x + 2 + m.col;
647
+ if (used.has(m.col) || used.has(m.col + 1) || cx + 1 >= x + w - 1)
648
+ continue;
649
+ used.add(m.col);
650
+ used.add(m.col + 1);
651
+ const col = m.side === 'buy' ? t.up : t.down;
652
+ c.text(cx, y + 7, `${markerGlyph(m.side, 'paper')}${markerLetter(m.side)}`, { fg: col, bg: mix(bg, col, 0.2), b: true, hard: true });
653
+ }
622
654
  }
623
655
  else
624
656
  c.text(x + 2, y + 6, '· waiting for the first mark tick', { fg: t.dim, bg });
@@ -20,7 +20,7 @@ export function renderAnalystSession(c, x, y, w, h, s, opts) {
20
20
  // Narrow panels (token screen) drop the model so cost · latency · tools still fits beside the title.
21
21
  const full = s.status === 'error' ? 'analyst unavailable' : s.footer;
22
22
  // 0.1.5: a codex conversation says so in the title; claude panels keep the original default.
23
- const title = opts.title ?? (s.engine === 'codex' ? '✦ ANALYST · codex' : '✦ ANALYST · claude');
23
+ const title = opts.title ?? (s.engine === 'codex' ? '✦ ANALYST · codex' : s.engine === 'ollama' ? '✦ ANALYST · ollama' : '✦ ANALYST · claude');
24
24
  const caption = title.length + full.length + 10 <= w ? full : full.split(' · ').slice(0, 3).join(' · ');
25
25
  const capFg = s.status === 'error' ? t.warn : t.dim;
26
26
  const box = c.box(x, y, w, h, { title, titleFg: t.ai, caption, captionFg: capFg, border: bc });
@@ -183,7 +183,7 @@ export function renderAnalystSession(c, x, y, w, h, s, opts) {
183
183
  // mcap/age + the analyst's one-line reason), navigable with ↑↓ and ⏎-to-jump. The numbers come from
184
184
  // live data (resolved by the caller from the app's data layer, never from the model's text).
185
185
  if (opts.results?.length && cy < inputY - 1) {
186
- cy = renderResultsTable(c, box, bg, cy, inputY, inner, opts.results, opts.resultsCursor ?? 0, opts.resultsActive ?? false);
186
+ cy = renderResultsTable(c, box, bg, cy, inputY, inner, opts.results, opts.resultsCursor ?? 0, opts.resultsActive ?? false, opts.priceMode !== 'price');
187
187
  }
188
188
  if (s.candidates.length && cy < inputY - 1) {
189
189
  cy += 1;
@@ -251,14 +251,17 @@ function fmtAge(min) {
251
251
  * so it stays legible instead of wrapping into prose. The highlighted row (cursor) is tinted; when the
252
252
  * table is active a `▸` marks it and the footer hint changes. Returns the new y.
253
253
  */
254
- function renderResultsTable(c, box, bg, startY, inputY, inner, rows, cursor, active) {
254
+ function renderResultsTable(c, box, bg, startY, inputY, inner, rows, cursor, active, mcapFirst = true) {
255
255
  const t = c.theme;
256
256
  let cy = startY + 1;
257
257
  if (cy >= inputY - 1)
258
258
  return cy;
259
259
  // Column plan: [symbol, price, 24h, liq, vol, mcap, age] then the reason fills the rest. Drop from
260
260
  // the right as width shrinks so a narrow panel still shows symbol + price + change + a reason.
261
- const full = [['SYMBOL', 10], ['PRICE', 9], ['24H', 8], ['LIQ', 8], ['VOL', 8], ['MCAP', 8], ['AGE', 5]];
261
+ // task-1537: in the market-cap basis MCAP takes PRICE's slot (the headline number) and PRICE moves to MCAP's
262
+ const full = mcapFirst
263
+ ? [['SYMBOL', 10], ['MCAP', 9], ['24H', 8], ['LIQ', 8], ['VOL', 8], ['PRICE', 8], ['AGE', 5]]
264
+ : [['SYMBOL', 10], ['PRICE', 9], ['24H', 8], ['LIQ', 8], ['VOL', 8], ['MCAP', 8], ['AGE', 5]];
262
265
  let cols = full.slice();
263
266
  // keep trimming trailing columns until the header + a minimal reason (12) fits
264
267
  while (cols.length > 3 && cols.reduce((a, [, w]) => a + w + 1, 0) + 12 > inner - 2)
@@ -6,6 +6,7 @@ import { Canvas, sparkline, heatPct, decryptText, spinnerGlyph, fmtUsd, fmtPrice
6
6
  import { headerModel } from "../state.js";
7
7
  import { screenerView } from "../screener/view.js";
8
8
  import { renderAnalystSession } from "./analyst-session.js";
9
+ import { priceModeOf } from "../display.js";
9
10
  import { resolveAnalystResults } from "../analyst/results.js";
10
11
  export function renderAnalyst(c, state) {
11
12
  const t = c.theme;
@@ -28,7 +29,7 @@ export function renderAnalyst(c, state) {
28
29
  frame: state.vfx.frame, code: state.vfx.code, calm: isCalm(state.vfx),
29
30
  input: state.aiInput ?? '', inputFocused: focused,
30
31
  scroll: state.aiScroll, revealAll: state.aiRevealAll,
31
- results, resultsCursor: state.analyst.resultsCursor, resultsActive,
32
+ results, resultsCursor: state.analyst.resultsCursor, resultsActive, priceMode: priceModeOf(state), // task-1537
32
33
  inputHint: focused ? 'type your question… ⏎ send · esc blur · analysis not advice'
33
34
  : results.length ? 'i ask · ↑↓ pick coin · ⏎ open · o ticket · esc back'
34
35
  : 'i ask · ⏎ focus · ↑↓ pick coin · PgUp/Dn scroll',
@@ -43,7 +44,7 @@ export function renderAnalyst(c, state) {
43
44
  if (state.screener.source !== 'fixtures') {
44
45
  const live = state.aiFocused === true;
45
46
  const eng = state.analystEngine ?? 'claude'; // 0.1.5: the user's own CLI that will answer (claude | codex)
46
- const b = c.box(0, top, chatW, bottom - top, { title: `✦ ANALYST · ${eng}`, titleFg: t.ai, caption: `read-only · local ${eng} CLI · on demand`, captionFg: t.dim, border: bc });
47
+ const b = c.box(0, top, chatW, bottom - top, { title: `✦ ANALYST · ${eng}`, titleFg: t.ai, caption: `read-only · local ${eng === 'ollama' ? 'ollama model' : `${eng} CLI`} · on demand`, captionFg: t.dim, border: bc });
47
48
  const bbg = b.bg;
48
49
  const bin = b.w - 2;
49
50
  let by = b.y + 1;
@@ -0,0 +1,169 @@
1
+ // dexterm — the AUTOTRADE screen's key handling (`Q`, task-1508). The loop calls autotradeKey() first while the
2
+ // screen captures typed keys (an id / file line, a field value, the flatten confirm), and autotradeAction() for
3
+ // every resolved action while the screen is open. Both only change this screen's state and the user's own
4
+ // strategy files; nothing here reaches a wallet, a key, a mandate or the signer.
5
+ import { INPUT_MAX, backtestSubject, beginFieldEdit, beginImport, beginNew, commitInput, commitTyping, confirmKey, exportSelected, nextView, openEditor, reloadList, refreshRunner, revalidateTyping, runnerVerb, saveEditor, selectedEntry, startBacktest, startCompareBacktest, startSweep, toggleMark, undoEditor, } from "./autotrade-ui.js";
6
+ import { openReview, reviewKey } from "./autotrade-proposals.js";
7
+ const PRINTABLE = (k) => (k.name === 'space' ? ' ' : k.name === 'char' && k.ch && !k.ctrl && !k.alt && !/[\u0000-\u001f\u007f]/.test(k.ch) ? k.ch : null);
8
+ /** A captured key (input line, field value or confirm). Returns true when the key was consumed. */
9
+ export function autotradeKey(ui, key) {
10
+ // task-1507: a chat proposal's review card owns the keyboard (y applies, a LOOSER edit needs a second y, n dismisses)
11
+ if (ui.review) {
12
+ ui.notice = reviewKey(ui, key);
13
+ if (!ui.review)
14
+ reloadList(ui);
15
+ return true;
16
+ }
17
+ if (ui.confirm) {
18
+ ui.notice = confirmKey(ui, key.name === 'char' && key.ch === 'y' && !key.ctrl && !key.alt);
19
+ return true;
20
+ }
21
+ if (ui.input) {
22
+ const inp = ui.input;
23
+ if (key.name === 'esc') {
24
+ ui.input = null;
25
+ ui.notice = null;
26
+ return true;
27
+ }
28
+ if (key.name === 'enter') {
29
+ ui.notice = commitInput(ui);
30
+ return true;
31
+ }
32
+ if (key.name === 'backspace') {
33
+ inp.text = inp.text.slice(0, -1);
34
+ return true;
35
+ }
36
+ if (key.ctrl && key.ch === 'u') {
37
+ inp.text = '';
38
+ return true;
39
+ }
40
+ const ch = PRINTABLE(key);
41
+ // an id is lowercase letters, digits and dashes only, so it can never carry a path
42
+ if (ch && (inp.kind === 'import' ? inp.text.length < INPUT_MAX : /^[a-z0-9-]$/.test(ch) && inp.text.length < 32))
43
+ inp.text += ch;
44
+ return true;
45
+ }
46
+ const ty = ui.view === 'editor' ? ui.editor?.typing : null;
47
+ if (ty) {
48
+ if (key.name === 'esc') {
49
+ ui.editor.typing = null;
50
+ ui.notice = null;
51
+ return true;
52
+ }
53
+ if (key.name === 'enter') {
54
+ ui.notice = commitTyping(ui);
55
+ return true;
56
+ }
57
+ if (key.name === 'backspace')
58
+ ty.text = ty.text.slice(0, -1);
59
+ else if (key.ctrl && key.ch === 'u')
60
+ ty.text = '';
61
+ else {
62
+ const ch = PRINTABLE(key);
63
+ if (ch && ty.text.length < INPUT_MAX)
64
+ ty.text += ch;
65
+ else
66
+ return true;
67
+ }
68
+ revalidateTyping(ui);
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+ function move(ui, delta) {
74
+ if (ui.view === 'list')
75
+ ui.selected = Math.max(0, Math.min(ui.entries.length - 1, ui.selected + delta));
76
+ else if (ui.view === 'editor' && ui.editor)
77
+ ui.editor.selected = Math.max(0, Math.min(ui.editor.fields.length - 1, ui.editor.selected + delta));
78
+ else if (ui.view === 'compare' && ui.compare)
79
+ ui.compare.scroll = Math.max(0, ui.compare.scroll + delta);
80
+ }
81
+ const PAGE = 10;
82
+ /**
83
+ * One resolved action on the AUTOTRADE screen. Returns true when it was handled here (the loop then does
84
+ * nothing else with it); false lets the loop's own handling run (esc on the list pops the screen stack, `q`
85
+ * quits, the global screen keys still navigate away).
86
+ */
87
+ export function autotradeAction(ui, action) {
88
+ const say = (s) => { ui.notice = s ?? null; return true; };
89
+ const e = selectedEntry(ui);
90
+ switch (action) {
91
+ case 'move.down':
92
+ move(ui, 1);
93
+ return true;
94
+ case 'move.up':
95
+ move(ui, -1);
96
+ return true;
97
+ case 'move.pagedown':
98
+ move(ui, PAGE);
99
+ return true;
100
+ case 'move.pageup':
101
+ move(ui, -PAGE);
102
+ return true;
103
+ case 'move.top':
104
+ move(ui, -1e9);
105
+ return true;
106
+ case 'move.bottom':
107
+ move(ui, 1e9);
108
+ return true;
109
+ case 'back':
110
+ if (ui.view === 'list')
111
+ return false;
112
+ ui.view = 'list';
113
+ return say(null);
114
+ case 'autotrade.view':
115
+ nextView(ui);
116
+ if (ui.view === 'runner')
117
+ refreshRunner(ui);
118
+ return say(null);
119
+ case 'open':
120
+ case 'autotrade.edit':
121
+ if (ui.view === 'editor')
122
+ return say(beginFieldEdit(ui));
123
+ if (ui.view !== 'list')
124
+ return action === 'open' ? true : say('e edits from the strategies list');
125
+ return say(e ? openEditor(ui, e.id) : 'no strategy selected');
126
+ case 'autotrade.new': return say(ui.view === 'list' ? beginNew(ui, false) : 'n works on the strategies list (esc)');
127
+ case 'autotrade.dup': return say(ui.view === 'list' ? beginNew(ui, true) : 'd works on the strategies list (esc)');
128
+ case 'autotrade.import':
129
+ if (ui.view !== 'list')
130
+ return say('i works on the strategies list (esc)');
131
+ beginImport(ui);
132
+ return say(null);
133
+ case 'autotrade.export': return say(ui.view === 'list' ? exportSelected(ui) : 'x works on the strategies list (esc)');
134
+ case 'autotrade.compare': return say(ui.view === 'list' ? toggleMark(ui) : 'c marks strategies on the list (esc)');
135
+ case 'autotrade.save': return say(ui.view === 'editor' ? saveEditor(ui) : 's saves in the editor');
136
+ case 'autotrade.undo': return say(ui.view === 'editor' ? undoEditor(ui) : 'u undoes in the editor');
137
+ case 'autotrade.backtest': {
138
+ if (ui.view === 'compare')
139
+ return say(startCompareBacktest(ui));
140
+ if (ui.view === 'editor' && ui.editor)
141
+ return say(`${startBacktest(ui, ui.editor.id)}${ui.editor.edits.length ? ' (the saved version; s saves your edits first)' : ''}`);
142
+ if (ui.view === 'backtest') {
143
+ const s = backtestSubject(ui);
144
+ return say(s ? startBacktest(ui, s.id) : 'no strategy selected');
145
+ }
146
+ return say(e ? startBacktest(ui, e.id) : 'no strategy selected');
147
+ }
148
+ case 'autotrade.sweep': {
149
+ const id = ui.view === 'editor' && ui.editor ? ui.editor.id : ui.view === 'backtest' ? backtestSubject(ui)?.id : e?.id;
150
+ return say(id ? startSweep(ui, id) : 'no strategy selected');
151
+ }
152
+ case 'autotrade.proposal': return say(ui.view === 'list' ? openReview(ui) || null : 'o reviews chat proposals on the strategies list (esc)');
153
+ case 'autotrade.run':
154
+ ui.view = 'runner';
155
+ refreshRunner(ui);
156
+ reloadList(ui);
157
+ return say('starting and stopping a paper run ships with the runner (not in this build yet) · this view shows a runner as soon as one writes its status file');
158
+ case 'autotrade.kill':
159
+ refreshRunner(ui);
160
+ return say(runnerVerb(ui, 'kill'));
161
+ case 'autotrade.pause':
162
+ refreshRunner(ui);
163
+ return say(runnerVerb(ui, 'pause'));
164
+ case 'autotrade.flatten':
165
+ refreshRunner(ui);
166
+ return say(runnerVerb(ui, 'flatten'));
167
+ default: return false;
168
+ }
169
+ }