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,312 @@
1
+ // dexterm autotrade — the trade-fire delivery channels and their config (task-1645, 0.2.0 card A).
2
+ //
3
+ // The user enables any of these, in ONE place: the `signals` block of $DEXTERM_HOME/connectors.json, next to the
4
+ // Telegram setup (`dexterm autotrade signal channels …`, shown by `dexterm telegram status` too). No secret is in
5
+ // the file: the webhook's HMAC secret lives in the OS keychain store, like the bot token.
6
+ //
7
+ // webhook POST the signal line to a loopback URL (http://127.0.0.1|localhost|[::1]) or a user https URL.
8
+ // Headers: x-dexterm-signature (HMAC, signal.ts), x-dexterm-nonce, idempotency-key. Redirects are an
9
+ // error. 2xx = ack, 409 = duplicate (ack), 408/425/429 and 5xx/network = retry, other 4xx = rejected.
10
+ // cmd `--signal-cmd`: an argv (absolute program path, no shell, no PATH lookup); the signal line on stdin;
11
+ // a minimal environment (no dexterm secret, DEXTERM_SIGNAL_SIGNATURE when an HMAC secret is set).
12
+ // Exit 0 = ack, 2 = rejected, 3 = duplicate (ack), anything else or a 10 s timeout = retry.
13
+ // jsonl append the line to a stream file (default autotrade/real/signals.jsonl, 0600); an executor tails it.
14
+ // task-1654: `signed` (opt-in, needs the HMAC secret) writes each line as an envelope instead,
15
+ // {"schema":"dexterm-trade-fire-envelope/1","signature":"t=…,v1=…","signal":"<the exact signal line>"},
16
+ // so a tailing executor can verify the HMAC like the webhook's.
17
+ // mcp append to autotrade/real/mcp-feed.jsonl, which the MCP tool `trade_fire_feed` reads (status only).
18
+ // telegram the human-visible copy to the paired chat (the bot cannot be the executor's input: a bot cannot read
19
+ // another bot's messages). Never sufficient on its own for real mode.
20
+ import { spawn } from 'node:child_process';
21
+ import { closeSync, fsyncSync, mkdirSync, openSync, writeSync } from 'node:fs';
22
+ import { dirname, isAbsolute, join } from 'node:path';
23
+ import { chooseSecretStore, secretStore, SIGNAL_HMAC_ITEM } from "./chat/secrets.js";
24
+ import { loadConnectors, saveConnectors, CONNECTORS_SCHEMA, defaultTelegram } from "./chat/config.js";
25
+ import { inert, shortMint, usdShort } from "./chat/format.js";
26
+ import { realDir } from "./outbox.js";
27
+ import { clampTtl, signBody, SIGNAL_TTL_DEFAULT_SEC } from "./signal.js";
28
+ export const defaultSignalsConfig = () => ({
29
+ webhook: { enabled: false, url: null, secretStore: null }, cmd: { enabled: false, argv: [] }, jsonl: { enabled: false, path: null, signed: false },
30
+ mcp: { enabled: false }, telegram: { enabled: true }, ttlSec: SIGNAL_TTL_DEFAULT_SEC, executor: { listenPort: null },
31
+ });
32
+ export const streamPathDefault = (home) => join(realDir(home), 'signals.jsonl');
33
+ export const mcpFeedPath = (home) => join(realDir(home), 'mcp-feed.jsonl');
34
+ const LOOPBACK = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
35
+ /** null when the URL may receive signals: loopback http(s), or https anywhere. No credentials in the URL. */
36
+ export function webhookUrlProblem(u) {
37
+ let url;
38
+ try {
39
+ url = new URL(u);
40
+ }
41
+ catch {
42
+ return 'not a URL';
43
+ }
44
+ if (url.username || url.password)
45
+ return 'credentials in a URL are refused (the HMAC secret lives in the keychain)';
46
+ if (url.protocol === 'https:')
47
+ return null;
48
+ if (url.protocol === 'http:' && LOOPBACK.has(url.hostname))
49
+ return null;
50
+ return 'use https://, or http:// to 127.0.0.1 / localhost / [::1] only';
51
+ }
52
+ export function argvProblem(argv) {
53
+ if (!Array.isArray(argv) || argv.length < 1 || argv.length > 16)
54
+ return 'the command is an argv array of 1 to 16 strings';
55
+ if (!argv.every((a) => typeof a === 'string' && a.length > 0 && a.length <= 512 && !a.includes('\0')))
56
+ return 'every argv item is a non-empty string (512 chars max)';
57
+ if (!isAbsolute(argv[0]))
58
+ return 'the program is an absolute path (no shell, no PATH lookup)';
59
+ return null;
60
+ }
61
+ /** Read the `signals` block; anything invalid is turned off (fail closed), with the reasons. */
62
+ export function parseSignalsConfig(raw) {
63
+ const c = defaultSignalsConfig();
64
+ const problems = [];
65
+ const r = (raw && typeof raw === 'object' ? raw : {});
66
+ const w = (r.webhook ?? {});
67
+ if (typeof w.url === 'string') {
68
+ const p = webhookUrlProblem(w.url);
69
+ if (p)
70
+ problems.push(`webhook: ${p}`);
71
+ else
72
+ c.webhook.url = w.url;
73
+ }
74
+ c.webhook.enabled = w.enabled === true && c.webhook.url !== null;
75
+ c.webhook.secretStore = w.secretStore === 'keychain' || w.secretStore === 'libsecret' || w.secretStore === 'file' ? w.secretStore : null;
76
+ const m = (r.cmd ?? {});
77
+ if (m.argv !== undefined) {
78
+ const p = argvProblem(m.argv);
79
+ if (p)
80
+ problems.push(`cmd: ${p}`);
81
+ else
82
+ c.cmd.argv = [...m.argv];
83
+ }
84
+ c.cmd.enabled = m.enabled === true && c.cmd.argv.length > 0;
85
+ const j = (r.jsonl ?? {});
86
+ if (typeof j.path === 'string') {
87
+ if (isAbsolute(j.path))
88
+ c.jsonl.path = j.path;
89
+ else
90
+ problems.push('jsonl: the stream path must be absolute');
91
+ }
92
+ c.jsonl.enabled = j.enabled === true;
93
+ c.jsonl.signed = j.signed === true;
94
+ const ex = (r.executor ?? {});
95
+ if (ex.listenPort !== undefined && ex.listenPort !== null) {
96
+ const n = Number(ex.listenPort);
97
+ if (Number.isInteger(n) && n >= 1024 && n <= 65535)
98
+ c.executor.listenPort = n;
99
+ else
100
+ problems.push('executor: listenPort must be a whole number from 1024 to 65535');
101
+ }
102
+ c.mcp.enabled = (r.mcp ?? {}).enabled === true;
103
+ c.telegram.enabled = (r.telegram ?? {}).enabled !== false;
104
+ c.ttlSec = clampTtl(typeof r.ttlSec === 'number' ? r.ttlSec : SIGNAL_TTL_DEFAULT_SEC);
105
+ return { config: c, problems };
106
+ }
107
+ /** The `signals` block of connectors.json (a missing file or block = the defaults: only the Telegram copy). */
108
+ export function loadSignalsConfig(home) {
109
+ const c = loadConnectors(home);
110
+ if (!c.ok)
111
+ return { config: { ...defaultSignalsConfig(), telegram: { enabled: false } }, problems: [c.error] };
112
+ return parseSignalsConfig(c.config.signals);
113
+ }
114
+ /** Write the `signals` block back, keeping the Telegram setup as it is. */
115
+ export function saveSignalsConfig(home, sc) {
116
+ const c = loadConnectors(home);
117
+ if (!c.ok)
118
+ throw new Error(c.error);
119
+ saveConnectors(home, { ...(c.ok ? c.config : { schema: CONNECTORS_SCHEMA, telegram: defaultTelegram() }), signals: sc });
120
+ }
121
+ export function enabledChannels(c) {
122
+ return [c.webhook.enabled ? 'webhook' : null, c.cmd.enabled ? 'cmd' : null, c.jsonl.enabled ? 'jsonl' : null, c.mcp.enabled ? 'mcp' : null, c.telegram.enabled ? 'telegram' : null].filter((x) => !!x);
123
+ }
124
+ export class WebhookChannel {
125
+ name = 'webhook';
126
+ o;
127
+ constructor(o) {
128
+ const p = webhookUrlProblem(o.url);
129
+ if (p)
130
+ throw new Error(`webhook: ${p}`);
131
+ this.o = o;
132
+ }
133
+ async deliver(s, line) {
134
+ const headers = { 'content-type': 'application/json', 'x-dexterm-nonce': String(s.nonce), 'idempotency-key': s.id, 'user-agent': 'dexterm-trade-fire/1' };
135
+ const secret = this.o.secret();
136
+ if (secret)
137
+ headers['x-dexterm-signature'] = signBody(secret, line, (this.o.now ?? Date.now)());
138
+ const ac = new AbortController();
139
+ const timer = setTimeout(() => ac.abort(), this.o.timeoutMs ?? 5000);
140
+ try {
141
+ const f = this.o.fetch ?? globalThis.fetch;
142
+ const res = await f(this.o.url, { method: 'POST', headers, body: line, redirect: 'error', signal: ac.signal });
143
+ if (res.status >= 200 && res.status < 300)
144
+ return { ok: true };
145
+ if (res.status === 409)
146
+ return { ok: true, duplicate: true };
147
+ if (res.status === 408 || res.status === 425 || res.status === 429 || res.status >= 500)
148
+ return { ok: false, retry: true, why: `http ${res.status}` };
149
+ return { ok: false, retry: false, why: `http ${res.status}` };
150
+ }
151
+ catch (e) {
152
+ return { ok: false, retry: true, why: ac.signal.aborted ? 'timeout' : e.message.slice(0, 120) };
153
+ }
154
+ finally {
155
+ clearTimeout(timer);
156
+ }
157
+ }
158
+ }
159
+ // ───────────────────────────── command hook ─────────────────────────────
160
+ export class CommandChannel {
161
+ name = 'cmd';
162
+ o;
163
+ constructor(o) {
164
+ const p = argvProblem(o.argv);
165
+ if (p)
166
+ throw new Error(`signal-cmd: ${p}`);
167
+ this.o = o;
168
+ }
169
+ deliver(s, line) {
170
+ const secret = this.o.secret?.() ?? null;
171
+ // A minimal environment: no token, no RPC URL, no DEXTERM_* setting leaks into the user's executor.
172
+ const env = { PATH: process.env.PATH ?? '/usr/bin:/bin', LANG: process.env.LANG ?? 'C.UTF-8', DEXTERM_SIGNAL_NONCE: String(s.nonce), DEXTERM_SIGNAL_ID: s.id };
173
+ if (process.env.HOME)
174
+ env.HOME = process.env.HOME;
175
+ if (process.env.SystemRoot)
176
+ env.SystemRoot = process.env.SystemRoot;
177
+ if (secret)
178
+ env.DEXTERM_SIGNAL_SIGNATURE = signBody(secret, line, (this.o.now ?? Date.now)());
179
+ return new Promise((done) => {
180
+ let settled = false;
181
+ const finish = (r) => { if (!settled) {
182
+ settled = true;
183
+ clearTimeout(timer);
184
+ done(r);
185
+ } };
186
+ let child;
187
+ try {
188
+ child = spawn(this.o.argv[0], this.o.argv.slice(1), { shell: false, env, stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true });
189
+ }
190
+ catch (e) {
191
+ done({ ok: false, retry: true, why: `spawn: ${e.message.slice(0, 100)}` });
192
+ return;
193
+ }
194
+ const timer = setTimeout(() => { child.kill('SIGKILL'); finish({ ok: false, retry: true, why: 'timeout' }); }, this.o.timeoutMs ?? 10_000);
195
+ child.on('error', (e) => finish({ ok: false, retry: true, why: `spawn: ${e.message.slice(0, 100)}` }));
196
+ child.on('close', (code) => finish(code === 0 ? { ok: true } : code === 3 ? { ok: true, duplicate: true } : code === 2 ? { ok: false, retry: false, why: 'rejected (exit 2)' } : { ok: false, retry: true, why: `exit ${code}` }));
197
+ child.stdin.on('error', () => { });
198
+ child.stdin.end(`${line}\n`);
199
+ });
200
+ }
201
+ }
202
+ // ───────────────────────────── JSONL stream / MCP feed ─────────────────────────────
203
+ export const ENVELOPE_SCHEMA = 'dexterm-trade-fire-envelope/1';
204
+ export class JsonlChannel {
205
+ name;
206
+ path;
207
+ signer;
208
+ /** task-1654: `signed` = HMAC envelope lines (a secret must be set, else the line is retried until one is). */
209
+ constructor(path, name = 'jsonl', signed) { this.path = path; this.name = name; this.signer = signed ? { secret: signed.secret, now: signed.now ?? Date.now } : null; }
210
+ async deliver(_s, line) {
211
+ let out = line;
212
+ if (this.signer) {
213
+ const secret = this.signer.secret();
214
+ if (!secret)
215
+ return { ok: false, retry: true, why: 'signed stream: no HMAC secret set (dexterm autotrade signal secret generate --out FILE)' };
216
+ out = JSON.stringify({ schema: ENVELOPE_SCHEMA, signature: signBody(secret, line, this.signer.now()), signal: line });
217
+ }
218
+ try {
219
+ mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
220
+ const fd = openSync(this.path, 'a', 0o600);
221
+ try {
222
+ writeSync(fd, `${out}\n`);
223
+ fsyncSync(fd);
224
+ }
225
+ finally {
226
+ closeSync(fd);
227
+ }
228
+ return { ok: true };
229
+ }
230
+ catch (e) {
231
+ return { ok: false, retry: true, why: e.code ?? e.message.slice(0, 80) };
232
+ }
233
+ }
234
+ }
235
+ // ───────────────────────────── Telegram copy ─────────────────────────────
236
+ /** The human-visible copy (plain text lines, the LIVE badge). The symbol is already sanitized; it still goes through inert(). */
237
+ export function telegramLines(s, now = Date.now()) {
238
+ const left = Math.max(0, Math.round((s.expiresAt - now) / 1000));
239
+ const strat = `${inert(s.strategy.id, 32)} v${inert(s.strategy.version, 12)}`;
240
+ if (s.type === 'halted') {
241
+ // task-1679 (0.2.0 decision): open positions at the end of a real run must be impossible to miss.
242
+ const open = s.open ?? [];
243
+ return [
244
+ `LIVE TRADE FIRE · HALTED · ${inert(s.reason, 40)}`,
245
+ `${s.outstanding.length} outstanding signal(s) · run ${inert(s.runId.slice(-12), 16)} · account ${inert(s.accountId, 32)} · nonce ${s.nonce}`,
246
+ ...(open.length ? [`${open.length} position(s): ${open.slice(0, 10).map(shortMint).join(', ')}${open.length > 10 ? ` +${open.length - 10} more` : ''}`, 'these positions are still open: manage them in your executor', 'until the next real run of this strategy, dexterm sends NO signal for them, sells included'] : []),
247
+ 'no more signals from this run',
248
+ ];
249
+ }
250
+ if (s.type === 'test')
251
+ return ['LIVE TRADE FIRE · TEST', `nonce ${s.nonce} · account ${inert(s.accountId, 32)} · never trade on this`];
252
+ const size = s.side === 'buy' ? `${s.sizeSol} SOL (${usdShort(s.sizeUsd)}, ${s.pctOfBook}% of book)` : `${Math.round((s.sellFraction ?? 1) * 1000) / 10}% of the position (~${s.sizeSol} SOL)`;
253
+ return [
254
+ `LIVE TRADE FIRE · ${s.side.toUpperCase()} "${inert(s.symbol, 16)}" ${shortMint(s.mint)}`,
255
+ `${size}${s.exitReason ? ` · ${inert(s.exitReason, 40)}` : ''}`,
256
+ `${strat} · account ${inert(s.accountId, 32)} · nonce ${s.nonce} · ignore after ${left} s`,
257
+ 'your executor acts on it, not this chat',
258
+ ];
259
+ }
260
+ export class TelegramCopyChannel {
261
+ name = 'telegram';
262
+ send;
263
+ now;
264
+ constructor(send, now = Date.now) { this.send = send; this.now = now; }
265
+ async deliver(s) {
266
+ try {
267
+ await this.send(telegramLines(s, this.now()));
268
+ return { ok: true };
269
+ }
270
+ catch (e) {
271
+ return { ok: false, retry: true, why: e.message.slice(0, 120) };
272
+ }
273
+ }
274
+ }
275
+ // ───────────────────────────── from the config ─────────────────────────────
276
+ /** Where the webhook's HMAC secret is (the store setup recorded, else the best on this machine). */
277
+ export function hmacStore(home, sc, env = process.env) {
278
+ return sc.webhook.secretStore ? secretStore(sc.webhook.secretStore, home, SIGNAL_HMAC_ITEM) : chooseSecretStore(home, env, process.platform, SIGNAL_HMAC_ITEM);
279
+ }
280
+ /**
281
+ * The channel objects for a config. Without a Telegram sender (outside a run) the copy is deferred: it stays pending
282
+ * in the outbox for the next run instead of being dropped. The secret is read once, on first use.
283
+ */
284
+ export function channelsFromConfig(home, sc, o = {}) {
285
+ let cached;
286
+ const store = o.secret ?? hmacStore(home, sc, o.env);
287
+ const secret = () => { if (cached === undefined) {
288
+ try {
289
+ cached = store.get();
290
+ }
291
+ catch {
292
+ cached = null;
293
+ }
294
+ } return cached; };
295
+ const out = [];
296
+ const defer = [];
297
+ if (sc.webhook.enabled && sc.webhook.url)
298
+ out.push(new WebhookChannel({ url: sc.webhook.url, secret, fetch: o.fetch, now: o.now }));
299
+ if (sc.cmd.enabled && sc.cmd.argv.length)
300
+ out.push(new CommandChannel({ argv: sc.cmd.argv, secret, now: o.now }));
301
+ if (sc.jsonl.enabled)
302
+ out.push(new JsonlChannel(sc.jsonl.path ?? streamPathDefault(home), 'jsonl', sc.jsonl.signed ? { secret, now: o.now } : undefined));
303
+ if (sc.mcp.enabled)
304
+ out.push(new JsonlChannel(mcpFeedPath(home), 'mcp'));
305
+ if (sc.telegram.enabled) {
306
+ if (o.telegram)
307
+ out.push(new TelegramCopyChannel(o.telegram, o.now));
308
+ else
309
+ defer.push('telegram');
310
+ }
311
+ return { channels: out, defer };
312
+ }
@@ -0,0 +1,196 @@
1
+ // dexterm autotrade — the LIVE TRADE FIRE signal, `dexterm-trade-fire/1` (task-1645, 0.2.0 card A).
2
+ //
3
+ // A strategy in mode "real" trades exactly like paper (same clamp, exits, monitor and ledger: the paper fill model is
4
+ // the monitor's own view), and every buy or sell it fills also becomes one of these signals for the user's OWN
5
+ // executor (a Telegram trading bot of their choice, or any script). dexterm holds no key, signs no transaction and
6
+ // builds none: a signal is a request the executor may act on, ignore, or reject.
7
+ //
8
+ // type "trade" side buy|sell, mint, sanitized symbol, size in SOL / USD / % of book (a sell also as the fraction
9
+ // of the position), price + mcap + liquidity context, exit reason for a sell, strategy id/version/
10
+ // hash, run id, account id, nonce, idempotency key, issued/expires, `paper: false`
11
+ // type "halted" the run stopped (end, halt, kill, a lost runner): every outstanding signal's nonce, a reason, and
12
+ // `open`: the mints dexterm still counts as held for the account (the next real run of the
13
+ // strategy takes them over and keeps their exits; they still count against the open-position cap)
14
+ // type "test" `dexterm autotrade signal test`: proves the channel; an executor must never trade on it
15
+ //
16
+ // Rules an executor follows (and SignalReceiver below implements, for scripts and the tests' fake executors):
17
+ // 1. verify the HMAC header when a secret is set (`x-dexterm-signature: t=<unix s>,v1=<hex>` over "<t>.<body>")
18
+ // 2. ignore anything past `expiresAt` (the TTL): a late signal is never acted on
19
+ // 3. dedupe by nonce (and the idempotency key `id`): delivery is at-least-once, so a repeat is normal
20
+ // 4. act only on the account id it was set up for
21
+ //
22
+ // Firewall: nothing token-authored rides along except the symbol, reduced to [A-Za-z0-9._$-] (16 chars). No name,
23
+ // no description, no links, no agent text, no paths, no keys. Every other field is an engine number or an id that
24
+ // dexterm validated itself.
25
+ import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
26
+ export const TRADE_FIRE_SCHEMA = 'dexterm-trade-fire/1';
27
+ export const SIGNAL_TTL_DEFAULT_SEC = 60;
28
+ export const SIGNAL_TTL_MIN_SEC = 10;
29
+ export const SIGNAL_TTL_MAX_SEC = 600;
30
+ /** How far a signature's timestamp may sit from the receiver's clock. */
31
+ export const SIGNATURE_TOLERANCE_SEC = 300;
32
+ const SOL_MINT_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
33
+ const ID_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
34
+ export const ACCOUNT_ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
35
+ const RUN_RE = /^[A-Za-z0-9._:-]{1,96}$/;
36
+ const HEX32 = /^[0-9a-f]{32}$/;
37
+ /** The only token text a signal carries: NFKC, then [A-Za-z0-9._$-] only, 16 chars, '?' when nothing is left. */
38
+ export function sanitizeSymbol(s) {
39
+ const t = String(s ?? '').normalize('NFKC').replace(/[^A-Za-z0-9._$-]/g, '').slice(0, 16);
40
+ return t || '?';
41
+ }
42
+ /** An engine reason ("hard-stop", "time_review:no-answer") reduced to [a-z0-9:_-], 40 chars. */
43
+ export function sanitizeReason(s) {
44
+ return String(s ?? '').toLowerCase().replace(/[^a-z0-9:_-]/g, '-').replace(/-+/g, '-').slice(0, 40) || 'unknown';
45
+ }
46
+ export const signalIdOf = (accountId, runId, nonce) => createHash('sha256').update(`${TRADE_FIRE_SCHEMA}|${accountId}|${runId}|${nonce}`).digest('hex').slice(0, 32);
47
+ export const clampTtl = (sec) => Math.min(SIGNAL_TTL_MAX_SEC, Math.max(SIGNAL_TTL_MIN_SEC, Math.round(Number.isFinite(sec) ? sec : SIGNAL_TTL_DEFAULT_SEC)));
48
+ const r6 = (v) => Math.round(v * 1e6) / 1e6;
49
+ const r2 = (v) => Math.round(v * 100) / 100;
50
+ const fin = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
51
+ function base(type, h) {
52
+ const ttlSec = clampTtl(h.ttlSec);
53
+ return {
54
+ schema: TRADE_FIRE_SCHEMA, type, id: signalIdOf(h.accountId, h.runId, h.nonce), nonce: h.nonce, issuedAt: h.now, expiresAt: h.now + ttlSec * 1000, ttlSec,
55
+ runId: h.runId, accountId: h.accountId, strategy: { id: h.strategy.id, version: h.strategy.version, hash: h.strategy.hash.slice(0, 64) }, paper: false,
56
+ };
57
+ }
58
+ /** A trade signal; throws on anything that is not a plain engine value (fail closed: no signal rather than a bad one). */
59
+ export function buildTradeSignal(t, h) {
60
+ if (!SOL_MINT_RE.test(t.mint))
61
+ throw new Error('signal: mint is not a base58 address');
62
+ if (!(t.solUsd > 0))
63
+ throw new Error('signal: no SOL/USD price');
64
+ // A buy states a positive size; a sell's size is the modelled proceeds and can be ~0 (a rug exit must still go out).
65
+ if (t.side === 'buy' ? !(t.sizeUsd > 0) : !(t.sizeUsd >= 0))
66
+ throw new Error('signal: size must be positive');
67
+ const s = {
68
+ ...base('trade', h), type: 'trade', side: t.side, mint: t.mint, symbol: sanitizeSymbol(t.symbol),
69
+ sizeSol: r6(t.sizeUsd / t.solUsd), sizeUsd: r2(t.sizeUsd), pctOfBook: t.bookUsd > 0 ? Math.round((t.sizeUsd / t.bookUsd) * 1000) / 10 : 0,
70
+ sellFraction: t.side === 'sell' ? Math.min(1, Math.max(0, r6(t.sellFraction ?? 1))) : null,
71
+ price: { usd: fin(t.priceUsd), mcap: fin(t.mcap), liqUsd: fin(t.liqUsd), solUsd: r2(t.solUsd) },
72
+ exitReason: t.side === 'sell' ? sanitizeReason(t.exitReason) : null,
73
+ slippageBps: Math.round(t.slippageBps), maxImpactPct: t.maxImpactPct, positionId: t.positionId && /^[A-Za-z0-9._-]{1,48}$/.test(t.positionId) ? t.positionId : null,
74
+ ...(t.model ? { model: { priceUsd: fin(t.model.priceUsd), tokens: fin(t.model.tokens) } } : {}),
75
+ };
76
+ const v = validateSignal(s);
77
+ if (!v.ok)
78
+ throw new Error(`signal: ${v.error}`);
79
+ return s;
80
+ }
81
+ export function buildHaltedSignal(reason, outstanding, h, open = []) {
82
+ return { ...base('halted', h), type: 'halted', reason: sanitizeReason(reason), outstanding: [...new Set(outstanding)].filter((n) => Number.isInteger(n) && n > 0).sort((a, b) => a - b).slice(0, 500), open: [...new Set(open)].filter((m) => SOL_MINT_RE.test(m)).sort().slice(0, 100) };
83
+ }
84
+ export function buildTestSignal(h) {
85
+ return { ...base('test', h), type: 'test', test: true, note: 'channel test: never trade on this' };
86
+ }
87
+ /** One line of JSON, fixed key order (the webhook body, the hook's stdin, the stream line: byte-identical). */
88
+ export const signalLine = (s) => JSON.stringify(s);
89
+ /** Strict shape check: used on replay of the outbox and by receivers. */
90
+ export function validateSignal(x) {
91
+ const s = x;
92
+ if (!s || typeof s !== 'object')
93
+ return { ok: false, error: 'not an object' };
94
+ if (s.schema !== TRADE_FIRE_SCHEMA)
95
+ return { ok: false, error: 'schema' };
96
+ if (s.type !== 'trade' && s.type !== 'halted' && s.type !== 'test')
97
+ return { ok: false, error: 'type' };
98
+ if (!Number.isSafeInteger(s.nonce) || s.nonce < 1)
99
+ return { ok: false, error: 'nonce' };
100
+ if (typeof s.id !== 'string' || !HEX32.test(s.id))
101
+ return { ok: false, error: 'id' };
102
+ if (typeof s.accountId !== 'string' || !ACCOUNT_ID_RE.test(s.accountId))
103
+ return { ok: false, error: 'accountId' };
104
+ if (typeof s.runId !== 'string' || !RUN_RE.test(s.runId))
105
+ return { ok: false, error: 'runId' };
106
+ if (s.id !== signalIdOf(s.accountId, s.runId, s.nonce))
107
+ return { ok: false, error: 'id does not match account/run/nonce' };
108
+ if (s.paper !== false)
109
+ return { ok: false, error: 'paper must be false' };
110
+ if (!fin(s.issuedAt) || !fin(s.expiresAt) || s.expiresAt <= s.issuedAt)
111
+ return { ok: false, error: 'times' };
112
+ const st = s.strategy;
113
+ if (!st || typeof st.id !== 'string' || !ID_RE.test(st.id) || typeof st.version !== 'string' || typeof st.hash !== 'string')
114
+ return { ok: false, error: 'strategy' };
115
+ if (s.type === 'trade') {
116
+ if (s.side !== 'buy' && s.side !== 'sell')
117
+ return { ok: false, error: 'side' };
118
+ if (typeof s.mint !== 'string' || !SOL_MINT_RE.test(s.mint))
119
+ return { ok: false, error: 'mint' };
120
+ if (typeof s.symbol !== 'string' || s.symbol !== sanitizeSymbol(s.symbol))
121
+ return { ok: false, error: 'symbol' };
122
+ if (s.side === 'buy' ? !(fin(s.sizeSol) > 0) || !(fin(s.sizeUsd) > 0) : !(fin(s.sizeSol) >= 0) || !(fin(s.sizeUsd) >= 0))
123
+ return { ok: false, error: 'size' };
124
+ if (s.side === 'sell' && !(fin(s.sellFraction) > 0 && s.sellFraction <= 1))
125
+ return { ok: false, error: 'sellFraction' };
126
+ }
127
+ if (s.type === 'test' && s.test !== undefined && s.test !== true)
128
+ return { ok: false, error: 'test' };
129
+ if (s.type === 'test' && 'mint' in s)
130
+ return { ok: false, error: 'a test signal names no mint' };
131
+ if (s.type === 'trade' && s.model !== undefined && (typeof s.model !== 'object' || s.model === null))
132
+ return { ok: false, error: 'model' };
133
+ if (s.type === 'halted' && s.open !== undefined && (!Array.isArray(s.open) || s.open.length > 100 || !s.open.every((m) => typeof m === 'string' && SOL_MINT_RE.test(m))))
134
+ return { ok: false, error: 'open' };
135
+ return { ok: true, signal: s };
136
+ }
137
+ // ───────────────────────────── HMAC ─────────────────────────────
138
+ /** `t=<unix s>,v1=<hex sha256 hmac of "<t>.<body>">`. */
139
+ export function signBody(secret, body, nowMs) {
140
+ const t = Math.floor(nowMs / 1000);
141
+ return `t=${t},v1=${createHmac('sha256', secret).update(`${t}.${body}`).digest('hex')}`;
142
+ }
143
+ export function verifyBody(secret, header, body, nowMs, toleranceSec = SIGNATURE_TOLERANCE_SEC) {
144
+ const m = /^t=(\d{1,12}),v1=([0-9a-f]{64})$/.exec(String(header ?? ''));
145
+ if (!m)
146
+ return { ok: false, why: 'signature-missing' };
147
+ const t = Number(m[1]);
148
+ if (Math.abs(nowMs / 1000 - t) > toleranceSec)
149
+ return { ok: false, why: 'signature-stale' };
150
+ const want = createHmac('sha256', secret).update(`${t}.${body}`).digest();
151
+ const got = Buffer.from(m[2], 'hex');
152
+ return got.length === want.length && timingSafeEqual(got, want) ? { ok: true } : { ok: false, why: 'signature-bad' };
153
+ }
154
+ /**
155
+ * What a well-behaved executor does with an incoming signal: signature (when a secret is set), shape, account, TTL,
156
+ * replay (nonce and id). A duplicate is reported as such so a transport can ack it (at-least-once delivery).
157
+ * Exported for users' scripts and for the fake executors of the tests; dexterm itself never executes anything.
158
+ */
159
+ export class SignalReceiver {
160
+ seen = new Set();
161
+ maxNonce = 0;
162
+ o;
163
+ constructor(o) { this.o = o; }
164
+ receive(body, signature) {
165
+ const now = (this.o.now ?? Date.now)();
166
+ if (this.o.secret) {
167
+ const v = verifyBody(this.o.secret, signature, body, now);
168
+ if (!v.ok)
169
+ return { ok: false, why: v.why };
170
+ }
171
+ let raw;
172
+ try {
173
+ raw = JSON.parse(body);
174
+ }
175
+ catch {
176
+ return { ok: false, why: 'not-json' };
177
+ }
178
+ const v = validateSignal(raw);
179
+ if (!v.ok)
180
+ return { ok: false, why: `invalid:${v.error}` };
181
+ const s = v.signal;
182
+ if (s.accountId !== this.o.accountId)
183
+ return { ok: false, why: 'wrong-account', signal: s };
184
+ if (this.seen.has(s.id) || this.seen.has(`n${s.nonce}`))
185
+ return { ok: false, why: 'duplicate', signal: s };
186
+ if (now > s.expiresAt)
187
+ return { ok: false, why: 'expired', signal: s };
188
+ // A nonce at or below one already acted on (that is not a duplicate of it) is a replay of an old signal.
189
+ if (s.nonce <= this.maxNonce && s.type !== 'halted')
190
+ return { ok: false, why: 'nonce-replay', signal: s };
191
+ this.seen.add(s.id);
192
+ this.seen.add(`n${s.nonce}`);
193
+ this.maxNonce = Math.max(this.maxNonce, s.nonce);
194
+ return { ok: true, signal: s };
195
+ }
196
+ }