dexterm-cli 0.1.6 → 0.2.0

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 (395) hide show
  1. package/dist/packages/agents/src/schema.js +22 -1
  2. package/dist/packages/agents/src/spectator.js +3 -1
  3. package/dist/packages/agents/src/story.js +11 -2
  4. package/dist/packages/agents/src/tiers.js +36 -2
  5. package/dist/packages/agents/src/tools.js +35 -2
  6. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -0
  7. package/dist/packages/autotrade/assets/limits.schema.json +569 -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/examples/executor/README.md +70 -0
  20. package/dist/packages/autotrade/examples/executor/jsonl-tail-executor.mjs +57 -0
  21. package/dist/packages/autotrade/examples/executor/kit.mjs +144 -0
  22. package/dist/packages/autotrade/examples/executor/signal-cmd-executor.mjs +25 -0
  23. package/dist/packages/autotrade/examples/executor/telegram-bot-recipe.md +47 -0
  24. package/dist/packages/autotrade/examples/executor/webhook-executor.mjs +47 -0
  25. package/dist/packages/autotrade/src/account.js +74 -0
  26. package/dist/packages/autotrade/src/backtest.js +358 -0
  27. package/dist/packages/autotrade/src/basis-text.js +32 -0
  28. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  29. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  30. package/dist/packages/autotrade/src/chat/config.js +95 -0
  31. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  32. package/dist/packages/autotrade/src/chat/control.js +104 -0
  33. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  34. package/dist/packages/autotrade/src/chat/format.js +71 -0
  35. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  36. package/dist/packages/autotrade/src/chat/index.js +18 -0
  37. package/dist/packages/autotrade/src/chat/notify.js +302 -0
  38. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  39. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  40. package/dist/packages/autotrade/src/chat/secrets.js +106 -0
  41. package/dist/packages/autotrade/src/chat/service.js +251 -0
  42. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  43. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  44. package/dist/packages/autotrade/src/engine.js +401 -0
  45. package/dist/packages/autotrade/src/execution-port.js +97 -0
  46. package/dist/packages/autotrade/src/executor-health.js +217 -0
  47. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  48. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  49. package/dist/packages/autotrade/src/fleet.js +642 -0
  50. package/dist/packages/autotrade/src/forward.js +355 -0
  51. package/dist/packages/autotrade/src/index.js +72 -0
  52. package/dist/packages/autotrade/src/journey.js +477 -0
  53. package/dist/packages/autotrade/src/ledger.js +108 -0
  54. package/dist/packages/autotrade/src/limits.js +682 -0
  55. package/dist/packages/autotrade/src/live-source.js +182 -0
  56. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  57. package/dist/packages/autotrade/src/monitor.js +190 -0
  58. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  59. package/dist/packages/autotrade/src/outbox.js +394 -0
  60. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  61. package/dist/packages/autotrade/src/paper-limits.js +87 -0
  62. package/dist/packages/autotrade/src/real-mode.js +688 -0
  63. package/dist/packages/autotrade/src/receipts.js +330 -0
  64. package/dist/packages/autotrade/src/replay.js +198 -0
  65. package/dist/packages/autotrade/src/report.js +168 -0
  66. package/dist/packages/autotrade/src/runner-config.js +99 -0
  67. package/dist/packages/autotrade/src/runner.js +923 -0
  68. package/dist/packages/autotrade/src/safety.js +243 -0
  69. package/dist/packages/autotrade/src/service-guard.js +125 -0
  70. package/dist/packages/autotrade/src/service-install.js +213 -0
  71. package/dist/packages/autotrade/src/service-recover.js +232 -0
  72. package/dist/packages/autotrade/src/service.js +289 -0
  73. package/dist/packages/autotrade/src/session.js +269 -0
  74. package/dist/packages/autotrade/src/sessions.js +503 -0
  75. package/dist/packages/autotrade/src/signal-channels.js +312 -0
  76. package/dist/packages/autotrade/src/signal.js +196 -0
  77. package/dist/packages/autotrade/src/store.js +250 -0
  78. package/dist/packages/autotrade/src/strategy.js +258 -0
  79. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  80. package/dist/packages/autotrade/src/yolo.js +140 -0
  81. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  82. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  83. package/dist/packages/cli/src/ai-cmd.js +412 -0
  84. package/dist/packages/cli/src/analyst/backend.js +83 -15
  85. package/dist/packages/cli/src/analyst/codex.js +15 -9
  86. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  87. package/dist/packages/cli/src/analyst/session.js +5 -2
  88. package/dist/packages/cli/src/app-help-gen.js +56 -8
  89. package/dist/packages/cli/src/autotrade-cmd/audit.js +74 -0
  90. package/dist/packages/cli/src/autotrade-cmd/backtest.js +216 -0
  91. package/dist/packages/cli/src/autotrade-cmd/executor.js +91 -0
  92. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  93. package/dist/packages/cli/src/autotrade-cmd/fleet.js +401 -0
  94. package/dist/packages/cli/src/autotrade-cmd/forward.js +283 -0
  95. package/dist/packages/cli/src/autotrade-cmd/journey.js +208 -0
  96. package/dist/packages/cli/src/autotrade-cmd/limits.js +393 -0
  97. package/dist/packages/cli/src/autotrade-cmd/receipt.js +63 -0
  98. package/dist/packages/cli/src/autotrade-cmd/report.js +79 -0
  99. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  100. package/dist/packages/cli/src/autotrade-cmd/run.js +657 -0
  101. package/dist/packages/cli/src/autotrade-cmd/service.js +873 -0
  102. package/dist/packages/cli/src/autotrade-cmd/signal.js +824 -0
  103. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  104. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  105. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  106. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  107. package/dist/packages/cli/src/autotrade-cmd.js +214 -0
  108. package/dist/packages/cli/src/display.js +156 -17
  109. package/dist/packages/cli/src/doctor.js +62 -3
  110. package/dist/packages/cli/src/export.js +10 -2
  111. package/dist/packages/cli/src/index.js +1 -1
  112. package/dist/packages/cli/src/ledger-cmd.js +290 -0
  113. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  114. package/dist/packages/cli/src/live/index.js +23 -89
  115. package/dist/packages/cli/src/live/screens/token-live.js +9 -6
  116. package/dist/packages/cli/src/live/trade.js +12 -457
  117. package/dist/packages/cli/src/live/watchlist.js +1 -1
  118. package/dist/packages/cli/src/main.js +147 -190
  119. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  120. package/dist/packages/cli/src/plan-cmd.js +1 -1
  121. package/dist/packages/cli/src/pnl-app.js +31 -21
  122. package/dist/packages/cli/src/pnl-card.js +162 -21
  123. package/dist/packages/cli/src/popup.js +22 -6
  124. package/dist/packages/cli/src/record-cmd.js +141 -20
  125. package/dist/packages/cli/src/screens/agent-hud.js +34 -2
  126. package/dist/packages/cli/src/screens/analyst-session.js +7 -4
  127. package/dist/packages/cli/src/screens/analyst.js +3 -2
  128. package/dist/packages/cli/src/screens/autotrade-journey.js +134 -0
  129. package/dist/packages/cli/src/screens/autotrade-keys.js +182 -0
  130. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  131. package/dist/packages/cli/src/screens/autotrade-screen.js +776 -0
  132. package/dist/packages/cli/src/screens/autotrade-ui.js +650 -0
  133. package/dist/packages/cli/src/screens/autotrade-worker.js +91 -0
  134. package/dist/packages/cli/src/screens/brain.js +37 -11
  135. package/dist/packages/cli/src/screens/overlay.js +5 -2
  136. package/dist/packages/cli/src/screens/plans.js +5 -17
  137. package/dist/packages/cli/src/screens/pnl-chip.js +9 -8
  138. package/dist/packages/cli/src/screens/positions.js +6 -28
  139. package/dist/packages/cli/src/screens/ticket.js +38 -205
  140. package/dist/packages/cli/src/screens/token.js +3 -3
  141. package/dist/packages/cli/src/state.js +25 -19
  142. package/dist/packages/cli/src/telegram-cmd.js +454 -0
  143. package/dist/packages/cli/src/trade.js +3 -128
  144. package/dist/packages/cli/src/version.js +1 -1
  145. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  146. package/dist/packages/cli/src/wallet-command.js +8 -11
  147. package/dist/packages/cli/src/widgets.js +0 -12
  148. package/dist/packages/custom/src/config.js +20 -2
  149. package/dist/packages/custom/src/schema.js +35 -10
  150. package/dist/packages/custom/src/workspace.js +3 -3
  151. package/dist/packages/data/src/basis.js +191 -0
  152. package/dist/packages/data/src/detail.js +53 -2
  153. package/dist/packages/data/src/index.js +0 -14
  154. package/dist/packages/data/src/store.js +14 -5
  155. package/dist/packages/data/src/trade/agent/paper-desk.js +228 -16
  156. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  157. package/dist/packages/data/src/trade/bridge.js +4 -102
  158. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  159. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  160. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  161. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  162. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  163. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  164. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  165. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  166. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  167. package/dist/packages/data/src/trade/plans/index.js +0 -1
  168. package/dist/packages/data/src/trade/plans/service.js +3 -27
  169. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  170. package/dist/packages/mcp/assets/kb/app-help/autotrade-executor-kit.md +44 -0
  171. package/dist/packages/mcp/assets/kb/app-help/autotrade-journey.md +46 -0
  172. package/dist/packages/mcp/assets/kb/app-help/autotrade-limits.md +48 -0
  173. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  174. package/dist/packages/mcp/assets/kb/app-help/autotrade-real-signal.md +45 -0
  175. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  176. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  177. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  178. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  179. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  180. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  181. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  182. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  183. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  184. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  185. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  186. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +229 -0
  187. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  188. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  189. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  190. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  191. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +3 -0
  192. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  193. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  194. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  195. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  196. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  197. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +4 -1
  198. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  200. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  201. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  202. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  203. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  204. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +51 -0
  205. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  208. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  209. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +38 -0
  210. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  211. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +37 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-always-on-service.md +57 -0
  219. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  220. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  223. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  225. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +50 -0
  226. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  227. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  228. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  229. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  230. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  231. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  232. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-1-ask-for-a-strategy.md +27 -0
  233. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-2-backtest-it-held-out-split.md +31 -0
  234. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-3-forward-test-it-on-live-data.md +44 -0
  235. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-4-promote-it-to-always-on-paper.md +36 -0
  236. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-5-real-only-if-you-choose.md +36 -0
  237. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-ladder.md +39 -0
  238. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-the-report.md +31 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat-what-the-analyst-knows.md +30 -0
  240. package/dist/packages/mcp/assets/kb/docs/docs-build-a-strategy-by-chat.md +24 -0
  241. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  242. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +1 -1
  243. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  244. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  246. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +8 -6
  247. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  248. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  249. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  250. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  251. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  254. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  255. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  256. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  257. package/dist/packages/mcp/assets/kb/docs/docs-going-real-executor-health.md +38 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-going-real-paper-vs-real.md +33 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-digest.md +36 -0
  260. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-executor-kit.md +48 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-going-real-the-whole-path.md +37 -0
  262. package/dist/packages/mcp/assets/kb/docs/docs-going-real-what-dexterm-does-and-never-does.md +44 -0
  263. package/dist/packages/mcp/assets/kb/docs/docs-going-real-your-limits.md +77 -0
  264. package/dist/packages/mcp/assets/kb/docs/docs-going-real.md +24 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  266. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-real-mode-your-strategy-s-trades-to-your.md +31 -0
  267. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  269. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +31 -0
  270. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  271. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  272. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  273. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  274. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  275. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  276. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  277. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  278. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  279. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  280. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  281. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  282. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  283. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  284. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  285. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  286. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  287. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  288. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  289. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +3 -4
  290. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  291. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +6 -7
  292. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  293. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  294. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  295. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  296. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  297. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  298. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  299. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  300. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +2 -1
  301. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-receipts-what-your-executor-did.md +42 -0
  302. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-safety.md +34 -0
  303. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-set-it-up.md +47 -0
  304. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-channels.md +42 -0
  305. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-the-signal.md +48 -0
  306. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-is.md +35 -0
  307. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-what-it-will-not-do.md +34 -0
  308. package/dist/packages/mcp/assets/kb/docs/docs-real-mode-where-you-see-it.md +34 -0
  309. package/dist/packages/mcp/assets/kb/docs/docs-real-mode.md +27 -0
  310. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  311. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  312. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  313. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +1 -1
  314. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  315. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  316. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  317. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  318. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  319. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  320. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  321. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  322. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  323. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  324. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  325. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  326. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  327. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  328. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  329. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  330. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  331. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  332. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  333. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  334. package/dist/packages/mcp/assets/kb/entries/autotrade.md +40 -0
  335. package/dist/packages/mcp/src/app-help.js +1 -1
  336. package/dist/packages/mcp/src/autotrade-tools.js +1107 -0
  337. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  338. package/dist/packages/mcp/src/engine/config.js +159 -0
  339. package/dist/packages/mcp/src/engine/index.js +10 -0
  340. package/dist/packages/mcp/src/engine/loop.js +101 -0
  341. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  342. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  343. package/dist/packages/mcp/src/engine/probe.js +110 -0
  344. package/dist/packages/mcp/src/engine/reason.js +35 -0
  345. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  346. package/dist/packages/mcp/src/index.js +7 -1
  347. package/dist/packages/mcp/src/ledger-tools.js +494 -0
  348. package/dist/packages/mcp/src/paper-agent.js +2 -0
  349. package/dist/packages/mcp/src/server.js +138 -19
  350. package/dist/packages/packs/src/backtest.js +3 -1
  351. package/dist/packages/popup/src/pages/chart-lib.js +134 -21
  352. package/dist/packages/popup/src/pages/chart.css +9 -0
  353. package/dist/packages/popup/src/pages/chart.html +3 -1
  354. package/dist/packages/popup/src/pages/chart.js +104 -7
  355. package/dist/packages/popup/src/protocol.js +2 -0
  356. package/dist/packages/popup/src/server.js +4 -0
  357. package/dist/packages/recorder/src/index.js +2 -0
  358. package/dist/packages/recorder/src/recorder.js +63 -6
  359. package/dist/packages/recorder/src/retention.js +100 -0
  360. package/dist/packages/recorder/src/schema.js +34 -1
  361. package/dist/packages/recorder/src/ticks.js +237 -0
  362. package/dist/packages/tui/src/index.js +1 -0
  363. package/dist/packages/tui/src/keymap.js +28 -1
  364. package/dist/packages/tui/src/markers.js +219 -0
  365. package/dist/packages/tui/src/widgets.js +149 -2
  366. package/package.json +2 -1
  367. package/dist/packages/data/src/trade/caps.js +0 -323
  368. package/dist/packages/data/src/trade/executor.js +0 -933
  369. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  370. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  371. package/dist/packages/data/src/trade/signer/client.js +0 -197
  372. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  373. package/dist/packages/data/src/trade/signer/host.js +0 -97
  374. package/dist/packages/data/src/trade/signer/main.js +0 -121
  375. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  376. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  377. package/dist/packages/data/src/trade/signer/service.js +0 -400
  378. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  379. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  380. package/dist/packages/data/src/trade/swap-build.js +0 -108
  381. package/dist/packages/data/src/trade/token-delta.js +0 -201
  382. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  383. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  384. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  385. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  386. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  387. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  388. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  389. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  390. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  391. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  392. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  393. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  394. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  395. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -0,0 +1,650 @@
1
+ // dexterm — the AUTOTRADE (paper) screen's state and verbs (`Q`, task-1508, AUTOTRADE-SPEC §2b). PAPER ONLY.
2
+ //
3
+ // Five views on one screen key ('autotrade'), cycled with tab:
4
+ // list presets + the user's strategies (mode, version, hash, last backtest, the active runner's marker)
5
+ // editor one form, six sections; every field re-validates on change (validateStrategy), errors inline with
6
+ // their path; the screen filter is checked by the screener's own parser (error column highlighted);
7
+ // `s` saves only a valid draft (a new version through StrategyStore.patch), `u` undoes with the
8
+ // custom patch inverses (an unsaved edit first, else the last saved patch as a new version)
9
+ // compare two strategies side by side (changed fields highlighted) + both backtests on one tape
10
+ // backtest the §3 report (attribution, equity sparkline, clamps/refusals, limits) + a sweep with the held-out verdict
11
+ // runner the runner's rendezvous file (heartbeat, halt, candidates, sessions, positions, ledger tail)
12
+ //
13
+ // What this screen can never do: switch a strategy to live (the editor refuses /mode, an import is forced to
14
+ // paper), touch a wallet, a key, a mandate or the signer, or apply a model's edit (chat proposals are shown as
15
+ // cards; applying them is the chat card's keypress flow). Kill / pause / flatten go through RunnerControl: the
16
+ // runner's control file (task-1509 fileControl), the same request `dexterm autotrade stop|flatten` writes.
17
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { dirname, join } from 'node:path';
19
+ import { applyOps, dextermHome } from '../../../custom/src/index.js';
20
+ import { StrategyStore, MAX_OPS, recordBacktestEvidence, STRATEGY_ID, activeRunner, bumpPatch, fmtIssue, readControl, readFleetStatus, requestControl, runnerRendezvousPath, validateStrategy, } from '../../../autotrade/src/index.js';
21
+ import { parseFilter } from "../screener/filter.js";
22
+ import { proposalFilesFor, proposalSource } from "./autotrade-proposals.js";
23
+ import { confirmPromote } from "./autotrade-journey.js";
24
+ export const AUTOTRADE_VIEWS = ['list', 'editor', 'compare', 'backtest', 'runner'];
25
+ export const VIEW_TITLE = { list: 'STRATEGIES', editor: 'EDITOR', compare: 'COMPARE', backtest: 'BACKTEST', runner: 'RUNNER', journey: 'JOURNEY' };
26
+ export const SECTIONS = ['entry', 'exits', 'risk', 'sizing', 'agent', 'execution'];
27
+ /** Who the journal names for an edit made on this screen (the same actor the CLI uses for the user's own edits). */
28
+ export const TUI_ACTOR = 'user';
29
+ /** The default backtest window (the chat tool's limit, §2a). */
30
+ export const BACKTEST_DAYS = 14;
31
+ /** The runner writes its heartbeat every 10 s (§6.3); older than this the view says stale. */
32
+ export const HEARTBEAT_STALE_MS = 30_000;
33
+ export const INPUT_MAX = 400;
34
+ export const STUB_CONTROL = {
35
+ wired: false,
36
+ kill: () => 'kill is not wired in this build yet (it ships with the runner\'s stop command) · `dexterm wallet panic` stops every paper desk now',
37
+ pause: () => 'pause is not wired in this build yet (it ships with the runner\'s stop command)',
38
+ flatten: () => 'flatten is not wired in this build yet (it ships with the runner\'s flatten command) · nothing was sold',
39
+ };
40
+ /**
41
+ * The running runner's control file (task-1509, @dexterm/autotrade safety.ts): `!` is a soft stop (no new entries,
42
+ * sessions closed, orders dropped, mechanical exits keep running), `p` pauses or resumes new entries, `f` + y
43
+ * flattens. A stop or flatten is final for the run; the runner reads the request on its next tick.
44
+ */
45
+ export function fileControl(home) {
46
+ const send = (action) => {
47
+ const r = activeRunner(home);
48
+ if (!r)
49
+ return 'no autotrade runner is running';
50
+ const w = requestControl(home, r.runId, action, 'tui');
51
+ if (!w.ok)
52
+ return w.why;
53
+ switch (action) {
54
+ case 'stop': return w.already ? 'stop already requested · mechanical exits keep running until nothing is held' : 'stop requested: no new entries, sessions close, orders dropped · mechanical exits keep running until nothing is held';
55
+ case 'flatten': return w.already ? 'flatten already requested' : 'flatten requested: every paper position sells at its next tick, then the runner stops';
56
+ case 'pause': return 'new entries paused · open positions keep their exits · p again resumes';
57
+ default: return 'new entries resumed';
58
+ }
59
+ };
60
+ return {
61
+ wired: true,
62
+ kill: () => send('stop'),
63
+ pause: () => {
64
+ const r = activeRunner(home);
65
+ const cur = readControl(home);
66
+ const paused = !!r && cur !== null && cur !== 'unreadable' && cur.runId === r.runId && cur.action === 'pause';
67
+ return send(paused ? 'resume-entries' : 'pause');
68
+ },
69
+ flatten: () => send('flatten'),
70
+ };
71
+ }
72
+ // ───────────────────────────── the runner rendezvous ─────────────────────────────
73
+ export const RUNNER_SCHEMA = 'dexterm-autotrade-runner/1';
74
+ /** The runner's rendezvous, `$DEXTERM_HOME/autotrade/runner-status.json` (the runner owns the path; runner.json next to it is
75
+ * the runner config, §7.3), written atomically every heartbeat; this screen only reads it. */
76
+ export { runnerRendezvousPath };
77
+ export function readRunnerRendezvous(path, now = Date.now()) {
78
+ if (!existsSync(path))
79
+ return { kind: 'none', path };
80
+ let doc;
81
+ try {
82
+ doc = JSON.parse(readFileSync(path, 'utf8'));
83
+ }
84
+ catch (e) {
85
+ return { kind: 'bad', path, error: `not JSON: ${e.message}` };
86
+ }
87
+ const err = runnerShapeError(doc);
88
+ if (err)
89
+ return { kind: 'bad', path, error: err };
90
+ return { kind: 'ok', path, snap: doc, readAt: now };
91
+ }
92
+ function runnerShapeError(d) {
93
+ if (!d || typeof d !== 'object')
94
+ return 'not an object';
95
+ const o = d;
96
+ if (o.schema !== RUNNER_SCHEMA)
97
+ return `unknown rendezvous shape ${JSON.stringify(o.schema)} (expected ${RUNNER_SCHEMA})`;
98
+ // Paper, or real (task-1645: signals to the user's executor). Anything else is an error, never a running live runner.
99
+ if (o.mode !== 'paper' && o.mode !== 'real')
100
+ return `mode ${JSON.stringify(o.mode)}: this screen shows paper runners only (and real-mode signal runners)`;
101
+ for (const k of ['pid', 'startedAt', 'heartbeatAt'])
102
+ if (typeof o[k] !== 'number')
103
+ return `${k} is not a number`;
104
+ if (!['running', 'paused', 'halted', 'stopped'].includes(o.state))
105
+ return `unknown state ${JSON.stringify(o.state)}`;
106
+ for (const k of ['candidates', 'sessions', 'positions', 'ledgerTail'])
107
+ if (!Array.isArray(o[k]))
108
+ return `${k} is not a list`;
109
+ for (const k of ['strategy', 'book', 'spend'])
110
+ if (!o[k] || typeof o[k] !== 'object')
111
+ return `${k} is missing`;
112
+ return null;
113
+ }
114
+ export function heartbeatStale(r, now) {
115
+ return r.kind === 'ok' && now - r.snap.heartbeatAt > HEARTBEAT_STALE_MS;
116
+ }
117
+ export function defaultDeps(env = process.env, jobs = null) {
118
+ const home = dextermHome(env);
119
+ const proposalFiles = proposalFilesFor(env);
120
+ const source = proposalSource(proposalFiles);
121
+ return {
122
+ store: new StrategyStore({ env }),
123
+ jobs,
124
+ readRunner: () => readRunnerRendezvous(runnerRendezvousPath(home)),
125
+ readFleet: () => { const r = readFleetStatus(home); return r.ok ? r.status : null; },
126
+ control: fileControl(home),
127
+ proposals: source.cards,
128
+ proposalSource: source,
129
+ proposalFiles,
130
+ exportDir: join(home, 'exports'),
131
+ now: Date.now,
132
+ };
133
+ }
134
+ export function newAutotradeUi(deps) {
135
+ const ui = {
136
+ view: 'list', deps, entries: [], selected: 0, marked: [], notice: null, input: null, editor: null, compare: null,
137
+ focusId: null, backtests: new Map(), compareRun: null, sweep: null, runner: { kind: 'none', path: '' }, fleet: null, runnerReadAt: 0, confirm: null, review: null, journey: null,
138
+ };
139
+ reloadList(ui);
140
+ refreshRunner(ui);
141
+ return ui;
142
+ }
143
+ export function reloadList(ui) {
144
+ try {
145
+ ui.entries = ui.deps.store.list();
146
+ }
147
+ catch (e) {
148
+ ui.entries = [];
149
+ ui.notice = `strategies unreadable: ${e.message}`;
150
+ }
151
+ ui.selected = Math.max(0, Math.min(ui.entries.length - 1, ui.selected));
152
+ }
153
+ export function refreshRunner(ui) {
154
+ ui.runner = ui.deps.readRunner();
155
+ const f = ui.runner.kind === 'ok' ? ui.deps.readFleet?.() ?? null : null;
156
+ // Only the fleet the rendezvous names: a fleet-status.json left by an earlier parallel run is never shown as this one.
157
+ ui.fleet = f && ui.runner.kind === 'ok' && f.fleetId === ui.runner.snap.runId ? f : null;
158
+ ui.runnerReadAt = ui.deps.now();
159
+ }
160
+ /** The loop's 2 Hz tick: re-read the rendezvous every 2 s while the runner (or list, for its marker) is on screen. */
161
+ export function autotradeTick(ui) {
162
+ if (ui.deps.now() - ui.runnerReadAt >= 2000)
163
+ refreshRunner(ui);
164
+ }
165
+ export const selectedEntry = (ui) => ui.entries[ui.selected];
166
+ /** Whether the screen is capturing typed keys (an input line, a field value, or the flatten confirm). */
167
+ export function autotradeCapturing(ui) {
168
+ return ui.input !== null || ui.confirm !== null || ui.review !== null || (ui.view === 'editor' && !!ui.editor?.typing);
169
+ }
170
+ // ───────────────────────────── the form ─────────────────────────────
171
+ /** Every editable leaf of the six sections, in document order. Empty lists/objects and nulls are leaves typed as JSON. */
172
+ export function fieldsOf(doc) {
173
+ const out = [];
174
+ const walk = (v, path, section) => {
175
+ if (v !== null && typeof v === 'object') {
176
+ const entries = Array.isArray(v) ? v.map((x, i) => [String(i), x]) : Object.entries(v);
177
+ if (!entries.length) {
178
+ out.push({ path, section, kind: 'json' });
179
+ return;
180
+ }
181
+ for (const [k, x] of entries)
182
+ walk(x, `${path}/${k}`, section);
183
+ return;
184
+ }
185
+ out.push({ path, section, kind: v === null ? 'null' : typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' });
186
+ };
187
+ for (const s of SECTIONS)
188
+ walk(doc[s], `/${s}`, s);
189
+ return out;
190
+ }
191
+ export function getAt(doc, path) {
192
+ let o = doc;
193
+ for (const seg of path.split('/').slice(1)) {
194
+ if (o === null || typeof o !== 'object')
195
+ return undefined;
196
+ o = o[seg.replace(/~1/g, '/').replace(/~0/g, '~')];
197
+ }
198
+ return o;
199
+ }
200
+ /** Fields only the user may change by hand in a file, never on this screen (no path to live mode from the TUI). */
201
+ export const READ_ONLY_PATHS = new Set(['/mode', '/id', '/schema', '/kind']);
202
+ /** A typed value: strings stay verbatim (the screen filter, model ids, builtin refs), everything else is JSON. */
203
+ export function parseFieldInput(text, kind) {
204
+ if (kind === 'string')
205
+ return { ok: true, value: text };
206
+ const t = text.trim();
207
+ if (!t)
208
+ return { ok: false, error: 'type a value (JSON: a number, true/false, null, a list or an object)' };
209
+ try {
210
+ return { ok: true, value: JSON.parse(t) };
211
+ }
212
+ catch {
213
+ return { ok: false, error: `not a JSON value: ${t.slice(0, 40)}` };
214
+ }
215
+ }
216
+ export function openEditor(ui, id) {
217
+ const got = ui.deps.store.get(id);
218
+ const raw = ui.deps.store.raw(id);
219
+ if (!got || !raw)
220
+ return `no strategy ${id}`;
221
+ const check = validateStrategy(raw);
222
+ ui.editor = { id, source: got.source, draft: raw, baseVersion: raw.version, edits: [], fields: fieldsOf(raw), selected: 0, check, typing: null };
223
+ ui.view = 'editor';
224
+ return got.source === 'preset' ? `${id} is a shipped preset (read-only): edits here are a draft · d on the list makes your own copy` : null;
225
+ }
226
+ /** Issues pinned to the first field at or under their path; the rest (whole-document ones) go to `loose`. */
227
+ export function issuesByField(fields, issues) {
228
+ const at = new Map();
229
+ const loose = [];
230
+ for (const i of issues) {
231
+ const f = fields.find((x) => x.path === i.path || x.path.startsWith(`${i.path}/`));
232
+ if (!f || !i.path) {
233
+ loose.push(i);
234
+ continue;
235
+ }
236
+ at.set(f.path, [...(at.get(f.path) ?? []), i]);
237
+ }
238
+ return { at, loose };
239
+ }
240
+ export function beginFieldEdit(ui) {
241
+ const ed = ui.editor;
242
+ if (!ed)
243
+ return null;
244
+ const f = ed.fields[ed.selected];
245
+ if (!f)
246
+ return null;
247
+ if (READ_ONLY_PATHS.has(f.path))
248
+ return `${f.path} cannot be changed here`;
249
+ const cur = getAt(ed.draft, f.path);
250
+ // booleans flip in place (one keypress, re-validated like any edit)
251
+ if (f.kind === 'boolean')
252
+ return commitField(ui, f.path, !cur);
253
+ const text = f.kind === 'string' ? String(cur ?? '') : JSON.stringify(cur);
254
+ ed.typing = { path: f.path, text, check: null, parseError: null };
255
+ revalidateTyping(ui);
256
+ return null;
257
+ }
258
+ /** Re-validate the draft with the value being typed (every keystroke). */
259
+ export function revalidateTyping(ui) {
260
+ const ed = ui.editor;
261
+ if (!ed?.typing)
262
+ return;
263
+ const f = ed.fields.find((x) => x.path === ed.typing.path);
264
+ const p = parseFieldInput(ed.typing.text, f?.kind ?? 'json');
265
+ if (!p.ok) {
266
+ ed.typing.parseError = p.error;
267
+ ed.typing.check = null;
268
+ return;
269
+ }
270
+ ed.typing.parseError = null;
271
+ try {
272
+ ed.typing.check = validateStrategy(applyOps(ed.draft, [{ op: 'replace', path: ed.typing.path, value: p.value }]).doc);
273
+ }
274
+ catch (e) {
275
+ ed.typing.parseError = e.message;
276
+ ed.typing.check = null;
277
+ }
278
+ }
279
+ export function commitTyping(ui) {
280
+ const ed = ui.editor;
281
+ if (!ed?.typing)
282
+ return null;
283
+ const { path, text } = ed.typing;
284
+ const f = ed.fields.find((x) => x.path === path);
285
+ const p = parseFieldInput(text, f?.kind ?? 'json');
286
+ if (!p.ok)
287
+ return p.error;
288
+ ed.typing = null;
289
+ return commitField(ui, path, p.value);
290
+ }
291
+ function commitField(ui, path, value) {
292
+ const ed = ui.editor;
293
+ if (READ_ONLY_PATHS.has(path))
294
+ return `${path} cannot be changed here`;
295
+ if (JSON.stringify(getAt(ed.draft, path)) === JSON.stringify(value))
296
+ return 'unchanged';
297
+ const op = { op: 'replace', path, value };
298
+ let next;
299
+ try {
300
+ next = applyOps(ed.draft, [op]);
301
+ }
302
+ catch (e) {
303
+ return e.message;
304
+ }
305
+ ed.draft = next.doc;
306
+ ed.edits.push({ op, inverse: next.inverse });
307
+ rebuildForm(ed, path);
308
+ return ed.check.ok ? null : `✗ ${ed.check.errors.length} problem${ed.check.errors.length === 1 ? '' : 's'} (fix before saving)`;
309
+ }
310
+ function rebuildForm(ed, keepPath) {
311
+ ed.check = validateStrategy(ed.draft);
312
+ ed.fields = fieldsOf(ed.draft);
313
+ if (keepPath) {
314
+ const i = ed.fields.findIndex((x) => x.path === keepPath || x.path.startsWith(`${keepPath}/`));
315
+ if (i >= 0)
316
+ ed.selected = i;
317
+ }
318
+ ed.selected = Math.max(0, Math.min(ed.fields.length - 1, ed.selected));
319
+ }
320
+ /** The unsaved edits as the fewest ops: the last value per path, in first-touched order. */
321
+ export function pendingOps(ed) {
322
+ const last = new Map();
323
+ for (const e of ed.edits) {
324
+ last.delete(e.op.path);
325
+ last.set(e.op.path, e.op);
326
+ }
327
+ return [...last.values()];
328
+ }
329
+ /** `s`: only a valid draft is written, as a new version; the journal line carries the ops and their inverse. */
330
+ export function saveEditor(ui) {
331
+ const ed = ui.editor;
332
+ if (!ed)
333
+ return 'nothing to save';
334
+ if (ed.source === 'preset')
335
+ return `${ed.id} is a shipped preset (read-only) · press d on the list to make your own copy, then edit that`;
336
+ if (!ed.edits.length)
337
+ return 'nothing to save (no changes)';
338
+ if (!ed.check.ok)
339
+ return `not saved: ${ed.check.errors.length} problem${ed.check.errors.length === 1 ? '' : 's'} · first: ${fmtIssue(ed.check.errors[0])}`;
340
+ const ops = pendingOps(ed);
341
+ const store = ui.deps.store;
342
+ const r = ops.length <= MAX_OPS
343
+ ? store.patch(ed.id, ops, { actor: TUI_ACTOR, why: 'edited on the Q screen' })
344
+ : store.update(ed.id, { ...ed.draft, version: bumpPatch(ed.baseVersion) }, { actor: TUI_ACTOR, why: 'edited on the Q screen' });
345
+ if (!r.ok)
346
+ return `not saved: ${fmtIssue(r.errors[0])}`;
347
+ const raw = store.raw(ed.id);
348
+ ui.editor = { ...ed, draft: raw, baseVersion: raw.version, edits: [], fields: fieldsOf(raw), check: validateStrategy(raw), typing: null };
349
+ reloadList(ui);
350
+ return `saved ${ed.id} v${r.strategy.version} (${r.strategy.hash}) · ${ops.length} change${ops.length === 1 ? '' : 's'} journaled`;
351
+ }
352
+ /**
353
+ * `u`: undo the newest unsaved edit with its inverse; with nothing unsaved, undo the last saved patch of this
354
+ * strategy by applying that journal line's inverse as a new version (refused when the file changed since).
355
+ */
356
+ export function undoEditor(ui) {
357
+ const ed = ui.editor;
358
+ if (!ed)
359
+ return 'nothing to undo';
360
+ const last = ed.edits.pop();
361
+ if (last) {
362
+ try {
363
+ ed.draft = applyOps(ed.draft, last.inverse).doc;
364
+ }
365
+ catch (e) {
366
+ ed.edits.push(last);
367
+ return `undo failed: ${e.message}`;
368
+ }
369
+ rebuildForm(ed, last.op.path);
370
+ return `undid ${last.op.path}`;
371
+ }
372
+ if (ed.source === 'preset')
373
+ return 'nothing to undo';
374
+ const store = ui.deps.store;
375
+ const j = store.journal(ed.id).filter((x) => x.action !== 'remove');
376
+ const top = j[j.length - 1];
377
+ if (!top || top.action !== 'patch' || !top.inverse?.length)
378
+ return 'nothing to undo (the last saved change was not a field edit)';
379
+ const cur = store.get(ed.id);
380
+ if (!cur?.ok || cur.strategy.hash !== top.afterHash)
381
+ return 'undo refused: the file changed since the last saved edit';
382
+ const inverse = top.inverse.filter((x) => x.path !== '/version');
383
+ const r = store.patch(ed.id, inverse, { actor: TUI_ACTOR, why: `undo of v${top.version}` });
384
+ if (!r.ok)
385
+ return `undo refused: ${fmtIssue(r.errors[0])}`;
386
+ const raw = store.raw(ed.id);
387
+ ui.editor = { ...ed, draft: raw, baseVersion: raw.version, edits: [], fields: fieldsOf(raw), check: validateStrategy(raw), typing: null };
388
+ reloadList(ui);
389
+ return `undid v${top.version} → saved as v${r.strategy.version} (${r.strategy.hash})`;
390
+ }
391
+ /** The screen filter split for highlighting: the screener's tokens with their columns, and the parser's error column. */
392
+ export function screenHighlight(src) {
393
+ const r = parseFilter(src);
394
+ return r.ok ? { error: null, at: null } : { error: r.error, at: r.at };
395
+ }
396
+ // ───────────────────────────── list verbs ─────────────────────────────
397
+ export function beginNew(ui, dup) {
398
+ const e = selectedEntry(ui);
399
+ if (!e)
400
+ return 'no strategy selected';
401
+ if (!dup && e.source !== 'preset')
402
+ return 'n starts from a preset: select one (or d to duplicate this one)';
403
+ ui.input = { kind: dup ? 'dup' : 'new', text: `${e.id}-copy`.slice(0, 32), from: e.id };
404
+ return null;
405
+ }
406
+ export function beginImport(ui) { ui.input = { kind: 'import', text: '' }; }
407
+ export function commitInput(ui) {
408
+ const inp = ui.input;
409
+ if (!inp)
410
+ return '';
411
+ ui.input = null;
412
+ const store = ui.deps.store;
413
+ if (inp.kind === 'import')
414
+ return importFile(ui, inp.text.trim());
415
+ const id = inp.text.trim();
416
+ if (!STRATEGY_ID.test(id))
417
+ return `bad id ${JSON.stringify(id)} (lowercase letters, digits and dashes, 2-32 chars)`;
418
+ const r = store.createFrom(inp.from, id, { actor: TUI_ACTOR, why: `${inp.kind === 'dup' ? 'duplicated' : 'new from preset'} ${inp.from} on the Q screen` });
419
+ if (!r.ok)
420
+ return `not created: ${fmtIssue(r.errors[0])}`;
421
+ reloadList(ui);
422
+ ui.selected = Math.max(0, ui.entries.findIndex((x) => x.id === id && x.source === 'user'));
423
+ return `created ${id} v${r.strategy.version} from ${inp.from} · e to edit`;
424
+ }
425
+ /** `i`: a strategy file into the store. Always paper, whatever the file says (§2c); validated; an existing id is refused. */
426
+ export function importFile(ui, file) {
427
+ if (!file)
428
+ return 'no file';
429
+ let doc;
430
+ try {
431
+ doc = JSON.parse(readFileSync(file, 'utf8'));
432
+ }
433
+ catch (e) {
434
+ return `not imported: ${e.message}`;
435
+ }
436
+ if (!doc || typeof doc !== 'object')
437
+ return 'not imported: not a strategy document';
438
+ const forced = doc.mode !== 'paper';
439
+ doc = { ...doc, mode: 'paper' };
440
+ const r = ui.deps.store.create(doc, { actor: 'import', why: `import ${file} on the Q screen` });
441
+ if (!r.ok)
442
+ return `not imported: ${fmtIssue(r.errors[0])}`;
443
+ reloadList(ui);
444
+ return `imported ${r.strategy.id} v${r.strategy.version}${forced ? ' · mode forced to paper' : ''}`;
445
+ }
446
+ /** `x`: the strategy document as a shareable file (paper; never a key, a wallet or a mandate: the schema has none). */
447
+ export function exportSelected(ui) {
448
+ const e = selectedEntry(ui);
449
+ if (!e)
450
+ return 'no strategy selected';
451
+ const raw = ui.deps.store.raw(e.id);
452
+ if (!raw)
453
+ return `${e.id} is not readable`;
454
+ mkdirSync(ui.deps.exportDir, { recursive: true, mode: 0o700 });
455
+ const path = join(ui.deps.exportDir, `strategy-${raw.id}-v${raw.version}.json`);
456
+ writeFileSync(path, JSON.stringify({ ...raw, mode: 'paper' }, null, 2) + '\n', { mode: 0o600 });
457
+ return `exported ${raw.id} v${raw.version} → ${path}`;
458
+ }
459
+ export function toggleMark(ui) {
460
+ const e = selectedEntry(ui);
461
+ if (!e)
462
+ return 'no strategy selected';
463
+ const key = e.id;
464
+ ui.marked = ui.marked.includes(key) ? ui.marked.filter((x) => x !== key) : [...ui.marked, key].slice(-2);
465
+ if (ui.marked.length === 2) {
466
+ ui.compare = { a: ui.marked[0], b: ui.marked[1], scroll: 0 };
467
+ ui.marked = [];
468
+ ui.view = 'compare';
469
+ return null;
470
+ }
471
+ return ui.marked.length ? `marked ${key} · c on a second strategy compares them` : `unmarked ${key}`;
472
+ }
473
+ /** Every leaf of both documents (union, a's order first); `changed` when the JSON differs. */
474
+ export function compareRows(a, b) {
475
+ const leaves = (doc) => {
476
+ const m = new Map();
477
+ const walk = (v, path) => {
478
+ if (v !== null && typeof v === 'object') {
479
+ const entries = Array.isArray(v) ? v.map((x, i) => [String(i), x]) : Object.entries(v);
480
+ if (!entries.length) {
481
+ m.set(path, JSON.stringify(v));
482
+ return;
483
+ }
484
+ for (const [k, x] of entries)
485
+ walk(x, `${path}/${k}`);
486
+ return;
487
+ }
488
+ m.set(path || '/', typeof v === 'string' ? v : JSON.stringify(v));
489
+ };
490
+ walk(doc, '');
491
+ return m;
492
+ };
493
+ const la = leaves(a), lb = leaves(b);
494
+ const paths = [...la.keys(), ...[...lb.keys()].filter((k) => !la.has(k))];
495
+ return paths.map((p) => ({ path: p, a: la.get(p) ?? '—', b: lb.get(p) ?? '—', changed: la.get(p) !== lb.get(p) }));
496
+ }
497
+ // ───────────────────────────── backtests ─────────────────────────────
498
+ function resolved(ui, id) {
499
+ const g = ui.deps.store.get(id);
500
+ if (!g)
501
+ return `no strategy ${id}`;
502
+ if (!g.ok)
503
+ return `${id} is invalid: ${fmtIssue(g.errors[0])}`;
504
+ if (g.strategy.mode !== 'paper')
505
+ return `${id} is mode "${g.strategy.mode}": autotrade runs paper only`;
506
+ return g.strategy;
507
+ }
508
+ const errText = (e) => (e?.message ?? String(e)).split('\n')[0].slice(0, 200);
509
+ /** `b`: backtest the selected (or focused) strategy on recorded history, off the render loop. */
510
+ export function startBacktest(ui, id) {
511
+ const s = resolved(ui, id);
512
+ if (typeof s === 'string')
513
+ return s;
514
+ ui.focusId = id;
515
+ ui.view = 'backtest';
516
+ if (!ui.deps.jobs)
517
+ return 'backtests need the recorder history (not available in this mode)';
518
+ const cur = ui.backtests.get(s.hash);
519
+ if (cur?.status === 'running')
520
+ return 'a backtest of this version is already running';
521
+ const job = { key: s.hash, status: 'running', startedAt: ui.deps.now() };
522
+ ui.backtests.set(s.hash, job);
523
+ const until = ui.deps.now();
524
+ void ui.deps.jobs.backtest([s], { days: BACKTEST_DAYS, until }).then(([out]) => {
525
+ job.status = 'done';
526
+ job.result = out;
527
+ job.doneAt = ui.deps.now();
528
+ recordLine(ui, s, out.report);
529
+ // task-1647: the held-out split of a stored strategy is its journey evidence
530
+ if (out.evidence && ui.deps.store.get(s.id)?.ok) {
531
+ try {
532
+ recordBacktestEvidence(dirname(ui.deps.store.dir), out.evidence);
533
+ }
534
+ catch { /* evidence is best effort here */ }
535
+ }
536
+ }, (e) => { job.status = 'error'; job.error = errText(e); job.doneAt = ui.deps.now(); });
537
+ return `backtesting ${s.id} v${s.version} on the last ${BACKTEST_DAYS} days of recorded history…`;
538
+ }
539
+ function recordLine(ui, s, r) {
540
+ try {
541
+ ui.deps.store.recordBacktest({ hash: s.hash, t: ui.deps.now(), trades: r.trades, netUsd: r.netUsd, netPct: r.netPct, maxDrawdownPct: r.maxDrawdownPct, tape: `${r.tape.mints} coins ${r.tape.from ?? ''}..${r.tape.to ?? ''}` });
542
+ reloadList(ui);
543
+ }
544
+ catch { /* the line is a convenience for the list; never fail a backtest on it */ }
545
+ }
546
+ /** `b` on compare: both strategies on the SAME tape window. */
547
+ export function startCompareBacktest(ui) {
548
+ if (!ui.compare)
549
+ return 'nothing to compare';
550
+ const a = resolved(ui, ui.compare.a), b = resolved(ui, ui.compare.b);
551
+ if (typeof a === 'string')
552
+ return a;
553
+ if (typeof b === 'string')
554
+ return b;
555
+ if (!ui.deps.jobs)
556
+ return 'backtests need the recorder history (not available in this mode)';
557
+ if (ui.compareRun?.status === 'running')
558
+ return 'already running';
559
+ const job = { key: `${a.hash}|${b.hash}`, status: 'running', startedAt: ui.deps.now() };
560
+ ui.compareRun = job;
561
+ void ui.deps.jobs.backtest([a, b], { days: BACKTEST_DAYS, until: ui.deps.now() }).then((outs) => {
562
+ job.status = 'done';
563
+ job.result = outs;
564
+ job.doneAt = ui.deps.now();
565
+ recordLine(ui, a, outs[0].report);
566
+ recordLine(ui, b, outs[1].report);
567
+ }, (e) => { job.status = 'error'; job.error = errText(e); job.doneAt = ui.deps.now(); });
568
+ return `backtesting ${a.id} and ${b.id} on one tape (last ${BACKTEST_DAYS} days)…`;
569
+ }
570
+ const r2 = (x) => Math.round(x * 100) / 100;
571
+ const around = (x, lo, hi) => [...new Set([x * 0.67, x, x * 1.5].map((v) => Math.max(lo, Math.min(hi, Math.abs(v) >= 5 ? Math.round(v) : r2(v)))))];
572
+ /**
573
+ * The sweep grid for a strategy's default exit profile (§3 skill-care method): cost-back level × bag trail for a
574
+ * moonbag profile, else the trail and the first take-profit, else the size. Three values each, around the current.
575
+ */
576
+ export function defaultGrid(s) {
577
+ const p = s.exits.profiles[s.exits.default];
578
+ const g = {};
579
+ if (p.costRecovery) {
580
+ g['profile.costRecovery.atPct'] = around(p.costRecovery.atPct, 10, 10000);
581
+ if (p.bagTrail)
582
+ g['profile.bagTrail.trailPct'] = around(p.bagTrail.trailPct, 10, 90);
583
+ }
584
+ else {
585
+ if (p.trailingStop)
586
+ g['profile.trailingStop.trailPct'] = around(p.trailingStop.trailPct, 5, 60);
587
+ if (p.tpLadder.length)
588
+ g['profile.tpLadder.0.atPct'] = around(p.tpLadder[0].atPct, 0.1, 10000);
589
+ }
590
+ if (!Object.keys(g).length) {
591
+ if (s.sizing.mode === 'pct' && s.sizing.pct)
592
+ g['sizing.pct'] = around(s.sizing.pct, 0.1, 25);
593
+ else
594
+ g['risk.maxConcurrent'] = [...new Set([Math.max(1, s.risk.maxConcurrent - 1), s.risk.maxConcurrent, Math.min(20, s.risk.maxConcurrent + 1)])];
595
+ }
596
+ return g;
597
+ }
598
+ /** `w`: a held-out sweep (newest 30% of coins never tuned on; the verdict needs >= 20 trades both sides and a positive held-out net). */
599
+ export function startSweep(ui, id) {
600
+ const s = resolved(ui, id);
601
+ if (typeof s === 'string')
602
+ return s;
603
+ ui.focusId = id;
604
+ ui.view = 'backtest';
605
+ if (!ui.deps.jobs)
606
+ return 'sweeps need the recorder history (not available in this mode)';
607
+ if (ui.sweep?.status === 'running')
608
+ return 'a sweep is already running';
609
+ const grid = defaultGrid(s);
610
+ const job = { key: s.hash, id: s.id, grid, status: 'running', startedAt: ui.deps.now() };
611
+ ui.sweep = job;
612
+ void ui.deps.jobs.sweep(s, grid, { days: BACKTEST_DAYS, until: ui.deps.now() }).then((res) => {
613
+ job.status = 'done';
614
+ job.result = res;
615
+ job.doneAt = ui.deps.now();
616
+ }, (e) => { job.status = 'error'; job.error = errText(e); job.doneAt = ui.deps.now(); });
617
+ return `sweeping ${s.id} over ${Object.keys(grid).join(' × ')} with a held-out split…`;
618
+ }
619
+ /** The strategy the backtest view is about: the focused one, else the list selection. */
620
+ export function backtestSubject(ui) {
621
+ const id = ui.focusId ?? selectedEntry(ui)?.id ?? null;
622
+ if (!id)
623
+ return null;
624
+ const e = ui.entries.find((x) => x.id === id);
625
+ return { id, hash: e?.hash ?? null };
626
+ }
627
+ // ───────────────────────────── runner verbs ─────────────────────────────
628
+ export function runnerVerb(ui, verb) {
629
+ if (ui.runner.kind !== 'ok')
630
+ return 'no autotrade runner is running';
631
+ if (verb === 'flatten') {
632
+ ui.confirm = { kind: 'flatten' };
633
+ return 'flatten: sell every paper position at the next tick? y to confirm, any other key cancels';
634
+ }
635
+ return verb === 'kill' ? ui.deps.control.kill() : ui.deps.control.pause();
636
+ }
637
+ export function confirmKey(ui, yes) {
638
+ const c = ui.confirm;
639
+ ui.confirm = null;
640
+ if (!c)
641
+ return '';
642
+ if (c.kind === 'promote')
643
+ return confirmPromote(ui, c.proposalId, yes);
644
+ return yes ? ui.deps.control.flatten() : 'flatten cancelled';
645
+ }
646
+ /** The views tab cycles through (compare only once two strategies were marked). */
647
+ export function nextView(ui) {
648
+ const order = AUTOTRADE_VIEWS.filter((v) => (v !== 'compare' || ui.compare) && (v !== 'editor' || ui.editor));
649
+ ui.view = order[(order.indexOf(ui.view) + 1) % order.length] ?? 'list';
650
+ }