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
@@ -0,0 +1,191 @@
1
+ // @dexterm/data/basis (task-1537): THE price-basis module. Market cap is the default way dexterm shows and talks
2
+ // about a coin ("entered at $69.7K, exited at $264.9K, 3.8x"); USD price per token is the alternative when the user
3
+ // sets `display.priceMode: "price"` in ~/.dexterm/config.json (or for one run: `--price` / DEXTERM_PRICE_BASIS=price).
4
+ //
5
+ // One resolver (`resolveBasis`) decides what a price slot shows, one formatter pair (`fmtCap` / `fmtUsdPrice`) draws it,
6
+ // and `basisLanguage` is the paragraph every model-facing prompt carries so the agents speak the same basis as the UI.
7
+ //
8
+ // Supply rule (shared with the PnL card, task-1518): a market cap the data source reports, else price × a supply that
9
+ // is KNOWN: the source's own mcap ÷ price, the recorded FDV ÷ price, or pump.fun's fixed 1B for a pump.fun coin. A
10
+ // supply is never guessed: with none, the slot shows the price and says why (`fallback`).
11
+ //
12
+ // Pure: no I/O, no imports. Display and wording only; nothing here is read by the signer, caps, plans or tickets.
13
+ // JSON outputs keep BOTH `price` and `mcap` fields always (`withMcap`); only the human-facing text switches.
14
+ export const PRICE_BASES = ['mcap', 'price'];
15
+ /** The default basis everywhere (UI, CLI text, agent language) when config, flag and env say nothing. */
16
+ export const DEFAULT_PRICE_BASIS = 'mcap';
17
+ /** One-run override: `DEXTERM_PRICE_BASIS=price dexterm …` (the CLI flags `--price` / `--mcap` set it too). */
18
+ export const PRICE_BASIS_ENV = 'DEXTERM_PRICE_BASIS';
19
+ export function isPriceBasis(v) { return v === 'mcap' || v === 'price'; }
20
+ /** The one-run override from the environment, or null when unset / not a basis. */
21
+ export function envPriceBasis(env) {
22
+ const v = env?.[PRICE_BASIS_ENV]?.trim().toLowerCase();
23
+ return isPriceBasis(v) ? v : null;
24
+ }
25
+ /**
26
+ * The basis in effect: the one-run override (flag/env) wins, then the config value (`display.priceMode`), then mcap.
27
+ * A config written before task-1537 is migrated (custom v4 → v5), so `configured` is the user's saved choice.
28
+ */
29
+ export function pickBasis(configured, env) {
30
+ return envPriceBasis(env) ?? (isPriceBasis(configured) ? configured : DEFAULT_PRICE_BASIS);
31
+ }
32
+ // ------------------------------------------------------------------------------------------------ formatters
33
+ const SUB = '₀₁₂₃₄₅₆₇₈₉';
34
+ const trim = (s) => (s.includes('.') ? s.replace(/0+$/, '').replace(/\.$/, '') : s);
35
+ /**
36
+ * A market cap (or any large USD amount) in sentences, rows and lines: `$950`, `$69.7K`, `$264.9K`, `$1.2M`, `$1.23M`,
37
+ * `$4.5B`. K keeps one decimal, M / B / T two, trailing zeros dropped; the unit is promoted AFTER rounding so 999 960
38
+ * reads `$1M`, never `$1000K`. Null / non-finite → '—'. (Narrow table cells use the 3-significant-digit tui form.)
39
+ */
40
+ export function fmtCap(n) {
41
+ if (n === null || n === undefined || !Number.isFinite(n))
42
+ return '—';
43
+ const sign = n < 0 ? '-' : '';
44
+ const a = Math.abs(n);
45
+ if (a < 999.5)
46
+ return `${sign}$${a < 1 ? trim(a.toFixed(2)) : String(Math.round(a))}`;
47
+ const units = ['K', 'M', 'B', 'T'];
48
+ let u = 0, v = a / 1000;
49
+ while (u < units.length - 1 && v >= 1000) {
50
+ v /= 1000;
51
+ u++;
52
+ }
53
+ const dp = (i) => (i === 0 ? 1 : 2);
54
+ let r = Number(v.toFixed(dp(u)));
55
+ if (r >= 1000 && u < units.length - 1) {
56
+ u++;
57
+ r = Number((r / 1000).toFixed(dp(u)));
58
+ }
59
+ return `${sign}$${trim(r.toFixed(dp(u)))}${units[u]}`;
60
+ }
61
+ /**
62
+ * A USD price per token, the usual small-number form with subscript zeros: `$0.0₄123`, `$0.0123`, `$1.234`, `$142.50`.
63
+ * Matches `fmtPrice` in @dexterm/tui (tested), so a price reads the same on every surface.
64
+ */
65
+ export function fmtUsdPrice(p) {
66
+ if (p === null || p === undefined || !Number.isFinite(p))
67
+ return '—';
68
+ if (p < 0)
69
+ return `-${fmtUsdPrice(-p)}`;
70
+ if (p >= 1000)
71
+ return `$${p.toLocaleString('en-US', { maximumFractionDigits: 0 })}`;
72
+ if (p >= 1)
73
+ return `$${p.toFixed(p >= 100 ? 2 : 3)}`;
74
+ if (p >= 0.01)
75
+ return `$${p.toFixed(4)}`;
76
+ const m = p.toFixed(12).match(/^0\.(0*)(\d+)/);
77
+ if (!m)
78
+ return `$${p}`;
79
+ const zeros = m[1].length;
80
+ const digits = m[2].replace(/0+$/, '').slice(0, 4) || '0';
81
+ if (zeros < 3)
82
+ return `$0.${m[1]}${digits}`;
83
+ return `$0.0${String(zeros).split('').map((c) => SUB[Number(c)]).join('')}${digits}`;
84
+ }
85
+ /** An x-multiple between two levels of the same basis: `3.8x`, `0.42x`, `12x`. Null when either side is unusable. */
86
+ export function fmtX(from, to) {
87
+ if (from === null || from === undefined || to === null || to === undefined || !Number.isFinite(from) || !Number.isFinite(to) || from <= 0 || to < 0)
88
+ return null;
89
+ const x = to / from;
90
+ return `${x >= 10 ? x.toFixed(0) : x >= 1 ? x.toFixed(1) : x.toFixed(2)}x`;
91
+ }
92
+ // ------------------------------------------------------------------------------------------------ supply + resolver
93
+ /** pump.fun mints a fixed 1,000,000,000 supply per coin (none is minted later), so its market cap is price × 1B. */
94
+ export const PUMP_FUN_SUPPLY = 1_000_000_000;
95
+ const pos = (n) => n !== null && n !== undefined && Number.isFinite(n) && n > 0;
96
+ /**
97
+ * The KNOWN supply for a coin, or null. Order: an explicit supply; the source's mcap ÷ price; recorded FDV ÷ price
98
+ * (a fully diluted cap on a non-pump coin, and the source says so); pump.fun's fixed 1B for a pump.fun coin (by
99
+ * launchpad, or a `…pump` mint with no launchpad). Never guessed.
100
+ */
101
+ export function knownSupply(h) {
102
+ const via = h.via ? ` (${h.via})` : '';
103
+ if (pos(h.supply))
104
+ return { tokens: h.supply, source: `recorded supply${via}` };
105
+ if (pos(h.mcap) && pos(h.price) && Number.isFinite(h.mcap / h.price))
106
+ return { tokens: h.mcap / h.price, source: `market cap ÷ price${via}` };
107
+ if (pos(h.fdv) && pos(h.price) && Number.isFinite(h.fdv / h.price))
108
+ return { tokens: h.fdv / h.price, source: `recorded FDV ÷ price${via}` };
109
+ const mint = h.mint ?? '';
110
+ if (h.launchpad === 'pump.fun' || ((!h.launchpad || h.launchpad === 'unknown') && mint.endsWith('pump')))
111
+ return { tokens: PUMP_FUN_SUPPLY, source: 'pump.fun fixed supply' };
112
+ return null;
113
+ }
114
+ /** Why a slot that wanted market cap shows a price instead (said next to the number, never hidden). */
115
+ export const NO_SUPPLY_REASON = 'market cap unknown: no supply recorded, showing price';
116
+ /**
117
+ * THE resolver every screen, line, table and prompt calls: the headline figure for one price slot.
118
+ * `price` is the USD (or the caller's unit) price at that moment; `mcap` a cap the source reports for the SAME moment.
119
+ * A historical price (an entry fill) is turned into a cap with a known supply (`supply` / FDV / pump.fun 1B).
120
+ */
121
+ export function resolveBasis(want, o) {
122
+ const price = Number.isFinite(o.price) ? o.price : null;
123
+ const sup = knownSupply(o);
124
+ const mcap = pos(o.mcap) && !pos(o.supply) ? o.mcap : price !== null && sup ? price * sup.tokens : null;
125
+ const pText = fmtUsdPrice(price);
126
+ if (want === 'mcap' && mcap !== null) {
127
+ return { want, basis: 'mcap', price, mcap, text: fmtCap(mcap), both: price !== null ? `${fmtCap(mcap)} mcap (${pText})` : `${fmtCap(mcap)} mcap`, fallback: null, supply: sup };
128
+ }
129
+ const fallback = want === 'mcap' && price !== null ? NO_SUPPLY_REASON : null;
130
+ const both = mcap !== null ? `${pText} (mcap ${fmtCap(mcap)})` : fallback ? `${pText} (${fallback})` : pText;
131
+ return { want, basis: 'price', price, mcap, text: pText, both, fallback, supply: sup };
132
+ }
133
+ /** A level in a sentence: `$69.7K` (mcap) or `$0.0₄697` (price, or the mcap fallback with `· supply unknown`). */
134
+ export function fmtLevel(want, o) {
135
+ const v = resolveBasis(want, o);
136
+ return v.fallback ? `${v.text} (price; supply unknown)` : v.text;
137
+ }
138
+ /**
139
+ * An entry → exit (or → now) sentence in the basis: mcap `entered at $69.7K, exited at $264.9K (3.8x)`, price
140
+ * `entered at $0.0₄697, exited at $0.0₃265 (3.8x)`. Both levels use the same supply, so the multiple is the same in
141
+ * either basis. `exit` null with `now` → `…, now $120K (1.7x)`.
142
+ */
143
+ export function entryExitLine(want, o) {
144
+ const sup = knownSupply({ ...o, mcap: o.mcap, price: o.price });
145
+ const asCap = want === 'mcap' && sup !== null;
146
+ const f = (p) => (p === null || p === undefined || !Number.isFinite(p) ? '—' : asCap ? fmtCap(p * sup.tokens) : fmtUsdPrice(p));
147
+ const end = o.exit !== null && o.exit !== undefined ? o.exit : o.now ?? null;
148
+ const verb = o.exit !== null && o.exit !== undefined ? 'exited at' : 'now';
149
+ const x = fmtX(o.entry, end);
150
+ const tail = want === 'mcap' && !asCap ? ' (prices: supply unknown)' : '';
151
+ return `entered at ${f(o.entry)}${end !== null ? `, ${verb} ${f(end)}` : ''}${x ? ` (${x})` : ''}${tail}`;
152
+ }
153
+ /**
154
+ * JSON keeps BOTH numbers: return `obj` with a market-cap twin for each price key (`pairs` maps price key → cap key,
155
+ * e.g. `{ price: 'mcap', entryPrice: 'entryMcap' }`), from a known supply, else null. A cap already on the object (a
156
+ * source-reported one) is kept. Never removes or changes a price field.
157
+ */
158
+ export function withMcap(obj, pairs, h) {
159
+ const sup = knownSupply(h);
160
+ const out = { ...obj };
161
+ for (const [k, name] of Object.entries(pairs)) {
162
+ if (typeof out[name] === 'number' && Number.isFinite(out[name]))
163
+ continue;
164
+ const p = obj[k];
165
+ out[name] = typeof p === 'number' && Number.isFinite(p) && sup ? p * sup.tokens : null;
166
+ }
167
+ return out;
168
+ }
169
+ // ------------------------------------------------------------------------------------------------ agent language
170
+ /** A short hint for tool descriptions (MCP, agent tool table) so external agents also speak the basis. */
171
+ export const MCAP_FIELD_HINT = 'Every price field has a market-cap twin (`mcap`, `entryMcap`, …; null when the supply is unknown). basis: talk in market cap by default (cap at entry, cap now, targets/stops as cap levels, x-multiples); use the price fields only when the user\'s display.priceMode is "price" or the cap is null.';
172
+ /**
173
+ * The paragraph every model-facing prompt carries (the chat assistant, the autotrade candidate agent, help answers).
174
+ * Follows the basis AT RUN TIME: tests assert both texts.
175
+ */
176
+ export function basisLanguage(basis) {
177
+ if (basis === 'price') {
178
+ return [
179
+ 'PRICE BASIS: USD PRICE (the user set display.priceMode = "price").',
180
+ 'Talk in USD price per token: the price at entry, the price now, targets and stops as price levels (with x-multiples).',
181
+ 'Quote market cap only as a secondary number in brackets ("$0.0₄697 (mcap $69.7K)"). Tools return both `price` and `mcap` fields.',
182
+ ].join(' ');
183
+ }
184
+ return [
185
+ 'PRICE BASIS: MARKET CAP (the default; display.priceMode = "mcap").',
186
+ 'Talk about a coin the way traders do, in market cap: the cap at entry and the cap now ("in at $69.7K, now $264.9K, 3.8x"),',
187
+ 'targets and stops as market-cap levels ("take profit at $500K, stop at $40K"), and moves as x-multiples.',
188
+ 'Use the `mcap` / `*Mcap` fields tools return; write caps as $69.7K / $1.2M. A per-token price only in brackets when it helps,',
189
+ 'or alone when the cap is null (the supply is unknown): then say "price only, supply unknown". Never invent a supply.',
190
+ ].join(' ');
191
+ }
@@ -3,7 +3,10 @@
3
3
  // stale-while-revalidate, so the detail screen and the MCP tools share one
4
4
  // set of requests.
5
5
  import { TTL } from "./cache.js";
6
- const NS = { mint: 'rpc:mint', holders: 'rpc:holders', rug: 'rugcheck', pools: 'gt:pools', goplus: 'goplus', trades: 'gt:trades' };
6
+ import { classifyHolder, EX_POOL_TAGS } from "./intel/holder-class.js";
7
+ const NS = { mint: 'rpc:mint', holders: 'rpc:holders', ownerMeta: 'rpc:owner-meta', rug: 'rugcheck', pools: 'gt:pools', goplus: 'goplus', trades: 'gt:trades' };
8
+ /** The program that owns an account (a pool, a curve PDA, a wallet) does not change: a week per owner. */
9
+ const OWNER_META_TTL = 7 * 86_400_000;
7
10
  /** Pools for a mint change rarely; one lookup per hour per mint. */
8
11
  const POOLS_TTL = 3_600_000;
9
12
  /** The trades tape shares GeckoTerminal's 10/min bucket with the candles: refresh at most every 20 s per pool. */
@@ -51,7 +54,7 @@ export class TokenDetail {
51
54
  return this.cache.fetch(NS.holders, mint, async () => {
52
55
  const info = (await this.mintInfo(mint)).value;
53
56
  try {
54
- return await this.pool.topHolders(mint, { info });
57
+ return await this.exPool(await this.pool.topHolders(mint, { info }));
55
58
  }
56
59
  catch (rpcErr) {
57
60
  const agg = await this.aggregateHolders(mint, info).catch(() => null);
@@ -61,6 +64,54 @@ export class TokenDetail {
61
64
  }
62
65
  }, { ttlMs: TTL.holders, source: this.pool.primary.source });
63
66
  }
67
+ /**
68
+ * The HOLDERS panel's ex-pool rule (task-1367 `classifyHolder`) for every caller of `holders()` (the MCP
69
+ * `get_holders` / `get_dossier` concentration, the autotrade dossier, rug-check), task-1518. A top account whose
70
+ * owner is a pool, a bonding curve, a launch vault, a lock or the burn address is not a person's position: it gets
71
+ * `poolSide` (and `label: 'LP'` for a pool, curve or vault) and is left out of `top10Pct`. Without this, every young
72
+ * PumpSwap pool (the vault owner is the pool account) and every Meteora DAMM v2 pool (the global pool authority)
73
+ * read as one 80-95 % "holder". One extra getMultipleAccounts over the owners not yet known (no data, <= 20
74
+ * addresses, cached a week per owner: an account's program does not change), the same lookup the wallet-intel scan
75
+ * makes; when it fails, only the derived authorities and the pump.fun curve are recognised and every other owner
76
+ * stays counted (never hidden).
77
+ */
78
+ async exPool(h) {
79
+ if (h.aggregateOnly || !h.holders.length)
80
+ return h;
81
+ const owners = [...new Set(h.holders.map((x) => x.owner).filter((o) => !!o))];
82
+ const meta = new Map();
83
+ const misses = [];
84
+ for (const o of owners) {
85
+ const hit = this.cache.get(NS.ownerMeta, o);
86
+ if (hit)
87
+ meta.set(o, hit.value);
88
+ else
89
+ misses.push(o);
90
+ }
91
+ if (misses.length) {
92
+ try {
93
+ const got = await this.pool.accountMeta(misses);
94
+ // an address with no account yet can be created later: an hour for those, a week for accounts that exist
95
+ for (const [o, m] of got) {
96
+ meta.set(o, m);
97
+ this.cache.set(NS.ownerMeta, o, m, { ttlMs: m.exists ? OWNER_META_TTL : 3_600_000, source: this.pool.primary.source });
98
+ }
99
+ }
100
+ catch { /* counted, never hidden */ }
101
+ }
102
+ const holders = h.holders.map((x) => {
103
+ if (!x.owner || x.label)
104
+ return x;
105
+ const c = classifyHolder({ mint: h.mint, owner: x.owner, tokenAccounts: [x.tokenAccount], meta: meta.get(x.owner), cex: null, dev: null, poolRule: null, linked: null });
106
+ if (!EX_POOL_TAGS.has(c.tag))
107
+ return x;
108
+ return { ...x, ...(c.tag === 'POOL' || c.tag === 'CURVE' || c.tag === 'VAULT' ? { label: 'LP' } : {}), poolSide: { tag: c.tag, program: c.program, reason: c.reason } };
109
+ });
110
+ const sum = (xs) => Math.round(xs.reduce((s, x) => s + x.pct, 0) * 1000) / 1000;
111
+ const notCounted = new Set(['LP', 'cex', 'bridge']);
112
+ const counted = holders.filter((x) => !x.poolSide && !(x.label && notCounted.has(x.label))).slice(0, 10);
113
+ return { ...h, holders, top10Pct: h.top10Pct === null ? null : sum(counted), top10PctRaw: h.top10Pct === null ? null : sum(holders.slice(0, 10)) };
114
+ }
64
115
  /** Build an aggregate-only TopHolders from the keyless Jupiter holder data; null when unavailable. */
65
116
  async aggregateHolders(mint, info) {
66
117
  if (!this.holdersAggregate)
@@ -28,31 +28,19 @@ export * from "./credits.js";
28
28
  export * from "./budget.js";
29
29
  export * from "./watchlist.js";
30
30
  export * from "./paper.js";
31
- export * from "./trade/caps.js";
32
31
  export * from "./trade/bridge.js";
33
- export * from "./trade/swap-build.js";
34
- export * from "./trade/rpc-exec.js";
35
- export * from "./trade/executor.js";
36
32
  export * from "./trade/link-store.js";
37
33
  export * from "./trade/tx-decode.js";
38
- export * from "./trade/sol-usd.js";
39
34
  export * from "./trade/kill-switch.js";
40
- export * from "./trade/token-delta.js";
41
35
  // task-1443: the paper agent desk + its rendezvous (agents paper-trade inside an armed mandate; never sign/send).
42
36
  export * from "./trade/agent/paper-desk.js";
43
37
  export * from "./trade/agent/rendezvous.js";
44
- export * from "./trade/signer/protocol.js";
45
- export * from "./trade/signer/service.js";
46
- export * from "./trade/signer/host.js";
47
- export * from "./trade/signer/client.js";
48
- export * from "./trade/signer/probe-builder.js";
49
- export * from "./trade/signer/fast-wallet.js";
50
- export * from "./trade/signer/sol-usd-feed.js";
51
- export { parseSignerArgs, buildSignerService } from "./trade/signer/main.js";
52
38
  export * from "./larp/links.js";
53
39
  export * from "./larp/safe-fetch.js";
54
40
  export * from "./larp/evidence.js";
55
41
  export * from "./larp/link-baseline.js";
42
+ export * from "./larp/x-account.js";
43
+ export * from "./larp/score.js";
56
44
  // task-1401: parse a CA / coin link from any input (go-to, analyst chat, filter line, --mint, MCP refs).
57
45
  export * from "./coin-target.js";
58
46
  export * from "./analyst-actions.js";
@@ -0,0 +1,220 @@
1
+ // LARP score (task-1471): a 0-100 summary of the LARP check's flags. Pure, offline, deterministic.
2
+ //
3
+ // What it is: the transparent SUM of named flags, each with its points and the evidence that raised it. Higher
4
+ // means more of the things that go with LARP coins showed up. It is evidence, not a verdict: every flag can be
5
+ // innocent on its own, and the score never says a coin is fake, safe or worth anything.
6
+ //
7
+ // Missing data never lowers the score and is never scored as clean. A flag whose evidence could not be read (page
8
+ // blocked, RDAP down, the X sources unavailable) goes to `missing` with the points it COULD have added, and the
9
+ // range `[score, score + those points]` (and its band range) widens instead.
10
+ //
11
+ // Where the points come from (LARP_SCORE_WEIGHTS[*].source):
12
+ // - `calibrated`: the link-shape flags, fitted on the TRAIN split of the task-1401 eval set (1054 coins, 72 h
13
+ // outcome labels; packages/packs/src/larp-score-backtest.ts). points = clamp(round(8 × ln odds ratio of a bad
14
+ // outcome), 0, 12), using the flag only when ≥ 20 train coins carry it. The cap keeps link shape alone in the
15
+ // `medium` band at most: the larp-check skill never calls a coin LARP from links alone.
16
+ // - `prior`: page, domain, GitHub and X-account flags. The eval set does not hold that evidence point-in-time (the
17
+ // pages are gone or changed, and a deleted X account after a coin dies would leak the outcome), so these are
18
+ // pre-registered weights written from the skill text before any data, and are labelled as such everywhere.
19
+ import { linkSignals } from "./link-baseline.js";
20
+ export const LARP_SCORE_MODEL = 'larp-score-v1';
21
+ /**
22
+ * The shipped weights. The `calibrated` rows are asserted equal to a fresh fit on the committed eval set
23
+ * (packages/packs/test/larp-score-1471.test.ts), so a re-tune cannot ship stale points.
24
+ */
25
+ export const LARP_SCORE_WEIGHTS = {
26
+ // links (DexScreener / Jupiter advertised links): calibrated where the eval set had ≥ 20 train coins.
27
+ no_website: { group: 'links', label: 'no website advertised', points: 9, source: 'calibrated' },
28
+ no_x: { group: 'links', label: 'no X account advertised', points: 10, source: 'calibrated' },
29
+ x_not_account: { group: 'links', label: 'X link is a single post or a community, not an account', points: 5, source: 'calibrated' },
30
+ no_telegram: { group: 'links', label: 'no Telegram advertised', points: 12, source: 'calibrated' },
31
+ site_is_social: { group: 'links', label: 'the "website" is a social page', points: 1, source: 'calibrated' },
32
+ domain_no_name_match: { group: 'links', label: "the website's domain does not name the token", points: 0, source: 'calibrated' },
33
+ cheap_tld: { group: 'links', label: 'website on a throwaway-priced TLD', points: 0, source: 'calibrated' },
34
+ site_is_launchpad: { group: 'links', label: 'the "website" is a launchpad/explorer page', points: 4, source: 'prior' },
35
+ site_linktree: { group: 'links', label: 'the "website" is a link hub', points: 2, source: 'prior' },
36
+ site_free_host: { group: 'links', label: 'website on a free/site-builder host', points: 2, source: 'prior' },
37
+ links_mismatch: { group: 'links', label: 'sources advertise different sites or X accounts', points: 8, source: 'prior' },
38
+ // website (one SSRF-safe fetch, no JavaScript)
39
+ drainer_markers: { group: 'website', label: 'wallet-drainer markers on the page', points: 30, source: 'prior' },
40
+ default_page: { group: 'website', label: 'website is a default/placeholder page', points: 15, source: 'prior' },
41
+ wallet_connect: { group: 'website', label: 'website asks to connect a wallet', points: 8, source: 'prior' },
42
+ never_names_token: { group: 'website', label: 'website never names the token', points: 8, source: 'prior' },
43
+ // domain (RDAP)
44
+ domain_new: { group: 'domain', label: 'domain registered under 30 days ago', points: 8, source: 'prior' },
45
+ domain_privacy: { group: 'domain', label: 'domain behind registrant privacy', points: 2, source: 'prior' },
46
+ // GitHub (keyless API)
47
+ no_github: { group: 'github', label: 'no GitHub repo advertised', points: 2, source: 'prior' },
48
+ github_archived: { group: 'github', label: 'GitHub repo is archived', points: 4, source: 'prior' },
49
+ github_fork: { group: 'github', label: 'GitHub repo is a fork', points: 4, source: 'prior' },
50
+ github_stale: { group: 'github', label: 'GitHub repo not pushed in 180+ days', points: 4, source: 'prior' },
51
+ // X account (keyless lookup chain)
52
+ x_suspended: { group: 'x', label: 'X account is suspended', points: 20, source: 'prior' },
53
+ x_not_found: { group: 'x', label: 'X account does not exist', points: 15, source: 'prior' },
54
+ x_account_days_old: { group: 'x', label: 'X account under 7 days old', points: 12, source: 'prior' },
55
+ x_account_new: { group: 'x', label: 'X account under 30 days old', points: 6, source: 'prior' },
56
+ x_tiny_following: { group: 'x', label: 'X account has under 50 followers', points: 5, source: 'prior' },
57
+ x_no_posts: { group: 'x', label: 'X account has no posts in 30 days', points: 6, source: 'prior' },
58
+ x_handle_mismatch: { group: 'x', label: "the website's X link and the advertised X account differ", points: 10, source: 'prior' },
59
+ x_renamed: { group: 'x', label: 'the linked X post is now under another handle', points: 5, source: 'prior' },
60
+ x_no_site_link: { group: 'x', label: "the X account's bio/website does not link the coin's site", points: 3, source: 'prior' },
61
+ };
62
+ /** Band thresholds (pre-registered round numbers; their measured precision is in LARP_SCORE_MEASURED). */
63
+ export const LARP_SCORE_BANDS = { medium: 25, high: 50 };
64
+ export const bandOf = (score) => (score >= LARP_SCORE_BANDS.high ? 'high' : score >= LARP_SCORE_BANDS.medium ? 'medium' : 'low');
65
+ /**
66
+ * Held-out TEST-split numbers for the link-shape part of the score (the only part the eval set can replay),
67
+ * from packages/packs/eval/larp-score-backtest.json (`dexterm backtest --subject larp-score`). A test asserts these match the committed report.
68
+ */
69
+ export const LARP_SCORE_MEASURED = {
70
+ model: LARP_SCORE_MODEL,
71
+ eval: 'task-1401 eval set (built 2026-10-16), 72h outcome labels, bad = rug or dead',
72
+ part: 'link-shape flags only (page, domain, GitHub and X-account flags cannot be replayed on history)',
73
+ test: { n: 328, bad: 61, auroc: 0.691, by_band: { low: { n: 218, bad: 27 }, medium: { n: 110, bad: 34 }, high: { n: 0, bad: 0 } } },
74
+ train: { n: 726, bad: 294, auroc: 0.727 },
75
+ };
76
+ const W = LARP_SCORE_WEIGHTS;
77
+ const LINK_FLAG = {
78
+ site_is_social: 'site_is_social', site_is_launchpad: 'site_is_launchpad', site_linktree: 'site_linktree', site_free_host: 'site_free_host',
79
+ site_no_name_match: 'domain_no_name_match', cheap_tld: 'cheap_tld', x_not_profile: 'x_not_account', no_x: 'no_x', no_tg: 'no_telegram',
80
+ };
81
+ /** The link-shape flags of a feature set (shared by the live score and the backtest, so both score the same thing). */
82
+ export function linkFlagIds(f) {
83
+ const ids = linkSignals(f).map((s) => LINK_FLAG[s]);
84
+ if (f.web === 'none')
85
+ ids.unshift('no_website');
86
+ return ids;
87
+ }
88
+ /** Sum the link-shape points only (what the backtest scores). */
89
+ export function linkScore(f, weights = {}) {
90
+ return Math.min(100, linkFlagIds(f).reduce((a, id) => a + (weights[id] ?? W[id].points), 0));
91
+ }
92
+ /** Compute the LARP score from the gathered evidence. Pure. */
93
+ export function larpScore(ev) {
94
+ const flags = [];
95
+ const missing = [];
96
+ // A flag fitted to 0 points (it did not go with bad outcomes) is not listed: it would add nothing but noise.
97
+ const add = (id, evidence) => { const w = W[id]; if (w.points > 0)
98
+ flags.push({ id, group: w.group, label: w.label, points: w.points, weight: w.source, evidence }); };
99
+ const miss = (id, group, what, maxPoints) => { if (maxPoints > 0)
100
+ missing.push({ id, group, what, maxPoints }); };
101
+ const pts = (...ids) => ids.reduce((a, id) => a + W[id].points, 0);
102
+ if (ev.mode === 'token' && ev.link) {
103
+ const why = {
104
+ no_website: 'DexScreener/Jupiter list no website', no_x: 'DexScreener/Jupiter list no X link', no_telegram: 'DexScreener/Jupiter list no Telegram link',
105
+ x_not_account: `the advertised X link is a ${ev.link.x === 'status' ? 'single post' : ev.link.x === 'community' ? 'community' : 'non-account page'}`,
106
+ site_is_social: 'the advertised website is a social-network page', site_is_launchpad: 'the advertised website is a launchpad/explorer page',
107
+ site_linktree: 'the advertised website is a link hub', site_free_host: 'the advertised website is on a free/site-builder host',
108
+ domain_no_name_match: 'neither the symbol nor the name appears in the domain', cheap_tld: 'the domain is on a throwaway-priced TLD',
109
+ };
110
+ for (const id of linkFlagIds(ev.link))
111
+ add(id, why[id] ?? W[id].label);
112
+ }
113
+ if (ev.mismatch.length)
114
+ add('links_mismatch', ev.mismatch.join('; '));
115
+ // website page
116
+ if (ev.website) {
117
+ const p = ev.page;
118
+ if (!p || !p.ok)
119
+ miss('website:*', 'website', `website page not read${p && !p.ok ? ` (${p.reason})` : ''}`, pts('drainer_markers', 'default_page', 'wallet_connect', 'never_names_token'));
120
+ else {
121
+ if (p.drainerFlags.length)
122
+ add('drainer_markers', `markers: ${p.drainerFlags.slice(0, 3).join(', ')}`);
123
+ if (p.defaultPage)
124
+ add('default_page', 'the page matches a default/placeholder template');
125
+ if (p.walletConnect)
126
+ add('wallet_connect', 'the page carries a wallet-connect prompt');
127
+ if (p.mentionsSymbol === false && p.mentionsName === false)
128
+ add('never_names_token', 'neither the symbol nor the name appears in the page text');
129
+ else if (p.mentionsSymbol === null && p.mentionsName === null)
130
+ miss('never_names_token', 'website', 'whether the page names the token (no symbol/name to look for)', W.never_names_token.points);
131
+ }
132
+ const d = ev.domain;
133
+ if (!d || !d.ok)
134
+ miss('domain:*', 'domain', `domain registration not read${d && !d.ok ? ` (${d.reason})` : ''}`, pts('domain_new', 'domain_privacy'));
135
+ else {
136
+ if (typeof d.ageDays === 'number' && d.ageDays < 30)
137
+ add('domain_new', `registered ${d.ageDays} days ago (RDAP)`);
138
+ else if (d.ageDays === null)
139
+ miss('domain_new', 'domain', 'domain registration date', W.domain_new.points);
140
+ if (d.privacy === true)
141
+ add('domain_privacy', 'the RDAP registrant contact is redacted');
142
+ else if (d.privacy === null)
143
+ miss('domain_privacy', 'domain', 'registrant privacy', W.domain_privacy.points);
144
+ }
145
+ }
146
+ // GitHub
147
+ const g = ev.github;
148
+ if (g === 'none') {
149
+ if (ev.mode === 'token')
150
+ add('no_github', 'no github.com repo link among the advertised links or on the fetched page');
151
+ }
152
+ else if (g && !g.ok)
153
+ miss('github:*', 'github', `GitHub repo not read (${g.reason})`, pts('github_archived', 'github_fork', 'github_stale'));
154
+ else if (g) {
155
+ if (g.archived)
156
+ add('github_archived', 'the GitHub API marks the repo archived');
157
+ if (g.fork)
158
+ add('github_fork', 'the GitHub API marks the repo a fork');
159
+ if (typeof g.daysSincePush === 'number' && g.daysSincePush > 180)
160
+ add('github_stale', `last push ${g.daysSincePush} days ago`);
161
+ }
162
+ // X account
163
+ const x = ev.x;
164
+ if (x && x.handle) {
165
+ const at = `@${x.screenName ?? x.handle}`;
166
+ if (x.status === 'suspended')
167
+ add('x_suspended', `${at}: an X lookup source reports the account suspended`);
168
+ else if (x.status === 'not_found')
169
+ add('x_not_found', `@${x.handle}: two independent sources (vxtwitter, X oEmbed) found no such account`);
170
+ else if (x.status === 'unavailable')
171
+ miss('x:*', 'x', `X account not read (${x.unavailable[0] ?? 'every source failed'})`, Math.max(pts('x_suspended'), pts('x_account_days_old', 'x_tiny_following', 'x_no_posts', 'x_no_site_link')));
172
+ else {
173
+ if (typeof x.ageDays === 'number' && x.ageDays < 7)
174
+ add('x_account_days_old', `${at} created ${x.createdAt?.slice(0, 10)} (${x.ageDays} days ago)`);
175
+ else if (typeof x.ageDays === 'number' && x.ageDays < 30)
176
+ add('x_account_new', `${at} created ${x.createdAt?.slice(0, 10)} (${x.ageDays} days ago)`);
177
+ else if (x.ageDays === null)
178
+ miss('x_account_days_old', 'x', 'X account created date', W.x_account_days_old.points);
179
+ if (typeof x.followers === 'number' && x.followers < 50)
180
+ add('x_tiny_following', `${at} has ${x.followers} followers${x.countsSource === 'vxtwitter' && x.countsAsOf ? ` (cached copy from ${x.countsAsOf.slice(0, 16).replace('T', ' ')} UTC)` : ''}`);
181
+ else if (x.followers === null)
182
+ miss('x_tiny_following', 'x', 'X follower count', W.x_tiny_following.points);
183
+ if (x.tweets === 0)
184
+ add('x_no_posts', `${at} has never posted`);
185
+ else if (typeof x.daysSinceLastPost === 'number' && x.daysSinceLastPost > 30)
186
+ add('x_no_posts', `${at} last posted ${x.lastPostAt?.slice(0, 10)} (${x.daysSinceLastPost} days ago)`);
187
+ else if (x.lastPostAt === null && x.tweets !== 0)
188
+ miss('x_no_posts', 'x', 'X last post date', W.x_no_posts.points);
189
+ if (x.linksSite === false)
190
+ add('x_no_site_link', `${at}'s bio and website field do not mention ${ev.website ? new URL(ev.website).hostname.replace(/^www\./, '') : 'the site'}`);
191
+ else if (x.linksSite === null && ev.website)
192
+ miss('x_no_site_link', 'x', "whether the X account links the coin's site", W.x_no_site_link.points);
193
+ }
194
+ if (x.renamed)
195
+ add('x_renamed', x.renamed.evidence);
196
+ }
197
+ if (ev.xAgreement?.siteAgrees === false)
198
+ add('x_handle_mismatch', ev.xAgreement.note ?? "the website's X link and the advertised account differ");
199
+ const GROUP_MAX = {
200
+ website: pts('drainer_markers', 'default_page', 'wallet_connect', 'never_names_token'),
201
+ domain: pts('domain_new', 'domain_privacy'),
202
+ github: pts('github_archived', 'github_fork', 'github_stale'),
203
+ x: Math.max(pts('x_suspended'), pts('x_account_days_old', 'x_tiny_following', 'x_no_posts', 'x_no_site_link')),
204
+ };
205
+ for (const n of ev.notChecked ?? [])
206
+ if (!missing.some((m) => m.group === n.group))
207
+ miss(`${n.group}:*`, n.group, n.why, GROUP_MAX[n.group]);
208
+ flags.sort((a, b) => b.points - a.points);
209
+ const score = Math.min(100, flags.reduce((a, f) => a + f.points, 0));
210
+ const hi = Math.min(100, score + missing.reduce((a, m) => a + m.maxPoints, 0));
211
+ const m = LARP_SCORE_MEASURED.test;
212
+ return {
213
+ model: LARP_SCORE_MODEL, score, band: bandOf(score),
214
+ range: { lo: score, hi, bandLo: bandOf(score), bandHi: bandOf(hi) },
215
+ thresholds: { ...LARP_SCORE_BANDS },
216
+ flags, top: flags.filter((f) => f.points > 0).slice(0, 3), missing,
217
+ measured: `link-shape part only, held-out test split (${m.n} coins, ${m.bad} went bad within 72 h): AUROC ${m.auroc.toFixed(2)}; ${m.by_band.low.bad}/${m.by_band.low.n} low-band and ${m.by_band.medium.bad}/${m.by_band.medium.n} medium-band coins went bad. Page, domain, GitHub and X points are pre-registered priors, not fitted.`,
218
+ caveat: 'a sum of named flags, evidence not a verdict: any flag can be innocent, a low score is not a safety check, and missing data widens the range instead of lowering the score',
219
+ };
220
+ }