dexterm-cli 0.0.0-stage → 0.1.2

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 (377) hide show
  1. package/LICENSE +661 -0
  2. package/dist/packages/agents/src/audit.js +122 -0
  3. package/dist/packages/agents/src/bus.js +394 -0
  4. package/dist/packages/agents/src/executor.js +202 -0
  5. package/dist/packages/agents/src/gate.js +179 -0
  6. package/dist/packages/agents/src/identity.js +218 -0
  7. package/dist/packages/agents/src/index.js +15 -0
  8. package/dist/packages/agents/src/proposals.js +153 -0
  9. package/dist/packages/agents/src/schema.js +87 -0
  10. package/dist/packages/agents/src/sdk.js +140 -0
  11. package/dist/packages/agents/src/tiers.js +119 -0
  12. package/dist/packages/agents/src/tools.js +72 -0
  13. package/dist/packages/agents/src/ws.js +158 -0
  14. package/dist/packages/cli/assets/claude/analyst-system.md +76 -0
  15. package/dist/packages/cli/assets/claude/plugin/.claude-plugin/plugin.json +5 -0
  16. package/dist/packages/cli/assets/claude/plugin/skills/REGISTRY.json +16 -0
  17. package/dist/packages/cli/assets/claude/plugin/skills/larp-check/SKILL.md +75 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +74 -0
  19. package/dist/packages/cli/assets/claude/plugin/skills/token-dossier/SKILL.md +63 -0
  20. package/dist/packages/cli/assets/claude/plugin/skills/trench-scan/SKILL.md +44 -0
  21. package/dist/packages/cli/assets/claude/schemas/larp.schema.json +117 -0
  22. package/dist/packages/cli/assets/claude/schemas/trench-scan.schema.json +71 -0
  23. package/dist/packages/cli/assets/claude/schemas/verdict.schema.json +115 -0
  24. package/dist/packages/cli/bin/dexterm.mjs +11 -0
  25. package/dist/packages/cli/src/agent-cmd.js +336 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/alerts/adapters/desktop.js +40 -0
  28. package/dist/packages/cli/src/alerts/adapters/discord.js +36 -0
  29. package/dist/packages/cli/src/alerts/adapters/telegram.js +43 -0
  30. package/dist/packages/cli/src/alerts/adapters/toast.js +16 -0
  31. package/dist/packages/cli/src/alerts/creds.js +83 -0
  32. package/dist/packages/cli/src/alerts/format.js +41 -0
  33. package/dist/packages/cli/src/alerts/index.js +10 -0
  34. package/dist/packages/cli/src/alerts/notifier.js +53 -0
  35. package/dist/packages/cli/src/alerts/route.js +19 -0
  36. package/dist/packages/cli/src/alerts/types.js +1 -0
  37. package/dist/packages/cli/src/alerts-cmd.js +212 -0
  38. package/dist/packages/cli/src/analyst/backend.js +526 -0
  39. package/dist/packages/cli/src/analyst/results.js +38 -0
  40. package/dist/packages/cli/src/analyst/session.js +319 -0
  41. package/dist/packages/cli/src/audit.js +51 -0
  42. package/dist/packages/cli/src/backtest-cmd.js +208 -0
  43. package/dist/packages/cli/src/boot-state.js +62 -0
  44. package/dist/packages/cli/src/coin-target.js +2 -0
  45. package/dist/packages/cli/src/custom.js +261 -0
  46. package/dist/packages/cli/src/display.js +191 -0
  47. package/dist/packages/cli/src/doctor.js +375 -0
  48. package/dist/packages/cli/src/export.js +235 -0
  49. package/dist/packages/cli/src/feed.js +634 -0
  50. package/dist/packages/cli/src/fixtures.js +275 -0
  51. package/dist/packages/cli/src/follow-cmd.js +221 -0
  52. package/dist/packages/cli/src/images-config.js +59 -0
  53. package/dist/packages/cli/src/index.js +31 -0
  54. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  55. package/dist/packages/cli/src/larp-record.js +21 -0
  56. package/dist/packages/cli/src/live/copy.js +80 -0
  57. package/dist/packages/cli/src/live/flow.js +233 -0
  58. package/dist/packages/cli/src/live/index.js +511 -0
  59. package/dist/packages/cli/src/live/intel.js +227 -0
  60. package/dist/packages/cli/src/live/newpairs.js +334 -0
  61. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  62. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  63. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  64. package/dist/packages/cli/src/live/token.js +585 -0
  65. package/dist/packages/cli/src/live/trade.js +929 -0
  66. package/dist/packages/cli/src/live/watchlist.js +178 -0
  67. package/dist/packages/cli/src/main.js +5785 -0
  68. package/dist/packages/cli/src/mcp-cmd.js +326 -0
  69. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  70. package/dist/packages/cli/src/pack-cmd.js +323 -0
  71. package/dist/packages/cli/src/plan-cmd.js +278 -0
  72. package/dist/packages/cli/src/popup.js +810 -0
  73. package/dist/packages/cli/src/rules-cmd.js +131 -0
  74. package/dist/packages/cli/src/rules.js +358 -0
  75. package/dist/packages/cli/src/scan/command.js +57 -0
  76. package/dist/packages/cli/src/scan/engine.js +419 -0
  77. package/dist/packages/cli/src/scan/observe.js +64 -0
  78. package/dist/packages/cli/src/scan/ui.js +98 -0
  79. package/dist/packages/cli/src/screener/filter.js +241 -0
  80. package/dist/packages/cli/src/screener/presets.js +80 -0
  81. package/dist/packages/cli/src/screener/rows.js +146 -0
  82. package/dist/packages/cli/src/screener/rug.js +50 -0
  83. package/dist/packages/cli/src/screener/sort.js +52 -0
  84. package/dist/packages/cli/src/screener/view.js +183 -0
  85. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  86. package/dist/packages/cli/src/screens/analyst.js +326 -0
  87. package/dist/packages/cli/src/screens/boot.js +74 -0
  88. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  89. package/dist/packages/cli/src/screens/coin.js +33 -0
  90. package/dist/packages/cli/src/screens/follows.js +201 -0
  91. package/dist/packages/cli/src/screens/kol.js +90 -0
  92. package/dist/packages/cli/src/screens/narrative.js +90 -0
  93. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  94. package/dist/packages/cli/src/screens/overlay.js +257 -0
  95. package/dist/packages/cli/src/screens/plans.js +126 -0
  96. package/dist/packages/cli/src/screens/positions.js +176 -0
  97. package/dist/packages/cli/src/screens/rules.js +170 -0
  98. package/dist/packages/cli/src/screens/screener.js +459 -0
  99. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  100. package/dist/packages/cli/src/screens/ticket.js +342 -0
  101. package/dist/packages/cli/src/screens/token.js +312 -0
  102. package/dist/packages/cli/src/screens/trending.js +181 -0
  103. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  104. package/dist/packages/cli/src/skill-eval-build.js +109 -0
  105. package/dist/packages/cli/src/social/demo.js +96 -0
  106. package/dist/packages/cli/src/social/view.js +250 -0
  107. package/dist/packages/cli/src/state.js +463 -0
  108. package/dist/packages/cli/src/theme-cmd.js +126 -0
  109. package/dist/packages/cli/src/trade.js +319 -0
  110. package/dist/packages/cli/src/update.js +294 -0
  111. package/dist/packages/cli/src/version.js +3 -0
  112. package/dist/packages/cli/src/voice-cmd.js +119 -0
  113. package/dist/packages/cli/src/voice-hud.js +42 -0
  114. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  115. package/dist/packages/cli/src/wallet-command.js +31 -0
  116. package/dist/packages/cli/src/watch-cmd.js +190 -0
  117. package/dist/packages/cli/src/widgets.js +208 -0
  118. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  119. package/dist/packages/cli/src/x-cmd.js +154 -0
  120. package/dist/packages/custom/src/config.js +213 -0
  121. package/dist/packages/custom/src/index.js +10 -0
  122. package/dist/packages/custom/src/keymap.js +107 -0
  123. package/dist/packages/custom/src/layout.js +235 -0
  124. package/dist/packages/custom/src/packs.js +633 -0
  125. package/dist/packages/custom/src/rules.js +704 -0
  126. package/dist/packages/custom/src/schema.js +538 -0
  127. package/dist/packages/custom/src/themes.js +176 -0
  128. package/dist/packages/custom/src/widget-html.js +222 -0
  129. package/dist/packages/custom/src/workspace.js +141 -0
  130. package/dist/packages/data/src/analyst-actions.js +133 -0
  131. package/dist/packages/data/src/budget.js +88 -0
  132. package/dist/packages/data/src/cache.js +142 -0
  133. package/dist/packages/data/src/candles.js +168 -0
  134. package/dist/packages/data/src/clock.js +64 -0
  135. package/dist/packages/data/src/coin-target.js +82 -0
  136. package/dist/packages/data/src/credits.js +137 -0
  137. package/dist/packages/data/src/detail.js +97 -0
  138. package/dist/packages/data/src/fast-lane.js +321 -0
  139. package/dist/packages/data/src/fixtures.js +135 -0
  140. package/dist/packages/data/src/health.js +245 -0
  141. package/dist/packages/data/src/http.js +277 -0
  142. package/dist/packages/data/src/images/cache.js +141 -0
  143. package/dist/packages/data/src/images/decode-worker.js +28 -0
  144. package/dist/packages/data/src/images/decode.js +81 -0
  145. package/dist/packages/data/src/images/fetch.js +162 -0
  146. package/dist/packages/data/src/images/gif.js +214 -0
  147. package/dist/packages/data/src/images/index.js +10 -0
  148. package/dist/packages/data/src/images/jpeg.js +544 -0
  149. package/dist/packages/data/src/images/png.js +310 -0
  150. package/dist/packages/data/src/images/service.js +334 -0
  151. package/dist/packages/data/src/images/types.js +27 -0
  152. package/dist/packages/data/src/index.js +211 -0
  153. package/dist/packages/data/src/intel/helius-history.js +70 -0
  154. package/dist/packages/data/src/intel/holder-class.js +125 -0
  155. package/dist/packages/data/src/intel/rules.js +421 -0
  156. package/dist/packages/data/src/intel/scan.js +170 -0
  157. package/dist/packages/data/src/larp/evidence.js +288 -0
  158. package/dist/packages/data/src/larp/link-baseline.js +196 -0
  159. package/dist/packages/data/src/larp/links.js +137 -0
  160. package/dist/packages/data/src/larp/robots.js +90 -0
  161. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  162. package/dist/packages/data/src/limiter.js +341 -0
  163. package/dist/packages/data/src/model.js +12 -0
  164. package/dist/packages/data/src/paper.js +331 -0
  165. package/dist/packages/data/src/rpc-config.js +126 -0
  166. package/dist/packages/data/src/rpc-pool.js +166 -0
  167. package/dist/packages/data/src/social/chain.js +83 -0
  168. package/dist/packages/data/src/social/dedupe.js +205 -0
  169. package/dist/packages/data/src/social/feed.js +462 -0
  170. package/dist/packages/data/src/social/firewall.js +110 -0
  171. package/dist/packages/data/src/social/index.js +11 -0
  172. package/dist/packages/data/src/social/kol.js +269 -0
  173. package/dist/packages/data/src/social/model.js +11 -0
  174. package/dist/packages/data/src/social/rank.js +184 -0
  175. package/dist/packages/data/src/social/rss.js +113 -0
  176. package/dist/packages/data/src/social/summary.js +104 -0
  177. package/dist/packages/data/src/social/venues.js +186 -0
  178. package/dist/packages/data/src/sources/dexscreener.js +184 -0
  179. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  180. package/dist/packages/data/src/sources/goplus.js +161 -0
  181. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  182. package/dist/packages/data/src/sources/jupiter.js +198 -0
  183. package/dist/packages/data/src/sources/pda.js +113 -0
  184. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  185. package/dist/packages/data/src/sources/pumpportal.js +252 -0
  186. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  187. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  188. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  189. package/dist/packages/data/src/store.js +289 -0
  190. package/dist/packages/data/src/trade/bridge.js +464 -0
  191. package/dist/packages/data/src/trade/caps.js +323 -0
  192. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  193. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  194. package/dist/packages/data/src/trade/copy/index.js +9 -0
  195. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  196. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  197. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  198. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  199. package/dist/packages/data/src/trade/copy/types.js +6 -0
  200. package/dist/packages/data/src/trade/executor.js +933 -0
  201. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  202. package/dist/packages/data/src/trade/link-store.js +52 -0
  203. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  204. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  205. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  206. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  207. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  208. package/dist/packages/data/src/trade/plans/index.js +12 -0
  209. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  210. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  211. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  212. package/dist/packages/data/src/trade/plans/service.js +168 -0
  213. package/dist/packages/data/src/trade/plans/types.js +6 -0
  214. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  215. package/dist/packages/data/src/trade/signer/client.js +197 -0
  216. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  217. package/dist/packages/data/src/trade/signer/host.js +97 -0
  218. package/dist/packages/data/src/trade/signer/main.js +121 -0
  219. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  220. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  221. package/dist/packages/data/src/trade/signer/service.js +400 -0
  222. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  223. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  224. package/dist/packages/data/src/trade/swap-build.js +108 -0
  225. package/dist/packages/data/src/trade/token-delta.js +201 -0
  226. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  227. package/dist/packages/data/src/util.js +24 -0
  228. package/dist/packages/data/src/watchlist.js +176 -0
  229. package/dist/packages/data/src/x/api.js +256 -0
  230. package/dist/packages/data/src/x/budget.js +66 -0
  231. package/dist/packages/data/src/x/config.js +49 -0
  232. package/dist/packages/data/src/x/index.js +10 -0
  233. package/dist/packages/data/src/x/mock-server.js +217 -0
  234. package/dist/packages/data/src/x/oauth.js +242 -0
  235. package/dist/packages/data/src/x/pkce.js +21 -0
  236. package/dist/packages/data/src/x/source.js +285 -0
  237. package/dist/packages/data/src/x/token-store.js +134 -0
  238. package/dist/packages/data/src/x/transport.js +71 -0
  239. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  240. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  241. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  242. package/dist/packages/mcp/assets/cases.json +1194 -0
  243. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  244. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  245. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  246. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  247. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  248. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  249. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  250. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  251. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  252. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  253. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  254. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  255. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  256. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  257. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  258. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  259. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  260. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  261. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  262. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  263. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  264. package/dist/packages/mcp/src/dossier.js +78 -0
  265. package/dist/packages/mcp/src/firewall.js +21 -0
  266. package/dist/packages/mcp/src/index.js +22 -0
  267. package/dist/packages/mcp/src/server.js +936 -0
  268. package/dist/packages/mcp/src/user-content.js +434 -0
  269. package/dist/packages/packs/src/backtest.js +183 -0
  270. package/dist/packages/packs/src/digest.js +95 -0
  271. package/dist/packages/packs/src/firewall.js +57 -0
  272. package/dist/packages/packs/src/index.js +20 -0
  273. package/dist/packages/packs/src/larp-eval.js +100 -0
  274. package/dist/packages/packs/src/narrative.js +309 -0
  275. package/dist/packages/packs/src/reason.js +135 -0
  276. package/dist/packages/packs/src/rug-model.js +137 -0
  277. package/dist/packages/packs/src/schema.js +54 -0
  278. package/dist/packages/packs/src/skill-backtest.js +300 -0
  279. package/dist/packages/packs/src/token-dossier.js +174 -0
  280. package/dist/packages/packs/src/verdicts.js +17 -0
  281. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  282. package/dist/packages/popup/src/config.js +71 -0
  283. package/dist/packages/popup/src/dock.js +271 -0
  284. package/dist/packages/popup/src/host.js +210 -0
  285. package/dist/packages/popup/src/index.js +12 -0
  286. package/dist/packages/popup/src/loopback.js +53 -0
  287. package/dist/packages/popup/src/open.js +183 -0
  288. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  289. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  290. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  291. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  292. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  293. package/dist/packages/popup/src/pages/chart.css +50 -0
  294. package/dist/packages/popup/src/pages/chart.html +56 -0
  295. package/dist/packages/popup/src/pages/chart.js +554 -0
  296. package/dist/packages/popup/src/pages/common.css +34 -0
  297. package/dist/packages/popup/src/pages/common.js +187 -0
  298. package/dist/packages/popup/src/pages/feeds.html +20 -0
  299. package/dist/packages/popup/src/pages/feeds.js +77 -0
  300. package/dist/packages/popup/src/pages/narrative.html +20 -0
  301. package/dist/packages/popup/src/pages/narrative.js +61 -0
  302. package/dist/packages/popup/src/pages/plans.css +30 -0
  303. package/dist/packages/popup/src/pages/plans.html +26 -0
  304. package/dist/packages/popup/src/pages/plans.js +82 -0
  305. package/dist/packages/popup/src/pages/roster.css +41 -0
  306. package/dist/packages/popup/src/pages/roster.html +25 -0
  307. package/dist/packages/popup/src/pages/roster.js +75 -0
  308. package/dist/packages/popup/src/pages/token.html +20 -0
  309. package/dist/packages/popup/src/pages/token.js +112 -0
  310. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  311. package/dist/packages/popup/src/pages/widget.css +5 -0
  312. package/dist/packages/popup/src/pages/widget.html +21 -0
  313. package/dist/packages/popup/src/pages/widget.js +85 -0
  314. package/dist/packages/popup/src/protocol.js +31 -0
  315. package/dist/packages/popup/src/selftest.js +156 -0
  316. package/dist/packages/popup/src/server.js +582 -0
  317. package/dist/packages/popup/src/sinkproxy.js +167 -0
  318. package/dist/packages/popup/src/widget-html.js +231 -0
  319. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  320. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  321. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  322. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  323. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  324. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  325. package/dist/packages/recorder/src/cli.js +113 -0
  326. package/dist/packages/recorder/src/export.js +71 -0
  327. package/dist/packages/recorder/src/index.js +20 -0
  328. package/dist/packages/recorder/src/labels.js +151 -0
  329. package/dist/packages/recorder/src/open.js +29 -0
  330. package/dist/packages/recorder/src/recorder.js +553 -0
  331. package/dist/packages/recorder/src/schedule.js +43 -0
  332. package/dist/packages/recorder/src/schema.js +135 -0
  333. package/dist/packages/recorder/src/snapshot.js +191 -0
  334. package/dist/packages/recorder/src/sweep.js +52 -0
  335. package/dist/packages/recorder/src/verdicts.js +52 -0
  336. package/dist/packages/tui/src/bubbles.js +415 -0
  337. package/dist/packages/tui/src/canvas.js +370 -0
  338. package/dist/packages/tui/src/chrome.js +85 -0
  339. package/dist/packages/tui/src/color.js +133 -0
  340. package/dist/packages/tui/src/flush.js +62 -0
  341. package/dist/packages/tui/src/fmt.js +188 -0
  342. package/dist/packages/tui/src/fx-trade.js +278 -0
  343. package/dist/packages/tui/src/fx.js +527 -0
  344. package/dist/packages/tui/src/images/blocks.js +141 -0
  345. package/dist/packages/tui/src/images/coin.js +22 -0
  346. package/dist/packages/tui/src/images/detect.js +140 -0
  347. package/dist/packages/tui/src/images/index.js +7 -0
  348. package/dist/packages/tui/src/images/layer.js +146 -0
  349. package/dist/packages/tui/src/images/pixels.js +157 -0
  350. package/dist/packages/tui/src/images/protocols.js +87 -0
  351. package/dist/packages/tui/src/index.js +18 -0
  352. package/dist/packages/tui/src/keymap.js +357 -0
  353. package/dist/packages/tui/src/layout.js +11 -0
  354. package/dist/packages/tui/src/raster-font.js +195 -0
  355. package/dist/packages/tui/src/raster.js +213 -0
  356. package/dist/packages/tui/src/sound.js +177 -0
  357. package/dist/packages/tui/src/theme.js +182 -0
  358. package/dist/packages/tui/src/widgets.js +598 -0
  359. package/dist/packages/voice/src/controller.js +407 -0
  360. package/dist/packages/voice/src/doctor.js +131 -0
  361. package/dist/packages/voice/src/download.js +184 -0
  362. package/dist/packages/voice/src/engines.js +84 -0
  363. package/dist/packages/voice/src/index.js +20 -0
  364. package/dist/packages/voice/src/router.js +502 -0
  365. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  366. package/dist/packages/voice/src/stt/apple.js +179 -0
  367. package/dist/packages/voice/src/stt/fake.js +34 -0
  368. package/dist/packages/voice/src/stt/whisper.js +112 -0
  369. package/dist/packages/voice/src/summary.js +44 -0
  370. package/dist/packages/voice/src/tts/espeak.js +131 -0
  371. package/dist/packages/voice/src/tts/fake.js +30 -0
  372. package/dist/packages/voice/src/tts/piper.js +206 -0
  373. package/dist/packages/voice/src/tts/say.js +132 -0
  374. package/dist/packages/voice/src/tts/voices.js +206 -0
  375. package/dist/packages/voice/src/types.js +3 -0
  376. package/package.json +38 -4
  377. package/README.md +0 -3
@@ -0,0 +1,288 @@
1
+ // LARP detector — mechanical evidence signals (task-1315). Pure + deterministic
2
+ // given fetched bytes; no network except through an injected fetcher. These are
3
+ // SIGNALS, never verdicts: the analyst weighs them. HTML is parsed with regex
4
+ // only (zero-dep, and we never execute it) because the page is untrusted data.
5
+ import { classifyLink, parseHttpUrl } from "./links.js";
6
+ const DRAINER_SIGNATURES = [
7
+ [/setApprovalForAll/i, 'setApprovalForAll (ERC-721/1155 approval drainer)'],
8
+ [/permit2|permit\(/i, 'permit/permit2 signature grab'],
9
+ [/eth_sendTransaction|window\.ethereum/i, 'EVM wallet calls on a Solana token (chain mismatch)'],
10
+ [/eval\s*\(\s*atob\s*\(/i, 'eval(atob( obfuscated script'],
11
+ [/Function\s*\(\s*atob/i, 'Function(atob( obfuscated script'],
12
+ [/drainer|seaport\.js|inferno|monkey-drainer|pink-drainer/i, 'known drainer-kit string'],
13
+ [/signAllTransactions[\s\S]{0,400}(SystemProgram\.transfer|createTransferInstruction)/i, 'sign-all then transfer-all pattern'],
14
+ ];
15
+ const BUILDER_SIGNATURES = [
16
+ [/\.carrd\.co|data-carrd|cardScript/i, 'carrd'],
17
+ [/static\.wixstatic\.com|X-Wix-|wix\.com/i, 'wix'],
18
+ [/squarespace\.com|static1\.squarespace/i, 'squarespace'],
19
+ [/framerusercontent\.com|framer\.com|data-framer/i, 'framer'],
20
+ [/assets\.website-files\.com|webflow\.(io|com)|data-wf-/i, 'webflow'],
21
+ [/cdn\.durable\.co|durable\.co/i, 'durable'],
22
+ [/\.github\.io|github-pages/i, 'github-pages'],
23
+ [/vercel\.app|_next\/static/i, 'vercel/next'],
24
+ [/netlify\.app|netlify/i, 'netlify'],
25
+ [/dorik|data-dorik/i, 'dorik'],
26
+ ];
27
+ const DEFAULT_PAGE_SIGNATURES = [
28
+ [/there isn.?t a github pages site here|404\.html/i, 'github-pages 404'],
29
+ [/the deployment could not be found|deployment_not_found|welcome to vercel/i, 'vercel default/missing'],
30
+ [/this (domain|site) is (for sale|parked)|buy this domain|parkingcrew|sedoparking|godaddy\.com\/domainfind/i, 'parked domain'],
31
+ [/future home of something|default web page|it works!|apache2 (ubuntu|debian) default/i, 'web-server default page'],
32
+ [/site not found|no such app|there's nothing here/i, 'host placeholder (missing app)'],
33
+ [/this site can.?t be reached|dns_probe_finished/i, 'unreachable'],
34
+ [/^\s*(coming soon|under construction)\s*$/i, 'coming-soon stub'],
35
+ ];
36
+ function firstMatch(html, table) {
37
+ for (const [re, name] of table)
38
+ if (re.test(html))
39
+ return name;
40
+ return null;
41
+ }
42
+ function allMatches(html, table) {
43
+ const out = [];
44
+ for (const [re, name] of table)
45
+ if (re.test(html))
46
+ out.push(name);
47
+ return out;
48
+ }
49
+ function tag(html, re) {
50
+ const m = re.exec(html);
51
+ return m ? m[1].replace(/\s+/g, ' ').trim() : null;
52
+ }
53
+ /** Strip tags/scripts/styles to a rough visible-text approximation. */
54
+ function visibleText(html) {
55
+ return html
56
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
57
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
58
+ .replace(/<!--[\s\S]*?-->/g, ' ')
59
+ .replace(/<[^>]+>/g, ' ')
60
+ .replace(/&[a-z#0-9]+;/gi, ' ')
61
+ .replace(/\s+/g, ' ')
62
+ .trim();
63
+ }
64
+ /** Every absolute http(s) href/src on the page. */
65
+ export function extractUrls(html, baseUrl) {
66
+ const out = new Set();
67
+ const re = /(?:href|src)\s*=\s*["']([^"']+)["']/gi;
68
+ let m;
69
+ while ((m = re.exec(html)) !== null) {
70
+ const raw = m[1];
71
+ let abs;
72
+ try {
73
+ abs = new URL(raw, baseUrl ?? undefined).toString();
74
+ }
75
+ catch {
76
+ abs = null;
77
+ }
78
+ const p = abs ? parseHttpUrl(abs) : null;
79
+ if (p)
80
+ out.add(p.toString());
81
+ }
82
+ return [...out];
83
+ }
84
+ function hostOf(url) {
85
+ const u = parseHttpUrl(url);
86
+ return u ? u.hostname.toLowerCase().replace(/^www\./, '') : null;
87
+ }
88
+ /**
89
+ * Compute mechanical signals from a fetched page. `symbol`/`name` enable the "does the site even
90
+ * mention this token" check. Returns a `fetched:false` bundle (with the block reason) when the
91
+ * fetch failed, so the analyst can report it as a missing-data fact rather than guess.
92
+ */
93
+ export function pageSignals(res, opts = {}) {
94
+ const base = {
95
+ fetched: res.ok, status: res.status, finalUrl: res.ok ? res.finalUrl : null, blocked: res.blocked,
96
+ title: null, description: null, generator: null, siteBuilder: null, defaultPage: null,
97
+ textLength: 0, linkCount: 0, externalHosts: [], hasApp: false, hasDocs: false, hasWhitepaper: false,
98
+ hasGithub: false, walletConnect: false, drainerFlags: [], scriptHosts: [], mentionsSymbol: null, mentionsName: null, excerpt: '',
99
+ links: [], teamClaim: false, stockTextSignals: [],
100
+ };
101
+ if (!res.ok || !res.body)
102
+ return base;
103
+ const html = res.body;
104
+ const finalHost = hostOf(res.finalUrl);
105
+ const title = tag(html, /<title[^>]*>([\s\S]*?)<\/title>/i);
106
+ const description = tag(html, /<meta[^>]+name=["']description["'][^>]*content=["']([^"']*)["']/i)
107
+ ?? tag(html, /<meta[^>]+property=["']og:description["'][^>]*content=["']([^"']*)["']/i);
108
+ const generator = tag(html, /<meta[^>]+name=["']generator["'][^>]*content=["']([^"']*)["']/i);
109
+ const text = visibleText(html);
110
+ const urls = extractUrls(html, res.finalUrl);
111
+ const externalHosts = [...new Set(urls.map(hostOf).filter((h) => !!h && h !== finalHost))];
112
+ const scriptRe = /<script[^>]+src\s*=\s*["']([^"']+)["']/gi;
113
+ const scriptHosts = new Set();
114
+ let sm;
115
+ while ((sm = scriptRe.exec(html)) !== null) {
116
+ const h = hostOf(sm[1].startsWith('http') ? sm[1] : new URL(sm[1], res.finalUrl ?? 'https://x/').toString());
117
+ if (h && h !== finalHost)
118
+ scriptHosts.add(h);
119
+ }
120
+ const kinds = new Set(urls.map((u) => { const p = parseHttpUrl(u); return p ? classifyLink(p) : 'other'; }));
121
+ const lowerHtml = html.toLowerCase();
122
+ const hasApp = /\b(launch app|open app|dapp|\/app\b|app\.|demo|try (it|now)|get started)\b/i.test(text) || /href=["'][^"']*\/(app|dapp|demo)\b/i.test(html);
123
+ const sym = (opts.symbol ?? '').replace(/^\$/, '').toLowerCase();
124
+ const name = (opts.name ?? '').toLowerCase();
125
+ const lowerText = text.toLowerCase();
126
+ return {
127
+ ...base,
128
+ title: title ? title.slice(0, 160) : null,
129
+ description: description ? description.slice(0, 300) : null,
130
+ generator: generator ? generator.slice(0, 80) : null,
131
+ siteBuilder: firstMatch(html, BUILDER_SIGNATURES) ?? (generator ? generator.split(/[\s(]/)[0].toLowerCase() : null),
132
+ defaultPage: firstMatch(text, DEFAULT_PAGE_SIGNATURES) ?? (text.length < 40 ? 'near-empty page' : null),
133
+ textLength: text.length,
134
+ linkCount: urls.length,
135
+ externalHosts: externalHosts.slice(0, 30),
136
+ hasApp,
137
+ hasDocs: kinds.has('docs') || /\bdocs?\b|documentation|whitepaper|litepaper/i.test(text),
138
+ hasWhitepaper: /whitepaper|litepaper/i.test(lowerHtml),
139
+ hasGithub: kinds.has('github'),
140
+ walletConnect: /connect\s*wallet|walletconnect|@solana\/wallet-adapter|window\.solana|phantom|solflare/i.test(html),
141
+ drainerFlags: allMatches(html, DRAINER_SIGNATURES),
142
+ scriptHosts: [...scriptHosts].slice(0, 20),
143
+ mentionsSymbol: sym ? lowerText.includes(sym) : null,
144
+ mentionsName: name ? lowerText.includes(name) : null,
145
+ excerpt: text.slice(0, 1200),
146
+ links: urls.slice(0, 24).map((url) => ({ url, kind: classifyLink(new URL(url)) })),
147
+ teamClaim: /\b(our team|founder|co-founder|meet the team)\b/i.test(text),
148
+ stockTextSignals: allMatches(text, [[/lorem ipsum/i, 'lorem ipsum placeholder'], [/your (company|brand|business) (name|here)/i, 'template placeholder'], [/insert (text|copy|title) here/i, 'unfilled template']]),
149
+ };
150
+ }
151
+ /** Exact advertised domain, stripping only www. Without a PSL, never attribute
152
+ * a public suffix's registration (co.uk) to a project (docs.coin.co.uk).
153
+ * RDAP may refuse a subdomain; that is missing data, not an invented age. */
154
+ export function registrableDomain(url) {
155
+ const u = parseHttpUrl(url);
156
+ if (!u)
157
+ return null;
158
+ const host = u.hostname.toLowerCase().replace(/^www\./, '');
159
+ if (/^\d/.test(host) || host.includes(':'))
160
+ return null; // IP literal
161
+ const parts = host.split('.');
162
+ if (parts.length < 2)
163
+ return null;
164
+ return host;
165
+ }
166
+ /** Domain registration age + registrar via RDAP (rdap.org bootstrap). Keyless. */
167
+ export async function domainAgeViaRdap(url, fetchText, now = () => Date.now()) {
168
+ const domain = registrableDomain(url);
169
+ const base = { domain: domain ?? '', registeredAt: null, ageDays: null, registrar: null, privacy: null, source: 'rdap', error: null };
170
+ if (!domain)
171
+ return { ...base, error: 'no registrable domain' };
172
+ let r;
173
+ try {
174
+ r = await fetchText(`https://rdap.org/domain/${encodeURIComponent(domain)}`);
175
+ }
176
+ catch (e) {
177
+ return { ...base, error: `rdap fetch failed: ${e.message}` };
178
+ }
179
+ if (!r.ok)
180
+ return { ...base, error: `rdap status ${r.status}` };
181
+ let data;
182
+ try {
183
+ data = JSON.parse(r.body);
184
+ }
185
+ catch {
186
+ return { ...base, error: 'rdap: invalid json' };
187
+ }
188
+ const events = data.events ?? [];
189
+ const reg = events.find((e) => e.eventAction === 'registration')?.eventDate ?? null;
190
+ const entities = data.entities ?? [];
191
+ const registrar = entities.find((e) => e.roles?.includes('registrar'))?.vcardArray ? vcardName(entities.find((e) => e.roles?.includes('registrar')).vcardArray) : null;
192
+ const registrant = entities.find((e) => e.roles?.includes('registrant'));
193
+ const privacy = registrant ? /redact|privacy|protect|withheld|gdpr/i.test(JSON.stringify(registrant)) : null;
194
+ const ageDays = reg ? Math.max(0, Math.round((now() - Date.parse(reg)) / 86_400_000)) : null;
195
+ return { domain, registeredAt: reg, ageDays: Number.isFinite(ageDays) ? ageDays : null, registrar, privacy, source: 'rdap', error: null };
196
+ }
197
+ function vcardName(vcardArray) {
198
+ // vCard jCard: ["vcard", [ ["fn", {}, "text", "Name"], ... ]]
199
+ try {
200
+ const entries = vcardArray[1];
201
+ const fn = entries.find((e) => e[0] === 'fn');
202
+ return fn ? String(fn[3]).slice(0, 80) : null;
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }
208
+ /** Parse github.com/<owner>/<repo> from a URL. */
209
+ export function parseGithubRepo(url) {
210
+ const u = parseHttpUrl(url);
211
+ if (!u || u.hostname.toLowerCase().replace(/^www\./, '') !== 'github.com')
212
+ return null;
213
+ const parts = u.pathname.split('/').filter(Boolean);
214
+ if (parts.length < 2)
215
+ return null;
216
+ const owner = parts[0];
217
+ const repo = parts[1].replace(/\.git$/, '');
218
+ if (/^(orgs|sponsors|features|about|topics|collections|marketplace)$/i.test(owner))
219
+ return null;
220
+ return { owner, repo };
221
+ }
222
+ /** Repo creation/last-push/stars via the keyless GitHub REST API (60 req/hr unauth). */
223
+ export async function githubRepoActivity(url, fetchText, now = () => Date.now()) {
224
+ const r = parseGithubRepo(url);
225
+ if (!r)
226
+ return null;
227
+ const base = { owner: r.owner, repo: r.repo, createdAt: null, pushedAt: null, daysSincePush: null, stars: null, openIssues: null, archived: null, fork: null, description: null, error: null };
228
+ let res;
229
+ try {
230
+ res = await fetchText(`https://api.github.com/repos/${encodeURIComponent(r.owner)}/${encodeURIComponent(r.repo)}`);
231
+ }
232
+ catch (e) {
233
+ return { ...base, error: `github fetch failed: ${e.message}` };
234
+ }
235
+ if (!res.ok)
236
+ return { ...base, error: res.status === 404 ? 'repo not found' : `github status ${res.status}` };
237
+ let d;
238
+ try {
239
+ d = JSON.parse(res.body);
240
+ }
241
+ catch {
242
+ return { ...base, error: 'github: invalid json' };
243
+ }
244
+ const pushedAt = typeof d.pushed_at === 'string' ? d.pushed_at : null;
245
+ const daysSincePush = pushedAt ? Math.max(0, Math.round((now() - Date.parse(pushedAt)) / 86_400_000)) : null;
246
+ const commits = [];
247
+ let historyError = null;
248
+ try {
249
+ const history = await fetchText(`https://api.github.com/repos/${encodeURIComponent(r.owner)}/${encodeURIComponent(r.repo)}/commits?per_page=5`);
250
+ const parsed = history.ok ? JSON.parse(history.body) : null;
251
+ if (!Array.isArray(parsed))
252
+ historyError = `commit history unavailable (${history.status})`;
253
+ else
254
+ for (const item of parsed.slice(0, 5)) {
255
+ if (typeof item?.sha === 'string' && /^[a-f0-9]{40}$/.test(item.sha))
256
+ commits.push({ sha: item.sha, date: typeof item.commit?.committer?.date === 'string' ? item.commit.committer.date : null });
257
+ }
258
+ }
259
+ catch {
260
+ historyError = 'commit history unavailable';
261
+ }
262
+ return {
263
+ owner: r.owner, repo: r.repo,
264
+ createdAt: typeof d.created_at === 'string' ? d.created_at : null,
265
+ pushedAt, daysSincePush,
266
+ stars: typeof d.stargazers_count === 'number' ? d.stargazers_count : null,
267
+ openIssues: typeof d.open_issues_count === 'number' ? d.open_issues_count : null,
268
+ archived: typeof d.archived === 'boolean' ? d.archived : null,
269
+ fork: typeof d.fork === 'boolean' ? d.fork : null,
270
+ description: typeof d.description === 'string' ? d.description.slice(0, 200) : null,
271
+ commits, historyError,
272
+ error: null,
273
+ };
274
+ }
275
+ /** Reachability of a small set of advertised links (broken-link signal). Caller caps the list. */
276
+ export async function probeLinks(urls, fetchProbe, max = 8) {
277
+ const out = [];
278
+ for (const url of urls.slice(0, max)) {
279
+ try {
280
+ const r = await fetchProbe(url);
281
+ out.push({ url, ok: r.ok, status: r.status, blocked: r.blocked });
282
+ }
283
+ catch (e) {
284
+ out.push({ url, ok: false, status: 0, blocked: e.message });
285
+ }
286
+ }
287
+ return out;
288
+ }
@@ -0,0 +1,196 @@
1
+ // LARP check — links-only baseline grade (task-1401). Pure, offline, deterministic.
2
+ //
3
+ // The LARP check (skill `larp-check`) judges a coin's product from fetched pages, the domain and GitHub. That
4
+ // model stage cannot be replayed on history: the pages are gone or have changed since. What CAN be replayed is
5
+ // the part of the evidence the recorder keeps point-in-time: the website / X / Telegram links a coin advertised
6
+ // (DexScreener `info` + Jupiter metadata, the same fields `get_links` reads). This module turns those three links
7
+ // into named signals and a baseline grade, so the same function scores the backtest eval set and the live
8
+ // `get_links` result the skill starts from.
9
+ //
10
+ // What the grade is: a RISK FLAG from link shape alone, never a verdict on the product. It uses only the skill's
11
+ // verdict names it can honestly reach: `LARP` (the link shape contradicts a product claim: a social or launchpad
12
+ // page posing as the website, a placeholder host, a tweet instead of an account), `UNVERIFIABLE` (no website at
13
+ // all, nothing to vet) and `EARLY_BUT_REAL` (an own-domain site with nothing contradicting it). It never says
14
+ // `REAL_PRODUCT`: links alone cannot show a reachable product. Weights are tuned on the TRAIN split of the
15
+ // recorded eval set only (packages/packs/eval/README.md); the measured test-split numbers ship with the rubric.
16
+ import { parseHttpUrl } from "./links.js";
17
+ /** The named signals a rubric weighs. All are LARP-direction (a weight > 0 pushes toward `LARP`). */
18
+ export const LINK_SIGNALS = [
19
+ 'site_is_social', 'site_is_launchpad', 'site_linktree', 'site_free_host', 'site_no_name_match',
20
+ 'cheap_tld', 'x_not_profile', 'no_x', 'no_tg',
21
+ ];
22
+ /**
23
+ * v0: the pre-tuning rubric, written from the larp-check skill text BEFORE any backtest (task-1401): a social or
24
+ * launchpad page posing as the website and a tweet/community link instead of an account are the link-visible
25
+ * LARP signals the skill names; a placeholder host, a link hub, and a site whose domain does not name the coin
26
+ * count one point each. This is the "before" the tuned rubric is compared with.
27
+ */
28
+ export const LINK_RUBRIC_V0 = {
29
+ id: 'larp-links-v0',
30
+ weights: { site_is_social: 2, site_is_launchpad: 2, site_linktree: 1, site_free_host: 1, site_no_name_match: 1, cheap_tld: 0, x_not_profile: 1, no_x: 0, no_tg: 0 },
31
+ threshold: 2,
32
+ provenance: 'hand-written from the larp-check skill text before the backtest (task-1401)',
33
+ };
34
+ /**
35
+ * v1: tuned on the TRAIN split of packages/packs/eval/skills-eval.json (grid search over weights 0..2 and
36
+ * threshold 1..5, objective MCC of LARP→bad, ties to the smaller weight sum). Test-split numbers:
37
+ * packages/packs/eval/skills-backtest.json. Regenerate with `dexterm backtest --subject skills --tune`.
38
+ */
39
+ export const LINK_RUBRIC_V1 = {
40
+ id: 'larp-links-v1',
41
+ weights: { site_is_social: 0, site_is_launchpad: 0, site_linktree: 0, site_free_host: 1, site_no_name_match: 1, cheap_tld: 1, x_not_profile: 2, no_x: 1, no_tg: 1 },
42
+ threshold: 4,
43
+ provenance: 'tuned on the train split of the task-1401 eval set (see packages/packs/eval/README.md)',
44
+ };
45
+ /**
46
+ * The held-out TEST-split counts for LINK_RUBRIC_V1 (packages/packs/eval/skills-backtest.json `larp.after.test`).
47
+ * A test asserts these match the committed report, so a re-tune cannot ship stale numbers.
48
+ */
49
+ export const LINK_RUBRIC_MEASURED = {
50
+ rubric: 'larp-links-v1',
51
+ eval: 'task-1401 eval set, 2026-10-16',
52
+ horizon: '72h',
53
+ test: {
54
+ n: 328, bad: 61,
55
+ by_grade: { LARP: { n: 14, bad: 4 }, UNVERIFIABLE: { n: 130, bad: 38 }, EARLY_BUT_REAL: { n: 184, bad: 19 } },
56
+ },
57
+ };
58
+ /** The rubric the live `get_links` baseline uses. */
59
+ export const DEFAULT_LINK_RUBRIC = LINK_RUBRIC_V1;
60
+ const SOCIAL_HOSTS = new Set([
61
+ 'x.com', 'twitter.com', 'mobile.twitter.com', 't.me', 'telegram.me', 'telegram.dog', 'discord.gg', 'discord.com',
62
+ 'discordapp.com', 'instagram.com', 'tiktok.com', 'youtube.com', 'm.youtube.com', 'youtu.be', 'facebook.com', 'fb.com',
63
+ 'reddit.com', 'threads.net', 'warpcast.com', 'truthsocial.com', 'kick.com', 'twitch.tv',
64
+ ]);
65
+ const LAUNCHPAD_HOSTS = new Set([
66
+ 'pump.fun', 'dexscreener.com', 'birdeye.so', 'solscan.io', 'jup.ag', 'raydium.io', 'meteora.ag', 'letsbonk.fun',
67
+ 'bonk.fun', 'moonshot.money', 'believe.app', 'bags.fm', 'gmgn.ai', 'axiom.trade', 'dextools.io', 'coingecko.com',
68
+ 'coinmarketcap.com', 'photon-sol.tinyastro.io', 'geckoterminal.com', 'rugcheck.xyz', 'solana.fm', 'heaven.xyz',
69
+ ]);
70
+ const LINKHUB_HOSTS = new Set(['linktr.ee', 'linktree.com', 'link3.to', 'bio.link', 'beacons.ai', 'campsite.bio', 'solo.to', 'lnk.bio', 'hoo.be', 'allmylinks.com']);
71
+ const FREE_HOST_SUFFIXES = [
72
+ 'vercel.app', 'netlify.app', 'github.io', 'carrd.co', 'wixsite.com', 'webflow.io', 'framer.website', 'framer.app',
73
+ 'framer.ai', 'notion.site', 'gitbook.io', 'substack.com', 'pages.dev', 'web.app', 'firebaseapp.com', 'herokuapp.com',
74
+ 'replit.app', 'repl.co', 'glitch.me', 'weebly.com', 'wordpress.com', 'blogspot.com', 'mystrikingly.com',
75
+ 'godaddysites.com', 'squarespace.com', 'medium.com', 'durable.co', 'lovable.app', 'onrender.com', 'surge.sh',
76
+ 'webnode.page', 'site123.me', 'tilda.ws', 'super.site', 'my.canva.site', 'canva.site', 'bolt.host', 'netlify.com',
77
+ ];
78
+ const FREE_HOST_EXACT = new Set(['sites.google.com', 'docs.google.com', 'drive.google.com', 'telegra.ph', 'pastebin.com', 'medium.com']);
79
+ const CHEAP_TLDS = new Set(['fun', 'xyz', 'lol', 'meme', 'site', 'online', 'club', 'top', 'vip', 'wtf', 'space', 'icu', 'buzz', 'cfd', 'sbs', 'click', 'live', 'world', 'today', 'pw', 'cc', 'monster', 'rest', 'bond', 'cyou']);
80
+ const X_RESERVED = new Set(['home', 'search', 'explore', 'i', 'hashtag', 'intent', 'share', 'messages', 'notifications', 'settings', 'compose']);
81
+ const hostOf = (u) => u.hostname.toLowerCase().replace(/^www\./, '').replace(/\.$/, '');
82
+ const endsWithHost = (host, suffix) => host === suffix || host.endsWith(`.${suffix}`);
83
+ const norm = (s) => s.toLowerCase().normalize('NFKD').replace(/[^a-z0-9]/g, '');
84
+ function webKind(raw) {
85
+ if (raw === null || raw === undefined || String(raw).trim() === '')
86
+ return { kind: 'none', url: null };
87
+ const u = parseHttpUrl(raw);
88
+ if (!u)
89
+ return { kind: 'invalid', url: null };
90
+ const host = hostOf(u);
91
+ if ([...SOCIAL_HOSTS].some((h) => endsWithHost(host, h)))
92
+ return { kind: 'social', url: u };
93
+ if ([...LAUNCHPAD_HOSTS].some((h) => endsWithHost(host, h)))
94
+ return { kind: 'launchpad', url: u };
95
+ if (LINKHUB_HOSTS.has(host))
96
+ return { kind: 'linktree', url: u };
97
+ if (FREE_HOST_EXACT.has(host) || FREE_HOST_SUFFIXES.some((s) => host.endsWith(`.${s}`)))
98
+ return { kind: 'free-host', url: u };
99
+ return { kind: 'own-domain', url: u };
100
+ }
101
+ function xKind(raw) {
102
+ if (raw === null || raw === undefined || String(raw).trim() === '')
103
+ return 'none';
104
+ const u = parseHttpUrl(raw);
105
+ if (!u)
106
+ return 'other';
107
+ const host = hostOf(u);
108
+ if (!['x.com', 'twitter.com', 'mobile.twitter.com'].includes(host))
109
+ return 'other';
110
+ const parts = u.pathname.split('/').filter(Boolean).map((p) => p.toLowerCase());
111
+ if (parts[0] === 'i' && parts[1] === 'communities')
112
+ return 'community';
113
+ if (!parts.length || X_RESERVED.has(parts[0]))
114
+ return 'other';
115
+ if (parts[1] === 'status' || parts[1] === 'statuses')
116
+ return 'status';
117
+ if (parts.length === 1 && /^[a-z0-9_]{1,15}$/.test(parts[0]))
118
+ return 'profile';
119
+ return 'other';
120
+ }
121
+ /** Does the site's domain carry the token's symbol or name? Symbol ≥ 3 chars, name compacted or a ≥ 4-char word. */
122
+ function domainMatches(u, symbol, name) {
123
+ const sym = norm(String(symbol ?? '').replace(/^\$/, ''));
124
+ const nm = norm(String(name ?? ''));
125
+ const words = String(name ?? '').toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 4);
126
+ if (!sym && !nm)
127
+ return null;
128
+ const host = hostOf(u);
129
+ const labels = host.split('.');
130
+ const body = norm(labels.length > 1 ? labels.slice(0, -1).join('') : host);
131
+ if (!body)
132
+ return false;
133
+ if (sym.length >= 3 && body.includes(sym))
134
+ return true;
135
+ if (nm.length >= 3 && body.includes(nm))
136
+ return true;
137
+ return words.some((w) => body.includes(norm(w)));
138
+ }
139
+ /**
140
+ * Derive the URL-free features from the three primary links (the store's merge: Jupiter first, then DexScreener's
141
+ * first website / first X / first Telegram). `symbol`/`name` enable the domain-match signal; they are not kept.
142
+ */
143
+ export function linkFeatures(input) {
144
+ const w = webKind(input.web);
145
+ return {
146
+ web: w.kind,
147
+ x: xKind(input.x),
148
+ tg: !!(input.tg && String(input.tg).trim()),
149
+ webMatchesToken: w.url && (w.kind === 'own-domain' || w.kind === 'free-host') ? domainMatches(w.url, input.symbol, input.name) : null,
150
+ cheapTld: w.url && w.kind === 'own-domain' ? CHEAP_TLDS.has(hostOf(w.url).split('.').pop() ?? '') : null,
151
+ };
152
+ }
153
+ /** The signals present in a feature set. */
154
+ export function linkSignals(f) {
155
+ const s = [];
156
+ if (f.web === 'social')
157
+ s.push('site_is_social');
158
+ if (f.web === 'launchpad')
159
+ s.push('site_is_launchpad');
160
+ if (f.web === 'linktree')
161
+ s.push('site_linktree');
162
+ if (f.web === 'free-host' || f.web === 'invalid')
163
+ s.push('site_free_host');
164
+ if (f.webMatchesToken === false)
165
+ s.push('site_no_name_match');
166
+ if (f.cheapTld === true)
167
+ s.push('cheap_tld');
168
+ if (f.x === 'status' || f.x === 'community' || f.x === 'other')
169
+ s.push('x_not_profile');
170
+ if (f.x === 'none')
171
+ s.push('no_x');
172
+ if (!f.tg)
173
+ s.push('no_tg');
174
+ return s;
175
+ }
176
+ /** Grade one coin's links with a rubric. No website at all is `UNVERIFIABLE` (fixed rule, never tuned). */
177
+ export function gradeLinks(f, rubric = DEFAULT_LINK_RUBRIC) {
178
+ const signals = linkSignals(f);
179
+ const score = signals.reduce((a, s) => a + (rubric.weights[s] ?? 0), 0);
180
+ const grade = f.web === 'none' ? 'UNVERIFIABLE' : score >= rubric.threshold ? 'LARP' : 'EARLY_BUT_REAL';
181
+ return { rubric: rubric.id, grade, score, signals };
182
+ }
183
+ /**
184
+ * The store's primary-link merge (packages/data/src/store.ts: Jupiter first, then DexScreener), so the live
185
+ * baseline reads the same three links the recorder's cache holds for the eval set.
186
+ */
187
+ export function primaryLinks(src) {
188
+ const info = src.dexscreener?.info;
189
+ const dsX = info?.socials?.find((s) => s?.type === 'twitter' || s?.type === 'x')?.url ?? null;
190
+ const dsTg = info?.socials?.find((s) => s?.type === 'telegram')?.url ?? null;
191
+ return {
192
+ web: src.jupiter?.website ?? info?.websites?.[0]?.url ?? null,
193
+ x: src.jupiter?.twitter ?? dsX,
194
+ tg: src.jupiter?.telegram ?? dsTg,
195
+ };
196
+ }
@@ -0,0 +1,137 @@
1
+ // LARP detector — link extraction (task-1315). Pure, offline, deterministic.
2
+ // Collects every website/social link a coin advertises from the three keyless
3
+ // sources we already read — DexScreener token `info` (websites[] + socials[]),
4
+ // Jupiter token metadata (website/twitter/telegram) and the pump.fun metadata
5
+ // URI JSON — then classifies and de-duplicates them. No network here: callers
6
+ // pass already-fetched raw shapes. Only http/https links survive; mailto:,
7
+ // javascript:, data: and relative links are dropped.
8
+ const MAX_LINKS = 24;
9
+ /** Parse to a URL only if it is a syntactically valid absolute http(s) link. */
10
+ export function parseHttpUrl(raw) {
11
+ if (typeof raw !== 'string')
12
+ return null;
13
+ const s = raw.trim();
14
+ if (!s || s.length > 2048)
15
+ return null;
16
+ let u;
17
+ try {
18
+ u = new URL(s);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ if (u.protocol !== 'http:' && u.protocol !== 'https:')
24
+ return null;
25
+ if (u.username || u.password)
26
+ return null;
27
+ if (!u.hostname)
28
+ return null;
29
+ return u;
30
+ }
31
+ /** Lower-cased host with any leading `www.` removed. */
32
+ function bareHost(u) {
33
+ return u.hostname.toLowerCase().replace(/^www\./, '');
34
+ }
35
+ /** Classify a link by host (and path for docs). Host is the source of truth, not any label. */
36
+ export function classifyLink(u) {
37
+ const host = bareHost(u);
38
+ const path = u.pathname.toLowerCase();
39
+ if (host === 'twitter.com' || host === 'x.com' || host === 'fxtwitter.com' || host === 'nitter.net' || host === 't.co')
40
+ return 'x';
41
+ if (host === 't.me' || host === 'telegram.me' || host === 'telegram.dog')
42
+ return 'telegram';
43
+ if (host === 'discord.gg' || host === 'discord.com' || host === 'discordapp.com')
44
+ return 'discord';
45
+ if (host === 'github.com' || host === 'raw.githubusercontent.com')
46
+ return 'github';
47
+ if (host === 'medium.com' || host.endsWith('.medium.com'))
48
+ return 'medium';
49
+ if (host === 'youtube.com' || host === 'youtu.be' || host.endsWith('.youtube.com'))
50
+ return 'youtube';
51
+ if (host === 'reddit.com' || host.endsWith('.reddit.com'))
52
+ return 'reddit';
53
+ if (host === 'instagram.com' || host.endsWith('.instagram.com'))
54
+ return 'instagram';
55
+ if (host === 'tiktok.com' || host.endsWith('.tiktok.com'))
56
+ return 'tiktok';
57
+ if (host === 'facebook.com' || host === 'fb.com' || host.endsWith('.facebook.com'))
58
+ return 'facebook';
59
+ if (host === 'linktr.ee' || host === 'linktree.com')
60
+ return 'linktree';
61
+ if (host.endsWith('.gitbook.io') || host === 'gitbook.io' || host.startsWith('docs.') || path.startsWith('/docs') || path.startsWith('/whitepaper'))
62
+ return 'docs';
63
+ return 'website';
64
+ }
65
+ /** A short, injection-inert label for the link list (host; DexScreener website labels when sane). */
66
+ function labelFor(u, rawLabel, kind) {
67
+ const host = bareHost(u);
68
+ if (kind === 'website' && rawLabel) {
69
+ const clean = rawLabel.replace(/[\u0000-\u001F\u007F]/g, '').replace(/\s+/g, ' ').trim().slice(0, 24);
70
+ if (clean)
71
+ return clean;
72
+ }
73
+ return host;
74
+ }
75
+ /** Map a DexScreener social `type` to our kind when the host is ambiguous (host still wins). */
76
+ function socialTypeKind(type) {
77
+ switch ((type ?? '').toLowerCase()) {
78
+ case 'twitter':
79
+ case 'x': return 'x';
80
+ case 'telegram': return 'telegram';
81
+ case 'discord': return 'discord';
82
+ case 'github': return 'github';
83
+ case 'medium': return 'medium';
84
+ case 'youtube': return 'youtube';
85
+ case 'reddit': return 'reddit';
86
+ case 'instagram': return 'instagram';
87
+ case 'tiktok': return 'tiktok';
88
+ case 'facebook': return 'facebook';
89
+ default: return null;
90
+ }
91
+ }
92
+ /** De-dup key: scheme-insensitive host + path (ignores trailing slash and query). */
93
+ function dedupKey(u) {
94
+ return `${bareHost(u)}${u.pathname.replace(/\/+$/, '')}`.toLowerCase();
95
+ }
96
+ /**
97
+ * Extract and classify every advertised link for a token. Source order sets priority for the
98
+ * primary website/x/telegram picks and for de-duplication (the first occurrence wins its label).
99
+ */
100
+ export function extractLinks(src) {
101
+ const out = [];
102
+ const seen = new Set();
103
+ const add = (raw, source, typeHint, rawLabel) => {
104
+ if (out.length >= MAX_LINKS)
105
+ return;
106
+ const u = parseHttpUrl(raw);
107
+ if (!u)
108
+ return;
109
+ const key = dedupKey(u);
110
+ if (seen.has(key))
111
+ return;
112
+ seen.add(key);
113
+ // Host classification wins; the DexScreener `type` only disambiguates a generic host.
114
+ let kind = classifyLink(u);
115
+ if (kind === 'website') {
116
+ const hinted = socialTypeKind(typeHint);
117
+ if (hinted)
118
+ kind = hinted;
119
+ }
120
+ out.push({ kind, url: u.toString(), label: labelFor(u, rawLabel, kind), source });
121
+ };
122
+ // 1) DexScreener info: explicit websites[] then socials[].
123
+ for (const w of src.dexscreener?.info?.websites ?? [])
124
+ add(w?.url, 'dexscreener', undefined, w?.label);
125
+ for (const s of src.dexscreener?.info?.socials ?? [])
126
+ add(s?.url, 'dexscreener', s?.type, s?.label);
127
+ // 2) Jupiter flat fields.
128
+ add(src.jupiter?.website, 'jupiter');
129
+ add(src.jupiter?.twitter, 'jupiter', 'twitter');
130
+ add(src.jupiter?.telegram, 'jupiter', 'telegram');
131
+ // 3) pump.fun metadata URI JSON.
132
+ add(src.pumpfun?.website, 'pumpfun');
133
+ add(src.pumpfun?.twitter ?? src.pumpfun?.x, 'pumpfun', 'twitter');
134
+ add(src.pumpfun?.telegram, 'pumpfun', 'telegram');
135
+ const first = (k) => out.find((l) => l.kind === k)?.url ?? null;
136
+ return { website: first('website'), x: first('x'), telegram: first('telegram'), all: out };
137
+ }