dexterm-cli 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (379) hide show
  1. package/dist/packages/agents/src/index.js +2 -0
  2. package/dist/packages/agents/src/schema.js +22 -1
  3. package/dist/packages/agents/src/spectator.js +54 -0
  4. package/dist/packages/agents/src/story.js +532 -0
  5. package/dist/packages/agents/src/tiers.js +28 -2
  6. package/dist/packages/agents/src/tools.js +31 -2
  7. package/dist/packages/autotrade/assets/exit-profile.schema.json +492 -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/src/account.js +74 -0
  20. package/dist/packages/autotrade/src/backtest.js +357 -0
  21. package/dist/packages/autotrade/src/basis-text.js +32 -0
  22. package/dist/packages/autotrade/src/chat/approvals.js +484 -0
  23. package/dist/packages/autotrade/src/chat/audit.js +140 -0
  24. package/dist/packages/autotrade/src/chat/config.js +93 -0
  25. package/dist/packages/autotrade/src/chat/connector.js +24 -0
  26. package/dist/packages/autotrade/src/chat/control.js +103 -0
  27. package/dist/packages/autotrade/src/chat/fake.js +100 -0
  28. package/dist/packages/autotrade/src/chat/format.js +71 -0
  29. package/dist/packages/autotrade/src/chat/hub.js +187 -0
  30. package/dist/packages/autotrade/src/chat/index.js +18 -0
  31. package/dist/packages/autotrade/src/chat/notify.js +292 -0
  32. package/dist/packages/autotrade/src/chat/pairing.js +46 -0
  33. package/dist/packages/autotrade/src/chat/redact.js +48 -0
  34. package/dist/packages/autotrade/src/chat/secrets.js +99 -0
  35. package/dist/packages/autotrade/src/chat/service.js +248 -0
  36. package/dist/packages/autotrade/src/chat/telegram.js +290 -0
  37. package/dist/packages/autotrade/src/desk-executor.js +188 -0
  38. package/dist/packages/autotrade/src/engine.js +387 -0
  39. package/dist/packages/autotrade/src/execution-port.js +97 -0
  40. package/dist/packages/autotrade/src/fleet-config.js +169 -0
  41. package/dist/packages/autotrade/src/fleet-control.js +276 -0
  42. package/dist/packages/autotrade/src/fleet.js +639 -0
  43. package/dist/packages/autotrade/src/index.js +53 -0
  44. package/dist/packages/autotrade/src/ledger.js +108 -0
  45. package/dist/packages/autotrade/src/live-source.js +182 -0
  46. package/dist/packages/autotrade/src/monitor-daemon.js +715 -0
  47. package/dist/packages/autotrade/src/monitor.js +190 -0
  48. package/dist/packages/autotrade/src/ollama-sessions.js +137 -0
  49. package/dist/packages/autotrade/src/paper-accounts.js +72 -0
  50. package/dist/packages/autotrade/src/replay.js +198 -0
  51. package/dist/packages/autotrade/src/runner-config.js +99 -0
  52. package/dist/packages/autotrade/src/runner.js +872 -0
  53. package/dist/packages/autotrade/src/safety.js +243 -0
  54. package/dist/packages/autotrade/src/session.js +269 -0
  55. package/dist/packages/autotrade/src/sessions.js +503 -0
  56. package/dist/packages/autotrade/src/store.js +250 -0
  57. package/dist/packages/autotrade/src/strategy.js +256 -0
  58. package/dist/packages/autotrade/src/trade-ledger.js +241 -0
  59. package/dist/packages/autotrade/src/yolo.js +140 -0
  60. package/dist/packages/cli/assets/claude/analyst-system.md +4 -4
  61. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +1 -1
  62. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +15 -8
  63. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +1 -1
  64. package/dist/packages/cli/src/agent-cmd.js +6 -4
  65. package/dist/packages/cli/src/agent-spectator.js +17 -1
  66. package/dist/packages/cli/src/ai-cmd.js +412 -0
  67. package/dist/packages/cli/src/analyst/backend.js +105 -15
  68. package/dist/packages/cli/src/analyst/codex.js +14 -9
  69. package/dist/packages/cli/src/analyst/larp-gauge.js +60 -0
  70. package/dist/packages/cli/src/analyst/ollama.js +263 -0
  71. package/dist/packages/cli/src/analyst/session.js +17 -3
  72. package/dist/packages/cli/src/app-help-gen.js +51 -15
  73. package/dist/packages/cli/src/autotrade-cmd/audit.js +57 -0
  74. package/dist/packages/cli/src/autotrade-cmd/backtest.js +199 -0
  75. package/dist/packages/cli/src/autotrade-cmd/flatten.js +64 -0
  76. package/dist/packages/cli/src/autotrade-cmd/fleet.js +397 -0
  77. package/dist/packages/cli/src/autotrade-cmd/requests.js +79 -0
  78. package/dist/packages/cli/src/autotrade-cmd/run.js +413 -0
  79. package/dist/packages/cli/src/autotrade-cmd/stop.js +76 -0
  80. package/dist/packages/cli/src/autotrade-cmd/strategies.js +198 -0
  81. package/dist/packages/cli/src/autotrade-cmd/strategy-control.js +174 -0
  82. package/dist/packages/cli/src/autotrade-cmd/telegram-host.js +101 -0
  83. package/dist/packages/cli/src/autotrade-cmd.js +168 -0
  84. package/dist/packages/cli/src/backtest-cmd.js +13 -2
  85. package/dist/packages/cli/src/display.js +208 -23
  86. package/dist/packages/cli/src/doctor.js +62 -3
  87. package/dist/packages/cli/src/export.js +10 -2
  88. package/dist/packages/cli/src/index.js +1 -1
  89. package/dist/packages/cli/src/ledger-cmd.js +288 -0
  90. package/dist/packages/cli/src/live/chart-fills.js +46 -0
  91. package/dist/packages/cli/src/live/dev-history.js +31 -15
  92. package/dist/packages/cli/src/live/index.js +28 -90
  93. package/dist/packages/cli/src/live/screens/newpairs-live.js +1 -1
  94. package/dist/packages/cli/src/live/screens/token-live.js +48 -15
  95. package/dist/packages/cli/src/live/screens/watchlist-live.js +1 -1
  96. package/dist/packages/cli/src/live/trade.js +30 -453
  97. package/dist/packages/cli/src/live/watchlist.js +1 -1
  98. package/dist/packages/cli/src/main.js +290 -199
  99. package/dist/packages/cli/src/mcp-cmd.js +7 -1
  100. package/dist/packages/cli/src/mcp-follow.js +103 -6
  101. package/dist/packages/cli/src/plan-cmd.js +15 -5
  102. package/dist/packages/cli/src/pnl-app.js +31 -21
  103. package/dist/packages/cli/src/pnl-card.js +158 -21
  104. package/dist/packages/cli/src/popup.js +38 -6
  105. package/dist/packages/cli/src/record-cmd.js +141 -20
  106. package/dist/packages/cli/src/scan/ui.js +7 -1
  107. package/dist/packages/cli/src/screens/agent-hud.js +773 -0
  108. package/dist/packages/cli/src/screens/analyst-session.js +11 -4
  109. package/dist/packages/cli/src/screens/analyst.js +3 -2
  110. package/dist/packages/cli/src/screens/autotrade-keys.js +169 -0
  111. package/dist/packages/cli/src/screens/autotrade-proposals.js +123 -0
  112. package/dist/packages/cli/src/screens/autotrade-screen.js +725 -0
  113. package/dist/packages/cli/src/screens/autotrade-ui.js +640 -0
  114. package/dist/packages/cli/src/screens/autotrade-worker.js +89 -0
  115. package/dist/packages/cli/src/screens/brain.js +634 -0
  116. package/dist/packages/cli/src/screens/newpairs.js +1 -1
  117. package/dist/packages/cli/src/screens/overlay.js +5 -2
  118. package/dist/packages/cli/src/screens/plans.js +5 -17
  119. package/dist/packages/cli/src/screens/pnl-chip.js +144 -0
  120. package/dist/packages/cli/src/screens/positions.js +6 -28
  121. package/dist/packages/cli/src/screens/screener.js +1 -0
  122. package/dist/packages/cli/src/screens/ticket.js +38 -205
  123. package/dist/packages/cli/src/screens/token.js +18 -9
  124. package/dist/packages/cli/src/screens/trending.js +1 -1
  125. package/dist/packages/cli/src/state.js +88 -18
  126. package/dist/packages/cli/src/telegram-cmd.js +451 -0
  127. package/dist/packages/cli/src/trade.js +3 -128
  128. package/dist/packages/cli/src/version.js +1 -1
  129. package/dist/packages/cli/src/wallet-cmd.js +39 -340
  130. package/dist/packages/cli/src/wallet-command.js +8 -11
  131. package/dist/packages/cli/src/widgets.js +0 -12
  132. package/dist/packages/custom/src/config.js +20 -2
  133. package/dist/packages/custom/src/schema.js +41 -10
  134. package/dist/packages/custom/src/workspace.js +3 -3
  135. package/dist/packages/data/src/basis.js +191 -0
  136. package/dist/packages/data/src/detail.js +53 -2
  137. package/dist/packages/data/src/index.js +2 -14
  138. package/dist/packages/data/src/larp/score.js +220 -0
  139. package/dist/packages/data/src/larp/x-account.js +316 -0
  140. package/dist/packages/data/src/store.js +14 -5
  141. package/dist/packages/data/src/trade/agent/paper-desk.js +300 -23
  142. package/dist/packages/data/src/trade/agent/rendezvous.js +3 -2
  143. package/dist/packages/data/src/trade/bridge.js +4 -102
  144. package/dist/packages/data/src/trade/ledger/export.js +101 -0
  145. package/dist/packages/data/src/trade/ledger/file.js +135 -0
  146. package/dist/packages/data/src/trade/ledger/fold.js +191 -0
  147. package/dist/packages/data/src/trade/ledger/index.js +9 -0
  148. package/dist/packages/data/src/trade/ledger/schema.js +83 -0
  149. package/dist/packages/data/src/trade/ledger/sources.js +307 -0
  150. package/dist/packages/data/src/trade/ledger/stats.js +123 -0
  151. package/dist/packages/data/src/trade/ledger/tags.js +116 -0
  152. package/dist/packages/data/src/trade/ledger/view.js +51 -0
  153. package/dist/packages/data/src/trade/plans/engine.js +7 -0
  154. package/dist/packages/data/src/trade/plans/index.js +0 -1
  155. package/dist/packages/data/src/trade/plans/mandate.js +28 -15
  156. package/dist/packages/data/src/trade/plans/service.js +4 -28
  157. package/dist/packages/mcp/assets/kb/app-help/autotrade-chat.md +48 -0
  158. package/dist/packages/mcp/assets/kb/app-help/autotrade-parallel.md +42 -0
  159. package/dist/packages/mcp/assets/kb/app-help/autotrade-runner.md +35 -0
  160. package/dist/packages/mcp/assets/kb/app-help/autotrade-stop.md +37 -0
  161. package/dist/packages/mcp/assets/kb/app-help/autotrade-strategies.md +35 -0
  162. package/dist/packages/mcp/assets/kb/app-help/autotrade-telegram.md +40 -0
  163. package/dist/packages/mcp/assets/kb/app-help/chart-trade-markers.md +42 -0
  164. package/dist/packages/mcp/assets/kb/app-help/dev-history.md +1 -1
  165. package/dist/packages/mcp/assets/kb/app-help/larp-check.md +5 -1
  166. package/dist/packages/mcp/assets/kb/app-help/link-a-wallet.md +4 -10
  167. package/dist/packages/mcp/assets/kb/app-help/live-trading.md +38 -0
  168. package/dist/packages/mcp/assets/kb/app-help/live-yolo.md +34 -0
  169. package/dist/packages/mcp/assets/kb/app-help/order-plans.md +1 -1
  170. package/dist/packages/mcp/assets/kb/app-help/price-or-marketcap.md +5 -2
  171. package/dist/packages/mcp/assets/kb/app-help/scan-mode.md +1 -1
  172. package/dist/packages/mcp/assets/kb/app-help/screen-analyst.md +3 -0
  173. package/dist/packages/mcp/assets/kb/app-help/screen-autotrade.md +217 -0
  174. package/dist/packages/mcp/assets/kb/app-help/screen-kol.md +3 -0
  175. package/dist/packages/mcp/assets/kb/app-help/screen-newpairs.md +3 -0
  176. package/dist/packages/mcp/assets/kb/app-help/screen-positions.md +3 -0
  177. package/dist/packages/mcp/assets/kb/app-help/screen-screener.md +3 -0
  178. package/dist/packages/mcp/assets/kb/app-help/screen-token.md +6 -0
  179. package/dist/packages/mcp/assets/kb/app-help/screen-trending.md +3 -0
  180. package/dist/packages/mcp/assets/kb/app-help/screen-watchlist.md +3 -0
  181. package/dist/packages/mcp/assets/kb/app-help/spectate-an-agent.md +2 -1
  182. package/dist/packages/mcp/assets/kb/app-help/standing-mandates.md +1 -1
  183. package/dist/packages/mcp/assets/kb/app-help/trade-ledger.md +54 -0
  184. package/dist/packages/mcp/assets/kb/app-help/use-local-model.md +38 -0
  185. package/dist/packages/mcp/assets/kb/docs/docs-agents-agent-paper-trading.md +7 -3
  186. package/dist/packages/mcp/assets/kb/docs/docs-agents-reading-your-trade-ledger.md +31 -0
  187. package/dist/packages/mcp/assets/kb/docs/docs-agents-untrusted-by-default.md +1 -1
  188. package/dist/packages/mcp/assets/kb/docs/docs-analyst-if-claude-is-missing.md +1 -1
  189. package/dist/packages/mcp/assets/kb/docs/docs-analyst-starting-a-run.md +2 -0
  190. package/dist/packages/mcp/assets/kb/docs/docs-analyst-use-a-local-model-instead.md +27 -0
  191. package/dist/packages/mcp/assets/kb/docs/docs-analyst-what-it-sees.md +2 -0
  192. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-backtests-and-their-limits.md +56 -0
  193. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-change-a-strategy-by-chat.md +49 -0
  194. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-checking-the-record-of-a-run.md +28 -0
  195. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-comparing-them.md +41 -0
  196. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-exits-and-wakes.md +76 -0
  197. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-how-a-stop-reaches-the-runner.md +32 -0
  198. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-on-the-q-screen.md +37 -0
  199. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-one-strategy-or-all-of-them.md +35 -0
  200. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-paper-accounts.md +50 -0
  201. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-presets.md +37 -0
  202. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-running-it-all-day.md +31 -0
  203. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-sessions-and-their-limits.md +31 -0
  204. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-several-strategies-at-once.md +33 -0
  205. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-stopping-it.md +35 -0
  206. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-strategies-and-presets.md +34 -0
  207. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-autotrade-json-file.md +48 -0
  208. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-heartbeat.md +27 -0
  209. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-run-file-and-the-heartbeat.md +34 -0
  210. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-the-runner-and-candidate-sessions.md +45 -0
  211. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-each-session-shows-first.md +31 -0
  212. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-costs-claude-usage-per-session.md +58 -0
  213. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-is-paper-self-directed.md +43 -0
  214. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-what-it-will-not-do.md +31 -0
  215. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-which-account-day-caps-and-the-usage-guard.md +44 -0
  216. package/dist/packages/mcp/assets/kb/docs/docs-autotrade-your-strategies.md +37 -0
  217. package/dist/packages/mcp/assets/kb/docs/docs-autotrade.md +22 -0
  218. package/dist/packages/mcp/assets/kb/docs/docs-beginners-before-any-experimental-trading.md +2 -2
  219. package/dist/packages/mcp/assets/kb/docs/docs-beginners.md +1 -1
  220. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-markers.md +30 -0
  221. package/dist/packages/mcp/assets/kb/docs/docs-configuration-chart-y-axis.md +32 -0
  222. package/dist/packages/mcp/assets/kb/docs/docs-configuration-environment-variables.md +4 -1
  223. package/dist/packages/mcp/assets/kb/docs/docs-configuration-files-on-disk.md +1 -0
  224. package/dist/packages/mcp/assets/kb/docs/docs-configuration-headless-recorder-dexterm-record.md +3 -1
  225. package/dist/packages/mcp/assets/kb/docs/docs-configuration-price-display-mode.md +9 -7
  226. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-2-the-one-key-mirror-ticket-a-proposal.md +1 -1
  227. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-stage-3-capped-auto-mirror-default-off-less.md +1 -1
  228. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading-what-is-proven-what-is-not.md +1 -1
  229. package/dist/packages/mcp/assets/kb/docs/docs-copy-trading.md +1 -1
  230. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-recorded-history-and-retention.md +36 -0
  231. package/dist/packages/mcp/assets/kb/docs/docs-data-sources-sources.md +1 -0
  232. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-first-run.md +1 -1
  233. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-install.md +3 -3
  234. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-requirements.md +1 -1
  235. package/dist/packages/mcp/assets/kb/docs/docs-getting-started-running-the-tests.md +1 -1
  236. package/dist/packages/mcp/assets/kb/docs/docs-getting-started.md +1 -1
  237. package/dist/packages/mcp/assets/kb/docs/docs-larp-measured-accuracy.md +1 -0
  238. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-larp-score.md +60 -0
  239. package/dist/packages/mcp/assets/kb/docs/docs-larp-the-verdict.md +0 -2
  240. package/dist/packages/mcp/assets/kb/docs/docs-larp-what-it-looks-at.md +4 -1
  241. package/dist/packages/mcp/assets/kb/docs/docs-larp.md +1 -1
  242. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-changed-in-0-1-7.md +26 -0
  243. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-stopping.md +28 -0
  244. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-the-short-version.md +34 -0
  245. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-using-a-trading-bot-with-dexterm.md +33 -0
  246. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does-not-do.md +30 -0
  247. package/dist/packages/mcp/assets/kb/docs/docs-live-trading-what-dexterm-does.md +29 -0
  248. package/dist/packages/mcp/assets/kb/docs/docs-live-trading.md +23 -0
  249. package/dist/packages/mcp/assets/kb/docs/docs-ollama-another-machine.md +28 -0
  250. package/dist/packages/mcp/assets/kb/docs/docs-ollama-autotrade-on-a-local-model.md +29 -0
  251. package/dist/packages/mcp/assets/kb/docs/docs-ollama-check-your-model-dexterm-ai-probe.md +38 -0
  252. package/dist/packages/mcp/assets/kb/docs/docs-ollama-how-we-checked.md +27 -0
  253. package/dist/packages/mcp/assets/kb/docs/docs-ollama-models-without-tool-calling.md +32 -0
  254. package/dist/packages/mcp/assets/kb/docs/docs-ollama-pick-the-engine-globally-or-per-surface.md +43 -0
  255. package/dist/packages/mcp/assets/kb/docs/docs-ollama-restriction-profiles.md +35 -0
  256. package/dist/packages/mcp/assets/kb/docs/docs-ollama-set-it-up-in-two-minutes.md +39 -0
  257. package/dist/packages/mcp/assets/kb/docs/docs-ollama-troubleshooting.md +35 -0
  258. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-runs-on-a-local-model.md +39 -0
  259. package/dist/packages/mcp/assets/kb/docs/docs-ollama-what-stays-outside-the-model.md +36 -0
  260. package/dist/packages/mcp/assets/kb/docs/docs-ollama-which-model.md +38 -0
  261. package/dist/packages/mcp/assets/kb/docs/docs-ollama-your-own-agent-on-the-local-model.md +28 -0
  262. package/dist/packages/mcp/assets/kb/docs/docs-ollama.md +28 -0
  263. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-letting-an-agent-paper-trade-experimental.md +3 -4
  264. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet-read-only.md +30 -0
  265. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-order-plans-and-standing-mandates.md +4 -13
  266. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-panic.md +4 -7
  267. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-paper-only-mandates.md +40 -0
  268. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-pnl-cards.md +7 -7
  269. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-safety-model.md +34 -0
  270. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-ticket.md +3 -4
  271. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-trade-ledger.md +42 -0
  272. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-wallet.md +4 -5
  273. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-trade-moments.md +4 -5
  274. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading.md +4 -5
  275. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-buy-and-sell-markers.md +37 -0
  276. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-market-cap-axis.md +1 -1
  277. package/dist/packages/mcp/assets/kb/docs/docs-popup-chart-what-it-shows.md +3 -1
  278. package/dist/packages/mcp/assets/kb/docs/docs-scan-the-read.md +2 -0
  279. package/dist/packages/mcp/assets/kb/docs/docs-screens-autotrade-paper.md +26 -0
  280. package/dist/packages/mcp/assets/kb/docs/docs-screens-dev-history.md +10 -7
  281. package/dist/packages/mcp/assets/kb/docs/docs-screens-order-plans.md +1 -1
  282. package/dist/packages/mcp/assets/kb/docs/docs-screens-token-screen.md +2 -0
  283. package/dist/packages/mcp/assets/kb/docs/docs-spectator-follow-over-mcp-in-the-running-terminal.md +1 -1
  284. package/dist/packages/mcp/assets/kb/docs/docs-spectator-limits.md +2 -0
  285. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-agent-hud-spectator-v2.md +58 -0
  286. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-brain-view-and-the-pnl-chip.md +46 -0
  287. package/dist/packages/mcp/assets/kb/docs/docs-spectator-the-three-surfaces.md +1 -1
  288. package/dist/packages/mcp/assets/kb/docs/docs-spectator.md +1 -1
  289. package/dist/packages/mcp/assets/kb/docs/docs-telegram-approve-one-tap-entries.md +50 -0
  290. package/dist/packages/mcp/assets/kb/docs/docs-telegram-control-the-commands.md +43 -0
  291. package/dist/packages/mcp/assets/kb/docs/docs-telegram-if-something-goes-wrong.md +30 -0
  292. package/dist/packages/mcp/assets/kb/docs/docs-telegram-notify-what-reaches-your-phone.md +41 -0
  293. package/dist/packages/mcp/assets/kb/docs/docs-telegram-set-it-up.md +47 -0
  294. package/dist/packages/mcp/assets/kb/docs/docs-telegram-the-record.md +26 -0
  295. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-is.md +33 -0
  296. package/dist/packages/mcp/assets/kb/docs/docs-telegram-what-it-will-not-do.md +31 -0
  297. package/dist/packages/mcp/assets/kb/docs/docs-telegram.md +23 -0
  298. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-common-questions.md +1 -1
  299. package/dist/packages/mcp/assets/kb/docs/docs-troubleshooting-doctor-lines.md +4 -0
  300. package/dist/packages/mcp/assets/kb/docs/docs-voice-linux-and-windows.md +1 -1
  301. package/dist/packages/mcp/assets/kb/docs/docs-voice.md +1 -1
  302. package/dist/packages/mcp/assets/kb/docs/docs-yolo-1-replay-it-on-your-own-history.md +42 -0
  303. package/dist/packages/mcp/assets/kb/docs/docs-yolo-2-run-it-on-paper.md +34 -0
  304. package/dist/packages/mcp/assets/kb/docs/docs-yolo-3-watch-it-and-stop-it.md +32 -0
  305. package/dist/packages/mcp/assets/kb/docs/docs-yolo-the-rules.md +37 -0
  306. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-is.md +34 -0
  307. package/dist/packages/mcp/assets/kb/docs/docs-yolo-what-it-will-not-do.md +31 -0
  308. package/dist/packages/mcp/assets/kb/docs/docs-yolo.md +26 -0
  309. package/dist/packages/mcp/assets/kb/entries/autotrade.md +38 -0
  310. package/dist/packages/mcp/src/app-help.js +1 -1
  311. package/dist/packages/mcp/src/autotrade-tools.js +944 -0
  312. package/dist/packages/mcp/src/backtest-requests.js +71 -0
  313. package/dist/packages/mcp/src/engine/config.js +159 -0
  314. package/dist/packages/mcp/src/engine/index.js +10 -0
  315. package/dist/packages/mcp/src/engine/loop.js +101 -0
  316. package/dist/packages/mcp/src/engine/mcp-client.js +123 -0
  317. package/dist/packages/mcp/src/engine/ollama.js +258 -0
  318. package/dist/packages/mcp/src/engine/probe.js +110 -0
  319. package/dist/packages/mcp/src/engine/reason.js +35 -0
  320. package/dist/packages/mcp/src/engine/restrictions.js +111 -0
  321. package/dist/packages/mcp/src/index.js +7 -1
  322. package/dist/packages/mcp/src/ledger-tools.js +346 -0
  323. package/dist/packages/mcp/src/paper-agent.js +5 -2
  324. package/dist/packages/mcp/src/server.js +285 -36
  325. package/dist/packages/mcp/src/spectator-sink.js +85 -0
  326. package/dist/packages/packs/src/backtest.js +3 -1
  327. package/dist/packages/packs/src/index.js +1 -0
  328. package/dist/packages/packs/src/larp-score-backtest.js +119 -0
  329. package/dist/packages/popup/src/pages/chart-lib.js +169 -35
  330. package/dist/packages/popup/src/pages/chart.css +18 -0
  331. package/dist/packages/popup/src/pages/chart.html +6 -1
  332. package/dist/packages/popup/src/pages/chart.js +136 -10
  333. package/dist/packages/popup/src/pages/spectator.css +45 -0
  334. package/dist/packages/popup/src/pages/spectator.html +1 -0
  335. package/dist/packages/popup/src/pages/spectator.js +69 -0
  336. package/dist/packages/popup/src/protocol.js +4 -0
  337. package/dist/packages/popup/src/server.js +9 -1
  338. package/dist/packages/recorder/src/dev-history.js +7 -3
  339. package/dist/packages/recorder/src/index.js +2 -0
  340. package/dist/packages/recorder/src/recorder.js +63 -6
  341. package/dist/packages/recorder/src/retention.js +100 -0
  342. package/dist/packages/recorder/src/schema.js +34 -1
  343. package/dist/packages/recorder/src/ticks.js +237 -0
  344. package/dist/packages/tui/src/canvas.js +7 -0
  345. package/dist/packages/tui/src/fmt.js +53 -0
  346. package/dist/packages/tui/src/index.js +1 -0
  347. package/dist/packages/tui/src/keymap.js +25 -1
  348. package/dist/packages/tui/src/markers.js +219 -0
  349. package/dist/packages/tui/src/widgets.js +158 -6
  350. package/package.json +2 -1
  351. package/dist/packages/data/src/trade/caps.js +0 -323
  352. package/dist/packages/data/src/trade/executor.js +0 -933
  353. package/dist/packages/data/src/trade/plans/executor-live.js +0 -220
  354. package/dist/packages/data/src/trade/rpc-exec.js +0 -229
  355. package/dist/packages/data/src/trade/signer/client.js +0 -197
  356. package/dist/packages/data/src/trade/signer/fast-wallet.js +0 -154
  357. package/dist/packages/data/src/trade/signer/host.js +0 -97
  358. package/dist/packages/data/src/trade/signer/main.js +0 -121
  359. package/dist/packages/data/src/trade/signer/probe-builder.js +0 -85
  360. package/dist/packages/data/src/trade/signer/protocol.js +0 -92
  361. package/dist/packages/data/src/trade/signer/service.js +0 -400
  362. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +0 -98
  363. package/dist/packages/data/src/trade/sol-usd.js +0 -86
  364. package/dist/packages/data/src/trade/swap-build.js +0 -108
  365. package/dist/packages/data/src/trade/token-delta.js +0 -201
  366. package/dist/packages/data/vendor/solana-web3/LICENSE +0 -20
  367. package/dist/packages/data/vendor/solana-web3/NOTICE.md +0 -13
  368. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +0 -20
  369. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-a-live-ticket.md +0 -42
  370. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-before-you-press-size-vs-caps.md +0 -34
  371. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-devnet-signer-mainnet-token.md +0 -35
  372. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-jupiter-rate-limits.md +0 -33
  373. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-link-a-wallet.md +0 -32
  374. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-live-trading-devnet-first.md +0 -30
  375. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-mainnet.md +0 -31
  376. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-live-fire-path-devnet-the-fast-wallet.md +0 -41
  377. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-the-signer-process.md +0 -34
  378. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-what-the-live-path-keeps.md +0 -37
  379. package/dist/packages/mcp/assets/kb/docs/docs-paper-trading-your-first-live-trade.md +0 -41
@@ -9,11 +9,12 @@
9
9
  // changed, otherwise just the last two bars ("tail"), so a live tick costs the page one `series.update`.
10
10
  // When the coin is held (task-1323 PositionSource) the VM carries the position row + entry/stop/target levels
11
11
  // and the fills of the current position from the paper ledger. Read-only: nothing here can trade.
12
- import { createPopupManager, loadConfig, nativeShellAvailable, TerminalDock, CHART_INTERVALS } from '../../popup/src/index.js';
12
+ import { createPopupManager, loadConfig, nativeShellAvailable, TerminalDock, CHART_INTERVALS, CHART_MARKS_MAX } from '../../popup/src/index.js';
13
13
  import { fmtPrice, fmtNum } from '../../tui/src/index.js';
14
14
  import { positionRow } from "./live/trade.js";
15
15
  import { signedUsd, signedPctOf, fmtUsdFine, sol as fmtSol, signedSol } from "./screens/positions.js";
16
- import { supplyRatio, positionPriceCells } from "./display.js";
16
+ import { supplyRatio, positionPriceCells, levelCell } from "./display.js";
17
+ import { chipSegments } from "./screens/pnl-chip.js";
17
18
  /** Map the app's active Theme to the popup protocol's theme-token subset. */
18
19
  export function popupThemeFrom(t) {
19
20
  return {
@@ -25,6 +26,11 @@ export function popupThemeFrom(t) {
25
26
  /** Most bars one VM carries (the 1m base is 1000 bars; this only guards a future bigger base). */
26
27
  export const CHART_MAX_BARS = 1500;
27
28
  export const DEFAULT_CHART_PREFS = { style: 'candles', scale: 'auto', indicators: ['volume', 'tape', 'ema9', 'ema21'] };
29
+ /** task-1483: the VM's markers layer: the newest CHART_MARKS_MAX fills (oldest first), and how many older ones were left out. */
30
+ export function chartMarksFrom(on, fills) {
31
+ const keep = fills.length > CHART_MARKS_MAX ? fills.slice(-CHART_MARKS_MAX) : fills;
32
+ return { on, fills: keep.map((f) => ({ t: f.t, side: f.side, source: f.source, priceUsd: f.priceUsd, tokens: f.tokens, sol: f.sol })), truncated: fills.length - keep.length };
33
+ }
28
34
  const finitePos = (n) => typeof n === 'number' && Number.isFinite(n) && n > 0;
29
35
  /** Token amounts up to trillions (same rule as the terminal's position row). */
30
36
  function fmtAmt(n) { return n >= 1e12 ? `${(n / 1e12).toFixed(2)}T` : fmtNum(n); }
@@ -56,9 +62,9 @@ export function positionCells(r, mode = 'price', supply = null) {
56
62
  if (r.realizedSol !== 0)
57
63
  cells.push({ label: 'REALIZED', value: usd && r.realizedUsd !== null ? signedUsd(r.realizedUsd) : `${signedSol(r.realizedSol)} SOL`, tone: tone(r.realizedSol) });
58
64
  if (r.stopUsd !== null)
59
- cells.push({ label: 'STOP', value: `${fmtPrice(r.stopUsd)}${r.stopDistPct !== null ? ` ${signedPctOf(r.stopDistPct)}` : ''}`, tone: 'down' });
65
+ cells.push({ label: 'STOP', value: `${levelCell(mode, supply, r.stopUsd)}${r.stopDistPct !== null ? ` ${signedPctOf(r.stopDistPct)}` : ''}`, tone: 'down' });
60
66
  if (r.targetUsd !== null)
61
- cells.push({ label: 'TARGET', value: `${fmtPrice(r.targetUsd)}${r.targetDistPct !== null ? ` ${signedPctOf(r.targetDistPct)}` : ''}`, tone: 'up' });
67
+ cells.push({ label: 'TARGET', value: `${levelCell(mode, supply, r.targetUsd)}${r.targetDistPct !== null ? ` ${signedPctOf(r.targetDistPct)}` : ''}`, tone: 'up' });
62
68
  return cells;
63
69
  }
64
70
  /** The chart's position model from the shared task-1323 selector (PositionSource → positionRow). */
@@ -137,7 +143,7 @@ export function chartVMFromView(v, x = {}) {
137
143
  vm.feed = x.feed ?? 'POLL';
138
144
  vm.livePrice = x.feed === 'LIVE' && finitePos(x.livePrice) ? x.livePrice : null;
139
145
  vm.trades = v.trades.map((t) => ({ t: t.t, side: t.side, usd: t.usd, price: t.price })); // no wallet addresses
140
- const mode = x.priceMode === 'mcap' ? 'mcap' : 'price';
146
+ const mode = x.priceMode === 'price' ? 'price' : 'mcap'; // task-1537: market cap unless the host says price
141
147
  const supply = mode === 'mcap' ? supplyRatio(v.price, v.mcap) : null;
142
148
  vm.position = x.held ? chartPositionFrom(x.held, tick, x.solUsd ?? null, x.positionKind ?? 'paper', x.fills ?? [], mode, supply) : null;
143
149
  vm.prefs = x.prefs ?? DEFAULT_CHART_PREFS;
@@ -146,6 +152,8 @@ export function chartVMFromView(v, x = {}) {
146
152
  // task-1366: display only — the bars stay USD prices; the page formats the axis as price × supply in mcap mode
147
153
  vm.priceMode = mode;
148
154
  vm.supply = supply;
155
+ if (x.marks)
156
+ vm.marks = chartMarksFrom(x.marks.on, x.marks.fills); // task-1483
149
157
  return vm;
150
158
  }
151
159
  /** Build the token-detail popup view model from the live TokenView (reused by the openPopup interface / Jarvis). */
@@ -160,6 +168,12 @@ export function tokenDetailVMFromView(v, intel = null) {
160
168
  flags: v.flags.map((f) => String(f)), holdersSample: v.holdersSample, updatedAt: v.updatedAt,
161
169
  };
162
170
  }
171
+ /** task-1473 r2: the chip as the chart page draws it (same segments as the terminal). */
172
+ export function chartChipFrom(m) {
173
+ if (!m)
174
+ return null;
175
+ return { kind: m.kind, symbol: m.symbol, tone: m.pnlPct === null ? 'muted' : m.pnlPct >= 0 ? 'up' : 'down', newPeak: m.newPeak, segments: chipSegments(m) };
176
+ }
163
177
  /** "docked right of Terminal" / "screen right (unknown terminal)" — for the TUI toast. */
164
178
  export function describeDock(p) {
165
179
  if (!p)
@@ -432,6 +446,22 @@ export class TokenPopups {
432
446
  case 'chart.scale':
433
447
  this.prefs = { ...this.prefs, scale: p.scale };
434
448
  return;
449
+ case 'chart.axis': { // task-1472: a display preference the terminal saves; nothing else
450
+ if (p.axis !== 'mcap' && p.axis !== 'price')
451
+ return;
452
+ this.source?.setChartAxis?.(p.axis);
453
+ if (this.lastView)
454
+ this.push(this.lastView);
455
+ return;
456
+ }
457
+ case 'chart.markers': { // task-1483: a display preference the terminal saves; nothing else
458
+ if (typeof p.on !== 'boolean')
459
+ return;
460
+ this.source?.setChartMarkers?.(p.on);
461
+ if (this.lastView)
462
+ this.push(this.lastView);
463
+ return;
464
+ }
435
465
  case 'chart.painted': {
436
466
  const ms = Number(p.ms);
437
467
  if (p.kind === 'first' && this.metrics.openedAt !== null && this.metrics.openToFirstPaintMs === null)
@@ -520,8 +550,10 @@ export class TokenPopups {
520
550
  prefs: this.prefs,
521
551
  now: this.now(),
522
552
  image: src.imagePath?.(view) ?? null,
523
- priceMode: src.priceMode?.() ?? 'price',
553
+ priceMode: src.chartAxis?.() ?? src.priceMode?.() ?? 'mcap', // task-1472: the chart axis drives the popup
554
+ marks: src.markFills ? { on: src.chartMarkers?.() ?? true, fills: src.markFills(view.mint) } : null, // task-1483
524
555
  });
556
+ vm.chip = chartChipFrom(src.pnlChip?.() ?? null); // task-1473 r2
525
557
  const key = vm.seriesKey ?? null;
526
558
  const n = vm.candles.length;
527
559
  const canTail = !full && this.ready && key !== null && key === this.lastKey && this.lastLen > 0 && n >= this.lastLen && n <= this.lastLen + 1;
@@ -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,8 +11,14 @@ export const SCAN_TICK_MS = 2_000;
11
11
  export const SCAN_RECOMPUTE_MS = 10_000;
12
12
  /** The analyst UI actions a scan session may request: NONE (observe-only). */
13
13
  export const SCAN_ACTION_TYPES = Object.freeze([]);
14
+ /** task-1473: append one line to the scan's session log (the analyst panel's brain view shows it). */
15
+ export function scanLog(scan, t, kind, symbol, text) {
16
+ scan.log.push({ t, kind, symbol: cleanText(symbol, 12), text });
17
+ if (scan.log.length > 40)
18
+ scan.log.splice(0, scan.log.length - 40);
19
+ }
14
20
  export function makeScanUi(cfg = {}) {
15
- return { engine: new ScanEngine(cfg), sig: '', lastTickAt: 0, lastComputeAt: 0, session: null, blocked: [], returnTo: null };
21
+ return { engine: new ScanEngine(cfg), sig: '', lastTickAt: 0, lastComputeAt: 0, session: null, blocked: [], returnTo: null, log: [] };
16
22
  }
17
23
  export const MODE_LABEL = { off: 'OFF', scanning: 'SCANNING', locked: 'LOCKED', paused: 'PAUSED' };
18
24
  const SHORT = { vol: 'vol', flow: 'flow', fresh: 'fresh', breakout: 'brk', liq: 'liq', holders: 'hold' };