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,97 @@
1
+ // dexterm autotrade — the execution port (task-1527, AUTOTRADE-SPEC §8.1, §8.5 step 7). PAPER today.
2
+ //
3
+ // Where an approved entry goes. The runner's Executor (sim book, paper desk) is the paper side; a live port (the
4
+ // local signer with its caps, a later card) is the other side, behind the same three calls:
5
+ //
6
+ // entryBlock() why no entry can go through right now (null = ready). A live port says "live is off", "the
7
+ // wallet is locked", "the kill switch is engaged", "no caps file" here.
8
+ // confirmRemote() a chat button approved this exact order. The port cannot check the chat itself (it holds no
9
+ // bot token), so it gets the connector's evidence {proposalHash, senderId, chatId, updateId} and
10
+ // answers with a SINGLE-USE proof bound to the order's hash, or a refusal. A live port checks its
11
+ // own caps here (per trade at the remote-button tier, its remote daily cap, the allowed
12
+ // strategies, slippage and impact) and refuses anything outside them, whatever the broker said.
13
+ // buy() / sell() the Executor calls; an approved buy carries its proof in ctx and a port that issued proofs
14
+ // refuses a buy whose proof is missing, spent, expired or bound to another order (the proof is
15
+ // bound to the order's hash and the proposal id, and needs a 64-hex proposal hash).
16
+ //
17
+ // The port has no `badge` field: the runner's Executor may carry its own optional order-line badge (DEVNET/LIVE, the
18
+ // live signer's executor), and PAPER must never print there. Chat derives its badge from `network` (badgeOf).
19
+ //
20
+ // The paper port issues its proofs locally (there is nothing to sign). The live stub refuses everything: it exists
21
+ // so the approval path, the runner and the docs can name the live side before it is installed, and so a
22
+ // misconfiguration fails closed. Nothing in this file holds a key, signs, or touches the network.
23
+ import { createHash, randomBytes } from 'node:crypto';
24
+ import { canonical } from "./strategy.js";
25
+ export const badgeOf = (n) => (n === 'paper' ? 'PAPER' : n === 'devnet' ? 'DEVNET' : 'LIVE');
26
+ /** sha256 (hex, 32 chars) of the canonical order: the proof binds to it (the runner hands it back in OrderContext). */
27
+ export const orderHash = (o) => createHash('sha256').update(canonical(o)).digest('hex').slice(0, 32);
28
+ export const PROOF_TTL_MS = 30_000;
29
+ const PROPOSAL_HASH = /^[0-9a-f]{64}$/;
30
+ /**
31
+ * The paper side: the runner's Executor behind the port calls. confirmRemote issues a local single-use proof
32
+ * (PROOF_TTL_MS) for the order; a buy that carries a proofId must match it (order hash, proposal id, mint, size
33
+ * at most the approved one) and spends it. A buy
34
+ * without one is an ordinary runner entry (no approval was required) and goes straight to the executor.
35
+ */
36
+ export class PaperPort {
37
+ network = 'paper';
38
+ name;
39
+ exec;
40
+ proofs = new Map();
41
+ now;
42
+ block;
43
+ constructor(exec, o = {}) {
44
+ this.exec = exec;
45
+ this.name = `paper-port(${exec.name})`;
46
+ this.now = o.now ?? Date.now;
47
+ this.block = o.entryBlock ?? (() => null);
48
+ }
49
+ entryBlock() { return this.block(); }
50
+ async confirmRemote(order, ev) {
51
+ if (order.network !== 'paper')
52
+ return { ok: false, refused: 'port-network-mismatch' };
53
+ if (ev.tier !== 'remote-button')
54
+ return { ok: false, refused: 'tier-unknown' };
55
+ if (!PROPOSAL_HASH.test(ev.proposalHash) || !ev.proposalId)
56
+ return { ok: false, refused: 'evidence-malformed' };
57
+ const b = this.entryBlock();
58
+ if (b)
59
+ return { ok: false, refused: b };
60
+ if (!(order.usd > 0))
61
+ return { ok: false, refused: 'size-not-positive' };
62
+ const proofId = randomBytes(12).toString('base64url');
63
+ const expiresAt = this.now() + PROOF_TTL_MS;
64
+ this.proofs.set(proofId, { orderHash: orderHash(order), proposalId: ev.proposalId, usd: order.usd, mint: order.mint, expiresAt });
65
+ return { ok: true, proofId, expiresAt };
66
+ }
67
+ async buy(k, usd, ctx) {
68
+ if (ctx?.proofId !== undefined) {
69
+ const p = this.proofs.get(ctx.proofId);
70
+ this.proofs.delete(ctx.proofId); // single use, whatever happens next
71
+ if (!p)
72
+ return { refused: 'proof-unknown-or-spent' };
73
+ if (this.now() > p.expiresAt)
74
+ return { refused: 'proof-expired' };
75
+ if (ctx.orderHash !== p.orderHash || ctx.proposalId !== p.proposalId || p.mint !== k.mint || usd > p.usd + 1e-9)
76
+ return { refused: 'proof-order-mismatch' };
77
+ }
78
+ return this.exec.buy(k, usd, ctx);
79
+ }
80
+ sell(k, tokens, reason) {
81
+ return this.exec.sell(k, tokens, reason);
82
+ }
83
+ }
84
+ /**
85
+ * The live side before it is installed: every call refuses. A runner or a broker that is handed this port takes no
86
+ * entry and says why; exits that would need it are refused too (a live position cannot exist without a live port).
87
+ */
88
+ export class LivePortStub {
89
+ network;
90
+ name = 'live-port-stub';
91
+ static REASON = 'live-port-not-installed';
92
+ constructor(network = 'devnet') { this.network = network; }
93
+ entryBlock() { return LivePortStub.REASON; }
94
+ async confirmRemote() { return { ok: false, refused: LivePortStub.REASON }; }
95
+ buy() { return { refused: LivePortStub.REASON }; }
96
+ sell() { return { refused: LivePortStub.REASON }; }
97
+ }
@@ -0,0 +1,217 @@
1
+ // dexterm autotrade — executor health: heartbeats, the silence pause, the loopback listener (task-1654, 0.2.0 card E).
2
+ //
3
+ // Your executor (or the reference kit, examples/executor/) says it is alive by appending a
4
+ // `dexterm-executor-heartbeat/1` line to $DEXTERM_HOME/autotrade/real/executor.jsonl, by running
5
+ // `dexterm autotrade executor heartbeat`, or by POSTing it to the optional loopback listener a real run opens
6
+ // (`dexterm autotrade signal channels --executor-port N`: 127.0.0.1 only, the same HMAC as the webhook when a secret
7
+ // is set, /heartbeat and /receipt, 4 KB bodies). A receipt, and an ack on the webhook or command hook, count as
8
+ // signs of life too (the JSONL stream and the MCP feed prove nothing: dexterm writes those itself).
9
+ //
10
+ // When nothing has been heard for longer than the user's `executorSilenceMin` (limits.json; placeholder 15 min) a real
11
+ // run PAUSES new buy signals and says so (run log, Telegram). Exits still go out, and the kill switch and the TTL are
12
+ // unchanged. When the executor is heard again, buys resume and that is said too. Heartbeats are data: they never make
13
+ // dexterm do anything except pause or resume.
14
+ import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readSync, renameSync, statSync, writeSync, fsyncSync } from 'node:fs';
15
+ import { createServer } from 'node:http';
16
+ import { join } from 'node:path';
17
+ import { realDir } from "./outbox.js";
18
+ import { appendReceipt, readReceipts, validateReceipt } from "./receipts.js";
19
+ import { ACCOUNT_ID_RE, verifyBody } from "./signal.js";
20
+ export const HEARTBEAT_SCHEMA = 'dexterm-executor-heartbeat/1';
21
+ export const heartbeatPath = (home) => join(realDir(home), 'executor.jsonl');
22
+ const ROTATE_ABOVE = 1 << 20;
23
+ const TAIL_BYTES = 64 << 10;
24
+ const text = (v, max) => { if (typeof v !== 'string')
25
+ return null; const t = v.replace(/[^\x20-\x7e]/g, '').trim().slice(0, max); return t || null; };
26
+ export function validateHeartbeat(x) {
27
+ const h = x;
28
+ if (!h || typeof h !== 'object')
29
+ return { ok: false, error: 'not an object' };
30
+ if (h.schema !== HEARTBEAT_SCHEMA)
31
+ return { ok: false, error: 'schema' };
32
+ if (typeof h.at !== 'number' || !Number.isFinite(h.at) || h.at <= 0)
33
+ return { ok: false, error: 'at' };
34
+ const executor = text(h.executor, 32);
35
+ if (!executor)
36
+ return { ok: false, error: 'executor' };
37
+ if (h.accountId !== undefined && h.accountId !== null && (typeof h.accountId !== 'string' || !ACCOUNT_ID_RE.test(h.accountId)))
38
+ return { ok: false, error: 'accountId' };
39
+ if (h.lastNonce !== undefined && h.lastNonce !== null && (!Number.isSafeInteger(h.lastNonce) || h.lastNonce < 0))
40
+ return { ok: false, error: 'lastNonce' };
41
+ return { ok: true, heartbeat: { schema: HEARTBEAT_SCHEMA, at: h.at, executor, accountId: typeof h.accountId === 'string' ? h.accountId : null, dryRun: typeof h.dryRun === 'boolean' ? h.dryRun : null, lastNonce: typeof h.lastNonce === 'number' ? h.lastNonce : null, note: text(h.note, 120) } };
42
+ }
43
+ /** Append one heartbeat (fsync); the file is rotated to executor.1.jsonl past 1 MB. */
44
+ export function appendHeartbeat(home, hb) {
45
+ const v = validateHeartbeat({ ...hb, schema: HEARTBEAT_SCHEMA });
46
+ if (!v.ok)
47
+ throw new Error(`heartbeat: ${v.error}`);
48
+ mkdirSync(realDir(home), { recursive: true, mode: 0o700 });
49
+ const p = heartbeatPath(home);
50
+ try {
51
+ if (statSync(p).size > ROTATE_ABOVE)
52
+ renameSync(p, join(realDir(home), 'executor.1.jsonl'));
53
+ }
54
+ catch { /* no file yet */ }
55
+ const fd = openSync(p, 'a', 0o600);
56
+ try {
57
+ writeSync(fd, `${JSON.stringify(v.heartbeat)}\n`);
58
+ fsyncSync(fd);
59
+ }
60
+ finally {
61
+ closeSync(fd);
62
+ }
63
+ return v.heartbeat;
64
+ }
65
+ const hbCache = new Map();
66
+ /** The newest valid heartbeat (reads only the last 64 KB, and only when the file changed since the last read). */
67
+ export function lastHeartbeat(home) {
68
+ const p = heartbeatPath(home);
69
+ if (!existsSync(p))
70
+ return null;
71
+ const fd = openSync(p, 'r');
72
+ try {
73
+ const st = fstatSync(fd);
74
+ const size = st.size;
75
+ const c = hbCache.get(p);
76
+ if (c && c.size === size && c.mtimeMs === st.mtimeMs)
77
+ return c.hb;
78
+ const n = Math.min(size, TAIL_BYTES);
79
+ const buf = Buffer.alloc(n);
80
+ readSync(fd, buf, 0, n, size - n);
81
+ const lines = buf.toString('utf8').split('\n');
82
+ let best = null;
83
+ for (const l of lines) {
84
+ if (!l.trim())
85
+ continue;
86
+ try {
87
+ const v = validateHeartbeat(JSON.parse(l));
88
+ if (v.ok && (!best || v.heartbeat.at > best.at))
89
+ best = v.heartbeat;
90
+ }
91
+ catch { /* torn or partial line */ }
92
+ }
93
+ hbCache.set(p, { size, mtimeMs: st.mtimeMs, hb: best });
94
+ return best;
95
+ }
96
+ finally {
97
+ closeSync(fd);
98
+ }
99
+ }
100
+ /**
101
+ * How the executor looks now. `ackAt` = the newest ack on the webhook or command hook (the outbox's channel health,
102
+ * this process); `since` = a floor for "last seen" (the run's start: a run is not paused in its first window).
103
+ */
104
+ export function executorHealth(home, o) {
105
+ const hb = lastHeartbeat(home);
106
+ const receipts = o.receipts ?? readReceipts(home).receipts;
107
+ const last = receipts.reduce((a, r) => (!a || r.at > a.at ? r : a), null);
108
+ const cands = [];
109
+ if (hb)
110
+ cands.push([hb.at, 'heartbeat']);
111
+ if (last)
112
+ cands.push([last.at, 'receipt']);
113
+ if (o.ackAt)
114
+ cands.push([o.ackAt, 'ack']);
115
+ if (o.since)
116
+ cands.push([o.since, 'run-start']);
117
+ const best = cands.reduce((a, c) => (!a || c[0] > a[0] ? c : a), null);
118
+ const lags = [];
119
+ if (o.signal)
120
+ for (const r of receipts.slice(-20)) {
121
+ const s = o.signal(r.nonce);
122
+ if (s && s.id === r.signalId)
123
+ lags.push(Math.max(0, (r.at - s.issuedAt) / 1000));
124
+ }
125
+ lags.sort((a, b) => a - b);
126
+ const silentSec = best ? Math.max(0, Math.round((o.now - best[0]) / 1000)) : null;
127
+ const windowMin = o.windowMin ?? null;
128
+ return {
129
+ alive: windowMin === null ? null : silentSec !== null && silentSec <= windowMin * 60,
130
+ windowMin, lastSeenAt: best?.[0] ?? null, lastSeenBy: best?.[1] ?? null, silentSec,
131
+ heartbeat: hb ? { at: hb.at, executor: hb.executor, dryRun: hb.dryRun, lastNonce: hb.lastNonce } : null,
132
+ lastReceipt: last ? { at: last.at, nonce: last.nonce, status: last.status } : null,
133
+ receiptLagSec: lags.length ? Math.round(lags[Math.floor(lags.length / 2)] * 10) / 10 : null,
134
+ };
135
+ }
136
+ const ago = (sec) => (sec < 90 ? `${sec} s` : sec < 5400 ? `${Math.round(sec / 60)} min` : `${Math.round(sec / 3600)} h`);
137
+ export function healthLine(h) {
138
+ const state = h.alive === null ? (h.lastSeenAt ? 'seen' : 'never seen') : h.alive ? 'ALIVE' : 'SILENT';
139
+ const parts = [state];
140
+ if (h.silentSec !== null && h.lastSeenBy && h.lastSeenBy !== 'run-start')
141
+ parts.push(`last ${h.lastSeenBy} ${ago(h.silentSec)} ago`);
142
+ else if (h.lastSeenBy === 'run-start')
143
+ parts.push('nothing heard since the run started');
144
+ if (h.heartbeat)
145
+ parts.push(`${h.heartbeat.executor}${h.heartbeat.dryRun ? ' (dry run)' : ''}`);
146
+ if (h.lastReceipt)
147
+ parts.push(`last receipt #${h.lastReceipt.nonce} ${h.lastReceipt.status}`);
148
+ if (h.receiptLagSec !== null)
149
+ parts.push(`receipt lag ${h.receiptLagSec} s`);
150
+ if (h.windowMin !== null)
151
+ parts.push(`pause after ${h.windowMin} min silent`);
152
+ return parts.join(' · ');
153
+ }
154
+ /**
155
+ * A loopback-only HTTP listener for executors that prefer to POST: /heartbeat (a heartbeat body) and /receipt (a
156
+ * receipt body). With a secret, the `x-dexterm-signature` header (the webhook's scheme) is required. Writes the same
157
+ * files the CLI and the kit append to. Never acts on anything.
158
+ */
159
+ export function startExecutorListener(o) {
160
+ const host = o.host ?? '127.0.0.1';
161
+ if (!['127.0.0.1', '::1', 'localhost'].includes(host))
162
+ return Promise.reject(new Error('the executor listener binds to loopback only'));
163
+ const now = o.now ?? Date.now;
164
+ const server = createServer((req, res) => {
165
+ const reply = (code, body) => { res.writeHead(code, { 'content-type': 'text/plain' }); res.end(body); };
166
+ if (req.method !== 'POST' || (req.url !== '/heartbeat' && req.url !== '/receipt'))
167
+ return reply(404, 'POST /heartbeat or /receipt');
168
+ let body = '';
169
+ let tooBig = false;
170
+ req.setEncoding('utf8');
171
+ req.on('data', (c) => { body += c; if (body.length > 4096) {
172
+ tooBig = true;
173
+ req.destroy();
174
+ } });
175
+ req.on('end', () => {
176
+ if (tooBig)
177
+ return;
178
+ const secret = o.secret?.() ?? null;
179
+ if (secret) {
180
+ const v = verifyBody(secret, req.headers['x-dexterm-signature'], body, now());
181
+ if (!v.ok)
182
+ return reply(401, v.why);
183
+ }
184
+ let raw;
185
+ try {
186
+ raw = JSON.parse(body);
187
+ }
188
+ catch {
189
+ return reply(400, 'not json');
190
+ }
191
+ try {
192
+ if (req.url === '/heartbeat') {
193
+ const v = validateHeartbeat({ ...raw, schema: HEARTBEAT_SCHEMA });
194
+ if (!v.ok)
195
+ return reply(400, v.error);
196
+ appendHeartbeat(o.home, v.heartbeat);
197
+ return reply(204, '');
198
+ }
199
+ const v = validateReceipt(raw);
200
+ if (!v.ok)
201
+ return reply(400, v.error);
202
+ appendReceipt(o.home, v.receipt);
203
+ return reply(204, '');
204
+ }
205
+ catch (e) {
206
+ return reply(500, e.message.slice(0, 80));
207
+ }
208
+ });
209
+ });
210
+ return new Promise((resolve, reject) => {
211
+ server.once('error', reject);
212
+ server.listen(o.port, host, () => {
213
+ const a = server.address();
214
+ resolve({ port: typeof a === 'object' && a ? a.port : o.port, close: () => new Promise((r) => server.close(() => r())) });
215
+ });
216
+ });
217
+ }
@@ -0,0 +1,169 @@
1
+ // dexterm autotrade — the multi-strategy config, `autotrade.json` (task-1529, parallel strategies). PAPER today.
2
+ //
3
+ // $DEXTERM_HOME/autotrade/autotrade.json, schema dexterm-autotrade/1, lists the strategies one runner process runs at
4
+ // the same time on the same live data feed. Each one is a MEMBER with its own id, its own bankroll, its own run
5
+ // ledger, caps, breaker, candidate sessions and spend budget; `global` holds the caps across all of them:
6
+ //
7
+ // { "schema": "dexterm-autotrade/1",
8
+ // "strategies": [
9
+ // { "id": "moon-a", "preset": "moonbag", "bankroll": { "usd": 500 } },
10
+ // { "id": "yolo", "preset": "my-yolo", "bankroll": { "sol": 1 }, "caps": { "modelUsdPerDay": 2, "maxSessions": 1 } },
11
+ // { "id": "dip", "file": "/abs/path/dip.json", "bankroll": { "usd": 200 }, "enabled": false } ],
12
+ // "global": { "maxConcurrentSessions": 3, "modelUsdPerDay": 15, "candidateSessionsPerDay": 60 } }
13
+ //
14
+ // `preset` names a shipped preset or one of the user's own strategies (~/.dexterm/strategies); `file` is an absolute
15
+ // path to a strategy file. Member caps can only TIGHTEN: the stricter of the strategy, the member caps, runner.json
16
+ // and the global caps applies, so a shared config can never loosen a strategy. `mode` is "paper" (the default);
17
+ // "live" is refused (task-1571: dexterm holds no key and places no live order).
18
+ //
19
+ // Paper ACCOUNTS (task-1538): `accounts` lists shared simulated wallets, and a strategy with `account` trades from
20
+ // that wallet's cash instead of its own bankroll, next to every other strategy on it (paper-accounts.ts). One strategy
21
+ // is not tied to one account: `"account": ["main", "yolo"]` runs it once per account, as members `<id>-main` and
22
+ // `<id>-yolo` (each its own run, ledger, positions and caps):
23
+ //
24
+ // "accounts": [ { "id": "main", "bankroll": { "usd": 1000 } }, { "id": "yolo", "bankroll": { "sol": 1 } } ],
25
+ // "strategies": [ { "id": "moon", "preset": "moonbag", "account": "main" },
26
+ // { "id": "scalp", "preset": "scalp", "account": ["main", "yolo"] } ]
27
+ //
28
+ // A member on an account takes no `bankroll` of its own (the account holds the money). Accounts are paper only.
29
+ //
30
+ // The file is user data: no login, no key, no token, no wallet address (a paper account is a name and a starting
31
+ // balance). Like runner.json, it is machine-local.
32
+ import { existsSync, readFileSync } from 'node:fs';
33
+ import { join } from 'node:path';
34
+ import { validate } from '../../custom/src/index.js';
35
+ import { STRATEGY_ID } from "./store.js";
36
+ import { MAX_PAPER_ACCOUNTS } from "./paper-accounts.js";
37
+ import { strategyHash } from "./strategy.js";
38
+ export const FLEET_CONFIG_SCHEMA = 'dexterm-autotrade/1';
39
+ export const fleetConfigPath = (home) => join(home, 'autotrade', 'autotrade.json');
40
+ /** How many strategies one process runs at most (each one is a book, a ledger and up to its own session cap). */
41
+ export const MAX_MEMBERS = 8;
42
+ export const DEFAULT_GLOBAL_CAPS = { maxConcurrentSessions: 3, modelUsdPerDay: 20, candidateSessionsPerDay: 96, positionSessionsPerDay: 96, turnsPerDay: 4000 };
43
+ const num = (min, max, int = false) => ({ type: 'number', min, max, int });
44
+ const CAPS = { type: 'object', additional: false, props: {
45
+ maxSessions: num(1, 8, true), modelUsdPerDay: num(0, 10_000), candidateSessionsPerDay: num(0, 10_000, true), positionSessionsPerDay: num(0, 10_000, true), turnsPerDay: num(0, 1_000_000, true),
46
+ } };
47
+ const BANKROLL = { oneOf: [
48
+ { type: 'object', additional: false, required: ['usd'], props: { usd: num(1, 1e7) } },
49
+ { type: 'object', additional: false, required: ['sol'], props: { sol: num(0.001, 1e5) } },
50
+ ] };
51
+ const ACCOUNT_REF = { oneOf: [
52
+ { type: 'string', pattern: STRATEGY_ID.source },
53
+ { type: 'array', max: MAX_PAPER_ACCOUNTS, items: { type: 'string', pattern: STRATEGY_ID.source } },
54
+ ] };
55
+ const SCHEMA = {
56
+ type: 'object', additional: false, required: ['schema', 'strategies'],
57
+ props: {
58
+ schema: { type: 'string', enum: [FLEET_CONFIG_SCHEMA] },
59
+ strategies: { type: 'array', max: MAX_MEMBERS, items: {
60
+ type: 'object', additional: false, required: ['id'],
61
+ props: {
62
+ id: { type: 'string', pattern: STRATEGY_ID.source },
63
+ preset: { type: 'string', pattern: STRATEGY_ID.source },
64
+ file: { type: 'string', max: 500 },
65
+ bankroll: BANKROLL,
66
+ account: ACCOUNT_REF,
67
+ mode: { type: 'string', enum: ['paper', 'live'] },
68
+ enabled: { type: 'boolean' },
69
+ caps: CAPS,
70
+ },
71
+ } },
72
+ global: { type: 'object', additional: false, props: {
73
+ maxConcurrentSessions: num(1, 32, true), modelUsdPerDay: num(0, 10_000), candidateSessionsPerDay: num(0, 10_000, true), positionSessionsPerDay: num(0, 10_000, true), turnsPerDay: num(0, 1_000_000, true),
74
+ } },
75
+ solUsd: num(1, 100_000),
76
+ accounts: { type: 'array', max: MAX_PAPER_ACCOUNTS, items: {
77
+ type: 'object', additional: false, required: ['id', 'bankroll'],
78
+ props: { id: { type: 'string', pattern: STRATEGY_ID.source }, bankroll: BANKROLL },
79
+ } },
80
+ },
81
+ };
82
+ /** Validate a parsed autotrade.json and fill the defaults. */
83
+ export function parseFleetConfig(doc, path = 'autotrade.json', o = {}) {
84
+ const errs = validate(SCHEMA, doc, '');
85
+ if (errs.length)
86
+ return { ok: false, errors: errs.map((e) => `${e.path || '/'} ${e.msg}`), path };
87
+ const d = doc;
88
+ const errors = [];
89
+ const accounts = d.accounts ?? [];
90
+ const accountIds = new Set();
91
+ accounts.forEach((a, i) => { if (accountIds.has(a.id))
92
+ errors.push(`/accounts/${i}/id ${a.id} is used twice (every paper account needs its own id)`); accountIds.add(a.id); });
93
+ const seen = new Set();
94
+ const strategies = [];
95
+ d.strategies.forEach((m, i) => {
96
+ const at = `/strategies/${i}`;
97
+ if (!!m.preset === !!m.file)
98
+ errors.push(`${at} needs exactly one of preset or file`);
99
+ if (m.file && !m.file.startsWith('/'))
100
+ errors.push(`${at}/file must be an absolute path`);
101
+ if (m.mode === 'live' && !o.liveAvailable)
102
+ errors.push(`${at}/mode live execution is not available for parallel strategies in this build; every strategy runs on paper`);
103
+ const refs = m.account === undefined ? [] : Array.isArray(m.account) ? m.account : [m.account];
104
+ if (m.account !== undefined) {
105
+ if (!refs.length)
106
+ errors.push(`${at}/account lists no paper account`);
107
+ if (new Set(refs).size !== refs.length)
108
+ errors.push(`${at}/account names an account twice`);
109
+ for (const a of refs)
110
+ if (!accountIds.has(a))
111
+ errors.push(`${at}/account ${a} is not a paper account in /accounts`);
112
+ if (m.bankroll)
113
+ errors.push(`${at}/bankroll a strategy on a paper account trades from the account's balance; drop its own bankroll`);
114
+ if (m.mode === 'live')
115
+ errors.push(`${at}/account paper accounts hold simulated cash only; a live strategy cannot use one`);
116
+ }
117
+ const base = { ...(m.preset ? { preset: m.preset } : {}), ...(m.file ? { file: m.file } : {}), ...(m.bankroll ? { bankroll: m.bankroll } : {}), mode: m.mode ?? 'paper', enabled: m.enabled ?? true, caps: m.caps ?? {} };
118
+ // A list of accounts runs the same strategy once per account: one member each, `<id>-<account>`.
119
+ const ids = Array.isArray(m.account) ? m.account.map((a) => ({ id: `${m.id}-${a}`, account: a })) : [{ id: m.id, account: refs[0] }];
120
+ for (const x of ids) {
121
+ if (!STRATEGY_ID.test(x.id))
122
+ errors.push(`${at}/account ${x.id} (strategy id + "-" + account id) is not a valid member id (lowercase, at most 32 characters): shorten one of them`);
123
+ if (seen.has(x.id))
124
+ errors.push(`${at}/id ${x.id} is used twice (every strategy in the list needs its own id)`);
125
+ seen.add(x.id);
126
+ strategies.push({ id: x.id, ...base, ...(x.account ? { account: x.account } : {}), ...(Array.isArray(m.account) ? { entry: m.id } : {}) });
127
+ }
128
+ });
129
+ if (!strategies.length)
130
+ errors.push('/strategies lists no strategy');
131
+ if (strategies.length > MAX_MEMBERS)
132
+ errors.push(`/strategies ${strategies.length} strategy runs (after one per listed account); at most ${MAX_MEMBERS} run at once`);
133
+ if (errors.length)
134
+ return { ok: false, errors, path };
135
+ return { ok: true, config: { schema: FLEET_CONFIG_SCHEMA, strategies, global: { ...DEFAULT_GLOBAL_CAPS, ...d.global }, ...(accounts.length ? { accounts: accounts.map((a) => ({ id: a.id, bankroll: a.bankroll })) } : {}), ...(d.solUsd ? { solUsd: d.solUsd } : {}) }, path };
136
+ }
137
+ export function loadFleetConfig(home, file, o = {}) {
138
+ const path = file ?? fleetConfigPath(home);
139
+ if (!existsSync(path))
140
+ return { ok: false, missing: true, path, errors: [`no ${path}: list the strategies to run at once there (dexterm autotrade run --all --help)`] };
141
+ let doc;
142
+ try {
143
+ doc = JSON.parse(readFileSync(path, 'utf8'));
144
+ }
145
+ catch (e) {
146
+ return { ok: false, errors: [`not JSON: ${e.message}`], path };
147
+ }
148
+ return parseFleetConfig(doc, path, o);
149
+ }
150
+ /**
151
+ * A member's strategy: the resolved strategy under the member's id, with the member's bankroll, re-hashed so every
152
+ * ledger line names the exact rules it ran (two members on one preset with different bankrolls hash differently).
153
+ * The base strategy's id / version / hash stay in `basedOn` (the member's first ledger line).
154
+ */
155
+ export function memberStrategy(base, m, startUsd) {
156
+ const { hash: _h, ...rest } = base;
157
+ const body = { ...rest, id: m.id, bankroll: { ...base.bankroll, ...(startUsd !== null ? { startUsd } : {}), source: 'fixed' } };
158
+ return { strategy: { ...body, hash: strategyHash(body) }, basedOn: { id: base.id, version: base.version, hash: base.hash } };
159
+ }
160
+ /** The bankroll in USD (a SOL bankroll at the given SOL price), or null when the strategy's own startUsd applies. */
161
+ export function bankrollUsd(b, solUsd) {
162
+ if (!b)
163
+ return { usd: null };
164
+ if ('usd' in b)
165
+ return { usd: b.usd };
166
+ if (!solUsd || !(solUsd > 0))
167
+ return { usd: null, error: 'a SOL bankroll needs the SOL price (live: read at start; a replay: "solUsd" in autotrade.json or --sol-usd)' };
168
+ return { usd: Math.round(b.sol * solUsd * 100) / 100 };
169
+ }