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
@@ -4,8 +4,9 @@
4
4
  // (observe snapshots + feed membership, the 1 h/2 h/6 h/24 h/72 h outcome sweeps, labels, retention) with no
5
5
  // TUI, to be driven by a user-level launchd LaunchAgent on the Mac mini.
6
6
  //
7
- // dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--no-newpairs] [--db PATH] [--verbose]
8
- // dexterm record install [--interval S] [--max-db-mb N] [--load] [--dry-run]
7
+ // dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--retention-mb N] [--retention-days D]
8
+ // [--focus-ticks] [--no-newpairs] [--db PATH] [--verbose]
9
+ // dexterm record install [--interval S] [--max-db-mb N] [--retention-mb N] [--retention-days D] [--focus-ticks] [--load] [--dry-run]
9
10
  // dexterm record uninstall [--unload]
10
11
  // dexterm record status [--db PATH]
11
12
  //
@@ -15,13 +16,18 @@
15
16
  // the data layer; only the provider name is ever logged). Modest CPU: one feed refresh per `--interval` (60 s
16
17
  // default) and recorder maintenance at most every `--maintain` seconds. A soft disk cap pauses OBSERVATION
17
18
  // (the growth source) above `--max-db-mb` while labels + retention keep running, so the DB cannot run away.
19
+ // task-1504 (AUTOTRADE-SPEC §3, §10 Q4), both opt-in so an installed collector behaves as before until it is
20
+ // re-installed with them: `--retention-mb N` replaces that pause with eviction of the oldest history (observation
21
+ // never pauses), `--retention-days D` is the age rule for snapshot rows, and `--focus-ticks` runs the autotrade
22
+ // tick lane (5–10 s DexScreener samples of watchlist / held / candidate / open coins, plus 1m candles).
18
23
  import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
19
24
  import { spawnSync } from 'node:child_process';
20
25
  import { dirname, join, resolve } from 'node:path';
21
26
  import { fileURLToPath } from 'node:url';
22
27
  import { defaultDbPath, resolveRpcUrl, extraRpcUrls, redactSecrets } from '../../data/src/index.js';
23
28
  import { PumpPortalClient } from '../../data/src/index.js';
24
- import { Recorder, recorderEnabled } from '../../recorder/src/index.js';
29
+ import { Recorder, recorderEnabled, dbFocus, focusFilePath, readFocusFile, DEFAULT_TICKS } from '../../recorder/src/index.js';
30
+ import { dextermHome } from "./boot-state.js";
25
31
  import { createLiveFeed } from "./feed.js";
26
32
  import { NewPairsLive } from "./live/index.js";
27
33
  /** The LaunchAgent label (reverse-DNS). One collector per user. */
@@ -41,7 +47,17 @@ export function buildLiveEngine(env, opts) {
41
47
  dbPath: opts.dbPath, rpcUrl: rpc.url, rpcFallbackUrl: rpc.fallbackUrl, rpcPublicPool: true, rpcExtraUrls: extraRpcUrls(env),
42
48
  onRequest: opts.verbose ? (req, meta, o) => opts.onLog?.(` ${meta.source.padEnd(14)} ${String(o.status ?? 'ERR').padStart(3)} ${String(o.latencyMs).padStart(5)}ms ${redactSecrets(req.url).slice(0, 110)}\n`) : undefined,
43
49
  });
44
- const recorder = new Recorder({ path: opts.dbPath, onLog: opts.verbose ? opts.onLog : undefined });
50
+ // Focus coins for the tick lane: the watchlist + paper positions in this DB, and whatever the autotrade runner /
51
+ // the app publish in the focus file. Read on every poll (two indexed reads + one small file).
52
+ const focusFile = focusFilePath(dextermHome(env));
53
+ const recorder = new Recorder({
54
+ path: opts.dbPath, onLog: opts.verbose ? opts.onLog : undefined,
55
+ // The cap reclaims the data layer's expired cache entries (its own rule: a day past their TTL) before it evicts
56
+ // any history; `feed` is built above, so the hook can reach it.
57
+ ...(opts.retentionBytes ? { cap: { maxBytes: opts.retentionBytes, reclaim: () => feed.dl.cache.prune() } } : {}),
58
+ ...(opts.retentionDays ? { retention: { columnsDays: opts.retentionDays } } : {}),
59
+ ...(opts.focusTicks ? { ticks: { focus: () => [...dbFocus(recorder.database), ...readFocusFile(focusFile, Date.now())] } } : {}),
60
+ });
45
61
  recorder.attach(feed.dl.store);
46
62
  let newpairs = null;
47
63
  if (!opts.noNewpairs) {
@@ -66,6 +82,8 @@ export function buildLiveEngine(env, opts) {
66
82
  recorder.prune();
67
83
  }
68
84
  catch { /* logged inside; never fatal */ } },
85
+ enforceCap() { return recorder.enforceCap(); },
86
+ ...(opts.focusTicks ? { pollTicks: () => recorder.pollTicks(dl) } : {}),
69
87
  dbBytes() {
70
88
  try {
71
89
  const db = recorder.database;
@@ -101,7 +119,7 @@ export function buildLiveEngine(env, opts) {
101
119
  * Never throws out of the loop: an engine error is logged and the loop continues to the next tick.
102
120
  */
103
121
  export async function runCollectorLoop(engine, opts) {
104
- const res = { ticks: 0, observePasses: 0, rowsWritten: 0, maintainPasses: 0, cappedTicks: 0 };
122
+ const res = { ticks: 0, observePasses: 0, rowsWritten: 0, maintainPasses: 0, cappedTicks: 0, evictions: 0 };
105
123
  let lastMaintain = -Infinity;
106
124
  for (let i = 0; i < opts.ticks && !opts.aborted(); i++) {
107
125
  if (i > 0) {
@@ -111,7 +129,19 @@ export async function runCollectorLoop(engine, opts) {
111
129
  }
112
130
  res.ticks++;
113
131
  let over = false;
114
- if (opts.maxDbBytes !== null) {
132
+ if (opts.capMode === 'evict') {
133
+ try {
134
+ const c = engine.enforceCap?.() ?? null;
135
+ if (c?.evicted)
136
+ res.evictions++;
137
+ if (c?.over)
138
+ opts.onLog?.(`record: DB still at ${(c.usedAfter / 1e6).toFixed(0)} MB used after evicting all but the newest hour (other tables are large); observation continues\n`);
139
+ }
140
+ catch (e) {
141
+ opts.onLog?.(`record: retention failed: ${redactSecrets(e.message)}\n`);
142
+ }
143
+ }
144
+ else if (opts.maxDbBytes !== null) {
115
145
  const b = engine.dbBytes();
116
146
  if (b !== null && b >= opts.maxDbBytes) {
117
147
  engine.prune();
@@ -146,6 +176,29 @@ export async function runCollectorLoop(engine, opts) {
146
176
  }
147
177
  return res;
148
178
  }
179
+ /** task-1504: drive the tick lane every `everyMs` until aborted. Never throws (a failed poll is logged). */
180
+ export async function runTickLoop(engine, opts) {
181
+ const res = { polls: 0, calls: 0, written: 0 };
182
+ if (!engine.pollTicks)
183
+ return res;
184
+ while (!opts.aborted()) {
185
+ try {
186
+ const r = await engine.pollTicks();
187
+ res.polls++;
188
+ res.calls += r.calls;
189
+ res.written += r.written;
190
+ if (r.error)
191
+ opts.onLog?.(`record: tick poll failed: ${redactSecrets(r.error)}\n`);
192
+ }
193
+ catch (e) {
194
+ opts.onLog?.(`record: tick poll failed: ${redactSecrets(e.message)}\n`);
195
+ }
196
+ if (opts.aborted())
197
+ break;
198
+ await opts.sleep(opts.everyMs);
199
+ }
200
+ return res;
201
+ }
149
202
  const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
150
203
  /**
151
204
  * A user-level LaunchAgent (no sudo). KeepAlive so a crash or a reboot-after-login brings it back, RunAtLoad,
@@ -155,7 +208,7 @@ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g,
155
208
  * collector to run; a true pre-login daemon would be a LaunchDaemon under sudo, which this task does not use.
156
209
  */
157
210
  export function renderCollectorPlist(o) {
158
- const args = [o.nodePath, o.binPath, 'record', 'run', '--interval', String(Math.round(o.intervalMs / 1000)), '--max-db-mb', String(o.maxDbMb)];
211
+ const args = [o.nodePath, o.binPath, 'record', 'run', '--interval', String(Math.round(o.intervalMs / 1000)), '--max-db-mb', String(o.maxDbMb), ...(o.extraArgs ?? [])];
159
212
  const argXml = args.map((a) => ` <string>${esc(a)}</string>`).join('\n');
160
213
  return `<?xml version="1.0" encoding="UTF-8"?>
161
214
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -202,10 +255,37 @@ function dataDir(env, db) {
202
255
  function sourceBin() {
203
256
  return fileURLToPath(new URL('../bin/dexterm.mjs', import.meta.url));
204
257
  }
258
+ /** task-1504: the shared `--retention-mb` / `--retention-days` / `--focus-ticks` parser (run + install). */
259
+ function parseRetention(t, next, a) {
260
+ if (t === '--focus-ticks') {
261
+ a.focusTicks = true;
262
+ return null;
263
+ }
264
+ if (t === '--retention-mb') {
265
+ const n = Number(next());
266
+ if (!Number.isFinite(n) || n < 1)
267
+ return '--retention-mb needs a number ≥ 1';
268
+ a.retentionBytes = Math.round(n * 1_000_000);
269
+ return null;
270
+ }
271
+ if (t === '--retention-days') {
272
+ const n = Number(next());
273
+ if (!Number.isFinite(n) || n < 1)
274
+ return '--retention-days needs a number of days ≥ 1';
275
+ a.retentionDays = n;
276
+ return null;
277
+ }
278
+ return undefined;
279
+ }
205
280
  export function parseRun(argv) {
206
- const a = { intervalMs: DEFAULT_INTERVAL_MS, ticks: Infinity, maintainMs: DEFAULT_MAINTAIN_MS, maxDbBytes: DEFAULT_MAX_DB_MB * 1_000_000, noNewpairs: false, db: null, verbose: false };
281
+ const a = { intervalMs: DEFAULT_INTERVAL_MS, ticks: Infinity, maintainMs: DEFAULT_MAINTAIN_MS, maxDbBytes: DEFAULT_MAX_DB_MB * 1_000_000, noNewpairs: false, db: null, verbose: false, retentionBytes: null, retentionDays: null, focusTicks: false };
207
282
  for (let i = 0; i < argv.length; i++) {
208
283
  const t = argv[i];
284
+ const r = parseRetention(t, () => argv[++i], a);
285
+ if (typeof r === 'string')
286
+ return { error: r };
287
+ if (r === null)
288
+ continue;
209
289
  if (t === '--verbose' || t === '-v')
210
290
  a.verbose = true;
211
291
  else if (t === '--no-newpairs')
@@ -248,15 +328,20 @@ export function parseRun(argv) {
248
328
  }
249
329
  export const RECORD_USAGE = `dexterm record: keep the history recorder fed with no TUI open (task-1414).
250
330
 
251
- dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--no-newpairs] [--db PATH] [--verbose]
252
- dexterm record install [--interval S] [--max-db-mb N] [--load] [--dry-run]
331
+ dexterm record [run] [--interval S] [--ticks N | --once] [--maintain S] [--max-db-mb N] [--retention-mb N] [--retention-days D]
332
+ [--focus-ticks] [--no-newpairs] [--db PATH] [--verbose]
333
+ dexterm record install [--interval S] [--max-db-mb N] [--retention-mb N] [--retention-days D] [--focus-ticks] [--load] [--dry-run]
253
334
  dexterm record uninstall [--unload]
254
335
  dexterm record status [--db PATH]
255
336
 
256
337
  run the collector loop: one keyless feed refresh per --interval (default 60 s) feeds the recorder
257
338
  (snapshots + feed membership + new pairs), recorder maintenance (outcome sweeps, labels, retention)
258
339
  every --maintain seconds. --max-db-mb is a soft cap (default 1024): above it observation pauses while
259
- labels + retention keep running. --no-newpairs drops the PumpPortal stream. Ctrl-C stops cleanly
340
+ labels + retention keep running. --retention-mb N replaces that pause: the oldest history is evicted
341
+ to stay under N MB and observation never pauses. --retention-days D drops snapshot rows (sweep rows
342
+ too) older than D days (default 90). --focus-ticks samples watchlist / held / candidate / open coins
343
+ every 5-10 s (DexScreener batches of 30, a daily call ceiling) with 1m candles; autotrade backtests
344
+ read them. --no-newpairs drops the PumpPortal stream. Ctrl-C stops cleanly
260
345
  (labels whatever is due). Requests honour the data layer's rate limits, health gate and sweep budgets.
261
346
  install write a user-level launchd LaunchAgent (no sudo) that runs \`record run\`; prints the launchctl
262
347
  command. --load also runs launchctl (off by default). --dry-run prints the plist, writes nothing.
@@ -300,47 +385,77 @@ async function run(argv, io, deps) {
300
385
  return 2;
301
386
  }
302
387
  // SIGINT/SIGTERM-aware cancellable sleeper (launchd sends SIGTERM to stop the agent).
388
+ // Several loops may sleep at once (the collector + the tick lane), so every sleeper is woken on abort.
303
389
  const stop = (() => {
304
390
  let aborted = false;
305
- let wake = () => { };
391
+ const wakes = new Set();
306
392
  return {
307
393
  get aborted() { return aborted; },
308
- abort() { aborted = true; wake(); },
309
- sleep(ms) { return new Promise((r) => { const t = setTimeout(r, ms); t.unref?.(); wake = () => { clearTimeout(t); r(); }; }); },
394
+ abort() { aborted = true; for (const w of [...wakes])
395
+ w(); },
396
+ sleep(ms) {
397
+ return new Promise((r) => {
398
+ const done = () => { clearTimeout(t); wakes.delete(done); r(); };
399
+ const t = setTimeout(done, ms);
400
+ t.unref?.();
401
+ wakes.add(done);
402
+ });
403
+ },
310
404
  };
311
405
  })();
312
406
  const log = (s) => io.stderr.write(s);
313
- const engine = deps.engine ?? buildLiveEngine(io.env, { dbPath, noNewpairs: parsed.noNewpairs, verbose: parsed.verbose, onLog: log });
407
+ const engine = deps.engine ?? buildLiveEngine(io.env, { dbPath, noNewpairs: parsed.noNewpairs, verbose: parsed.verbose, onLog: log, retentionBytes: parsed.retentionBytes, retentionDays: parsed.retentionDays, focusTicks: parsed.focusTicks });
314
408
  const own = !deps.engine;
315
409
  const onSig = () => { io.stderr.write('\ndexterm record: stopping…\n'); stop.abort(); };
316
410
  if (own) {
317
411
  process.on('SIGINT', onSig);
318
412
  process.on('SIGTERM', onSig);
319
413
  }
414
+ // The sleeps are unref'd; without the PumpPortal socket (--no-newpairs) nothing else would keep the event loop
415
+ // alive between passes and node would exit mid-run (task-1504 smoke). Hold it open until the loops finish.
416
+ const hold = own ? setInterval(() => { }, 1 << 30) : null;
320
417
  if (parsed.verbose || parsed.ticks !== Infinity)
321
- io.stderr.write(`dexterm record: db ${dbPath} · interval ${Math.round(parsed.intervalMs / 1000)}s · maintain ${Math.round(parsed.maintainMs / 1000)}s · cap ${parsed.maxDbBytes === null ? 'off' : `${Math.round(parsed.maxDbBytes / 1e6)} MB`} · newpairs ${parsed.noNewpairs ? 'off' : 'on'}\n`);
418
+ io.stderr.write(`dexterm record: db ${dbPath} · interval ${Math.round(parsed.intervalMs / 1000)}s · maintain ${Math.round(parsed.maintainMs / 1000)}s · cap ${parsed.maxDbBytes === null ? 'off' : `${Math.round(parsed.maxDbBytes / 1e6)} MB`} · newpairs ${parsed.noNewpairs ? 'off' : 'on'}${parsed.retentionBytes !== null ? ` · retention ${Math.round(parsed.retentionBytes / 1e6)} MB (evict, never pause)` : ''}${parsed.retentionDays !== null ? ` · rows ${parsed.retentionDays} d` : ''} · focus ticks ${parsed.focusTicks ? 'on' : 'off'}\n`);
322
419
  let result;
420
+ let tickRes = { polls: 0, calls: 0, written: 0 };
323
421
  try {
324
- result = await runCollectorLoop(engine, {
422
+ let mainDone = false;
423
+ const main = runCollectorLoop(engine, {
325
424
  intervalMs: parsed.intervalMs, ticks: parsed.ticks, maintainEveryMs: parsed.maintainMs, maxDbBytes: parsed.maxDbBytes,
425
+ capMode: parsed.retentionBytes !== null ? 'evict' : 'pause',
326
426
  now, sleep: (ms) => stop.sleep(ms), aborted: () => stop.aborted, onLog: log,
327
- });
427
+ }).finally(() => { mainDone = true; if (parsed.ticks !== Infinity)
428
+ stop.abort(); });
429
+ const lane = parsed.focusTicks && engine.pollTicks
430
+ ? runTickLoop(engine, { everyMs: DEFAULT_TICKS.fastEveryMs, sleep: (ms) => stop.sleep(ms), aborted: () => stop.aborted || mainDone, onLog: log })
431
+ : Promise.resolve(tickRes);
432
+ [result, tickRes] = await Promise.all([main, lane]);
328
433
  }
329
434
  finally {
330
435
  if (own) {
331
436
  process.removeListener('SIGINT', onSig);
332
437
  process.removeListener('SIGTERM', onSig);
333
438
  }
439
+ if (hold)
440
+ clearInterval(hold);
334
441
  engine.close();
335
442
  }
336
443
  if (parsed.verbose || parsed.ticks !== Infinity)
337
- io.stdout.write(`record: ${result.observePasses} observe pass(es), ${result.rowsWritten} snapshot row(s), ${result.maintainPasses} maintenance pass(es)${result.cappedTicks ? `, ${result.cappedTicks} tick(s) paused at the disk cap` : ''}\n`);
444
+ io.stdout.write(`record: ${result.observePasses} observe pass(es), ${result.rowsWritten} snapshot row(s), ${result.maintainPasses} maintenance pass(es)${result.cappedTicks ? `, ${result.cappedTicks} tick(s) paused at the disk cap` : ''}${result.evictions ? `, ${result.evictions} retention eviction(s)` : ''}${tickRes.polls ? `, ${tickRes.polls} focus-tick poll(s) (${tickRes.calls} call(s), ${tickRes.written} tick row(s))` : ''}\n`);
338
445
  return 0;
339
446
  }
340
447
  function install(argv, io) {
341
448
  let intervalMs = DEFAULT_INTERVAL_MS, maxDbMb = DEFAULT_MAX_DB_MB, load = false, dry = false;
449
+ const ret = { retentionBytes: null, retentionDays: null, focusTicks: false };
342
450
  for (let i = 0; i < argv.length; i++) {
343
451
  const t = argv[i];
452
+ const r = parseRetention(t, () => argv[++i], ret);
453
+ if (typeof r === 'string') {
454
+ io.stderr.write(`dexterm record: ${r}\n`);
455
+ return 2;
456
+ }
457
+ if (r === null)
458
+ continue;
344
459
  if (t === '--load')
345
460
  load = true;
346
461
  else if (t === '--dry-run')
@@ -369,7 +484,12 @@ function install(argv, io) {
369
484
  const path = plistPath(io.env);
370
485
  const logPath = join(dataDir(io.env), 'collector.log');
371
486
  const bin = sourceBin();
372
- const plist = renderCollectorPlist({ label: COLLECTOR_LABEL, nodePath: process.execPath, binPath: bin, intervalMs, maxDbMb, logPath, workingDir: resolve(dirname(bin), '..', '..', '..') });
487
+ const extraArgs = [
488
+ ...(ret.retentionBytes !== null ? ['--retention-mb', String(Math.round(ret.retentionBytes / 1e6))] : []),
489
+ ...(ret.retentionDays !== null ? ['--retention-days', String(ret.retentionDays)] : []),
490
+ ...(ret.focusTicks ? ['--focus-ticks'] : []),
491
+ ];
492
+ const plist = renderCollectorPlist({ label: COLLECTOR_LABEL, nodePath: process.execPath, binPath: bin, intervalMs, maxDbMb, extraArgs, logPath, workingDir: resolve(dirname(bin), '..', '..', '..') });
373
493
  if (dry) {
374
494
  io.stdout.write(plist);
375
495
  io.stderr.write(`\ndexterm record: dry run — would write ${path}\n`);
@@ -427,6 +547,7 @@ function status(argv, io) {
427
547
  `snapshots ${s.snapshots} (live ${s.live}, sweep ${s.sweep}, t0 anchors ${s.t0})`,
428
548
  `outcomes ${s.outcomes}${s.outcomes ? ` (${Object.entries(s.outcomesByLabel).map(([k, v]) => `${k} ${v}`).join(', ')})` : ''}`,
429
549
  `sweeps ${s.sweeps} runs · ${s.sweepCallsToday} DexScreener calls today`,
550
+ `focus ticks ${s.ticks} rows · ${s.candles} 1m candles · ${s.tickCallsToday} calls today`,
430
551
  `span ${when(s.oldestT)} → ${when(s.newestT)}`,
431
552
  ].join('\n') + '\n');
432
553
  }
@@ -11,7 +11,7 @@
11
11
  // Pure: (canvas, StoryView, opts) → cells. Every number comes from the story (app-computed); nothing here can act.
12
12
  // Reduced motion (calm mode / DEXTERM_REDUCED_MOTION) drops every sweep, flash, glitch and typewriter: the same
13
13
  // information appears at once. NO_COLOR keeps meaning in glyphs (▸ active phase, ✓ ! ✗ chips, ▶ cursor).
14
- import { mix, hash3, KATAKANA, decryptText, fmtCompactUsd } from '../../../tui/src/index.js';
14
+ import { mix, hash3, KATAKANA, decryptText, fmtCompactUsd, markerGlyph, markerLetter } from '../../../tui/src/index.js';
15
15
  import { STORY_RAIL, fmtQty, fmtPx } from '../../../agents/src/index.js';
16
16
  import { pnlChipVM, renderPnlChip } from "./pnl-chip.js";
17
17
  /** Rows the dock takes at the bottom of the terminal. */
@@ -550,7 +550,8 @@ function ticketCard(c, v, arc, o, screenH) {
550
550
  ['SIZE', arc.sizeUsd != null ? `$${arc.sizeUsd.toFixed(2)}${v.caps ? ` cap $${v.caps.perTradeUsd.toFixed(2)}/trade · $${v.caps.totalUsd.toFixed(0)} total` : ''}` : 'sizing…', arc.sizeUsd != null ? t.text : t.dim],
551
551
  ['GATE', 'YOUR keypress armed the mandate · tier hands-free', t.muted],
552
552
  ['', 'no keypress per trade inside it · desk enforces caps', mix(bg, t.dim, 0.85)],
553
- ['ENTRY', fill ? `${Math.abs(fill.sol).toFixed(4)} SOL → ${fmtQty(fill.qty)} $${arc.symbol}${fill.priceUsd != null ? ` @ $${fmtPx(fill.priceUsd)}` : ''}` : refused ? '—' : 'awaiting confirm · fresh quote at fill', fill ? t.text : t.dim],
553
+ // task-1537: the fill reads as the market cap it filled at when the HUD's cap scale knows the supply (like the position card)
554
+ ['ENTRY', fill ? `${Math.abs(fill.sol).toFixed(4)} SOL → ${fmtQty(fill.qty)} $${arc.symbol}${fill.priceUsd != null ? ` @ ${((sc) => (sc ? `MC ${fmtCompactUsd(fill.priceUsd * sc)}` : `$${fmtPx(fill.priceUsd)}`))(o.mcapScale?.(arc.mint) ?? null)}` : ''}` : refused ? '—' : 'awaiting confirm · fresh quote at fill', fill ? t.text : t.dim],
554
555
  ['IMPACT', fill?.impactPct != null ? `${fill.impactPct.toFixed(2)}%${v.caps?.maxPriceImpactPct != null ? ` max ${v.caps.maxPriceImpactPct}%` : ''}` : '—', t.muted],
555
556
  ['EXITS', 'agent-managed · no resting orders on PAPER', t.muted],
556
557
  ];
@@ -585,6 +586,26 @@ function ticketCard(c, v, arc, o, screenH) {
585
586
  c.text(x + Math.floor((w - s.length) / 2), sy, s, { fg: mix(bg, t.meteora, pulse), bg });
586
587
  }
587
588
  }
589
+ /**
590
+ * task-1483: where the arc's fills sit on the PnL trace: the column of the first sample at or after the fill (the sample
591
+ * that first reflects it). A fill before the first SHOWN sample when older samples were cut off, or after the last
592
+ * sample, gets no column (it is not moved onto another one). Columns are 0-based into `shownT`.
593
+ */
594
+ export function traceFillCols(allT, shownFrom, fills) {
595
+ const by = new Map();
596
+ for (const f of fills) {
597
+ const i = allT.findIndex((t) => t >= f.t);
598
+ if (i < 0 || i < shownFrom)
599
+ continue;
600
+ const key = `${i}|${f.side}`;
601
+ const e = by.get(key);
602
+ if (e)
603
+ e.count++;
604
+ else
605
+ by.set(key, { col: i - shownFrom, side: f.side, count: 1 });
606
+ }
607
+ return [...by.values()].sort((a, b) => a.col - b.col);
608
+ }
588
609
  function positionCard(c, v, arc, o, screenH) {
589
610
  const t = c.theme;
590
611
  const w = Math.min(62, c.w - 4);
@@ -619,6 +640,17 @@ function positionCard(c, v, arc, o, screenH) {
619
640
  const lv = Math.round(((s.pct - lo) / span) * 7);
620
641
  c.put(x + 2 + i, y + 6, LV[lv], { fg: s.pct >= 0 ? t.up : t.down, bg, soft: true });
621
642
  });
643
+ // task-1483: the agent's PAPER fills under the trace (△B / ▽S, hollow = paper), at the sample that first shows them
644
+ const used = new Set();
645
+ for (const m of traceFillCols(arc.trace.map((s) => s.t), arc.trace.length - tr.length, arc.fills)) {
646
+ const cx = x + 2 + m.col;
647
+ if (used.has(m.col) || used.has(m.col + 1) || cx + 1 >= x + w - 1)
648
+ continue;
649
+ used.add(m.col);
650
+ used.add(m.col + 1);
651
+ const col = m.side === 'buy' ? t.up : t.down;
652
+ c.text(cx, y + 7, `${markerGlyph(m.side, 'paper')}${markerLetter(m.side)}`, { fg: col, bg: mix(bg, col, 0.2), b: true, hard: true });
653
+ }
622
654
  }
623
655
  else
624
656
  c.text(x + 2, y + 6, '· waiting for the first mark tick', { fg: t.dim, bg });
@@ -20,7 +20,7 @@ export function renderAnalystSession(c, x, y, w, h, s, opts) {
20
20
  // Narrow panels (token screen) drop the model so cost · latency · tools still fits beside the title.
21
21
  const full = s.status === 'error' ? 'analyst unavailable' : s.footer;
22
22
  // 0.1.5: a codex conversation says so in the title; claude panels keep the original default.
23
- const title = opts.title ?? (s.engine === 'codex' ? '✦ ANALYST · codex' : '✦ ANALYST · claude');
23
+ const title = opts.title ?? (s.engine === 'codex' ? '✦ ANALYST · codex' : s.engine === 'ollama' ? '✦ ANALYST · ollama' : '✦ ANALYST · claude');
24
24
  const caption = title.length + full.length + 10 <= w ? full : full.split(' · ').slice(0, 3).join(' · ');
25
25
  const capFg = s.status === 'error' ? t.warn : t.dim;
26
26
  const box = c.box(x, y, w, h, { title, titleFg: t.ai, caption, captionFg: capFg, border: bc });
@@ -183,7 +183,7 @@ export function renderAnalystSession(c, x, y, w, h, s, opts) {
183
183
  // mcap/age + the analyst's one-line reason), navigable with ↑↓ and ⏎-to-jump. The numbers come from
184
184
  // live data (resolved by the caller from the app's data layer, never from the model's text).
185
185
  if (opts.results?.length && cy < inputY - 1) {
186
- cy = renderResultsTable(c, box, bg, cy, inputY, inner, opts.results, opts.resultsCursor ?? 0, opts.resultsActive ?? false);
186
+ cy = renderResultsTable(c, box, bg, cy, inputY, inner, opts.results, opts.resultsCursor ?? 0, opts.resultsActive ?? false, opts.priceMode !== 'price');
187
187
  }
188
188
  if (s.candidates.length && cy < inputY - 1) {
189
189
  cy += 1;
@@ -251,14 +251,17 @@ function fmtAge(min) {
251
251
  * so it stays legible instead of wrapping into prose. The highlighted row (cursor) is tinted; when the
252
252
  * table is active a `▸` marks it and the footer hint changes. Returns the new y.
253
253
  */
254
- function renderResultsTable(c, box, bg, startY, inputY, inner, rows, cursor, active) {
254
+ function renderResultsTable(c, box, bg, startY, inputY, inner, rows, cursor, active, mcapFirst = true) {
255
255
  const t = c.theme;
256
256
  let cy = startY + 1;
257
257
  if (cy >= inputY - 1)
258
258
  return cy;
259
259
  // Column plan: [symbol, price, 24h, liq, vol, mcap, age] then the reason fills the rest. Drop from
260
260
  // the right as width shrinks so a narrow panel still shows symbol + price + change + a reason.
261
- const full = [['SYMBOL', 10], ['PRICE', 9], ['24H', 8], ['LIQ', 8], ['VOL', 8], ['MCAP', 8], ['AGE', 5]];
261
+ // task-1537: in the market-cap basis MCAP takes PRICE's slot (the headline number) and PRICE moves to MCAP's
262
+ const full = mcapFirst
263
+ ? [['SYMBOL', 10], ['MCAP', 9], ['24H', 8], ['LIQ', 8], ['VOL', 8], ['PRICE', 8], ['AGE', 5]]
264
+ : [['SYMBOL', 10], ['PRICE', 9], ['24H', 8], ['LIQ', 8], ['VOL', 8], ['MCAP', 8], ['AGE', 5]];
262
265
  let cols = full.slice();
263
266
  // keep trimming trailing columns until the header + a minimal reason (12) fits
264
267
  while (cols.length > 3 && cols.reduce((a, [, w]) => a + w + 1, 0) + 12 > inner - 2)
@@ -6,6 +6,7 @@ import { Canvas, sparkline, heatPct, decryptText, spinnerGlyph, fmtUsd, fmtPrice
6
6
  import { headerModel } from "../state.js";
7
7
  import { screenerView } from "../screener/view.js";
8
8
  import { renderAnalystSession } from "./analyst-session.js";
9
+ import { priceModeOf } from "../display.js";
9
10
  import { resolveAnalystResults } from "../analyst/results.js";
10
11
  export function renderAnalyst(c, state) {
11
12
  const t = c.theme;
@@ -28,7 +29,7 @@ export function renderAnalyst(c, state) {
28
29
  frame: state.vfx.frame, code: state.vfx.code, calm: isCalm(state.vfx),
29
30
  input: state.aiInput ?? '', inputFocused: focused,
30
31
  scroll: state.aiScroll, revealAll: state.aiRevealAll,
31
- results, resultsCursor: state.analyst.resultsCursor, resultsActive,
32
+ results, resultsCursor: state.analyst.resultsCursor, resultsActive, priceMode: priceModeOf(state), // task-1537
32
33
  inputHint: focused ? 'type your question… ⏎ send · esc blur · analysis not advice'
33
34
  : results.length ? 'i ask · ↑↓ pick coin · ⏎ open · o ticket · esc back'
34
35
  : 'i ask · ⏎ focus · ↑↓ pick coin · PgUp/Dn scroll',
@@ -43,7 +44,7 @@ export function renderAnalyst(c, state) {
43
44
  if (state.screener.source !== 'fixtures') {
44
45
  const live = state.aiFocused === true;
45
46
  const eng = state.analystEngine ?? 'claude'; // 0.1.5: the user's own CLI that will answer (claude | codex)
46
- const b = c.box(0, top, chatW, bottom - top, { title: `✦ ANALYST · ${eng}`, titleFg: t.ai, caption: `read-only · local ${eng} CLI · on demand`, captionFg: t.dim, border: bc });
47
+ const b = c.box(0, top, chatW, bottom - top, { title: `✦ ANALYST · ${eng}`, titleFg: t.ai, caption: `read-only · local ${eng === 'ollama' ? 'ollama model' : `${eng} CLI`} · on demand`, captionFg: t.dim, border: bc });
47
48
  const bbg = b.bg;
48
49
  const bin = b.w - 2;
49
50
  let by = b.y + 1;
@@ -0,0 +1,134 @@
1
+ // dexterm — the AUTOTRADE screen's JOURNEY view (`Q`, then `J` on a strategy; 0.2.0 card C). One strategy's ladder:
2
+ // draft → backtested → forward-tested → always-on (paper) → real, with the last backtest (held-out split), the forward
3
+ // test (running or its result next to the backtest), the always-on service membership and the mode.
4
+ //
5
+ // Keys on the view: `b` backtests (recorded as evidence), `F` starts a 24 h forward test (paper, live data, the
6
+ // strategy's own bankroll, in the background), `X` stops it, `A` opens the pending ALWAYS-ON promotion's card (y
7
+ // applies it: autotrade.json gets the paper member). A promotion to REAL is never applied here: it needs the user's own
8
+ // caps and the typed phrase on a terminal (`dexterm autotrade promote apply <pp-id>`). Nothing here reaches a wallet,
9
+ // a key, a mandate or the signer.
10
+ import { dirname } from 'node:path';
11
+ import { PromotionFiles, applyAlwaysOn, createForwardTest, findForwardTest, forwardReport, journeyView, liveState, loadArmings, requestForwardStop, saveForwardTest, windowText, } from '../../../autotrade/src/index.js';
12
+ import { spawnForwardHost } from "../autotrade-cmd/forward.js";
13
+ export const defaultForwardSpawn = (home, t) => spawnForwardHost(home, t, process.env);
14
+ const homeOf = (ui) => dirname(ui.deps.store.dir);
15
+ export function openJourney(ui, id) {
16
+ if (!id)
17
+ return 'no strategy selected';
18
+ ui.journey = { id, view: null, error: null, forward: null, report: null, pending: [], readAt: 0 };
19
+ ui.view = 'journey';
20
+ refreshJourney(ui, true);
21
+ return ui.journey.error;
22
+ }
23
+ /** Re-read the journey (at most every 2 s unless forced): evidence, forward test, pending promotions. */
24
+ export function refreshJourney(ui, force = false) {
25
+ const j = ui.journey;
26
+ if (!j)
27
+ return;
28
+ const now = ui.deps.now();
29
+ if (!force && now - j.readAt < 2000)
30
+ return;
31
+ j.readAt = now;
32
+ const home = homeOf(ui);
33
+ const got = ui.deps.store.get(j.id);
34
+ if (!got || !got.ok) {
35
+ j.view = null;
36
+ j.error = got ? `${j.id} does not validate` : `no strategy ${j.id}`;
37
+ return;
38
+ }
39
+ try {
40
+ j.view = journeyView(home, got.strategy, { armedHash: loadArmings(home)[j.id]?.strategyHash ?? null });
41
+ j.forward = findForwardTest(home, j.id, now);
42
+ j.report = j.forward ? forwardReport(home, j.forward, now) : null;
43
+ j.pending = new PromotionFiles(home).list({ status: 'pending', id: j.id });
44
+ j.error = null;
45
+ }
46
+ catch (e) {
47
+ j.error = `journey unreadable: ${e.message}`;
48
+ }
49
+ }
50
+ /** `F`: a 24 h forward test of this version, on paper, live data, its own bankroll, started in the background. */
51
+ export function journeyForward(ui) {
52
+ const j = ui.journey;
53
+ if (!j)
54
+ return 'F starts a forward test from the journey view (J)';
55
+ const got = ui.deps.store.get(j.id);
56
+ if (!got || !got.ok)
57
+ return `no valid strategy ${j.id}`;
58
+ if (got.source !== 'user')
59
+ return `${j.id} is a preset: copy it first (d)`;
60
+ const home = homeOf(ui);
61
+ const c = createForwardTest(home, got.strategy, ui.deps.store.raw(j.id), { hours: 24, by: 'user:tui', now: ui.deps.now() });
62
+ if (!c.ok)
63
+ return c.why;
64
+ const pid = (ui.deps.forwardSpawn ?? defaultForwardSpawn)(home, c.test);
65
+ if (!pid) {
66
+ saveForwardTest(home, { ...c.test, state: 'failed', endedAt: ui.deps.now(), endReason: 'could not start the host' });
67
+ return `could not start it; in a terminal: dexterm autotrade forward start ${j.id}`;
68
+ }
69
+ refreshJourney(ui, true);
70
+ return `forward test ${c.test.forwardId} started · ${windowText(c.test.window)} · paper, live data, own bankroll $${c.test.bankrollUsd} · X stops it`;
71
+ }
72
+ /** `X`: stop the running forward test (it flattens and writes its report; an early stop does not count). */
73
+ export function journeyStopForward(ui) {
74
+ const j = ui.journey;
75
+ if (!j)
76
+ return 'X stops a forward test from the journey view (J)';
77
+ const t = findForwardTest(homeOf(ui), j.id, ui.deps.now());
78
+ if (!t)
79
+ return 'no forward test';
80
+ const r = requestForwardStop(homeOf(ui), t.forwardId, 'tui', ui.deps.now());
81
+ refreshJourney(ui, true);
82
+ return r.ok ? `${r.already ? 'already stopping' : 'stop requested'}: ${t.forwardId} flattens and writes its report (an early stop does not count)` : r.why;
83
+ }
84
+ /** `A`: open the pending always-on promotion's confirm card (y applies). Real is a terminal-only step. */
85
+ export function journeyPromote(ui) {
86
+ const j = ui.journey;
87
+ if (!j)
88
+ return 'A applies a promotion from the journey view (J)';
89
+ refreshJourney(ui, true);
90
+ const p = j.pending.find((x) => x.to === 'always-on');
91
+ if (p) {
92
+ ui.confirm = { kind: 'promote', proposalId: p.proposalId };
93
+ return `promotion ${p.proposalId} → always-on (proposed by ${p.actor}): y applies, any key cancels`;
94
+ }
95
+ const real = j.pending.find((x) => x.to === 'real');
96
+ if (real)
97
+ return `real mode is armed only on your own terminal with your caps and a typed phrase: dexterm autotrade promote apply ${real.proposalId}`;
98
+ return j.view?.next?.stage === 'always-on' ? `nothing pending: ask the analyst to propose it, or in a terminal: dexterm autotrade promote ${j.id} --to always-on` : 'no promotion pending';
99
+ }
100
+ /** The y / any-key answer on the promotion card. */
101
+ export function confirmPromote(ui, proposalId, yes) {
102
+ if (!yes)
103
+ return 'promotion not applied (it stays pending)';
104
+ const j = ui.journey;
105
+ const got = j ? ui.deps.store.get(j.id) : null;
106
+ if (!got || !got.ok)
107
+ return 'the strategy is not valid any more';
108
+ const a = applyAlwaysOn(homeOf(ui), proposalId, got.strategy, { by: 'user:tui', now: ui.deps.now() });
109
+ refreshJourney(ui, true);
110
+ return a.ok ? `applied: ${got.strategy.id} is an always-on paper member (${a.memberId}); the service runs it from its next start` : `not applied: ${a.why}`;
111
+ }
112
+ export function journeyLines(j, now) {
113
+ if (j.error || !j.view)
114
+ return [{ text: j.error ?? 'loading…', tone: 'warn' }];
115
+ const v = j.view;
116
+ const out = [
117
+ { text: `${v.id} v${v.version} · rules ${v.rulesHash.slice(0, 8)} · mode ${v.mode} · stage ${v.stage.toUpperCase()}`, tone: 'text' },
118
+ { text: '', tone: 'dim' },
119
+ ...v.rungs.map((r) => ({ text: `${r.ok ? '[x]' : '[ ]'} ${r.stage.padEnd(15)} ${r.detail}`, tone: (r.ok ? 'ok' : 'dim') })),
120
+ ...v.notes.map((n) => ({ text: `! ${n}`, tone: 'warn' })),
121
+ { text: v.next ? `next: ${v.next.stage} — ${v.next.how}` : 'top of the ladder: real (caps, kill switch, receipts: dexterm autotrade signal status)', tone: 'text' },
122
+ { text: '', tone: 'dim' },
123
+ ];
124
+ if (j.forward && j.report) {
125
+ const st = liveState(j.forward, now);
126
+ out.push({ text: `forward test ${j.forward.forwardId} · ${st.toUpperCase()} · ${windowText(j.forward.window)}`, tone: st === 'running' || st === 'starting' ? 'warn' : 'text' });
127
+ for (const l of j.report.lines.slice(1))
128
+ out.push({ text: l.replace(/^ /, ''), tone: 'text' });
129
+ out.push({ text: '', tone: 'dim' });
130
+ }
131
+ for (const p of j.pending)
132
+ out.push({ text: `pending promotion ${p.proposalId} → ${p.to} (proposed by ${p.actor})${p.to === 'always-on' ? ' · A to review' : ' · terminal only: dexterm autotrade promote apply'}`, tone: 'warn' });
133
+ return out;
134
+ }