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,316 @@
1
+ // LARP detector — keyless X account lookup (task-1471). Read-only, no login, no cookies, no X API key.
2
+ //
3
+ // Why a fallback chain: measured 2026-10-19 (handoff evidence x-repro.md), FxTwitter answers `404 User not found`
4
+ // for accounts that exist on roughly one call in six, a missing handle gets a 302 to the project's GitHub page on
5
+ // some calls, and "suspended" is reported only some of the time. vxtwitter serves a cached copy (its own
6
+ // `fetched_on`, up to about a day old). X's own oEmbed endpoint says exists / missing and the canonical handle, and
7
+ // nothing else. The X syndication timeline (429 / empty) and the Nitter-class mirrors (offline, suspended, or a 200
8
+ // "offline" page) gave nothing usable and are not in the chain.
9
+ //
10
+ // So no single answer is a verdict: `active` needs one source returning the profile; `suspended` needs a source
11
+ // saying so; `not_found` needs TWO independent sources (vxtwitter AND oEmbed) saying missing; anything else is
12
+ // `unavailable`. Every field that could not be read is listed in `unavailable` and is never guessed.
13
+ //
14
+ // Politeness: one handle per user action, at most five calls, a per-host spacing limiter, a TTL cache, and a
15
+ // circuit breaker per source (the shared HealthRegistry: 3 failures or 2× 429 in a minute → degraded, 30 s..10 min
16
+ // backoff). The bio is untrusted text: the caller firewalls it before it reaches a model.
17
+ import { HealthRegistry } from "../health.js";
18
+ import { systemClock } from "../clock.js";
19
+ import { parseHttpUrl } from "./links.js";
20
+ const RESERVED = new Set(['i', 'home', 'search', 'intent', 'hashtag', 'share', 'explore', 'settings', 'messages', 'notifications', 'login', 'signup', 'tos', 'privacy', 'compose']);
21
+ const HANDLE = /^[A-Za-z0-9_]{1,15}$/;
22
+ const X_HOSTS = new Set(['x.com', 'twitter.com', 'mobile.twitter.com', 'mobile.x.com', 'fxtwitter.com', 'vxtwitter.com', 'fixupx.com', 'nitter.net']);
23
+ const SOURCE_ID = { fxtwitter: 'fxtwitter', vxtwitter: 'vxtwitter', 'x-oembed': 'x-oembed' };
24
+ const HOST = { fxtwitter: 'api.fxtwitter.com', vxtwitter: 'api.vxtwitter.com', 'x-oembed': 'publish.x.com' };
25
+ /** Parse an advertised X link into its kind, handle and status id. Pure. */
26
+ export function parseXLink(raw) {
27
+ const u = parseHttpUrl(raw);
28
+ if (!u)
29
+ return { kind: 'other', handle: null, statusId: null };
30
+ const host = u.hostname.toLowerCase().replace(/^www\./, '');
31
+ if (!X_HOSTS.has(host))
32
+ return { kind: 'other', handle: null, statusId: null };
33
+ const parts = u.pathname.split('/').filter(Boolean);
34
+ if (parts[0]?.toLowerCase() === 'i' && parts[1]?.toLowerCase() === 'communities')
35
+ return { kind: 'community', handle: null, statusId: null };
36
+ const h = parts[0] ?? '';
37
+ if (!HANDLE.test(h) || RESERVED.has(h.toLowerCase()))
38
+ return { kind: 'other', handle: null, statusId: null };
39
+ if (parts[1]?.toLowerCase() === 'status' && /^\d{1,25}$/.test(parts[2] ?? ''))
40
+ return { kind: 'status', handle: h, statusId: parts[2] };
41
+ return { kind: parts.length === 1 ? 'profile' : 'other', handle: h, statusId: null };
42
+ }
43
+ const json = (body) => {
44
+ try {
45
+ const v = JSON.parse(body);
46
+ return v && typeof v === 'object' ? v : null;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ };
52
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
53
+ const str = (v) => (typeof v === 'string' && v ? v : null);
54
+ const iso = (v) => { const t = typeof v === 'string' ? Date.parse(v) : NaN; return Number.isFinite(t) ? new Date(t).toISOString() : null; };
55
+ const days = (now, at) => (at ? Math.max(0, Math.floor((now - Date.parse(at)) / 86_400_000)) : null);
56
+ const bareHost = (raw) => { const u = raw ? parseHttpUrl(raw) : null; return u ? u.hostname.toLowerCase().replace(/^www\./, '') : null; };
57
+ function mentionsToken(bio, ctx) {
58
+ const sym = (ctx.symbol ?? '').trim();
59
+ const name = (ctx.name ?? '').trim();
60
+ if (!sym && !name && !ctx.mint)
61
+ return null;
62
+ const b = bio.toLowerCase();
63
+ if (ctx.mint && bio.includes(ctx.mint))
64
+ return true;
65
+ if (sym.length >= 2 && new RegExp(`(^|[^a-z0-9])\\$?${sym.toLowerCase().replace(/[^a-z0-9]/g, '')}([^a-z0-9]|$)`).test(b))
66
+ return true;
67
+ if (name.length >= 3 && b.includes(name.toLowerCase()))
68
+ return true;
69
+ return false;
70
+ }
71
+ /** The lookup service: chain + cache + spacing + circuit breakers. One instance per process. */
72
+ export class XAccountLookup {
73
+ fetchImpl;
74
+ clock;
75
+ health;
76
+ minIntervalMs;
77
+ ttlMs;
78
+ ttlUnavailableMs;
79
+ maxEntries;
80
+ cache = new Map();
81
+ lastCall = new Map();
82
+ constructor(opts) {
83
+ this.fetchImpl = opts.fetch;
84
+ this.clock = opts.clock ?? systemClock;
85
+ this.health = opts.health ?? new HealthRegistry({ clock: this.clock });
86
+ this.minIntervalMs = opts.minIntervalMs ?? 1000;
87
+ this.ttlMs = opts.ttlMs ?? 30 * 60_000;
88
+ this.ttlUnavailableMs = opts.ttlUnavailableMs ?? 10 * 60_000;
89
+ this.maxEntries = opts.maxEntries ?? 500;
90
+ }
91
+ /** Per-host spacing: calls to one host are serialized at least minIntervalMs apart. */
92
+ async spaced(host, fn) {
93
+ const prev = this.lastCall.get(host) ?? Promise.resolve();
94
+ let release;
95
+ const mine = new Promise((r) => { release = r; });
96
+ this.lastCall.set(host, prev.then(() => mine));
97
+ await prev;
98
+ try {
99
+ return await fn();
100
+ }
101
+ finally {
102
+ if (this.minIntervalMs > 0)
103
+ void this.clock.sleep(this.minIntervalMs).then(release);
104
+ else
105
+ release();
106
+ }
107
+ }
108
+ async get(source, endpoint, url, trace) {
109
+ const sid = SOURCE_ID[source];
110
+ if (!this.health.isAvailable(sid)) {
111
+ trace.push({ source, endpoint, status: null, result: 'skipped: circuit open (source degraded)', ms: 0 });
112
+ return null;
113
+ }
114
+ const t0 = this.clock.now();
115
+ try {
116
+ const r = await this.spaced(HOST[source], () => this.fetchImpl(url));
117
+ const ms = this.clock.now() - t0;
118
+ // 404 / 3xx are answers (a vote), not source failures; 429 and 5xx other than vxtwitter's suspended body are.
119
+ const failed = r.status === 0 || r.status === 429 || (r.status >= 500 && !/suspended/i.test(r.body));
120
+ this.health.record(sid, failed ? { ok: false, status: r.status, latencyMs: ms, error: `http ${r.status}` } : { ok: true, status: r.status, latencyMs: ms });
121
+ return { status: r.status, body: r.body, ms };
122
+ }
123
+ catch (e) {
124
+ const ms = this.clock.now() - t0;
125
+ this.health.record(sid, { ok: false, latencyMs: ms, error: e.message });
126
+ trace.push({ source, endpoint, status: null, result: `error: ${e.message}`.slice(0, 120), ms });
127
+ return null;
128
+ }
129
+ }
130
+ /** Look up the account behind an advertised X link. Never throws. */
131
+ async lookup(xUrl, ctx = {}) {
132
+ const link = parseXLink(xUrl);
133
+ const now = this.clock.now();
134
+ const key = `${link.handle?.toLowerCase() ?? xUrl}|${link.statusId ?? ''}|${(ctx.symbol ?? '').toLowerCase()}|${(ctx.name ?? '').toLowerCase()}|${ctx.mint ?? ''}|${bareHost(ctx.website) ?? ''}`;
135
+ const hit = this.cache.get(key);
136
+ if (hit && now - hit.at < (hit.report.status === 'unavailable' ? this.ttlUnavailableMs : this.ttlMs))
137
+ return { ...hit.report, cached: true };
138
+ const report = await this.uncached(xUrl, link, ctx);
139
+ this.cache.delete(key);
140
+ this.cache.set(key, { at: now, report });
141
+ while (this.cache.size > this.maxEntries)
142
+ this.cache.delete(this.cache.keys().next().value);
143
+ return report;
144
+ }
145
+ async uncached(url, link, ctx) {
146
+ const now = this.clock.now();
147
+ const trace = [];
148
+ const base = {
149
+ url, kind: link.kind, handle: link.handle, status: 'unavailable', screenName: null, id: null, renamed: null,
150
+ createdAt: null, ageDays: null, followers: null, following: null, tweets: null, lastPostAt: null, daysSinceLastPost: null,
151
+ bio: null, website: null, bioMentionsToken: null, linksSite: null, countsAsOf: null, countsSource: null,
152
+ unavailable: [], sources: trace, cached: false, fetchedAt: new Date(now).toISOString(),
153
+ };
154
+ if (!link.handle) {
155
+ base.unavailable = ['account (the X link is not an account or a post)'];
156
+ return base;
157
+ }
158
+ let h = link.handle;
159
+ // A status link: who wrote the post now. A different author means a renamed account or a borrowed post.
160
+ if (link.statusId) {
161
+ const r = await this.get('fxtwitter', 'status', `https://api.fxtwitter.com/${h}/status/${link.statusId}`, trace);
162
+ const j = r ? json(r.body) : null;
163
+ const author = str(j?.tweet?.author?.screen_name);
164
+ if (r)
165
+ trace.push({ source: 'fxtwitter', endpoint: 'status', status: r.status, result: author ? `post by @${author}` : `no post (${str(j?.message) ?? r.status})`, ms: r.ms });
166
+ if (author && author.toLowerCase() !== h.toLowerCase()) {
167
+ base.renamed = { from: h, to: author, evidence: `the linked post ${link.statusId} is now shown under @${author}` };
168
+ h = author;
169
+ }
170
+ }
171
+ let profile = null;
172
+ let suspended = false;
173
+ let missingVotes = 0;
174
+ const missingBy = [];
175
+ // 1) FxTwitter profile: fresh counts. A 404 is one vote only (measured false 404s).
176
+ const fx = await this.get('fxtwitter', 'profile', `https://api.fxtwitter.com/${h}`, trace);
177
+ if (fx) {
178
+ const j = json(fx.body);
179
+ const u = j?.user;
180
+ if (fx.status === 200 && u) {
181
+ profile = { screenName: str(u.screen_name), id: str(u.id), createdAt: iso(u.joined), followers: num(u.followers), following: num(u.following), tweets: num(u.tweets), bio: typeof u.description === 'string' ? u.description : null, website: str(u.website?.url), asOf: new Date(now).toISOString() };
182
+ trace.push({ source: 'fxtwitter', endpoint: 'profile', status: 200, result: 'profile', ms: fx.ms });
183
+ }
184
+ else if (str(j?.reason) === 'suspended') {
185
+ suspended = true;
186
+ trace.push({ source: 'fxtwitter', endpoint: 'profile', status: fx.status, result: 'suspended', ms: fx.ms });
187
+ }
188
+ else {
189
+ trace.push({ source: 'fxtwitter', endpoint: 'profile', status: fx.status, result: fx.status >= 300 && fx.status < 400 ? 'redirect (missing handle or error page)' : `no profile (${str(j?.message) ?? fx.status})`, ms: fx.ms });
190
+ }
191
+ }
192
+ // 2) vxtwitter: cached copy; says suspended / missing reliably in the probe.
193
+ if (!profile) {
194
+ const vx = await this.get('vxtwitter', 'profile', `https://api.vxtwitter.com/${h}`, trace);
195
+ if (vx) {
196
+ const j = json(vx.body);
197
+ if (vx.status === 200 && j && str(j.screen_name)) {
198
+ const fetchedOn = num(j.fetched_on);
199
+ profile = { screenName: str(j.screen_name), id: j.id === undefined || j.id === null ? null : String(j.id), createdAt: iso(j.created_at), followers: num(j.followers_count), following: num(j.following_count), tweets: num(j.tweet_count), bio: typeof j.description === 'string' ? j.description : null, website: null, asOf: fetchedOn ? new Date(fetchedOn * 1000).toISOString() : null };
200
+ trace.push({ source: 'vxtwitter', endpoint: 'profile', status: 200, result: 'profile (cached copy)', ms: vx.ms });
201
+ }
202
+ else if (/suspended/i.test(str(j?.error) ?? '')) {
203
+ suspended = true;
204
+ trace.push({ source: 'vxtwitter', endpoint: 'profile', status: vx.status, result: 'suspended', ms: vx.ms });
205
+ }
206
+ else if (vx.status === 404) {
207
+ missingVotes++;
208
+ missingBy.push('vxtwitter');
209
+ trace.push({ source: 'vxtwitter', endpoint: 'profile', status: 404, result: 'not found', ms: vx.ms });
210
+ }
211
+ else
212
+ trace.push({ source: 'vxtwitter', endpoint: 'profile', status: vx.status, result: 'no answer', ms: vx.ms });
213
+ }
214
+ }
215
+ // 3) X oEmbed: exists vs missing (200 for a suspended account too).
216
+ let oembedExists = null;
217
+ if (!profile && !suspended) {
218
+ const oe = await this.get('x-oembed', 'oembed', `https://publish.x.com/oembed?url=${encodeURIComponent(`https://x.com/${h}`)}`, trace);
219
+ if (oe) {
220
+ const j = json(oe.body);
221
+ if (oe.status === 200 && j) {
222
+ oembedExists = true;
223
+ trace.push({ source: 'x-oembed', endpoint: 'oembed', status: 200, result: 'exists', ms: oe.ms });
224
+ }
225
+ else if (oe.status === 404) {
226
+ oembedExists = false;
227
+ missingVotes++;
228
+ missingBy.push('x-oembed');
229
+ trace.push({ source: 'x-oembed', endpoint: 'oembed', status: 404, result: 'not found', ms: oe.ms });
230
+ }
231
+ else
232
+ trace.push({ source: 'x-oembed', endpoint: 'oembed', status: oe.status, result: 'no answer', ms: oe.ms });
233
+ }
234
+ }
235
+ // 4) Last post date (FxTwitter statuses), only for an account that exists.
236
+ let lastPostAt = null;
237
+ let postsRead = false;
238
+ if (profile) {
239
+ const st = await this.get('fxtwitter', 'statuses', `https://api.fxtwitter.com/2/profile/${h}/statuses`, trace);
240
+ const j = st ? json(st.body) : null;
241
+ const results = Array.isArray(j?.results) ? j.results : null;
242
+ if (st?.status === 200 && results) {
243
+ postsRead = true;
244
+ const times = results.map((r) => iso(r.created_at)).filter((t) => !!t).sort();
245
+ lastPostAt = times.at(-1) ?? null;
246
+ trace.push({ source: 'fxtwitter', endpoint: 'statuses', status: 200, result: `${results.length} recent posts`, ms: st.ms });
247
+ }
248
+ else if (st)
249
+ trace.push({ source: 'fxtwitter', endpoint: 'statuses', status: st.status, result: 'posts unavailable', ms: st.ms });
250
+ }
251
+ const out = { ...base };
252
+ if (profile) {
253
+ out.status = 'active';
254
+ Object.assign(out, {
255
+ screenName: profile.screenName, id: profile.id, createdAt: profile.createdAt, ageDays: days(now, profile.createdAt),
256
+ followers: profile.followers, following: profile.following, tweets: profile.tweets,
257
+ bio: profile.bio !== null ? profile.bio.slice(0, 280) : null, website: profile.website,
258
+ countsAsOf: profile.asOf, countsSource: trace.find((t) => t.endpoint === 'profile' && t.status === 200)?.source ?? null,
259
+ lastPostAt, daysSinceLastPost: days(now, lastPostAt),
260
+ });
261
+ // A posts list that came back empty for an account with zero tweets is "no posts", not missing.
262
+ if (postsRead && !lastPostAt && profile.tweets === 0)
263
+ out.daysSinceLastPost = null;
264
+ out.bioMentionsToken = profile.bio !== null ? mentionsToken(profile.bio, ctx) : null;
265
+ const site = bareHost(ctx.website);
266
+ if (site) {
267
+ const bioHit = (profile.bio ?? '').toLowerCase().includes(site);
268
+ const webHit = bareHost(profile.website) === site;
269
+ out.linksSite = bioHit || webHit ? true : (profile.website !== null || profile.bio !== null) ? false : null;
270
+ }
271
+ }
272
+ else if (suspended)
273
+ out.status = 'suspended';
274
+ else if (missingVotes >= 2)
275
+ out.status = 'not_found';
276
+ else if (oembedExists)
277
+ out.status = 'unavailable'; // exists per X, but no source returned the profile
278
+ const miss = [];
279
+ if (out.status !== 'active') {
280
+ if (out.status === 'unavailable')
281
+ miss.push(`account status (${missingBy.length ? `only ${missingBy.join(' + ')} said missing; ` : ''}no source returned the profile)`);
282
+ }
283
+ else {
284
+ if (out.createdAt === null)
285
+ miss.push('account created date');
286
+ if (out.followers === null)
287
+ miss.push('follower count');
288
+ if (out.following === null)
289
+ miss.push('following count');
290
+ if (out.tweets === null)
291
+ miss.push('post count');
292
+ if (!postsRead)
293
+ miss.push('last post date');
294
+ if (out.bio === null)
295
+ miss.push('bio');
296
+ if (ctx.website && out.linksSite === null)
297
+ miss.push('site cross-link');
298
+ if (out.bioMentionsToken === null && (ctx.symbol || ctx.name))
299
+ miss.push('bio mentions the token');
300
+ }
301
+ out.unavailable = miss;
302
+ return out;
303
+ }
304
+ }
305
+ /** Advertised handles per source, and whether they and the site's own X links agree. Pure. */
306
+ export function xHandleAgreement(advertised, siteLinks) {
307
+ const handles = advertised.map((l) => ({ h: parseXLink(l.url), source: l.source })).filter((x) => x.h.handle).map((x) => ({ handle: x.h.handle, source: x.source }));
308
+ const distinct = [...new Set(handles.map((x) => x.handle.toLowerCase()))];
309
+ const siteHandles = siteLinks === null ? null : [...new Set(siteLinks.map((u) => parseXLink(u)).filter((x) => x.kind === 'profile' && x.handle).map((x) => x.handle.toLowerCase()))];
310
+ const siteLinksAdvertised = siteHandles === null || !distinct.length ? null : siteHandles.length === 0 ? false : siteHandles.some((s) => distinct.includes(s));
311
+ const agree = distinct.length === 0 ? null : distinct.length === 1 && (siteHandles === null || siteHandles.length === 0 || siteHandles.includes(distinct[0]));
312
+ const siteAgrees = !distinct.length || !siteHandles?.length ? null : siteHandles.some((s) => distinct.includes(s));
313
+ const note = distinct.length > 1 ? `sources advertise different X accounts: ${distinct.map((d) => `@${d}`).join(', ')}`
314
+ : agree === false && siteHandles?.length ? `the website links @${siteHandles.join(', @')} but the token lists @${distinct[0]}` : null;
315
+ return { handles, agree, siteAgrees, siteHandles, siteLinksAdvertised, note };
316
+ }
@@ -10,15 +10,24 @@ export const MCAP_AGREE_RATIO = 5;
10
10
  * (or `fdv` when absent) can reflect only the Solana-side supply of bridged/wrapped
11
11
  * tokens. Take the larger of DexScreener and Jupiter `mcap` when they agree within
12
12
  * 5x; otherwise keep DexScreener's number and flag the row. One side only: use it.
13
+ *
14
+ * task-1518: when both sources' prices are known, Jupiter's cap is first moved to DexScreener's price (its implied
15
+ * supply x the newer price), so "agree" compares the SUPPLY each source counts, which is what the rule is about. A
16
+ * Jupiter record served from the cache can be minutes old: on a coin that ran 6x since, the raw caps are 6x apart
17
+ * while both count the same supply. Comparing the raw caps flagged every fast runner (and the autotrade thesis-split
18
+ * exit sold it, "market-cap sources 1.0x apart"), and on a coin that dumped it showed the stale, larger cap.
13
19
  */
14
- export function resolveMcap(ds, jupMcap) {
20
+ export function resolveMcap(ds, jupMcap, prices) {
15
21
  const d = ds ? (ds.mcap ?? ds.fdv) : null;
16
22
  const j = jupMcap ?? null;
17
- const pos = (n) => n !== null && Number.isFinite(n) && n > 0;
23
+ const pos = (n) => n !== null && n !== undefined && Number.isFinite(n) && n > 0;
18
24
  if (pos(d) && pos(j)) {
19
- const ratio = Math.max(d, j) / Math.min(d, j);
25
+ const dp = prices?.dexscreener, jp = prices?.jupiter;
26
+ // Jupiter's cap at DexScreener's (newer) price: its supply x the price the row shows.
27
+ const jNow = pos(dp) && pos(jp) ? j * (dp / jp) : j;
28
+ const ratio = Math.max(d, jNow) / Math.min(d, jNow);
20
29
  if (ratio <= MCAP_AGREE_RATIO) {
21
- return j > d ? { value: j, basis: 'jupiter', flagged: false, dexscreener: d, jupiter: j } : { value: d, basis: 'dexscreener', flagged: false, dexscreener: d, jupiter: j };
30
+ return jNow > d ? { value: jNow, basis: 'jupiter', flagged: false, dexscreener: d, jupiter: j } : { value: d, basis: 'dexscreener', flagged: false, dexscreener: d, jupiter: j };
22
31
  }
23
32
  return { value: d, basis: 'dexscreener', flagged: true, dexscreener: d, jupiter: j };
24
33
  }
@@ -104,7 +113,7 @@ export class Store {
104
113
  price: p,
105
114
  sources,
106
115
  stale: ds.stale.has(mint) || jup.stale.has(mint) || price.stale.has(mint),
107
- mcap: resolveMcap(d?.pair, j?.pair.mcap),
116
+ mcap: resolveMcap(d?.pair, j?.pair.mcap, { dexscreener: d?.pair.priceUsd, jupiter: j?.pair.priceUsd }),
108
117
  });
109
118
  }
110
119
  const latencyMs = {};