dexterm-cli 0.0.0-stage → 0.1.1

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 (349) 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 +379 -0
  4. package/dist/packages/agents/src/executor.js +177 -0
  5. package/dist/packages/agents/src/gate.js +176 -0
  6. package/dist/packages/agents/src/identity.js +179 -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 +114 -0
  12. package/dist/packages/agents/src/tools.js +69 -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 +67 -0
  18. package/dist/packages/cli/assets/claude/plugin/skills/rug-check/SKILL.md +64 -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 +303 -0
  26. package/dist/packages/cli/src/agent-roster.js +26 -0
  27. package/dist/packages/cli/src/analyst/backend.js +526 -0
  28. package/dist/packages/cli/src/analyst/results.js +38 -0
  29. package/dist/packages/cli/src/analyst/session.js +319 -0
  30. package/dist/packages/cli/src/audit.js +51 -0
  31. package/dist/packages/cli/src/backtest-cmd.js +149 -0
  32. package/dist/packages/cli/src/boot-state.js +62 -0
  33. package/dist/packages/cli/src/custom.js +261 -0
  34. package/dist/packages/cli/src/display.js +191 -0
  35. package/dist/packages/cli/src/doctor.js +361 -0
  36. package/dist/packages/cli/src/feed.js +634 -0
  37. package/dist/packages/cli/src/fixtures.js +275 -0
  38. package/dist/packages/cli/src/follow-cmd.js +221 -0
  39. package/dist/packages/cli/src/images-config.js +59 -0
  40. package/dist/packages/cli/src/index.js +31 -0
  41. package/dist/packages/cli/src/kb-skill-cmd.js +240 -0
  42. package/dist/packages/cli/src/larp-record.js +21 -0
  43. package/dist/packages/cli/src/live/copy.js +80 -0
  44. package/dist/packages/cli/src/live/flow.js +233 -0
  45. package/dist/packages/cli/src/live/index.js +509 -0
  46. package/dist/packages/cli/src/live/intel.js +227 -0
  47. package/dist/packages/cli/src/live/newpairs.js +334 -0
  48. package/dist/packages/cli/src/live/screens/newpairs-live.js +219 -0
  49. package/dist/packages/cli/src/live/screens/token-live.js +459 -0
  50. package/dist/packages/cli/src/live/screens/watchlist-live.js +149 -0
  51. package/dist/packages/cli/src/live/token.js +585 -0
  52. package/dist/packages/cli/src/live/trade.js +929 -0
  53. package/dist/packages/cli/src/live/watchlist.js +178 -0
  54. package/dist/packages/cli/src/main.js +5583 -0
  55. package/dist/packages/cli/src/narrative-cmd.js +76 -0
  56. package/dist/packages/cli/src/pack-cmd.js +323 -0
  57. package/dist/packages/cli/src/plan-cmd.js +278 -0
  58. package/dist/packages/cli/src/popup.js +810 -0
  59. package/dist/packages/cli/src/rules-cmd.js +131 -0
  60. package/dist/packages/cli/src/rules.js +339 -0
  61. package/dist/packages/cli/src/scan/command.js +57 -0
  62. package/dist/packages/cli/src/scan/engine.js +419 -0
  63. package/dist/packages/cli/src/scan/observe.js +64 -0
  64. package/dist/packages/cli/src/scan/ui.js +98 -0
  65. package/dist/packages/cli/src/screener/filter.js +241 -0
  66. package/dist/packages/cli/src/screener/presets.js +80 -0
  67. package/dist/packages/cli/src/screener/rows.js +146 -0
  68. package/dist/packages/cli/src/screener/rug.js +50 -0
  69. package/dist/packages/cli/src/screener/sort.js +52 -0
  70. package/dist/packages/cli/src/screener/view.js +183 -0
  71. package/dist/packages/cli/src/screens/analyst-session.js +343 -0
  72. package/dist/packages/cli/src/screens/analyst.js +326 -0
  73. package/dist/packages/cli/src/screens/boot.js +74 -0
  74. package/dist/packages/cli/src/screens/bubbles.js +255 -0
  75. package/dist/packages/cli/src/screens/coin.js +33 -0
  76. package/dist/packages/cli/src/screens/follows.js +201 -0
  77. package/dist/packages/cli/src/screens/kol.js +90 -0
  78. package/dist/packages/cli/src/screens/narrative.js +90 -0
  79. package/dist/packages/cli/src/screens/newpairs.js +150 -0
  80. package/dist/packages/cli/src/screens/overlay.js +257 -0
  81. package/dist/packages/cli/src/screens/plans.js +126 -0
  82. package/dist/packages/cli/src/screens/positions.js +176 -0
  83. package/dist/packages/cli/src/screens/rules.js +170 -0
  84. package/dist/packages/cli/src/screens/screener.js +459 -0
  85. package/dist/packages/cli/src/screens/social-panel.js +107 -0
  86. package/dist/packages/cli/src/screens/ticket.js +342 -0
  87. package/dist/packages/cli/src/screens/token.js +312 -0
  88. package/dist/packages/cli/src/screens/trending.js +181 -0
  89. package/dist/packages/cli/src/screens/watchlist.js +104 -0
  90. package/dist/packages/cli/src/social/demo.js +96 -0
  91. package/dist/packages/cli/src/social/view.js +250 -0
  92. package/dist/packages/cli/src/state.js +463 -0
  93. package/dist/packages/cli/src/theme-cmd.js +126 -0
  94. package/dist/packages/cli/src/trade.js +319 -0
  95. package/dist/packages/cli/src/voice-hud.js +42 -0
  96. package/dist/packages/cli/src/wallet-cmd.js +440 -0
  97. package/dist/packages/cli/src/wallet-command.js +31 -0
  98. package/dist/packages/cli/src/widgets.js +208 -0
  99. package/dist/packages/cli/src/workspace-cmd.js +68 -0
  100. package/dist/packages/cli/src/x-cmd.js +154 -0
  101. package/dist/packages/custom/src/config.js +213 -0
  102. package/dist/packages/custom/src/index.js +10 -0
  103. package/dist/packages/custom/src/keymap.js +107 -0
  104. package/dist/packages/custom/src/layout.js +235 -0
  105. package/dist/packages/custom/src/packs.js +633 -0
  106. package/dist/packages/custom/src/rules.js +704 -0
  107. package/dist/packages/custom/src/schema.js +515 -0
  108. package/dist/packages/custom/src/themes.js +176 -0
  109. package/dist/packages/custom/src/widget-html.js +222 -0
  110. package/dist/packages/custom/src/workspace.js +141 -0
  111. package/dist/packages/data/src/analyst-actions.js +133 -0
  112. package/dist/packages/data/src/budget.js +88 -0
  113. package/dist/packages/data/src/cache.js +142 -0
  114. package/dist/packages/data/src/candles.js +168 -0
  115. package/dist/packages/data/src/clock.js +64 -0
  116. package/dist/packages/data/src/credits.js +137 -0
  117. package/dist/packages/data/src/detail.js +97 -0
  118. package/dist/packages/data/src/fast-lane.js +321 -0
  119. package/dist/packages/data/src/fixtures.js +135 -0
  120. package/dist/packages/data/src/health.js +245 -0
  121. package/dist/packages/data/src/http.js +277 -0
  122. package/dist/packages/data/src/images/cache.js +141 -0
  123. package/dist/packages/data/src/images/decode-worker.js +28 -0
  124. package/dist/packages/data/src/images/decode.js +81 -0
  125. package/dist/packages/data/src/images/fetch.js +162 -0
  126. package/dist/packages/data/src/images/gif.js +214 -0
  127. package/dist/packages/data/src/images/index.js +10 -0
  128. package/dist/packages/data/src/images/jpeg.js +544 -0
  129. package/dist/packages/data/src/images/png.js +310 -0
  130. package/dist/packages/data/src/images/service.js +334 -0
  131. package/dist/packages/data/src/images/types.js +27 -0
  132. package/dist/packages/data/src/index.js +208 -0
  133. package/dist/packages/data/src/intel/helius-history.js +59 -0
  134. package/dist/packages/data/src/intel/holder-class.js +125 -0
  135. package/dist/packages/data/src/intel/rules.js +421 -0
  136. package/dist/packages/data/src/intel/scan.js +170 -0
  137. package/dist/packages/data/src/larp/evidence.js +288 -0
  138. package/dist/packages/data/src/larp/links.js +137 -0
  139. package/dist/packages/data/src/larp/robots.js +90 -0
  140. package/dist/packages/data/src/larp/safe-fetch.js +389 -0
  141. package/dist/packages/data/src/limiter.js +341 -0
  142. package/dist/packages/data/src/model.js +12 -0
  143. package/dist/packages/data/src/paper.js +331 -0
  144. package/dist/packages/data/src/rpc-config.js +126 -0
  145. package/dist/packages/data/src/rpc-pool.js +166 -0
  146. package/dist/packages/data/src/social/chain.js +83 -0
  147. package/dist/packages/data/src/social/dedupe.js +205 -0
  148. package/dist/packages/data/src/social/feed.js +462 -0
  149. package/dist/packages/data/src/social/firewall.js +110 -0
  150. package/dist/packages/data/src/social/index.js +11 -0
  151. package/dist/packages/data/src/social/kol.js +269 -0
  152. package/dist/packages/data/src/social/model.js +11 -0
  153. package/dist/packages/data/src/social/rank.js +184 -0
  154. package/dist/packages/data/src/social/rss.js +113 -0
  155. package/dist/packages/data/src/social/summary.js +104 -0
  156. package/dist/packages/data/src/social/venues.js +186 -0
  157. package/dist/packages/data/src/sources/dexscreener.js +170 -0
  158. package/dist/packages/data/src/sources/geckoterminal.js +133 -0
  159. package/dist/packages/data/src/sources/goplus.js +161 -0
  160. package/dist/packages/data/src/sources/jupiter-swap.js +236 -0
  161. package/dist/packages/data/src/sources/jupiter.js +198 -0
  162. package/dist/packages/data/src/sources/pda.js +113 -0
  163. package/dist/packages/data/src/sources/pump-curve.js +58 -0
  164. package/dist/packages/data/src/sources/pumpportal.js +247 -0
  165. package/dist/packages/data/src/sources/rugcheck.js +71 -0
  166. package/dist/packages/data/src/sources/solana-rpc.js +299 -0
  167. package/dist/packages/data/src/sources/solana-ws.js +287 -0
  168. package/dist/packages/data/src/store.js +289 -0
  169. package/dist/packages/data/src/trade/bridge.js +464 -0
  170. package/dist/packages/data/src/trade/caps.js +323 -0
  171. package/dist/packages/data/src/trade/copy/auto.js +385 -0
  172. package/dist/packages/data/src/trade/copy/follows.js +103 -0
  173. package/dist/packages/data/src/trade/copy/index.js +9 -0
  174. package/dist/packages/data/src/trade/copy/mirror.js +91 -0
  175. package/dist/packages/data/src/trade/copy/stats.js +120 -0
  176. package/dist/packages/data/src/trade/copy/tracker.js +158 -0
  177. package/dist/packages/data/src/trade/copy/trades.js +87 -0
  178. package/dist/packages/data/src/trade/copy/types.js +6 -0
  179. package/dist/packages/data/src/trade/executor.js +933 -0
  180. package/dist/packages/data/src/trade/kill-switch.js +47 -0
  181. package/dist/packages/data/src/trade/link-store.js +52 -0
  182. package/dist/packages/data/src/trade/plans/confirm.js +157 -0
  183. package/dist/packages/data/src/trade/plans/engine.js +784 -0
  184. package/dist/packages/data/src/trade/plans/executor-live.js +220 -0
  185. package/dist/packages/data/src/trade/plans/executor-paper.js +44 -0
  186. package/dist/packages/data/src/trade/plans/executor.js +11 -0
  187. package/dist/packages/data/src/trade/plans/index.js +12 -0
  188. package/dist/packages/data/src/trade/plans/journal.js +77 -0
  189. package/dist/packages/data/src/trade/plans/mandate.js +129 -0
  190. package/dist/packages/data/src/trade/plans/plan.js +179 -0
  191. package/dist/packages/data/src/trade/plans/service.js +168 -0
  192. package/dist/packages/data/src/trade/plans/types.js +6 -0
  193. package/dist/packages/data/src/trade/rpc-exec.js +229 -0
  194. package/dist/packages/data/src/trade/signer/client.js +197 -0
  195. package/dist/packages/data/src/trade/signer/fast-wallet.js +154 -0
  196. package/dist/packages/data/src/trade/signer/host.js +97 -0
  197. package/dist/packages/data/src/trade/signer/main.js +121 -0
  198. package/dist/packages/data/src/trade/signer/probe-builder.js +85 -0
  199. package/dist/packages/data/src/trade/signer/protocol.js +92 -0
  200. package/dist/packages/data/src/trade/signer/service.js +400 -0
  201. package/dist/packages/data/src/trade/signer/sol-usd-feed.js +98 -0
  202. package/dist/packages/data/src/trade/sol-usd.js +86 -0
  203. package/dist/packages/data/src/trade/swap-build.js +108 -0
  204. package/dist/packages/data/src/trade/token-delta.js +201 -0
  205. package/dist/packages/data/src/trade/tx-decode.js +632 -0
  206. package/dist/packages/data/src/util.js +24 -0
  207. package/dist/packages/data/src/watchlist.js +176 -0
  208. package/dist/packages/data/src/x/api.js +256 -0
  209. package/dist/packages/data/src/x/budget.js +66 -0
  210. package/dist/packages/data/src/x/config.js +49 -0
  211. package/dist/packages/data/src/x/index.js +10 -0
  212. package/dist/packages/data/src/x/mock-server.js +217 -0
  213. package/dist/packages/data/src/x/oauth.js +242 -0
  214. package/dist/packages/data/src/x/pkce.js +21 -0
  215. package/dist/packages/data/src/x/source.js +285 -0
  216. package/dist/packages/data/src/x/token-store.js +134 -0
  217. package/dist/packages/data/src/x/transport.js +71 -0
  218. package/dist/packages/data/vendor/solana-web3/LICENSE +20 -0
  219. package/dist/packages/data/vendor/solana-web3/NOTICE.md +13 -0
  220. package/dist/packages/data/vendor/solana-web3/index.iife.min.js +20 -0
  221. package/dist/packages/mcp/assets/cases.json +1194 -0
  222. package/dist/packages/mcp/assets/kb/entries/cabal-and-coordinated-launches.md +28 -0
  223. package/dist/packages/mcp/assets/kb/entries/common-rug-patterns.md +40 -0
  224. package/dist/packages/mcp/assets/kb/entries/dbc-mechanics.md +30 -0
  225. package/dist/packages/mcp/assets/kb/entries/dev-wallet-behaviour.md +33 -0
  226. package/dist/packages/mcp/assets/kb/entries/glossary.md +35 -0
  227. package/dist/packages/mcp/assets/kb/entries/holder-concentration.md +30 -0
  228. package/dist/packages/mcp/assets/kb/entries/jito-bundles.md +29 -0
  229. package/dist/packages/mcp/assets/kb/entries/launchlab-curve.md +31 -0
  230. package/dist/packages/mcp/assets/kb/entries/lp-burn-vs-lock.md +31 -0
  231. package/dist/packages/mcp/assets/kb/entries/metadata-and-copycats.md +29 -0
  232. package/dist/packages/mcp/assets/kb/entries/mint-freeze-authority.md +31 -0
  233. package/dist/packages/mcp/assets/kb/entries/narrative-lifecycle.md +32 -0
  234. package/dist/packages/mcp/assets/kb/entries/pump-bonding-curve.md +33 -0
  235. package/dist/packages/mcp/assets/kb/entries/pumpswap-migration.md +29 -0
  236. package/dist/packages/mcp/assets/kb/entries/sniping-and-snipers.md +29 -0
  237. package/dist/packages/mcp/assets/kb/entries/token2022-extensions-risk.md +32 -0
  238. package/dist/packages/mcp/assets/kb/entries/wash-trading-fake-volume.md +33 -0
  239. package/dist/packages/mcp/assets/skills/rug-check.md +52 -0
  240. package/dist/packages/mcp/assets/skills/token-dossier.md +57 -0
  241. package/dist/packages/mcp/assets/skills/trench-scan.md +40 -0
  242. package/dist/packages/mcp/bin/dexterm-mcp.mjs +16 -0
  243. package/dist/packages/mcp/src/dossier.js +78 -0
  244. package/dist/packages/mcp/src/firewall.js +21 -0
  245. package/dist/packages/mcp/src/index.js +22 -0
  246. package/dist/packages/mcp/src/server.js +849 -0
  247. package/dist/packages/mcp/src/user-content.js +434 -0
  248. package/dist/packages/packs/src/backtest.js +183 -0
  249. package/dist/packages/packs/src/digest.js +95 -0
  250. package/dist/packages/packs/src/firewall.js +57 -0
  251. package/dist/packages/packs/src/index.js +19 -0
  252. package/dist/packages/packs/src/larp-eval.js +100 -0
  253. package/dist/packages/packs/src/narrative.js +309 -0
  254. package/dist/packages/packs/src/reason.js +135 -0
  255. package/dist/packages/packs/src/rug-model.js +137 -0
  256. package/dist/packages/packs/src/schema.js +54 -0
  257. package/dist/packages/packs/src/token-dossier.js +174 -0
  258. package/dist/packages/packs/src/verdicts.js +17 -0
  259. package/dist/packages/packs/src/wallet-dossier.js +105 -0
  260. package/dist/packages/popup/src/config.js +71 -0
  261. package/dist/packages/popup/src/dock.js +271 -0
  262. package/dist/packages/popup/src/host.js +210 -0
  263. package/dist/packages/popup/src/index.js +12 -0
  264. package/dist/packages/popup/src/loopback.js +53 -0
  265. package/dist/packages/popup/src/open.js +183 -0
  266. package/dist/packages/popup/src/pages/bubbles-lib.js +160 -0
  267. package/dist/packages/popup/src/pages/bubbles.css +23 -0
  268. package/dist/packages/popup/src/pages/bubbles.html +36 -0
  269. package/dist/packages/popup/src/pages/bubbles.js +214 -0
  270. package/dist/packages/popup/src/pages/chart-lib.js +262 -0
  271. package/dist/packages/popup/src/pages/chart.css +50 -0
  272. package/dist/packages/popup/src/pages/chart.html +56 -0
  273. package/dist/packages/popup/src/pages/chart.js +554 -0
  274. package/dist/packages/popup/src/pages/common.css +34 -0
  275. package/dist/packages/popup/src/pages/common.js +187 -0
  276. package/dist/packages/popup/src/pages/feeds.html +20 -0
  277. package/dist/packages/popup/src/pages/feeds.js +77 -0
  278. package/dist/packages/popup/src/pages/narrative.html +20 -0
  279. package/dist/packages/popup/src/pages/narrative.js +61 -0
  280. package/dist/packages/popup/src/pages/plans.css +30 -0
  281. package/dist/packages/popup/src/pages/plans.html +26 -0
  282. package/dist/packages/popup/src/pages/plans.js +82 -0
  283. package/dist/packages/popup/src/pages/roster.css +41 -0
  284. package/dist/packages/popup/src/pages/roster.html +25 -0
  285. package/dist/packages/popup/src/pages/roster.js +75 -0
  286. package/dist/packages/popup/src/pages/token.html +20 -0
  287. package/dist/packages/popup/src/pages/token.js +112 -0
  288. package/dist/packages/popup/src/pages/widget-sdk.js +224 -0
  289. package/dist/packages/popup/src/pages/widget.css +5 -0
  290. package/dist/packages/popup/src/pages/widget.html +21 -0
  291. package/dist/packages/popup/src/pages/widget.js +85 -0
  292. package/dist/packages/popup/src/protocol.js +31 -0
  293. package/dist/packages/popup/src/selftest.js +156 -0
  294. package/dist/packages/popup/src/server.js +582 -0
  295. package/dist/packages/popup/src/sinkproxy.js +167 -0
  296. package/dist/packages/popup/src/widget-html.js +231 -0
  297. package/dist/packages/popup/vendor/lightweight-charts/LICENSE +201 -0
  298. package/dist/packages/popup/vendor/lightweight-charts/NOTICE +2 -0
  299. package/dist/packages/popup/vendor/lightweight-charts/README.md +30 -0
  300. package/dist/packages/popup/vendor/lightweight-charts/VERSION +1 -0
  301. package/dist/packages/popup/vendor/lightweight-charts/lightweight-charts.standalone.production.js +7 -0
  302. package/dist/packages/recorder/bin/dexterm-recorder.mjs +11 -0
  303. package/dist/packages/recorder/src/cli.js +113 -0
  304. package/dist/packages/recorder/src/export.js +71 -0
  305. package/dist/packages/recorder/src/index.js +20 -0
  306. package/dist/packages/recorder/src/labels.js +151 -0
  307. package/dist/packages/recorder/src/open.js +29 -0
  308. package/dist/packages/recorder/src/recorder.js +553 -0
  309. package/dist/packages/recorder/src/schedule.js +43 -0
  310. package/dist/packages/recorder/src/schema.js +135 -0
  311. package/dist/packages/recorder/src/snapshot.js +191 -0
  312. package/dist/packages/recorder/src/sweep.js +52 -0
  313. package/dist/packages/recorder/src/verdicts.js +52 -0
  314. package/dist/packages/tui/src/bubbles.js +415 -0
  315. package/dist/packages/tui/src/canvas.js +370 -0
  316. package/dist/packages/tui/src/chrome.js +85 -0
  317. package/dist/packages/tui/src/color.js +133 -0
  318. package/dist/packages/tui/src/flush.js +62 -0
  319. package/dist/packages/tui/src/fmt.js +188 -0
  320. package/dist/packages/tui/src/fx-trade.js +278 -0
  321. package/dist/packages/tui/src/fx.js +527 -0
  322. package/dist/packages/tui/src/images/blocks.js +141 -0
  323. package/dist/packages/tui/src/images/coin.js +22 -0
  324. package/dist/packages/tui/src/images/detect.js +140 -0
  325. package/dist/packages/tui/src/images/index.js +7 -0
  326. package/dist/packages/tui/src/images/layer.js +146 -0
  327. package/dist/packages/tui/src/images/pixels.js +157 -0
  328. package/dist/packages/tui/src/images/protocols.js +87 -0
  329. package/dist/packages/tui/src/index.js +15 -0
  330. package/dist/packages/tui/src/keymap.js +355 -0
  331. package/dist/packages/tui/src/layout.js +11 -0
  332. package/dist/packages/tui/src/sound.js +177 -0
  333. package/dist/packages/tui/src/theme.js +182 -0
  334. package/dist/packages/tui/src/widgets.js +598 -0
  335. package/dist/packages/voice/src/controller.js +395 -0
  336. package/dist/packages/voice/src/doctor.js +63 -0
  337. package/dist/packages/voice/src/engines.js +44 -0
  338. package/dist/packages/voice/src/index.js +16 -0
  339. package/dist/packages/voice/src/router.js +502 -0
  340. package/dist/packages/voice/src/stt/apple-helper.swift.js +103 -0
  341. package/dist/packages/voice/src/stt/apple.js +179 -0
  342. package/dist/packages/voice/src/stt/fake.js +34 -0
  343. package/dist/packages/voice/src/stt/whisper.js +102 -0
  344. package/dist/packages/voice/src/summary.js +44 -0
  345. package/dist/packages/voice/src/tts/fake.js +30 -0
  346. package/dist/packages/voice/src/tts/say.js +96 -0
  347. package/dist/packages/voice/src/types.js +3 -0
  348. package/package.json +42 -4
  349. package/README.md +0 -3
@@ -0,0 +1,241 @@
1
+ const F = (key, kind, label, get) => ({ key, kind, label, get });
2
+ /**
3
+ * Own-property lookup for the alias tables below. User text indexes these tables, and a plain
4
+ * `table[key]` would hand back inherited members for `__proto__`, `constructor`, `toString`, …
5
+ * (a function where a string was expected, which is how `__proto__:yes` once crashed the TUI).
6
+ */
7
+ function own(table, key) {
8
+ return Object.hasOwn(table, key) ? table[key] : undefined;
9
+ }
10
+ /** Canonical fields; FIELD_ALIASES maps the spellings people type onto them. */
11
+ export const FIELDS = [
12
+ F('vol24', 'usd', 'volume 24h', (r) => r.vol24),
13
+ F('vol6h', 'usd', 'volume 6h', (r) => r.vol6h),
14
+ F('vol1h', 'usd', 'volume 1h', (r) => r.vol1h),
15
+ F('vol5m', 'usd', 'volume 5m', (r) => r.vol5m),
16
+ F('liq', 'usd', 'liquidity', (r) => r.liq),
17
+ F('mcap', 'usd', 'market cap', (r) => r.mcap),
18
+ F('fdv', 'usd', 'FDV', (r) => r.fdv),
19
+ F('price', 'price', 'price', (r) => r.price),
20
+ F('age', 'age', 'age', (r) => r.ageMin),
21
+ F('holders', 'num', 'holders', (r) => r.holders),
22
+ F('buys', 'num', 'buys 24h', (r) => r.buys),
23
+ F('sells', 'num', 'sells 24h', (r) => r.sells),
24
+ F('org', 'num', 'organic score', (r) => r.org),
25
+ F('top10', 'pct', 'top-10 share', (r) => r.top10),
26
+ F('dev', 'pct', 'dev share', (r) => r.dev),
27
+ F('lplocked', 'pct', 'LP locked', (r) => r.lpLocked),
28
+ F('bundled', 'pct', 'bundled', (r) => r.bundled),
29
+ F('snipers', 'num', 'snipers', (r) => r.snipers),
30
+ F('m5', 'pct', 'change 5m', (r) => r.chg.m5),
31
+ F('h1', 'pct', 'change 1h', (r) => r.chg.h1),
32
+ F('h6', 'pct', 'change 6h', (r) => r.chg.h6),
33
+ F('h24', 'pct', 'change 24h', (r) => r.chg.h24),
34
+ ];
35
+ const FIELD_ALIASES = {
36
+ vol: 'vol24', volume: 'vol24', v24: 'vol24', v6h: 'vol6h', v1h: 'vol1h', v5m: 'vol5m',
37
+ liquidity: 'liq', lp: 'liq', marketcap: 'mcap', cap: 'mcap', mc: 'mcap',
38
+ holder: 'holders', h: 'holders', organic: 'org', score: 'org',
39
+ top: 'top10', 'top-10': 'top10', devpct: 'dev', locked: 'lplocked', lplock: 'lplocked', bundle: 'bundled', sniper: 'snipers',
40
+ chg5m: 'm5', '5m': 'm5', chg1h: 'h1', '1h': 'h1', chg6h: 'h6', '6h': 'h6', chg24: 'h24', chg24h: 'h24', '24h': 'h24', change: 'h24',
41
+ };
42
+ const FIELD_BY_KEY = new Map(FIELDS.map((f) => [f.key, f]));
43
+ export function fieldSpec(name) {
44
+ const k = name.toLowerCase();
45
+ return FIELD_BY_KEY.get(k) ?? FIELD_BY_KEY.get(own(FIELD_ALIASES, k) ?? '');
46
+ }
47
+ const TAG_ALIASES = { chain: 'chain', launchpad: 'launchpad', lp: 'launchpad', pad: 'launchpad', flag: 'flag', flags: 'flag', verified: 'verified', sus: 'sus' };
48
+ const LAUNCHPAD_ALIASES = {
49
+ pump: 'pump.fun', pumpfun: 'pump.fun', 'pump.fun': 'pump.fun', dbc: 'dbc', 'meteora-dbc': 'dbc',
50
+ raydium: 'raydium', ray: 'raydium', meteora: 'meteora', met: 'meteora', bonk: 'bonk', letsbonk: 'bonk', unknown: 'unknown',
51
+ };
52
+ const FLAG_ALIASES = {
53
+ mint: 'MINT', freeze: 'FREEZE', top10: 'TOP10', dev: 'DEV', bundle: 'BUNDLE', bundled: 'BUNDLE', lp: 'LP', clean: 'clean', none: 'clean',
54
+ };
55
+ const OPS = { '>': '>', '>=': '>=', '<': '<', '<=': '<=', '=': '=', '==': '=', '!=': '!=' };
56
+ export function tokenize(src) {
57
+ const out = [];
58
+ const re = /\s+|(>=|<=|!=|==|>|<|=)|(:)|(!)|([^\s<>=!:]+)/gy;
59
+ let m;
60
+ while ((m = re.exec(src)) !== null) {
61
+ if (m[0].length === 0)
62
+ break;
63
+ const at = m.index + 1;
64
+ if (m[1])
65
+ out.push({ kind: 'op', text: m[1], at });
66
+ else if (m[2])
67
+ out.push({ kind: 'colon', text: ':', at });
68
+ else if (m[3])
69
+ out.push({ kind: 'not', text: '!', at });
70
+ else if (m[4])
71
+ out.push({ kind: 'word', text: m[4], at });
72
+ }
73
+ return out;
74
+ }
75
+ const MONEY_UNITS = { k: 1e3, m: 1e6, b: 1e9 };
76
+ const AGE_UNITS = { s: 1 / 60, m: 1, min: 1, h: 60, hr: 60, d: 1440, w: 10080, mo: 43200 };
77
+ /** Parse a value for a field kind. Returns a number or an error string. */
78
+ export function parseValue(raw, kind) {
79
+ let s = raw.toLowerCase().replace(/,/g, '');
80
+ if (kind === 'usd' || kind === 'price')
81
+ s = s.replace(/^\$/, '');
82
+ if (kind === 'pct')
83
+ s = s.replace(/%$/, '');
84
+ const m = s.match(/^(-?\d+(?:\.\d+)?)([a-z]*)$/);
85
+ if (!m)
86
+ return `bad number "${raw}"`;
87
+ const n = Number(m[1]);
88
+ const unit = m[2];
89
+ if (kind === 'age') {
90
+ if (!unit)
91
+ return `"${raw}" needs an age unit (s, m, h, d, w, mo)`;
92
+ const mult = own(AGE_UNITS, unit);
93
+ if (mult === undefined)
94
+ return `unknown age unit "${unit}" in "${raw}" (use s, m, h, d, w, mo)`;
95
+ return n * mult;
96
+ }
97
+ if (!unit)
98
+ return n;
99
+ if (kind === 'pct')
100
+ return `unknown suffix "${unit}" in "${raw}" (percentages take an optional %)`;
101
+ const mult = own(MONEY_UNITS, unit);
102
+ if (mult === undefined)
103
+ return `unknown unit "${unit}" in "${raw}" (use K, M, B)`;
104
+ return n * mult;
105
+ }
106
+ export function parseFilter(src) {
107
+ const toks = tokenize(src);
108
+ const clauses = [];
109
+ const fail = (error, at) => ({ ok: false, error, at });
110
+ let i = 0;
111
+ while (i < toks.length) {
112
+ const t = toks[i];
113
+ const next = toks[i + 1];
114
+ if (t.kind === 'op')
115
+ return fail(`"${t.text}" has no field in front of it`, t.at);
116
+ if (t.kind === 'colon')
117
+ return fail(`":" has no tag in front of it (try chain:, launchpad:, flag:)`, t.at);
118
+ if (t.kind === 'not') {
119
+ if (!next || next.kind !== 'word' || toks[i + 2]?.kind !== 'colon')
120
+ return fail(`"!" must be followed by a tag like !launchpad:pump`, t.at);
121
+ const r = parseTag(toks, i + 1, true);
122
+ if (typeof r === 'string')
123
+ return fail(r, next.at);
124
+ clauses.push(r.clause);
125
+ i = r.next;
126
+ continue;
127
+ }
128
+ // word
129
+ if (next?.kind === 'op') {
130
+ const field = fieldSpec(t.text);
131
+ if (!field)
132
+ return fail(`unknown field "${t.text}" (try ${FIELDS.slice(0, 7).map((f) => f.key).join(', ')}, …)`, t.at);
133
+ const op = own(OPS, next.text);
134
+ if (!op)
135
+ return fail(`unknown operator "${next.text}"`, next.at);
136
+ const val = toks[i + 2];
137
+ if (!val || val.kind !== 'word')
138
+ return fail(`"${t.text} ${next.text}" needs a value`, val ? val.at : next.at + next.text.length);
139
+ const v = parseValue(val.text, field.kind);
140
+ if (typeof v === 'string')
141
+ return fail(`${v} for ${field.key}`, val.at);
142
+ clauses.push({ kind: 'cmp', field, op, value: v, raw: `${field.key} ${op} ${val.text}` });
143
+ i += 3;
144
+ continue;
145
+ }
146
+ if (next?.kind === 'colon') {
147
+ const r = parseTag(toks, i, false);
148
+ if (typeof r === 'string')
149
+ return fail(r, t.at);
150
+ clauses.push(r.clause);
151
+ i = r.next;
152
+ continue;
153
+ }
154
+ clauses.push({ kind: 'text', value: t.text.toLowerCase(), raw: t.text });
155
+ i += 1;
156
+ }
157
+ const preds = clauses.map(clausePredicate);
158
+ const predicate = (r) => preds.every((p) => p(r));
159
+ return { ok: true, filter: { text: clauses.map((c) => (c.kind === 'tag' && c.not ? '!' : '') + c.raw).join(' '), clauses, predicate } };
160
+ }
161
+ function parseTag(toks, i, not) {
162
+ const name = toks[i];
163
+ const val = toks[i + 2];
164
+ const tag = own(TAG_ALIASES, name.text.toLowerCase());
165
+ if (!tag)
166
+ return `unknown tag "${name.text}:" (chain, launchpad, flag, verified, sus)`;
167
+ if (!val || val.kind !== 'word')
168
+ return `"${name.text}:" needs a value`;
169
+ const v = val.text.toLowerCase();
170
+ let value = v;
171
+ if (tag === 'chain') {
172
+ if (v !== 'solana' && v !== 'sol')
173
+ return `chain:${val.text} is not supported (solana only)`;
174
+ value = 'solana';
175
+ }
176
+ else if (tag === 'launchpad') {
177
+ const lp = own(LAUNCHPAD_ALIASES, v);
178
+ if (!lp)
179
+ return `unknown launchpad "${val.text}" (pump, dbc, raydium, meteora, bonk)`;
180
+ value = lp;
181
+ }
182
+ else if (tag === 'flag') {
183
+ const f = own(FLAG_ALIASES, v);
184
+ if (!f)
185
+ return `unknown flag "${val.text}" (mint, freeze, top10, dev, bundle, lp, clean)`;
186
+ value = f;
187
+ }
188
+ else {
189
+ if (!['yes', 'no', 'true', 'false', '1', '0'].includes(v))
190
+ return `${tag}: takes yes or no`;
191
+ value = v === 'yes' || v === 'true' || v === '1' ? 'yes' : 'no';
192
+ }
193
+ return { clause: { kind: 'tag', tag, value, not, raw: `${tag}:${value}` }, next: i + 3 };
194
+ }
195
+ function cmp(a, op, b) {
196
+ switch (op) {
197
+ case '>': return a > b;
198
+ case '>=': return a >= b;
199
+ case '<': return a < b;
200
+ case '<=': return a <= b;
201
+ case '=': return a === b;
202
+ case '!=': return a !== b;
203
+ }
204
+ }
205
+ export function clausePredicate(c) {
206
+ if (c.kind === 'cmp') {
207
+ const { field, op, value } = c;
208
+ return (r) => { const v = field.get(r); return v !== null && v !== undefined && !Number.isNaN(v) && cmp(v, op, value); };
209
+ }
210
+ if (c.kind === 'text') {
211
+ const q = c.value;
212
+ return (r) => r.symbol.toLowerCase().includes(q) || r.name.toLowerCase().includes(q);
213
+ }
214
+ let p;
215
+ switch (c.tag) {
216
+ case 'chain':
217
+ p = () => true;
218
+ break;
219
+ case 'launchpad':
220
+ p = (r) => r.launchpad === c.value;
221
+ break;
222
+ case 'flag':
223
+ p = c.value === 'clean' ? (r) => r.flags.length === 0 : (r) => r.flags.includes(c.value);
224
+ break;
225
+ case 'verified':
226
+ p = (r) => r.verified === (c.value === 'yes');
227
+ break;
228
+ case 'sus':
229
+ p = (r) => r.sus === (c.value === 'yes');
230
+ break;
231
+ // parseTag only emits the tags above; a clause built by hand with a stray tag matches nothing
232
+ default: p = () => false;
233
+ }
234
+ return c.not ? (r) => !p(r) : p;
235
+ }
236
+ /** Apply a parsed filter (or none) to rows, keeping order. */
237
+ export function applyFilter(rows, f) {
238
+ if (!f || f.clauses.length === 0)
239
+ return rows.slice();
240
+ return rows.filter(f.predicate);
241
+ }
@@ -0,0 +1,80 @@
1
+ import { gate, COLUMN_SOURCES, FEATURE_SOURCES } from '../../../data/src/index.js';
2
+ import { parseFilter } from "./filter.js";
3
+ /** Jupiter's "runner" criteria (SPEC §4.2): mcap ≥ $1M, liq ≥ $100K, holders ≥ 1K, organic ≥ 75 within 24h of launch. */
4
+ export const RUNNER_FILTER = 'mcap >= 1M liq >= 100K holders >= 1000 org >= 75 age < 24h';
5
+ /**
6
+ * Trench feed (task-1311, QA M4): what a Solana memecoin trader wants first — fresh pump.fun and
7
+ * young memecoins over a sane liquidity floor. `verified:no` drops the Jupiter-verified set (blue
8
+ * chips, wrapped assets, tokenized stocks and stablecoins), which stay reachable under Trending /
9
+ * Top traded / All. `live` folds the PumpPortal new-pairs stream in so fresh launches surface.
10
+ */
11
+ export const TRENCH_FILTER = 'verified:no liq >= 5K';
12
+ /**
13
+ * Graduating — keyless approximation (task-1356 Tier 6). The real signal is bonding-curve progress
14
+ * (COLUMN_SOURCES 'bonding', D-08), which needs a keyed launch source. Keyless we approximate "a
15
+ * bonding-curve launch closing on graduation" by the mcap band a pump.fun/bonk/dbc coin sits in as it
16
+ * fills (roughly $30K–$120K), still young, with a real-ish pool forming. It is an APPROXIMATION, not the
17
+ * true graduation %, and the UI says so; when 'bonding' is served the preset swaps to the real column.
18
+ */
19
+ export const GRADUATING_APPROX_FILTER = 'launchpad:pump mcap >= 30K mcap <= 120K liq >= 4K age < 24h';
20
+ export const PRESETS = [
21
+ { id: 'trench', label: 'Trench', feeds: ['trending', 'organic', 'recent'], baked: TRENCH_FILTER, live: true },
22
+ { id: 'trending', label: 'Trending', feeds: ['trending'] },
23
+ { id: 'runners', label: 'Runners', feeds: ['trending', 'organic', 'recent'], baked: RUNNER_FILTER },
24
+ { id: 'organic', label: 'Organic', feeds: ['organic'] },
25
+ { id: 'graduating', label: 'Graduating', feeds: ['recent'], baked: GRADUATING_APPROX_FILTER, live: true, gate: { id: 'bonding', of: 'column' }, approx: 'approx' },
26
+ { id: 'smart', label: 'Smart money', feeds: ['trending'], gate: { id: 'top-traders', of: 'feature' } },
27
+ // task-1303 feed switcher: the remaining Jupiter / DexScreener nets, keyless and always available.
28
+ { id: 'traded', label: 'Top traded', feeds: ['traded'] },
29
+ { id: 'new', label: 'New', feeds: ['recent'], live: true },
30
+ { id: 'boosted', label: 'Boosted', feeds: ['boosted'] },
31
+ { id: 'all', label: 'All', feeds: ['trending', 'organic', 'traded', 'recent', 'boosted'], live: true },
32
+ ];
33
+ export function presetSpec(id) {
34
+ return PRESETS.find((p) => p.id === id || p.label.toLowerCase() === id.toLowerCase());
35
+ }
36
+ const CAPTION = { 'needs-key': 'needs key', degraded: 'degraded', disabled: 'off', ok: '' };
37
+ /** Availability of every preset. Without a registry (fixtures mode) everything is on for the demo. */
38
+ export function presetViews(health) {
39
+ return PRESETS.map((p) => {
40
+ if (!health)
41
+ return { id: p.id, label: p.label, available: true, caption: p.approx ? p.approx : null, approx: !!p.approx };
42
+ if (p.pending)
43
+ return { id: p.id, label: p.label, available: false, caption: p.pending };
44
+ // An approximation preset is served keyless by its baked filter over keyless fields, so it is always
45
+ // selectable. The dexterm filter grammar has no real bonding-% field at this tier, so the preset is
46
+ // ALWAYS the approximation (the `gate` only marks the future upgrade to a real column); the 'approx'
47
+ // caption therefore stays visible even when available, so the UI never implies the keyed signal.
48
+ if (p.approx)
49
+ return { id: p.id, label: p.label, available: true, caption: p.approx, approx: true };
50
+ if (!p.gate)
51
+ return { id: p.id, label: p.label, available: true, caption: null };
52
+ const spec = p.gate.of === 'column' ? COLUMN_SOURCES[p.gate.id] : FEATURE_SOURCES[p.gate.id];
53
+ const g = gate(health, p.gate.id, spec);
54
+ return { id: p.id, label: p.label, available: g.available, caption: g.available ? null : CAPTION[g.state] || g.state };
55
+ });
56
+ }
57
+ const bakedCache = new Map();
58
+ export function bakedFilter(p) {
59
+ if (!p.baked)
60
+ return null;
61
+ let f = bakedCache.get(p.baked);
62
+ if (!f) {
63
+ const r = parseFilter(p.baked);
64
+ if (!r.ok)
65
+ throw new Error(`preset ${p.id} has a bad baked filter: ${r.error}`);
66
+ f = r.filter;
67
+ bakedCache.set(p.baked, f);
68
+ }
69
+ return f;
70
+ }
71
+ /** Next/previous selectable preset from `cur` (wraps; disabled presets are skipped). */
72
+ export function cyclePreset(views, cur, step) {
73
+ const i = Math.max(0, views.findIndex((v) => v.id === cur));
74
+ for (let k = 1; k <= views.length; k++) {
75
+ const v = views[(i + step * k + views.length * k) % views.length];
76
+ if (v.available)
77
+ return v.id;
78
+ }
79
+ return cur;
80
+ }
@@ -0,0 +1,146 @@
1
+ /** How many placeholder rows the screener shows while the first live fetch is in flight (task-1323). */
2
+ export const SKELETON_ROWS = 12;
3
+ /** Placeholder rows for the empty-feed first paint (task-1323): shimmer bars, no data, no mint. */
4
+ export function skeletonRows(n = SKELETON_ROWS) {
5
+ const bars = ['░░░░░', '░░░░', '░░░░░░', '░░░'];
6
+ return Array.from({ length: n }, (_, i) => ({
7
+ mint: '', symbol: bars[i % bars.length], name: i === 0 ? 'loading feed…' : '', launchpad: 'unknown',
8
+ price: null, mcap: null, mcapFlagged: false, fdv: null, liq: null, vol24: null, vol6h: null, vol1h: null, vol5m: null,
9
+ ageMin: null, holders: null, holders24: null, holdersChgPct: null, chg: { m5: null, h1: null, h6: null, h24: null },
10
+ spark: undefined, buys: null, sells: null, org: null, top10: null, dev: null, lpLocked: null, mintAuth: null, freezeAuth: null,
11
+ snipers: null, bundled: null, flags: [], verified: false, sus: false, bonding: null, pair: null, sources: [], stale: true,
12
+ fetchedAt: null, aiNote: null, image: null, skeleton: true,
13
+ }));
14
+ }
15
+ /** Risk flags from the audit fields, same thresholds as the prototype fixtures. */
16
+ export function deriveFlags(r) {
17
+ const flags = [];
18
+ if (r.mintAuth)
19
+ flags.push('MINT');
20
+ if (r.freezeAuth)
21
+ flags.push('FREEZE');
22
+ if (r.top10 !== null && r.top10 > 35)
23
+ flags.push('TOP10');
24
+ if (r.dev !== null && r.dev > 5)
25
+ flags.push('DEV');
26
+ if (r.bundled !== null && r.bundled > 15)
27
+ flags.push('BUNDLE');
28
+ if (r.lpLocked !== null && r.lpLocked < 50)
29
+ flags.push('LP');
30
+ return flags;
31
+ }
32
+ export function fromFixture(tk) {
33
+ const verdict = tk.flags.length >= 2 ? 'avoid: holder structure fragile' : tk.org >= 60 ? 'watch: organic flow, decent spread' : 'speculative: thin, mostly bots';
34
+ return {
35
+ mint: tk.mint, symbol: tk.symbol, name: tk.name, launchpad: tk.launchpad,
36
+ price: tk.price, mcap: tk.mcap, mcapFlagged: false, fdv: tk.fdv, liq: tk.liq,
37
+ vol24: tk.vol24, vol6h: tk.vol24 * 0.3, vol1h: tk.vol24 * 0.06, vol5m: tk.vol24 * 0.005,
38
+ ageMin: tk.ageMin, holders: tk.holders, holders24: tk.holders24, holdersChgPct: null,
39
+ chg: { ...tk.chg }, spark: tk.spark, buys: tk.buys, sells: tk.sells, org: tk.org,
40
+ top10: tk.top10, dev: tk.dev, lpLocked: tk.lpLocked, mintAuth: tk.mintAuth, freezeAuth: tk.freezeAuth,
41
+ snipers: tk.snipers, bundled: tk.bundled, flags: tk.flags, verified: false, sus: false, bonding: tk.bonding, pair: null,
42
+ sources: ['fixture'], stale: false, fetchedAt: null, aiNote: verdict, image: null, fixture: tk,
43
+ };
44
+ }
45
+ /**
46
+ * Terminal safety only (CL-06 firewalling is D-12): drop control, bidi and zero-width characters,
47
+ * replace glyphs the cell canvas cannot place in one column (East Asian wide, emoji) with ▯, cap length.
48
+ */
49
+ export function cleanText(s, max = 48) {
50
+ if (!s)
51
+ return '';
52
+ let out = '';
53
+ for (const ch of s) {
54
+ const cp = ch.codePointAt(0);
55
+ if (cp < 0x20 || (cp >= 0x7f && cp <= 0x9f))
56
+ continue;
57
+ if (cp === 0x200b || cp === 0x200c || cp === 0x200d || cp === 0x2060 || cp === 0xfeff)
58
+ continue; // zero-width
59
+ if ((cp >= 0x200e && cp <= 0x200f) || (cp >= 0x202a && cp <= 0x202e) || (cp >= 0x2066 && cp <= 0x2069))
60
+ continue; // bidi
61
+ if ((cp >= 0x0300 && cp <= 0x036f) || (cp >= 0xfe00 && cp <= 0xfe0f) || (cp >= 0x1f3fb && cp <= 0x1f3ff))
62
+ continue; // combining / variation / skin tone
63
+ out += isWide(cp) ? '▯' : ch;
64
+ if (out.length >= max)
65
+ break;
66
+ }
67
+ return out;
68
+ }
69
+ /** Code points that terminals render two columns wide (EAW W/F ranges) or as emoji. */
70
+ export function isWide(cp) {
71
+ return (cp >= 0x1100 && cp <= 0x115f) || (cp >= 0x2e80 && cp <= 0x303e) || (cp >= 0x3041 && cp <= 0x33ff)
72
+ || (cp >= 0x3400 && cp <= 0x4dbf) || (cp >= 0x4e00 && cp <= 0x9fff) || (cp >= 0xa000 && cp <= 0xa4cf)
73
+ || (cp >= 0xac00 && cp <= 0xd7a3) || (cp >= 0xf900 && cp <= 0xfaff) || (cp >= 0xfe30 && cp <= 0xfe4f)
74
+ || (cp >= 0xff00 && cp <= 0xff60) || (cp >= 0xffe0 && cp <= 0xffe6) || (cp >= 0x1f000 && cp <= 0x1faff)
75
+ || (cp >= 0x20000 && cp <= 0x3fffd);
76
+ }
77
+ /**
78
+ * The 24h trend implied by the window deltas: the price 24h/6h/1h/5m ago and now, interpolated
79
+ * on a linear time axis. Not a tick history (that is D-17's history table); it is the path the
80
+ * reported changes describe, which is what the 24H TREND column claims.
81
+ */
82
+ export function impliedSpark(price, chg, cells = 16) {
83
+ if (price === null || !(price > 0))
84
+ return undefined;
85
+ const anchors = [];
86
+ const at = (min, c) => { if (c !== null && c > -100)
87
+ anchors.push([min, price / (1 + c / 100)]); };
88
+ at(1440, chg.h24);
89
+ at(360, chg.h6);
90
+ at(60, chg.h1);
91
+ at(5, chg.m5);
92
+ anchors.push([0, price]);
93
+ if (anchors.length < 2)
94
+ return undefined;
95
+ const span = anchors[0][0];
96
+ const out = [];
97
+ for (let i = 0; i < cells; i++) {
98
+ const t = span - (i / (cells - 1)) * span; // minutes ago
99
+ let k = 0;
100
+ while (k < anchors.length - 2 && anchors[k + 1][0] >= t)
101
+ k++;
102
+ const [t0, p0] = anchors[k], [t1, p1] = anchors[k + 1];
103
+ const f = t0 === t1 ? 1 : (t0 - t) / (t0 - t1);
104
+ out.push(p0 + (p1 - p0) * Math.min(1, Math.max(0, f)));
105
+ }
106
+ return out;
107
+ }
108
+ export function fromSnapshot(r, nowMs) {
109
+ const { token, pair, stats, audit } = r;
110
+ const createdAt = token.createdAt ?? pair.createdAt;
111
+ const chg = { m5: pair.priceChange.m5, h1: pair.priceChange.h1, h6: pair.priceChange.h6, h24: pair.priceChange.h24 };
112
+ const price = pair.priceUsd > 0 ? pair.priceUsd : r.price?.usdPrice ?? null;
113
+ const launchpad = token.launchpad;
114
+ // SPEC §3.6: pump.fun / PumpSwap pools burn LP by design; RugCheck (D-06) fills the rest.
115
+ const lpLocked = audit?.lpLockedPct ?? (launchpad === 'pump.fun' ? 100 : null);
116
+ const base = {
117
+ mintAuth: audit?.mintAuthority ?? null,
118
+ freezeAuth: audit?.freezeAuthority ?? null,
119
+ top10: audit?.top10Pct ?? null,
120
+ dev: audit?.devPct ?? null,
121
+ bundled: null,
122
+ lpLocked,
123
+ };
124
+ const fetchedAt = Math.max(pair.fetchedAt, token.fetchedAt, stats?.fetchedAt ?? 0);
125
+ // MCAP: the D-06 cross-source rule (larger of DexScreener/Jupiter when they agree within 5x, else
126
+ // DexScreener's number flagged); FDV as the last fallback.
127
+ const mcap = r.mcap ? r.mcap.value : pair.mcap ?? pair.fdv;
128
+ return {
129
+ mint: r.mint, symbol: cleanText(token.symbol, 12) || '?', name: cleanText(token.name, 40), launchpad,
130
+ price,
131
+ mcap, mcapFlagged: r.mcap?.flagged ?? false, fdv: pair.fdv, liq: pair.liquidityUsd,
132
+ vol24: pair.volume.h24, vol6h: pair.volume.h6, vol1h: pair.volume.h1, vol5m: pair.volume.m5,
133
+ ageMin: createdAt ? Math.max(0, (nowMs - createdAt) / 60000) : null,
134
+ // Jupiter's holderChange is a percentage, not a count: keep the two apart so the UI never shows "+-1.02".
135
+ holders: stats?.holderCount ?? null, holders24: null, holdersChgPct: stats?.holders24hDelta ?? null,
136
+ chg, spark: r.sparkline ?? impliedSpark(price, chg),
137
+ buys: pair.txns.h24?.buys ?? null, sells: pair.txns.h24?.sells ?? null,
138
+ org: stats?.organicScore ?? null,
139
+ ...base,
140
+ snipers: null,
141
+ flags: deriveFlags(base),
142
+ verified: token.verified, sus: token.sus, bonding: null, pair: pair.pairAddress || null,
143
+ sources: r.sources, stale: r.stale, fetchedAt: fetchedAt || null, aiNote: null,
144
+ image: typeof token.image === 'string' && /^https?:\/\//i.test(token.image) ? token.image : null,
145
+ };
146
+ }
@@ -0,0 +1,50 @@
1
+ // task-1361: p(rug) for a screener row, from the pre-registered H1-v0.1 heuristic in @dexterm/packs
2
+ // (rug-model.ts). Zero model cost, zero requests: it reads the numbers already on the row. It is a MODEL
3
+ // OUTPUT, not a fact: every surface that shows it names the model and how old the row's data is.
4
+ // H1 is a hand-written additive heuristic (not learned); a trained v1 only replaces it once the recorder
5
+ // has enough labelled outcomes and `dexterm backtest` says it is better (AI-SUITE-DESIGN §4.3).
6
+ import { H1_FLAG_THRESHOLD, h1Model } from '../../../packs/src/index.js';
7
+ /** The model the screener column scores with. */
8
+ export const SCREENER_RUG_MODEL = h1Model();
9
+ /** Short label for captions / the sort menu: `H1-v0.1 heuristic`. */
10
+ export const RUG_MODEL_LABEL = `${SCREENER_RUG_MODEL.id} ${SCREENER_RUG_MODEL.kind}`;
11
+ /** The canonical feature vector from what the screener row carries (nulls where no source said). */
12
+ export function rugFeaturesFromRow(r) {
13
+ return {
14
+ liquidity_usd: r.liq,
15
+ mcap: r.mcap,
16
+ age_sec: r.ageMin === null ? null : r.ageMin * 60,
17
+ top_holders_pct: r.top10,
18
+ dev_pct: r.dev,
19
+ organic_score: r.org,
20
+ holder_count: r.holders,
21
+ mint_auth_enabled: r.mintAuth,
22
+ freeze_auth_enabled: r.freezeAuth,
23
+ has_pool: r.liq === null ? null : r.liq > 0,
24
+ is_verified: null,
25
+ };
26
+ }
27
+ /**
28
+ * p(rug) in [0.02, 0.98] for a row with market data, else null: a skeleton row, or one where no source
29
+ * has reported liquidity, cap, age or an audit yet (the heuristic would only be scoring its own defaults).
30
+ */
31
+ export function rugP(r) {
32
+ if (r.skeleton)
33
+ return null;
34
+ if (r.liq === null && r.mcap === null && r.ageMin === null && r.mintAuth === null && r.freezeAuth === null)
35
+ return null;
36
+ return SCREENER_RUG_MODEL.score(rugFeaturesFromRow(r));
37
+ }
38
+ /** `0.45` — two decimals, never a percent (it reads as a probability, which is what the backtest measures). */
39
+ export function fmtRugP(p) {
40
+ return p.toFixed(2);
41
+ }
42
+ /** The model's own flag line: at or above it the heuristic calls the token a likely rug. */
43
+ export const RUG_FLAG_THRESHOLD = H1_FLAG_THRESHOLD;
44
+ /** `42s` / `4m` / `3h` since the row's data was fetched; null when the row never recorded a fetch time. */
45
+ export function rugDataAge(r, now) {
46
+ if (r.fetchedAt === null || !Number.isFinite(r.fetchedAt))
47
+ return null;
48
+ const s = Math.max(0, Math.round((now - r.fetchedAt) / 1000));
49
+ return s < 60 ? `${s}s` : s < 3600 ? `${Math.floor(s / 60)}m` : s < 86_400 ? `${Math.floor(s / 3600)}h` : `${Math.floor(s / 86_400)}d`;
50
+ }
@@ -0,0 +1,52 @@
1
+ import { RUG_MODEL_LABEL, rugP } from "./rug.js";
2
+ export const SORT_KEYS = [
3
+ { key: 'vol', label: 'vol 24h', defaultDir: 'desc', get: (r) => r.vol24 },
4
+ { key: 'liq', label: 'liquidity', defaultDir: 'desc', get: (r) => r.liq },
5
+ { key: 'mcap', label: 'mcap', defaultDir: 'desc', get: (r) => r.mcap },
6
+ { key: 'price', label: 'price', defaultDir: 'desc', get: (r) => r.price },
7
+ { key: 'age', label: 'age', defaultDir: 'asc', get: (r) => r.ageMin },
8
+ { key: 'm5', label: '5m change', defaultDir: 'desc', get: (r) => r.chg.m5 },
9
+ { key: 'h1', label: '1h change', defaultDir: 'desc', get: (r) => r.chg.h1 },
10
+ { key: 'h6', label: '6h change', defaultDir: 'desc', get: (r) => r.chg.h6 },
11
+ { key: 'h24', label: '24h change', defaultDir: 'desc', get: (r) => r.chg.h24 },
12
+ { key: 'holders', label: 'holders', defaultDir: 'desc', get: (r) => r.holders },
13
+ { key: 'org', label: 'organic score', defaultDir: 'desc', get: (r) => r.org },
14
+ { key: 'top10', label: 'top-10 share', defaultDir: 'asc', get: (r) => r.top10 },
15
+ // task-1361: the H1-v0.1 heuristic's p(rug) (a model output, see screener/rug.ts); safest first.
16
+ { key: 'rug', label: `p(rug) ${RUG_MODEL_LABEL}`, defaultDir: 'asc', get: (r) => rugP(r) },
17
+ ];
18
+ export const DEFAULT_SORT = { key: 'vol', dir: 'desc' };
19
+ export function sortSpec(key) {
20
+ return SORT_KEYS.find((s) => s.key === key);
21
+ }
22
+ /** Parse `--sort key[:asc|desc]`. */
23
+ export function parseSortArg(s) {
24
+ const [k, d] = s.split(':');
25
+ const spec = sortSpec(k);
26
+ if (!spec)
27
+ throw new Error(`unknown sort key ${k} (expected ${SORT_KEYS.map((x) => x.key).join('|')})`);
28
+ if (d !== undefined && d !== 'asc' && d !== 'desc')
29
+ throw new Error(`sort direction must be asc or desc, got ${d}`);
30
+ return { key: spec.key, dir: d ?? spec.defaultDir };
31
+ }
32
+ /** Stable sort; rows without the value go last whichever direction is chosen. */
33
+ export function sortRows(rows, key, dir) {
34
+ const spec = sortSpec(key);
35
+ if (!spec)
36
+ return rows.slice();
37
+ const sign = dir === 'desc' ? -1 : 1;
38
+ return rows
39
+ .map((r, i) => ({ r, i, v: spec.get(r) }))
40
+ .sort((a, b) => {
41
+ const an = a.v === null || a.v === undefined || Number.isNaN(a.v);
42
+ const bn = b.v === null || b.v === undefined || Number.isNaN(b.v);
43
+ if (an && bn)
44
+ return a.i - b.i;
45
+ if (an)
46
+ return 1;
47
+ if (bn)
48
+ return -1;
49
+ return a.v === b.v ? a.i - b.i : (a.v - b.v) * sign;
50
+ })
51
+ .map((x) => x.r);
52
+ }